@tscircuit/schematic-trace-solver 0.0.30 → 0.0.31

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 CHANGED
@@ -1137,6 +1137,64 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1137
1137
  }
1138
1138
  };
1139
1139
 
1140
+ // lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/applyJogToTrace.ts
1141
+ var applyJogToTerminalSegment = ({
1142
+ pts,
1143
+ segmentIndex: si,
1144
+ offset,
1145
+ JOG_SIZE,
1146
+ EPS: EPS2 = 1e-6
1147
+ }) => {
1148
+ if (si !== 0 && si !== pts.length - 2) return;
1149
+ const start = pts[si];
1150
+ const end = pts[si + 1];
1151
+ const isVertical = Math.abs(start.x - end.x) < EPS2;
1152
+ const isHorizontal = Math.abs(start.y - end.y) < EPS2;
1153
+ if (!isVertical && !isHorizontal) return;
1154
+ const segDir = isVertical ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
1155
+ if (si === 0) {
1156
+ if (isVertical) {
1157
+ const jogY = start.y + segDir * JOG_SIZE;
1158
+ pts.splice(
1159
+ 1,
1160
+ 1,
1161
+ { x: start.x, y: jogY },
1162
+ { x: start.x + offset, y: jogY },
1163
+ { x: end.x + offset, y: end.y }
1164
+ );
1165
+ } else {
1166
+ const jogX = start.x + segDir * JOG_SIZE;
1167
+ pts.splice(
1168
+ 1,
1169
+ 1,
1170
+ { x: jogX, y: start.y },
1171
+ { x: jogX, y: start.y + offset },
1172
+ { x: end.x, y: end.y + offset }
1173
+ );
1174
+ }
1175
+ } else {
1176
+ if (isVertical) {
1177
+ const jogY = end.y - segDir * JOG_SIZE;
1178
+ pts.splice(
1179
+ si,
1180
+ 1,
1181
+ { x: start.x + offset, y: start.y },
1182
+ { x: end.x + offset, y: jogY },
1183
+ { x: end.x, y: jogY }
1184
+ );
1185
+ } else {
1186
+ const jogX = end.x - segDir * JOG_SIZE;
1187
+ pts.splice(
1188
+ si,
1189
+ 1,
1190
+ { x: start.x, y: start.y + offset },
1191
+ { x: jogX, y: end.y + offset },
1192
+ { x: jogX, y: end.y }
1193
+ );
1194
+ }
1195
+ }
1196
+ };
1197
+
1140
1198
  // lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts
1141
1199
  var TraceOverlapIssueSolver = class extends BaseSolver {
1142
1200
  overlappingTraceSegments;
@@ -1180,33 +1238,30 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1180
1238
  const current = this.correctedTraceMap[original.mspPairId] ?? original;
1181
1239
  const pts = current.tracePath.map((p) => ({ ...p }));
1182
1240
  const segIdxs = Array.from(segIdxSet).sort((a, b) => a - b);
1183
- const appliedX = /* @__PURE__ */ new Set();
1184
- const appliedY = /* @__PURE__ */ new Set();
1185
- for (const si of segIdxs) {
1241
+ const segIdxsRev = Array.from(segIdxSet).sort((a, b) => a - b).reverse();
1242
+ const JOG_SIZE = this.SHIFT_DISTANCE;
1243
+ for (const si of segIdxsRev) {
1186
1244
  if (si < 0 || si >= pts.length - 1) continue;
1187
- if (si === 0 || si === pts.length - 2) continue;
1188
- const start = pts[si];
1189
- const end = pts[si + 1];
1190
- const isVertical = Math.abs(start.x - end.x) < EPS2;
1191
- const isHorizontal = Math.abs(start.y - end.y) < EPS2;
1192
- if (!isVertical && !isHorizontal) continue;
1193
- if (isVertical) {
1194
- if (!appliedX.has(si)) {
1245
+ if (si === 0 || si === pts.length - 2) {
1246
+ applyJogToTerminalSegment({
1247
+ pts,
1248
+ segmentIndex: si,
1249
+ offset,
1250
+ JOG_SIZE,
1251
+ EPS: EPS2
1252
+ });
1253
+ } else {
1254
+ const start = pts[si];
1255
+ const end = pts[si + 1];
1256
+ const isVertical = Math.abs(start.x - end.x) < EPS2;
1257
+ const isHorizontal = Math.abs(start.y - end.y) < EPS2;
1258
+ if (!isVertical && !isHorizontal) continue;
1259
+ if (isVertical) {
1195
1260
  start.x += offset;
1196
- appliedX.add(si);
1197
- }
1198
- if (!appliedX.has(si + 1)) {
1199
1261
  end.x += offset;
1200
- appliedX.add(si + 1);
1201
- }
1202
- } else if (isHorizontal) {
1203
- if (!appliedY.has(si)) {
1262
+ } else {
1204
1263
  start.y += offset;
1205
- appliedY.add(si);
1206
- }
1207
- if (!appliedY.has(si + 1)) {
1208
1264
  end.y += offset;
1209
- appliedY.add(si + 1);
1210
1265
  }
1211
1266
  }
1212
1267
  }
@@ -1295,7 +1350,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1295
1350
  return islands;
1296
1351
  }
1297
1352
  findNextOverlapIssue() {
1298
- const EPS2 = 1e-6;
1353
+ const EPS2 = 2e-3;
1299
1354
  const netIds = Object.keys(this.traceNetIslands);
1300
1355
  for (let i = 0; i < netIds.length; i++) {
1301
1356
  for (let j = i + 1; j < netIds.length; j++) {
@@ -2,6 +2,7 @@ import type { GraphicsObject } from "graphics-debug"
2
2
  import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
3
3
  import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
4
4
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+ import { applyJogToTerminalSegment } from "./applyJogToTrace"
5
6
 
6
7
  type ConnNetId = string
7
8
 
@@ -83,38 +84,39 @@ export class TraceOverlapIssueSolver extends BaseSolver {
83
84
 
84
85
  const segIdxs = Array.from(segIdxSet).sort((a, b) => a - b)
85
86
 
86
- // Track per-point adjustments to avoid double-shifting shared joints
87
- const appliedX = new Set<number>()
88
- const appliedY = new Set<number>()
87
+ const segIdxsRev = Array.from(segIdxSet)
88
+ .sort((a, b) => a - b)
89
+ .reverse()
89
90
 
90
- for (const si of segIdxs) {
91
- if (si < 0 || si >= pts.length - 1) continue
92
- // Do not move the first or last segment since they connect directly to pins
93
- if (si === 0 || si === pts.length - 2) continue
94
- const start = pts[si]!
95
- const end = pts[si + 1]!
96
- const isVertical = Math.abs(start.x - end.x) < EPS
97
- const isHorizontal = Math.abs(start.y - end.y) < EPS
91
+ const JOG_SIZE = this.SHIFT_DISTANCE
98
92
 
99
- if (!isVertical && !isHorizontal) continue
93
+ // Process from end to start to keep indices valid after splicing
94
+ for (const si of segIdxsRev) {
95
+ if (si < 0 || si >= pts.length - 1) continue
100
96
 
101
- if (isVertical) {
102
- if (!appliedX.has(si)) {
97
+ if (si === 0 || si === pts.length - 2) {
98
+ applyJogToTerminalSegment({
99
+ pts,
100
+ segmentIndex: si,
101
+ offset,
102
+ JOG_SIZE,
103
+ EPS,
104
+ })
105
+ } else {
106
+ // Internal segment - shift both points
107
+ const start = pts[si]!
108
+ const end = pts[si + 1]!
109
+ const isVertical = Math.abs(start.x - end.x) < EPS
110
+ const isHorizontal = Math.abs(start.y - end.y) < EPS
111
+ if (!isVertical && !isHorizontal) continue
112
+
113
+ if (isVertical) {
103
114
  start.x += offset
104
- appliedX.add(si)
105
- }
106
- if (!appliedX.has(si + 1)) {
107
115
  end.x += offset
108
- appliedX.add(si + 1)
109
- }
110
- } else if (isHorizontal) {
111
- if (!appliedY.has(si)) {
116
+ } else {
117
+ // Horizontal
112
118
  start.y += offset
113
- appliedY.add(si)
114
- }
115
- if (!appliedY.has(si + 1)) {
116
119
  end.y += offset
117
- appliedY.add(si + 1)
118
120
  }
119
121
  }
120
122
  }
@@ -0,0 +1,76 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+
3
+ export const applyJogToTerminalSegment = ({
4
+ pts,
5
+ segmentIndex: si,
6
+ offset,
7
+ JOG_SIZE,
8
+ EPS = 1e-6,
9
+ }: {
10
+ pts: Point[]
11
+ segmentIndex: number
12
+ offset: number
13
+ JOG_SIZE: number
14
+ EPS?: number
15
+ }) => {
16
+ if (si !== 0 && si !== pts.length - 2) return
17
+
18
+ const start = pts[si]!
19
+ const end = pts[si + 1]!
20
+ const isVertical = Math.abs(start.x - end.x) < EPS
21
+ const isHorizontal = Math.abs(start.y - end.y) < EPS
22
+ if (!isVertical && !isHorizontal) return
23
+
24
+ const segDir = isVertical
25
+ ? end.y > start.y
26
+ ? 1
27
+ : -1
28
+ : end.x > start.x
29
+ ? 1
30
+ : -1
31
+
32
+ if (si === 0) {
33
+ if (isVertical) {
34
+ const jogY = start.y + segDir * JOG_SIZE
35
+ pts.splice(
36
+ 1,
37
+ 1,
38
+ { x: start.x, y: jogY },
39
+ { x: start.x + offset, y: jogY },
40
+ { x: end.x + offset, y: end.y },
41
+ )
42
+ } else {
43
+ // Horizontal
44
+ const jogX = start.x + segDir * JOG_SIZE
45
+ pts.splice(
46
+ 1,
47
+ 1,
48
+ { x: jogX, y: start.y },
49
+ { x: jogX, y: start.y + offset },
50
+ { x: end.x, y: end.y + offset },
51
+ )
52
+ }
53
+ } else {
54
+ // si === pts.length - 2
55
+ if (isVertical) {
56
+ const jogY = end.y - segDir * JOG_SIZE
57
+ pts.splice(
58
+ si,
59
+ 1,
60
+ { x: start.x + offset, y: start.y },
61
+ { x: end.x + offset, y: jogY },
62
+ { x: end.x, y: jogY },
63
+ )
64
+ } else {
65
+ // Horizontal
66
+ const jogX = end.x - segDir * JOG_SIZE
67
+ pts.splice(
68
+ si,
69
+ 1,
70
+ { x: start.x, y: start.y + offset },
71
+ { x: jogX, y: end.y + offset },
72
+ { x: jogX, y: end.y },
73
+ )
74
+ }
75
+ }
76
+ }
@@ -89,7 +89,7 @@ export class TraceOverlapShiftSolver extends BaseSolver {
89
89
  overlappingTraceSegments: Array<OverlappingTraceSegmentLocator>
90
90
  } | null {
91
91
  // Detect the next set of overlapping segments between two different net islands.
92
- const EPS = 1e-6
92
+ const EPS = 2e-3
93
93
 
94
94
  const netIds = Object.keys(this.traceNetIslands)
95
95
  // Compare each pair of different nets
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.30",
4
+ "version": "0.0.31",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,177 @@
1
+ import { GenericSolverDebugger } from "site/components/GenericSolverDebugger"
2
+ import { useMemo } from "react"
3
+ import type { InputProblem } from "lib/types/InputProblem"
4
+ import { SchematicTracePipelineSolver } from "lib/index"
5
+
6
+ const inputProblem = {
7
+ chips: [
8
+ {
9
+ chipId: "schematic_component_0",
10
+ center: {
11
+ x: 0,
12
+ y: 0,
13
+ },
14
+ width: 0.4,
15
+ height: 0.8,
16
+ pins: [
17
+ {
18
+ pinId: "U1.6",
19
+ x: 0.6000000000000001,
20
+ y: -0.2,
21
+ },
22
+ {
23
+ pinId: "U1.8",
24
+ x: 0.6000000000000001,
25
+ y: 0,
26
+ },
27
+ {
28
+ pinId: "U1.1",
29
+ x: 0.6000000000000001,
30
+ y: 0.2,
31
+ },
32
+ ],
33
+ },
34
+ {
35
+ chipId: "schematic_component_1",
36
+ center: {
37
+ x: 1.4,
38
+ y: 0.55,
39
+ },
40
+ width: 0.5291665999999999,
41
+ height: 1.0583333000000001,
42
+ pins: [
43
+ {
44
+ pinId: "C2.1",
45
+ x: 1.4002733499999995,
46
+ y: -0.0012093000000001908,
47
+ },
48
+ {
49
+ pinId: "C2.2",
50
+ x: 1.3997266500000003,
51
+ y: 1.1012093000000003,
52
+ },
53
+ ],
54
+ },
55
+ {
56
+ chipId: "schematic_component_2",
57
+ center: {
58
+ x: 2.7,
59
+ y: 1.3,
60
+ },
61
+ width: 1.0583332999999997,
62
+ height: 0.388910699999999,
63
+ pins: [
64
+ {
65
+ pinId: "R1.1",
66
+ x: 2.1487093,
67
+ y: 1.3002732499999994,
68
+ },
69
+ {
70
+ pinId: "R1.2",
71
+ x: 3.2512907000000006,
72
+ y: 1.2997267500000007,
73
+ },
74
+ ],
75
+ },
76
+ {
77
+ chipId: "schematic_component_3",
78
+ center: {
79
+ x: 4.4,
80
+ y: 0,
81
+ },
82
+ width: 0.4,
83
+ height: 0.4,
84
+ pins: [
85
+ {
86
+ pinId: "JP5.1",
87
+ x: 3.8000000000000003,
88
+ y: 0,
89
+ },
90
+ ],
91
+ },
92
+ {
93
+ chipId: "schematic_component_4",
94
+ center: {
95
+ x: 4.4,
96
+ y: -0.9,
97
+ },
98
+ width: 0.4,
99
+ height: 0.4,
100
+ pins: [
101
+ {
102
+ pinId: "JP9.1",
103
+ x: 3.8000000000000003,
104
+ y: -0.9,
105
+ },
106
+ ],
107
+ },
108
+ {
109
+ chipId: "schematic_component_5",
110
+ center: {
111
+ x: 2,
112
+ y: -1.1,
113
+ },
114
+ width: 0.8843008999999997,
115
+ height: 0.5299361999999987,
116
+ pins: [
117
+ {
118
+ pinId: "JP8.1",
119
+ x: 2.4458007999999998,
120
+ y: -1.2015872704999997,
121
+ },
122
+ {
123
+ pinId: "JP8.2",
124
+ x: 2.0034928,
125
+ y: -0.8474009705000005,
126
+ },
127
+ {
128
+ pinId: "JP8.3",
129
+ x: 1.5541992,
130
+ y: -1.2014628704999997,
131
+ },
132
+ ],
133
+ },
134
+ ],
135
+ directConnections: [
136
+ {
137
+ pinIds: ["C2.1", "U1.8"],
138
+ netId: "capacitor.C2 > port.pin1 to .U1 > .pin8",
139
+ },
140
+ {
141
+ pinIds: ["C2.2", "R1.1"],
142
+ netId: "capacitor.C2 > port.pin2 to .R1 > .pin1",
143
+ },
144
+ {
145
+ pinIds: ["R1.1", "U1.1"],
146
+ netId: "resistor.R1 > port.pin1 to .U1 > .pin1",
147
+ },
148
+ {
149
+ pinIds: ["JP5.1", "R1.2"],
150
+ netId: "pinheader.JP5 > port.pin1 to .R1 > .pin2",
151
+ },
152
+ {
153
+ pinIds: ["JP9.1", "R1.2"],
154
+ netId: "pinheader.JP9 > port.pin1 to .R1 > .pin2",
155
+ },
156
+ {
157
+ pinIds: ["JP8.2", "U1.6"],
158
+ netId: "solderjumper.JP8 > port.pin2 to .U1 > .pin6",
159
+ },
160
+ ],
161
+ netConnections: [
162
+ {
163
+ netId: "PAD",
164
+ pinIds: ["R1.2", "JP5.1", "JP9.1"],
165
+ },
166
+ ],
167
+ availableNetLabelOrientations: {},
168
+ maxMspPairDistance: 5,
169
+ } as InputProblem
170
+
171
+ export default () => {
172
+ const solver = useMemo(
173
+ () => new SchematicTracePipelineSolver(inputProblem),
174
+ [],
175
+ )
176
+ return <GenericSolverDebugger solver={solver} />
177
+ }
@@ -70,7 +70,7 @@ x+" data-x="1.48" data-y="-0.665" cx="516.9811320754716" cy="426.7924528301886"
70
70
  <polyline data-points="0.5800000000000001,-0.665 0.1,-0.665 0.1,0.1 -1.6800000000000002,0.1 -1.6800000000000002,0.765 -1.58,0.765" data-type="line" data-label="" points="381.1320754716981,426.7924528301886 308.6792452830188,426.7924528301886 308.6792452830188,311.3207547169811 39.99999999999997,311.3207547169811 39.99999999999997,210.94339622641508 55.09433962264151,210.94339622641508" fill="none" stroke="purple" stroke-width="1" />
71
71
  </g>
72
72
  <g>
73
- <polyline data-points="1.48,-0.665 1.78,-0.665 1.78,0.03527232500000027 0,0.03527232500000027 0,0.765 -0.48,0.765" data-type="line" data-label="" points="516.9811320754716,426.7924528301886 562.2641509433961,426.7924528301886 562.2641509433961,321.0909698113207 293.58490566037733,321.0909698113207 293.58490566037733,210.94339622641508 221.1320754716981,210.94339622641508" fill="none" stroke="purple" stroke-width="1" />
73
+ <polyline data-points="1.48,-0.665 1.78,-0.665 1.78,0.03527232500000027 0,0.03527232500000027 0,0.865 -0.38,0.865 -0.38,0.765 -0.48,0.765" data-type="line" data-label="" points="516.9811320754716,426.7924528301886 562.2641509433961,426.7924528301886 562.2641509433961,321.0909698113207 293.58490566037733,321.0909698113207 293.58490566037733,195.84905660377356 236.2264150943396,195.84905660377356 236.2264150943396,210.94339622641508 221.1320754716981,210.94339622641508" fill="none" stroke="purple" stroke-width="1" />
74
74
  </g>
75
75
  <g>
76
76
  <rect data-type="rect" data-label="schematic_component_0" data-x="-1.03" data-y="0.765" x="55.09433962264151" y="181.59164528301895" width="166.0377358490566" height="58.70350188679228" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.006625000000000001" />
@@ -0,0 +1,195 @@
1
+ <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="100%" height="100%" fill="white" />
3
+ <g>
4
+ <circle data-type="point" data-label="U1.6
5
+ x+" data-x="0.6000000000000001" data-y="-0.2" cx="160.00000000000003" cy="359.23793250000006" r="3" fill="hsl(324, 100%, 50%, 0.8)" />
6
+ </g>
7
+ <g>
8
+ <circle data-type="point" data-label="U1.8
9
+ x+" data-x="0.6000000000000001" data-y="0" cx="160.00000000000003" cy="339.23793250000006" r="3" fill="hsl(326, 100%, 50%, 0.8)" />
10
+ </g>
11
+ <g>
12
+ <circle data-type="point" data-label="U1.1
13
+ x+" data-x="0.6000000000000001" data-y="0.2" cx="160.00000000000003" cy="319.23793250000006" r="3" fill="hsl(319, 100%, 50%, 0.8)" />
14
+ </g>
15
+ <g>
16
+ <circle data-type="point" data-label="C2.1
17
+ y-" data-x="1.4002733499999995" data-y="-0.0012093000000001908" cx="240.027335" cy="339.3588625000001" r="3" fill="hsl(2, 100%, 50%, 0.8)" />
18
+ </g>
19
+ <g>
20
+ <circle data-type="point" data-label="C2.2
21
+ y+" data-x="1.3997266500000003" data-y="1.1012093000000003" cx="239.97266500000006" cy="229.1170025" r="3" fill="hsl(3, 100%, 50%, 0.8)" />
22
+ </g>
23
+ <g>
24
+ <circle data-type="point" data-label="R1.1
25
+ x-" data-x="2.1487092999999997" data-y="1.3002732499999994" cx="314.87093" cy="209.21060750000012" r="3" fill="hsl(226, 100%, 50%, 0.8)" />
26
+ </g>
27
+ <g>
28
+ <circle data-type="point" data-label="R1.2
29
+ x+" data-x="3.2512907000000006" data-y="1.2997267500000007" cx="425.12907000000007" cy="209.2652575" r="3" fill="hsl(227, 100%, 50%, 0.8)" />
30
+ </g>
31
+ <g>
32
+ <circle data-type="point" data-label="JP5.1
33
+ x-" data-x="3.8000000000000003" data-y="0" cx="480" cy="339.23793250000006" r="3" fill="hsl(242, 100%, 50%, 0.8)" />
34
+ </g>
35
+ <g>
36
+ <circle data-type="point" data-label="JP9.1
37
+ x-" data-x="3.8000000000000003" data-y="-0.9" cx="480" cy="429.23793250000006" r="3" fill="hsl(126, 100%, 50%, 0.8)" />
38
+ </g>
39
+ <g>
40
+ <circle data-type="point" data-label="JP8.1
41
+ x+" data-x="2.4458007999999998" data-y="-1.2015872704999997" cx="344.58008" cy="459.39665955000004" r="3" fill="hsl(245, 100%, 50%, 0.8)" />
42
+ </g>
43
+ <g>
44
+ <circle data-type="point" data-label="JP8.2
45
+ y+" data-x="2.0034928" data-y="-0.8350319000000007" cx="300.34928" cy="422.74112250000013" r="3" fill="hsl(246, 100%, 50%, 0.8)" />
46
+ </g>
47
+ <g>
48
+ <circle data-type="point" data-label="JP8.3
49
+ x-" data-x="1.5541992" data-y="-1.2014628704999997" cx="255.41992000000002" cy="459.38421955" r="3" fill="hsl(247, 100%, 50%, 0.8)" />
50
+ </g>
51
+ <g>
52
+ <circle data-type="point" data-label="" data-x="1.4002733499999995" data-y="-0.30120930000000024" cx="240.027335" cy="369.3588625000001" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
53
+ </g>
54
+ <g>
55
+ <circle data-type="point" data-label="" data-x="0.733854175" data-y="0.2" cx="173.38541750000002" cy="319.23793250000006" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
56
+ </g>
57
+ <g>
58
+ <circle data-type="point" data-label="" data-x="3.451290700000001" data-y="1.2997267500000007" cx="445.12907000000007" cy="209.2652575" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
59
+ </g>
60
+ <g>
61
+ <circle data-type="point" data-label="" data-x="2.0034928" data-y="-0.46751595000000035" cx="300.34928" cy="385.9895275000001" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
62
+ </g>
63
+ <g>
64
+ <polyline data-points="1.4002733499999995,-0.0012093000000001908 0.6000000000000001,0" data-type="line" data-label="" points="240.027335,339.3588625000001 160.00000000000003,339.23793250000006" fill="none" stroke="hsl(256, 100%, 50%, 0.8)" stroke-width="1" />
65
+ </g>
66
+ <g>
67
+ <polyline data-points="1.3997266500000003,1.1012093000000003 2.1487092999999997,1.3002732499999994" data-type="line" data-label="" points="239.97266500000006,229.1170025 314.87093,209.21060750000012" fill="none" stroke="hsl(256, 100%, 50%, 0.8)" stroke-width="1" />
68
+ </g>
69
+ <g>
70
+ <polyline data-points="2.1487092999999997,1.3002732499999994 0.6000000000000001,0.2" data-type="line" data-label="" points="314.87093,209.21060750000012 160.00000000000003,319.23793250000006" fill="none" stroke="hsl(280, 100%, 50%, 0.8)" stroke-width="1" />
71
+ </g>
72
+ <g>
73
+ <polyline data-points="3.8000000000000003,0 3.2512907000000006,1.2997267500000007" data-type="line" data-label="" points="480,339.23793250000006 425.12907000000007,209.2652575" fill="none" stroke="hsl(336, 100%, 50%, 0.8)" stroke-width="1" />
74
+ </g>
75
+ <g>
76
+ <polyline data-points="3.8000000000000003,-0.9 3.2512907000000006,1.2997267500000007" data-type="line" data-label="" points="480,429.23793250000006 425.12907000000007,209.2652575" fill="none" stroke="hsl(336, 100%, 50%, 0.8)" stroke-width="1" />
77
+ </g>
78
+ <g>
79
+ <polyline data-points="2.0034928,-0.8350319000000007 0.6000000000000001,-0.2" data-type="line" data-label="" points="300.34928,422.74112250000013 160.00000000000003,359.23793250000006" fill="none" stroke="hsl(352, 100%, 50%, 0.8)" stroke-width="1" />
80
+ </g>
81
+ <g>
82
+ <polyline data-points="3.2512907000000006,1.2997267500000007 3.8000000000000003,0" data-type="line" data-label="" points="425.12907000000007,209.2652575 480,339.23793250000006" fill="none" stroke="hsl(123, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
83
+ </g>
84
+ <g>
85
+ <polyline data-points="3.2512907000000006,1.2997267500000007 3.8000000000000003,-0.9" data-type="line" data-label="" points="425.12907000000007,209.2652575 480,429.23793250000006" fill="none" stroke="hsl(123, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
86
+ </g>
87
+ <g>
88
+ <polyline data-points="3.8000000000000003,0 3.8000000000000003,-0.9" data-type="line" data-label="" points="480,339.23793250000006 480,429.23793250000006" fill="none" stroke="hsl(123, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
89
+ </g>
90
+ <g>
91
+ <polyline data-points="1.4002733499999995,-0.0012093000000001908 1.4002733499999995,-0.30120930000000024 1.0001366749999998,-0.30120930000000024 1.0001366749999998,0 0.6000000000000001,0" data-type="line" data-label="" points="240.027335,339.3588625000001 240.027335,369.3588625000001 200.0136675,369.3588625000001 200.0136675,339.23793250000006 160.00000000000003,339.23793250000006" fill="none" stroke="purple" stroke-width="1" />
92
+ </g>
93
+ <g>
94
+ <polyline data-points="2.1487092999999997,1.3002732499999994 1.3997266500000003,1.3002732499999994 1.3997266500000003,1.1012093000000003" data-type="line" data-label="" points="314.87093,209.21060750000012 239.97266500000006,209.21060750000012 239.97266500000006,229.1170025" fill="none" stroke="purple" stroke-width="1" />
95
+ </g>
96
+ <g>
97
+ <polyline data-points="0.6000000000000001,0.2 0.86770835,0.2 0.86770835,1.1033769750000004 1.3997266500000003,1.1033769750000004 1.3997266500000003,1.1012093000000003" data-type="line" data-label="" points="160.00000000000003,319.23793250000006 186.77083500000003,319.23793250000006 186.77083500000003,228.900235 239.97266500000006,228.900235 239.97266500000006,229.1170025" fill="none" stroke="purple" stroke-width="1" />
98
+ </g>
99
+ <g>
100
+ <polyline data-points="3.8000000000000003,-0.9 3.6000000000000005,-0.9 3.6000000000000005,0 3.8000000000000003,0" data-type="line" data-label="" points="480,429.23793250000006 460.0000000000001,429.23793250000006 460.0000000000001,339.23793250000006 480,339.23793250000006" fill="none" stroke="purple" stroke-width="1" />
101
+ </g>
102
+ <g>
103
+ <polyline data-points="3.2512907000000006,1.2997267500000007 3.451290700000001,1.2997267500000007 3.5256453500000005,1.2997267500000007 3.5256453500000005,0 3.6,0 3.8000000000000003,0" data-type="line" data-label="" points="425.12907000000007,209.2652575 445.12907000000007,209.2652575 452.5645350000001,209.2652575 452.5645350000001,339.23793250000006 460,339.23793250000006 480,339.23793250000006" fill="none" stroke="purple" stroke-width="1" />
104
+ </g>
105
+ <g>
106
+ <polyline data-points="2.0034928,-0.8350319000000007 2.0034928,-0.1 0.7000000000000001,-0.1 0.7000000000000001,-0.2 0.6000000000000001,-0.2" data-type="line" data-label="" points="300.34928,422.74112250000013 300.34928,349.23793250000006 170.00000000000003,349.23793250000006 170.00000000000003,359.23793250000006 160.00000000000003,359.23793250000006" fill="none" stroke="purple" stroke-width="1" />
107
+ </g>
108
+ <g>
109
+ <rect data-type="rect" data-label="schematic_component_0" data-x="0" data-y="0" x="40.00000000000002" y="299.23793250000006" width="120" height="80" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.01" />
110
+ </g>
111
+ <g>
112
+ <rect data-type="rect" data-label="schematic_component_1" data-x="1.4" data-y="0.55" x="213.54167" y="229.1170025" width="52.916660000000036" height="110.24186000000009" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.01" />
113
+ </g>
114
+ <g>
115
+ <rect data-type="rect" data-label="schematic_component_2" data-x="2.7" data-y="1.3" x="314.87093" y="189.7923975000001" width="110.25814000000008" height="38.8910699999999" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.01" />
116
+ </g>
117
+ <g>
118
+ <rect data-type="rect" data-label="schematic_component_3" data-x="4.4" data-y="0" x="480" y="319.23793250000006" width="120" height="40" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.01" />
119
+ </g>
120
+ <g>
121
+ <rect data-type="rect" data-label="schematic_component_4" data-x="4.4" data-y="-0.9" x="480" y="409.23793250000006" width="120" height="40" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.01" />
122
+ </g>
123
+ <g>
124
+ <rect data-type="rect" data-label="schematic_component_5" data-x="2" data-y="-1.1" x="255.41992000000002" y="422.74112250000013" width="89.16015999999999" height="52.99361999999991" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.01" />
125
+ </g>
126
+ <g>
127
+ <rect data-type="rect" data-label="" data-x="1.6252733499999996" data-y="-0.30120930000000024" x="240.027335" y="359.3588625000001" width="45" height="20" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.01" />
128
+ </g>
129
+ <g>
130
+ <rect data-type="rect" data-label="" data-x="0.733854175" data-y="0.42500000000000004" x="163.38541750000005" y="274.23793250000006" width="19.99999999999997" height="45" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.01" />
131
+ </g>
132
+ <g>
133
+ <rect data-type="rect" data-label="" data-x="3.451290700000001" data-y="1.5247267500000008" x="435.12907000000007" y="164.26525749999996" width="20" height="45.00000000000003" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.01" />
134
+ </g>
135
+ <g>
136
+ <rect data-type="rect" data-label="" data-x="2.2284928" data-y="-0.46751595000000035" x="300.34928" y="375.9895275000001" width="45" height="19.999999999999943" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.01" />
137
+ </g>
138
+ <g id="crosshair" style="display: none">
139
+ <line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5" />
140
+ <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>
141
+ </g>
142
+ <script>
143
+ <![CDATA[
144
+ document.currentScript.parentElement.addEventListener('mousemove', (e) => {
145
+ const svg = e.currentTarget;
146
+ const rect = svg.getBoundingClientRect();
147
+ const x = e.clientX - rect.left;
148
+ const y = e.clientY - rect.top;
149
+ const crosshair = svg.getElementById('crosshair');
150
+ const h = svg.getElementById('crosshair-h');
151
+ const v = svg.getElementById('crosshair-v');
152
+ const coords = svg.getElementById('coordinates');
153
+
154
+ crosshair.style.display = 'block';
155
+ h.setAttribute('x1', '0');
156
+ h.setAttribute('x2', '640');
157
+ h.setAttribute('y1', y);
158
+ h.setAttribute('y2', y);
159
+ v.setAttribute('x1', x);
160
+ v.setAttribute('x2', x);
161
+ v.setAttribute('y1', '0');
162
+ v.setAttribute('y2', '640');
163
+
164
+ // Calculate real coordinates using inverse transformation
165
+ const matrix = {
166
+ "a": 100,
167
+ "c": 0,
168
+ "e": 100.00000000000003,
169
+ "b": 0,
170
+ "d": -100,
171
+ "f": 339.23793250000006
172
+ };
173
+ // Manually invert and apply the affine transform
174
+ // Since we only use translate and scale, we can directly compute:
175
+ // x' = (x - tx) / sx
176
+ // y' = (y - ty) / sy
177
+ const sx = matrix.a;
178
+ const sy = matrix.d;
179
+ const tx = matrix.e;
180
+ const ty = matrix.f;
181
+ const realPoint = {
182
+ x: (x - tx) / sx,
183
+ y: (y - ty) / sy // Flip y back since we used negative scale
184
+ }
185
+
186
+ coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
187
+ coords.setAttribute('x', (x + 5).toString());
188
+ coords.setAttribute('y', (y - 5).toString());
189
+ });
190
+ document.currentScript.parentElement.addEventListener('mouseleave', () => {
191
+ document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
192
+ });
193
+ ]]>
194
+ </script>
195
+ </svg>
@@ -0,0 +1,175 @@
1
+ import { expect } from "bun:test"
2
+ import { test } from "bun:test"
3
+ import { SchematicTracePipelineSolver, type InputProblem } from "lib/index"
4
+ import "tests/fixtures/matcher"
5
+
6
+ const inputProblem = {
7
+ chips: [
8
+ {
9
+ chipId: "schematic_component_0",
10
+ center: {
11
+ x: 0,
12
+ y: 0,
13
+ },
14
+ width: 0.4,
15
+ height: 0.8,
16
+ pins: [
17
+ {
18
+ pinId: "U1.6",
19
+ x: 0.6000000000000001,
20
+ y: -0.2,
21
+ },
22
+ {
23
+ pinId: "U1.8",
24
+ x: 0.6000000000000001,
25
+ y: 0,
26
+ },
27
+ {
28
+ pinId: "U1.1",
29
+ x: 0.6000000000000001,
30
+ y: 0.2,
31
+ },
32
+ ],
33
+ },
34
+ {
35
+ chipId: "schematic_component_1",
36
+ center: {
37
+ x: 1.4,
38
+ y: 0.55,
39
+ },
40
+ width: 0.5291665999999999,
41
+ height: 1.0583333000000001,
42
+ pins: [
43
+ {
44
+ pinId: "C2.1",
45
+ x: 1.4002733499999995,
46
+ y: -0.0012093000000001908,
47
+ },
48
+ {
49
+ pinId: "C2.2",
50
+ x: 1.3997266500000003,
51
+ y: 1.1012093000000003,
52
+ },
53
+ ],
54
+ },
55
+ {
56
+ chipId: "schematic_component_2",
57
+ center: {
58
+ x: 2.7,
59
+ y: 1.3,
60
+ },
61
+ width: 1.0583332999999997,
62
+ height: 0.388910699999999,
63
+ pins: [
64
+ {
65
+ pinId: "R1.1",
66
+ x: 2.1487093,
67
+ y: 1.3002732499999994,
68
+ },
69
+ {
70
+ pinId: "R1.2",
71
+ x: 3.2512907000000006,
72
+ y: 1.2997267500000007,
73
+ },
74
+ ],
75
+ },
76
+ {
77
+ chipId: "schematic_component_3",
78
+ center: {
79
+ x: 4.4,
80
+ y: 0,
81
+ },
82
+ width: 0.4,
83
+ height: 0.4,
84
+ pins: [
85
+ {
86
+ pinId: "JP5.1",
87
+ x: 3.8000000000000003,
88
+ y: 0,
89
+ },
90
+ ],
91
+ },
92
+ {
93
+ chipId: "schematic_component_4",
94
+ center: {
95
+ x: 4.4,
96
+ y: -0.9,
97
+ },
98
+ width: 0.4,
99
+ height: 0.4,
100
+ pins: [
101
+ {
102
+ pinId: "JP9.1",
103
+ x: 3.8000000000000003,
104
+ y: -0.9,
105
+ },
106
+ ],
107
+ },
108
+ {
109
+ chipId: "schematic_component_5",
110
+ center: {
111
+ x: 2,
112
+ y: -1.1,
113
+ },
114
+ width: 0.8843008999999997,
115
+ height: 0.5299361999999987,
116
+ pins: [
117
+ {
118
+ pinId: "JP8.1",
119
+ x: 2.4458007999999998,
120
+ y: -1.2015872704999997,
121
+ },
122
+ {
123
+ pinId: "JP8.2",
124
+ x: 2.0034928,
125
+ y: -0.8474009705000005,
126
+ },
127
+ {
128
+ pinId: "JP8.3",
129
+ x: 1.5541992,
130
+ y: -1.2014628704999997,
131
+ },
132
+ ],
133
+ },
134
+ ],
135
+ directConnections: [
136
+ {
137
+ pinIds: ["C2.1", "U1.8"],
138
+ netId: "capacitor.C2 > port.pin1 to .U1 > .pin8",
139
+ },
140
+ {
141
+ pinIds: ["C2.2", "R1.1"],
142
+ netId: "capacitor.C2 > port.pin2 to .R1 > .pin1",
143
+ },
144
+ {
145
+ pinIds: ["R1.1", "U1.1"],
146
+ netId: "resistor.R1 > port.pin1 to .U1 > .pin1",
147
+ },
148
+ {
149
+ pinIds: ["JP5.1", "R1.2"],
150
+ netId: "pinheader.JP5 > port.pin1 to .R1 > .pin2",
151
+ },
152
+ {
153
+ pinIds: ["JP9.1", "R1.2"],
154
+ netId: "pinheader.JP9 > port.pin1 to .R1 > .pin2",
155
+ },
156
+ {
157
+ pinIds: ["JP8.2", "U1.6"],
158
+ netId: "solderjumper.JP8 > port.pin2 to .U1 > .pin6",
159
+ },
160
+ ],
161
+ netConnections: [
162
+ {
163
+ netId: "PAD",
164
+ pinIds: ["R1.2", "JP5.1", "JP9.1"],
165
+ },
166
+ ],
167
+ availableNetLabelOrientations: {},
168
+ maxMspPairDistance: 5,
169
+ } as InputProblem
170
+
171
+ test("example16", () => {
172
+ const solver = new SchematicTracePipelineSolver(inputProblem)
173
+ solver.solve()
174
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
175
+ })