shibosoftwaredev/f1c100s-linux-nema17-stepper-controller

Compact four-layer NEMA 17 carrier PCB combining an F1C100S Linux SoC, USB-C PD power and buck regulation, W5500 10/100 Ethernet, STM32 safety/motion control, and DRV8825 stepper-driver hardware with protection, sensing, connectors, and dual-sided SMT assembly.

Version
0.4.15
License
unset
Stars
0

routing-boundary.ts

import { applyPartMetadata, applyCadMetadata, applyAssemblyPasteMetadata } from "./assembly-metadata"
import { addGroundViaFanout, applyCompactComponentPlan, packCompactBoard } from "./compact-layout"
import {
  CopperPourPipelineSolver,
  convertCircuitJsonToInputProblem,
  initializeManifoldGeometry,
} from "@tscircuit/copper-pour-solver"

async function regenerateGroundPours(board: any) {
  const db = board.root.db
  const ground = db.source_net.list().find((net: any) => net.name === "BOARD_GND")
  const pcbBoard = board.pcb_board_id ? db.pcb_board.get(board.pcb_board_id) : undefined
  if (!ground || !pcbBoard) return

  for (const pour of db.pcb_copper_pour.list())
    if ((pour.layer === "top" || pour.layer === "bottom") && pour.source_net_id === ground.source_net_id)
      db.pcb_copper_pour.delete(pour.pcb_copper_pour_id)

  // Remove stale annotations left by the preliminary pour. The regenerated
  // B-rep is authoritative for final connectivity and DRC.
  for (const trace of db.pcb_trace.list()) db.pcb_trace.update(trace.pcb_trace_id, {
    route: trace.route.map((point: any) => {
      const { copper_pour_id, is_inside_copper_pour, ...cleanPoint } = point
      return cleanPoint
    }),
  })

  const outline = pcbBoard.outline?.length ? pcbBoard.outline : [
    { x: pcbBoard.center.x - pcbBoard.width / 2, y: pcbBoard.center.y - pcbBoard.height / 2 },
    { x: pcbBoard.center.x + pcbBoard.width / 2, y: pcbBoard.center.y - pcbBoard.height / 2 },
    { x: pcbBoard.center.x + pcbBoard.width / 2, y: pcbBoard.center.y + pcbBoard.height / 2 },
    { x: pcbBoard.center.x - pcbBoard.width / 2, y: pcbBoard.center.y + pcbBoard.height / 2 },
  ]
  await initializeManifoldGeometry()
  // Keep the continuous ground reference on top. The imported SoC fanout has
  // a dense set of intentional bottom-layer supply escapes; a global bottom
  // fill is unnecessary here and can create fragile slivers around that fixed
  // copper. Bottom-side ground pads still reach this plane through the local
  // 0.30/0.45 mm stitching vias added immediately above.
  for (const layer of ["top"] as const) {
    const input = convertCircuitJsonToInputProblem(db.toArray(), {
      layer,
      subcircuit_id: board.subcircuit_id,
      source_net_id: ground.source_net_id,
      pad_margin: 0.3,
      trace_margin: 0.3,
      pour_margin: 0.3,
      board_edge_margin: 0.25,
      board_edge_outline: outline,
      cutout_margin: 0.25,
      use_thermal_reliefs: true,
      thermal_relief_spoke_width: 0.3,
      outline,
    })
    const solver = new CopperPourPipelineSolver(input)
    solver.solve()
    if (solver.failed) throw new Error(solver.error ?? `Final ${layer} ground-pour regeneration failed`)
    for (const shape of solver.getOutput().brep_shapes) db.pcb_copper_pour.insert({
      shape: "brep",
      layer,
      brep_shape: shape,
      source_net_id: ground.source_net_id,
      subcircuit_id: board.subcircuit_id,
      covered_with_solder_mask: false,
    })
  }
}

/** Keep the reservation in the autorouter input, then remove the non-fabricated
 * helper after routing and before copper generation / final DRC. */
export function routingKeepoutRef(keepout: any) {
  if (!keepout) return
  keepout.doInitialPcbCopperPourRender=()=>{
    if (keepout.root && keepout.pcb_keepout_id)
      keepout.root.db.pcb_keepout.delete(keepout.pcb_keepout_id)
  }
}
/** Apply assembly metadata before the unmodified full DRC. */
export function routingBoundaryRef(board: any) {
  // React 19 calls callback refs with null during cleanup.
  if (!board?.doInitialPcbDesignRuleChecks) return
  // The ref fires before composite child components have expanded. Apply the
  // face/connector plan immediately before footprint emission, when the full
  // component tree exists but no PCB primitives have been created yet.
  const runChildren=board.runRenderPhaseForChildren.bind(board)
  board.runRenderPhaseForChildren=(phase:any)=>{
    if(phase==="PcbComponentRender") applyCompactComponentPlan(board)
    return runChildren(phase)
  }
  const placementCheck=board.doInitialPcbPlacementDesignRuleChecks.bind(board)
  board.doInitialPcbPlacementDesignRuleChecks=()=>{ packCompactBoard(board); return placementCheck() }
  const check=board.doInitialPcbDesignRuleChecks.bind(board)
  board.doInitialPcbDesignRuleChecks=()=>{
    if (board.root?.pcbRoutingDisabled) {
      applyPartMetadata(board)
      check()
      return
    }
    board._queueAsyncEffect("post-route-copper-and-drc", async()=>{
      addGroundViaFanout(board)
      await regenerateGroundPours(board)
      applyPartMetadata(board)
      check()
    })
  }
  const parts=board.doInitialPartsEngineRender.bind(board)
  board.doInitialPartsEngineRender=()=>{ applyCadMetadata(board); applyAssemblyPasteMetadata(board); return parts() }
}