diff --git a/lib/components/primitive-components/Group/Group_doInitialSchematicTraceRender/compute-crossings.ts b/lib/components/primitive-components/Group/Group_doInitialSchematicTraceRender/compute-crossings.ts index e6aa6c646..1d11c55aa 100644 --- a/lib/components/primitive-components/Group/Group_doInitialSchematicTraceRender/compute-crossings.ts +++ b/lib/components/primitive-components/Group/Group_doInitialSchematicTraceRender/compute-crossings.ts @@ -160,6 +160,26 @@ export function computeCrossings( const crossLen = opts.crossSegmentLength ?? 0.075 const tol = opts.tolerance ?? TOL + // Precompute endpoint orientations for all traces to help choose which + // trace receives a crossing when a junction is also present at the + // intersection point. + const endpointInfo = new Map< + string, + Array<{ traceIdx: number; orient: "horizontal" | "vertical" }> + >() + for (let ti = 0; ti < traces.length; ti++) { + const trace = traces[ti] + for (const e of trace.edges) { + const orient = isHorizontalEdge(e) ? "horizontal" : "vertical" + for (const p of [e.from, e.to]) { + const key = `${p.x.toFixed(6)},${p.y.toFixed(6)}` + const arr = endpointInfo.get(key) ?? [] + arr.push({ traceIdx: ti, orient }) + endpointInfo.set(key, arr) + } + } + } + // Collect intersections per edge reference type EdgeRef = { traceIdx: number; edgeIdx: number } const crossingsByEdge = new Map() @@ -196,12 +216,25 @@ export function computeCrossings( // Crossing is when both are away from endpoints if (!nearEndpointA && !nearEndpointB) { // Only one of the traces should receive the crossing marker. - // Prefer assigning the crossing to the more horizontal edge. + // Prefer assigning the crossing to the more horizontal edge, but if + // there's a junction at this point choose the edge that doesn't + // participate in that junction to avoid overlap. + + const pointKey = `${P.x.toFixed(6)},${P.y.toFixed(6)}` + const otherOrients = new Set( + (endpointInfo.get(pointKey) ?? []) + .filter((info) => info.traceIdx !== ti && info.traceIdx !== tj) + .map((info) => info.orient), + ) + const aIsHorizontal = isHorizontalEdge(eA) const bIsHorizontal = isHorizontalEdge(eB) let assignToA: boolean - if (aIsHorizontal !== bIsHorizontal) { + if (otherOrients.size === 1 && aIsHorizontal !== bIsHorizontal) { + const orient = otherOrients.values().next().value + assignToA = orient === "horizontal" ? aIsHorizontal : !aIsHorizontal + } else if (aIsHorizontal !== bIsHorizontal) { // If exactly one is horizontal, prefer the horizontal one assignToA = aIsHorizontal } else { diff --git a/tests/repros/__snapshots__/repro51-junction-hop-overlap-schematic.snap.svg b/tests/repros/__snapshots__/repro51-junction-hop-overlap-schematic.snap.svg new file mode 100644 index 000000000..5111719e3 --- /dev/null +++ b/tests/repros/__snapshots__/repro51-junction-hop-overlap-schematic.snap.svg @@ -0,0 +1,41 @@ +-4,-2-4,-1-4,0-4,1-4,2-4,3-4,4-3,-2-3,-1-3,0-3,1-3,2-3,3-3,4-2,-2-2,-1-2,0-2,1-2,2-2,3-2,4-1,-2-1,-1-1,0-1,1-1,2-1,3-1,40,-20,-10,00,10,20,30,41,-21,-11,01,11,21,31,42,-22,-12,02,12,22,32,43,-23,-13,03,13,23,33,44,-24,-14,04,14,24,34,4U11GND2TRIG3OUT4RESET5CTRL6THRES7DISCH8VCCR11kΩR210kΩC110uFC210nFJ11VCC2OUT3GNDGNDNODENODEOUTVCC \ No newline at end of file diff --git a/tests/repros/repro51-junction-hop-overlap.test.tsx b/tests/repros/repro51-junction-hop-overlap.test.tsx new file mode 100644 index 000000000..e9af5d07d --- /dev/null +++ b/tests/repros/repro51-junction-hop-overlap.test.tsx @@ -0,0 +1,73 @@ +import { test, expect } from "bun:test" +import { getTestFixture } from "../fixtures/get-test-fixture" + +test("repro51: junction and hop over traces do not overlap", () => { + const { circuit } = getTestFixture() + + circuit.add( + + {/* --- 555 timer as a generic 8‑pin chip --- */} + + {/* VCC -> DISCH */} + {" "} + {/* DISCH -> node */} + {" "} + {/* node -> GND */} + {" "} + {/* CTRL -> GND (stability) */} + {/* 3-pin header for power + output */} + + {/* Power & housekeeping */} + + + + + + {/* Astable wiring: tie THRES & TRIG; R1, R2, C1 form RC network */} + + + + + + {/* R1 from VCC to DISCH; R2 from DISCH to node */} + + {/* Output to header */} + + , + ) + + circuit.render() + + expect(circuit).toMatchSchematicSnapshot(import.meta.path) +})