@tscircuit/schematic-trace-solver 0.0.98 → 0.0.99

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 (18) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +310 -220
  3. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +19 -9
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +30 -0
  5. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/generateEndpointCollisionDetours.ts +84 -0
  6. package/package.json +1 -1
  7. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +18 -18
  8. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +7 -7
  9. package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +5 -7
  10. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +6 -12
  11. package/tests/examples/__snapshots__/example09.snap.svg +9 -13
  12. package/tests/examples/__snapshots__/example21.snap.svg +30 -32
  13. package/tests/examples/__snapshots__/example29.snap.svg +13 -19
  14. package/tests/repros/__snapshots__/repro129-host-custom-symbol-passives.snap.svg +60 -0
  15. package/tests/repros/assets/repro129-host-custom-symbol-passives.input.json +94 -0
  16. package/tests/repros/repro129-host-custom-symbol-passives.test.ts +16 -0
  17. package/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-direct-connection-distance.test.ts +46 -0
  18. package/tests/solvers/SchematicTraceSingleLineSolver2/generate-endpoint-collision-detours.test.ts +38 -0
package/dist/index.js CHANGED
@@ -76,80 +76,39 @@ var BaseSolver = class {
76
76
  };
77
77
 
78
78
  // lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts
79
- import "connectivity-map";
79
+ import { distance } from "@tscircuit/math-utils";
80
80
 
81
- // lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem.ts
82
- import { ConnectivityMap } from "connectivity-map";
83
- var getConnectivityMapsFromInputProblem = (inputProblem) => {
84
- const directConnMap = new ConnectivityMap({});
85
- for (const directConn of inputProblem.directConnections) {
86
- directConnMap.addConnections([
87
- directConn.netId ? [directConn.netId, ...directConn.pinIds] : directConn.pinIds
88
- ]);
89
- }
90
- const netConnMap = new ConnectivityMap(directConnMap.netMap);
91
- for (const netConn of inputProblem.netConnections) {
92
- netConnMap.addConnections([[netConn.netId, ...netConn.pinIds]]);
93
- }
94
- return { directConnMap, netConnMap };
81
+ // lib/utils/getColorFromString.ts
82
+ var getColorFromString = (string, alpha = 1) => {
83
+ const hash = string.split("").reduce((acc, char) => {
84
+ return acc * 31 + char.charCodeAt(0);
85
+ }, 0);
86
+ return `hsl(${hash % 360}, 100%, 50%, ${alpha})`;
95
87
  };
96
88
 
97
- // lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts
98
- function getOrthogonalMinimumSpanningTree(pins, opts = {}) {
99
- const n = pins.length;
100
- const maxDistance = opts?.maxDistance ?? Number.POSITIVE_INFINITY;
101
- if (n <= 1) return [];
102
- {
103
- const seen = /* @__PURE__ */ new Set();
104
- for (const p of pins) {
105
- if (seen.has(p.pinId)) {
106
- throw new Error(`Duplicate pinId detected: "${p.pinId}"`);
107
- }
108
- seen.add(p.pinId);
109
- }
110
- }
111
- const manhattan = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
112
- const inTree = new Array(n).fill(false);
113
- const bestDist = new Array(n).fill(Number.POSITIVE_INFINITY);
114
- const parent = new Array(n).fill(-1);
115
- let startIndex = 0;
116
- for (let i = 1; i < n; i++) {
117
- if (pins[i].pinId < pins[startIndex].pinId) startIndex = i;
89
+ // lib/utils/arePinsInDifferentSchematicSections.ts
90
+ var getSectionNameForPin = (sectionByChipId, sectionByPinId, pin) => {
91
+ if (pin.chipId) {
92
+ const chipSection = sectionByChipId.get(pin.chipId);
93
+ if (chipSection) return chipSection;
118
94
  }
119
- bestDist[startIndex] = 0;
120
- const edges = [];
121
- for (let iter = 0; iter < n; iter++) {
122
- let u = -1;
123
- let best = Number.POSITIVE_INFINITY;
124
- let bestId = "";
125
- for (let i = 0; i < n; i++) {
126
- if (!inTree[i]) {
127
- const d = bestDist[i];
128
- if (d < best || d === best && (bestId === "" || pins[i].pinId < bestId)) {
129
- best = d;
130
- bestId = pins[i].pinId;
131
- u = i;
132
- }
133
- }
134
- }
135
- inTree[u] = true;
136
- if (parent[u] !== -1) {
137
- edges.push([pins[u].pinId, pins[parent[u]].pinId]);
138
- }
139
- for (let v = 0; v < n; v++) {
140
- if (!inTree[v]) {
141
- const d0 = manhattan(pins[u], pins[v]);
142
- const isForbidden = opts?.forbidEdge?.(pins[u], pins[v]) ?? false;
143
- const d = d0 > maxDistance || isForbidden ? Number.POSITIVE_INFINITY : d0;
144
- if (d < bestDist[v] || d === bestDist[v] && pins[u].pinId < pins[parent[v]]?.pinId) {
145
- bestDist[v] = d;
146
- parent[v] = u;
147
- }
148
- }
95
+ return sectionByPinId.get(pin.pinId);
96
+ };
97
+ var arePinsInDifferentSchematicSections = (inputProblem, p1, p2) => {
98
+ const sectionByChipId = /* @__PURE__ */ new Map();
99
+ const sectionByPinId = /* @__PURE__ */ new Map();
100
+ for (const chip of inputProblem.chips) {
101
+ if (!chip.sectionId) continue;
102
+ sectionByChipId.set(chip.chipId, chip.sectionId);
103
+ for (const pin of chip.pins) {
104
+ sectionByPinId.set(pin.pinId, chip.sectionId);
149
105
  }
150
106
  }
151
- return edges;
152
- }
107
+ if (sectionByChipId.size === 0) return false;
108
+ const s1 = getSectionNameForPin(sectionByChipId, sectionByPinId, p1);
109
+ const s2 = getSectionNameForPin(sectionByChipId, sectionByPinId, p2);
110
+ return s1 !== s2;
111
+ };
153
112
 
154
113
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection.ts
155
114
  var getPinDirection = (pin, chip) => {
@@ -181,6 +140,101 @@ var getPinDirection = (pin, chip) => {
181
140
  return "x-";
182
141
  };
183
142
 
143
+ // lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts
144
+ var visualizeInputProblem = (inputProblem, opts = {}) => {
145
+ const {
146
+ connectionAlpha = 0.8,
147
+ chipAlpha = 0.8,
148
+ hideRatsNet = inputProblem._hideRatsNet ?? false
149
+ } = opts;
150
+ const graphics = {
151
+ lines: [],
152
+ points: [],
153
+ rects: []
154
+ };
155
+ const pinIdMap = /* @__PURE__ */ new Map();
156
+ for (const chip of inputProblem.chips) {
157
+ for (const pin of chip.pins) {
158
+ pinIdMap.set(pin.pinId, pin);
159
+ }
160
+ }
161
+ for (const chip of inputProblem.chips) {
162
+ graphics.rects.push({
163
+ label: chip.chipId,
164
+ center: chip.center,
165
+ width: chip.width,
166
+ height: chip.height,
167
+ fill: getColorFromString(chip.chipId, chipAlpha)
168
+ });
169
+ for (const pin of chip.pins) {
170
+ graphics.points.push({
171
+ label: `${pin.pinId}
172
+ ${pin._facingDirection ?? getPinDirection(pin, chip)}`,
173
+ x: pin.x,
174
+ y: pin.y,
175
+ color: getColorFromString(pin.pinId, 0.8)
176
+ });
177
+ }
178
+ }
179
+ for (const textBox of inputProblem.textBoxes ?? []) {
180
+ graphics.rects.push({
181
+ label: textBox.text ?? "schematic_text",
182
+ center: textBox.center,
183
+ width: textBox.width,
184
+ height: textBox.height,
185
+ fill: "rgba(160, 0, 220, 0.14)",
186
+ strokeColor: "rgba(160, 0, 220, 0.9)"
187
+ });
188
+ }
189
+ if (!hideRatsNet) {
190
+ for (const directConn of inputProblem.directConnections) {
191
+ const [pinId1, pinId2] = directConn.pinIds;
192
+ const pin1 = pinIdMap.get(pinId1);
193
+ const pin2 = pinIdMap.get(pinId2);
194
+ if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
195
+ continue;
196
+ }
197
+ graphics.lines.push({
198
+ points: [
199
+ {
200
+ x: pin1.x,
201
+ y: pin1.y
202
+ },
203
+ {
204
+ x: pin2.x,
205
+ y: pin2.y
206
+ }
207
+ ],
208
+ strokeColor: getColorFromString(
209
+ directConn.netId ?? `${pinId1}-${pinId2}`,
210
+ connectionAlpha
211
+ )
212
+ });
213
+ }
214
+ for (const netConn of inputProblem.netConnections) {
215
+ const pins = netConn.pinIds.map((pinId) => pinIdMap.get(pinId));
216
+ for (let i = 0; i < pins.length - 1; i++) {
217
+ for (let j = i + 1; j < pins.length; j++) {
218
+ const pin1 = pins[i];
219
+ const pin2 = pins[j];
220
+ if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
221
+ continue;
222
+ }
223
+ graphics.lines.push({
224
+ points: [
225
+ { x: pin1.x, y: pin1.y },
226
+ { x: pin2.x, y: pin2.y }
227
+ ],
228
+ strokeColor: getColorFromString(netConn.netId, connectionAlpha),
229
+ strokeDash: "4 2"
230
+ });
231
+ }
232
+ }
233
+ }
234
+ }
235
+ return graphics;
236
+ };
237
+
184
238
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getRestrictedCenterLines.ts
185
239
  var getRestrictedCenterLines = (params) => {
186
240
  const { pins, inputProblem, pinIdMap, chipMap } = params;
@@ -293,134 +347,81 @@ var doesPairCrossRestrictedCenterLines = (params) => {
293
347
  return hvCrosses && vhCrosses;
294
348
  };
295
349
 
296
- // lib/utils/getColorFromString.ts
297
- var getColorFromString = (string, alpha = 1) => {
298
- const hash = string.split("").reduce((acc, char) => {
299
- return acc * 31 + char.charCodeAt(0);
300
- }, 0);
301
- return `hsl(${hash % 360}, 100%, 50%, ${alpha})`;
302
- };
303
-
304
- // lib/utils/arePinsInDifferentSchematicSections.ts
305
- var getSectionNameForPin = (sectionByChipId, sectionByPinId, pin) => {
306
- if (pin.chipId) {
307
- const chipSection = sectionByChipId.get(pin.chipId);
308
- if (chipSection) return chipSection;
350
+ // lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem.ts
351
+ import { ConnectivityMap } from "connectivity-map";
352
+ var getConnectivityMapsFromInputProblem = (inputProblem) => {
353
+ const directConnMap = new ConnectivityMap({});
354
+ for (const directConn of inputProblem.directConnections) {
355
+ directConnMap.addConnections([
356
+ directConn.netId ? [directConn.netId, ...directConn.pinIds] : directConn.pinIds
357
+ ]);
309
358
  }
310
- return sectionByPinId.get(pin.pinId);
311
- };
312
- var arePinsInDifferentSchematicSections = (inputProblem, p1, p2) => {
313
- const sectionByChipId = /* @__PURE__ */ new Map();
314
- const sectionByPinId = /* @__PURE__ */ new Map();
315
- for (const chip of inputProblem.chips) {
316
- if (!chip.sectionId) continue;
317
- sectionByChipId.set(chip.chipId, chip.sectionId);
318
- for (const pin of chip.pins) {
319
- sectionByPinId.set(pin.pinId, chip.sectionId);
320
- }
359
+ const netConnMap = new ConnectivityMap(directConnMap.netMap);
360
+ for (const netConn of inputProblem.netConnections) {
361
+ netConnMap.addConnections([[netConn.netId, ...netConn.pinIds]]);
321
362
  }
322
- if (sectionByChipId.size === 0) return false;
323
- const s1 = getSectionNameForPin(sectionByChipId, sectionByPinId, p1);
324
- const s2 = getSectionNameForPin(sectionByChipId, sectionByPinId, p2);
325
- return s1 !== s2;
363
+ return { directConnMap, netConnMap };
326
364
  };
327
365
 
328
- // lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts
329
- var visualizeInputProblem = (inputProblem, opts = {}) => {
330
- const {
331
- connectionAlpha = 0.8,
332
- chipAlpha = 0.8,
333
- hideRatsNet = inputProblem._hideRatsNet ?? false
334
- } = opts;
335
- const graphics = {
336
- lines: [],
337
- points: [],
338
- rects: []
339
- };
340
- const pinIdMap = /* @__PURE__ */ new Map();
341
- for (const chip of inputProblem.chips) {
342
- for (const pin of chip.pins) {
343
- pinIdMap.set(pin.pinId, pin);
344
- }
345
- }
346
- for (const chip of inputProblem.chips) {
347
- graphics.rects.push({
348
- label: chip.chipId,
349
- center: chip.center,
350
- width: chip.width,
351
- height: chip.height,
352
- fill: getColorFromString(chip.chipId, chipAlpha)
353
- });
354
- for (const pin of chip.pins) {
355
- graphics.points.push({
356
- label: `${pin.pinId}
357
- ${pin._facingDirection ?? getPinDirection(pin, chip)}`,
358
- x: pin.x,
359
- y: pin.y,
360
- color: getColorFromString(pin.pinId, 0.8)
361
- });
366
+ // lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts
367
+ function getOrthogonalMinimumSpanningTree(pins, opts = {}) {
368
+ const n = pins.length;
369
+ const maxDistance = opts?.maxDistance ?? Number.POSITIVE_INFINITY;
370
+ if (n <= 1) return [];
371
+ {
372
+ const seen = /* @__PURE__ */ new Set();
373
+ for (const p of pins) {
374
+ if (seen.has(p.pinId)) {
375
+ throw new Error(`Duplicate pinId detected: "${p.pinId}"`);
376
+ }
377
+ seen.add(p.pinId);
362
378
  }
363
379
  }
364
- for (const textBox of inputProblem.textBoxes ?? []) {
365
- graphics.rects.push({
366
- label: textBox.text ?? "schematic_text",
367
- center: textBox.center,
368
- width: textBox.width,
369
- height: textBox.height,
370
- fill: "rgba(160, 0, 220, 0.14)",
371
- strokeColor: "rgba(160, 0, 220, 0.9)"
372
- });
380
+ const manhattan = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
381
+ const inTree = new Array(n).fill(false);
382
+ const bestDist = new Array(n).fill(Number.POSITIVE_INFINITY);
383
+ const parent = new Array(n).fill(-1);
384
+ let startIndex = 0;
385
+ for (let i = 1; i < n; i++) {
386
+ if (pins[i].pinId < pins[startIndex].pinId) startIndex = i;
373
387
  }
374
- if (!hideRatsNet) {
375
- for (const directConn of inputProblem.directConnections) {
376
- const [pinId1, pinId2] = directConn.pinIds;
377
- const pin1 = pinIdMap.get(pinId1);
378
- const pin2 = pinIdMap.get(pinId2);
379
- if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
380
- continue;
388
+ bestDist[startIndex] = 0;
389
+ const edges = [];
390
+ for (let iter = 0; iter < n; iter++) {
391
+ let u = -1;
392
+ let best = Number.POSITIVE_INFINITY;
393
+ let bestId = "";
394
+ for (let i = 0; i < n; i++) {
395
+ if (!inTree[i]) {
396
+ const d = bestDist[i];
397
+ if (d < best || d === best && (bestId === "" || pins[i].pinId < bestId)) {
398
+ best = d;
399
+ bestId = pins[i].pinId;
400
+ u = i;
401
+ }
381
402
  }
382
- graphics.lines.push({
383
- points: [
384
- {
385
- x: pin1.x,
386
- y: pin1.y
387
- },
388
- {
389
- x: pin2.x,
390
- y: pin2.y
391
- }
392
- ],
393
- strokeColor: getColorFromString(
394
- directConn.netId ?? `${pinId1}-${pinId2}`,
395
- connectionAlpha
396
- )
397
- });
398
403
  }
399
- for (const netConn of inputProblem.netConnections) {
400
- const pins = netConn.pinIds.map((pinId) => pinIdMap.get(pinId));
401
- for (let i = 0; i < pins.length - 1; i++) {
402
- for (let j = i + 1; j < pins.length; j++) {
403
- const pin1 = pins[i];
404
- const pin2 = pins[j];
405
- if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
406
- continue;
407
- }
408
- graphics.lines.push({
409
- points: [
410
- { x: pin1.x, y: pin1.y },
411
- { x: pin2.x, y: pin2.y }
412
- ],
413
- strokeColor: getColorFromString(netConn.netId, connectionAlpha),
414
- strokeDash: "4 2"
415
- });
404
+ inTree[u] = true;
405
+ if (parent[u] !== -1) {
406
+ edges.push([pins[u].pinId, pins[parent[u]].pinId]);
407
+ }
408
+ for (let v = 0; v < n; v++) {
409
+ if (!inTree[v]) {
410
+ const d0 = manhattan(pins[u], pins[v]);
411
+ const isForbidden = opts?.forbidEdge?.(pins[u], pins[v]) ?? false;
412
+ const d = d0 > maxDistance || isForbidden ? Number.POSITIVE_INFINITY : d0;
413
+ if (d < bestDist[v] || d === bestDist[v] && pins[u].pinId < pins[parent[v]]?.pinId) {
414
+ bestDist[v] = d;
415
+ parent[v] = u;
416
416
  }
417
417
  }
418
418
  }
419
419
  }
420
- return graphics;
421
- };
420
+ return edges;
421
+ }
422
422
 
423
423
  // lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts
424
+ var getPinPairKey = (pinIds) => [...pinIds].sort().join("::");
424
425
  var MspConnectionPairSolver = class extends BaseSolver {
425
426
  inputProblem;
426
427
  mspConnectionPairs = [];
@@ -431,6 +432,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
431
432
  maxMspPairDistance;
432
433
  pinMap;
433
434
  userNetIdByPinId;
435
+ directConnectionPinPairKeys;
434
436
  constructor({ inputProblem }) {
435
437
  super();
436
438
  this.inputProblem = inputProblem;
@@ -449,7 +451,9 @@ var MspConnectionPairSolver = class extends BaseSolver {
449
451
  this.chipMap[chip.chipId] = chip;
450
452
  }
451
453
  this.userNetIdByPinId = {};
454
+ this.directConnectionPinPairKeys = /* @__PURE__ */ new Set();
452
455
  for (const dc of inputProblem.directConnections) {
456
+ this.directConnectionPinPairKeys.add(getPinPairKey(dc.pinIds));
453
457
  if (dc.netId) {
454
458
  const [a, b] = dc.pinIds;
455
459
  this.userNetIdByPinId[a] = dc.netId;
@@ -483,8 +487,9 @@ var MspConnectionPairSolver = class extends BaseSolver {
483
487
  const [pin1, pin2] = directlyConnectedPins;
484
488
  const p1 = this.pinMap[pin1];
485
489
  const p2 = this.pinMap[pin2];
486
- const manhattanDist = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
487
- if (manhattanDist > this.maxMspPairDistance) {
490
+ const pinPairKey = getPinPairKey([pin1, pin2]);
491
+ const pairDistance = this.directConnectionPinPairKeys.has(pinPairKey) ? distance(p1, p2) : Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
492
+ if (pairDistance > this.maxMspPairDistance) {
488
493
  return;
489
494
  }
490
495
  if (arePinsInDifferentSchematicSections(this.inputProblem, p1, p2)) {
@@ -860,6 +865,68 @@ var candidateMidsFromSet = (axis, colliding, collisionRects, aabb, eps = EPS2) =
860
865
  }
861
866
  };
862
867
 
868
+ // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/generateEndpointCollisionDetours.ts
869
+ var getSegmentAxis = (start, end) => {
870
+ if (isVertical(start, end)) return "x";
871
+ if (isHorizontal(start, end)) return "y";
872
+ return null;
873
+ };
874
+ var hasOnlyNonzeroOrthogonalSegments = (path) => path.every((point, index) => {
875
+ const nextPoint = path[index + 1];
876
+ if (!nextPoint) return true;
877
+ if (!isHorizontal(point, nextPoint) && !isVertical(point, nextPoint)) {
878
+ return false;
879
+ }
880
+ return Math.abs(point.x - nextPoint.x) + Math.abs(point.y - nextPoint.y) > 0;
881
+ });
882
+ var generateEndpointCollisionDetours = ({
883
+ path,
884
+ collidingSegmentIndex,
885
+ obstacle
886
+ }) => {
887
+ if (path.length !== 3) return [];
888
+ const lastSegmentIndex = path.length - 2;
889
+ if (collidingSegmentIndex !== 0 && collidingSegmentIndex !== lastSegmentIndex) {
890
+ return [];
891
+ }
892
+ const shouldReverse = collidingSegmentIndex === lastSegmentIndex;
893
+ const orderedPath = shouldReverse ? [...path].reverse() : path;
894
+ const [start, corner, end] = orderedPath;
895
+ const firstSegmentAxis = getSegmentAxis(start, corner);
896
+ const secondSegmentAxis = getSegmentAxis(corner, end);
897
+ if (!firstSegmentAxis || !secondSegmentAxis) return [];
898
+ if (firstSegmentAxis === secondSegmentAxis) return [];
899
+ const escapeCoordinates = [
900
+ ...midBetweenPointAndRect(secondSegmentAxis, start, obstacle),
901
+ ...midBetweenPointAndRect(secondSegmentAxis, end, obstacle)
902
+ ];
903
+ const detourCoordinates = [
904
+ ...midBetweenPointAndRect(firstSegmentAxis, start, obstacle),
905
+ ...midBetweenPointAndRect(firstSegmentAxis, end, obstacle)
906
+ ];
907
+ const detours = [];
908
+ for (const escapeCoordinate of [...new Set(escapeCoordinates)]) {
909
+ for (const detourCoordinate of [...new Set(detourCoordinates)]) {
910
+ const orderedDetour = firstSegmentAxis === "y" ? [
911
+ start,
912
+ { x: escapeCoordinate, y: start.y },
913
+ { x: escapeCoordinate, y: detourCoordinate },
914
+ { x: end.x, y: detourCoordinate },
915
+ end
916
+ ] : [
917
+ start,
918
+ { x: start.x, y: escapeCoordinate },
919
+ { x: detourCoordinate, y: escapeCoordinate },
920
+ { x: detourCoordinate, y: end.y },
921
+ end
922
+ ];
923
+ const detour = shouldReverse ? orderedDetour.reverse() : orderedDetour;
924
+ if (hasOnlyNonzeroOrthogonalSegments(detour)) detours.push(detour);
925
+ }
926
+ }
927
+ return detours;
928
+ };
929
+
863
930
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/pathOps.ts
864
931
  var EPS3 = 1e-9;
865
932
  var shiftSegmentOrth = (pts, segIndex, axis, newCoord, eps = EPS3) => {
@@ -1210,6 +1277,29 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1210
1277
  let { segIndex, rect } = collision;
1211
1278
  const isFirstSegment = segIndex === 0;
1212
1279
  const isLastSegment = segIndex === path.length - 2;
1280
+ if (path.length === 3 && (isFirstSegment || isLastSegment)) {
1281
+ const detours = generateEndpointCollisionDetours({
1282
+ path,
1283
+ collidingSegmentIndex: segIndex,
1284
+ obstacle: rect
1285
+ }).filter((detour) => {
1286
+ const key = pathKey(detour);
1287
+ if (this.visited.has(key)) return false;
1288
+ this.visited.add(key);
1289
+ return true;
1290
+ }).sort(
1291
+ (a2, b2) => this.pathLength(a2) - this.pathLength(b2) || this.getPinBandPenalty(a2) - this.getPinBandPenalty(b2)
1292
+ );
1293
+ for (const detour of detours) {
1294
+ const nextCollisionRects = new Set(collisionRects);
1295
+ nextCollisionRects.add(rect);
1296
+ this.queue.push({
1297
+ path: detour,
1298
+ collisionRects: nextCollisionRects
1299
+ });
1300
+ }
1301
+ return;
1302
+ }
1213
1303
  if (isFirstSegment) {
1214
1304
  if (path.length < 3) {
1215
1305
  return;
@@ -2961,7 +3051,7 @@ var mergeLabelGroup = (group, groupKey) => {
2961
3051
  };
2962
3052
 
2963
3053
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/filterLabelsAtTraceEdges.ts
2964
- import { distance } from "@tscircuit/math-utils";
3054
+ import { distance as distance2 } from "@tscircuit/math-utils";
2965
3055
  var filterLabelsAtTraceEdges = ({
2966
3056
  labels,
2967
3057
  traces,
@@ -2994,8 +3084,8 @@ var filterLabelsAtTraceEdges = ({
2994
3084
  if (trace.tracePath.length === 0) continue;
2995
3085
  const startPoint = trace.tracePath[0];
2996
3086
  const endPoint = trace.tracePath[trace.tracePath.length - 1];
2997
- const startDist = distance(label.center, startPoint);
2998
- const endDist = distance(label.center, endPoint);
3087
+ const startDist = distance2(label.center, startPoint);
3088
+ const endDist = distance2(label.center, endPoint);
2999
3089
  if (startDist <= distanceThreshold || endDist <= distanceThreshold) {
3000
3090
  isNearTraceEdge = true;
3001
3091
  break;
@@ -4077,7 +4167,7 @@ function doesTraceOverlapWithExistingTraces(newTracePath, existingTraces) {
4077
4167
 
4078
4168
  // lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts
4079
4169
  var NEAREST_NEIGHBOR_COUNT = 3;
4080
- var distance2 = (p1, p2) => {
4170
+ var distance3 = (p1, p2) => {
4081
4171
  return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
4082
4172
  };
4083
4173
  var LongDistancePairSolver = class extends BaseSolver {
@@ -4118,7 +4208,7 @@ var LongDistancePairSolver = class extends BaseSolver {
4118
4208
  return [
4119
4209
  {
4120
4210
  pin: targetPin,
4121
- distance: distance2(sourcePin, targetPin)
4211
+ distance: distance3(sourcePin, targetPin)
4122
4212
  }
4123
4213
  ];
4124
4214
  }).sort((a, b) => a.distance - b.distance).slice(0, NEAREST_NEIGHBOR_COUNT);
@@ -4932,10 +5022,10 @@ var projectPointToPath = (point, path) => {
4932
5022
  let bestDistance = Number.POSITIVE_INFINITY;
4933
5023
  for (let i = 0; i < path.length - 1; i++) {
4934
5024
  const projectedPoint = projectPointToSegment(point, path[i], path[i + 1]);
4935
- const distance3 = getDistance2(point, projectedPoint);
4936
- if (distance3 < bestDistance) {
5025
+ const distance4 = getDistance2(point, projectedPoint);
5026
+ if (distance4 < bestDistance) {
4937
5027
  bestPoint = projectedPoint;
4938
- bestDistance = distance3;
5028
+ bestDistance = distance4;
4939
5029
  }
4940
5030
  }
4941
5031
  return bestPoint;
@@ -6565,11 +6655,11 @@ var getLabelHugDistance = (tracePath, obstacleLabel) => {
6565
6655
  obstacleLabel.width,
6566
6656
  obstacleLabel.height
6567
6657
  );
6568
- let distance3 = 0;
6658
+ let distance4 = 0;
6569
6659
  for (const point of tracePath) {
6570
- distance3 += getPointDistanceFromRect(point, bounds);
6660
+ distance4 += getPointDistanceFromRect(point, bounds);
6571
6661
  }
6572
- return distance3;
6662
+ return distance4;
6573
6663
  };
6574
6664
  var getPointDistanceFromRect = (point, rect) => {
6575
6665
  const dx = Math.max(rect.minX - point.x, 0, point.x - rect.maxX);
@@ -6846,16 +6936,16 @@ var Example28Solver = class extends BaseSolver {
6846
6936
  const outward = dir(label.orientation);
6847
6937
  if (outward.x === 0 && outward.y === 0) return null;
6848
6938
  for (let step = 1; step <= LABEL_MAX_OUTWARD_STEPS; step++) {
6849
- const distance3 = step * LABEL_OUTWARD_STEP;
6939
+ const distance4 = step * LABEL_OUTWARD_STEP;
6850
6940
  const candidate = {
6851
6941
  ...label,
6852
6942
  anchorPoint: {
6853
- x: label.anchorPoint.x + outward.x * distance3,
6854
- y: label.anchorPoint.y + outward.y * distance3
6943
+ x: label.anchorPoint.x + outward.x * distance4,
6944
+ y: label.anchorPoint.y + outward.y * distance4
6855
6945
  },
6856
6946
  center: {
6857
- x: label.center.x + outward.x * distance3,
6858
- y: label.center.y + outward.y * distance3
6947
+ x: label.center.x + outward.x * distance4,
6948
+ y: label.center.y + outward.y * distance4
6859
6949
  }
6860
6950
  };
6861
6951
  const candidateWithClearance = {
@@ -7393,10 +7483,10 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7393
7483
  outwardDistance,
7394
7484
  phase = "shift"
7395
7485
  } = params;
7396
- for (let distance3 = LABEL_SEARCH_STEP; distance3 <= maxSearchDistance + EPS9; distance3 += LABEL_SEARCH_STEP) {
7486
+ for (let distance4 = LABEL_SEARCH_STEP; distance4 <= maxSearchDistance + EPS9; distance4 += LABEL_SEARCH_STEP) {
7397
7487
  const anchorPoint = {
7398
- x: baseAnchor.x + direction.x * distance3,
7399
- y: baseAnchor.y + direction.y * distance3
7488
+ x: baseAnchor.x + direction.x * distance4,
7489
+ y: baseAnchor.y + direction.y * distance4
7400
7490
  };
7401
7491
  const candidate = this.createCandidate(label, anchorPoint, orientation);
7402
7492
  const result = this.evaluateCandidate(
@@ -7404,7 +7494,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7404
7494
  label,
7405
7495
  labelIndex,
7406
7496
  phase,
7407
- distance3,
7497
+ distance4,
7408
7498
  outwardDistance
7409
7499
  );
7410
7500
  this.currentCandidateResults.push(result);
@@ -7416,11 +7506,11 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7416
7506
  }
7417
7507
  return null;
7418
7508
  }
7419
- evaluateCandidate(candidate, label, labelIndex, phase, distance3, outwardDistance) {
7509
+ evaluateCandidate(candidate, label, labelIndex, phase, distance4, outwardDistance) {
7420
7510
  return {
7421
7511
  ...candidate,
7422
7512
  phase,
7423
- distance: distance3,
7513
+ distance: distance4,
7424
7514
  outwardDistance,
7425
7515
  selected: false,
7426
7516
  status: this.getCandidateStatus({
@@ -7758,10 +7848,10 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7758
7848
  if (point.x < bounds.minX - EPS9 || point.x > bounds.maxX + EPS9 || point.y < bounds.minY - EPS9 || point.y > bounds.maxY + EPS9) {
7759
7849
  continue;
7760
7850
  }
7761
- for (const [side, distance3] of getSideDistances(point, bounds)) {
7762
- if (distance3 < nearestDistance) {
7851
+ for (const [side, distance4] of getSideDistances(point, bounds)) {
7852
+ if (distance4 < nearestDistance) {
7763
7853
  nearestSide = side;
7764
- nearestDistance = distance3;
7854
+ nearestDistance = distance4;
7765
7855
  }
7766
7856
  }
7767
7857
  }
@@ -7790,10 +7880,10 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7790
7880
  let nearestSide = null;
7791
7881
  let nearestDistance = Number.POSITIVE_INFINITY;
7792
7882
  for (const chip of this.chipObstacleSpatialIndex.chips) {
7793
- for (const [side, distance3] of getSideDistances(point, chip.bounds)) {
7794
- if (distance3 < nearestDistance) {
7883
+ for (const [side, distance4] of getSideDistances(point, chip.bounds)) {
7884
+ if (distance4 < nearestDistance) {
7795
7885
  nearestSide = side;
7796
- nearestDistance = distance3;
7886
+ nearestDistance = distance4;
7797
7887
  }
7798
7888
  }
7799
7889
  }
@@ -8194,17 +8284,17 @@ var getTraceLength = (trace) => {
8194
8284
  }
8195
8285
  return length;
8196
8286
  };
8197
- var getPointAtTraceDistance = (trace, distance3) => {
8287
+ var getPointAtTraceDistance = (trace, distance4) => {
8198
8288
  let pathDistance = 0;
8199
8289
  for (let i = 0; i < trace.tracePath.length - 1; i++) {
8200
8290
  const start = trace.tracePath[i];
8201
8291
  const end = trace.tracePath[i + 1];
8202
8292
  const segmentLength = getManhattanDistance(start, end);
8203
8293
  const nextDistance = pathDistance + segmentLength;
8204
- if (distance3 <= nextDistance + EPS10) {
8294
+ if (distance4 <= nextDistance + EPS10) {
8205
8295
  const offset = Math.max(
8206
8296
  0,
8207
- Math.min(segmentLength, distance3 - pathDistance)
8297
+ Math.min(segmentLength, distance4 - pathDistance)
8208
8298
  );
8209
8299
  const direction = getSegmentDirection(start, end);
8210
8300
  return {
@@ -8329,13 +8419,13 @@ var getCandidateDistances = (traceLength, vertexDistances) => {
8329
8419
  const distances = /* @__PURE__ */ new Set();
8330
8420
  const maxSteps = Math.ceil(traceLength / CANDIDATE_STEP);
8331
8421
  for (let i = 0; i <= maxSteps; i++) {
8332
- const distance3 = Math.min(traceLength, i * CANDIDATE_STEP);
8333
- distances.add(roundDistance(distance3));
8422
+ const distance4 = Math.min(traceLength, i * CANDIDATE_STEP);
8423
+ distances.add(roundDistance(distance4));
8334
8424
  }
8335
- for (const distance3 of vertexDistances) {
8336
- distances.add(roundDistance(distance3));
8425
+ for (const distance4 of vertexDistances) {
8426
+ distances.add(roundDistance(distance4));
8337
8427
  }
8338
- return [...distances].filter((distance3) => distance3 >= -EPS10 && distance3 <= traceLength + EPS10).sort((a, b) => a - b);
8428
+ return [...distances].filter((distance4) => distance4 >= -EPS10 && distance4 <= traceLength + EPS10).sort((a, b) => a - b);
8339
8429
  };
8340
8430
  var getOrientationsForPoint = (params) => {
8341
8431
  const { inputProblem, label, point, orientationConstraint } = params;
@@ -8513,7 +8603,7 @@ var getNetLabelHeight = (inputProblem, label) => {
8513
8603
  (nc) => nc.pinIds.some((pid) => label.pinIds.includes(pid))
8514
8604
  )?.netLabelHeight;
8515
8605
  };
8516
- var roundDistance = (distance3) => Number(distance3.toFixed(6));
8606
+ var roundDistance = (distance4) => Number(distance4.toFixed(6));
8517
8607
  var isSamePlacement = (label, point, orientation) => Math.abs(point.x - label.anchorPoint.x) <= EPS10 && Math.abs(point.y - label.anchorPoint.y) <= EPS10 && orientation === label.orientation;
8518
8608
 
8519
8609
  // lib/solvers/TraceAnchoredNetLabelOverlapSolver/visualize.ts