imrishabh18/nema-23-stepper-controller
A 57 mm four-layer RP2040 NEMA23 stepper-controller PCB with USB‑C/USB‑PD power input, protected motor rail, DRV8452 dual-winding driver, bidirectional current sensing, temperature/enable interlock, optional magnetic encoder, RGB LED and buzzer status indicators, and motor/test connectors.
- Version
- 1.2.3
- License
- unset
- Stars
- 0
scripts/render-previews.mjs
import {readFileSync,writeFileSync,mkdirSync} from 'node:fs'
import {convertCircuitJsonToSchematicSvg,convertCircuitJsonToStackedSchematicSheetsSvg,convertCircuitJsonToPcbSvg} from 'circuit-to-svg'
import {Resvg} from '@resvg/resvg-js'
import sharp from 'sharp'
const c=JSON.parse(readFileSync('dist/index/circuit.json','utf8'))
mkdirSync('previews',{recursive:true})
function save(name,svg){writeFileSync(`previews/${name}.svg`,svg);writeFileSync(`previews/${name}.png`,new Resvg(svg).render().asPng())}
// Preserve labels that extend slightly beyond the renderer's nominal viewport.
function sheetMargin(svg){return svg.replace('<svg ', '<svg viewBox="-60 -60 1920 1370" ').replace(/(<svg[^>]*>)/,'$1<rect x="-60" y="-60" width="1920" height="1370" fill="rgb(245,241,237)"/>')}
save('pcb',convertCircuitJsonToPcbSvg(c,{width:1600,height:1600}))
// Fabrication notes are assembly annotations, not printed silkscreen.
const boardArtwork=c.filter(e=>!e.type.startsWith('pcb_fabrication_note'))
save('pcb-top',convertCircuitJsonToPcbSvg(boardArtwork,{width:1600,height:1600,layer:'top',showSolderMask:true}))
// The SVG layer renderer uses top-board coordinates. Flip the entire bottom
// artwork for a physical underside view; core already mirrors bottom text.
const bottomSvg=convertCircuitJsonToPcbSvg(boardArtwork,{width:1600,height:1600,layer:'bottom',showSolderMask:true})
save('pcb-bottom',bottomSvg.replace(/(<svg[^>]*>)/,'$1<g transform="translate(1600 0) scale(-1 1)">').replace('</svg>','</g></svg>'))
for(const sheet of c.filter(e=>e.type==='schematic_sheet')){
save(sheet.name,sheetMargin(convertCircuitJsonToSchematicSvg(c,{schematicSheetId:sheet.schematic_sheet_id,width:1800,height:1250})))
}
for(const [name,file] of [['J_USB','usb-logic-detail'],['J_USB_POWER','usb-power-detail']]){
const source=c.find(e=>e.type==='source_component'&&e.name===name)
const part=c.find(e=>e.type==='schematic_component'&&e.source_component_id===source.source_component_id)
const svg=convertCircuitJsonToSchematicSvg(c,{schematicSheetId:part.schematic_sheet_id,width:1800,height:1250})
const [a,, ,d,e,f]=svg.match(/data-real-to-screen-transform="matrix\(([^)]+)\)"/)[1].split(',').map(Number)
const x=a*(part.center.x-part.size.width/2)+e-20,y=d*(part.center.y+part.size.height/2)+f-40
const w=a*part.size.width+180,h=Math.abs(d)*part.size.height+130
const detail=svg.replace(/<svg[^>]*>/,`<svg xmlns="http://www.w3.org/2000/svg" width="900" height="${Math.round(900*h/w)}" viewBox="0 0 ${w} ${h}"><rect width="${w}" height="${h}" fill="rgb(245,241,237)"/><g transform="translate(${-x} ${-y})">`).replace('</svg>','</g></svg>')
// librsvg does not implement these SVG baseline modes consistently.
.replace(/dominant-baseline="(hanging|central|middle)"/g,(_,mode)=>`dominant-baseline="auto" dy="${mode==='hanging'?0.8:0.35}em"`)
writeFileSync(`previews/${file}.svg`,detail)
// librsvg avoids the bundled resvg crash on clipped off-viewport elements.
await sharp(Buffer.from(detail)).png().toFile(`previews/${file}.png`)
}
writeFileSync('previews/all-schematics.svg',convertCircuitJsonToStackedSchematicSheetsSvg(c,{width:1800,height:1250}))
writeFileSync('previews/mounting-template.png',new Resvg(readFileSync('mounting-template.svg','utf8'),{fitTo:{mode:'width',value:1000}}).render().asPng())
console.log('Rendered PCB, all schematic sheets and mounting template.')