diff --git a/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts b/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts index 31e0df39..f6c2539f 100644 --- a/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +++ b/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts @@ -66,6 +66,16 @@ export class LongDistancePairSolver extends BaseSolver { } } + // Build helpers to skip nets that are handled by net labels only + // (mirrors the same logic in MspConnectionPairSolver). + const directlyWiredPinIds = new Set() + for (const dc of inputProblem.directConnections) { + for (const pid of dc.pinIds) directlyWiredPinIds.add(pid) + } + const netLabelOrientedNets = new Set( + Object.keys(inputProblem.availableNetLabelOrientations ?? {}), + ) + // 2. Generate candidate pairs using N-Nearest-Neighbors approach const candidatePairs: Array< [InputPin & { chipId: string }, InputPin & { chipId: string }] @@ -76,8 +86,18 @@ export class LongDistancePairSolver extends BaseSolver { const allPinIdsInNet = netConnMap.getIdsConnectedToNet(netId) if (allPinIdsInNet.length < 2) continue + // Skip nets that are handled by net labels only — same logic as + // MspConnectionPairSolver: no direct-wired pin AND has a label orientation. + const hasDirect = allPinIdsInNet.some((id: string) => + directlyWiredPinIds.has(id), + ) + const hasLabel = allPinIdsInNet.some((id: string) => + netLabelOrientedNets.has(id), + ) + if (!hasDirect && hasLabel) continue + const unconnectedPinIds = allPinIdsInNet.filter( - (pinId) => !primaryConnectedPinIds.has(pinId), + (pinId: string) => !primaryConnectedPinIds.has(pinId), ) for (const unconnectedPinId of unconnectedPinIds) { diff --git a/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts b/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts index 48b46c90..d7b53162 100644 --- a/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +++ b/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts @@ -74,7 +74,40 @@ export class MspConnectionPairSolver extends BaseSolver { } } - this.queuedDcNetIds = Object.keys(netConnMap.netMap) + // Build a set of all pinIds that have explicit direct wire connections. + const directlyWiredPinIds = new Set() + for (const dc of inputProblem.directConnections) { + for (const pid of dc.pinIds) { + directlyWiredPinIds.add(pid) + } + } + + // Build a set of nets that have configured net-label orientations. + // These nets will be represented by net labels in the schematic, not wire + // traces — but only when none of their pins appear in directConnections. + const netLabelOrientedNets = new Set( + Object.keys(inputProblem.availableNetLabelOrientations ?? {}), + ) + + // Only queue nets that need wire traces: + // • Nets with at least one directly-wired pin always need a trace. + // • Nets with NO directly-wired pins but also NO label orientations still + // need a trace (there is no other way to show the connection). + // • Nets with NO directly-wired pins AND a label orientation are skipped + // because NetLabelPlacementSolver will place the label instead. + this.queuedDcNetIds = Object.keys(netConnMap.netMap).filter((netId) => { + const connectedIds = netConnMap.getIdsConnectedToNet(netId) as string[] + const hasDirect = connectedIds.some((id) => directlyWiredPinIds.has(id)) + if (hasDirect) return true + // connectedIds includes the user-provided net label string (e.g. "GND") + // because ConnectivityMap treats the net label as just another member of + // the group. Check whether any connected ID is a net with a configured + // label orientation — if so, NetLabelPlacementSolver will handle it. + const hasLabelOrientation = connectedIds.some((id) => + netLabelOrientedNets.has(id), + ) + return !hasLabelOrientation + }) } override getConstructorParams(): ConstructorParameters< diff --git a/site/SchematicTracePipelineSolver/SchematicTracePipelineSolver_issue79.page.tsx b/site/SchematicTracePipelineSolver/SchematicTracePipelineSolver_issue79.page.tsx new file mode 100644 index 00000000..97492684 --- /dev/null +++ b/site/SchematicTracePipelineSolver/SchematicTracePipelineSolver_issue79.page.tsx @@ -0,0 +1,64 @@ +/** + * Demo page for issue #79: + * "Fix extra net label in repro61, or remove trace" + * + * This circuit has VCC and GND connected exclusively via netConnections + * with availableNetLabelOrientations. Before the fix both nets also + * produced spurious wire traces on top of their net labels. After the + * fix only the two signal traces (U1.out→R1.pin1, U1.sigA→R1.pin2) + * are drawn; VCC and GND are shown by net labels only. + */ +import { PipelineDebugger } from "site/components/PipelineDebugger" +import type { InputProblem } from "lib/types/InputProblem" + +export const inputProblem: InputProblem = { + chips: [ + { + chipId: "U1", + center: { x: 0, y: 0 }, + width: 2.4, + height: 1.0, + pins: [ + { pinId: "U1.1", x: -1.2, y: 0.3 }, + { pinId: "U1.2", x: -1.2, y: 0.1 }, + { pinId: "U1.3", x: -1.2, y: -0.3 }, + { pinId: "U1.4", x: 1.2, y: 0.3 }, + ], + }, + { + chipId: "R1", + center: { x: 2.5, y: 0.3 }, + width: 1.0, + height: 0.4, + pins: [ + { pinId: "R1.1", x: 2.0, y: 0.3 }, + { pinId: "R1.2", x: 3.0, y: 0.3 }, + ], + }, + { + chipId: "C1", + center: { x: -2.5, y: -0.3 }, + width: 0.5, + height: 0.8, + pins: [ + { pinId: "C1.1", x: -2.5, y: 0.1 }, + { pinId: "C1.2", x: -2.5, y: -0.7 }, + ], + }, + ], + directConnections: [ + { pinIds: ["U1.4", "R1.1"], netId: "U1.out to R1.pin1" }, + { pinIds: ["U1.2", "R1.2"], netId: "U1.sigA to R1.pin2" }, + ], + netConnections: [ + { netId: "VCC", pinIds: ["U1.1", "C1.1"] }, + { netId: "GND", pinIds: ["U1.3", "C1.2"] }, + ], + availableNetLabelOrientations: { + VCC: ["y+"], + GND: ["y-"], + }, + maxMspPairDistance: 5, +} + +export default () => diff --git a/tests/examples/__snapshots__/example01.snap.svg b/tests/examples/__snapshots__/example01.snap.svg index 293bf05a..b596f082 100644 --- a/tests/examples/__snapshots__/example01.snap.svg +++ b/tests/examples/__snapshots__/example01.snap.svg @@ -2,97 +2,103 @@ +x-" data-x="-0.8" data-y="0.2" cx="422.5742574257426" cy="289.44950495049505" r="3" fill="hsl(319, 100%, 50%, 0.8)" /> +x-" data-x="-0.8" data-y="0" cx="422.5742574257426" cy="311.62772277227725" r="3" fill="hsl(320, 100%, 50%, 0.8)" /> +x-" data-x="-0.8" data-y="-0.2" cx="422.5742574257426" cy="333.80594059405945" r="3" fill="hsl(321, 100%, 50%, 0.8)" /> +x+" data-x="0.8" data-y="-0.2" cx="600" cy="333.80594059405945" r="3" fill="hsl(322, 100%, 50%, 0.8)" /> +x+" data-x="0.8" data-y="0" cx="600" cy="311.62772277227725" r="3" fill="hsl(323, 100%, 50%, 0.8)" /> +x+" data-x="0.8" data-y="0.2" cx="600" cy="289.44950495049505" r="3" fill="hsl(324, 100%, 50%, 0.8)" /> +y+" data-x="-2" data-y="0.5" cx="289.50495049504957" cy="256.1821782178218" r="3" fill="hsl(121, 100%, 50%, 0.8)" /> +y-" data-x="-2" data-y="-0.5" cx="289.50495049504957" cy="367.0732673267327" r="3" fill="hsl(122, 100%, 50%, 0.8)" /> +y+" data-x="-4" data-y="0.5" cx="67.72277227722776" cy="256.1821782178218" r="3" fill="hsl(2, 100%, 50%, 0.8)" /> +y-" data-x="-4" data-y="-0.5" cx="67.72277227722776" cy="367.0732673267327" r="3" fill="hsl(3, 100%, 50%, 0.8)" /> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + +