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/reimport-symbols.mjs
import {readFileSync,writeFileSync,mkdirSync,existsSync,readdirSync} from 'node:fs'
import {dirname,resolve} from 'node:path'
import {fileURLToPath,pathToFileURL} from 'node:url'
import {createHash} from 'node:crypto'
import {fetchEasyEDAComponent,EasyEdaJsonSchema,convertEasyEdaJsonToCircuitJson} from 'easyeda'
import {Circuit} from 'tscircuit'
import {createElement} from 'react'
// Reproduce supplier artwork except ICs and standard USB-C connectors. These
// use native tscircuit schematic styling, so their artwork is not imported.
// Expose the converter generator through a disposable copy; dependencies stay intact.
const entry=fileURLToPath(import.meta.resolve('easyeda'))
const pkg=JSON.parse(readFileSync(resolve(dirname(entry),'../package.json'),'utf8'))
if(pkg.version!=='0.0.364')throw Error('Review converter adapter for this version')
const source=readFileSync(entry,'utf8')
if(!source.includes('var generateSymbolTsx ='))throw Error('Converter symbol generator changed')
mkdirSync('.tscircuit',{recursive:true})
const adapter=resolve('.tscircuit/easyeda-symbol-generator.mjs')
writeFileSync(adapter,source+'\nexport { generateSymbolTsx };\n')
const {generateSymbolTsx}=await import(pathToFileURL(adapter).href)
mkdirSync('engineering/easyeda',{recursive:true})
mkdirSync('imports/symbols',{recursive:true})
const parts={}
for(const file of readdirSync('imports').filter(f=>f.endsWith('.tsx'))){
const text=readFileSync(`imports/${file}`,'utf8')
const id=text.match(/"(C\d+)"/)[1]
parts[file.replace('.tsx','')]=id
}
Object.assign(parts,{RP2040:'C2040',TYPE_C_16PIN_2MD_073_:'C2765186',W25Q16JVUXIQ:'C2843335',AP2112K_3_3TRG1:'C23380830',X322512MSB4SI:'C20625731',SKRPACE010:'C139797',XL_1608SURC_06:'C965799'})
const {nativeChipSymbols,nativeConnectorSymbols=[]}=JSON.parse(readFileSync('engineering/schematic-style.json'))
for(const name of [...nativeChipSymbols,...nativeConnectorSymbols])delete parts[name]
const manifest={converter:pkg.version,source:'https://github.com/tscircuit/easyeda-converter',parts:{}}
const compatibility=JSON.parse(readFileSync('engineering/easyeda/compatibility.json','utf8'))
const queue=Object.entries(parts)
await Promise.all(Array.from({length:4},async()=>{
while(queue.length){
const [name,id]=queue.shift();const rawPath=`engineering/easyeda/${id}.raweasy.json`
const raw=existsSync(rawPath)?JSON.parse(readFileSync(rawPath,'utf8')):await fetchEasyEDAComponent(id,{includeModelMetadata:false})
writeFileSync(rawPath,JSON.stringify(raw,null,2)+'\n')
const parsed=EasyEdaJsonSchema.parse(raw)
const circuit=convertEasyEdaJsonToCircuitJson(parsed,{shouldRecenter:true,showDesignator:true})
let symbol=generateSymbolTsx(parsed,circuit)
if(!symbol)throw Error(`No EasyEDA symbol for ${name} (${id})`)
symbol=symbol.replace(/(<port\s+)name="[^"]*"(\s+pinNumber=\{(\d+)\})/g,'$1name="pin$3"$2')
// The retained programming USB footprint splits the two shell contacts into
// four stakes. Map the supplier's signal pins to those physical pad numbers.
if(name==='TYPE_C_16PIN_2MD_073_'){
symbol=symbol.replace(/name="pin(\d+)" pinNumber=\{\d+\}/g,(_,p)=>{
const n=Number(p)>=15?Number(p)+2:Number(p)
return `name="pin${n}" pinNumber={${n}}`
})
symbol=symbol.replace('</symbol>',` <port name="pin15" pinNumber={15} aliases={[]} direction="right" schX={0.7} schY={0.5} schStemLength={0.2} />\n <port name="pin16" pinNumber={16} aliases={[]} direction="right" schX={0.7} schY={-0.5} schStemLength={0.2} />\n</symbol>`)
}
const primitiveAliases={
X322512MSB4SI:{1:['X1'],2:['GND1'],3:['X2'],4:['GND2']},
B5819W_SL:{1:['K','cathode','neg'],2:['A','anode','pos']},
XL_1608SURC_06:{1:['cathode','neg'],2:['anode','pos']},
WSLP1206R0100FEA:{1:['pos','left'],2:['neg','right']},
}
symbol=symbol.replace(/pinNumber=\{(\d+)\} aliases=\{(\[[^\]]*\])\}/g,(_,pin,aliases)=>
`pinNumber={${pin}} aliases={${JSON.stringify([...new Set([...(compatibility[name]?.pinAliases[pin]??JSON.parse(aliases)),...(primitiveAliases[name]?.[pin]??[])])])}}`)
// EasyEDA keeps the designator outside its shape list. Add the dynamic
// reference above the converted drawing, in local symbol coordinates.
const ys=[...symbol.matchAll(/schY=\{(-?[\d.]+)\}/g)].map(m=>Number(m[1]))
const titleY=Math.max(0,...ys)+0.6
const valueLabel=name==='WSLP1206R0100FEA'?'10 mΩ / 1 W':name==='X322512MSB4SI'?'12 MHz':name.replaceAll('_','-')
symbol=symbol.replace('</symbol>',` <schematictext text="{REF}" schY={${titleY+0.25}} fontSize={0.22} anchor="center" />\n <schematictext text=${JSON.stringify(valueLabel)} schY={${titleY}} fontSize={0.16} anchor="center" />\n</symbol>`)
const output=`// Generated from EasyEDA ${id} with easyeda-converter ${pkg.version}.\n// Coordinates define symbol artwork, not circuit component placement.\nexport const ${name}Symbol = () => (\n${symbol}\n)\n`
writeFileSync(`imports/symbols/${name}.tsx`,output)
// Circuit JSON symbols initialize their ports synchronously in core. This
// avoids deferred React symbol ports breaking cross-group selectors and the
// built-in pushbutton constructor. The artwork is still converter-generated.
const { [name+'Symbol']: artwork } = await import(pathToFileURL(resolve(`imports/symbols/${name}.tsx`)).href)
const rendering = new Circuit()
rendering.pcbDisabled = true
rendering.add(createElement('board',{routingDisabled:true,schAutoLayoutEnabled:false},
createElement('chip',{name:'SYMBOL',symbol:artwork()})))
await rendering.renderUntilSettled()
const elements=rendering.getCircuitJson()
if(elements.some(e=>e.type.endsWith('_error')))throw Error(`Symbol rendering failed: ${name}`)
const symbolJson=elements.filter(e=>['source_component','source_port','schematic_symbol','schematic_component','schematic_port','schematic_path','schematic_line','schematic_rect','schematic_circle','schematic_arc','schematic_text'].includes(e.type))
const container=symbolJson.find(e=>e.type==='schematic_symbol')
const component=symbolJson.find(e=>e.type==='schematic_component')
// Core's Circuit JSON inflator reconstructs port positions from linked box
// definitions. This flag describes the import graph; the rendered component
// uses its supplied drawing, including nonrectangular EasyEDA symbols.
component.schematic_symbol_id=container.schematic_symbol_id
component.is_box_with_pins=true
// Core's JSON inflator uses facing_direction to choose the stem end. Its
// linked-box import expects inward-facing stems; final Port directions still
// come from side_of_component. Preserve the supplier's pin-center geometry.
const inward={left:'right',right:'left',up:'down',down:'up'}
for(const port of symbolJson.filter(e=>e.type==='schematic_port'))
port.facing_direction=inward[port.facing_direction]
// Normalize the two diode drawings to their rail-oriented local symbols.
// Circuit JSON imports currently do not apply the primitive's schRotation.
// This rotates only supplier artwork, never circuit component placement.
const quarterTurn=name==='B5819W_SL'?-1:name==='XL_1608SURC_06'?1:0
if(quarterTurn){
const point=p=>({x:-quarterTurn*p.y,y:quarterTurn*p.x})
const directions=quarterTurn===1?{left:'down',down:'right',right:'up',up:'left'}:{left:'up',up:'right',right:'down',down:'left'}
const sides=quarterTurn===1?{left:'bottom',bottom:'right',right:'top',top:'left'}:{left:'top',top:'right',right:'bottom',bottom:'left'}
for(const e of symbolJson){
if(e.type==='schematic_text')continue
if(e.center)e.center=point(e.center)
if(e.size)e.size={width:e.size.height,height:e.size.width}
if(e.points)e.points=e.points.map(point)
if(e.type==='schematic_line'){
const a=point({x:e.x1,y:e.y1}),b=point({x:e.x2,y:e.y2})
Object.assign(e,{x1:a.x,y1:a.y,x2:b.x,y2:b.y})
}
if(e.facing_direction)e.facing_direction=directions[e.facing_direction]
if(e.side_of_component)e.side_of_component=sides[e.side_of_component]
}
const labels=symbolJson.filter(e=>e.type==='schematic_text')
labels.forEach((e,i)=>{e.position={x:0.45,y:0.15-i*0.25};e.anchor='left'})
}
if(name==='DTC114EU3HZGT106'){
// Keep the designator/value clear of the vertical collector net label.
symbolJson.filter(e=>e.type==='schematic_text').forEach((e,i)=>{
e.position={x:0.55,y:0.15-i*0.25};e.anchor='left'
})
}
for(const t of symbolJson.filter(e=>e.type==='schematic_text')){
t.schematic_component_id=component.schematic_component_id
if(t.text==='SYMBOL')t.text='{REF}'
}
const jsonOutput=JSON.stringify(symbolJson,null,2)+'\n'
writeFileSync(`imports/symbols/${name}.json`,jsonOutput)
manifest.parts[name]={lcsc:id,sourceSha256:createHash('sha256').update(readFileSync(rawPath)).digest('hex'),symbolSha256:createHash('sha256').update(output).digest('hex'),circuitSymbolSha256:createHash('sha256').update(jsonOutput).digest('hex')}
console.log(`Imported ${name}: ${id}`)
}
}))
writeFileSync('engineering/easyeda/manifest.json',JSON.stringify(manifest,null,2)+'\n')