{onSchematicComponentClicked && (
)}
{onSchematicPortClicked && (
)}
{
+ const x = Number(point.x.toFixed(POINT_PRECISION))
+ const y = Number(point.y.toFixed(POINT_PRECISION))
+ return `${x},${y}`
+}
+
+const sortUnique = (items: Iterable) =>
+ Array.from(new Set(items)).sort()
+
+const getTracePointKeys = (trace: SchematicTrace) => {
+ const pointKeys = new Set()
+
+ for (const edge of trace.edges ?? []) {
+ if (edge.from) pointKeys.add(toPointKey(edge.from))
+ if (edge.to) pointKeys.add(toPointKey(edge.to))
+ }
+
+ for (const junction of trace.junctions ?? []) {
+ pointKeys.add(toPointKey(junction))
+ }
+
+ return pointKeys
+}
+
+export const getConnectedSchematicTraceGroups = (
+ circuitJson: CircuitJson,
+): Record => {
+ const groupKeysByTraceId = getSchematicTraceGroupKeys(circuitJson)
+ const traceIdsByGroupKey = new Map()
+
+ for (const [traceId, groupKey] of Object.entries(groupKeysByTraceId)) {
+ if (!traceIdsByGroupKey.has(groupKey)) {
+ traceIdsByGroupKey.set(groupKey, [])
+ }
+ traceIdsByGroupKey.get(groupKey)?.push(traceId)
+ }
+
+ const groupsByTraceId: Record = {}
+ for (const [traceId, groupKey] of Object.entries(groupKeysByTraceId)) {
+ groupsByTraceId[traceId] = sortUnique(
+ traceIdsByGroupKey.get(groupKey) ?? [],
+ )
+ }
+
+ return groupsByTraceId
+}
+
+export const getSchematicTraceGroupKeys = (
+ circuitJson: CircuitJson,
+): Record => {
+ const schematicTraces = su(
+ circuitJson,
+ ).schematic_trace.list() as SchematicTrace[]
+ const schematicPorts = su(circuitJson).schematic_port.list()
+ const sourcePorts = su(circuitJson).source_port.list()
+
+ const connectivityKeysByPoint = new Map>()
+ const sourcePortById = new Map(
+ sourcePorts.map((sourcePort) => [
+ sourcePort.source_port_id as string,
+ sourcePort as SourcePort,
+ ]),
+ )
+
+ for (const schematicPort of schematicPorts) {
+ if (!schematicPort.center || !schematicPort.source_port_id) continue
+
+ const sourcePort = sourcePortById.get(schematicPort.source_port_id)
+ const connectivityKey = sourcePort?.subcircuit_connectivity_map_key
+ if (!connectivityKey) continue
+
+ const pointKey = toPointKey(schematicPort.center)
+ if (!connectivityKeysByPoint.has(pointKey)) {
+ connectivityKeysByPoint.set(pointKey, new Set())
+ }
+ connectivityKeysByPoint.get(pointKey)?.add(connectivityKey)
+ }
+
+ const traceIdsByPoint = new Map>()
+ const pointKeysByTraceId = new Map()
+ const schematicTraceById = new Map()
+
+ for (const trace of schematicTraces) {
+ const traceId = trace.schematic_trace_id
+ if (!traceId) continue
+
+ const pointKeys = sortUnique(getTracePointKeys(trace))
+ schematicTraceById.set(traceId, trace)
+ pointKeysByTraceId.set(traceId, pointKeys)
+
+ for (const pointKey of pointKeys) {
+ if (!traceIdsByPoint.has(pointKey)) {
+ traceIdsByPoint.set(pointKey, new Set())
+ }
+ traceIdsByPoint.get(pointKey)?.add(traceId)
+ }
+ }
+
+ const groupKeysByTraceId: Record = {}
+ const visitedTraceIds = new Set()
+ let fallbackGroupIndex = 0
+
+ for (const trace of schematicTraces) {
+ const startTraceId = trace.schematic_trace_id
+ if (!startTraceId || visitedTraceIds.has(startTraceId)) continue
+
+ const queue = [startTraceId]
+ const connectedTraceIds = new Set()
+ const componentConnectivityKeys = new Set()
+
+ while (queue.length > 0) {
+ const currentTraceId = queue.shift()
+ if (!currentTraceId || visitedTraceIds.has(currentTraceId)) continue
+
+ visitedTraceIds.add(currentTraceId)
+ connectedTraceIds.add(currentTraceId)
+
+ const currentTrace = schematicTraceById.get(currentTraceId)
+ if (currentTrace?.subcircuit_connectivity_map_key) {
+ componentConnectivityKeys.add(
+ currentTrace.subcircuit_connectivity_map_key,
+ )
+ }
+
+ for (const pointKey of pointKeysByTraceId.get(currentTraceId) ?? []) {
+ for (const connectivityKey of connectivityKeysByPoint.get(pointKey) ??
+ []) {
+ componentConnectivityKeys.add(connectivityKey)
+ }
+
+ for (const neighborTraceId of traceIdsByPoint.get(pointKey) ?? []) {
+ if (!visitedTraceIds.has(neighborTraceId)) {
+ queue.push(neighborTraceId)
+ }
+ }
+ }
+ }
+
+ if (componentConnectivityKeys.size > 1) {
+ console.warn(
+ "Multiple connectivity keys found for schematic trace group",
+ sortUnique(componentConnectivityKeys),
+ sortUnique(connectedTraceIds),
+ )
+ }
+
+ const groupKey =
+ sortUnique(componentConnectivityKeys)[0] ??
+ `${FALLBACK_GROUP_PREFIX}_${fallbackGroupIndex++}`
+
+ for (const traceId of connectedTraceIds) {
+ groupKeysByTraceId[traceId] = groupKey
+ }
+ }
+
+ return groupKeysByTraceId
+}
+
+export const addConnectivityKeysToSchematicTraces = (
+ circuitJson: CircuitJson,
+): CircuitJson => {
+ const groupKeysByTraceId = getSchematicTraceGroupKeys(circuitJson)
+
+ return circuitJson.map((entry) => {
+ if (entry.type !== "schematic_trace" || !entry.schematic_trace_id) {
+ return entry
+ }
+
+ const subcircuitConnectivityMapKey =
+ groupKeysByTraceId[entry.schematic_trace_id] ??
+ entry.subcircuit_connectivity_map_key
+
+ if (!subcircuitConnectivityMapKey) {
+ return entry
+ }
+
+ return {
+ ...entry,
+ subcircuit_connectivity_map_key: subcircuitConnectivityMapKey,
+ }
+ }) as CircuitJson
+}
diff --git a/tests/connected-schematic-trace-groups.test.tsx b/tests/connected-schematic-trace-groups.test.tsx
new file mode 100644
index 0000000..242f2cb
--- /dev/null
+++ b/tests/connected-schematic-trace-groups.test.tsx
@@ -0,0 +1,171 @@
+import { describe, expect, test } from "bun:test"
+import type { CircuitJson } from "circuit-json"
+import { renderToCircuitJson } from "../lib/dev/render-to-circuit-json"
+import {
+ addConnectivityKeysToSchematicTraces,
+ getConnectedSchematicTraceGroups,
+ getSchematicTraceGroupKeys,
+} from "../lib/utils/getConnectedSchematicTraceGroups"
+
+describe("getConnectedSchematicTraceGroups", () => {
+ test("copies source-port connectivity keys onto rendered schematic traces", () => {
+ const circuitJson = renderToCircuitJson(
+
+
+
+
+ ,
+ )
+
+ const augmentedCircuitJson =
+ addConnectivityKeysToSchematicTraces(circuitJson)
+ const schematicTrace = augmentedCircuitJson.find(
+ (entry: any) => entry.type === "schematic_trace",
+ ) as any
+ const sourceTrace = augmentedCircuitJson.find(
+ (entry: any) => entry.type === "source_trace",
+ ) as any
+
+ expect(schematicTrace.subcircuit_connectivity_map_key).toBe(
+ sourceTrace.subcircuit_connectivity_map_key,
+ )
+ })
+
+ test("groups chain-connected trace segments under the same source net key", () => {
+ const circuitJson = [
+ {
+ type: "source_port",
+ source_port_id: "source_port_0",
+ subcircuit_connectivity_map_key: "net_a",
+ },
+ {
+ type: "source_port",
+ source_port_id: "source_port_1",
+ subcircuit_connectivity_map_key: "net_a",
+ },
+ {
+ type: "schematic_port",
+ schematic_port_id: "schematic_port_0",
+ source_port_id: "source_port_0",
+ center: { x: 0, y: 0 },
+ },
+ {
+ type: "schematic_port",
+ schematic_port_id: "schematic_port_1",
+ source_port_id: "source_port_1",
+ center: { x: 3, y: 0 },
+ },
+ {
+ type: "schematic_trace",
+ schematic_trace_id: "trace_0",
+ edges: [{ from: { x: 0, y: 0 }, to: { x: 1, y: 0 } }],
+ junctions: [{ x: 1, y: 0 }],
+ },
+ {
+ type: "schematic_trace",
+ schematic_trace_id: "trace_1",
+ edges: [{ from: { x: 1, y: 0 }, to: { x: 2, y: 0 } }],
+ junctions: [
+ { x: 1, y: 0 },
+ { x: 2, y: 0 },
+ ],
+ },
+ {
+ type: "schematic_trace",
+ schematic_trace_id: "trace_2",
+ edges: [{ from: { x: 2, y: 0 }, to: { x: 3, y: 0 } }],
+ junctions: [{ x: 2, y: 0 }],
+ },
+ ] as CircuitJson
+
+ expect(getSchematicTraceGroupKeys(circuitJson)).toEqual({
+ trace_0: "net_a",
+ trace_1: "net_a",
+ trace_2: "net_a",
+ })
+
+ expect(getConnectedSchematicTraceGroups(circuitJson)).toEqual({
+ trace_0: ["trace_0", "trace_1", "trace_2"],
+ trace_1: ["trace_0", "trace_1", "trace_2"],
+ trace_2: ["trace_0", "trace_1", "trace_2"],
+ })
+ })
+
+ test("groups disconnected trace segments that share the same port net key", () => {
+ const circuitJson = [
+ {
+ type: "source_port",
+ source_port_id: "source_port_0",
+ subcircuit_connectivity_map_key: "net_b",
+ },
+ {
+ type: "source_port",
+ source_port_id: "source_port_1",
+ subcircuit_connectivity_map_key: "net_b",
+ },
+ {
+ type: "schematic_port",
+ schematic_port_id: "schematic_port_0",
+ source_port_id: "source_port_0",
+ center: { x: 0, y: 0 },
+ },
+ {
+ type: "schematic_port",
+ schematic_port_id: "schematic_port_1",
+ source_port_id: "source_port_1",
+ center: { x: 10, y: 0 },
+ },
+ {
+ type: "schematic_trace",
+ schematic_trace_id: "trace_0",
+ edges: [{ from: { x: 0, y: 0 }, to: { x: 1, y: 0 } }],
+ junctions: [],
+ },
+ {
+ type: "schematic_trace",
+ schematic_trace_id: "trace_1",
+ edges: [{ from: { x: 10, y: 0 }, to: { x: 11, y: 0 } }],
+ junctions: [],
+ },
+ ] as CircuitJson
+
+ expect(getSchematicTraceGroupKeys(circuitJson)).toEqual({
+ trace_0: "net_b",
+ trace_1: "net_b",
+ })
+
+ expect(getConnectedSchematicTraceGroups(circuitJson)).toEqual({
+ trace_0: ["trace_0", "trace_1"],
+ trace_1: ["trace_0", "trace_1"],
+ })
+ })
+
+ test("falls back to geometric grouping when net metadata is missing", () => {
+ const circuitJson = [
+ {
+ type: "schematic_trace",
+ schematic_trace_id: "trace_0",
+ edges: [{ from: { x: 0, y: 0 }, to: { x: 1, y: 0 } }],
+ junctions: [{ x: 1, y: 0 }],
+ },
+ {
+ type: "schematic_trace",
+ schematic_trace_id: "trace_1",
+ edges: [{ from: { x: 1, y: 0 }, to: { x: 2, y: 0 } }],
+ junctions: [{ x: 1, y: 0 }],
+ },
+ {
+ type: "schematic_trace",
+ schematic_trace_id: "trace_2",
+ edges: [{ from: { x: 10, y: 0 }, to: { x: 11, y: 0 } }],
+ junctions: [],
+ },
+ ] as CircuitJson
+
+ const groupKeys = getSchematicTraceGroupKeys(circuitJson)
+
+ expect(groupKeys.trace_0).toBe(groupKeys.trace_1)
+ expect(groupKeys.trace_0).toMatch(/^schematic_trace_group_/)
+ expect(groupKeys.trace_2).not.toBe(groupKeys.trace_0)
+ })
+})