@tscircuit/schematic-trace-solver 0.0.102 → 0.0.104

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.d.ts CHANGED
@@ -760,6 +760,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
760
760
  private finish;
761
761
  private setCurrentLabel;
762
762
  private findCorrectedCandidate;
763
+ private hasTraceContinuingInOrientation;
763
764
  private findValidRotatedCandidate;
764
765
  private findValidTraceAnchorCandidate;
765
766
  private getTraceAnchorCandidatePoints;
package/dist/index.js CHANGED
@@ -1051,6 +1051,23 @@ var getObstacleRects = (problem, opts = {}) => {
1051
1051
  };
1052
1052
 
1053
1053
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts
1054
+ var calculateElbowForPins = ({
1055
+ pin1,
1056
+ pin2,
1057
+ overshoot
1058
+ }) => calculateElbow2(
1059
+ {
1060
+ x: pin1.x,
1061
+ y: pin1.y,
1062
+ facingDirection: pin1._facingDirection
1063
+ },
1064
+ {
1065
+ x: pin2.x,
1066
+ y: pin2.y,
1067
+ facingDirection: pin2._facingDirection
1068
+ },
1069
+ { overshoot }
1070
+ );
1054
1071
  var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1055
1072
  pins;
1056
1073
  connectionPair;
@@ -1090,19 +1107,25 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1090
1107
  );
1091
1108
  const [pin1, pin2] = this.pins;
1092
1109
  const directShortPath = calculateDirectShortPath(pin1, pin2);
1093
- this.baseElbow = directShortPath ?? calculateElbow2(
1094
- {
1095
- x: pin1.x,
1096
- y: pin1.y,
1097
- facingDirection: pin1._facingDirection
1098
- },
1099
- {
1100
- x: pin2.x,
1101
- y: pin2.y,
1102
- facingDirection: pin2._facingDirection
1103
- },
1104
- { overshoot: 0.2 }
1105
- );
1110
+ const defaultElbow = calculateElbowForPins({
1111
+ pin1,
1112
+ pin2,
1113
+ overshoot: 0.2
1114
+ });
1115
+ const routingDistance = Math.abs(pin1.x - pin2.x) + Math.abs(pin1.y - pin2.y);
1116
+ const adaptiveElbow = calculateElbowForPins({
1117
+ pin1,
1118
+ pin2,
1119
+ overshoot: Math.min(0.2, Math.max(0.02, routingDistance / 4))
1120
+ });
1121
+ const shouldUseAdaptiveElbow = findFirstCollision(defaultElbow, this.obstacles) !== null && findFirstCollision(adaptiveElbow, this.obstacles) === null;
1122
+ this.baseElbow = defaultElbow;
1123
+ if (shouldUseAdaptiveElbow) {
1124
+ this.baseElbow = adaptiveElbow;
1125
+ }
1126
+ if (directShortPath) {
1127
+ this.baseElbow = directShortPath;
1128
+ }
1106
1129
  this.solvedTracePath = directShortPath;
1107
1130
  this.aabb = aabbFromPoints(
1108
1131
  { x: pin1.x, y: pin1.y },
@@ -7484,6 +7507,26 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7484
7507
  }
7485
7508
  findCorrectedCandidate(label, labelIndex) {
7486
7509
  const orientations = this.getAvailableOrientations(label);
7510
+ const requiredOrientation = orientations[0];
7511
+ const isTwoPinNet = this.inputProblem.netConnections.some(
7512
+ (connection) => connection.netId === label.netId && connection.pinIds.length === 2
7513
+ );
7514
+ if (isTwoPinNet && orientations.length === 1 && isYOrientation(requiredOrientation) && this.hasTraceContinuingInOrientation(label, requiredOrientation)) {
7515
+ const alignedCandidate = this.findValidCandidateInShiftColumn({
7516
+ label,
7517
+ labelIndex,
7518
+ orientation: requiredOrientation,
7519
+ direction: dir(requiredOrientation),
7520
+ baseAnchor: this.getWickOffsetAnchor(
7521
+ label.anchorPoint,
7522
+ requiredOrientation
7523
+ ),
7524
+ maxSearchDistance: this.maxSearchDistance,
7525
+ outwardDistance: 0,
7526
+ stopOnTraceCollision: false
7527
+ });
7528
+ if (alignedCandidate) return alignedCandidate;
7529
+ }
7487
7530
  const rotatedCandidate = this.findValidRotatedCandidate(
7488
7531
  label,
7489
7532
  labelIndex,
@@ -7508,6 +7551,21 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7508
7551
  labelIndex
7509
7552
  );
7510
7553
  }
7554
+ hasTraceContinuingInOrientation(label, orientation) {
7555
+ const direction = dir(orientation);
7556
+ return Object.values(this.traceMap).some((trace) => {
7557
+ if (trace.globalConnNetId !== label.globalConnNetId) return false;
7558
+ const path = trace.tracePath;
7559
+ return path.some((point, pointIndex) => {
7560
+ if (Math.abs(point.x - label.anchorPoint.x) > EPS9 || Math.abs(point.y - label.anchorPoint.y) > EPS9) {
7561
+ return false;
7562
+ }
7563
+ return [path[pointIndex - 1], path[pointIndex + 1]].some(
7564
+ (neighbor) => neighbor && Math.abs(neighbor.x - point.x) <= EPS9 && (neighbor.y - point.y) * direction.y > EPS9
7565
+ );
7566
+ });
7567
+ });
7568
+ }
7511
7569
  findValidRotatedCandidate(label, labelIndex, orientations) {
7512
7570
  for (const orientation of orientations) {
7513
7571
  const candidate = this.createCandidate(
@@ -7653,7 +7711,8 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7653
7711
  baseAnchor,
7654
7712
  maxSearchDistance,
7655
7713
  outwardDistance,
7656
- phase = "shift"
7714
+ phase = "shift",
7715
+ stopOnTraceCollision = true
7657
7716
  } = params;
7658
7717
  for (let distance4 = LABEL_SEARCH_STEP; distance4 <= maxSearchDistance + EPS9; distance4 += LABEL_SEARCH_STEP) {
7659
7718
  const anchorPoint = {
@@ -7674,7 +7733,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7674
7733
  result.selected = true;
7675
7734
  return result;
7676
7735
  }
7677
- if (result.status === "trace-collision") break;
7736
+ if (stopOnTraceCollision && result.status === "trace-collision") break;
7678
7737
  }
7679
7738
  return null;
7680
7739
  }
@@ -11,6 +11,7 @@ import {
11
11
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
12
12
  import type { InputPin, InputProblem } from "lib/types/InputProblem"
13
13
  import { dir, type FacingDirection } from "lib/utils/dir"
14
+ import { rectIntersectsAnyTextBox } from "lib/utils/textBoxBounds"
14
15
  import { EPS, LABEL_SEARCH_STEP, WICK_CLEARANCE } from "./constants"
15
16
  import {
16
17
  getConnectorTracePath,
@@ -22,8 +23,8 @@ import {
22
23
  rectsOverlap,
23
24
  simplifyOrthogonalPath,
24
25
  traceCrossesBoundsInterior,
25
- tracePathIntersectsBounds,
26
26
  tracePathCrossesAnyBounds,
27
+ tracePathIntersectsBounds,
27
28
  } from "./geometry"
28
29
  import { getPinMap, getTracePins, toNetLabelPlacementPatch } from "./traces"
29
30
  import type {
@@ -36,7 +37,6 @@ import type {
36
37
  EvaluatedCandidate,
37
38
  } from "./types"
38
39
  import { visualizeAvailableNetOrientationSolver } from "./visualize"
39
- import { rectIntersectsAnyTextBox } from "lib/utils/textBoxBounds"
40
40
 
41
41
  const LABEL_TRACE_CLEARANCE = 0.1
42
42
 
@@ -182,6 +182,34 @@ export class AvailableNetOrientationSolver extends BaseSolver {
182
182
 
183
183
  private findCorrectedCandidate(label: NetLabelPlacement, labelIndex: number) {
184
184
  const orientations = this.getAvailableOrientations(label)
185
+ const requiredOrientation = orientations[0]!
186
+ const isTwoPinNet = this.inputProblem.netConnections.some(
187
+ (connection) =>
188
+ connection.netId === label.netId && connection.pinIds.length === 2,
189
+ )
190
+ if (
191
+ isTwoPinNet &&
192
+ orientations.length === 1 &&
193
+ isYOrientation(requiredOrientation) &&
194
+ this.hasTraceContinuingInOrientation(label, requiredOrientation)
195
+ ) {
196
+ // Keep two-pin vertical net labels aligned with the existing trace.
197
+ const alignedCandidate = this.findValidCandidateInShiftColumn({
198
+ label,
199
+ labelIndex,
200
+ orientation: requiredOrientation,
201
+ direction: dir(requiredOrientation),
202
+ baseAnchor: this.getWickOffsetAnchor(
203
+ label.anchorPoint,
204
+ requiredOrientation,
205
+ ),
206
+ maxSearchDistance: this.maxSearchDistance,
207
+ outwardDistance: 0,
208
+ stopOnTraceCollision: false,
209
+ })
210
+ if (alignedCandidate) return alignedCandidate
211
+ }
212
+
185
213
  const rotatedCandidate = this.findValidRotatedCandidate(
186
214
  label,
187
215
  labelIndex,
@@ -210,6 +238,31 @@ export class AvailableNetOrientationSolver extends BaseSolver {
210
238
  )
211
239
  }
212
240
 
241
+ private hasTraceContinuingInOrientation(
242
+ label: NetLabelPlacement,
243
+ orientation: "y+" | "y-",
244
+ ) {
245
+ const direction = dir(orientation)
246
+ return Object.values(this.traceMap).some((trace) => {
247
+ if (trace.globalConnNetId !== label.globalConnNetId) return false
248
+ const path = trace.tracePath
249
+ return path.some((point, pointIndex) => {
250
+ if (
251
+ Math.abs(point.x - label.anchorPoint.x) > EPS ||
252
+ Math.abs(point.y - label.anchorPoint.y) > EPS
253
+ ) {
254
+ return false
255
+ }
256
+ return [path[pointIndex - 1], path[pointIndex + 1]].some(
257
+ (neighbor) =>
258
+ neighbor &&
259
+ Math.abs(neighbor.x - point.x) <= EPS &&
260
+ (neighbor.y - point.y) * direction.y > EPS,
261
+ )
262
+ })
263
+ })
264
+ }
265
+
213
266
  private findValidRotatedCandidate(
214
267
  label: NetLabelPlacement,
215
268
  labelIndex: number,
@@ -401,6 +454,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
401
454
  maxSearchDistance: number
402
455
  outwardDistance: number
403
456
  phase?: CandidatePhase
457
+ stopOnTraceCollision?: boolean
404
458
  }) {
405
459
  const {
406
460
  label,
@@ -411,6 +465,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
411
465
  maxSearchDistance,
412
466
  outwardDistance,
413
467
  phase = "shift",
468
+ stopOnTraceCollision = true,
414
469
  } = params
415
470
 
416
471
  for (
@@ -437,7 +492,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
437
492
  result.selected = true
438
493
  return result
439
494
  }
440
- if (result.status === "trace-collision") break
495
+ if (stopOnTraceCollision && result.status === "trace-collision") break
441
496
  }
442
497
 
443
498
  return null
@@ -28,6 +28,29 @@ import { getObstacleRects, type ObstacleRect } from "./rect"
28
28
 
29
29
  type PathKey = string
30
30
 
31
+ const calculateElbowForPins = ({
32
+ pin1,
33
+ pin2,
34
+ overshoot,
35
+ }: {
36
+ pin1: MspConnectionPair["pins"][number]
37
+ pin2: MspConnectionPair["pins"][number]
38
+ overshoot: number
39
+ }) =>
40
+ calculateElbow(
41
+ {
42
+ x: pin1.x,
43
+ y: pin1.y,
44
+ facingDirection: pin1._facingDirection!,
45
+ },
46
+ {
47
+ x: pin2.x,
48
+ y: pin2.y,
49
+ facingDirection: pin2._facingDirection!,
50
+ },
51
+ { overshoot },
52
+ )
53
+
31
54
  export class SchematicTraceSingleLineSolver2 extends BaseSolver {
32
55
  pins: MspConnectionPair["pins"]
33
56
  connectionPair?: MspConnectionPair
@@ -89,23 +112,30 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
89
112
 
90
113
  const [pin1, pin2] = this.pins
91
114
  const directShortPath = calculateDirectShortPath(pin1, pin2)
115
+ const defaultElbow = calculateElbowForPins({
116
+ pin1,
117
+ pin2,
118
+ overshoot: 0.2,
119
+ })
120
+ const routingDistance =
121
+ Math.abs(pin1.x - pin2.x) + Math.abs(pin1.y - pin2.y)
122
+ const adaptiveElbow = calculateElbowForPins({
123
+ pin1,
124
+ pin2,
125
+ overshoot: Math.min(0.2, Math.max(0.02, routingDistance / 4)),
126
+ })
127
+ const shouldUseAdaptiveElbow =
128
+ findFirstCollision(defaultElbow, this.obstacles) !== null &&
129
+ findFirstCollision(adaptiveElbow, this.obstacles) === null
92
130
 
93
131
  // Build initial elbow path
94
- this.baseElbow =
95
- directShortPath ??
96
- calculateElbow(
97
- {
98
- x: pin1.x,
99
- y: pin1.y,
100
- facingDirection: pin1._facingDirection!,
101
- },
102
- {
103
- x: pin2.x,
104
- y: pin2.y,
105
- facingDirection: pin2._facingDirection!,
106
- },
107
- { overshoot: 0.2 },
108
- )
132
+ this.baseElbow = defaultElbow
133
+ if (shouldUseAdaptiveElbow) {
134
+ this.baseElbow = adaptiveElbow
135
+ }
136
+ if (directShortPath) {
137
+ this.baseElbow = directShortPath
138
+ }
109
139
  this.solvedTracePath = directShortPath
110
140
 
111
141
  // Bounds defined by PA and PB
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.102",
4
+ "version": "0.0.104",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260717T042845Z/bug-report-20260717T042845Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -1,12 +1,8 @@
1
- <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="0.5279125,3.0973754500000004 0.7,3" data-type="line" data-label="" points="363.46296663543797,319.86511717601365 416.18376153125485,349.6970987396827" fill="none" stroke="hsl(72, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><rect data-type="rect" data-label="schematic_component_0" data-x="1" data-y="3" x="416.18376153125485" y="245.5345636073937" width="183.81623846874515" height="208.32507026457802" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.0032641294642857146"/></g><g><rect data-type="rect" data-label="schematic_component_1" data-x="0" data-y="3.1" x="40" y="186.14036612802857" width="323.46296663543797" height="265.8413857337265" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.0032641294642857146"/></g><g><rect data-type="rect" data-label="netId: .L1 &gt; .pin2 to .R1 &gt; .pin1
2
- globalConnNetId: connectivity_net0" data-x="0.7539125" data-y="3.0973754500000004" x="363.7693270328858" y="289.2290774312228" width="137.86217885155884" height="61.272079489581756" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.0032641294642857146"/></g><g><rect data-type="rect" data-label="netId: .L1 &gt; .pin2 to .R1 &gt; .pin1
3
- globalConnNetId: connectivity_net0" data-x="0.474" data-y="3" x="278.01522228224815" y="319.0610589948918" width="137.86217885155884" height="61.272079489581756" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.0032641294642857146"/></g><g><circle data-type="point" data-label="R1.1
1
+ <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="0.5279125,3.0973754500000004 0.7,3" data-type="line" data-label="" points="363.46296663543797,319.86511717601365 416.18376153125485,349.6970987396827" fill="none" stroke="hsl(72, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="0.5279125,3.0973754500000004 0.61395625,3.0973754500000004 0.61395625,3 0.7,3" data-type="line" data-label="" points="363.46296663543797,319.86511717601365 389.8233640833464,319.86511717601365 389.8233640833464,349.6970987396827 416.18376153125485,349.6970987396827" fill="none" stroke="purple" stroke-width="1px"/></g><g><rect data-type="rect" data-label="schematic_component_0" data-x="1" data-y="3" x="416.18376153125485" y="245.5345636073937" width="183.81623846874515" height="208.32507026457802" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.0032641294642857146"/></g><g><rect data-type="rect" data-label="schematic_component_1" data-x="0" data-y="3.1" x="40" y="186.14036612802857" width="323.46296663543797" height="265.8413857337265" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.0032641294642857146"/></g><g><circle data-type="point" data-label="R1.1
4
2
  x-" data-x="0.7" data-y="3" cx="416.18376153125485" cy="349.6970987396827" r="3" fill="hsl(226, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="R1.2
5
3
  x+" data-x="1.3" data-y="3" cx="600" cy="349.6970987396827" r="3" fill="hsl(227, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="L1.1
6
4
  x-" data-x="-0.5279125" data-y="3.10262455" cx="40.00000000000003" cy="318.25700081377" r="3" fill="hsl(40, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="L1.2
7
- x+" data-x="0.5279125" data-y="3.0973754500000004" cx="363.46296663543797" cy="319.86511717601365" r="3" fill="hsl(41, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
8
- orientation: x+" data-x="0.5279125" data-y="3.0973754500000004" cx="363.46296663543797" cy="319.86511717601365" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
9
- orientation: x-" data-x="0.7" data-y="3" cx="416.18376153125485" cy="349.6970987396827" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
5
+ x+" data-x="0.5279125" data-y="3.0973754500000004" cx="363.46296663543797" cy="319.86511717601365" r="3" fill="hsl(41, 100%, 50%, 0.8)"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
10
6
  document.currentScript.parentElement.addEventListener('mousemove', (e) => {
11
7
  const svg = e.currentTarget;
12
8
  const rect = svg.getBoundingClientRect();
@@ -1,8 +1,8 @@
1
- <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="-2.275,-2.5 -2.275,-3.5" data-type="line" data-label="" points="412.47706422018337,222.3853211009175 412.47706422018337,479.26605504587155" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1px" stroke-dasharray="4 2"/></g><g><polyline data-points="-2.275,-2.5 -2.0749999999999997,-2.5 -2.0749999999999997,-3.5 -2.275,-3.5" data-type="line" data-label="" points="412.47706422018337,222.3853211009175 463.8532110091743,222.3853211009175 463.8532110091743,479.26605504587155 412.47706422018337,479.26605504587155" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="-2.0749999999999997,-2.5 -1.7849999999999997,-2.5 -1.7849999999999997,-2.501" data-type="line" data-label="" points="463.8532110091743,222.3853211009175 538.3486238532109,222.3853211009175 538.3486238532109,222.64220183486248" fill="none" stroke="purple" stroke-width="1px"/></g><g><rect data-type="rect" data-label="schematic_component_0" data-x="-3" data-y="-2.5" x="40" y="171.0091743119267" width="372.47706422018337" height="102.75229357798162" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.003892857142857144"/></g><g><rect data-type="rect" data-label="schematic_component_1" data-x="-3" data-y="-3.5" x="40" y="427.88990825688074" width="372.47706422018337" height="102.75229357798162" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.003892857142857144"/></g><g><rect data-type="rect" data-label="J3" data-x="-3.205" data-y="-2.1849999999999996" x="127.3394495412843" y="109.35779816513764" width="92.47706422018348" height="64.22018348623851" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.003892857142857144"/></g><g><rect data-type="rect" data-label="J4" data-x="-3.205" data-y="-3.1849999999999996" x="127.3394495412843" y="366.2385321100917" width="92.47706422018348" height="64.22018348623851" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.003892857142857144"/></g><g><rect data-type="rect" data-label="netId: GND
2
- globalConnNetId: connectivity_net0" data-x="-1.7849999999999997" data-y="-2.711" x="476.697247706422" y="222.64220183486242" width="123.3027522935779" height="107.88990825688063" fill="#00000066" stroke="#000000" stroke-width="0.003892857142857144"/></g><g><circle data-type="point" data-label="J3.1
3
- x+" data-x="-2.275" data-y="-2.5" cx="412.47706422018337" cy="222.3853211009175" r="3" fill="hsl(340, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="J4.1
4
- x+" data-x="-2.275" data-y="-3.5" cx="412.47706422018337" cy="479.26605504587155" r="3" fill="hsl(221, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
5
- orientation: y-" data-x="-1.7849999999999997" data-y="-2.501" cx="538.3486238532109" cy="222.64220183486248" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
1
+ <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="-2.275,-2.5 -2.275,-3.5" data-type="line" data-label="" points="453.96494552344836,156.72193273330174 453.96494552344836,421.99905258171486" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1px" stroke-dasharray="4 2"/></g><g><polyline data-points="-2.275,-2.5 -2.0749999999999997,-2.5 -2.0749999999999997,-3.5 -2.275,-3.5" data-type="line" data-label="" points="453.96494552344836,156.72193273330174 507.02036949313106,156.72193273330174 507.02036949313106,421.99905258171486 453.96494552344836,421.99905258171486" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="-2.0749999999999997,-2.5 -2.0749999999999997,-3.7510000000000003" data-type="line" data-label="" points="507.02036949313106,156.72193273330174 507.02036949313106,488.5836096636666" fill="none" stroke="purple" stroke-width="1px"/></g><g><rect data-type="rect" data-label="schematic_component_0" data-x="-3" data-y="-2.5" x="69.31312174324955" y="103.6665087636191" width="384.6518237801988" height="106.11084793936527" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.003769642857142858"/></g><g><rect data-type="rect" data-label="schematic_component_1" data-x="-3" data-y="-3.5" x="69.31312174324955" y="368.9436286120322" width="384.6518237801988" height="106.11084793936527" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.003769642857142858"/></g><g><rect data-type="rect" data-label="J3" data-x="-3.205" data-y="-2.1849999999999996" x="159.50734249170984" y="39.99999999999994" width="95.4997631454288" height="66.31927996210322" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.003769642857142858"/></g><g><rect data-type="rect" data-label="J4" data-x="-3.205" data-y="-3.1849999999999996" x="159.50734249170984" y="305.27711984841295" width="95.4997631454288" height="66.31927996210322" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.003769642857142858"/></g><g><rect data-type="rect" data-label="netId: GND
2
+ globalConnNetId: connectivity_net0" data-x="-2.0749999999999997" data-y="-3.9610000000000003" x="443.353860729512" y="488.5836096636665" width="127.3330175272381" height="111.4163903363335" fill="#00000066" stroke="#000000" stroke-width="0.003769642857142858"/></g><g><circle data-type="point" data-label="J3.1
3
+ x+" data-x="-2.275" data-y="-2.5" cx="453.96494552344836" cy="156.72193273330174" r="3" fill="hsl(340, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="J4.1
4
+ x+" data-x="-2.275" data-y="-3.5" cx="453.96494552344836" cy="421.99905258171486" r="3" fill="hsl(221, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
5
+ orientation: y-" data-x="-2.0749999999999997" data-y="-3.7510000000000003" cx="507.02036949313106" cy="488.5836096636666" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
6
6
  document.currentScript.parentElement.addEventListener('mousemove', (e) => {
7
7
  const svg = e.currentTarget;
8
8
  const rect = svg.getBoundingClientRect();
@@ -24,7 +24,7 @@ orientation: y-" data-x="-1.7849999999999997" data-y="-2.501" cx="538.3486238532
24
24
  v.setAttribute('y2', '640');
25
25
 
26
26
  // Calculate real coordinates using inverse transformation
27
- const matrix = {"a":256.88073394495405,"c":0,"e":996.8807339449538,"b":0,"d":-256.88073394495405,"f":-419.8165137614676};
27
+ const matrix = {"a":265.277119848413,"c":0,"e":1057.470393178588,"b":0,"d":-265.277119848413,"f":-506.4708668877307};
28
28
  // Manually invert and apply the affine transform
29
29
  // Since we only use translate and scale, we can directly compute:
30
30
  // x' = (x - tx) / sx
@@ -0,0 +1,54 @@
1
+ <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="-0.6000000000000001,0.1 -0.6000000000000001,-0.1" data-type="line" data-label="" points="213.9715237806725,494.81975159042713 213.9715237806725,528.748863980612" fill="none" stroke="hsl(27, 100%, 50%, 0.8)" stroke-width="1px" stroke-dasharray="4 2"/></g><g><polyline data-points="-0.6000000000000001,0.1 -0.6599999999999999,2.36" data-type="line" data-label="" points="213.9715237806725,494.81975159042713 203.79279006361708,111.42078158133899" fill="none" stroke="hsl(27, 100%, 50%, 0.8)" stroke-width="1px" stroke-dasharray="4 2"/></g><g><polyline data-points="-0.6000000000000001,-0.1 -0.6599999999999999,2.36" data-type="line" data-label="" points="213.9715237806725,528.748863980612 203.79279006361708,111.42078158133899" fill="none" stroke="hsl(27, 100%, 50%, 0.8)" stroke-width="1px" stroke-dasharray="4 2"/></g><g><polyline data-points="0.6000000000000001,0.1 0.6000000000000001,-0.1" data-type="line" data-label="" points="417.5461981217813,494.81975159042713 417.5461981217813,528.748863980612" fill="none" stroke="hsl(64, 100%, 50%, 0.8)" stroke-width="1px" stroke-dasharray="4 2"/></g><g><polyline data-points="0.6000000000000001,0.1 0.6599999999999999,2.36" data-type="line" data-label="" points="417.5461981217813,494.81975159042713 427.7249318388367,111.42078158133899" fill="none" stroke="hsl(64, 100%, 50%, 0.8)" stroke-width="1px" stroke-dasharray="4 2"/></g><g><polyline data-points="0.6000000000000001,-0.1 0.6599999999999999,2.36" data-type="line" data-label="" points="417.5461981217813,528.748863980612 427.7249318388367,111.42078158133899" fill="none" stroke="hsl(64, 100%, 50%, 0.8)" stroke-width="1px" stroke-dasharray="4 2"/></g><g><polyline data-points="-0.6000000000000001,-0.1 -0.8,-0.1 -0.8,0.1 -0.6000000000000001,0.1" data-type="line" data-label="" points="213.9715237806725,528.748863980612 180.0424113904877,528.748863980612 180.0424113904877,494.81975159042713 213.9715237806725,494.81975159042713" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="-0.6599999999999999,2.36 -0.8599999999999999,2.36 -0.8599999999999999,0.1 -0.6000000000000001,0.1" data-type="line" data-label="" points="203.79279006361708,111.42078158133899 169.86367767343228,111.42078158133899 169.86367767343228,494.81975159042713 213.9715237806725,494.81975159042713" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="0.6000000000000001,0.1 1.4400000000000002,0.1 1.4400000000000002,-0.1 0.6000000000000001,-0.1" data-type="line" data-label="" points="417.5461981217813,494.81975159042713 560.0484701605575,494.81975159042713 560.0484701605575,528.748863980612 417.5461981217813,528.748863980612" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="0.6599999999999999,2.36 1.4400000000000002,2.36 1.4400000000000002,0.1 0.6000000000000001,0.1" data-type="line" data-label="" points="427.7249318388367,111.42078158133899 560.0484701605575,111.42078158133899 560.0484701605575,494.81975159042713 417.5461981217813,494.81975159042713" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="-0.8599999999999999,2.36 -1.15,2.36 -1.15,2.3609999999999998" data-type="line" data-label="" points="169.86367767343228,111.42078158133899 120.66646470766432,111.42078158133899 120.66646470766432,111.25113601938807" fill="none" stroke="purple" stroke-width="1px"/></g><g><rect data-type="rect" data-label="schematic_component_0" data-x="0" data-y="0" x="213.9715237806725" y="460.8906392002424" width="203.5746743411088" height="101.7873371705544" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.005894642857142856"/></g><g><rect data-type="rect" data-label="schematic_component_1" data-x="0" data-y="2.32" x="203.79279006361708" y="46.95546803998786" width="223.93214177521963" height="142.50227203877614" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.005894642857142856"/></g><g><rect data-type="rect" data-label="5x20-BLX-A" data-x="0.4" data-y="-0.43000000000000005" x="281.8297485610421" y="569.4637988488337" width="203.5746743411088" height="30.536201151166324" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.005894642857142856"/></g><g><rect data-type="rect" data-label="FUSEX20MM" data-x="0.3400000000000001" data-y="0.41500000000000004" x="271.6510148439867" y="420.1757043320206" width="203.5746743411088" height="42.41139048773101" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.005894642857142856"/></g><g><rect data-type="rect" data-label="netId: VIN
2
+ globalConnNetId: connectivity_net0" data-x="-1.15" data-y="2.5709999999999997" x="79.95152983944257" y="39.99999999999997" width="81.4298697364435" height="71.25113601938807" fill="#ef444466" stroke="#ef4444" stroke-width="0.005894642857142856"/></g><g><rect data-type="rect" data-label="netId: VOUT
3
+ globalConnNetId: connectivity_net1" data-x="1.05" data-y="2.57" x="442.9930324144199" y="40.16964556195089" width="101.78733717055445" height="71.25113601938807" fill="#ef444466" stroke="#ef4444" stroke-width="0.005894642857142856"/></g><g><circle data-type="point" data-label="F1.1
4
+ x-" data-x="-0.6000000000000001" data-y="0.1" cx="213.9715237806725" cy="494.81975159042713" r="3" fill="hsl(214, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="F1.2
5
+ x-" data-x="-0.6000000000000001" data-y="-0.1" cx="213.9715237806725" cy="528.748863980612" r="3" fill="hsl(215, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="F1.4
6
+ x+" data-x="0.6000000000000001" data-y="0.1" cx="417.5461981217813" cy="494.81975159042713" r="3" fill="hsl(217, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="F1.3
7
+ x+" data-x="0.6000000000000001" data-y="-0.1" cx="417.5461981217813" cy="528.748863980612" r="3" fill="hsl(216, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="F2.1
8
+ x-" data-x="-0.6599999999999999" data-y="2.36" cx="203.79279006361708" cy="111.42078158133899" r="3" fill="hsl(95, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="F2.2
9
+ x+" data-x="0.6599999999999999" data-y="2.36" cx="427.7249318388367" cy="111.42078158133899" r="3" fill="hsl(96, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
10
+ orientation: y+" data-x="-1.15" data-y="2.3609999999999998" cx="120.66646470766432" cy="111.25113601938807" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
11
+ orientation: y+" data-x="1.05" data-y="2.36" cx="493.8867009996971" cy="111.42078158133899" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
12
+ document.currentScript.parentElement.addEventListener('mousemove', (e) => {
13
+ const svg = e.currentTarget;
14
+ const rect = svg.getBoundingClientRect();
15
+ const x = e.clientX - rect.left;
16
+ const y = e.clientY - rect.top;
17
+ const crosshair = svg.getElementById('crosshair');
18
+ const h = svg.getElementById('crosshair-h');
19
+ const v = svg.getElementById('crosshair-v');
20
+ const coords = svg.getElementById('coordinates');
21
+
22
+ crosshair.style.display = 'block';
23
+ h.setAttribute('x1', '0');
24
+ h.setAttribute('x2', '640');
25
+ h.setAttribute('y1', y);
26
+ h.setAttribute('y2', y);
27
+ v.setAttribute('x1', x);
28
+ v.setAttribute('x2', x);
29
+ v.setAttribute('y1', '0');
30
+ v.setAttribute('y2', '640');
31
+
32
+ // Calculate real coordinates using inverse transformation
33
+ const matrix = {"a":169.645561950924,"c":0,"e":315.7588609512269,"b":0,"d":-169.645561950924,"f":511.78430778551956};
34
+ // Manually invert and apply the affine transform
35
+ // Since we only use translate and scale, we can directly compute:
36
+ // x' = (x - tx) / sx
37
+ // y' = (y - ty) / sy
38
+ const sx = matrix.a;
39
+ const sy = matrix.d;
40
+ const tx = matrix.e;
41
+ const ty = matrix.f;
42
+ const realPoint = {
43
+ x: (x - tx) / sx,
44
+ y: (y - ty) / sy // Flip y back since we used negative scale
45
+ }
46
+
47
+ coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
48
+ coords.setAttribute('x', (x + 5).toString());
49
+ coords.setAttribute('y', (y - 5).toString());
50
+ });
51
+ document.currentScript.parentElement.addEventListener('mouseleave', () => {
52
+ document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
53
+ });
54
+ ]]></script></svg>
@@ -0,0 +1,113 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "schematic_component_0",
5
+ "center": {
6
+ "x": 0,
7
+ "y": 0
8
+ },
9
+ "width": 1.2000000000000002,
10
+ "height": 0.6000000000000001,
11
+ "pins": [
12
+ {
13
+ "pinId": "F1.1",
14
+ "x": -0.6000000000000001,
15
+ "y": 0.1
16
+ },
17
+ {
18
+ "pinId": "F1.2",
19
+ "x": -0.6000000000000001,
20
+ "y": -0.1
21
+ },
22
+ {
23
+ "pinId": "F1.4",
24
+ "x": 0.6000000000000001,
25
+ "y": 0.1
26
+ },
27
+ {
28
+ "pinId": "F1.3",
29
+ "x": 0.6000000000000001,
30
+ "y": -0.1
31
+ }
32
+ ]
33
+ },
34
+ {
35
+ "chipId": "schematic_component_1",
36
+ "center": {
37
+ "x": 0,
38
+ "y": 2.32
39
+ },
40
+ "width": 1.3199999999999998,
41
+ "height": 0.8399999999999999,
42
+ "pins": [
43
+ {
44
+ "pinId": "F2.1",
45
+ "x": -0.6599999999999999,
46
+ "y": 2.36,
47
+ "_facingDirection": "x-"
48
+ },
49
+ {
50
+ "pinId": "F2.2",
51
+ "x": 0.6599999999999999,
52
+ "y": 2.36,
53
+ "_facingDirection": "x+"
54
+ }
55
+ ]
56
+ }
57
+ ],
58
+ "directConnections": [],
59
+ "netConnections": [
60
+ {
61
+ "netId": "VIN",
62
+ "pinIds": [
63
+ "F1.1",
64
+ "F1.2",
65
+ "F2.1"
66
+ ],
67
+ "netLabelWidth": 0.42,
68
+ "netLabelHeight": 0.48
69
+ },
70
+ {
71
+ "netId": "VOUT",
72
+ "pinIds": [
73
+ "F1.4",
74
+ "F1.3",
75
+ "F2.2"
76
+ ],
77
+ "netLabelWidth": 0.42,
78
+ "netLabelHeight": 0.6
79
+ }
80
+ ],
81
+ "textBoxes": [
82
+ {
83
+ "chipId": "schematic_component_0",
84
+ "center": {
85
+ "x": 0.4,
86
+ "y": -0.43000000000000005
87
+ },
88
+ "width": 1.2,
89
+ "height": 0.17999999999999994,
90
+ "text": "5x20-BLX-A"
91
+ },
92
+ {
93
+ "chipId": "schematic_component_0",
94
+ "center": {
95
+ "x": 0.3400000000000001,
96
+ "y": 0.41500000000000004
97
+ },
98
+ "width": 1.2000000000000002,
99
+ "height": 0.25,
100
+ "text": "FUSEX20MM"
101
+ }
102
+ ],
103
+ "availableNetLabelOrientations": {
104
+ "VIN": [
105
+ "y+"
106
+ ],
107
+ "VOUT": [
108
+ "y+"
109
+ ]
110
+ },
111
+ "maxMspPairDistance": 2.4,
112
+ "_hideRatsNet": false
113
+ }
@@ -0,0 +1,12 @@
1
+ import { expect, test } from "bun:test"
2
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
+ import inputProblem from "./bug-report-20260717T042845Z.json"
4
+ import "tests/fixtures/matcher"
5
+
6
+ test("bug-report-20260717T042845Z", () => {
7
+ const solver = new SchematicTracePipelineSolver(inputProblem as any)
8
+
9
+ solver.solve()
10
+
11
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
12
+ })