@tscircuit/schematic-trace-solver 0.0.155 → 0.0.157

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 (23) hide show
  1. package/dist/index.d.ts +4 -0
  2. package/dist/index.js +42 -6
  3. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +10 -2
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts +38 -0
  5. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +19 -11
  6. package/package.json +1 -1
  7. package/site/bug-reports/bug-report-20260825T045913Z.page.tsx +4 -0
  8. package/site/components/PipelineDebugger.tsx +4 -2
  9. package/site/examples/example51.page.tsx +3 -1
  10. package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +271 -0
  11. package/tests/bug-reports/bug-report-20260825T045913Z/bug-report-20260825T045913Z.json +874 -0
  12. package/tests/bug-reports/bug-report-20260825T045913Z/bug-report-20260825T045913Z.test.ts +23 -0
  13. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +71 -80
  14. package/tests/repros/__snapshots__/repro-dc-power-isolated-routing.snap.svg +129 -0
  15. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +1290 -0
  16. package/tests/repros/__snapshots__/repro-tida00553-j4-cell-divider.snap.svg +130 -0
  17. package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +10 -10
  18. package/tests/repros/assets/repro-dc-power-isolated-routing.input.json +193 -0
  19. package/tests/repros/assets/repro-pmp11282-isolated-dcdc.input.json +4570 -0
  20. package/tests/repros/board-1273-trace-overlap-cycle.test.ts +1 -1
  21. package/tests/repros/repro-dc-power-isolated-routing.test.ts +13 -0
  22. package/tests/repros/repro-pmp11282-isolated-dcdc.test.ts +77 -0
  23. package/tests/repros/repro-tida00553-j4-cell-divider.test.ts +111 -0
package/dist/index.d.ts CHANGED
@@ -191,6 +191,7 @@ type MspConnectionPair = {
191
191
  declare class MspConnectionPairSolver extends BaseSolver {
192
192
  inputProblem: InputProblem;
193
193
  mspConnectionPairs: MspConnectionPair[];
194
+ mspConnectionPairIds: Set<string>;
194
195
  dcConnMap: ConnectivityMap;
195
196
  globalConnMap: ConnectivityMap;
196
197
  queuedDcNetIds: string[];
@@ -205,6 +206,7 @@ declare class MspConnectionPairSolver extends BaseSolver {
205
206
  inputProblem: InputProblem;
206
207
  });
207
208
  getConstructorParams(): ConstructorParameters<typeof MspConnectionPairSolver>[0];
209
+ private addMspConnectionPair;
208
210
  _step(): void;
209
211
  visualize(): GraphicsObject;
210
212
  }
@@ -244,6 +246,7 @@ declare class SchematicTraceSingleLineSolver2 extends BaseSolver {
244
246
  maxY: number;
245
247
  };
246
248
  baseElbow: Point[];
249
+ preferExteriorDetours: boolean;
247
250
  solvedTracePath: Point[] | null;
248
251
  private queue;
249
252
  private visited;
@@ -252,6 +255,7 @@ declare class SchematicTraceSingleLineSolver2 extends BaseSolver {
252
255
  connectionPair?: MspConnectionPair;
253
256
  inputProblem: InputProblem;
254
257
  chipMap: Record<string, InputChip>;
258
+ preferExteriorDetours?: boolean;
255
259
  });
256
260
  getConstructorParams(): ConstructorParameters<typeof SchematicTraceSingleLineSolver2>[0];
257
261
  private getTextBoxPaddingForConnectionPair;
package/dist/index.js CHANGED
@@ -503,6 +503,7 @@ var getPinPairKey = (pinIds) => [...pinIds].sort().join("::");
503
503
  var MspConnectionPairSolver = class extends BaseSolver {
504
504
  inputProblem;
505
505
  mspConnectionPairs = [];
506
+ mspConnectionPairIds = /* @__PURE__ */ new Set();
506
507
  dcConnMap;
507
508
  globalConnMap;
508
509
  queuedDcNetIds;
@@ -550,6 +551,11 @@ var MspConnectionPairSolver = class extends BaseSolver {
550
551
  inputProblem: this.inputProblem
551
552
  };
552
553
  }
554
+ addMspConnectionPair(pair) {
555
+ if (this.mspConnectionPairIds.has(pair.mspPairId)) return;
556
+ this.mspConnectionPairIds.add(pair.mspPairId);
557
+ this.mspConnectionPairs.push(pair);
558
+ }
553
559
  _step() {
554
560
  if (this.queuedDcNetIds.length === 0) {
555
561
  this.solved = true;
@@ -594,7 +600,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
594
600
  const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1);
595
601
  const userNetId = this.userNetIdByPinId[pin1] ?? this.userNetIdByPinId[pin2];
596
602
  const suppressNetLabel = pairDistance > this.maxMspPairDistance && labeledConnectionRouteReason === "overlapping-fallback-labels";
597
- this.mspConnectionPairs.push({
603
+ this.addMspConnectionPair({
598
604
  mspPairId: `${pin1}-${pin2}`,
599
605
  dcConnNetId: dcNetId,
600
606
  globalConnNetId,
@@ -639,7 +645,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
639
645
  }
640
646
  const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1);
641
647
  const userNetId = this.userNetIdByPinId[pin1] ?? this.userNetIdByPinId[pin2];
642
- this.mspConnectionPairs.push({
648
+ this.addMspConnectionPair({
643
649
  mspPairId: `${pin1}-${pin2}`,
644
650
  dcConnNetId: dcNetId,
645
651
  globalConnNetId,
@@ -1255,6 +1261,7 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1255
1261
  endpointTextObstacles;
1256
1262
  aabb;
1257
1263
  baseElbow;
1264
+ preferExteriorDetours;
1258
1265
  solvedTracePath = null;
1259
1266
  queue = [];
1260
1267
  visited = /* @__PURE__ */ new Set();
@@ -1264,6 +1271,7 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1264
1271
  this.connectionPair = params.connectionPair;
1265
1272
  this.inputProblem = params.inputProblem;
1266
1273
  this.chipMap = params.chipMap;
1274
+ this.preferExteriorDetours = params.preferExteriorDetours ?? true;
1267
1275
  for (const pin of this.pins) {
1268
1276
  if (!pin._facingDirection) {
1269
1277
  const chip = this.chipMap[pin.chipId];
@@ -1316,7 +1324,8 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1316
1324
  chipMap: this.chipMap,
1317
1325
  pins: this.pins,
1318
1326
  connectionPair: this.connectionPair,
1319
- inputProblem: this.inputProblem
1327
+ inputProblem: this.inputProblem,
1328
+ preferExteriorDetours: this.preferExteriorDetours
1320
1329
  };
1321
1330
  }
1322
1331
  getTextBoxPaddingForConnectionPair() {
@@ -1481,7 +1490,6 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1481
1490
  const isEndpointChipObstacle = rect.kind === "chip" && this.pins.some((pin) => pin.chipId === rect.chipId);
1482
1491
  const canGenerateEndpointDetour = path.length === 3 || path.length === 4 && this.connectionPair !== void 0 && !isEndpointChipObstacle;
1483
1492
  if (canGenerateEndpointDetour && (isFirstSegment || isLastSegment)) {
1484
- const compareDetours = path.length === 4 ? (a2, b2) => this.getPinBandPenalty(a2) - this.getPinBandPenalty(b2) || this.pathLength(a2) - this.pathLength(b2) : (a2, b2) => this.pathLength(a2) - this.pathLength(b2) || this.getPinBandPenalty(a2) - this.getPinBandPenalty(b2);
1485
1493
  const detours = generateEndpointCollisionDetours({
1486
1494
  path,
1487
1495
  collidingSegmentIndex: segIndex,
@@ -1491,7 +1499,12 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1491
1499
  if (this.visited.has(key)) return false;
1492
1500
  this.visited.add(key);
1493
1501
  return true;
1494
- }).sort(compareDetours);
1502
+ }).sort((a2, b2) => {
1503
+ if (path.length === 4 && this.preferExteriorDetours) {
1504
+ return this.getPinBandPenalty(a2) - this.getPinBandPenalty(b2) || this.pathLength(a2) - this.pathLength(b2);
1505
+ }
1506
+ return this.pathLength(a2) - this.pathLength(b2) || this.getPinBandPenalty(a2) - this.getPinBandPenalty(b2);
1507
+ });
1495
1508
  for (const detour of detours) {
1496
1509
  const nextCollisionRects = new Set(collisionRects);
1497
1510
  nextCollisionRects.add(rect);
@@ -1644,6 +1657,24 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1644
1657
  import { getBounds } from "graphics-debug";
1645
1658
 
1646
1659
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts
1660
+ var shouldPreferExteriorDetours = ({
1661
+ connectionPair,
1662
+ allConnectionPairs,
1663
+ inputProblem
1664
+ }) => {
1665
+ const [firstPin, secondPin] = connectionPair.pins;
1666
+ const belongsToNetConnection = inputProblem.netConnections.some(
1667
+ (netConnection) => netConnection.pinIds.includes(firstPin.pinId) && netConnection.pinIds.includes(secondPin.pinId)
1668
+ );
1669
+ if (belongsToNetConnection) return true;
1670
+ return allConnectionPairs.some((otherPair) => {
1671
+ if (otherPair === connectionPair) return false;
1672
+ const [otherFirstPin, otherSecondPin] = otherPair.pins;
1673
+ const sameOrder = firstPin.chipId === otherFirstPin.chipId && secondPin.chipId === otherSecondPin.chipId;
1674
+ if (sameOrder) return true;
1675
+ return firstPin.chipId === otherSecondPin.chipId && secondPin.chipId === otherFirstPin.chipId;
1676
+ });
1677
+ };
1647
1678
  var SchematicTraceLinesSolver = class extends BaseSolver {
1648
1679
  inputProblem;
1649
1680
  mspConnectionPairs;
@@ -1711,7 +1742,12 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1711
1742
  inputProblem: this.inputProblem,
1712
1743
  pins,
1713
1744
  connectionPair,
1714
- chipMap: this.chipMap
1745
+ chipMap: this.chipMap,
1746
+ preferExteriorDetours: shouldPreferExteriorDetours({
1747
+ connectionPair,
1748
+ allConnectionPairs: this.mspConnectionPairs,
1749
+ inputProblem: this.inputProblem
1750
+ })
1715
1751
  });
1716
1752
  }
1717
1753
  visualize() {
@@ -35,6 +35,7 @@ export class MspConnectionPairSolver extends BaseSolver {
35
35
  inputProblem: InputProblem
36
36
 
37
37
  mspConnectionPairs: MspConnectionPair[] = []
38
+ mspConnectionPairIds = new Set<MspConnectionPairId>()
38
39
  dcConnMap: ConnectivityMap
39
40
  globalConnMap: ConnectivityMap
40
41
  queuedDcNetIds: string[]
@@ -97,6 +98,13 @@ export class MspConnectionPairSolver extends BaseSolver {
97
98
  }
98
99
  }
99
100
 
101
+ private addMspConnectionPair(pair: MspConnectionPair) {
102
+ if (this.mspConnectionPairIds.has(pair.mspPairId)) return
103
+
104
+ this.mspConnectionPairIds.add(pair.mspPairId)
105
+ this.mspConnectionPairs.push(pair)
106
+ }
107
+
100
108
  override _step() {
101
109
  if (this.queuedDcNetIds.length === 0) {
102
110
  this.solved = true
@@ -164,7 +172,7 @@ export class MspConnectionPairSolver extends BaseSolver {
164
172
  pairDistance > this.maxMspPairDistance &&
165
173
  labeledConnectionRouteReason === "overlapping-fallback-labels"
166
174
 
167
- this.mspConnectionPairs.push({
175
+ this.addMspConnectionPair({
168
176
  mspPairId: `${pin1}-${pin2}`,
169
177
  dcConnNetId: dcNetId,
170
178
  globalConnNetId,
@@ -226,7 +234,7 @@ export class MspConnectionPairSolver extends BaseSolver {
226
234
  const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1!)!
227
235
  const userNetId =
228
236
  this.userNetIdByPinId[pin1!] ?? this.userNetIdByPinId[pin2!]
229
- this.mspConnectionPairs.push({
237
+ this.addMspConnectionPair({
230
238
  mspPairId: `${pin1}-${pin2}`,
231
239
  dcConnNetId: dcNetId,
232
240
  globalConnNetId,
@@ -12,6 +12,39 @@ import type { Guideline } from "../GuidelinesSolver/GuidelinesSolver"
12
12
  import { visualizeGuidelines } from "../GuidelinesSolver/visualizeGuidelines"
13
13
  import type { Point } from "@tscircuit/math-utils"
14
14
 
15
+ const shouldPreferExteriorDetours = ({
16
+ connectionPair,
17
+ allConnectionPairs,
18
+ inputProblem,
19
+ }: {
20
+ connectionPair: MspConnectionPair
21
+ allConnectionPairs: MspConnectionPair[]
22
+ inputProblem: InputProblem
23
+ }) => {
24
+ const [firstPin, secondPin] = connectionPair.pins
25
+ const belongsToNetConnection = inputProblem.netConnections.some(
26
+ (netConnection) =>
27
+ netConnection.pinIds.includes(firstPin.pinId) &&
28
+ netConnection.pinIds.includes(secondPin.pinId),
29
+ )
30
+ if (belongsToNetConnection) return true
31
+
32
+ return allConnectionPairs.some((otherPair) => {
33
+ if (otherPair === connectionPair) return false
34
+
35
+ const [otherFirstPin, otherSecondPin] = otherPair.pins
36
+ const sameOrder =
37
+ firstPin.chipId === otherFirstPin.chipId &&
38
+ secondPin.chipId === otherSecondPin.chipId
39
+ if (sameOrder) return true
40
+
41
+ return (
42
+ firstPin.chipId === otherSecondPin.chipId &&
43
+ secondPin.chipId === otherFirstPin.chipId
44
+ )
45
+ })
46
+ }
47
+
15
48
  export interface SolvedTracePath extends MspConnectionPair {
16
49
  tracePath: Point[]
17
50
  mspConnectionPairIds: MspConnectionPairId[]
@@ -112,6 +145,11 @@ export class SchematicTraceLinesSolver extends BaseSolver {
112
145
  pins,
113
146
  connectionPair,
114
147
  chipMap: this.chipMap,
148
+ preferExteriorDetours: shouldPreferExteriorDetours({
149
+ connectionPair,
150
+ allConnectionPairs: this.mspConnectionPairs,
151
+ inputProblem: this.inputProblem,
152
+ }),
115
153
  })
116
154
  }
117
155
 
@@ -90,6 +90,7 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
90
90
  aabb: { minX: number; maxX: number; minY: number; maxY: number }
91
91
 
92
92
  baseElbow: Point[]
93
+ preferExteriorDetours: boolean
93
94
 
94
95
  solvedTracePath: Point[] | null = null
95
96
 
@@ -102,12 +103,14 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
102
103
  connectionPair?: MspConnectionPair
103
104
  inputProblem: InputProblem
104
105
  chipMap: Record<string, InputChip>
106
+ preferExteriorDetours?: boolean
105
107
  }) {
106
108
  super()
107
109
  this.pins = params.pins
108
110
  this.connectionPair = params.connectionPair
109
111
  this.inputProblem = params.inputProblem
110
112
  this.chipMap = params.chipMap
113
+ this.preferExteriorDetours = params.preferExteriorDetours ?? true
111
114
 
112
115
  // Ensure facing directions are present
113
116
  for (const pin of this.pins) {
@@ -190,6 +193,7 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
190
193
  pins: this.pins,
191
194
  connectionPair: this.connectionPair,
192
195
  inputProblem: this.inputProblem,
196
+ preferExteriorDetours: this.preferExteriorDetours,
193
197
  }
194
198
  }
195
199
 
@@ -423,16 +427,8 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
423
427
  if (canGenerateEndpointDetour && (isFirstSegment || isLastSegment)) {
424
428
  // A three-point detour replaces an L elbow, so the shortest valid route
425
429
  // remains the best choice. A four-point detour preserves the far side of
426
- // a U elbow; prefer the candidate that keeps its new internal rails out
427
- // of the pin band, then use length to choose between equivalent routes.
428
- const compareDetours =
429
- path.length === 4
430
- ? (a: Point[], b: Point[]) =>
431
- this.getPinBandPenalty(a) - this.getPinBandPenalty(b) ||
432
- this.pathLength(a) - this.pathLength(b)
433
- : (a: Point[], b: Point[]) =>
434
- this.pathLength(a) - this.pathLength(b) ||
435
- this.getPinBandPenalty(a) - this.getPinBandPenalty(b)
430
+ // a U elbow. Bundled or net-connected traces preserve the exterior route;
431
+ // isolated direct traces prefer the shorter route.
436
432
  const detours = generateEndpointCollisionDetours({
437
433
  path,
438
434
  collidingSegmentIndex: segIndex,
@@ -444,7 +440,19 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
444
440
  this.visited.add(key)
445
441
  return true
446
442
  })
447
- .sort(compareDetours)
443
+ .sort((a, b) => {
444
+ if (path.length === 4 && this.preferExteriorDetours) {
445
+ return (
446
+ this.getPinBandPenalty(a) - this.getPinBandPenalty(b) ||
447
+ this.pathLength(a) - this.pathLength(b)
448
+ )
449
+ }
450
+
451
+ return (
452
+ this.pathLength(a) - this.pathLength(b) ||
453
+ this.getPinBandPenalty(a) - this.getPinBandPenalty(b)
454
+ )
455
+ })
448
456
 
449
457
  for (const detour of detours) {
450
458
  const nextCollisionRects = new Set(collisionRects)
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/tscircuit/schematic-trace-solver.git"
6
6
  },
7
7
  "main": "dist/index.js",
8
- "version": "0.0.155",
8
+ "version": "0.0.157",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260825T045913Z/bug-report-20260825T045913Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -7,13 +7,15 @@ import { PipelineStageTable } from "./PipelineStageTable"
7
7
 
8
8
  export const PipelineDebugger = ({
9
9
  inputProblem,
10
+ hideRatsNet = false,
10
11
  }: {
11
12
  inputProblem: InputProblem
13
+ hideRatsNet?: boolean
12
14
  }) => {
13
15
  const [, incRenderCount] = useReducer((x) => x + 1, 0)
14
16
  const solver = useMemo(
15
- () => new SchematicTracePipelineSolver(inputProblem),
16
- [],
17
+ () => new SchematicTracePipelineSolver(inputProblem, { hideRatsNet }),
18
+ [inputProblem, hideRatsNet],
17
19
  )
18
20
 
19
21
  return (
@@ -1,4 +1,6 @@
1
1
  import { PipelineDebugger } from "site/components/PipelineDebugger"
2
2
  import inputProblem from "../../tests/assets/example51.json"
3
3
 
4
- export default () => <PipelineDebugger inputProblem={inputProblem as any} />
4
+ export default () => (
5
+ <PipelineDebugger inputProblem={inputProblem as any} hideRatsNet />
6
+ )