@tscircuit/schematic-trace-solver 0.0.99 → 0.0.100

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
@@ -801,6 +801,7 @@ var isPathCollidingWithObstacles = (path, obstacles) => {
801
801
 
802
802
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/mid.ts
803
803
  var EPS2 = 1e-9;
804
+ var OBSTACLE_CLEARANCE = 0.2;
804
805
  var aabbFromPoints = (a, b) => ({
805
806
  minX: Math.min(a.x, b.x),
806
807
  maxX: Math.max(a.x, b.x),
@@ -815,7 +816,7 @@ var midBetweenPointAndRect = (axis, p, r, eps = EPS2) => {
815
816
  if (p.x > r.maxX + eps) {
816
817
  return [(p.x + r.maxX) / 2];
817
818
  }
818
- return [r.minX - 0.2, r.maxX + 0.2];
819
+ return [r.minX - OBSTACLE_CLEARANCE, r.maxX + OBSTACLE_CLEARANCE];
819
820
  } else {
820
821
  if (p.y < r.minY - eps) {
821
822
  return [(p.y + r.minY) / 2];
@@ -823,10 +824,11 @@ var midBetweenPointAndRect = (axis, p, r, eps = EPS2) => {
823
824
  if (p.y > r.maxY + eps) {
824
825
  return [(p.y + r.maxY) / 2];
825
826
  }
826
- return [r.minY - 0.2, r.maxY + 0.2];
827
+ return [r.minY - OBSTACLE_CLEARANCE, r.maxY + OBSTACLE_CLEARANCE];
827
828
  }
828
829
  };
829
- var candidateMidsFromSet = (axis, colliding, collisionRects, aabb, eps = EPS2) => {
830
+ var candidateMidsFromSet = (axis, colliding, collisionRects, aabb, opts = {}) => {
831
+ const { allowOpenSideCandidates = false, eps = EPS2 } = opts;
830
832
  const setRects = [...collisionRects];
831
833
  if (axis === "x") {
832
834
  const leftBoundaries = [aabb.minX, ...setRects.map((r) => r.maxX)].filter(
@@ -840,9 +842,13 @@ var candidateMidsFromSet = (axis, colliding, collisionRects, aabb, eps = EPS2) =
840
842
  const out = [];
841
843
  if (leftNeighbor !== void 0) {
842
844
  out.push((leftNeighbor + colliding.minX) / 2);
845
+ } else if (allowOpenSideCandidates) {
846
+ out.push(colliding.minX - OBSTACLE_CLEARANCE);
843
847
  }
844
848
  if (rightNeighbor !== void 0) {
845
849
  out.push((colliding.maxX + rightNeighbor) / 2);
850
+ } else if (allowOpenSideCandidates) {
851
+ out.push(colliding.maxX + OBSTACLE_CLEARANCE);
846
852
  }
847
853
  return out;
848
854
  } else {
@@ -857,9 +863,13 @@ var candidateMidsFromSet = (axis, colliding, collisionRects, aabb, eps = EPS2) =
857
863
  const out = [];
858
864
  if (bottomNeighbor !== void 0) {
859
865
  out.push((bottomNeighbor + colliding.minY) / 2);
866
+ } else if (allowOpenSideCandidates) {
867
+ out.push(colliding.minY - OBSTACLE_CLEARANCE);
860
868
  }
861
869
  if (topNeighbor !== void 0) {
862
870
  out.push((colliding.maxY + topNeighbor) / 2);
871
+ } else if (allowOpenSideCandidates) {
872
+ out.push(colliding.maxY + OBSTACLE_CLEARANCE);
863
873
  }
864
874
  return out;
865
875
  }
@@ -1318,6 +1328,9 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1318
1328
  return;
1319
1329
  }
1320
1330
  const candidates = [];
1331
+ const candidateMidOptions = {
1332
+ allowOpenSideCandidates: this.connectionPair !== void 0
1333
+ };
1321
1334
  if (collisionRects.size === 0) {
1322
1335
  const m1 = midBetweenPointAndRect(axis, { x: PA.x, y: PA.y }, rect);
1323
1336
  const m2 = midBetweenPointAndRect(axis, { x: PB.x, y: PB.y }, rect);
@@ -1325,7 +1338,13 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1325
1338
  const uniqueCandidates = [...new Set(allCandidates)];
1326
1339
  candidates.push(...uniqueCandidates);
1327
1340
  } else {
1328
- const mids = candidateMidsFromSet(axis, rect, collisionRects, this.aabb);
1341
+ const mids = candidateMidsFromSet(
1342
+ axis,
1343
+ rect,
1344
+ collisionRects,
1345
+ this.aabb,
1346
+ candidateMidOptions
1347
+ );
1329
1348
  candidates.push(...mids);
1330
1349
  }
1331
1350
  const newStates = [];
@@ -1371,7 +1390,8 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1371
1390
  adjacentAxis,
1372
1391
  rect,
1373
1392
  collisionRects,
1374
- this.aabb
1393
+ this.aabb,
1394
+ candidateMidOptions
1375
1395
  )
1376
1396
  );
1377
1397
  }
@@ -413,6 +413,11 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
413
413
 
414
414
  // Note: PA and PB are already defined above
415
415
  const candidates: number[] = []
416
+ // Fixed MSP pairs may leave the endpoint bounds to complete a required
417
+ // route. Optional long-distance candidates retain their bounded search.
418
+ const candidateMidOptions = {
419
+ allowOpenSideCandidates: this.connectionPair !== undefined,
420
+ }
416
421
 
417
422
  if (collisionRects.size === 0) {
418
423
  // First collision on this search branch: use mid(PA, C) and mid(PB, C)
@@ -425,7 +430,13 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
425
430
  candidates.push(...uniqueCandidates)
426
431
  } else {
427
432
  // Subsequent collisions: mid between C and nearest rect/bounds from the set
428
- const mids = candidateMidsFromSet(axis, rect, collisionRects, this.aabb)
433
+ const mids = candidateMidsFromSet(
434
+ axis,
435
+ rect,
436
+ collisionRects,
437
+ this.aabb,
438
+ candidateMidOptions,
439
+ )
429
440
  candidates.push(...mids)
430
441
  }
431
442
 
@@ -496,6 +507,7 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
496
507
  rect,
497
508
  collisionRects,
498
509
  this.aabb,
510
+ candidateMidOptions,
499
511
  ),
500
512
  )
501
513
  }
@@ -2,6 +2,7 @@ import type { Point } from "@tscircuit/math-utils"
2
2
  import type { ObstacleRect } from "./rect"
3
3
 
4
4
  const EPS = 1e-9
5
+ const OBSTACLE_CLEARANCE = 0.2
5
6
 
6
7
  export type Axis = "x" | "y"
7
8
 
@@ -26,7 +27,7 @@ export const midBetweenPointAndRect = (
26
27
  return [(p.x + r.maxX) / 2]
27
28
  }
28
29
  // Point is within rect bounds on this axis - generate candidates on both sides
29
- return [r.minX - 0.2, r.maxX + 0.2]
30
+ return [r.minX - OBSTACLE_CLEARANCE, r.maxX + OBSTACLE_CLEARANCE]
30
31
  } else {
31
32
  if (p.y < r.minY - eps) {
32
33
  return [(p.y + r.minY) / 2]
@@ -35,7 +36,7 @@ export const midBetweenPointAndRect = (
35
36
  return [(p.y + r.maxY) / 2]
36
37
  }
37
38
  // Point is within rect bounds on this axis - generate candidates on both sides
38
- return [r.minY - 0.2, r.maxY + 0.2]
39
+ return [r.minY - OBSTACLE_CLEARANCE, r.maxY + OBSTACLE_CLEARANCE]
39
40
  }
40
41
  }
41
42
 
@@ -44,8 +45,9 @@ export const candidateMidsFromSet = (
44
45
  colliding: ObstacleRect,
45
46
  collisionRects: Set<ObstacleRect>,
46
47
  aabb: { minX: number; maxX: number; minY: number; maxY: number },
47
- eps = EPS,
48
+ opts: { allowOpenSideCandidates?: boolean; eps?: number } = {},
48
49
  ): number[] => {
50
+ const { allowOpenSideCandidates = false, eps = EPS } = opts
49
51
  const setRects = [...collisionRects]
50
52
 
51
53
  if (axis === "x") {
@@ -64,9 +66,13 @@ export const candidateMidsFromSet = (
64
66
  const out: number[] = []
65
67
  if (leftNeighbor !== undefined) {
66
68
  out.push((leftNeighbor + colliding.minX) / 2)
69
+ } else if (allowOpenSideCandidates) {
70
+ out.push(colliding.minX - OBSTACLE_CLEARANCE)
67
71
  }
68
72
  if (rightNeighbor !== undefined) {
69
73
  out.push((colliding.maxX + rightNeighbor) / 2)
74
+ } else if (allowOpenSideCandidates) {
75
+ out.push(colliding.maxX + OBSTACLE_CLEARANCE)
70
76
  }
71
77
  return out
72
78
  } else {
@@ -85,9 +91,13 @@ export const candidateMidsFromSet = (
85
91
  const out: number[] = []
86
92
  if (bottomNeighbor !== undefined) {
87
93
  out.push((bottomNeighbor + colliding.minY) / 2)
94
+ } else if (allowOpenSideCandidates) {
95
+ out.push(colliding.minY - OBSTACLE_CLEARANCE)
88
96
  }
89
97
  if (topNeighbor !== undefined) {
90
98
  out.push((colliding.maxY + topNeighbor) / 2)
99
+ } else if (allowOpenSideCandidates) {
100
+ out.push(colliding.maxY + OBSTACLE_CLEARANCE)
91
101
  }
92
102
  return out
93
103
  }
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.99",
4
+ "version": "0.0.100",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,65 @@
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.6499999999999999,0.825 -0.6499999999999999,-0.825" data-type="line" data-label="" points="410.65315315315314,198.6036036036036 205.6981981981982,458.7387387387388" fill="none" stroke="hsl(224, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="1.25,0.825 -1.25,-0.825" data-type="line" data-label="" points="505.2477477477478,198.6036036036036 111.1036036036036,458.7387387387388" fill="none" stroke="hsl(224, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="0.49999999999999994,-0.725 -1.25,0.825" data-type="line" data-label="" points="387.0045045045045,442.97297297297297 111.1036036036036,198.6036036036036" fill="none" stroke="hsl(168, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="1.4,-0.725 -0.6499999999999999,0.825" data-type="line" data-label="" points="528.8963963963964,442.97297297297297 205.6981981981982,198.6036036036036" fill="none" stroke="hsl(168, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="0.6499999999999999,0.825 0,0.825 0,-0.825 -0.6499999999999999,-0.825" data-type="line" data-label="" points="410.65315315315314,198.6036036036036 308.1756756756757,198.6036036036036 308.1756756756757,458.7387387387388 205.6981981981982,458.7387387387388" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="1.25,0.825 1.4500000000000002,0.825 1.4500000000000002,0 -1.45,0 -1.45,-0.825 -1.25,-0.825" data-type="line" data-label="" points="505.2477477477478,198.6036036036036 536.7792792792793,198.6036036036036 536.7792792792793,328.6711711711712 79.57207207207207,328.6711711711712 79.57207207207207,458.7387387387388 111.1036036036036,458.7387387387388" fill="none" stroke="purple" stroke-width="1px"/></g><g><rect data-type="rect" data-label="schematic_component_0" data-x="-0.95" data-y="0.825" x="111.10360360360359" y="145" width="94.59459459459461" height="107.20720720720723" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.006342857142857142"/></g><g><rect data-type="rect" data-label="schematic_component_1" data-x="0.95" data-y="0.825" x="410.6531531531532" y="145" width="94.59459459459464" height="107.20720720720723" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.006342857142857142"/></g><g><rect data-type="rect" data-label="schematic_component_2" data-x="-0.95" data-y="-0.825" x="111.10360360360359" y="405.13513513513516" width="94.59459459459461" height="107.20720720720726" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.006342857142857142"/></g><g><rect data-type="rect" data-label="schematic_component_3" data-x="0.95" data-y="-0.825" x="387.00450450450455" y="414.9290282263514" width="141.89189189189193" height="87.61942102477474" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.006342857142857142"/></g><g><rect data-type="rect" data-label="netId: .J1 &gt; .pin1 to .R1 &gt; .pin1
2
+ globalConnNetId: connectivity_net2" data-x="0.2739999999999999" data-y="-0.725" x="315.9009009009009" y="427.20720720720715" width="70.94594594594594" height="31.531531531531584" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.006342857142857142"/></g><g><rect data-type="rect" data-label="netId: .J1 &gt; .pin1 to .R1 &gt; .pin1
3
+ globalConnNetId: connectivity_net2" data-x="-1.476" data-y="0.825" x="39.999999999999986" y="182.8378378378378" width="70.94594594594597" height="31.531531531531556" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.006342857142857142"/></g><g><rect data-type="rect" data-label="netId: .J1 &gt; .pin3 to .R1 &gt; .pin2
4
+ globalConnNetId: connectivity_net3" data-x="1.626" data-y="-0.725" x="529.0540540540542" y="427.20720720720715" width="70.94594594594594" height="31.531531531531584" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.006342857142857142"/></g><g><rect data-type="rect" data-label="netId: .J1 &gt; .pin3 to .R1 &gt; .pin2
5
+ globalConnNetId: connectivity_net3" data-x="-0.42399999999999993" data-y="0.825" x="205.85585585585585" y="182.8378378378378" width="70.94594594594594" height="31.531531531531556" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.006342857142857142"/></g><g><rect data-type="rect" data-label="netId: .R2 &gt; .pin1 to .R3 &gt; .pin2
6
+ globalConnNetId: connectivity_net0" data-x="0.32499999999999996" data-y="1.05" x="343.64864864864865" y="127.65765765765765" width="31.531531531531527" height="70.94594594594597" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.006342857142857142"/></g><g><rect data-type="rect" data-label="netId: .R2 &gt; .pin2 to .R3 &gt; .pin1
7
+ globalConnNetId: connectivity_net1" data-x="1.4500000000000002" data-y="1.05" x="521.0135135135135" y="127.65765765765765" width="31.531531531531527" height="70.94594594594597" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.006342857142857142"/></g><g><circle data-type="point" data-label="R1.1
8
+ x-" data-x="-1.25" data-y="0.825" cx="111.1036036036036" cy="198.6036036036036" r="3" fill="hsl(226, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="R1.2
9
+ x+" data-x="-0.6499999999999999" data-y="0.825" cx="205.6981981981982" cy="198.6036036036036" r="3" fill="hsl(227, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="R2.1
10
+ x-" data-x="0.6499999999999999" data-y="0.825" cx="410.65315315315314" cy="198.6036036036036" r="3" fill="hsl(107, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="R2.2
11
+ x+" data-x="1.25" data-y="0.825" cx="505.2477477477478" cy="198.6036036036036" r="3" fill="hsl(108, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="R3.1
12
+ x-" data-x="-1.25" data-y="-0.825" cx="111.1036036036036" cy="458.7387387387388" r="3" fill="hsl(348, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="R3.2
13
+ x+" data-x="-0.6499999999999999" data-y="-0.825" cx="205.6981981981982" cy="458.7387387387388" r="3" fill="hsl(349, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="J1.1
14
+ x-" data-x="0.49999999999999994" data-y="-0.725" cx="387.0045045045045" cy="442.97297297297297" r="3" fill="hsl(218, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="J1.2
15
+ y-" data-x="0.95" data-y="-1.1028787352499998" cx="457.9504504504505" cy="502.5484492511261" r="3" fill="hsl(219, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="J1.3
16
+ x+" data-x="1.4" data-y="-0.725" cx="528.8963963963964" cy="442.97297297297297" r="3" fill="hsl(220, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
17
+ orientation: x-" data-x="0.49999999999999994" data-y="-0.725" cx="387.0045045045045" cy="442.97297297297297" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
18
+ orientation: x-" data-x="-1.25" data-y="0.825" cx="111.1036036036036" cy="198.6036036036036" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
19
+ orientation: x+" data-x="1.4" data-y="-0.725" cx="528.8963963963964" cy="442.97297297297297" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
20
+ orientation: x+" data-x="-0.6499999999999999" data-y="0.825" cx="205.6981981981982" cy="198.6036036036036" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
21
+ orientation: y+" data-x="0.32499999999999996" data-y="0.825" cx="359.4144144144144" cy="198.6036036036036" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
22
+ orientation: y+" data-x="1.4500000000000002" data-y="0.825" cx="536.7792792792793" cy="198.6036036036036" 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[
23
+ document.currentScript.parentElement.addEventListener('mousemove', (e) => {
24
+ const svg = e.currentTarget;
25
+ const rect = svg.getBoundingClientRect();
26
+ const x = e.clientX - rect.left;
27
+ const y = e.clientY - rect.top;
28
+ const crosshair = svg.getElementById('crosshair');
29
+ const h = svg.getElementById('crosshair-h');
30
+ const v = svg.getElementById('crosshair-v');
31
+ const coords = svg.getElementById('coordinates');
32
+
33
+ crosshair.style.display = 'block';
34
+ h.setAttribute('x1', '0');
35
+ h.setAttribute('x2', '640');
36
+ h.setAttribute('y1', y);
37
+ h.setAttribute('y2', y);
38
+ v.setAttribute('x1', x);
39
+ v.setAttribute('x2', x);
40
+ v.setAttribute('y1', '0');
41
+ v.setAttribute('y2', '640');
42
+
43
+ // Calculate real coordinates using inverse transformation
44
+ const matrix = {"a":157.65765765765767,"c":0,"e":308.1756756756757,"b":0,"d":-157.65765765765767,"f":328.6711711711712};
45
+ // Manually invert and apply the affine transform
46
+ // Since we only use translate and scale, we can directly compute:
47
+ // x' = (x - tx) / sx
48
+ // y' = (y - ty) / sy
49
+ const sx = matrix.a;
50
+ const sy = matrix.d;
51
+ const tx = matrix.e;
52
+ const ty = matrix.f;
53
+ const realPoint = {
54
+ x: (x - tx) / sx,
55
+ y: (y - ty) / sy // Flip y back since we used negative scale
56
+ }
57
+
58
+ coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
59
+ coords.setAttribute('x', (x + 5).toString());
60
+ coords.setAttribute('y', (y - 5).toString());
61
+ });
62
+ document.currentScript.parentElement.addEventListener('mouseleave', () => {
63
+ document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
64
+ });
65
+ ]]></script></svg>
@@ -0,0 +1,74 @@
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.52,1 -0.9,0.7999999999999999" data-type="line" data-label="" points="476.7266775777414,259.0507364975451 163.27332242225856,277.3813420621932" fill="none" stroke="hsl(248, 100%, 50%, 0.8)" stroke-width="1px"/></g><g><polyline data-points="2.5200000000000005,1 2.7200000000000006,1 2.7200000000000006,3.3900000000000006 -1.1,3.3900000000000006 -1.1,0.7999999999999999 -0.9,0.7999999999999999" data-type="line" data-label="" points="476.7266775777414,259.0507364975451 495.0572831423895,259.0507364975451 495.0572831423895,40 144.94271685761043,40 144.94271685761043,277.3813420621932 163.27332242225856,277.3813420621932" 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="163.27332242225856" y="121.57119476268417" width="164.97545008183306" height="458.26513911620293" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.010910714285714287"/></g><g><rect data-type="rect" data-label="schematic_component_1" data-x="2" data-y="1.0699999999999998" x="381.40752864157116" y="221.4729950900164" width="95.31914893617022" height="62.324058919803605" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.010910714285714287"/></g><g><rect data-type="rect" data-label="part-number" data-x="0.15999999999999992" data-y="-2.63" x="199.93453355155478" y="583.5024549918166" width="120.98199672667755" height="16.49754500818335" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.010910714285714287"/></g><g><rect data-type="rect" data-label="U1" data-x="-0.38" data-y="2.615" x="194.43535188216038" y="99.57446808510645" width="32.99509001636659" height="22.91325695581014" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.010910714285714287"/></g><g><rect data-type="rect" data-label="netId: .LED1 &gt; port.right to .U1 &gt; .pin20
2
+ globalConnNetId: connectivity_net0" data-x="2.6200000000000006" data-y="0.775" x="476.7266775777414" y="259.0507364975451" width="18.330605564648124" height="41.243862520458265" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010910714285714287"/></g><g><circle data-type="point" data-label="U1.1
3
+ x+" data-x="0.9" data-y="1.2" cx="328.2487725040916" cy="240.72013093289695" r="3" fill="hsl(319, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.2
4
+ x+" data-x="0.9" data-y="0.3999999999999999" cx="328.2487725040916" cy="314.04255319148945" r="3" fill="hsl(320, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.3
5
+ x+" data-x="0.9" data-y="0.5999999999999999" cx="328.2487725040916" cy="295.7119476268413" r="3" fill="hsl(321, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.4
6
+ x-" data-x="-0.9" data-y="0.3999999999999999" cx="163.27332242225856" cy="314.04255319148945" r="3" fill="hsl(322, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.5
7
+ x+" data-x="0.9" data-y="0.9999999999999998" cx="328.2487725040916" cy="259.0507364975451" r="3" fill="hsl(323, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.6
8
+ x+" data-x="0.9" data-y="-0.19999999999999996" cx="328.2487725040916" cy="369.03436988543376" r="3" fill="hsl(324, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.7
9
+ x-" data-x="-0.9" data-y="-0.7999999999999998" cx="163.27332242225856" cy="424.0261865793781" r="3" fill="hsl(325, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.9
10
+ x+" data-x="0.9" data-y="0.19999999999999996" cx="328.2487725040916" cy="332.3731587561375" r="3" fill="hsl(327, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.10
11
+ x+" data-x="0.9" data-y="0" cx="328.2487725040916" cy="350.70376432078564" r="3" fill="hsl(217, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.11
12
+ x+" data-x="0.9" data-y="0.7999999999999998" cx="328.2487725040916" cy="277.3813420621932" r="3" fill="hsl(218, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.12
13
+ x+" data-x="0.9" data-y="-1.2" cx="328.2487725040916" cy="460.6873977086743" r="3" fill="hsl(219, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.13
14
+ x+" data-x="0.9" data-y="-1" cx="328.2487725040916" cy="442.3567921440262" r="3" fill="hsl(220, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.14
15
+ x+" data-x="0.9" data-y="-0.7999999999999999" cx="328.2487725040916" cy="424.0261865793781" r="3" fill="hsl(221, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.15
16
+ x-" data-x="-0.9" data-y="1" cx="163.27332242225856" cy="259.0507364975451" r="3" fill="hsl(222, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.16
17
+ x-" data-x="-0.9" data-y="1.2" cx="163.27332242225856" cy="240.72013093289695" r="3" fill="hsl(223, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.17
18
+ x-" data-x="-0.9" data-y="0.5999999999999999" cx="163.27332242225856" cy="295.7119476268413" r="3" fill="hsl(224, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.18
19
+ x-" data-x="-0.9" data-y="-0.9999999999999998" cx="163.27332242225856" cy="442.3567921440262" r="3" fill="hsl(225, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.19
20
+ x-" data-x="-0.9" data-y="-0.19999999999999996" cx="163.27332242225856" cy="369.03436988543376" r="3" fill="hsl(226, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.20
21
+ x-" data-x="-0.9" data-y="0.7999999999999999" cx="163.27332242225856" cy="277.3813420621932" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.21
22
+ x-" data-x="-0.9" data-y="-1.2" cx="163.27332242225856" cy="460.6873977086743" r="3" fill="hsl(249, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.22
23
+ x+" data-x="0.9" data-y="-0.5999999999999999" cx="328.2487725040916" cy="405.69558101472995" r="3" fill="hsl(250, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.23
24
+ x+" data-x="0.9" data-y="-0.3999999999999999" cx="328.2487725040916" cy="387.36497545008183" r="3" fill="hsl(251, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.25
25
+ x-" data-x="-0.9" data-y="-0.5999999999999999" cx="163.27332242225856" cy="405.69558101472995" r="3" fill="hsl(253, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.26
26
+ x-" data-x="-0.9" data-y="-0.3999999999999999" cx="163.27332242225856" cy="387.36497545008183" r="3" fill="hsl(254, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.27
27
+ x-" data-x="-0.9" data-y="0.19999999999999996" cx="163.27332242225856" cy="332.3731587561375" r="3" fill="hsl(255, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="U1.28
28
+ x-" data-x="-0.9" data-y="0" cx="163.27332242225856" cy="350.70376432078564" r="3" fill="hsl(256, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="LED1.1
29
+ x-" data-x="1.48" data-y="1" cx="381.40752864157116" cy="259.0507364975451" r="3" fill="hsl(57, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="LED1.2
30
+ x+" data-x="2.52" data-y="1" cx="476.7266775777414" cy="259.0507364975451" r="3" fill="hsl(58, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
31
+ orientation: y-" data-x="2.6200000000000006" data-y="1" cx="485.8919803600654" cy="259.0507364975451" 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[
32
+ document.currentScript.parentElement.addEventListener('mousemove', (e) => {
33
+ const svg = e.currentTarget;
34
+ const rect = svg.getBoundingClientRect();
35
+ const x = e.clientX - rect.left;
36
+ const y = e.clientY - rect.top;
37
+ const crosshair = svg.getElementById('crosshair');
38
+ const h = svg.getElementById('crosshair-h');
39
+ const v = svg.getElementById('crosshair-v');
40
+ const coords = svg.getElementById('coordinates');
41
+
42
+ crosshair.style.display = 'block';
43
+ h.setAttribute('x1', '0');
44
+ h.setAttribute('x2', '640');
45
+ h.setAttribute('y1', y);
46
+ h.setAttribute('y2', y);
47
+ v.setAttribute('x1', x);
48
+ v.setAttribute('x2', x);
49
+ v.setAttribute('y1', '0');
50
+ v.setAttribute('y2', '640');
51
+
52
+ // Calculate real coordinates using inverse transformation
53
+ const matrix = {"a":91.65302782324058,"c":0,"e":245.7610474631751,"b":0,"d":-91.65302782324058,"f":350.70376432078564};
54
+ // Manually invert and apply the affine transform
55
+ // Since we only use translate and scale, we can directly compute:
56
+ // x' = (x - tx) / sx
57
+ // y' = (y - ty) / sy
58
+ const sx = matrix.a;
59
+ const sy = matrix.d;
60
+ const tx = matrix.e;
61
+ const ty = matrix.f;
62
+ const realPoint = {
63
+ x: (x - tx) / sx,
64
+ y: (y - ty) / sy // Flip y back since we used negative scale
65
+ }
66
+
67
+ coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
68
+ coords.setAttribute('x', (x + 5).toString());
69
+ coords.setAttribute('y', (y - 5).toString());
70
+ });
71
+ document.currentScript.parentElement.addEventListener('mouseleave', () => {
72
+ document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
73
+ });
74
+ ]]></script></svg>
@@ -0,0 +1,133 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "schematic_component_0",
5
+ "center": {
6
+ "x": -0.95,
7
+ "y": 0.825
8
+ },
9
+ "width": 0.6000000000000001,
10
+ "height": 0.6800000000000002,
11
+ "pins": [
12
+ {
13
+ "pinId": "R1.1",
14
+ "x": -1.25,
15
+ "y": 0.825,
16
+ "_facingDirection": "x-"
17
+ },
18
+ {
19
+ "pinId": "R1.2",
20
+ "x": -0.6499999999999999,
21
+ "y": 0.825,
22
+ "_facingDirection": "x+"
23
+ }
24
+ ]
25
+ },
26
+ {
27
+ "chipId": "schematic_component_1",
28
+ "center": {
29
+ "x": 0.95,
30
+ "y": 0.825
31
+ },
32
+ "width": 0.6000000000000001,
33
+ "height": 0.6800000000000002,
34
+ "pins": [
35
+ {
36
+ "pinId": "R2.1",
37
+ "x": 0.6499999999999999,
38
+ "y": 0.825,
39
+ "_facingDirection": "x-"
40
+ },
41
+ {
42
+ "pinId": "R2.2",
43
+ "x": 1.25,
44
+ "y": 0.825,
45
+ "_facingDirection": "x+"
46
+ }
47
+ ]
48
+ },
49
+ {
50
+ "chipId": "schematic_component_2",
51
+ "center": {
52
+ "x": -0.95,
53
+ "y": -0.825
54
+ },
55
+ "width": 0.6000000000000001,
56
+ "height": 0.6800000000000002,
57
+ "pins": [
58
+ {
59
+ "pinId": "R3.1",
60
+ "x": -1.25,
61
+ "y": -0.825,
62
+ "_facingDirection": "x-"
63
+ },
64
+ {
65
+ "pinId": "R3.2",
66
+ "x": -0.6499999999999999,
67
+ "y": -0.825,
68
+ "_facingDirection": "x+"
69
+ }
70
+ ]
71
+ },
72
+ {
73
+ "chipId": "schematic_component_3",
74
+ "center": {
75
+ "x": 0.95,
76
+ "y": -0.825
77
+ },
78
+ "width": 0.9,
79
+ "height": 0.5557574704999997,
80
+ "pins": [
81
+ {
82
+ "pinId": "J1.1",
83
+ "x": 0.49999999999999994,
84
+ "y": -0.725
85
+ },
86
+ {
87
+ "pinId": "J1.2",
88
+ "x": 0.95,
89
+ "y": -1.1028787352499998
90
+ },
91
+ {
92
+ "pinId": "J1.3",
93
+ "x": 1.4,
94
+ "y": -0.725
95
+ }
96
+ ]
97
+ }
98
+ ],
99
+ "directConnections": [
100
+ {
101
+ "pinIds": [
102
+ "R2.1",
103
+ "R3.2"
104
+ ],
105
+ "netId": ".R2 > .pin1 to .R3 > .pin2"
106
+ },
107
+ {
108
+ "pinIds": [
109
+ "R2.2",
110
+ "R3.1"
111
+ ],
112
+ "netId": ".R2 > .pin2 to .R3 > .pin1"
113
+ },
114
+ {
115
+ "pinIds": [
116
+ "J1.1",
117
+ "R1.1"
118
+ ],
119
+ "netId": ".J1 > .pin1 to .R1 > .pin1"
120
+ },
121
+ {
122
+ "pinIds": [
123
+ "J1.3",
124
+ "R1.2"
125
+ ],
126
+ "netId": ".J1 > .pin3 to .R1 > .pin2"
127
+ }
128
+ ],
129
+ "netConnections": [],
130
+ "textBoxes": [],
131
+ "availableNetLabelOrientations": {},
132
+ "maxMspPairDistance": 5
133
+ }
@@ -0,0 +1,202 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "schematic_component_0",
5
+ "center": {
6
+ "x": 0,
7
+ "y": 0
8
+ },
9
+ "width": 1.8,
10
+ "height": 5,
11
+ "pins": [
12
+ {
13
+ "pinId": "U1.1",
14
+ "x": 0.9,
15
+ "y": 1.2
16
+ },
17
+ {
18
+ "pinId": "U1.2",
19
+ "x": 0.9,
20
+ "y": 0.3999999999999999
21
+ },
22
+ {
23
+ "pinId": "U1.3",
24
+ "x": 0.9,
25
+ "y": 0.5999999999999999
26
+ },
27
+ {
28
+ "pinId": "U1.4",
29
+ "x": -0.9,
30
+ "y": 0.3999999999999999
31
+ },
32
+ {
33
+ "pinId": "U1.5",
34
+ "x": 0.9,
35
+ "y": 0.9999999999999998
36
+ },
37
+ {
38
+ "pinId": "U1.6",
39
+ "x": 0.9,
40
+ "y": -0.19999999999999996
41
+ },
42
+ {
43
+ "pinId": "U1.7",
44
+ "x": -0.9,
45
+ "y": -0.7999999999999998
46
+ },
47
+ {
48
+ "pinId": "U1.9",
49
+ "x": 0.9,
50
+ "y": 0.19999999999999996
51
+ },
52
+ {
53
+ "pinId": "U1.10",
54
+ "x": 0.9,
55
+ "y": 0
56
+ },
57
+ {
58
+ "pinId": "U1.11",
59
+ "x": 0.9,
60
+ "y": 0.7999999999999998
61
+ },
62
+ {
63
+ "pinId": "U1.12",
64
+ "x": 0.9,
65
+ "y": -1.2
66
+ },
67
+ {
68
+ "pinId": "U1.13",
69
+ "x": 0.9,
70
+ "y": -1
71
+ },
72
+ {
73
+ "pinId": "U1.14",
74
+ "x": 0.9,
75
+ "y": -0.7999999999999999
76
+ },
77
+ {
78
+ "pinId": "U1.15",
79
+ "x": -0.9,
80
+ "y": 1
81
+ },
82
+ {
83
+ "pinId": "U1.16",
84
+ "x": -0.9,
85
+ "y": 1.2
86
+ },
87
+ {
88
+ "pinId": "U1.17",
89
+ "x": -0.9,
90
+ "y": 0.5999999999999999
91
+ },
92
+ {
93
+ "pinId": "U1.18",
94
+ "x": -0.9,
95
+ "y": -0.9999999999999998
96
+ },
97
+ {
98
+ "pinId": "U1.19",
99
+ "x": -0.9,
100
+ "y": -0.19999999999999996
101
+ },
102
+ {
103
+ "pinId": "U1.20",
104
+ "x": -0.9,
105
+ "y": 0.7999999999999999
106
+ },
107
+ {
108
+ "pinId": "U1.21",
109
+ "x": -0.9,
110
+ "y": -1.2
111
+ },
112
+ {
113
+ "pinId": "U1.22",
114
+ "x": 0.9,
115
+ "y": -0.5999999999999999
116
+ },
117
+ {
118
+ "pinId": "U1.23",
119
+ "x": 0.9,
120
+ "y": -0.3999999999999999
121
+ },
122
+ {
123
+ "pinId": "U1.25",
124
+ "x": -0.9,
125
+ "y": -0.5999999999999999
126
+ },
127
+ {
128
+ "pinId": "U1.26",
129
+ "x": -0.9,
130
+ "y": -0.3999999999999999
131
+ },
132
+ {
133
+ "pinId": "U1.27",
134
+ "x": -0.9,
135
+ "y": 0.19999999999999996
136
+ },
137
+ {
138
+ "pinId": "U1.28",
139
+ "x": -0.9,
140
+ "y": 0
141
+ }
142
+ ]
143
+ },
144
+ {
145
+ "chipId": "schematic_component_1",
146
+ "center": {
147
+ "x": 2,
148
+ "y": 1.0699999999999998
149
+ },
150
+ "width": 1.04,
151
+ "height": 0.6799999999999999,
152
+ "pins": [
153
+ {
154
+ "pinId": "LED1.1",
155
+ "x": 1.48,
156
+ "y": 1,
157
+ "_facingDirection": "x-"
158
+ },
159
+ {
160
+ "pinId": "LED1.2",
161
+ "x": 2.52,
162
+ "y": 1,
163
+ "_facingDirection": "x+"
164
+ }
165
+ ]
166
+ }
167
+ ],
168
+ "directConnections": [
169
+ {
170
+ "pinIds": [
171
+ "LED1.2",
172
+ "U1.20"
173
+ ],
174
+ "netId": ".LED1 > port.right to .U1 > .pin20"
175
+ }
176
+ ],
177
+ "netConnections": [],
178
+ "textBoxes": [
179
+ {
180
+ "chipId": "schematic_component_0",
181
+ "center": {
182
+ "x": 0.15999999999999992,
183
+ "y": -2.63
184
+ },
185
+ "width": 1.3199999999999998,
186
+ "height": 0.17999999999999972,
187
+ "text": "part-number"
188
+ },
189
+ {
190
+ "chipId": "schematic_component_0",
191
+ "center": {
192
+ "x": -0.38,
193
+ "y": 2.615
194
+ },
195
+ "width": 0.36000000000000004,
196
+ "height": 0.24999999999999956,
197
+ "text": "U1"
198
+ }
199
+ ],
200
+ "availableNetLabelOrientations": {},
201
+ "maxMspPairDistance": 5
202
+ }
@@ -0,0 +1,19 @@
1
+ import { expect, test } from "bun:test"
2
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
+ import "tests/fixtures/matcher"
4
+ import inputProblem from "./assets/repro47-endpoint-obstacle-detour.input.json"
5
+
6
+ test("repro47: endpoint obstacle detours fall back to net labels", () => {
7
+ const solver = new SchematicTracePipelineSolver(inputProblem as any)
8
+
9
+ solver.solve()
10
+
11
+ expect(
12
+ solver
13
+ .schematicTraceLinesSolver!.failedConnectionPairs.map(
14
+ (pair) => pair.mspPairId,
15
+ )
16
+ .sort(),
17
+ ).toEqual(["J1.1-R1.1", "J1.3-R1.2"])
18
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
19
+ })
@@ -0,0 +1,17 @@
1
+ import { expect, test } from "bun:test"
2
+ import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
+ import "tests/fixtures/matcher"
4
+ import inputProblem from "./assets/repro5-escape-padded-text-obstacles.input.json"
5
+
6
+ test("repro5: route escapes past padded text obstacles", () => {
7
+ const solver = new SchematicTracePipelineSolver(inputProblem as any)
8
+
9
+ solver.solve()
10
+
11
+ expect(
12
+ solver.schematicTraceLinesSolver!.failedConnectionPairs.map(
13
+ (pair) => pair.mspPairId,
14
+ ),
15
+ ).toEqual([])
16
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
17
+ })
@@ -0,0 +1,39 @@
1
+ import { expect, test } from "bun:test"
2
+ import { candidateMidsFromSet } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/mid"
3
+ import type { ObstacleRect } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
4
+
5
+ test("candidate mids include clearance beyond an open obstacle side", () => {
6
+ const previousObstacle: ObstacleRect = {
7
+ kind: "chip",
8
+ chipId: "U1",
9
+ minX: -0.9,
10
+ maxX: 0.9,
11
+ minY: -2.5,
12
+ maxY: 2.5,
13
+ }
14
+ const collidingObstacle: ObstacleRect = {
15
+ kind: "text_box",
16
+ textBox: {
17
+ center: { x: -0.38, y: 2.615 },
18
+ width: 0.36,
19
+ height: 0.25,
20
+ text: "U1",
21
+ },
22
+ minX: -1.01,
23
+ maxX: 0.25,
24
+ minY: 2.04,
25
+ maxY: 3.19,
26
+ }
27
+ const aabb = { minX: -0.9, maxX: 2.52, minY: 0.8, maxY: 1 }
28
+ const collisionRects = new Set([previousObstacle])
29
+
30
+ expect(
31
+ candidateMidsFromSet("y", collidingObstacle, collisionRects, aabb),
32
+ ).toEqual([1.42])
33
+
34
+ expect(
35
+ candidateMidsFromSet("y", collidingObstacle, collisionRects, aabb, {
36
+ allowOpenSideCandidates: true,
37
+ }),
38
+ ).toEqual([1.42, 3.39])
39
+ })