@tscircuit/schematic-trace-solver 0.0.29 → 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++) {
@@ -1494,10 +1549,11 @@ function rectIntersectsAnyTrace(bounds, inputTraceMap, hostPathId, hostSegIndex)
1494
1549
  const pts = solved.tracePath;
1495
1550
  for (let i = 0; i < pts.length - 1; i++) {
1496
1551
  if (pairId === hostPathId && i === hostSegIndex) continue;
1497
- if (segmentIntersectsRect(pts[i], pts[i + 1], bounds)) return true;
1552
+ if (segmentIntersectsRect(pts[i], pts[i + 1], bounds))
1553
+ return { hasIntersection: true, mspPairId: pairId, segIndex: i };
1498
1554
  }
1499
1555
  }
1500
- return false;
1556
+ return { hasIntersection: false };
1501
1557
  }
1502
1558
 
1503
1559
  // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/host.ts
@@ -1582,14 +1638,16 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
1582
1638
  };
1583
1639
  }
1584
1640
  let pin = null;
1641
+ let pinFacingDirection = null;
1585
1642
  for (const chip of inputProblem.chips) {
1586
1643
  const p = chip.pins.find((pp) => pp.pinId === pinId);
1587
1644
  if (p) {
1588
1645
  pin = { x: p.x, y: p.y };
1646
+ pinFacingDirection = p._facingDirection || getPinDirection(p, chip);
1589
1647
  break;
1590
1648
  }
1591
1649
  }
1592
- if (!pin) {
1650
+ if (!pin || !pinFacingDirection) {
1593
1651
  return {
1594
1652
  placement: null,
1595
1653
  testedCandidates: [],
@@ -1601,21 +1659,21 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
1601
1659
  const outwardOf = (o) => o === "x+" ? { x: 1, y: 0 } : o === "x-" ? { x: -1, y: 0 } : o === "y+" ? { x: 0, y: 1 } : { x: 0, y: -1 };
1602
1660
  const testedCandidates = [];
1603
1661
  for (const orientation of orientations) {
1604
- const { width, height } = getDimsForOrientation(orientation);
1605
- const baseCenter = getCenterFromAnchor(anchor, orientation, width, height);
1606
- const outward = outwardOf(orientation);
1607
- const offset = 1e-3;
1662
+ const { width: width2, height: height2 } = getDimsForOrientation(orientation);
1663
+ const baseCenter2 = getCenterFromAnchor(anchor, orientation, width2, height2);
1664
+ const outward2 = outwardOf(orientation);
1665
+ const offset2 = 1e-3;
1608
1666
  const center = {
1609
- x: baseCenter.x + outward.x * offset,
1610
- y: baseCenter.y + outward.y * offset
1667
+ x: baseCenter2.x + outward2.x * offset2,
1668
+ y: baseCenter2.y + outward2.y * offset2
1611
1669
  };
1612
- const bounds = getRectBounds(center, width, height);
1670
+ const bounds = getRectBounds(center, width2, height2);
1613
1671
  const chips = chipObstacleSpatialIndex.getChipsInBounds(bounds);
1614
1672
  if (chips.length > 0) {
1615
1673
  testedCandidates.push({
1616
1674
  center,
1617
- width,
1618
- height,
1675
+ width: width2,
1676
+ height: height2,
1619
1677
  bounds,
1620
1678
  anchor,
1621
1679
  orientation,
@@ -1624,16 +1682,17 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
1624
1682
  });
1625
1683
  continue;
1626
1684
  }
1627
- if (rectIntersectsAnyTrace(
1685
+ const traceIntersectionResult = rectIntersectsAnyTrace(
1628
1686
  bounds,
1629
1687
  inputTraceMap,
1630
1688
  "",
1631
1689
  -1
1632
- )) {
1690
+ );
1691
+ if (traceIntersectionResult.hasIntersection) {
1633
1692
  testedCandidates.push({
1634
1693
  center,
1635
- width,
1636
- height,
1694
+ width: width2,
1695
+ height: height2,
1637
1696
  bounds,
1638
1697
  anchor,
1639
1698
  orientation,
@@ -1644,8 +1703,8 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
1644
1703
  }
1645
1704
  testedCandidates.push({
1646
1705
  center,
1647
- width,
1648
- height,
1706
+ width: width2,
1707
+ height: height2,
1649
1708
  bounds,
1650
1709
  anchor,
1651
1710
  orientation,
@@ -1660,17 +1719,39 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
1660
1719
  pinIds: [pinId],
1661
1720
  orientation,
1662
1721
  anchorPoint: anchor,
1663
- width,
1664
- height,
1722
+ width: width2,
1723
+ height: height2,
1665
1724
  center
1666
1725
  };
1667
1726
  return { placement, testedCandidates };
1668
1727
  }
1669
- return {
1670
- placement: null,
1671
- testedCandidates,
1672
- error: "Could not place net label at port without collisions"
1728
+ const fallbackOrientation = pinFacingDirection;
1729
+ const { width, height } = getDimsForOrientation(fallbackOrientation);
1730
+ const baseCenter = getCenterFromAnchor(
1731
+ anchor,
1732
+ fallbackOrientation,
1733
+ width,
1734
+ height
1735
+ );
1736
+ const outward = outwardOf(fallbackOrientation);
1737
+ const offset = 1e-3;
1738
+ const fallbackCenter = {
1739
+ x: baseCenter.x + outward.x * offset,
1740
+ y: baseCenter.y + outward.y * offset
1741
+ };
1742
+ const fallbackPlacement = {
1743
+ globalConnNetId: overlappingSameNetTraceGroup.globalConnNetId,
1744
+ dcConnNetId: void 0,
1745
+ netId: overlappingSameNetTraceGroup.netId,
1746
+ mspConnectionPairIds: [],
1747
+ pinIds: [pinId],
1748
+ orientation: fallbackOrientation,
1749
+ anchorPoint: anchor,
1750
+ width,
1751
+ height,
1752
+ center: fallbackCenter
1673
1753
  };
1754
+ return { placement: fallbackPlacement, testedCandidates };
1674
1755
  }
1675
1756
 
1676
1757
  // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver_visualize.ts
@@ -1686,12 +1767,11 @@ function visualizeSingleNetLabelPlacementSolver(solver) {
1686
1767
  const groupStroke = getColorFromString(groupId, 0.9);
1687
1768
  const groupFill = getColorFromString(groupId, 0.5);
1688
1769
  for (const trace of Object.values(solver.inputTraceMap)) {
1689
- if (trace.globalConnNetId !== groupId) continue;
1690
1770
  const isHost = host ? trace.mspPairId === host.mspPairId : false;
1691
1771
  graphics.lines.push({
1692
- points: trace.tracePath,
1693
- strokeColor: isHost ? groupStroke : groupFill,
1694
- strokeDash: isHost ? void 0 : "4 2"
1772
+ points: trace.tracePath
1773
+ // strokeColor: isHost ? groupStroke : groupFill,
1774
+ // strokeDash: isHost ? undefined : "4 2",
1695
1775
  });
1696
1776
  }
1697
1777
  for (const c of solver.testedCandidates) {
@@ -1863,12 +1943,13 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
1863
1943
  });
1864
1944
  continue;
1865
1945
  }
1866
- if (rectIntersectsAnyTrace(
1946
+ const traceIntersectionResult = rectIntersectsAnyTrace(
1867
1947
  bounds,
1868
1948
  this.inputTraceMap,
1869
1949
  curr.mspPairId,
1870
1950
  si
1871
- )) {
1951
+ );
1952
+ if (traceIntersectionResult.hasIntersection) {
1872
1953
  this.testedCandidates.push({
1873
1954
  center: testCenter,
1874
1955
  width,
@@ -261,14 +261,13 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
261
261
  }
262
262
 
263
263
  // Trace collision check (ignore the host segment)
264
- if (
265
- rectIntersectsAnyTrace(
266
- bounds,
267
- this.inputTraceMap,
268
- curr.mspPairId,
269
- si,
270
- )
271
- ) {
264
+ const traceIntersectionResult = rectIntersectsAnyTrace(
265
+ bounds,
266
+ this.inputTraceMap,
267
+ curr.mspPairId,
268
+ si,
269
+ )
270
+ if (traceIntersectionResult.hasIntersection) {
272
271
  this.testedCandidates.push({
273
272
  center: testCenter,
274
273
  width,
@@ -21,12 +21,11 @@ export function visualizeSingleNetLabelPlacementSolver(
21
21
  const groupFill = getColorFromString(groupId, 0.5)
22
22
 
23
23
  for (const trace of Object.values(solver.inputTraceMap)) {
24
- if (trace.globalConnNetId !== groupId) continue
25
24
  const isHost = host ? trace.mspPairId === host.mspPairId : false
26
25
  graphics.lines!.push({
27
26
  points: trace.tracePath,
28
- strokeColor: isHost ? groupStroke : groupFill,
29
- strokeDash: isHost ? undefined : "4 2",
27
+ // strokeColor: isHost ? groupStroke : groupFill,
28
+ // strokeDash: isHost ? undefined : "4 2",
30
29
  } as any)
31
30
  }
32
31
 
@@ -33,13 +33,20 @@ export function rectIntersectsAnyTrace(
33
33
  inputTraceMap: Record<MspConnectionPairId, SolvedTracePath>,
34
34
  hostPathId?: MspConnectionPairId,
35
35
  hostSegIndex?: number,
36
- ): boolean {
36
+ ):
37
+ | {
38
+ hasIntersection: true
39
+ mspPairId: MspConnectionPairId
40
+ segIndex: number
41
+ }
42
+ | { hasIntersection: false } {
37
43
  for (const [pairId, solved] of Object.entries(inputTraceMap)) {
38
44
  const pts = solved.tracePath
39
45
  for (let i = 0; i < pts.length - 1; i++) {
40
46
  if (pairId === hostPathId && i === hostSegIndex) continue
41
- if (segmentIntersectsRect(pts[i]!, pts[i + 1]!, bounds)) return true
47
+ if (segmentIntersectsRect(pts[i]!, pts[i + 1]!, bounds))
48
+ return { hasIntersection: true, mspPairId: pairId, segIndex: i }
42
49
  }
43
50
  }
44
- return false
51
+ return { hasIntersection: false }
45
52
  }
@@ -3,6 +3,7 @@ import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/Ms
3
3
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
4
  import type { FacingDirection } from "lib/utils/dir"
5
5
  import { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex"
6
+ import { getPinDirection } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
6
7
  import type {
7
8
  NetLabelPlacement,
8
9
  OverlappingSameNetTraceGroup,
@@ -51,16 +52,18 @@ export function solveNetLabelPlacementForPortOnlyPin(params: {
51
52
  }
52
53
  }
53
54
 
54
- // Find pin coordinates
55
+ // Find pin coordinates and facing direction
55
56
  let pin: { x: number; y: number } | null = null
57
+ let pinFacingDirection: FacingDirection | null = null
56
58
  for (const chip of inputProblem.chips) {
57
59
  const p = chip.pins.find((pp) => pp.pinId === pinId)
58
60
  if (p) {
59
61
  pin = { x: p.x, y: p.y }
62
+ pinFacingDirection = p._facingDirection || getPinDirection(p, chip)
60
63
  break
61
64
  }
62
65
  }
63
- if (!pin) {
66
+ if (!pin || !pinFacingDirection) {
64
67
  return {
65
68
  placement: null,
66
69
  testedCandidates: [],
@@ -123,14 +126,13 @@ export function solveNetLabelPlacementForPortOnlyPin(params: {
123
126
  }
124
127
 
125
128
  // Trace collision check
126
- if (
127
- rectIntersectsAnyTrace(
128
- bounds,
129
- inputTraceMap,
130
- "" as MspConnectionPairId,
131
- -1,
132
- )
133
- ) {
129
+ const traceIntersectionResult = rectIntersectsAnyTrace(
130
+ bounds,
131
+ inputTraceMap,
132
+ "" as MspConnectionPairId,
133
+ -1,
134
+ )
135
+ if (traceIntersectionResult.hasIntersection) {
134
136
  testedCandidates.push({
135
137
  center,
136
138
  width,
@@ -172,9 +174,34 @@ export function solveNetLabelPlacementForPortOnlyPin(params: {
172
174
  return { placement, testedCandidates }
173
175
  }
174
176
 
175
- return {
176
- placement: null,
177
- testedCandidates,
178
- error: "Could not place net label at port without collisions",
177
+ // If no valid placements found, return placement using pin's facing direction
178
+ const fallbackOrientation = pinFacingDirection
179
+ const { width, height } = getDimsForOrientation(fallbackOrientation)
180
+ const baseCenter = getCenterFromAnchor(
181
+ anchor,
182
+ fallbackOrientation,
183
+ width,
184
+ height,
185
+ )
186
+ const outward = outwardOf(fallbackOrientation)
187
+ const offset = 1e-3
188
+ const fallbackCenter = {
189
+ x: baseCenter.x + outward.x * offset,
190
+ y: baseCenter.y + outward.y * offset,
179
191
  }
192
+
193
+ const fallbackPlacement: NetLabelPlacement = {
194
+ globalConnNetId: overlappingSameNetTraceGroup.globalConnNetId,
195
+ dcConnNetId: undefined,
196
+ netId: overlappingSameNetTraceGroup.netId,
197
+ mspConnectionPairIds: [],
198
+ pinIds: [pinId],
199
+ orientation: fallbackOrientation,
200
+ anchorPoint: anchor,
201
+ width,
202
+ height,
203
+ center: fallbackCenter,
204
+ }
205
+
206
+ return { placement: fallbackPlacement, testedCandidates }
180
207
  }
@@ -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.29",
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>
@@ -1,7 +1,6 @@
1
1
  import { expect, test } from "bun:test"
2
2
  import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
3
  import { inputProblem } from "site/examples/example14.page"
4
- import "tests/fixtures/matcher"
5
4
 
6
5
  test.skip("example14 - should fail with net label placement collision error", () => {
7
6
  const solver = new SchematicTracePipelineSolver(inputProblem)
@@ -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
+ })
@@ -1,7 +1,6 @@
1
1
  import { expect, test } from "bun:test"
2
2
  import { SingleNetLabelPlacementSolver } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver"
3
3
  import { input } from "site/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver01.page"
4
- import "tests/fixtures/matcher"
5
4
 
6
5
  test("SingleNetLabelPlacementSolver01 issue reproduction (should pass)", () => {
7
6
  const solver = new SingleNetLabelPlacementSolver(input as any)
@@ -9,5 +8,5 @@ test("SingleNetLabelPlacementSolver01 issue reproduction (should pass)", () => {
9
8
  solver.solve()
10
9
 
11
10
  // TODO: Fix the test
12
- // expect(solver.solved).toBe(true)
11
+ expect(solver.solved).toBe(true)
13
12
  })