0hmX/am3352-ddr-track-a-local-dfm-wip-20261007
Experimental RJ45-corrected board with isolated PG indication. 1005/1005 source joins pass; 15 expanded-guard physical errors, sourcing and qualification gates remain. Not fabrication-ready.
- Version
- 0.0.14
- License
- unset
- Stars
- 0
work/style-experiment.tsx
import { Circuit } from "@tscircuit/core"
import { AutoroutingPipelineSolver11_Simplification } from "@tscircuit/capacity-autorouter"
import { analyzePcbStyle, renderPcbStyleSvg } from "@tscircuit/circuit-json-pcb-style-analysis"
import { EventEmitter } from "node:events"
import { writeFileSync } from "node:fs"
import { Children, cloneElement, isValidElement } from "react"
import Board from "../index"
import { SavedNativePours } from "../data/saved-native-pours"
import { savedRoutes } from "../data/saved-route-geometry"
import { checkRouting } from "./check-baseline"
const board = Board()
const children = Children.toArray(board.props.children).filter(child =>
!(isValidElement(child) && child.type === SavedNativePours))
const circuit = new Circuit()
circuit.schematicDisabled = true
const targeted = process.argv.includes("--targeted")
type ThroughCopper = [kind: "through_obstacle", startX: number, startY: number, endX: number, endY: number, from: string, to: string, width: number]
type StyledRoute = [name: string, nets: string[], ports: string[], points: (typeof savedRoutes[number][3][number] | ThroughCopper)[]]
let exportedRoutes: StyledRoute[] = []
let routingResult: { before: any[]; traces: any[] } | undefined
let rejectedResult: { before: any[]; traces: any[] } | undefined
const round = (n: number) => Math.round(n * 1e7) / 1e7
const geometry = (points: any[]) => JSON.stringify(points.map(p => p[0] === "via"
? [p[0], round(p[1]), round(p[2]), p[3], p[4]]
: p[0] === "through_obstacle" ? [p[0], ...p.slice(1, 5).map(round), ...p.slice(5)]
: [round(p[0]), round(p[1]), p[2], p[3]]))
const routePoints = (route: any[]) => route.map(p => p.route_type === "wire"
? [p.x, p.y, p.layer, p.width]
: p.route_type === "via" ? ["via", p.x, p.y, p.from_layer, p.to_layer, p.via_diameter ?? null, p.via_hole_diameter ?? null]
: p.route_type === "through_obstacle" || p.route_type === "through_pad" ? ["through_obstacle", p.start.x, p.start.y, p.end.x, p.end.y, p.from_layer ?? p.start_layer, p.to_layer ?? p.end_layer, p.width]
: (() => { throw Error(`Unsupported saved-route point ${p.route_type}`) })())
function exportNamedRoutes(before: any[], traces: any[], filename = "style-targeted-routes.tsx") {
const originalTraces = before.filter(record => record.type === "pcb_trace")
if (originalTraces.length !== savedRoutes.length || traces.length !== savedRoutes.length) throw Error("Cleanup changed trace count")
const definitions = new Map(originalTraces.map((trace, index) => {
const definition = savedRoutes[index]
if (geometry(routePoints(trace.route)) !== geometry(definition[3])) throw Error(`Original route order/geometry mismatch: ${definition[0]}`)
return [trace.pcb_trace_id, definition]
}))
exportedRoutes = traces.map(trace => {
const definition = definitions.get(trace.pcb_trace_id)
if (!definition) throw Error(`Missing original definition for ${trace.pcb_trace_id}`)
const metadata = new Map(definition[3].flatMap(point => typeof point[0] !== "number" || !point.slice(4).some(Boolean)
? [] : [[`${round(point[0])},${round(point[1])},${point[2]}`, point.slice(4)]]))
const points = routePoints(trace.route).map(point => typeof point[0] !== "number" ? point
: [...point, ...(metadata.get(`${round(point[0])},${round(point[1])},${point[2]}`) ?? [])])
return [definition[0], definition[1], definition[2], points] as StyledRoute
})
const changed = exportedRoutes.filter((definition, index) => geometry(definition[3]) !== geometry(savedRoutes[index][3])).length
writeFileSync(new URL(`./${filename}`, import.meta.url),
`import type { SavedRouteDefinition } from "../layout/SavedRoutes"\ntype ThroughCopper = [kind: "through_obstacle", startX: number, startY: number, endX: number, endY: number, from: string, to: string, width: number]\ntype StyledRoute = [name: string, nets: string[], ports: string[], points: (SavedRouteDefinition[3][number] | ThroughCopper)[]]\n\n// Native TSX route definitions: named nets/ports, no persisted Circuit JSON IDs.\nexport const savedRoutes: StyledRoute[] = [\n${exportedRoutes.map(definition => ` ${JSON.stringify(definition)},`).join("\n")}\n]\n`)
console.log(`Exported ${exportedRoutes.length} named TSX route definitions (${changed} changed; ${exportedRoutes.flatMap(definition => definition[3]).filter(point => point[0] === "through_obstacle").length} through-copper transitions)`)
}
const targetedAlgorithm = async (input: any) => {
const router = new EventEmitter() as any
router.input = input
router.isRouting = false
router.stop = () => { router.isRouting = false }
router.start = async () => {
if (router.isRouting) return
router.isRouting = true
let solver: any
let before: any[] = []
try {
before = circuit.getCircuitJson()
const beforeIssues = analyzePcbStyle(before).issues
const targets = new Set(beforeIssues.map(issue => issue.pcbTraceId))
const beforeTraces = before.filter(record => record.type === "pcb_trace")
for (const issue of beforeIssues) {
const index = beforeTraces.findIndex(trace => trace.pcb_trace_id === issue.pcbTraceId)
const definition = savedRoutes[index]
const widths = [...new Set(definition[3].flatMap(point => typeof point[0] === "number" ? [point[3]] : point[0] === "through_obstacle" ? [point[7]] : []))]
console.log(`TARGET ${definition[0]} (${definition[1].join(", ")}; widths ${widths.join(", ")} mm): ${issue.message}`)
}
writeFileSync(new URL("./style-targeted-before.svg", import.meta.url), renderPcbStyleSvg(before, beforeIssues))
// Add a conservative solver-only margin; authored board rules and copper
// remain unchanged. Physical barrel clearances are checked after replay.
// Core currently leaks native through_pad names into its SRJ seed. Normalize
// the solver input representation, keeping the live circuit untouched.
const solverInput = {
...input,
minTraceToPadEdgeClearance: Math.max(input.minTraceToPadEdgeClearance ?? 0, 0.1),
traces: input.traces.map((trace: any) => ({ ...trace, route: trace.route.map((point: any) => point.route_type !== "through_pad" ? point : {
route_type: "through_obstacle", start: point.start, end: point.end,
from_layer: point.start_layer, to_layer: point.end_layer, width: point.width,
}) })),
}
solver = new AutoroutingPipelineSolver11_Simplification(solverInput, {
iterations: 1, enableCrossingViaReduction: false, enableVertexShortcuts: true,
})
while (!solver.prepareTraceSimplificationSolver?.solved && !solver.failed) solver.step()
if (solver.failed) throw Error(solver.error)
const prepared = solver.prepareTraceSimplificationSolver!.getOutput()
const mutableNames = new Set<string>()
for (const trace of prepared.preparedTraces) {
if (!trace.mutableHdRoute) {
if (targets.has(trace.originalTrace.pcb_trace_id)) console.log(`IMMUTABLE ${trace.originalTrace.pcb_trace_id}: nonuniform width or unsupported route`)
continue
}
if (targets.has(trace.originalTrace.pcb_trace_id)) {
mutableNames.add(trace.mutableHdRoute.connectionName)
} else {
prepared.immutableHdRoutes.push(trace.mutableHdRoute)
trace.mutableHdRoute = undefined
}
}
prepared.mutableHdRoutes = prepared.mutableHdRoutes.filter(route => mutableNames.has(route.connectionName))
console.log(`Targeted cleanup: ${targets.size} failing traces, ${prepared.mutableHdRoutes.length} mutable uniform-width routes, ${prepared.immutableHdRoutes.length} immutable route fragments`)
let lastLog = Date.now()
while (!solver.solved && !solver.failed && router.isRouting) {
const until = Date.now() + 50
do { solver.step() } while (!solver.solved && !solver.failed && Date.now() < until)
if (Date.now() - lastLog > 15_000) {
let active: any = solver
const stages: string[] = []
while (active) { stages.push(`${active.getSolverName?.() ?? active.constructor.name} ${(active.progress ?? 0).toFixed(3)}`); active = active.activeSubSolver }
console.log(stages.join(" > "))
lastLog = Date.now()
}
await new Promise(resolve => setTimeout(resolve, 0))
}
if (solver.failed) throw Error(solver.error)
if (!router.isRouting) return
const traces = solver.getOutputSimplifiedPcbTraces()
routingResult = { before, traces }
router.isRouting = false
router.emit("complete", { type: "complete", traces })
} catch (error) {
const validation = solver?.validateTraceSimplificationSolver
if (validation?.inputProblem?.outputSrj?.traces) {
rejectedResult = { before, traces: validation.inputProblem.outputSrj.traces }
const oldErrors = validation.drcEngine.evaluate(validation.inputProblem.preparedInput.originalSrj.traces).errors
const newErrors = validation.drcEngine.evaluate(validation.inputProblem.outputSrj.traces).errors
const messages = new Set(oldErrors.map((finding: any) => finding.message))
for (const finding of newErrors) if (!messages.has(finding.message)) console.log("REJECTED NATIVE DRC", finding)
}
router.isRouting = false
router.emit("error", { type: "error", error })
}
}
return router
}
const started = Date.now()
circuit.on("autorouting:start", (event: any) => console.log(
`Routing phase started after ${((Date.now() - started) / 1000).toFixed(1)}s: ${event.simpleRouteJson?.connections?.length ?? "?"} connections, ${event.simpleRouteJson?.traces?.length ?? 0} existing traces`))
circuit.on("solver:started", (event: any) => console.log(`Solver: ${event.solverName}`))
circuit.on("autorouting:end", (event: any) => {
console.log(`Native route-path export: ${event.pcbTracePaths?.length ?? 0}; ${event.pcbTracePathsUnavailableReason ?? "available"}`)
if (event.pcbTracePaths?.length) writeFileSync(new URL(`./style-${targeted ? "targeted" : "all"}-paths.tsx`, import.meta.url),
`import type { FanoutTracePath } from "@tscircuit/props"\n\n// Native TSX routing paths; no Circuit JSON snapshot.\nexport const paths: FanoutTracePath[] = ${JSON.stringify(event.pcbTracePaths, null, 2)}\n`)
})
circuit.add(cloneElement(board, {
routingDisabled: false,
routeRemaining: false,
isViaInPadAllowed: true,
autorouter: { local: true, allowViaInPad: true },
}, ...children, <autoroutingphase phaseIndex={99} autorouter="simplify" algorithmFn={targeted ? targetedAlgorithm : undefined} reroute />))
await circuit.renderUntilSettled()
const records = circuit.getCircuitJson()
if (routingResult) {
try { exportNamedRoutes(routingResult.before, routingResult.traces) }
catch (error) { console.error("TSX export failed after routing completed:", error) }
}
if (rejectedResult) {
try { exportNamedRoutes(rejectedResult.before, rejectedResult.traces, "style-rejected-routes.tsx") }
catch (error) { console.error("Rejected diagnostic TSX export failed:", error) }
}
const { issues } = analyzePcbStyle(records)
writeFileSync(new URL(`./style-${targeted ? "targeted" : "all"}-after.svg`, import.meta.url), renderPcbStyleSvg(records, issues))
const counts = new Map<string, number>()
for (const issue of issues) counts.set(issue.lineItemType, (counts.get(issue.lineItemType) ?? 0) + 1)
console.log(`Simplify completed after ${((Date.now() - started) / 1000).toFixed(1)}s: ${records.filter(r => r.type === "pcb_trace").length} traces, ${issues.length} style issues`)
console.log([...counts].map(([type, count]) => `${type}: ${count}`).join("\n"))
const errors = records.filter(r => r.type.endsWith("_error"))
console.log(`Render errors: ${errors.length}`)
for (const error of errors.slice(0, 10)) console.log(`${error.type}: ${(error as any).message}`)
const nets = new Map(records.filter((r: any) => r.type === "source_net").map((r: any) => [r.source_net_id, r.name]))
const sourceTraces = new Map(records.filter((r: any) => r.type === "source_trace").map((r: any) => [r.source_trace_id, r]))
const pcbTraces = new Map(records.filter((r: any) => r.type === "pcb_trace").map((r: any) => [r.pcb_trace_id, r]))
for (const issue of issues) {
const source: any = sourceTraces.get((pcbTraces.get(issue.pcbTraceId) as any)?.source_trace_id)
console.log(`REMAINING ${source?.connected_source_net_ids?.map((id: string) => nets.get(id)).join(", ") ?? "unknown net"}: ${issue.message}`)
}
if (targeted && routingResult && exportedRoutes.length) {
const layerIndex = (layer: string) => layer === "top" ? 0 : layer === "bottom" ? 7 : Number(layer.slice(5))
const physical = new Map<string, any>()
const originalVias = routingResult?.before.filter(record => record.type === "pcb_via") ?? []
for (let via of records.filter((r: any) => r.type === "pcb_via") as any[]) {
// Core may materialize a smaller route via inside an existing full barrel.
// The original physical copper, drill and tenting remain authoritative.
via = originalVias.find(original => Math.hypot(original.x - via.x, original.y - via.y) < 1e-6
&& original.source_net_id === via.source_net_id && original.outer_diameter >= via.outer_diameter
&& via.layers.every((layer: string) => original.layers.includes(layer))) ?? via
const transitionNets = exportedRoutes.filter(definition => definition[3].some(point => point[0] === "via" && Math.hypot(point[1] - via.x, point[2] - via.y) < 1e-6)).flatMap(definition => definition[1])
const net = nets.get(via.source_net_id) ?? ([...new Set(transitionNets)].length === 1 ? transitionNets[0] : undefined)
if (!net) throw Error(`Cannot resolve physical via net at ${via.x},${via.y}`)
const layers = [...via.layers].sort((a: string, b: string) => layerIndex(a) - layerIndex(b))
const key = JSON.stringify([round(via.x), round(via.y), net, layers, via.hole_diameter, via.outer_diameter])
if (!physical.has(key)) physical.set(key, { ...via, layers, net })
}
const vias = [...physical.values()]
let transitions = 0
for (const definition of exportedRoutes) for (const point of definition[3]) if (point[0] === "via") {
transitions++
if (!vias.some(via => definition[1].includes(via.net) && Math.hypot(point[1] - via.x, point[2] - via.y) < 1e-6 && via.layers.includes(point[3]) && via.layers.includes(point[4]))) {
throw Error(`Missing physical via for ${definition[0]} at ${point[1]},${point[2]} ${point[3]}→${point[4]}`)
}
}
const lines = vias.map((via, index) => {
const tented = via.tented_on_top && via.tented_on_bottom ? "both_sides" : via.tented_on_top ? "top_tented" : via.tented_on_bottom ? "bottom_tented" : undefined
return ` <via name="savedVia${index}" pcbX={${via.x}} pcbY={${via.y}} holeDiameter={${via.hole_diameter}} outerDiameter={${via.outer_diameter}} fromLayer=${JSON.stringify(via.layers[0])} toLayer=${JSON.stringify(via.layers.at(-1))} connectsTo=${JSON.stringify(`net.${via.net}`)}${tented ? ` tented=${JSON.stringify(tented)}` : ""} />`
})
writeFileSync(new URL("./style-targeted-vias.tsx", import.meta.url), `// Physical vias for the targeted TSX routes; names and nets, no Circuit JSON.\nexport function SavedNativeVias() {\n return <>\n${lines.join("\n")}\n </>\n}\n`)
console.log(`Physical vias: ${records.filter(r => r.type === "pcb_via").length} records, ${vias.length} unique; all ${transitions} route transitions covered`)
}
await checkRouting(records)
if (records.some(record => record.type === "pcb_autorouting_error")) process.exitCode = 1