@tscircuit/schematic-trace-solver 0.0.32 → 0.0.34

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 (42) hide show
  1. package/dist/index.d.ts +24 -53
  2. package/dist/index.js +526 -847
  3. package/lib/index.ts +1 -0
  4. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +54 -3
  5. package/lib/solvers/MspConnectionPairSolver/doesPairCrossRestrictedCenterLines.ts +62 -0
  6. package/lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts +7 -2
  7. package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +25 -4
  8. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts +3 -10
  9. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +239 -0
  10. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts +57 -0
  11. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/mid.ts +97 -0
  12. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/pathOps.ts +65 -0
  13. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts +19 -0
  14. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +14 -14
  15. package/package.json +1 -1
  16. package/site/examples/example09.page.tsx +1 -1
  17. package/site/examples/example15-rp2040-caps.page.tsx +623 -0
  18. package/site/examples/example16-core-repro51.page.tsx +107 -0
  19. package/site/examples/example17-straight-line-trace.page.tsx +165 -0
  20. package/site/examples/example18.page.tsx +181 -0
  21. package/site/examples/example19.page.tsx +169 -0
  22. package/tests/examples/__snapshots__/example01.snap.svg +29 -29
  23. package/tests/examples/__snapshots__/example02.snap.svg +13 -10
  24. package/tests/examples/__snapshots__/example04.snap.svg +12 -12
  25. package/tests/examples/__snapshots__/example05.snap.svg +38 -38
  26. package/tests/examples/__snapshots__/example06.snap.svg +14 -14
  27. package/tests/examples/__snapshots__/example08.snap.svg +29 -23
  28. package/tests/examples/__snapshots__/example09.snap.svg +119 -149
  29. package/tests/examples/__snapshots__/example11.snap.svg +39 -33
  30. package/tests/examples/__snapshots__/example12.snap.svg +32 -29
  31. package/tests/examples/__snapshots__/example13.snap.svg +87 -84
  32. package/tests/examples/__snapshots__/example15.snap.svg +800 -71
  33. package/tests/examples/__snapshots__/example16.snap.svg +40 -86
  34. package/tests/examples/__snapshots__/example17.snap.svg +190 -0
  35. package/tests/examples/__snapshots__/example18.snap.svg +235 -0
  36. package/tests/examples/__snapshots__/example19.snap.svg +195 -0
  37. package/tests/examples/example15.test.tsx +524 -82
  38. package/tests/examples/example16.test.tsx +56 -118
  39. package/tests/examples/example17.test.tsx +171 -0
  40. package/tests/examples/example18.test.tsx +187 -0
  41. package/tests/examples/example19.test.tsx +175 -0
  42. package/tests/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver_repro03.test.ts +1 -0
package/dist/index.js CHANGED
@@ -139,7 +139,8 @@ function getOrthogonalMinimumSpanningTree(pins, opts = {}) {
139
139
  for (let v = 0; v < n; v++) {
140
140
  if (!inTree[v]) {
141
141
  const d0 = manhattan(pins[u], pins[v]);
142
- const d = d0 > maxDistance ? Number.POSITIVE_INFINITY : d0;
142
+ const isForbidden = opts?.forbidEdge?.(pins[u], pins[v]) ?? false;
143
+ const d = d0 > maxDistance || isForbidden ? Number.POSITIVE_INFINITY : d0;
143
144
  if (d < bestDist[v] || d === bestDist[v] && pins[u].pinId < pins[parent[v]]?.pinId) {
144
145
  bestDist[v] = d;
145
146
  parent[v] = u;
@@ -150,14 +151,6 @@ function getOrthogonalMinimumSpanningTree(pins, opts = {}) {
150
151
  return edges;
151
152
  }
152
153
 
153
- // lib/utils/getColorFromString.ts
154
- var getColorFromString = (string, alpha = 1) => {
155
- const hash = string.split("").reduce((acc, char) => {
156
- return acc * 31 + char.charCodeAt(0);
157
- }, 0);
158
- return `hsl(${hash % 360}, 100%, 50%, ${alpha})`;
159
- };
160
-
161
154
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection.ts
162
155
  var getPinDirection = (pin, chip) => {
163
156
  const { x, y } = pin;
@@ -188,6 +181,126 @@ var getPinDirection = (pin, chip) => {
188
181
  return "x-";
189
182
  };
190
183
 
184
+ // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getRestrictedCenterLines.ts
185
+ var getRestrictedCenterLines = (params) => {
186
+ const { pins, inputProblem, pinIdMap, chipMap } = params;
187
+ const findAllDirectlyConnectedPins = (startPinId) => {
188
+ const visited = /* @__PURE__ */ new Set();
189
+ const queue = [startPinId];
190
+ visited.add(startPinId);
191
+ const directConns = inputProblem.directConnections || [];
192
+ while (queue.length) {
193
+ const cur = queue.shift();
194
+ for (const dc of directConns) {
195
+ if (dc.pinIds.includes(cur)) {
196
+ for (const p of dc.pinIds) {
197
+ if (!visited.has(p)) {
198
+ visited.add(p);
199
+ queue.push(p);
200
+ }
201
+ }
202
+ }
203
+ }
204
+ }
205
+ return visited;
206
+ };
207
+ const p0 = pins[0].pinId;
208
+ const p1 = pins[1].pinId;
209
+ const relatedPinIds = /* @__PURE__ */ new Set([
210
+ ...findAllDirectlyConnectedPins(p0),
211
+ ...findAllDirectlyConnectedPins(p1)
212
+ ]);
213
+ const restrictedCenterLines = /* @__PURE__ */ new Map();
214
+ const chipFacingMap = /* @__PURE__ */ new Map();
215
+ const chipsOfFacingPins = new Set(pins.map((p) => p.chipId));
216
+ for (const pinId of relatedPinIds) {
217
+ const pin = pinIdMap.get(pinId);
218
+ if (!pin) continue;
219
+ const chip = chipMap[pin.chipId];
220
+ if (!chip) continue;
221
+ const facing = pin._facingDirection ?? getPinDirection(pin, chip);
222
+ let entry = chipFacingMap.get(chip.chipId);
223
+ if (!entry) {
224
+ entry = { center: chip.center };
225
+ const counts = { xPos: 0, xNeg: 0, yPos: 0, yNeg: 0 };
226
+ for (const cp of chip.pins) {
227
+ const cpFacing = cp._facingDirection ?? getPinDirection(cp, chip);
228
+ if (cpFacing === "x+") counts.xPos++;
229
+ if (cpFacing === "x-") counts.xNeg++;
230
+ if (cpFacing === "y+") counts.yPos++;
231
+ if (cpFacing === "y-") counts.yNeg++;
232
+ }
233
+ entry.counts = counts;
234
+ chipFacingMap.set(chip.chipId, entry);
235
+ }
236
+ if (facing === "x+") entry.hasXPos = true;
237
+ if (facing === "x-") entry.hasXNeg = true;
238
+ if (facing === "y+") entry.hasYPos = true;
239
+ if (facing === "y-") entry.hasYNeg = true;
240
+ }
241
+ for (const [chipId, faces] of chipFacingMap) {
242
+ const axes = /* @__PURE__ */ new Set();
243
+ const rcl = { axes };
244
+ const counts = faces.counts;
245
+ const anySideHasMultiplePins = !!(counts && (counts.xPos > 1 || counts.xNeg > 1 || counts.yPos > 1 || counts.yNeg > 1));
246
+ const skipCenterRestriction = !anySideHasMultiplePins && chipsOfFacingPins.has(chipId);
247
+ if (!skipCenterRestriction) {
248
+ if (faces.hasXPos && faces.hasXNeg) {
249
+ rcl.x = faces.center.x;
250
+ axes.add("x");
251
+ }
252
+ if (faces.hasYPos && faces.hasYNeg) {
253
+ rcl.y = faces.center.y;
254
+ axes.add("y");
255
+ }
256
+ }
257
+ if (axes.size > 0) {
258
+ restrictedCenterLines.set(chipId, rcl);
259
+ }
260
+ }
261
+ return restrictedCenterLines;
262
+ };
263
+
264
+ // lib/solvers/MspConnectionPairSolver/doesPairCrossRestrictedCenterLines.ts
265
+ var doesPairCrossRestrictedCenterLines = (params) => {
266
+ const { inputProblem, chipMap, pinIdMap, p1, p2 } = params;
267
+ const restrictedCenterLines = getRestrictedCenterLines({
268
+ pins: [p1, p2],
269
+ inputProblem,
270
+ pinIdMap,
271
+ chipMap
272
+ });
273
+ if (restrictedCenterLines.size === 0) return false;
274
+ const EPS4 = 1e-9;
275
+ const crossesSegment = (a, b) => {
276
+ for (const [, rcl] of restrictedCenterLines) {
277
+ if (rcl.axes.has("x") && typeof rcl.x === "number") {
278
+ if ((a.x - rcl.x) * (b.x - rcl.x) < -EPS4) return true;
279
+ }
280
+ if (rcl.axes.has("y") && typeof rcl.y === "number") {
281
+ if ((a.y - rcl.y) * (b.y - rcl.y) < -EPS4) return true;
282
+ }
283
+ }
284
+ return false;
285
+ };
286
+ if (Math.abs(p1.x - p2.x) < EPS4 || Math.abs(p1.y - p2.y) < EPS4) {
287
+ return crossesSegment({ x: p1.x, y: p1.y }, { x: p2.x, y: p2.y });
288
+ }
289
+ const elbowHV = { x: p2.x, y: p1.y };
290
+ const elbowVH = { x: p1.x, y: p2.y };
291
+ const hvCrosses = crossesSegment({ x: p1.x, y: p1.y }, elbowHV) || crossesSegment(elbowHV, { x: p2.x, y: p2.y });
292
+ const vhCrosses = crossesSegment({ x: p1.x, y: p1.y }, elbowVH) || crossesSegment(elbowVH, { x: p2.x, y: p2.y });
293
+ return hvCrosses && vhCrosses;
294
+ };
295
+
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
+
191
304
  // lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts
192
305
  var visualizeInputProblem = (inputProblem, opts = {}) => {
193
306
  const { connectionAlpha = 0.8, chipAlpha = 0.8 } = opts;
@@ -328,6 +441,16 @@ var MspConnectionPairSolver = class extends BaseSolver {
328
441
  if (manhattanDist > this.maxMspPairDistance) {
329
442
  return;
330
443
  }
444
+ const pinIdMap2 = new Map(Object.entries(this.pinMap));
445
+ if (doesPairCrossRestrictedCenterLines({
446
+ inputProblem: this.inputProblem,
447
+ chipMap: this.chipMap,
448
+ pinIdMap: pinIdMap2,
449
+ p1,
450
+ p2
451
+ })) {
452
+ return;
453
+ }
331
454
  const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1);
332
455
  const userNetId = this.userNetIdByPinId[pin1] ?? this.userNetIdByPinId[pin2];
333
456
  this.mspConnectionPairs.push({
@@ -339,11 +462,32 @@ var MspConnectionPairSolver = class extends BaseSolver {
339
462
  });
340
463
  return;
341
464
  }
465
+ const pinIdMap = new Map(Object.entries(this.pinMap));
342
466
  const msp = getOrthogonalMinimumSpanningTree(
343
467
  directlyConnectedPins.map((p) => this.pinMap[p]).filter(Boolean),
344
- { maxDistance: this.maxMspPairDistance }
468
+ {
469
+ maxDistance: this.maxMspPairDistance,
470
+ forbidEdge: (a, b) => doesPairCrossRestrictedCenterLines({
471
+ inputProblem: this.inputProblem,
472
+ chipMap: this.chipMap,
473
+ pinIdMap,
474
+ p1: a,
475
+ p2: b
476
+ })
477
+ }
345
478
  );
346
479
  for (const [pin1, pin2] of msp) {
480
+ const p1Obj = this.pinMap[pin1];
481
+ const p2Obj = this.pinMap[pin2];
482
+ if (doesPairCrossRestrictedCenterLines({
483
+ inputProblem: this.inputProblem,
484
+ chipMap: this.chipMap,
485
+ pinIdMap,
486
+ p1: p1Obj,
487
+ p2: p2Obj
488
+ })) {
489
+ continue;
490
+ }
347
491
  const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1);
348
492
  const userNetId = this.userNetIdByPinId[pin1] ?? this.userNetIdByPinId[pin2];
349
493
  this.mspConnectionPairs.push({
@@ -351,7 +495,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
351
495
  dcConnNetId: dcNetId,
352
496
  globalConnNetId,
353
497
  userNetId,
354
- pins: [this.pinMap[pin1], this.pinMap[pin2]]
498
+ pins: [p1Obj, p2Obj]
355
499
  });
356
500
  }
357
501
  }
@@ -382,11 +526,8 @@ var MspConnectionPairSolver = class extends BaseSolver {
382
526
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts
383
527
  import "graphics-debug";
384
528
 
385
- // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver.ts
386
- import { getBounds as getBounds2 } from "graphics-debug";
387
-
388
- // lib/data-structures/ChipObstacleSpatialIndex.ts
389
- import Flatbush from "flatbush";
529
+ // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts
530
+ import { calculateElbow } from "calculate-elbow";
390
531
 
391
532
  // lib/solvers/GuidelinesSolver/getInputChipBounds.ts
392
533
  function getInputChipBounds(chip) {
@@ -400,460 +541,189 @@ function getInputChipBounds(chip) {
400
541
  };
401
542
  }
402
543
 
403
- // lib/data-structures/ChipObstacleSpatialIndex.ts
404
- var ChipObstacleSpatialIndex = class {
405
- chips;
406
- spatialIndex;
407
- spatialIndexIdToChip;
408
- constructor(chips) {
409
- this.chips = chips.map((chip) => ({
410
- ...chip,
411
- bounds: getInputChipBounds(chip),
412
- spatialIndexId: null
413
- }));
414
- this.spatialIndexIdToChip = /* @__PURE__ */ new Map();
415
- this.spatialIndex = new Flatbush(chips.length);
416
- for (const chip of this.chips) {
417
- chip.spatialIndexId = this.spatialIndex.add(
418
- chip.bounds.minX,
419
- chip.bounds.minY,
420
- chip.bounds.maxX,
421
- chip.bounds.maxY
422
- );
423
- this.spatialIndexIdToChip.set(chip.spatialIndexId, chip);
424
- }
425
- this.spatialIndex.finish();
426
- }
427
- getChipsInBounds(bounds) {
428
- const chipSpatialIndexIds = this.spatialIndex.search(
429
- bounds.minX,
430
- bounds.minY,
431
- bounds.maxX,
432
- bounds.maxY
433
- );
434
- return chipSpatialIndexIds.map((id) => this.spatialIndexIdToChip.get(id));
435
- }
436
- doesOrthogonalLineIntersectChip(line, opts = {}) {
437
- const excludeChipIds = opts.excludeChipIds ?? [];
438
- const [p1, p2] = line;
439
- const { x: x1, y: y1 } = p1;
440
- const { x: x2, y: y2 } = p2;
441
- const chips = this.getChipsInBounds({
442
- minX: Math.min(x1, x2),
443
- minY: Math.min(y1, y2),
444
- maxX: Math.max(x1, x2),
445
- maxY: Math.max(y1, y2)
446
- }).filter((chip) => !excludeChipIds.includes(chip.chipId));
447
- return chips.length > 0;
448
- }
544
+ // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect.ts
545
+ var chipToRect = (chip) => {
546
+ const b = getInputChipBounds(chip);
547
+ return { chipId: chip.chipId, ...b };
449
548
  };
450
-
451
- // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver.ts
452
- import { calculateElbow } from "calculate-elbow";
453
-
454
- // lib/utils/dir.ts
455
- var dir = (facingDirection) => {
456
- switch (facingDirection) {
457
- case "x+":
458
- return { x: 1, y: 0 };
459
- case "x-":
460
- return { x: -1, y: 0 };
461
- case "y+":
462
- return { x: 0, y: 1 };
463
- case "y-":
464
- return { x: 0, y: -1 };
465
- }
466
- throw new Error(`Invalid facing direction: ${facingDirection}`);
549
+ var getObstacleRects = (problem) => {
550
+ return problem.chips.map(chipToRect);
467
551
  };
468
552
 
469
- // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants.ts
470
- var EPS = 1e-6;
471
- var MIN_LEN = 1e-6;
472
- var checkIfUTurnNeeded = (elbow) => {
473
- if (elbow.length !== 4) return false;
474
- const [start, p1, p2, end] = elbow;
475
- const startOrient = orientationOf(start, p1);
476
- const startFacing = startOrient === "horizontal" ? p1.x > start.x ? "x+" : "x-" : p1.y > start.y ? "y+" : "y-";
477
- const endOrient = orientationOf(p2, end);
478
- const endFacing = endOrient === "horizontal" ? end.x > p2.x ? "x+" : "x-" : end.y > p2.y ? "y+" : "y-";
479
- if (startFacing === "x+" && end.x < p1.x - EPS) return true;
480
- if (startFacing === "x-" && end.x > p1.x + EPS) return true;
481
- if (startFacing === "y+" && end.y < p1.y - EPS) return true;
482
- if (startFacing === "y-" && end.y > p1.y + EPS) return true;
483
- if (endFacing === "x-" && p2.x > end.x + EPS && startFacing === "x+")
484
- return true;
485
- if (endFacing === "x+" && p2.x < end.x - EPS && startFacing === "x-")
486
- return true;
487
- if (endFacing === "y-" && p2.y > end.y + EPS && startFacing === "y+")
488
- return true;
489
- if (endFacing === "y+" && p2.y < end.y - EPS && startFacing === "y-")
490
- return true;
491
- return false;
492
- };
493
- var expandToUTurn = (elbow) => {
494
- if (elbow.length !== 4) return elbow;
495
- const [start, p1, p2, end] = elbow;
496
- const overshoot = Math.max(
497
- Math.abs(start.x - p1.x),
498
- Math.abs(start.y - p1.y),
499
- 0.2
500
- // minimum overshoot
501
- );
502
- const startOrient = orientationOf(start, p1);
503
- const expanded = [{ ...start }];
504
- if (startOrient === "horizontal") {
505
- const startDir = p1.x > start.x ? 1 : -1;
506
- expanded.push({ x: start.x + startDir * overshoot, y: start.y });
507
- const verticalSpace = Math.max(
508
- overshoot * 2,
509
- Math.abs(end.y - start.y) + overshoot
510
- );
511
- const midY = end.y > start.y ? start.y - verticalSpace : start.y + verticalSpace;
512
- expanded.push({ x: start.x + startDir * overshoot, y: midY });
513
- const endOrient = orientationOf(p2, end);
514
- const endApproachX = endOrient === "horizontal" ? end.x > p2.x ? end.x - overshoot : end.x + overshoot : end.x;
515
- expanded.push({ x: endApproachX, y: midY });
516
- expanded.push({ x: endApproachX, y: end.y });
553
+ // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts
554
+ var EPS = 1e-9;
555
+ var isVertical = (a, b, eps = EPS) => Math.abs(a.x - b.x) < eps;
556
+ var isHorizontal = (a, b, eps = EPS) => Math.abs(a.y - b.y) < eps;
557
+ var segmentIntersectsRect = (a, b, r, eps = EPS) => {
558
+ const vert = isVertical(a, b, eps);
559
+ const horz = isHorizontal(a, b, eps);
560
+ if (!vert && !horz) return false;
561
+ if (vert) {
562
+ const x = a.x;
563
+ if (x < r.minX - eps || x > r.maxX + eps) return false;
564
+ const segMinY = Math.min(a.y, b.y);
565
+ const segMaxY = Math.max(a.y, b.y);
566
+ const overlap = Math.min(segMaxY, r.maxY) - Math.max(segMinY, r.minY);
567
+ return overlap > eps;
517
568
  } else {
518
- const startDir = p1.y > start.y ? 1 : -1;
519
- expanded.push({ x: start.x, y: start.y + startDir * overshoot });
520
- const horizontalSpace = Math.max(
521
- overshoot * 2,
522
- Math.abs(end.x - start.x) + overshoot
523
- );
524
- const midX = end.x > start.x ? start.x - horizontalSpace : start.x + horizontalSpace;
525
- expanded.push({ x: midX, y: start.y + startDir * overshoot });
526
- const endOrient = orientationOf(p2, end);
527
- const endApproachY = endOrient === "vertical" ? end.y > p2.y ? end.y - overshoot : end.y + overshoot : end.y;
528
- expanded.push({ x: midX, y: endApproachY });
529
- expanded.push({ x: end.x, y: endApproachY });
530
- }
531
- expanded.push({ ...end });
532
- return expanded;
533
- };
534
- var isAxisAligned = (a, b) => Math.abs(a.x - b.x) < EPS || Math.abs(a.y - b.y) < EPS;
535
- var orientationOf = (a, b) => {
536
- const dx = Math.abs(a.x - b.x);
537
- const dy = Math.abs(a.y - b.y);
538
- if (dx < EPS && dy >= EPS) return "vertical";
539
- if (dy < EPS && dx >= EPS) return "horizontal";
540
- throw new Error("Non-orthogonal or degenerate segment detected.");
541
- };
542
- var assertOrthogonalPolyline = (pts) => {
543
- if (pts.length < 2) {
544
- throw new Error("Polyline must have at least two points.");
569
+ const y = a.y;
570
+ if (y < r.minY - eps || y > r.maxY + eps) return false;
571
+ const segMinX = Math.min(a.x, b.x);
572
+ const segMaxX = Math.max(a.x, b.x);
573
+ const overlap = Math.min(segMaxX, r.maxX) - Math.max(segMinX, r.minX);
574
+ return overlap > eps;
545
575
  }
546
- for (let i = 0; i < pts.length - 1; i++) {
547
- const a = pts[i];
548
- const b = pts[i + 1];
549
- if (!isAxisAligned(a, b)) {
550
- throw new Error("Polyline contains a non-orthogonal segment.");
551
- }
552
- const manhattan = Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
553
- if (manhattan < MIN_LEN) {
554
- throw new Error("Polyline contains a near-zero length segment.");
555
- }
556
- }
557
- };
558
- var uniqSortedWithTol = (vals, tol = EPS) => {
559
- const sorted = vals.slice().sort((a, b) => a - b);
560
- const out = [];
561
- for (const v of sorted) {
562
- if (out.length === 0 || Math.abs(v - out[out.length - 1]) > tol) out.push(v);
563
- }
564
- return out;
565
- };
566
- var keyForPolyline = (pts, decimals = 9) => pts.map((p) => `${p.x.toFixed(decimals)},${p.y.toFixed(decimals)}`).join("|");
567
- var preprocessElbow = (pts, tol = MIN_LEN) => {
568
- if (pts.length === 0) return [];
569
- const out = [{ ...pts[0] }];
570
- for (let i = 1; i < pts.length; i++) {
571
- const p = pts[i];
572
- const last = out[out.length - 1];
573
- const manhattan = Math.abs(p.x - last.x) + Math.abs(p.y - last.y);
574
- if (manhattan > tol) out.push({ ...p });
575
- }
576
- return out;
577
- };
578
- var collectAxisCandidates = (axis, guidelines, currentCoord) => {
579
- const positions = [];
580
- for (const g of guidelines) {
581
- if (axis === "x") {
582
- if (g.orientation === "vertical" && typeof g.x === "number") {
583
- positions.push(g.x);
584
- }
585
- } else {
586
- if (g.orientation === "horizontal" && typeof g.y === "number") {
587
- positions.push(g.y);
588
- }
589
- }
590
- }
591
- positions.push(currentCoord);
592
- return uniqSortedWithTol(positions);
593
- };
594
- var computeFreedom = (prev, start, end) => {
595
- const orient = orientationOf(start, end);
596
- if (orient === "horizontal") {
597
- const facing = prev.y <= start.y ? "y+" : "y-";
598
- return { axis: "y", facing };
599
- } else {
600
- const facing = prev.x <= start.x ? "x+" : "x-";
601
- return { axis: "x", facing };
602
- }
603
- };
604
- var cartesian = (arrays) => arrays.length === 0 ? [[]] : arrays.reduce(
605
- (acc, curr) => acc.flatMap((a) => curr.map((c) => [...a, c])),
606
- [[]]
607
- );
608
- var generateElbowVariants = ({
609
- baseElbow,
610
- guidelines,
611
- maxVariants = 1e4
612
- }) => {
613
- const elbow = preprocessElbow(baseElbow);
614
- assertOrthogonalPolyline(elbow);
615
- const nPts = elbow.length;
616
- if (nPts < 4) {
617
- return {
618
- elbowVariants: [elbow.map((p) => ({ ...p }))],
619
- movableSegments: []
620
- };
621
- } else if (nPts === 4) {
622
- const needsUTurn = checkIfUTurnNeeded(elbow);
623
- if (needsUTurn) {
624
- const expandedElbow = expandToUTurn(elbow);
625
- return generateElbowVariants({ baseElbow: expandedElbow, guidelines });
626
- }
627
- }
628
- const firstMovableIndex = 1;
629
- const lastMovableIndex = nPts - 3;
630
- const movableSegments = [];
631
- const movableIdx = [];
632
- const axes = [];
633
- const optionsPerSegment = [];
634
- for (let i = firstMovableIndex; i <= lastMovableIndex; i++) {
635
- const prev = elbow[i - 1];
636
- const start = elbow[i];
637
- const end = elbow[i + 1];
638
- const next2 = elbow[i + 2];
639
- const { axis, facing } = computeFreedom(prev, start, end);
640
- movableSegments.push({
641
- start: { ...start },
642
- end: { ...end },
643
- freedom: facing,
644
- dir: dir(facing)
645
- });
646
- movableIdx.push(i);
647
- axes.push(axis);
648
- const currentCoord = axis === "x" ? start.x : start.y;
649
- const rawCandidates = collectAxisCandidates(axis, guidelines, currentCoord);
650
- const filtered = rawCandidates.filter((pos) => {
651
- if (axis === "y") {
652
- const lenPrev = Math.abs(prev.y - pos);
653
- const lenNext = Math.abs(next2.y - pos);
654
- return lenPrev > MIN_LEN && lenNext > MIN_LEN;
655
- } else {
656
- const lenPrev = Math.abs(prev.x - pos);
657
- const lenNext = Math.abs(next2.x - pos);
658
- return lenPrev > MIN_LEN && lenNext > MIN_LEN;
659
- }
660
- });
661
- optionsPerSegment.push(filtered);
662
- }
663
- const combos = cartesian(optionsPerSegment);
664
- const seen = /* @__PURE__ */ new Set();
665
- const elbowVariants = [];
666
- {
667
- const key = keyForPolyline(elbow);
668
- seen.add(key);
669
- elbowVariants.push(elbow.map((p) => ({ ...p })));
670
- }
671
- for (const combo of combos) {
672
- if (combo.length === 0) continue;
673
- const variant = elbow.map((p) => ({ ...p }));
674
- for (let k = 0; k < movableIdx.length; k++) {
675
- const segIndex = movableIdx[k];
676
- const axis = axes[k];
677
- const newPos = combo[k];
678
- if (typeof newPos !== "number" || Number.isNaN(newPos)) continue;
679
- if (axis === "y") {
680
- variant[segIndex] = { ...variant[segIndex], y: newPos };
681
- variant[segIndex + 1] = { ...variant[segIndex + 1], y: newPos };
682
- } else {
683
- variant[segIndex] = { ...variant[segIndex], x: newPos };
684
- variant[segIndex + 1] = { ...variant[segIndex + 1], x: newPos };
685
- }
686
- }
687
- try {
688
- assertOrthogonalPolyline(variant);
689
- } catch {
690
- continue;
691
- }
692
- const key = keyForPolyline(variant);
693
- if (!seen.has(key)) {
694
- seen.add(key);
695
- elbowVariants.push(variant);
696
- if (elbowVariants.length >= maxVariants) {
697
- break;
698
- }
699
- }
700
- }
701
- return { elbowVariants, movableSegments };
702
- };
703
-
704
- // lib/solvers/GuidelinesSolver/visualizeGuidelines.ts
705
- import { getBounds } from "graphics-debug";
706
- var visualizeGuidelines = ({
707
- guidelines,
708
- graphics
709
- }) => {
710
- const globalBounds = getBounds(graphics);
711
- const boundsWidth = globalBounds.maxX - globalBounds.minX;
712
- const boundsHeight = globalBounds.maxY - globalBounds.minY;
713
- globalBounds.minX -= boundsWidth * 0.3;
714
- globalBounds.maxX += boundsWidth * 0.3;
715
- globalBounds.minY -= boundsHeight * 0.3;
716
- globalBounds.maxY += boundsHeight * 0.3;
717
- for (const guideline of guidelines) {
718
- if (guideline.orientation === "horizontal") {
719
- graphics.lines.push({
720
- points: [
721
- { x: globalBounds.minX, y: guideline.y },
722
- { x: globalBounds.maxX, y: guideline.y }
723
- ],
724
- strokeColor: "rgba(0, 0, 0, 0.5)",
725
- strokeDash: "2 2"
726
- });
727
- }
728
- if (guideline.orientation === "vertical") {
729
- graphics.lines.push({
730
- points: [
731
- { x: guideline.x, y: globalBounds.minY },
732
- { x: guideline.x, y: globalBounds.maxY }
733
- ],
734
- strokeColor: "rgba(0, 0, 0, 0.5)",
735
- strokeDash: "2 2"
736
- });
737
- }
738
- }
739
- return graphics;
740
- };
741
-
742
- // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getRestrictedCenterLines.ts
743
- var getRestrictedCenterLines = (params) => {
744
- const { pins, inputProblem, pinIdMap, chipMap } = params;
745
- const findAllDirectlyConnectedPins = (startPinId) => {
746
- const visited = /* @__PURE__ */ new Set();
747
- const queue = [startPinId];
748
- visited.add(startPinId);
749
- const directConns = inputProblem.directConnections || [];
750
- while (queue.length) {
751
- const cur = queue.shift();
752
- for (const dc of directConns) {
753
- if (dc.pinIds.includes(cur)) {
754
- for (const p of dc.pinIds) {
755
- if (!visited.has(p)) {
756
- visited.add(p);
757
- queue.push(p);
758
- }
759
- }
760
- }
761
- }
762
- }
763
- return visited;
764
- };
765
- const p0 = pins[0].pinId;
766
- const p1 = pins[1].pinId;
767
- const relatedPinIds = /* @__PURE__ */ new Set([
768
- ...findAllDirectlyConnectedPins(p0),
769
- ...findAllDirectlyConnectedPins(p1)
770
- ]);
771
- const restrictedCenterLines = /* @__PURE__ */ new Map();
772
- const chipFacingMap = /* @__PURE__ */ new Map();
773
- const chipsOfFacingPins = new Set(pins.map((p) => p.chipId));
774
- for (const pinId of relatedPinIds) {
775
- const pin = pinIdMap.get(pinId);
776
- if (!pin) continue;
777
- const chip = chipMap[pin.chipId];
778
- if (!chip) continue;
779
- const facing = pin._facingDirection ?? getPinDirection(pin, chip);
780
- let entry = chipFacingMap.get(chip.chipId);
781
- if (!entry) {
782
- entry = { center: chip.center };
783
- const counts = { xPos: 0, xNeg: 0, yPos: 0, yNeg: 0 };
784
- for (const cp of chip.pins) {
785
- const cpFacing = cp._facingDirection ?? getPinDirection(cp, chip);
786
- if (cpFacing === "x+") counts.xPos++;
787
- if (cpFacing === "x-") counts.xNeg++;
788
- if (cpFacing === "y+") counts.yPos++;
789
- if (cpFacing === "y-") counts.yNeg++;
576
+ };
577
+ var findFirstCollision = (pts, rects, opts = {}) => {
578
+ for (let i = 0; i < pts.length - 1; i++) {
579
+ const a = pts[i];
580
+ const b = pts[i + 1];
581
+ const excluded = opts.excludeRectIdsForSegment?.(i) ?? /* @__PURE__ */ new Set();
582
+ for (const r of rects) {
583
+ if (excluded.has(r.chipId)) continue;
584
+ if (segmentIntersectsRect(a, b, r)) {
585
+ return { segIndex: i, rect: r };
790
586
  }
791
- entry.counts = counts;
792
- chipFacingMap.set(chip.chipId, entry);
793
587
  }
794
- if (facing === "x+") entry.hasXPos = true;
795
- if (facing === "x-") entry.hasXNeg = true;
796
- if (facing === "y+") entry.hasYPos = true;
797
- if (facing === "y-") entry.hasYNeg = true;
798
588
  }
799
- for (const [chipId, faces] of chipFacingMap) {
800
- const axes = /* @__PURE__ */ new Set();
801
- const rcl = { axes };
802
- const counts = faces.counts;
803
- const anySideHasMultiplePins = !!(counts && (counts.xPos > 1 || counts.xNeg > 1 || counts.yPos > 1 || counts.yNeg > 1));
804
- const skipCenterRestriction = !anySideHasMultiplePins && chipsOfFacingPins.has(chipId);
805
- if (!skipCenterRestriction) {
806
- if (faces.hasXPos && faces.hasXNeg) {
807
- rcl.x = faces.center.x;
808
- axes.add("x");
809
- }
810
- if (faces.hasYPos && faces.hasYNeg) {
811
- rcl.y = faces.center.y;
812
- axes.add("y");
813
- }
589
+ return null;
590
+ };
591
+
592
+ // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/mid.ts
593
+ var EPS2 = 1e-9;
594
+ var aabbFromPoints = (a, b) => ({
595
+ minX: Math.min(a.x, b.x),
596
+ maxX: Math.max(a.x, b.x),
597
+ minY: Math.min(a.y, b.y),
598
+ maxY: Math.max(a.y, b.y)
599
+ });
600
+ var midBetweenPointAndRect = (axis, p, r, eps = EPS2) => {
601
+ if (axis === "x") {
602
+ if (p.x < r.minX - eps) {
603
+ return [(p.x + r.minX) / 2];
604
+ }
605
+ if (p.x > r.maxX + eps) {
606
+ return [(p.x + r.maxX) / 2];
607
+ }
608
+ return [r.minX - 0.2, r.maxX + 0.2];
609
+ } else {
610
+ if (p.y < r.minY - eps) {
611
+ return [(p.y + r.minY) / 2];
814
612
  }
815
- if (axes.size > 0) {
816
- restrictedCenterLines.set(chipId, rcl);
613
+ if (p.y > r.maxY + eps) {
614
+ return [(p.y + r.maxY) / 2];
817
615
  }
616
+ return [r.minY - 0.2, r.maxY + 0.2];
818
617
  }
819
- return restrictedCenterLines;
820
618
  };
619
+ var candidateMidsFromSet = (axis, colliding, rectsById, collisionRectIds, aabb, eps = EPS2) => {
620
+ const setRects = [...collisionRectIds].map((id) => rectsById.get(id)).filter((r) => !!r);
621
+ if (axis === "x") {
622
+ const leftBoundaries = [aabb.minX, ...setRects.map((r) => r.maxX)].filter(
623
+ (v) => v < colliding.minX - eps
624
+ );
625
+ const rightBoundaries = [aabb.maxX, ...setRects.map((r) => r.minX)].filter(
626
+ (v) => v > colliding.maxX + eps
627
+ );
628
+ const leftNeighbor = leftBoundaries.length > 0 ? Math.max(...leftBoundaries) : void 0;
629
+ const rightNeighbor = rightBoundaries.length > 0 ? Math.min(...rightBoundaries) : void 0;
630
+ const out = [];
631
+ if (leftNeighbor !== void 0) {
632
+ out.push((leftNeighbor + colliding.minX) / 2);
633
+ }
634
+ if (rightNeighbor !== void 0) {
635
+ out.push((colliding.maxX + rightNeighbor) / 2);
636
+ }
637
+ return out;
638
+ } else {
639
+ const bottomBoundaries = [aabb.minY, ...setRects.map((r) => r.maxY)].filter(
640
+ (v) => v < colliding.minY - eps
641
+ );
642
+ const topBoundaries = [aabb.maxY, ...setRects.map((r) => r.minY)].filter(
643
+ (v) => v > colliding.maxY + eps
644
+ );
645
+ const bottomNeighbor = bottomBoundaries.length > 0 ? Math.max(...bottomBoundaries) : void 0;
646
+ const topNeighbor = topBoundaries.length > 0 ? Math.min(...topBoundaries) : void 0;
647
+ const out = [];
648
+ if (bottomNeighbor !== void 0) {
649
+ out.push((bottomNeighbor + colliding.minY) / 2);
650
+ }
651
+ if (topNeighbor !== void 0) {
652
+ out.push((colliding.maxY + topNeighbor) / 2);
653
+ }
654
+ return out;
655
+ }
656
+ };
657
+
658
+ // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/pathOps.ts
659
+ var EPS3 = 1e-9;
660
+ var shiftSegmentOrth = (pts, segIndex, axis, newCoord, eps = EPS3) => {
661
+ if (segIndex < 0 || segIndex >= pts.length - 1) return null;
662
+ const a = pts[segIndex];
663
+ const b = pts[segIndex + 1];
664
+ const vert = isVertical(a, b, eps);
665
+ const horz = isHorizontal(a, b, eps);
666
+ if (!vert && !horz) return null;
667
+ if (vert && axis !== "x") return null;
668
+ if (horz && axis !== "y") return null;
669
+ const out = pts.map((p) => ({ ...p }));
670
+ if (axis === "x") {
671
+ if (Math.abs(a.x - newCoord) < eps && Math.abs(b.x - newCoord) < eps)
672
+ return null;
673
+ out[segIndex] = { ...out[segIndex], x: newCoord };
674
+ out[segIndex + 1] = { ...out[segIndex + 1], x: newCoord };
675
+ } else {
676
+ if (Math.abs(a.y - newCoord) < eps && Math.abs(b.y - newCoord) < eps)
677
+ return null;
678
+ out[segIndex] = { ...out[segIndex], y: newCoord };
679
+ out[segIndex + 1] = { ...out[segIndex + 1], y: newCoord };
680
+ }
681
+ if (segIndex - 1 >= 0) {
682
+ const p = out[segIndex - 1];
683
+ const q = out[segIndex];
684
+ const manhattan = Math.abs(p.x - q.x) + Math.abs(p.y - q.y);
685
+ if (manhattan < eps) return null;
686
+ }
687
+ if (segIndex + 2 <= out.length - 1) {
688
+ const p = out[segIndex + 1];
689
+ const q = out[segIndex + 2];
690
+ const manhattan = Math.abs(p.x - q.x) + Math.abs(p.y - q.y);
691
+ if (manhattan < eps) return null;
692
+ }
693
+ for (let i = 0; i < out.length - 1; i++) {
694
+ const u = out[i];
695
+ const v = out[i + 1];
696
+ if (!isHorizontal(u, v, eps) && !isVertical(u, v, eps)) return null;
697
+ }
698
+ return out;
699
+ };
700
+ var pathKey = (pts, decimals = 6) => pts.map((p) => `${p.x.toFixed(decimals)},${p.y.toFixed(decimals)}`).join("|");
821
701
 
822
- // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver.ts
823
- var SchematicTraceSingleLineSolver = class extends BaseSolver {
702
+ // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts
703
+ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
824
704
  pins;
825
705
  inputProblem;
826
- guidelines;
827
706
  chipMap;
828
- movableSegments;
707
+ obstacles;
708
+ rectById;
709
+ aabb;
829
710
  baseElbow;
830
- allCandidatePaths;
831
- queuedCandidatePaths;
832
- chipObstacleSpatialIndex;
833
711
  solvedTracePath = null;
834
- // map of pinId -> pin (with chipId attached)
835
- pinIdMap = /* @__PURE__ */ new Map();
712
+ queue = [];
713
+ visited = /* @__PURE__ */ new Set();
836
714
  constructor(params) {
837
715
  super();
838
716
  this.pins = params.pins;
839
717
  this.inputProblem = params.inputProblem;
840
- this.guidelines = params.guidelines;
841
718
  this.chipMap = params.chipMap;
842
- this.chipObstacleSpatialIndex = this.inputProblem._chipObstacleSpatialIndex || new ChipObstacleSpatialIndex(this.inputProblem.chips);
843
- if (!this.inputProblem._chipObstacleSpatialIndex) {
844
- this.inputProblem._chipObstacleSpatialIndex = this.chipObstacleSpatialIndex;
845
- }
846
- for (const chip of this.inputProblem.chips) {
847
- for (const pin of chip.pins) {
848
- this.pinIdMap.set(pin.pinId, { ...pin, chipId: chip.chipId });
849
- }
850
- }
851
719
  for (const pin of this.pins) {
852
720
  if (!pin._facingDirection) {
853
721
  const chip = this.chipMap[pin.chipId];
854
722
  pin._facingDirection = getPinDirection(pin, chip);
855
723
  }
856
724
  }
725
+ this.obstacles = getObstacleRects(this.inputProblem);
726
+ this.rectById = new Map(this.obstacles.map((r) => [r.chipId, r]));
857
727
  const [pin1, pin2] = this.pins;
858
728
  this.baseElbow = calculateElbow(
859
729
  {
@@ -866,204 +736,130 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
866
736
  y: pin2.y,
867
737
  facingDirection: pin2._facingDirection
868
738
  },
869
- {
870
- overshoot: 0.2
871
- }
739
+ { overshoot: 0.2 }
872
740
  );
873
- const { elbowVariants, movableSegments } = generateElbowVariants({
874
- baseElbow: this.baseElbow,
875
- guidelines: this.guidelines,
876
- maxVariants: 1e3
877
- });
878
- this.movableSegments = movableSegments;
879
- const getPathLength = (pts) => {
880
- let len = 0;
881
- for (let i = 0; i < pts.length - 1; i++) {
882
- const dx = pts[i + 1].x - pts[i].x;
883
- const dy = pts[i + 1].y - pts[i].y;
884
- len += Math.sqrt(dx * dx + dy * dy);
885
- }
886
- return len;
887
- };
888
- this.allCandidatePaths = [this.baseElbow, ...elbowVariants];
889
- this.queuedCandidatePaths = [...this.allCandidatePaths].sort(
890
- (a, b) => getPathLength(a) - getPathLength(b)
741
+ this.aabb = aabbFromPoints(
742
+ { x: pin1.x, y: pin1.y },
743
+ { x: pin2.x, y: pin2.y }
891
744
  );
745
+ this.queue.push({ path: this.baseElbow, collisionChipIds: /* @__PURE__ */ new Set() });
746
+ this.visited.add(pathKey(this.baseElbow));
892
747
  }
893
748
  getConstructorParams() {
894
749
  return {
895
750
  chipMap: this.chipMap,
896
751
  pins: this.pins,
897
- guidelines: this.guidelines,
898
752
  inputProblem: this.inputProblem
899
753
  };
900
754
  }
755
+ axisOfSegment(a, b) {
756
+ if (isVertical(a, b)) return "x";
757
+ if (isHorizontal(a, b)) return "y";
758
+ return null;
759
+ }
760
+ pathLength(pts) {
761
+ let sum = 0;
762
+ for (let i = 0; i < pts.length - 1; i++) {
763
+ sum += Math.abs(pts[i + 1].x - pts[i].x) + Math.abs(pts[i + 1].y - pts[i].y);
764
+ }
765
+ return sum;
766
+ }
901
767
  _step() {
902
- if (this.queuedCandidatePaths.length === 0) {
768
+ if (this.solvedTracePath) {
769
+ this.solved = true;
770
+ return;
771
+ }
772
+ const state = this.queue.shift();
773
+ if (!state) {
903
774
  this.failed = true;
904
- this.error = "No more candidate elbows, everything had collisions";
775
+ this.error = "No collision-free path found";
905
776
  return;
906
777
  }
907
- const nextCandidatePath = this.queuedCandidatePaths.shift();
908
- const restrictedCenterLines = getRestrictedCenterLines({
909
- pins: this.pins,
910
- inputProblem: this.inputProblem,
911
- pinIdMap: this.pinIdMap,
912
- chipMap: this.chipMap
913
- });
914
- let pathIsValid = true;
915
- for (let i = 0; i < nextCandidatePath.length - 1; i++) {
916
- const start = nextCandidatePath[i];
917
- const end = nextCandidatePath[i + 1];
918
- let excludeChipIds = [];
919
- const EPS2 = 1e-9;
920
- for (const [, rcl] of restrictedCenterLines) {
921
- if (rcl.axes.has("x") && typeof rcl.x === "number") {
922
- if ((start.x - rcl.x) * (end.x - rcl.x) < -EPS2) {
923
- pathIsValid = false;
924
- break;
925
- }
926
- }
927
- if (rcl.axes.has("y") && typeof rcl.y === "number") {
928
- if ((start.y - rcl.y) * (end.y - rcl.y) < -EPS2) {
929
- pathIsValid = false;
930
- break;
931
- }
932
- }
933
- }
934
- if (!pathIsValid) break;
935
- const isStartPin = this.pins.some(
936
- (pin) => Math.abs(pin.x - start.x) < 1e-10 && Math.abs(pin.y - start.y) < 1e-10
937
- );
938
- const isEndPin = this.pins.some(
939
- (pin) => Math.abs(pin.x - end.x) < 1e-10 && Math.abs(pin.y - end.y) < 1e-10
940
- );
941
- if (isStartPin) {
942
- const startPin = this.pins.find(
943
- (pin) => Math.abs(pin.x - start.x) < 1e-10 && Math.abs(pin.y - start.y) < 1e-10
944
- );
945
- if (startPin) {
946
- const bounds = getInputChipBounds(this.chipMap[startPin.chipId]);
947
- const dx = end.x - start.x;
948
- const dy = end.y - start.y;
949
- const EPS3 = 1e-9;
950
- const onLeft = Math.abs(start.x - bounds.minX) < 1e-9;
951
- const onRight = Math.abs(start.x - bounds.maxX) < 1e-9;
952
- const onBottom = Math.abs(start.y - bounds.minY) < 1e-9;
953
- const onTop = Math.abs(start.y - bounds.maxY) < 1e-9;
954
- const entersInterior = onLeft && dx > EPS3 || onRight && dx < -EPS3 || onBottom && dy > EPS3 || onTop && dy < -EPS3;
955
- if (entersInterior) {
956
- pathIsValid = false;
957
- break;
958
- }
959
- if (!excludeChipIds.includes(startPin.chipId)) {
960
- excludeChipIds.push(startPin.chipId);
961
- }
962
- }
778
+ const { path, collisionChipIds } = state;
779
+ const collision = findFirstCollision(path, this.obstacles);
780
+ if (!collision) {
781
+ this.solvedTracePath = path;
782
+ this.solved = true;
783
+ return;
784
+ }
785
+ let { segIndex, rect } = collision;
786
+ const isFirstSegment = segIndex === 0;
787
+ const isLastSegment = segIndex === path.length - 2;
788
+ if (isFirstSegment) {
789
+ if (path.length < 3) {
790
+ return;
963
791
  }
964
- if (!pathIsValid) break;
965
- if (isEndPin) {
966
- const endPin = this.pins.find(
967
- (pin) => Math.abs(pin.x - end.x) < 1e-10 && Math.abs(pin.y - end.y) < 1e-10
968
- );
969
- if (endPin) {
970
- const bounds = getInputChipBounds(this.chipMap[endPin.chipId]);
971
- const dx = start.x - end.x;
972
- const dy = start.y - end.y;
973
- const EPS3 = 1e-9;
974
- const onLeft = Math.abs(end.x - bounds.minX) < 1e-9;
975
- const onRight = Math.abs(end.x - bounds.maxX) < 1e-9;
976
- const onBottom = Math.abs(end.y - bounds.minY) < 1e-9;
977
- const onTop = Math.abs(end.y - bounds.maxY) < 1e-9;
978
- const entersInterior = onLeft && dx > EPS3 || onRight && dx < -EPS3 || onBottom && dy > EPS3 || onTop && dy < -EPS3;
979
- if (entersInterior) {
980
- pathIsValid = false;
981
- break;
982
- }
983
- if (!excludeChipIds.includes(endPin.chipId)) {
984
- excludeChipIds.push(endPin.chipId);
985
- }
986
- }
792
+ segIndex = 1;
793
+ } else if (isLastSegment) {
794
+ if (path.length < 3) {
795
+ return;
987
796
  }
988
- if (!pathIsValid) break;
989
- const obstacleOps = { excludeChipIds };
990
- const intersects = this.chipObstacleSpatialIndex.doesOrthogonalLineIntersectChip(
991
- [start, end],
992
- obstacleOps
797
+ segIndex = path.length - 3;
798
+ }
799
+ const a = path[segIndex];
800
+ const b = path[segIndex + 1];
801
+ const axis = this.axisOfSegment(a, b);
802
+ if (!axis) {
803
+ return;
804
+ }
805
+ const [PA, PB] = this.pins;
806
+ const candidates = [];
807
+ if (collisionChipIds.size === 0) {
808
+ const m1 = midBetweenPointAndRect(axis, { x: PA.x, y: PA.y }, rect);
809
+ const m2 = midBetweenPointAndRect(axis, { x: PB.x, y: PB.y }, rect);
810
+ const allCandidates = [...m1, ...m2];
811
+ const uniqueCandidates = [...new Set(allCandidates)];
812
+ candidates.push(...uniqueCandidates);
813
+ } else {
814
+ const mids = candidateMidsFromSet(
815
+ axis,
816
+ rect,
817
+ this.rectById,
818
+ collisionChipIds,
819
+ this.aabb
993
820
  );
994
- if (intersects) {
995
- pathIsValid = false;
996
- break;
997
- }
821
+ candidates.push(...mids);
998
822
  }
999
- if (pathIsValid) {
1000
- this.solvedTracePath = nextCandidatePath;
1001
- this.solved = true;
823
+ const newStates = [];
824
+ for (const coord of candidates) {
825
+ const newPath = shiftSegmentOrth(path, segIndex, axis, coord);
826
+ if (!newPath) continue;
827
+ const key = pathKey(newPath);
828
+ if (this.visited.has(key)) continue;
829
+ this.visited.add(key);
830
+ const nextSet = new Set(collisionChipIds);
831
+ nextSet.add(rect.chipId);
832
+ const len = this.pathLength(newPath);
833
+ newStates.push({ path: newPath, collisionRectIds: nextSet, len });
834
+ }
835
+ newStates.sort((a2, b2) => a2.len - b2.len);
836
+ for (const st of newStates) {
837
+ this.queue.push({ path: st.path, collisionChipIds: st.collisionRectIds });
1002
838
  }
1003
839
  }
1004
840
  visualize() {
1005
- const graphics = visualizeInputProblem(this.inputProblem, {
841
+ const g = visualizeInputProblem(this.inputProblem, {
1006
842
  chipAlpha: 0.1,
1007
843
  connectionAlpha: 0.1
1008
844
  });
1009
- const bounds = getBounds2(graphics);
1010
- const boundsWidth = bounds.maxX - bounds.minX;
1011
- const boundsHeight = bounds.maxY - bounds.minY;
1012
- visualizeGuidelines({ guidelines: this.guidelines, graphics });
1013
- for (const { start, end, dir: dir2 } of this.movableSegments) {
1014
- const mid = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
1015
- const dist = Math.sqrt((start.x - end.x) ** 2 + (start.y - end.y) ** 2);
1016
- graphics.lines.push({
1017
- points: [start, mid, end],
1018
- strokeColor: "rgba(0,0,255,0.5)",
1019
- strokeDash: "2 2"
1020
- });
1021
- graphics.lines.push({
1022
- points: [
1023
- mid,
1024
- { x: mid.x + dir2.x * dist * 0.1, y: mid.y + dir2.y * dist * 0.1 }
1025
- ],
1026
- strokeColor: "rgba(0,0,255,0.5)",
1027
- strokeDash: "2 2"
1028
- });
1029
- }
1030
- if (!this.solvedTracePath) {
1031
- if (this.queuedCandidatePaths.length > 0) {
1032
- graphics.lines.push({
1033
- points: this.queuedCandidatePaths[0],
1034
- strokeColor: "orange"
1035
- });
1036
- }
1037
- for (let i = 1; i < this.queuedCandidatePaths.length; i++) {
1038
- const candidatePath = this.queuedCandidatePaths[i];
1039
- const pi = i / this.queuedCandidatePaths.length;
1040
- graphics.lines.push({
1041
- points: candidatePath.map((p) => ({
1042
- x: p.x + pi * boundsWidth * 5e-3,
1043
- y: p.y + pi * boundsHeight * 5e-3
1044
- })),
1045
- strokeColor: getColorFromString(
1046
- `${candidatePath.reduce((acc, p) => `${acc},${p.x},${p.y}`, "")}`,
1047
- 0.5
1048
- ),
1049
- strokeDash: "8 8"
1050
- });
1051
- }
845
+ for (const { path, collisionChipIds: collisionRectIds } of this.queue) {
846
+ g.lines.push({ points: path, strokeColor: "teal", strokeDash: "2 2" });
1052
847
  }
1053
848
  if (this.solvedTracePath) {
1054
- graphics.lines.push({
1055
- points: this.solvedTracePath,
1056
- strokeColor: "green"
1057
- });
849
+ g.lines.push({ points: this.solvedTracePath, strokeColor: "green" });
850
+ } else if (this.queue.length > 0) {
851
+ g.lines.push({ points: this.queue[0].path, strokeColor: "orange" });
1058
852
  }
1059
- return graphics;
853
+ return g;
1060
854
  }
1061
855
  };
1062
856
 
857
+ // lib/solvers/GuidelinesSolver/visualizeGuidelines.ts
858
+ import { getBounds } from "graphics-debug";
859
+
1063
860
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts
1064
861
  var SchematicTraceLinesSolver = class extends BaseSolver {
1065
862
  inputProblem;
1066
- guidelines;
1067
863
  mspConnectionPairs;
1068
864
  dcConnMap;
1069
865
  globalConnMap;
@@ -1078,7 +874,6 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1078
874
  this.mspConnectionPairs = params.mspConnectionPairs;
1079
875
  this.dcConnMap = params.dcConnMap;
1080
876
  this.globalConnMap = params.globalConnMap;
1081
- this.guidelines = params.guidelines;
1082
877
  this.chipMap = params.chipMap;
1083
878
  this.queuedConnectionPairs = [...this.mspConnectionPairs];
1084
879
  }
@@ -1088,8 +883,7 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1088
883
  chipMap: this.chipMap,
1089
884
  mspConnectionPairs: this.mspConnectionPairs,
1090
885
  dcConnMap: this.dcConnMap,
1091
- globalConnMap: this.globalConnMap,
1092
- guidelines: this.guidelines
886
+ globalConnMap: this.globalConnMap
1093
887
  };
1094
888
  }
1095
889
  _step() {
@@ -1127,11 +921,10 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1127
921
  }
1128
922
  this.currentConnectionPair = connectionPair;
1129
923
  const { pins } = connectionPair;
1130
- this.activeSubSolver = new SchematicTraceSingleLineSolver({
924
+ this.activeSubSolver = new SchematicTraceSingleLineSolver2({
1131
925
  inputProblem: this.inputProblem,
1132
926
  pins,
1133
- chipMap: this.chipMap,
1134
- guidelines: this.guidelines
927
+ chipMap: this.chipMap
1135
928
  });
1136
929
  }
1137
930
  visualize() {
@@ -1142,7 +935,6 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1142
935
  chipAlpha: 0.1,
1143
936
  connectionAlpha: 0.1
1144
937
  });
1145
- visualizeGuidelines({ guidelines: this.guidelines, graphics });
1146
938
  for (const { mspPairId, tracePath } of this.solvedTracePaths) {
1147
939
  graphics.lines.push({
1148
940
  points: tracePath,
@@ -1169,17 +961,17 @@ var applyJogToTerminalSegment = ({
1169
961
  segmentIndex: si,
1170
962
  offset,
1171
963
  JOG_SIZE,
1172
- EPS: EPS2 = 1e-6
964
+ EPS: EPS4 = 1e-6
1173
965
  }) => {
1174
966
  if (si !== 0 && si !== pts.length - 2) return;
1175
967
  const start = pts[si];
1176
968
  const end = pts[si + 1];
1177
- const isVertical = Math.abs(start.x - end.x) < EPS2;
1178
- const isHorizontal = Math.abs(start.y - end.y) < EPS2;
1179
- if (!isVertical && !isHorizontal) return;
1180
- const segDir = isVertical ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
969
+ const isVertical2 = Math.abs(start.x - end.x) < EPS4;
970
+ const isHorizontal2 = Math.abs(start.y - end.y) < EPS4;
971
+ if (!isVertical2 && !isHorizontal2) return;
972
+ const segDir = isVertical2 ? end.y > start.y ? 1 : -1 : end.x > start.x ? 1 : -1;
1181
973
  if (si === 0) {
1182
- if (isVertical) {
974
+ if (isVertical2) {
1183
975
  const jogY = start.y + segDir * JOG_SIZE;
1184
976
  pts.splice(
1185
977
  1,
@@ -1199,7 +991,7 @@ var applyJogToTerminalSegment = ({
1199
991
  );
1200
992
  }
1201
993
  } else {
1202
- if (isVertical) {
994
+ if (isVertical2) {
1203
995
  const jogY = end.y - segDir * JOG_SIZE;
1204
996
  pts.splice(
1205
997
  si,
@@ -1242,13 +1034,13 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1242
1034
  }
1243
1035
  }
1244
1036
  _step() {
1245
- const EPS2 = 1e-6;
1037
+ const EPS4 = 1e-6;
1246
1038
  const offsets = this.overlappingTraceSegments.map((_, idx) => {
1247
1039
  const n = Math.floor(idx / 2) + 1;
1248
1040
  const signed = idx % 2 === 0 ? -n : n;
1249
1041
  return signed * this.SHIFT_DISTANCE;
1250
1042
  });
1251
- const eq = (a, b) => Math.abs(a - b) < EPS2;
1043
+ const eq = (a, b) => Math.abs(a - b) < EPS4;
1252
1044
  const samePoint = (p, q) => !!p && !!q && eq(p.x, q.x) && eq(p.y, q.y);
1253
1045
  this.overlappingTraceSegments.forEach((group, gidx) => {
1254
1046
  const offset = offsets[gidx];
@@ -1274,15 +1066,15 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
1274
1066
  segmentIndex: si,
1275
1067
  offset,
1276
1068
  JOG_SIZE,
1277
- EPS: EPS2
1069
+ EPS: EPS4
1278
1070
  });
1279
1071
  } else {
1280
1072
  const start = pts[si];
1281
1073
  const end = pts[si + 1];
1282
- const isVertical = Math.abs(start.x - end.x) < EPS2;
1283
- const isHorizontal = Math.abs(start.y - end.y) < EPS2;
1284
- if (!isVertical && !isHorizontal) continue;
1285
- if (isVertical) {
1074
+ const isVertical2 = Math.abs(start.x - end.x) < EPS4;
1075
+ const isHorizontal2 = Math.abs(start.y - end.y) < EPS4;
1076
+ if (!isVertical2 && !isHorizontal2) continue;
1077
+ if (isVertical2) {
1286
1078
  start.x += offset;
1287
1079
  end.x += offset;
1288
1080
  } else {
@@ -1376,7 +1168,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1376
1168
  return islands;
1377
1169
  }
1378
1170
  findNextOverlapIssue() {
1379
- const EPS2 = 2e-3;
1171
+ const EPS4 = 2e-3;
1380
1172
  const netIds = Object.keys(this.traceNetIslands);
1381
1173
  for (let i = 0; i < netIds.length; i++) {
1382
1174
  for (let j = i + 1; j < netIds.length; j++) {
@@ -1394,7 +1186,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1394
1186
  const minB = Math.min(b1, b2);
1395
1187
  const maxB = Math.max(b1, b2);
1396
1188
  const overlap = Math.min(maxA, maxB) - Math.max(minA, minB);
1397
- return overlap > EPS2;
1189
+ return overlap > EPS4;
1398
1190
  };
1399
1191
  for (let pa = 0; pa < pathsA.length; pa++) {
1400
1192
  const pathA = pathsA[pa];
@@ -1402,8 +1194,8 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1402
1194
  for (let sa = 0; sa < ptsA.length - 1; sa++) {
1403
1195
  const a1 = ptsA[sa];
1404
1196
  const a2 = ptsA[sa + 1];
1405
- const aVert = Math.abs(a1.x - a2.x) < EPS2;
1406
- const aHorz = Math.abs(a1.y - a2.y) < EPS2;
1197
+ const aVert = Math.abs(a1.x - a2.x) < EPS4;
1198
+ const aHorz = Math.abs(a1.y - a2.y) < EPS4;
1407
1199
  if (!aVert && !aHorz) continue;
1408
1200
  for (let pb = 0; pb < pathsB.length; pb++) {
1409
1201
  const pathB = pathsB[pb];
@@ -1411,11 +1203,11 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1411
1203
  for (let sb = 0; sb < ptsB.length - 1; sb++) {
1412
1204
  const b1 = ptsB[sb];
1413
1205
  const b2 = ptsB[sb + 1];
1414
- const bVert = Math.abs(b1.x - b2.x) < EPS2;
1415
- const bHorz = Math.abs(b1.y - b2.y) < EPS2;
1206
+ const bVert = Math.abs(b1.x - b2.x) < EPS4;
1207
+ const bHorz = Math.abs(b1.y - b2.y) < EPS4;
1416
1208
  if (!bVert && !bHorz) continue;
1417
1209
  if (aVert && bVert) {
1418
- if (Math.abs(a1.x - b1.x) < EPS2) {
1210
+ if (Math.abs(a1.x - b1.x) < EPS4) {
1419
1211
  if (overlaps1D(a1.y, a2.y, b1.y, b2.y)) {
1420
1212
  const keyA = `${pa}:${sa}`;
1421
1213
  const keyB = `${pb}:${sb}`;
@@ -1436,7 +1228,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1436
1228
  }
1437
1229
  }
1438
1230
  } else if (aHorz && bHorz) {
1439
- if (Math.abs(a1.y - b1.y) < EPS2) {
1231
+ if (Math.abs(a1.y - b1.y) < EPS4) {
1440
1232
  if (overlaps1D(a1.x, a2.x, b1.x, b2.x)) {
1441
1233
  const keyA = `${pa}:${sa}`;
1442
1234
  const keyB = `${pb}:${sb}`;
@@ -1513,6 +1305,55 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
1513
1305
  }
1514
1306
  };
1515
1307
 
1308
+ // lib/data-structures/ChipObstacleSpatialIndex.ts
1309
+ import Flatbush from "flatbush";
1310
+ var ChipObstacleSpatialIndex = class {
1311
+ chips;
1312
+ spatialIndex;
1313
+ spatialIndexIdToChip;
1314
+ constructor(chips) {
1315
+ this.chips = chips.map((chip) => ({
1316
+ ...chip,
1317
+ bounds: getInputChipBounds(chip),
1318
+ spatialIndexId: null
1319
+ }));
1320
+ this.spatialIndexIdToChip = /* @__PURE__ */ new Map();
1321
+ this.spatialIndex = new Flatbush(chips.length);
1322
+ for (const chip of this.chips) {
1323
+ chip.spatialIndexId = this.spatialIndex.add(
1324
+ chip.bounds.minX,
1325
+ chip.bounds.minY,
1326
+ chip.bounds.maxX,
1327
+ chip.bounds.maxY
1328
+ );
1329
+ this.spatialIndexIdToChip.set(chip.spatialIndexId, chip);
1330
+ }
1331
+ this.spatialIndex.finish();
1332
+ }
1333
+ getChipsInBounds(bounds) {
1334
+ const chipSpatialIndexIds = this.spatialIndex.search(
1335
+ bounds.minX,
1336
+ bounds.minY,
1337
+ bounds.maxX,
1338
+ bounds.maxY
1339
+ );
1340
+ return chipSpatialIndexIds.map((id) => this.spatialIndexIdToChip.get(id));
1341
+ }
1342
+ doesOrthogonalLineIntersectChip(line, opts = {}) {
1343
+ const excludeChipIds = opts.excludeChipIds ?? [];
1344
+ const [p1, p2] = line;
1345
+ const { x: x1, y: y1 } = p1;
1346
+ const { x: x2, y: y2 } = p2;
1347
+ const chips = this.getChipsInBounds({
1348
+ minX: Math.min(x1, x2),
1349
+ minY: Math.min(y1, y2),
1350
+ maxX: Math.max(x1, x2),
1351
+ maxY: Math.max(y1, y2)
1352
+ }).filter((chip) => !excludeChipIds.includes(chip.chipId));
1353
+ return chips.length > 0;
1354
+ }
1355
+ };
1356
+
1516
1357
  // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry.ts
1517
1358
  var NET_LABEL_HORIZONTAL_WIDTH = 0.45;
1518
1359
  var NET_LABEL_HORIZONTAL_HEIGHT = 0.2;
@@ -1550,24 +1391,24 @@ function getRectBounds(center, w, h) {
1550
1391
  }
1551
1392
 
1552
1393
  // lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions.ts
1553
- function segmentIntersectsRect(p1, p2, rect, EPS2 = 1e-9) {
1554
- const isVert = Math.abs(p1.x - p2.x) < EPS2;
1555
- const isHorz = Math.abs(p1.y - p2.y) < EPS2;
1394
+ function segmentIntersectsRect2(p1, p2, rect, EPS4 = 1e-9) {
1395
+ const isVert = Math.abs(p1.x - p2.x) < EPS4;
1396
+ const isHorz = Math.abs(p1.y - p2.y) < EPS4;
1556
1397
  if (!isVert && !isHorz) return false;
1557
1398
  if (isVert) {
1558
1399
  const x = p1.x;
1559
- if (x < rect.minX - EPS2 || x > rect.maxX + EPS2) return false;
1400
+ if (x < rect.minX - EPS4 || x > rect.maxX + EPS4) return false;
1560
1401
  const segMinY = Math.min(p1.y, p2.y);
1561
1402
  const segMaxY = Math.max(p1.y, p2.y);
1562
1403
  const overlap = Math.min(segMaxY, rect.maxY) - Math.max(segMinY, rect.minY);
1563
- return overlap > EPS2;
1404
+ return overlap > EPS4;
1564
1405
  } else {
1565
1406
  const y = p1.y;
1566
- if (y < rect.minY - EPS2 || y > rect.maxY + EPS2) return false;
1407
+ if (y < rect.minY - EPS4 || y > rect.maxY + EPS4) return false;
1567
1408
  const segMinX = Math.min(p1.x, p2.x);
1568
1409
  const segMaxX = Math.max(p1.x, p2.x);
1569
1410
  const overlap = Math.min(segMaxX, rect.maxX) - Math.max(segMinX, rect.minX);
1570
- return overlap > EPS2;
1411
+ return overlap > EPS4;
1571
1412
  }
1572
1413
  }
1573
1414
  function rectIntersectsAnyTrace(bounds, inputTraceMap, hostPathId, hostSegIndex) {
@@ -1575,7 +1416,7 @@ function rectIntersectsAnyTrace(bounds, inputTraceMap, hostPathId, hostSegIndex)
1575
1416
  const pts = solved.tracePath;
1576
1417
  for (let i = 0; i < pts.length - 1; i++) {
1577
1418
  if (pairId === hostPathId && i === hostSegIndex) continue;
1578
- if (segmentIntersectsRect(pts[i], pts[i + 1], bounds))
1419
+ if (segmentIntersectsRect2(pts[i], pts[i + 1], bounds))
1579
1420
  return { hasIntersection: true, mspPairId: pairId, segIndex: i };
1580
1421
  }
1581
1422
  }
@@ -1924,14 +1765,14 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
1924
1765
  };
1925
1766
  let bestCandidate = null;
1926
1767
  let bestScore = -Infinity;
1927
- const EPS2 = 1e-6;
1768
+ const EPS4 = 1e-6;
1928
1769
  for (const curr of tracesToScan) {
1929
1770
  const pts = curr.tracePath.slice();
1930
1771
  for (let si = 0; si < pts.length - 1; si++) {
1931
1772
  const a = pts[si];
1932
1773
  const b = pts[si + 1];
1933
- const isH = Math.abs(a.y - b.y) < EPS2;
1934
- const isV = Math.abs(a.x - b.x) < EPS2;
1774
+ const isH = Math.abs(a.y - b.y) < EPS4;
1775
+ const isV = Math.abs(a.x - b.x) < EPS4;
1935
1776
  if (!isH && !isV) continue;
1936
1777
  const segmentAllowed = isH ? ["y+", "y-"] : ["x+", "x-"];
1937
1778
  const candidateOrients = orientations.filter(
@@ -2086,6 +1927,12 @@ var NetLabelPlacementSolver = class extends BaseSolver {
2086
1927
  const { netConnMap } = getConnectivityMapsFromInputProblem(
2087
1928
  this.inputProblem
2088
1929
  );
1930
+ const pinIdToPinMap = /* @__PURE__ */ new Map();
1931
+ for (const chip of this.inputProblem.chips) {
1932
+ for (const pin of chip.pins) {
1933
+ pinIdToPinMap.set(pin.pinId, pin);
1934
+ }
1935
+ }
2089
1936
  const userNetIdByPinId = {};
2090
1937
  for (const dc of this.inputProblem.directConnections) {
2091
1938
  if (dc.netId) {
@@ -2100,10 +1947,21 @@ var NetLabelPlacementSolver = class extends BaseSolver {
2100
1947
  }
2101
1948
  }
2102
1949
  const groups = [];
2103
- for (const globalConnNetId of Object.keys(netConnMap.netMap)) {
2104
- const pinsInNet = netConnMap.getIdsConnectedToNet(
1950
+ const allPinIds = this.inputProblem.chips.flatMap(
1951
+ (c) => c.pins.map((p) => p.pinId)
1952
+ );
1953
+ const allGlobalConnNetIds = /* @__PURE__ */ new Set();
1954
+ for (const pinId of allPinIds) {
1955
+ const netId = netConnMap.getNetConnectedToId(pinId);
1956
+ if (netId) {
1957
+ allGlobalConnNetIds.add(netId);
1958
+ }
1959
+ }
1960
+ for (const globalConnNetId of allGlobalConnNetIds) {
1961
+ const allIdsInNet = netConnMap.getIdsConnectedToNet(
2105
1962
  globalConnNetId
2106
1963
  );
1964
+ const pinsInNet = allIdsInNet.filter((id) => pinIdToPinMap.has(id));
2107
1965
  const adj = {};
2108
1966
  for (const pid of pinsInNet) adj[pid] = /* @__PURE__ */ new Set();
2109
1967
  for (const t of byGlobal[globalConnNetId] ?? []) {
@@ -2167,12 +2025,13 @@ var NetLabelPlacementSolver = class extends BaseSolver {
2167
2025
  )
2168
2026
  )
2169
2027
  );
2170
- groups.push({
2028
+ const group = {
2171
2029
  globalConnNetId,
2172
2030
  netId: userNetId,
2173
2031
  overlappingTraces: rep,
2174
2032
  mspConnectionPairIds
2175
- });
2033
+ };
2034
+ groups.push(group);
2176
2035
  } else {
2177
2036
  for (const p of component) {
2178
2037
  const userNetId = userNetIdByPinId[p];
@@ -2263,187 +2122,7 @@ var NetLabelPlacementSolver = class extends BaseSolver {
2263
2122
  };
2264
2123
 
2265
2124
  // lib/solvers/GuidelinesSolver/GuidelinesSolver.ts
2266
- import { getBounds as getBounds4 } from "graphics-debug";
2267
-
2268
- // lib/solvers/GuidelinesSolver/getGeneratorForAllChipPairs.ts
2269
- var getGeneratorForAllChipPairs = (chips) => {
2270
- return (function* () {
2271
- for (let i = 0; i < chips.length; i++) {
2272
- for (let j = 0; j < chips.length; j++) {
2273
- if (i !== j) {
2274
- yield [chips[i], chips[j]];
2275
- }
2276
- }
2277
- }
2278
- })();
2279
- };
2280
-
2281
- // lib/solvers/GuidelinesSolver/getHorizontalGuidelineY.ts
2282
- function getHorizontalGuidelineY(chip1Bounds, chip2Bounds) {
2283
- if (chip1Bounds.maxY <= chip2Bounds.minY) {
2284
- return (chip1Bounds.maxY + chip2Bounds.minY) / 2;
2285
- }
2286
- if (chip2Bounds.maxY <= chip1Bounds.minY) {
2287
- return (chip2Bounds.maxY + chip1Bounds.minY) / 2;
2288
- }
2289
- const overlapMinY = Math.max(chip1Bounds.minY, chip2Bounds.minY);
2290
- const overlapMaxY = Math.min(chip1Bounds.maxY, chip2Bounds.maxY);
2291
- return (overlapMinY + overlapMaxY) / 2;
2292
- }
2293
-
2294
- // lib/solvers/GuidelinesSolver/getVerticalGuidelineX.ts
2295
- function getVerticalGuidelineX(chip1Bounds, chip2Bounds) {
2296
- if (chip1Bounds.maxX <= chip2Bounds.minX) {
2297
- return (chip1Bounds.maxX + chip2Bounds.minX) / 2;
2298
- }
2299
- if (chip2Bounds.maxX <= chip1Bounds.minX) {
2300
- return (chip2Bounds.maxX + chip1Bounds.minX) / 2;
2301
- }
2302
- const overlapMinX = Math.max(chip1Bounds.minX, chip2Bounds.minX);
2303
- const overlapMaxX = Math.min(chip1Bounds.maxX, chip2Bounds.maxX);
2304
- return (overlapMinX + overlapMaxX) / 2;
2305
- }
2306
-
2307
- // lib/solvers/GuidelinesSolver/getInputProblemBounds.ts
2308
- var getInputProblemBounds = (inputProblem) => {
2309
- const bounds = {
2310
- minX: Infinity,
2311
- maxX: -Infinity,
2312
- minY: Infinity,
2313
- maxY: -Infinity
2314
- };
2315
- for (const chip of inputProblem.chips) {
2316
- const chipBounds = getInputChipBounds(chip);
2317
- bounds.minX = Math.min(bounds.minX, chipBounds.minX);
2318
- bounds.maxX = Math.max(bounds.maxX, chipBounds.maxX);
2319
- bounds.minY = Math.min(bounds.minY, chipBounds.minY);
2320
- bounds.maxY = Math.max(bounds.maxY, chipBounds.maxY);
2321
- }
2322
- return bounds;
2323
- };
2324
-
2325
- // lib/solvers/GuidelinesSolver/GuidelinesSolver.ts
2326
- var GUIDELINE_PADDING_FROM_PROBLEM_BOUNDS = 0.1;
2327
- var GuidelinesSolver = class extends BaseSolver {
2328
- inputProblem;
2329
- guidelines;
2330
- chipPairsGenerator;
2331
- usedXGuidelines;
2332
- usedYGuidelines;
2333
- constructor(params) {
2334
- super();
2335
- this.inputProblem = params.inputProblem;
2336
- const inputProblemBounds = getInputProblemBounds(this.inputProblem);
2337
- this.guidelines = [
2338
- {
2339
- orientation: "horizontal",
2340
- y: inputProblemBounds.minY - GUIDELINE_PADDING_FROM_PROBLEM_BOUNDS,
2341
- x: void 0
2342
- },
2343
- {
2344
- orientation: "horizontal",
2345
- y: inputProblemBounds.maxY + GUIDELINE_PADDING_FROM_PROBLEM_BOUNDS,
2346
- x: void 0
2347
- },
2348
- {
2349
- orientation: "vertical",
2350
- y: void 0,
2351
- x: inputProblemBounds.minX - GUIDELINE_PADDING_FROM_PROBLEM_BOUNDS
2352
- },
2353
- {
2354
- orientation: "vertical",
2355
- y: void 0,
2356
- x: inputProblemBounds.maxX + GUIDELINE_PADDING_FROM_PROBLEM_BOUNDS
2357
- }
2358
- ];
2359
- this.chipPairsGenerator = getGeneratorForAllChipPairs(
2360
- this.inputProblem.chips
2361
- );
2362
- this.usedXGuidelines = /* @__PURE__ */ new Set();
2363
- this.usedYGuidelines = /* @__PURE__ */ new Set();
2364
- }
2365
- getConstructorParams() {
2366
- return {
2367
- inputProblem: this.inputProblem
2368
- };
2369
- }
2370
- _step() {
2371
- const { done, value: chipPair } = this.chipPairsGenerator.next();
2372
- if (done) {
2373
- this.solved = true;
2374
- return;
2375
- }
2376
- const [chip1, chip2] = chipPair;
2377
- const chip1Bounds = getInputChipBounds(chip1);
2378
- const chip2Bounds = getInputChipBounds(chip2);
2379
- const horizontalGuidelineY = getHorizontalGuidelineY(
2380
- chip1Bounds,
2381
- chip2Bounds
2382
- );
2383
- const verticalGuidelineX = getVerticalGuidelineX(chip1Bounds, chip2Bounds);
2384
- if (!this.usedYGuidelines.has(horizontalGuidelineY)) {
2385
- this.usedYGuidelines.add(horizontalGuidelineY);
2386
- this.guidelines.push({
2387
- orientation: "horizontal",
2388
- y: horizontalGuidelineY,
2389
- x: void 0
2390
- });
2391
- }
2392
- if (!this.usedXGuidelines.has(verticalGuidelineX)) {
2393
- this.usedXGuidelines.add(verticalGuidelineX);
2394
- this.guidelines.push({
2395
- orientation: "vertical",
2396
- y: void 0,
2397
- x: verticalGuidelineX
2398
- });
2399
- }
2400
- }
2401
- visualize() {
2402
- const graphics = visualizeInputProblem(this.inputProblem);
2403
- const bounds = getBounds4(graphics);
2404
- const boundsWidth = bounds.maxX - bounds.minX;
2405
- const boundsHeight = bounds.maxY - bounds.minY;
2406
- bounds.minX -= boundsWidth * 0.3;
2407
- bounds.maxX += boundsWidth * 0.3;
2408
- bounds.minY -= boundsHeight * 0.3;
2409
- bounds.maxY += boundsHeight * 0.3;
2410
- for (const guideline of this.guidelines) {
2411
- if (guideline.orientation === "horizontal") {
2412
- graphics.lines.push({
2413
- points: [
2414
- {
2415
- x: bounds.minX,
2416
- y: guideline.y
2417
- },
2418
- {
2419
- x: bounds.maxX,
2420
- y: guideline.y
2421
- }
2422
- ],
2423
- strokeColor: "rgba(0, 0, 0, 0.5)",
2424
- strokeDash: "2 2"
2425
- });
2426
- }
2427
- if (guideline.orientation === "vertical") {
2428
- graphics.lines.push({
2429
- points: [
2430
- {
2431
- x: guideline.x,
2432
- y: bounds.minY
2433
- },
2434
- {
2435
- x: guideline.x,
2436
- y: bounds.maxY
2437
- }
2438
- ],
2439
- strokeColor: "rgba(0, 0, 0, 0.5)",
2440
- strokeDash: "2 2"
2441
- });
2442
- }
2443
- }
2444
- return graphics;
2445
- }
2446
- };
2125
+ import { getBounds as getBounds3 } from "graphics-debug";
2447
2126
 
2448
2127
  // lib/solvers/SchematicTracePipelineSolver/correctPinsInsideChip.ts
2449
2128
  var correctPinsInsideChips = (problem) => {
@@ -2507,7 +2186,7 @@ function definePipelineStep(solverName, solverClass, getConstructorParams, opts
2507
2186
  }
2508
2187
  var SchematicTracePipelineSolver = class extends BaseSolver {
2509
2188
  mspConnectionPairSolver;
2510
- guidelinesSolver;
2189
+ // guidelinesSolver?: GuidelinesSolver
2511
2190
  schematicTraceLinesSolver;
2512
2191
  traceOverlapShiftSolver;
2513
2192
  netLabelPlacementSolver;
@@ -2526,19 +2205,18 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
2526
2205
  }
2527
2206
  }
2528
2207
  ),
2529
- definePipelineStep(
2530
- "guidelinesSolver",
2531
- GuidelinesSolver,
2532
- () => [
2533
- {
2534
- inputProblem: this.inputProblem
2535
- }
2536
- ],
2537
- {
2538
- onSolved: (guidelinesSolver) => {
2539
- }
2540
- }
2541
- ),
2208
+ // definePipelineStep(
2209
+ // "guidelinesSolver",
2210
+ // GuidelinesSolver,
2211
+ // () => [
2212
+ // {
2213
+ // inputProblem: this.inputProblem,
2214
+ // },
2215
+ // ],
2216
+ // {
2217
+ // onSolved: (guidelinesSolver) => {},
2218
+ // },
2219
+ // ),
2542
2220
  definePipelineStep(
2543
2221
  "schematicTraceLinesSolver",
2544
2222
  SchematicTraceLinesSolver,
@@ -2548,7 +2226,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
2548
2226
  dcConnMap: this.mspConnectionPairSolver.dcConnMap,
2549
2227
  globalConnMap: this.mspConnectionPairSolver.globalConnMap,
2550
2228
  inputProblem: this.inputProblem,
2551
- guidelines: this.guidelinesSolver.guidelines,
2229
+ // guidelines: this.guidelinesSolver!.guidelines,
2552
2230
  chipMap: this.mspConnectionPairSolver.chipMap
2553
2231
  }
2554
2232
  ],
@@ -2695,5 +2373,6 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
2695
2373
  }
2696
2374
  };
2697
2375
  export {
2698
- SchematicTracePipelineSolver
2376
+ SchematicTracePipelineSolver,
2377
+ SchematicTraceSingleLineSolver2
2699
2378
  };