diff --git a/lib/dsn-pcb/circuit-json-to-dsn-json/convert-circuit-json-to-dsn-session.ts b/lib/dsn-pcb/circuit-json-to-dsn-json/convert-circuit-json-to-dsn-session.ts index b232069..9a2f311 100644 --- a/lib/dsn-pcb/circuit-json-to-dsn-json/convert-circuit-json-to-dsn-session.ts +++ b/lib/dsn-pcb/circuit-json-to-dsn-json/convert-circuit-json-to-dsn-session.ts @@ -3,6 +3,7 @@ import type { DsnPcb, DsnSession, Wire } from "../types" import { su } from "@tscircuit/soup-util" import { convertCircuitJsonToDsnJson } from "./convert-circuit-json-to-dsn-json" import { applyToPoint, scale } from "transformation-matrix" +import { processPcbTraces } from "./process-pcb-traces" export function convertCircuitJsonToDsnSession( dsnPcb: DsnPcb, @@ -25,57 +26,16 @@ export function convertCircuitJsonToDsnSession( resolution: dsnPcb.resolution, parser: dsnPcb.parser, library_out: { + images: [], padstacks: [], }, network_out: { - nets: pcb_traces.map((trace) => { - const source_trace = source_traces.find( - (st) => st.source_trace_id === trace.source_trace_id, - ) - const source_net = - source_trace && - nets.find((n) => - source_trace.connected_source_net_ids.includes(n.source_net_id), - ) - const net_name = source_net?.name || trace.source_trace_id - - // TODO only supports single layer traces - const traceLayer = - "layer" in trace.route[0] && trace.route[0].layer === "bottom" - ? "bottom" - : "top" - - const traceWidth = - "width" in trace.route[0] ? trace.route[0].width : 0.16 - - return { - name: net_name!, - wires: [ - { - path: { - layer: traceLayer === "bottom" ? "B.Cu" : "F.Cu", - width: traceWidth * 1000, - coordinates: trace.route - .filter( - (rp): rp is PcbTraceRoutePointWire => - rp.route_type === "wire", - ) - .map((rp) => - // Circuit JSON space to the SES space - applyToPoint(transformMmToSesUnit, { - x: rp.x, - y: rp.y, - }), - ) - .flatMap((trp) => [trp.x, trp.y]), - }, - }, - ], - } - }), + nets: [], }, }, } + processPcbTraces(circuitJson, session) + return session } diff --git a/lib/dsn-pcb/circuit-json-to-dsn-json/process-pcb-traces/DsnTraceOperationsWrapper.ts b/lib/dsn-pcb/circuit-json-to-dsn-json/process-pcb-traces/DsnTraceOperationsWrapper.ts new file mode 100644 index 0000000..8d2eb1f --- /dev/null +++ b/lib/dsn-pcb/circuit-json-to-dsn-json/process-pcb-traces/DsnTraceOperationsWrapper.ts @@ -0,0 +1,52 @@ +import type { DsnPcb, DsnSession, Wire } from "lib/dsn-pcb/types" + +export interface DsnTraceOperationsWrapper { + getNextNetId(): string + addWire(wire: Wire): void + getLibrary(): DsnPcb["library"] + getStructure(): DsnPcb["structure"] | null +} + +/** + * This operations wrapper allows you to operate on either DSN PCB or DSN + * Session objects, increasing code reusability when working with traces. + * + * Please add more methods to this as you need them! + */ +export const getDsnTraceOperationsWrapper = ( + dsnObj: DsnPcb | DsnSession, +): DsnTraceOperationsWrapper => { + if (dsnObj.is_dsn_pcb) { + return { + getNextNetId: () => `Net-${dsnObj.network.nets.length + 1}`, + addWire: (wire: Wire) => { + dsnObj.wiring.wires.push(wire as any) + }, + getStructure: () => dsnObj.structure, + getLibrary: () => dsnObj.library, + } + } + + if (dsnObj.is_dsn_session) { + return { + getNextNetId: () => `Net-${dsnObj.routes.network_out.nets.length + 1}`, + getLibrary: () => dsnObj.routes.library_out!, + getStructure: () => null, + addWire: (wire: Wire) => { + let net = dsnObj.routes.network_out.nets.find( + (net) => net.name === wire.net, + ) + if (!net) { + net = { + name: wire.net!, + wires: [], + } + dsnObj.routes.network_out.nets.push(net) + } + net.wires.push(wire) + }, + } + } + + throw new Error("Invalid DSN object") +} diff --git a/lib/dsn-pcb/circuit-json-to-dsn-json/process-pcb-traces/findOrCreateViaPadstack.ts b/lib/dsn-pcb/circuit-json-to-dsn-json/process-pcb-traces/findOrCreateViaPadstack.ts index a966ace..fa95543 100644 --- a/lib/dsn-pcb/circuit-json-to-dsn-json/process-pcb-traces/findOrCreateViaPadstack.ts +++ b/lib/dsn-pcb/circuit-json-to-dsn-json/process-pcb-traces/findOrCreateViaPadstack.ts @@ -1,14 +1,17 @@ import type { DsnPcb, Padstack } from "lib/dsn-pcb/types" +import type { DsnTraceOperationsWrapper } from "./DsnTraceOperationsWrapper" export function findOrCreateViaPadstack( - pcb: DsnPcb, + pcb: DsnTraceOperationsWrapper, outerDiameter: number, holeDiameter: number, ): string { const viaName = `Via[0-1]_${outerDiameter}:${holeDiameter}_um` + const library = pcb.getLibrary() + // Check if padstack already exists - const existingPadstack = pcb.library.padstacks.find((p) => p.name === viaName) + const existingPadstack = library.padstacks.find((p) => p.name === viaName) if (existingPadstack) { return viaName @@ -36,6 +39,6 @@ export function findOrCreateViaPadstack( }, } - pcb.library.padstacks.push(viaPadstack) + library.padstacks.push(viaPadstack) return viaName } diff --git a/lib/dsn-pcb/circuit-json-to-dsn-json/process-pcb-traces/index.ts b/lib/dsn-pcb/circuit-json-to-dsn-json/process-pcb-traces/index.ts index 15462f4..afad955 100644 --- a/lib/dsn-pcb/circuit-json-to-dsn-json/process-pcb-traces/index.ts +++ b/lib/dsn-pcb/circuit-json-to-dsn-json/process-pcb-traces/index.ts @@ -7,8 +7,10 @@ import type { import type { DsnPcb, DsnSession } from "../../types" import Debug from "debug" import { findOrCreateViaPadstack } from "./findOrCreateViaPadstack" +import { getDsnTraceOperationsWrapper } from "./DsnTraceOperationsWrapper" +import { su } from "@tscircuit/soup-util" -const debug = Debug("dsn-converter:process-pcb-traces") +const debug = Debug("dsn-converter:processPcbTraces") const DEFAULT_VIA_DIAMETER = 600 // μm const DEFAULT_VIA_HOLE = 300 // μm @@ -41,14 +43,29 @@ function createWire(opts: { export function processPcbTraces( circuitElements: AnyCircuitElement[], - pcb: DsnPcb, + pcb: DsnPcb | DsnSession, ) { + const dsnWrapper = getDsnTraceOperationsWrapper(pcb) + const CJ_TO_DSN_SCALE = pcb.is_dsn_pcb ? 1000 : 10000 + for (const element of circuitElements) { if (element.type === "pcb_trace") { const pcbTrace = element + const source_trace = su(circuitElements).source_trace.getWhere({ + source_trace_id: pcbTrace.source_trace_id, + }) + const source_net = + source_trace && + su(circuitElements) + .source_net.list() + .find((n) => + source_trace.connected_source_net_ids.includes(n.source_net_id), + ) debug("PCB TRACE\n----------\n", pcbTrace) const netName = - pcbTrace.source_trace_id || `Net-${pcb.network.nets.length + 1}` + source_net?.name || + pcbTrace.source_trace_id || + dsnWrapper.getNextNetId() let currentLayer = "" let currentWire: Wire | null = null @@ -71,36 +88,39 @@ export function processPcbTraces( netName, }) - pcb.wiring.wires.push(currentWire) + dsnWrapper.addWire(currentWire) currentLayer = point.layer } if (currentWire && !hasLayerChanged) { // Add coordinates to current wire - currentWire.path.coordinates.push(point.x * 1000) - currentWire.path.coordinates.push(point.y * 1000) + currentWire.path.coordinates.push(point.x * CJ_TO_DSN_SCALE) + currentWire.path.coordinates.push(point.y * CJ_TO_DSN_SCALE) continue } if (hasLayerChanged) { const prevPoint = pcbTrace.route[i - 1] const viaPadstackName = findOrCreateViaPadstack( - pcb, + dsnWrapper, DEFAULT_VIA_DIAMETER, DEFAULT_VIA_HOLE, ) // Add via reference to structure if not already there - if (!pcb.structure.via) { - pcb.structure.via = viaPadstackName + if (dsnWrapper.getStructure() && !dsnWrapper.getStructure()?.via) { + dsnWrapper.getStructure()!.via = viaPadstackName } // Create wire segment for via placement - pcb.wiring.wires.push({ + dsnWrapper.addWire({ path: { layer: currentLayer === "top" ? "F.Cu" : "B.Cu", width: DEFAULT_VIA_DIAMETER, - coordinates: [prevPoint.x * 1000, prevPoint.y * 1000], + coordinates: [ + prevPoint.x * CJ_TO_DSN_SCALE, + prevPoint.y * CJ_TO_DSN_SCALE, + ], }, net: netName, type: "via", @@ -114,14 +134,14 @@ export function processPcbTraces( // End current wire if (currentWire) { - currentWire.path.coordinates.push(point.x * 1000) - currentWire.path.coordinates.push(point.y * 1000) + currentWire.path.coordinates.push(point.x * CJ_TO_DSN_SCALE) + currentWire.path.coordinates.push(point.y * CJ_TO_DSN_SCALE) currentWire = null } // Handle explicit via points const viaPadstackName = findOrCreateViaPadstack( - pcb, + dsnWrapper, DEFAULT_VIA_DIAMETER, DEFAULT_VIA_HOLE, ) @@ -129,21 +149,23 @@ export function processPcbTraces( debug("VIA PADSTACK NAME:", viaPadstackName) // Add via reference to structure if not already there - if (!pcb.structure.via) { - pcb.structure.via = viaPadstackName + if (dsnWrapper.getStructure() && !dsnWrapper.getStructure()?.via) { + dsnWrapper.getStructure()!.via = viaPadstackName } // Create wire segment for via placement - pcb.wiring.wires.push({ + dsnWrapper.addWire({ path: { layer: point.from_layer === "top" ? "F.Cu" : "B.Cu", width: DEFAULT_VIA_DIAMETER, - coordinates: [point.x * 1000, point.y * 1000], + coordinates: [ + point.x * CJ_TO_DSN_SCALE, + point.y * CJ_TO_DSN_SCALE, + ], }, net: netName, type: "via", }) - debug("WIRING", pcb.wiring) currentLayer = point.to_layer currentWire = null // Start fresh wire after via @@ -151,5 +173,12 @@ export function processPcbTraces( } } } - debug("PCB WIRING AT END", pcb.wiring) + debug( + "PCB WIRING/NETWORK_OUT AT END", + JSON.stringify( + pcb.is_dsn_pcb ? pcb.wiring : pcb.routes.network_out.nets, + null, + 2, + ), + ) } diff --git a/lib/dsn-pcb/dsn-json-to-circuit-json/merge-dsn-session-into-dsn-pcb.ts b/lib/dsn-pcb/dsn-json-to-circuit-json/merge-dsn-session-into-dsn-pcb.ts index 19a3c3c..0cfc7e7 100644 --- a/lib/dsn-pcb/dsn-json-to-circuit-json/merge-dsn-session-into-dsn-pcb.ts +++ b/lib/dsn-pcb/dsn-json-to-circuit-json/merge-dsn-session-into-dsn-pcb.ts @@ -33,7 +33,7 @@ export function mergeDsnSessionIntoDsnPcb( coordinates: wire.path.coordinates.map((c) => c / 10), }, net: sessionNet.name, - type: "route", + type: wire.type ?? "route", }) } }) diff --git a/lib/dsn-pcb/dsn-json-to-circuit-json/parse-dsn-to-dsn-json.ts b/lib/dsn-pcb/dsn-json-to-circuit-json/parse-dsn-to-dsn-json.ts index b73ae5b..9c75a9e 100644 --- a/lib/dsn-pcb/dsn-json-to-circuit-json/parse-dsn-to-dsn-json.ts +++ b/lib/dsn-pcb/dsn-json-to-circuit-json/parse-dsn-to-dsn-json.ts @@ -1098,6 +1098,7 @@ function processSessionNode(ast: ASTNode): DsnSession { ) if (libraryNode) { session.routes.library_out = { + images: [], padstacks: libraryNode .children!.filter( (child) => diff --git a/lib/dsn-pcb/types.ts b/lib/dsn-pcb/types.ts index 70fbd01..b166aa9 100644 --- a/lib/dsn-pcb/types.ts +++ b/lib/dsn-pcb/types.ts @@ -301,6 +301,7 @@ export interface DsnSession { resolution: Resolution parser: Parser library_out?: { + images: Image[] padstacks: Padstack[] } network_out: { diff --git a/tests/dsn-pcb/merge-dsn-session-with-conversion.test.tsx b/tests/dsn-pcb/merge-dsn-session-with-conversion.test.tsx index 547859b..6478da4 100644 --- a/tests/dsn-pcb/merge-dsn-session-with-conversion.test.tsx +++ b/tests/dsn-pcb/merge-dsn-session-with-conversion.test.tsx @@ -116,5 +116,5 @@ test("merge-dsn-session-with-conversion", async () => { ) // TODO requires fix inside convertCircuitJsonToDsnSession, currently the vias // aren't converted properly- reference or adapt the code in processPcbTraces - // expect(looksSameResult.equal).toBe(true) // Should be identical after merge + expect(looksSameResult.equal).toBe(true) // Should be identical after merge })