@tscircuit/schematic-trace-solver 0.0.98 → 0.0.99

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.
Files changed (18) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +310 -220
  3. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +19 -9
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +30 -0
  5. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/generateEndpointCollisionDetours.ts +84 -0
  6. package/package.json +1 -1
  7. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +18 -18
  8. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +7 -7
  9. package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +5 -7
  10. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +6 -12
  11. package/tests/examples/__snapshots__/example09.snap.svg +9 -13
  12. package/tests/examples/__snapshots__/example21.snap.svg +30 -32
  13. package/tests/examples/__snapshots__/example29.snap.svg +13 -19
  14. package/tests/repros/__snapshots__/repro129-host-custom-symbol-passives.snap.svg +60 -0
  15. package/tests/repros/assets/repro129-host-custom-symbol-passives.input.json +94 -0
  16. package/tests/repros/repro129-host-custom-symbol-passives.test.ts +16 -0
  17. package/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-direct-connection-distance.test.ts +46 -0
  18. package/tests/solvers/SchematicTraceSingleLineSolver2/generate-endpoint-collision-detours.test.ts +38 -0
@@ -0,0 +1,60 @@
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="0.9000000000000004,1.25 1.9700000000000002,1.1099999999999999" data-type="line" data-label="" points="312.06477732793525,134.84480431848863 473.7921727395411,156.00539811066136" fill="none" stroke="hsl(248, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="0.9000000000000004,0.8499999999999999 1.9000000000000004,-0.7" data-type="line" data-label="" points="312.06477732793525,195.30364372469646 463.2118758434548,429.5816464237517" fill="none" stroke="hsl(248, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="0.9000000000000004,0.44999999999999996 1.9700000000000002,0.3500000000000001" data-type="line" data-label="" points="312.06477732793525,255.76248313090423 473.7921727395411,270.87719298245617" fill="none" stroke="hsl(248, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="0.9000000000000004,-1.3000000000000003 1.9000000000000004,-1.3" data-type="line" data-label="" points="312.06477732793525,520.2699055330635 463.2118758434548,520.2699055330634" fill="none" stroke="hsl(248, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="0.9000000000000004,1.25 1.9700000000000002,1.25 1.9700000000000002,1.1099999999999999" data-type="line" data-label="" points="312.06477732793525,134.84480431848863 473.7921727395411,134.84480431848863 473.7921727395411,156.00539811066136" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="0.9000000000000004,0.8499999999999999 1.2725000000000004,0.8499999999999999 1.2725000000000004,0.05000000000000007 1.9000000000000004,0.05000000000000007 1.9000000000000004,-0.7" data-type="line" data-label="" points="312.06477732793525,195.30364372469646 368.36707152496626,195.30364372469646 368.36707152496626,316.22132253711203 463.2118758434548,316.22132253711203 463.2118758434548,429.5816464237517" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="0.9000000000000004,0.44999999999999996 1.4350000000000003,0.44999999999999996 1.4350000000000003,0.2500000000000001 1.9700000000000002,0.2500000000000001 1.9700000000000002,0.3500000000000001" data-type="line" data-label="" points="312.06477732793525,255.76248313090423 392.9284750337382,255.76248313090423 392.9284750337382,285.9919028340081 473.7921727395411,285.9919028340081 473.7921727395411,270.87719298245617" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="0.9000000000000004,-1.3000000000000003 1.9000000000000004,-1.3" data-type="line" data-label="" points="312.06477732793525,520.2699055330635 463.2118758434548,520.2699055330634" fill="none" stroke="purple" stroke-width="1px"/></g><g><rect data-type="rect" data-label="schematic_component_0" data-x="1.9975000000000005" data-y="-1" x="414.08906882591106" y="429.58164642375175" width="127.71929824561391" height="90.68825910931167" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.00661607142857143"/></g><g><rect data-type="rect" data-label="schematic_component_1" data-x="2.2250000000000005" data-y="0.73" x="424.66936572199734" y="156.00539811066136" width="175.33063427800266" height="114.8717948717948" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.00661607142857143"/></g><g><rect data-type="rect" data-label="schematic_component_2" data-x="0" data-y="0" x="40" y="74.3859649122808" width="272.06477732793525" height="498.78542510121446" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.00661607142857143"/></g><g><rect data-type="rect" data-label="netId: .HOST &gt; .pin2 to R1.pin1
2
+ globalConnNetId: connectivity_net1" data-x="1.2725000000000004" data-y="-0.17499999999999993" x="353.25236167341427" y="316.22132253711203" width="30.22941970310393" height="68.0161943319838" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.00661607142857143"/></g><g><rect data-type="rect" data-label="netId: .HOST &gt; .pin4 to R1.pin2
3
+ globalConnNetId: connectivity_net3" data-x="1.4000000000000004" data-y="-1.0750000000000002" x="372.52361673414305" y="452.2537112010797" width="30.22941970310393" height="68.0161943319838" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.00661607142857143"/></g><g><rect data-type="rect" data-label="netId: .HOST &gt; .pin1 to C1.pin1
4
+ globalConnNetId: connectivity_net0" data-x="1.9700000000000002" data-y="1.475" x="458.6774628879891" y="66.82860998650477" width="30.229419703103986" height="68.01619433198385" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.00661607142857143"/></g><g><rect data-type="rect" data-label="netId: .HOST &gt; .pin3 to C1.pin2
5
+ globalConnNetId: connectivity_net2" data-x="1.4000000000000004" data-y="0.6749999999999999" x="372.52361673414305" y="187.7462887989205" width="30.22941970310393" height="68.01619433198377" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.00661607142857143"/></g><g><circle data-type="point" data-label="R1.1
6
+ y+" data-x="1.9000000000000004" data-y="-0.7" cx="463.2118758434548" cy="429.5816464237517" r="3" fill="hsl(226, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="R1.2
7
+ y-" data-x="1.9000000000000004" data-y="-1.3" cx="463.2118758434548" cy="520.2699055330634" r="3" fill="hsl(227, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="C1.1
8
+ y+" data-x="1.9700000000000002" data-y="1.1099999999999999" cx="473.7921727395411" cy="156.00539811066136" r="3" fill="hsl(121, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="C1.2
9
+ y-" data-x="1.9700000000000002" data-y="0.3500000000000001" cx="473.7921727395411" cy="270.87719298245617" r="3" fill="hsl(122, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="HOST.pin1
10
+ x+" data-x="0.9000000000000004" data-y="1.25" cx="312.06477732793525" cy="134.84480431848863" r="3" fill="hsl(50, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="HOST.pin2
11
+ x+" data-x="0.9000000000000004" data-y="0.8499999999999999" cx="312.06477732793525" cy="195.30364372469646" r="3" fill="hsl(51, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="HOST.pin3
12
+ x+" data-x="0.9000000000000004" data-y="0.44999999999999996" cx="312.06477732793525" cy="255.76248313090423" r="3" fill="hsl(52, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="HOST.pin4
13
+ x+" data-x="0.9000000000000004" data-y="-1.3000000000000003" cx="312.06477732793525" cy="520.2699055330635" r="3" fill="hsl(53, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
14
+ orientation: y-" data-x="1.2725000000000004" data-y="0.05000000000000007" cx="368.36707152496626" cy="316.22132253711203" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
15
+ orientation: y+" data-x="1.4000000000000004" data-y="-1.3000000000000003" cx="387.63832658569504" cy="520.2699055330635" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
16
+ orientation: y+" data-x="1.9700000000000002" data-y="1.25" cx="473.7921727395411" cy="134.84480431848863" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
17
+ orientation: y+" data-x="1.4000000000000004" data-y="0.44999999999999996" cx="387.63832658569504" cy="255.76248313090423" 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
+ document.currentScript.parentElement.addEventListener('mousemove', (e) => {
19
+ const svg = e.currentTarget;
20
+ const rect = svg.getBoundingClientRect();
21
+ const x = e.clientX - rect.left;
22
+ const y = e.clientY - rect.top;
23
+ const crosshair = svg.getElementById('crosshair');
24
+ const h = svg.getElementById('crosshair-h');
25
+ const v = svg.getElementById('crosshair-v');
26
+ const coords = svg.getElementById('coordinates');
27
+
28
+ crosshair.style.display = 'block';
29
+ h.setAttribute('x1', '0');
30
+ h.setAttribute('x2', '640');
31
+ h.setAttribute('y1', y);
32
+ h.setAttribute('y2', y);
33
+ v.setAttribute('x1', x);
34
+ v.setAttribute('x2', x);
35
+ v.setAttribute('y1', '0');
36
+ v.setAttribute('y2', '640');
37
+
38
+ // Calculate real coordinates using inverse transformation
39
+ const matrix = {"a":151.14709851551953,"c":0,"e":176.03238866396762,"b":0,"d":-151.14709851551953,"f":323.778677462888};
40
+ // Manually invert and apply the affine transform
41
+ // Since we only use translate and scale, we can directly compute:
42
+ // x' = (x - tx) / sx
43
+ // y' = (y - ty) / sy
44
+ const sx = matrix.a;
45
+ const sy = matrix.d;
46
+ const tx = matrix.e;
47
+ const ty = matrix.f;
48
+ const realPoint = {
49
+ x: (x - tx) / sx,
50
+ y: (y - ty) / sy // Flip y back since we used negative scale
51
+ }
52
+
53
+ coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
54
+ coords.setAttribute('x', (x + 5).toString());
55
+ coords.setAttribute('y', (y - 5).toString());
56
+ });
57
+ document.currentScript.parentElement.addEventListener('mouseleave', () => {
58
+ document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
59
+ });
60
+ ]]></script></svg>
@@ -0,0 +1,94 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "schematic_component_0",
5
+ "center": { "x": 1.9975000000000005, "y": -1 },
6
+ "width": 0.845,
7
+ "height": 0.6000000000000001,
8
+ "pins": [
9
+ {
10
+ "pinId": "R1.1",
11
+ "x": 1.9000000000000004,
12
+ "y": -0.7,
13
+ "_facingDirection": "y+"
14
+ },
15
+ {
16
+ "pinId": "R1.2",
17
+ "x": 1.9000000000000004,
18
+ "y": -1.3,
19
+ "_facingDirection": "y-"
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ "chipId": "schematic_component_1",
25
+ "center": { "x": 2.2250000000000005, "y": 0.73 },
26
+ "width": 1.1600000000000004,
27
+ "height": 0.7599999999999998,
28
+ "pins": [
29
+ {
30
+ "pinId": "C1.1",
31
+ "x": 1.9700000000000002,
32
+ "y": 1.1099999999999999,
33
+ "_facingDirection": "y+"
34
+ },
35
+ {
36
+ "pinId": "C1.2",
37
+ "x": 1.9700000000000002,
38
+ "y": 0.3500000000000001,
39
+ "_facingDirection": "y-"
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "chipId": "schematic_component_2",
45
+ "center": { "x": 0, "y": 0 },
46
+ "width": 1.8000000000000007,
47
+ "height": 3.3,
48
+ "pins": [
49
+ {
50
+ "pinId": "HOST.pin1",
51
+ "x": 0.9000000000000004,
52
+ "y": 1.25
53
+ },
54
+ {
55
+ "pinId": "HOST.pin2",
56
+ "x": 0.9000000000000004,
57
+ "y": 0.8499999999999999
58
+ },
59
+ {
60
+ "pinId": "HOST.pin3",
61
+ "x": 0.9000000000000004,
62
+ "y": 0.44999999999999996
63
+ },
64
+ {
65
+ "pinId": "HOST.pin4",
66
+ "x": 0.9000000000000004,
67
+ "y": -1.3000000000000003
68
+ }
69
+ ]
70
+ }
71
+ ],
72
+ "directConnections": [
73
+ {
74
+ "pinIds": ["HOST.pin1", "C1.1"],
75
+ "netId": ".HOST > .pin1 to C1.pin1"
76
+ },
77
+ {
78
+ "pinIds": ["HOST.pin2", "R1.1"],
79
+ "netId": ".HOST > .pin2 to R1.pin1"
80
+ },
81
+ {
82
+ "pinIds": ["HOST.pin3", "C1.2"],
83
+ "netId": ".HOST > .pin3 to C1.pin2"
84
+ },
85
+ {
86
+ "pinIds": ["HOST.pin4", "R1.2"],
87
+ "netId": ".HOST > .pin4 to R1.pin2"
88
+ }
89
+ ],
90
+ "netConnections": [],
91
+ "textBoxes": [],
92
+ "availableNetLabelOrientations": {},
93
+ "maxMspPairDistance": 2.4
94
+ }
@@ -0,0 +1,16 @@
1
+ import { expect, test } from "bun:test"
2
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
+ import "tests/fixtures/matcher"
4
+ import inputProblem from "./assets/repro129-host-custom-symbol-passives.input.json"
5
+
6
+ test("repro129: HOST custom symbol routes all passive connections", () => {
7
+ const solver = new SchematicTracePipelineSolver(inputProblem as any)
8
+
9
+ solver.solve()
10
+
11
+ expect(solver.schematicTraceLinesSolver!.solvedTracePaths).toHaveLength(4)
12
+ expect(solver.schematicTraceLinesSolver!.failedConnectionPairs).toHaveLength(
13
+ 0,
14
+ )
15
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
16
+ })
@@ -0,0 +1,46 @@
1
+ import { expect, test } from "bun:test"
2
+ import { MspConnectionPairSolver } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
3
+ import type { InputProblem } from "lib/types/InputProblem"
4
+
5
+ const createInputProblem = (): InputProblem => ({
6
+ chips: [
7
+ {
8
+ chipId: "U1",
9
+ center: { x: 0, y: 0 },
10
+ width: 1,
11
+ height: 1,
12
+ pins: [{ pinId: "U1.1", x: 0, y: 0, _facingDirection: "x+" }],
13
+ },
14
+ {
15
+ chipId: "U2",
16
+ center: { x: 2, y: 2 },
17
+ width: 1,
18
+ height: 1,
19
+ pins: [{ pinId: "U2.1", x: 2, y: 2, _facingDirection: "x-" }],
20
+ },
21
+ ],
22
+ directConnections: [{ pinIds: ["U1.1", "U2.1"] }],
23
+ netConnections: [],
24
+ availableNetLabelOrientations: {},
25
+ maxMspPairDistance: 3,
26
+ })
27
+
28
+ test("two-pin direct connections use Euclidean max distance", () => {
29
+ const directConnectionSolver = new MspConnectionPairSolver({
30
+ inputProblem: createInputProblem(),
31
+ })
32
+ directConnectionSolver.solve()
33
+
34
+ const netConnectionProblem = createInputProblem()
35
+ netConnectionProblem.directConnections = []
36
+ netConnectionProblem.netConnections = [
37
+ { pinIds: ["U1.1", "U2.1"], netId: "SIG" },
38
+ ]
39
+ const netConnectionSolver = new MspConnectionPairSolver({
40
+ inputProblem: netConnectionProblem,
41
+ })
42
+ netConnectionSolver.solve()
43
+
44
+ expect(directConnectionSolver.mspConnectionPairs).toHaveLength(1)
45
+ expect(netConnectionSolver.mspConnectionPairs).toHaveLength(0)
46
+ })
@@ -0,0 +1,38 @@
1
+ import { expect, test } from "bun:test"
2
+ import { generateEndpointCollisionDetours } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/generateEndpointCollisionDetours"
3
+
4
+ test("endpoint collision detours preserve both endpoint anchors", () => {
5
+ const path = [
6
+ { x: 0, y: 1 },
7
+ { x: 2, y: 1 },
8
+ { x: 2, y: -1 },
9
+ ]
10
+
11
+ const obstacle = {
12
+ minX: 1,
13
+ maxX: 3,
14
+ minY: 0,
15
+ maxY: 2,
16
+ kind: "chip" as const,
17
+ chipId: "U1",
18
+ }
19
+
20
+ const detours = [0, 1].flatMap((collidingSegmentIndex) =>
21
+ generateEndpointCollisionDetours({
22
+ path,
23
+ collidingSegmentIndex,
24
+ obstacle,
25
+ }),
26
+ )
27
+
28
+ expect(detours.length).toBeGreaterThan(0)
29
+ expect(
30
+ detours.every(
31
+ (detour) =>
32
+ detour[0]?.x === path[0]?.x &&
33
+ detour[0]?.y === path[0]?.y &&
34
+ detour.at(-1)?.x === path.at(-1)?.x &&
35
+ detour.at(-1)?.y === path.at(-1)?.y,
36
+ ),
37
+ ).toBe(true)
38
+ })