@tscircuit/schematic-trace-solver 0.0.123 → 0.0.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +51 -2
- package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +7 -2
- package/lib/solvers/TraceCleanupSolver/shortenExcessiveLabelPaddingDetour.ts +64 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +18 -18
- package/tests/bug-reports/bug-report-20260804T225912Z/bug-report-20260804T225912Z.test.ts +18 -0
package/dist/index.js
CHANGED
|
@@ -5108,6 +5108,51 @@ var getVisibleTraceMetrics = (traces) => {
|
|
|
5108
5108
|
var getVisibleTraceLength = (traces) => getVisibleTraceMetrics(traces).length;
|
|
5109
5109
|
var getVisibleTraceSegmentCount = (traces) => getVisibleTraceMetrics(traces).segmentCount;
|
|
5110
5110
|
|
|
5111
|
+
// lib/solvers/TraceCleanupSolver/shortenExcessiveLabelPaddingDetour.ts
|
|
5112
|
+
var MIN_EXCESSIVE_DETOUR_LENGTH_RATIO = 2.5;
|
|
5113
|
+
var getTracePathLength = (tracePath) => {
|
|
5114
|
+
let pathLength = 0;
|
|
5115
|
+
for (let pointIndex = 1; pointIndex < tracePath.length; pointIndex++) {
|
|
5116
|
+
const previousPoint = tracePath[pointIndex - 1];
|
|
5117
|
+
const point = tracePath[pointIndex];
|
|
5118
|
+
pathLength += Math.abs(point.x - previousPoint.x) + Math.abs(point.y - previousPoint.y);
|
|
5119
|
+
}
|
|
5120
|
+
return pathLength;
|
|
5121
|
+
};
|
|
5122
|
+
var shortenExcessiveLabelPaddingDetour = ({
|
|
5123
|
+
inputProblem,
|
|
5124
|
+
trace
|
|
5125
|
+
}) => {
|
|
5126
|
+
const exactConnection = inputProblem.directConnections.find(
|
|
5127
|
+
(connection) => connection.pinIds.every((pinId) => trace.pinIds.includes(pinId))
|
|
5128
|
+
);
|
|
5129
|
+
if (!exactConnection) return trace;
|
|
5130
|
+
const chipMap = {};
|
|
5131
|
+
for (const chip of inputProblem.chips) chipMap[chip.chipId] = chip;
|
|
5132
|
+
const candidateSolver = new SchematicTraceSingleLineSolver2({
|
|
5133
|
+
pins: trace.pins,
|
|
5134
|
+
connectionPair: trace,
|
|
5135
|
+
inputProblem: {
|
|
5136
|
+
...inputProblem,
|
|
5137
|
+
directConnections: [
|
|
5138
|
+
exactConnection,
|
|
5139
|
+
...inputProblem.directConnections.filter(
|
|
5140
|
+
(connection) => connection !== exactConnection
|
|
5141
|
+
)
|
|
5142
|
+
]
|
|
5143
|
+
},
|
|
5144
|
+
chipMap
|
|
5145
|
+
});
|
|
5146
|
+
candidateSolver.solve();
|
|
5147
|
+
if (!candidateSolver.solvedTracePath) return trace;
|
|
5148
|
+
const currentLength = getTracePathLength(trace.tracePath);
|
|
5149
|
+
const candidateLength = getTracePathLength(candidateSolver.solvedTracePath);
|
|
5150
|
+
if (currentLength <= candidateLength * MIN_EXCESSIVE_DETOUR_LENGTH_RATIO) {
|
|
5151
|
+
return trace;
|
|
5152
|
+
}
|
|
5153
|
+
return { ...trace, tracePath: candidateSolver.solvedTracePath };
|
|
5154
|
+
};
|
|
5155
|
+
|
|
5111
5156
|
// lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts
|
|
5112
5157
|
var minimizeTurnsWithFilteredLabels = ({
|
|
5113
5158
|
targetMspConnectionPairId,
|
|
@@ -5117,12 +5162,16 @@ var minimizeTurnsWithFilteredLabels = ({
|
|
|
5117
5162
|
mergedLabelNetIdMap,
|
|
5118
5163
|
paddingBuffer
|
|
5119
5164
|
}) => {
|
|
5120
|
-
const
|
|
5165
|
+
const originalTargetTrace = traces.find(
|
|
5121
5166
|
(t) => t.mspPairId === targetMspConnectionPairId
|
|
5122
5167
|
);
|
|
5123
|
-
if (!
|
|
5168
|
+
if (!originalTargetTrace) {
|
|
5124
5169
|
throw new Error(`Target trace ${targetMspConnectionPairId} not found`);
|
|
5125
5170
|
}
|
|
5171
|
+
const targetTrace = shortenExcessiveLabelPaddingDetour({
|
|
5172
|
+
inputProblem,
|
|
5173
|
+
trace: originalTargetTrace
|
|
5174
|
+
});
|
|
5126
5175
|
const targetPinIds = new Set(targetTrace.pinIds);
|
|
5127
5176
|
const otherTraces = traces.filter(
|
|
5128
5177
|
(trace) => trace.mspPairId !== targetMspConnectionPairId
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getVisibleTraceSegmentCount,
|
|
10
10
|
RAIL_ALIGNMENT_EPSILON,
|
|
11
11
|
} from "./sameNetRailAlignment/geometry"
|
|
12
|
+
import { shortenExcessiveLabelPaddingDetour } from "./shortenExcessiveLabelPaddingDetour"
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Minimizes turns with a strict pass that treats every other trace as an
|
|
@@ -31,12 +32,16 @@ export const minimizeTurnsWithFilteredLabels = ({
|
|
|
31
32
|
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
32
33
|
paddingBuffer: number
|
|
33
34
|
}): SolvedTracePath => {
|
|
34
|
-
const
|
|
35
|
+
const originalTargetTrace = traces.find(
|
|
35
36
|
(t) => t.mspPairId === targetMspConnectionPairId,
|
|
36
37
|
)
|
|
37
|
-
if (!
|
|
38
|
+
if (!originalTargetTrace) {
|
|
38
39
|
throw new Error(`Target trace ${targetMspConnectionPairId} not found`)
|
|
39
40
|
}
|
|
41
|
+
const targetTrace = shortenExcessiveLabelPaddingDetour({
|
|
42
|
+
inputProblem,
|
|
43
|
+
trace: originalTargetTrace,
|
|
44
|
+
})
|
|
40
45
|
|
|
41
46
|
const targetPinIds = new Set(targetTrace.pinIds)
|
|
42
47
|
const otherTraces = traces.filter(
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
2
|
+
import { SchematicTraceSingleLineSolver2 } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
|
|
3
|
+
import type { InputChip, InputProblem } from "lib/types/InputProblem"
|
|
4
|
+
|
|
5
|
+
const MIN_EXCESSIVE_DETOUR_LENGTH_RATIO = 2.5
|
|
6
|
+
|
|
7
|
+
const getTracePathLength = (
|
|
8
|
+
tracePath: SolvedTracePath["tracePath"],
|
|
9
|
+
): number => {
|
|
10
|
+
let pathLength = 0
|
|
11
|
+
for (let pointIndex = 1; pointIndex < tracePath.length; pointIndex++) {
|
|
12
|
+
const previousPoint = tracePath[pointIndex - 1]!
|
|
13
|
+
const point = tracePath[pointIndex]!
|
|
14
|
+
pathLength +=
|
|
15
|
+
Math.abs(point.x - previousPoint.x) + Math.abs(point.y - previousPoint.y)
|
|
16
|
+
}
|
|
17
|
+
return pathLength
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Retries an already-solved severe detour with its exact direct connection
|
|
22
|
+
* first, preventing a sibling branch's label width from inflating text padding.
|
|
23
|
+
* The shorter route is used only when it is less than 40% of the original.
|
|
24
|
+
*/
|
|
25
|
+
export const shortenExcessiveLabelPaddingDetour = ({
|
|
26
|
+
inputProblem,
|
|
27
|
+
trace,
|
|
28
|
+
}: {
|
|
29
|
+
inputProblem: InputProblem
|
|
30
|
+
trace: SolvedTracePath
|
|
31
|
+
}): SolvedTracePath => {
|
|
32
|
+
const exactConnection = inputProblem.directConnections.find((connection) =>
|
|
33
|
+
connection.pinIds.every((pinId) => trace.pinIds.includes(pinId)),
|
|
34
|
+
)
|
|
35
|
+
if (!exactConnection) return trace
|
|
36
|
+
|
|
37
|
+
const chipMap: Record<string, InputChip> = {}
|
|
38
|
+
for (const chip of inputProblem.chips) chipMap[chip.chipId] = chip
|
|
39
|
+
|
|
40
|
+
const candidateSolver = new SchematicTraceSingleLineSolver2({
|
|
41
|
+
pins: trace.pins,
|
|
42
|
+
connectionPair: trace,
|
|
43
|
+
inputProblem: {
|
|
44
|
+
...inputProblem,
|
|
45
|
+
directConnections: [
|
|
46
|
+
exactConnection,
|
|
47
|
+
...inputProblem.directConnections.filter(
|
|
48
|
+
(connection) => connection !== exactConnection,
|
|
49
|
+
),
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
chipMap,
|
|
53
|
+
})
|
|
54
|
+
candidateSolver.solve()
|
|
55
|
+
if (!candidateSolver.solvedTracePath) return trace
|
|
56
|
+
|
|
57
|
+
const currentLength = getTracePathLength(trace.tracePath)
|
|
58
|
+
const candidateLength = getTracePathLength(candidateSolver.solvedTracePath)
|
|
59
|
+
if (currentLength <= candidateLength * MIN_EXCESSIVE_DETOUR_LENGTH_RATIO) {
|
|
60
|
+
return trace
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { ...trace, tracePath: candidateSolver.solvedTracePath }
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
<svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="-1.2000000000000002,0.30000000000000004 -3.79,0.39" data-type="line" data-label="" points="357.4007220216606,
|
|
2
|
-
globalConnNetId: connectivity_net1" data-x="-3.79" data-y="-0.8" x="71.33574007220213" y="
|
|
3
|
-
globalConnNetId: connectivity_net0" data-x="-
|
|
4
|
-
y-" data-x="-3.79" data-y="-0.3899999999999999" cx="95.5956678700361" cy="
|
|
5
|
-
y+" data-x="-3.79" data-y="0.39" cx="95.5956678700361" cy="
|
|
6
|
-
y+" data-x="-2.22" data-y="0.38000000000000006" cx="254.29602888086637" cy="
|
|
7
|
-
y-" data-x="-2.22" data-y="-0.37999999999999995" cx="254.29602888086637" cy="
|
|
8
|
-
x-" data-x="-1.2000000000000002" data-y="0.30000000000000004" cx="357.4007220216606" cy="
|
|
9
|
-
x-" data-x="-1.2000000000000002" data-y="0.10000000000000003" cx="357.4007220216606" cy="
|
|
10
|
-
x-" data-x="-1.2000000000000002" data-y="-0.09999999999999998" cx="357.4007220216606" cy="
|
|
11
|
-
x-" data-x="-1.2000000000000002" data-y="-0.30000000000000004" cx="357.4007220216606" cy="
|
|
12
|
-
x+" data-x="1.2000000000000002" data-y="-0.30000000000000004" cx="600" cy="
|
|
13
|
-
x+" data-x="1.2000000000000002" data-y="-0.10000000000000003" cx="600" cy="
|
|
14
|
-
x+" data-x="1.2000000000000002" data-y="0.09999999999999998" cx="600" cy="
|
|
15
|
-
x+" data-x="1.2000000000000002" data-y="0.30000000000000004" cx="600" cy="
|
|
16
|
-
orientation: y-" data-x="-3.79" data-y="-0.5900000000000001" cx="95.5956678700361" cy="
|
|
17
|
-
orientation: y+" data-x="-
|
|
1
|
+
<svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="-1.2000000000000002,0.30000000000000004 -3.79,0.39" data-type="line" data-label="" points="357.4007220216606,296.2454873646209 95.5956678700361,287.1480144404332" fill="none" stroke="hsl(136, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="-1.2000000000000002,0.30000000000000004 -2.22,0.38000000000000006" data-type="line" data-label="" points="357.4007220216606,296.2454873646209 254.29602888086637,288.1588447653429" fill="none" stroke="hsl(136, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="-3.79,-0.3899999999999999 -2.22,-0.37999999999999995" data-type="line" data-label="" points="95.5956678700361,365.9927797833935 254.29602888086637,364.9819494584837" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1px" stroke-dasharray="4 2"/></g><g><polyline data-points="-2.22,0.3800000000000002 -2.22,0.5800000000000003 -3.79,0.5800000000000003 -3.79,0.39" data-type="line" data-label="" points="254.29602888086637,288.1588447653429 254.29602888086637,267.94223826714796 95.5956678700361,267.94223826714796 95.5956678700361,287.1480144404332" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="-1.2000000000000002,0.3 -1.4125,0.3 -1.4125,0.5800000000000003 -2.22,0.5800000000000003 -2.22,0.38000000000000006" data-type="line" data-label="" points="357.4007220216606,296.24548736462094 335.9205776173285,296.24548736462094 335.9205776173285,267.94223826714796 254.29602888086637,267.94223826714796 254.29602888086637,288.1588447653429" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="-3.79,-0.3899999999999999 -3.79,-0.5900000000000001 -2.22,-0.5900000000000001 -2.22,-0.3800000000000001" data-type="line" data-label="" points="95.5956678700361,365.9927797833935 95.5956678700361,386.2093862815884 254.29602888086637,386.2093862815884 254.29602888086637,364.98194945848377" fill="none" stroke="purple" stroke-width="1px"/></g><g><rect data-type="rect" data-label="schematic_component_0" data-x="-3.675" data-y="5.551115123125783e-17" x="40" y="287.1480144404332" width="134.44043321299637" height="78.84476534296027" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="schematic_component_1" data-x="-2.085" data-y="5.551115123125783e-17" x="221.44404332129963" y="288.1588447653429" width="92.99638989169677" height="76.8231046931408" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="schematic_component_2" data-x="0" data-y="0" x="357.4007220216606" y="276.028880866426" width="242.5992779783394" height="101.08303249097469" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="U1" data-x="-0.68" data-y="0.615" x="391.76895306859205" y="251.76895306859208" width="36.38989169675091" height="25.270758122743644" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="netId: GND
|
|
2
|
+
globalConnNetId: connectivity_net1" data-x="-3.79" data-y="-0.8" x="71.33574007220213" y="386.2093862815884" width="48.519855595667934" height="42.45487364620942" fill="#00000066" stroke="#000000" stroke-width="0.009892857142857142"/></g><g><rect data-type="rect" data-label="netId: .U1 > .pin1 to .C2 > .pin1
|
|
3
|
+
globalConnNetId: connectivity_net0" data-x="-1.3062500000000001" data-y="0.72" x="336.55234657039705" y="211.33574007220213" width="20.216606498195006" height="84.90974729241879" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.009892857142857142"/></g><g><circle data-type="point" data-label="schematic_port_0
|
|
4
|
+
y-" data-x="-3.79" data-y="-0.3899999999999999" cx="95.5956678700361" cy="365.9927797833935" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_1
|
|
5
|
+
y+" data-x="-3.79" data-y="0.39" cx="95.5956678700361" cy="287.1480144404332" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_2
|
|
6
|
+
y+" data-x="-2.22" data-y="0.38000000000000006" cx="254.29602888086637" cy="288.1588447653429" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_3
|
|
7
|
+
y-" data-x="-2.22" data-y="-0.37999999999999995" cx="254.29602888086637" cy="364.9819494584837" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_4
|
|
8
|
+
x-" data-x="-1.2000000000000002" data-y="0.30000000000000004" cx="357.4007220216606" cy="296.2454873646209" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_5
|
|
9
|
+
x-" data-x="-1.2000000000000002" data-y="0.10000000000000003" cx="357.4007220216606" cy="316.46209386281583" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_6
|
|
10
|
+
x-" data-x="-1.2000000000000002" data-y="-0.09999999999999998" cx="357.4007220216606" cy="336.6787003610108" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_7
|
|
11
|
+
x-" data-x="-1.2000000000000002" data-y="-0.30000000000000004" cx="357.4007220216606" cy="356.8953068592058" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_8
|
|
12
|
+
x+" data-x="1.2000000000000002" data-y="-0.30000000000000004" cx="600" cy="356.8953068592058" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_9
|
|
13
|
+
x+" data-x="1.2000000000000002" data-y="-0.10000000000000003" cx="600" cy="336.67870036101084" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_10
|
|
14
|
+
x+" data-x="1.2000000000000002" data-y="0.09999999999999998" cx="600" cy="316.4620938628159" r="3" fill="hsl(184, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_11
|
|
15
|
+
x+" data-x="1.2000000000000002" data-y="0.30000000000000004" cx="600" cy="296.2454873646209" r="3" fill="hsl(184, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
|
|
16
|
+
orientation: y-" data-x="-3.79" data-y="-0.5900000000000001" cx="95.5956678700361" cy="386.2093862815884" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
|
|
17
|
+
orientation: y+" data-x="-1.3062500000000001" data-y="0.3" cx="346.66064981949455" cy="296.24548736462094" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
|
|
18
18
|
document.currentScript.parentElement.addEventListener('mousemove', (e) => {
|
|
19
19
|
const svg = e.currentTarget;
|
|
20
20
|
const rect = svg.getBoundingClientRect();
|
|
@@ -36,7 +36,7 @@ orientation: y+" data-x="-2.22" data-y="1.78" cx="254.29602888086637" cy="221.44
|
|
|
36
36
|
v.setAttribute('y2', '640');
|
|
37
37
|
|
|
38
38
|
// Calculate real coordinates using inverse transformation
|
|
39
|
-
const matrix = {"a":101.08303249097473,"c":0,"e":478.7003610108303,"b":0,"d":-101.08303249097473,"f":
|
|
39
|
+
const matrix = {"a":101.08303249097473,"c":0,"e":478.7003610108303,"b":0,"d":-101.08303249097473,"f":326.57039711191334};
|
|
40
40
|
// Manually invert and apply the affine transform
|
|
41
41
|
// Since we only use translate and scale, we can directly compute:
|
|
42
42
|
// x' = (x - tx) / sx
|
|
@@ -3,10 +3,28 @@ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipeline
|
|
|
3
3
|
import inputProblem from "./bug-report-20260804T225912Z.json"
|
|
4
4
|
import "tests/fixtures/matcher"
|
|
5
5
|
|
|
6
|
+
const EXPECTED_CHIP_TO_CAPACITOR_MAX_Y = 0.58
|
|
7
|
+
|
|
6
8
|
test("bug-report-20260804T225912Z", () => {
|
|
7
9
|
const solver = new SchematicTracePipelineSolver(inputProblem as any)
|
|
8
10
|
|
|
9
11
|
solver.solve()
|
|
10
12
|
|
|
13
|
+
const chipToCapacitorTrace = solver.traceCleanupSolver
|
|
14
|
+
?.getOutput()
|
|
15
|
+
.traces.find(
|
|
16
|
+
(trace) =>
|
|
17
|
+
trace.pinIds.includes("schematic_port_4") &&
|
|
18
|
+
trace.pinIds.includes("schematic_port_2"),
|
|
19
|
+
)
|
|
20
|
+
expect(chipToCapacitorTrace).toBeDefined()
|
|
21
|
+
if (!chipToCapacitorTrace)
|
|
22
|
+
throw new Error("Chip-to-capacitor trace not found")
|
|
23
|
+
|
|
24
|
+
const maxTraceY = Math.max(
|
|
25
|
+
...chipToCapacitorTrace.tracePath.map((point) => point.y),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
expect(maxTraceY).toBeCloseTo(EXPECTED_CHIP_TO_CAPACITOR_MAX_Y)
|
|
11
29
|
expect(solver).toMatchSolverSnapshot(import.meta.path)
|
|
12
30
|
})
|