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')