From f6cf8c65235478e45fe91f4c4e3fd9277c4b88d6 Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Thu, 13 Nov 2025 21:42:05 +0200 Subject: [PATCH] feat: Add support for inflating traces from subcircuit circuitJson --- .../Group/Subcircuit/Subcircuit.ts | 7 +- .../inflators/inflateSourceTrace.ts | 80 +++++++++++++++++++ .../subcircuit-circuit-json06-pcb.snap.svg | 1 + .../subcircuit-circuit-json06.test.tsx | 65 +++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 lib/components/primitive-components/Group/Subcircuit/inflators/inflateSourceTrace.ts create mode 100644 tests/features/subcircuit-circuit-json/__snapshots__/subcircuit-circuit-json06-pcb.snap.svg create mode 100644 tests/features/subcircuit-circuit-json/subcircuit-circuit-json06.test.tsx diff --git a/lib/components/primitive-components/Group/Subcircuit/Subcircuit.ts b/lib/components/primitive-components/Group/Subcircuit/Subcircuit.ts index 50df89787..c08fc0814 100644 --- a/lib/components/primitive-components/Group/Subcircuit/Subcircuit.ts +++ b/lib/components/primitive-components/Group/Subcircuit/Subcircuit.ts @@ -10,6 +10,7 @@ import { inflateSourceChip } from "./inflators/inflateSourceChip" import { inflateSourceCapacitor } from "./inflators/inflateSourceCapacitor" import { inflateSourceInductor } from "./inflators/inflateSourceInductor" import { inflateSourceDiode } from "./inflators/inflateSourceDiode" +import { inflateSourceTrace } from "./inflators/inflateSourceTrace" export class Subcircuit extends Group { constructor(props: z.input) { @@ -83,10 +84,14 @@ export class Subcircuit extends Group { } } - // Finally, inflate source ports (group ports) const sourcePorts = injectionDb.source_port.list() for (const sourcePort of sourcePorts) { inflateSourcePort(sourcePort, inflationCtx) } + + const sourceTraces = injectionDb.source_trace.list() + for (const sourceTrace of sourceTraces) { + inflateSourceTrace(sourceTrace, inflationCtx) + } } } diff --git a/lib/components/primitive-components/Group/Subcircuit/inflators/inflateSourceTrace.ts b/lib/components/primitive-components/Group/Subcircuit/inflators/inflateSourceTrace.ts new file mode 100644 index 000000000..11734c7fc --- /dev/null +++ b/lib/components/primitive-components/Group/Subcircuit/inflators/inflateSourceTrace.ts @@ -0,0 +1,80 @@ +import type { SourceTrace } from "circuit-json" +import { Trace } from "lib/components/primitive-components/Trace/Trace" +import type { InflatorContext } from "../InflatorFn" +import type { CircuitJsonUtilObjects } from "@tscircuit/circuit-json-util" + +const getSelectorPath = ( + component: { name: string; source_group_id: string | undefined }, + db: CircuitJsonUtilObjects, +): string => { + const path_parts: string[] = [] + let currentGroupId = component.source_group_id + while (currentGroupId) { + const group = db.source_group.get(currentGroupId) + if (!group) break + path_parts.unshift(`.${group.name}`) + currentGroupId = group.parent_source_group_id + } + path_parts.push(`.${component.name}`) + return path_parts.join(" > ") +} + +export function inflateSourceTrace( + sourceTrace: SourceTrace, + inflatorContext: InflatorContext, +) { + const { injectionDb, subcircuit } = inflatorContext + + const connectedSelectors: string[] = [] + + // Get selectors for connected ports + for (const sourcePortId of sourceTrace.connected_source_port_ids) { + const sourcePort = injectionDb.source_port.get(sourcePortId) + if (!sourcePort) continue + + let selector: string | undefined + if (sourcePort.source_component_id) { + const sourceComponent = injectionDb.source_component.get( + sourcePort.source_component_id, + ) + if (sourceComponent) { + // This is a port on a component, e.g. .G1 > .R1 > .pin1 + const path = getSelectorPath( + { + name: sourceComponent.name, + source_group_id: sourceComponent.source_group_id, + }, + injectionDb, + ) + selector = `${path} > .${sourcePort.name}` + } + } else { + // This is a port on a group, usually the root group of the subcircuit. + // e.g. .P1 + selector = `.${sourcePort.name}` + } + + if (selector) { + connectedSelectors.push(selector) + } + } + + // Get selectors for connected nets + for (const sourceNetId of sourceTrace.connected_source_net_ids) { + const sourceNet = injectionDb.source_net.get(sourceNetId) + if (sourceNet) { + connectedSelectors.push(`net.${sourceNet.name}`) + } + } + + if (connectedSelectors.length < 2) return + + const trace = new Trace({ + path: connectedSelectors, + }) + + // Set source_trace_id on the new trace + trace.source_trace_id = sourceTrace.source_trace_id + + subcircuit.add(trace) +} diff --git a/tests/features/subcircuit-circuit-json/__snapshots__/subcircuit-circuit-json06-pcb.snap.svg b/tests/features/subcircuit-circuit-json/__snapshots__/subcircuit-circuit-json06-pcb.snap.svg new file mode 100644 index 000000000..823b0a569 --- /dev/null +++ b/tests/features/subcircuit-circuit-json/__snapshots__/subcircuit-circuit-json06-pcb.snap.svg @@ -0,0 +1 @@ +R1R2R3 \ No newline at end of file diff --git a/tests/features/subcircuit-circuit-json/subcircuit-circuit-json06.test.tsx b/tests/features/subcircuit-circuit-json/subcircuit-circuit-json06.test.tsx new file mode 100644 index 000000000..1973a87f2 --- /dev/null +++ b/tests/features/subcircuit-circuit-json/subcircuit-circuit-json06.test.tsx @@ -0,0 +1,65 @@ +import { test, expect } from "bun:test" +import { getTestFixture } from "tests/fixtures/get-test-fixture" +import { renderToCircuitJson } from "tests/fixtures/renderToCircuitJson" + +test("subcircuit-circuit-json06 - trace inflation", async () => { + const { circuit } = await getTestFixture() + + const subcircuitCircuitJson = await renderToCircuitJson( + + + + + , + ) + + circuit.add( + + + + , + ) + + await circuit.renderUntilSettled() + + const circuitJson = await circuit.getCircuitJson() + + // Find the trace between R1 and R2 inside the subcircuit + const sourceTraces = circuitJson.filter((elm) => elm.type === "source_trace") + + const sourceTraceR1R2 = sourceTraces.find((elm: any) => { + if (!elm.connected_source_port_ids) return false + const connectedPorts = elm.connected_source_port_ids + .map((id: string) => { + const port = circuitJson.find( + (p: any) => p.type === "source_port" && p.source_port_id === id, + ) as any + if (!port) return null + const comp = circuitJson.find( + (c: any) => + c.type === "source_component" && + c.source_component_id === port.source_component_id, + ) as any + if (!comp) return null + return `${comp.name}.${port.name}` + }) + .filter(Boolean) + + return ( + connectedPorts.includes("R1.pin2") && connectedPorts.includes("R2.pin1") + ) + }) + + expect(sourceTraceR1R2).toBeDefined() + + const pcbTraces = circuitJson.filter((c) => c.type === "pcb_trace") + const pcbTraceR1R2 = pcbTraces.find( + (c: any) => c.source_trace_id === (sourceTraceR1R2 as any).source_trace_id, + ) + + expect(pcbTraceR1R2).toBeDefined() + + expect(pcbTraces).toHaveLength(1) + + expect(circuit).toMatchPcbSnapshot(import.meta.path) +})