@tscircuit/schematic-trace-solver 0.0.108 → 0.0.110

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 (24) hide show
  1. package/dist/index.d.ts +29 -1
  2. package/dist/index.js +510 -39
  3. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +3 -1
  4. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +18 -3
  5. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/groupLabelsByChipAndOrientation.ts +12 -4
  6. package/lib/solvers/UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver.ts +592 -0
  7. package/package.json +1 -1
  8. package/site/bug-reports/bug-report-20260724T175257Z.page.tsx +4 -0
  9. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +5 -9
  10. package/tests/bug-reports/bug-report-20260724T175257Z/__snapshots__/bug-report-20260724T175257Z.snap.svg +58 -0
  11. package/tests/bug-reports/bug-report-20260724T175257Z/bug-report-20260724T175257Z.json +138 -0
  12. package/tests/bug-reports/bug-report-20260724T175257Z/bug-report-20260724T175257Z.test.ts +12 -0
  13. package/tests/examples/__snapshots__/example04.snap.svg +7 -9
  14. package/tests/examples/__snapshots__/example08.snap.svg +16 -20
  15. package/tests/examples/__snapshots__/example13.snap.svg +2 -4
  16. package/tests/examples/__snapshots__/example32.snap.svg +64 -66
  17. package/tests/examples/__snapshots__/example46.snap.svg +1 -1
  18. package/tests/repros/__snapshots__/repro-netlabel-collision-687.snap.svg +81 -0
  19. package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +16 -18
  20. package/tests/repros/assets/repro-netlabel-collision-687.input.json +212 -0
  21. package/tests/repros/repro-netlabel-collision-687.test.ts +59 -0
  22. package/tests/solvers/UnroutedTraceRecoverySolver/paired-junction-recovery.test.ts +49 -0
  23. package/tests/solvers/UnroutedTraceRecoverySolver/skip-ground.test.ts +30 -0
  24. package/tests/solvers/UnroutedTraceRecoverySolver/skip-long-connection.test.ts +24 -0
package/dist/index.js CHANGED
@@ -421,6 +421,7 @@ function getOrthogonalMinimumSpanningTree(pins, opts = {}) {
421
421
  }
422
422
 
423
423
  // lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts
424
+ var DEFAULT_MAX_MSP_PAIR_DISTANCE = 1;
424
425
  var getPinPairKey = (pinIds) => [...pinIds].sort().join("::");
425
426
  var MspConnectionPairSolver = class extends BaseSolver {
426
427
  inputProblem;
@@ -436,7 +437,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
436
437
  constructor({ inputProblem }) {
437
438
  super();
438
439
  this.inputProblem = inputProblem;
439
- this.maxMspPairDistance = inputProblem.maxMspPairDistance ?? 1;
440
+ this.maxMspPairDistance = inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE;
440
441
  const { directConnMap, netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
441
442
  this.dcConnMap = directConnMap;
442
443
  this.globalConnMap = netConnMap;
@@ -3258,11 +3259,18 @@ var groupLabelsByChipAndOrientation = ({
3258
3259
  chips
3259
3260
  }) => {
3260
3261
  const groupedLabels = {};
3262
+ const chipIdByPinId = new Map(
3263
+ chips.flatMap(
3264
+ (chip) => chip.pins.map((pin) => [pin.pinId, chip.chipId])
3265
+ )
3266
+ );
3261
3267
  for (const label of labels) {
3262
3268
  if (label.pinIds.length === 0) {
3263
3269
  continue;
3264
3270
  }
3265
- const chipId = label.pinIds[0].split(".")[0];
3271
+ const pinId = label.pinIds[0];
3272
+ const legacyChipId = pinId.includes(".") ? pinId.split(".")[0] : void 0;
3273
+ const chipId = legacyChipId ?? chipIdByPinId.get(pinId);
3266
3274
  if (!chipId) {
3267
3275
  continue;
3268
3276
  }
@@ -3893,7 +3901,7 @@ var SingleOverlapSolver = class extends BaseSolver {
3893
3901
  paddingBuffer: effectivePadding
3894
3902
  // Use the calculated, larger padding
3895
3903
  });
3896
- const getPathLength2 = (pts) => {
3904
+ const getPathLength3 = (pts) => {
3897
3905
  let len = 0;
3898
3906
  for (let i = 0; i < pts.length - 1; i++) {
3899
3907
  const dx = pts[i + 1].x - pts[i].x;
@@ -3903,7 +3911,7 @@ var SingleOverlapSolver = class extends BaseSolver {
3903
3911
  return len;
3904
3912
  };
3905
3913
  this.queuedCandidatePaths = candidates.sort(
3906
- (a, b) => getPathLength2(a) - getPathLength2(b)
3914
+ (a, b) => getPathLength3(a) - getPathLength3(b)
3907
3915
  );
3908
3916
  this.obstacles = getObstacleRects(this.problem);
3909
3917
  }
@@ -5343,10 +5351,10 @@ var projectPointToPath = (point, path) => {
5343
5351
  let bestDistance = Number.POSITIVE_INFINITY;
5344
5352
  for (let i = 0; i < path.length - 1; i++) {
5345
5353
  const projectedPoint = projectPointToSegment(point, path[i], path[i + 1]);
5346
- const distance4 = getDistance2(point, projectedPoint);
5347
- if (distance4 < bestDistance) {
5354
+ const distance5 = getDistance2(point, projectedPoint);
5355
+ if (distance5 < bestDistance) {
5348
5356
  bestPoint = projectedPoint;
5349
- bestDistance = distance4;
5357
+ bestDistance = distance5;
5350
5358
  }
5351
5359
  }
5352
5360
  return bestPoint;
@@ -7160,11 +7168,11 @@ var getLabelHugDistance = (tracePath, obstacleLabel) => {
7160
7168
  obstacleLabel.width,
7161
7169
  obstacleLabel.height
7162
7170
  );
7163
- let distance4 = 0;
7171
+ let distance5 = 0;
7164
7172
  for (const point of tracePath) {
7165
- distance4 += getPointDistanceFromRect(point, bounds);
7173
+ distance5 += getPointDistanceFromRect(point, bounds);
7166
7174
  }
7167
- return distance4;
7175
+ return distance5;
7168
7176
  };
7169
7177
  var getPointDistanceFromRect = (point, rect) => {
7170
7178
  const dx = Math.max(rect.minX - point.x, 0, point.x - rect.maxX);
@@ -7441,16 +7449,16 @@ var Example28Solver = class extends BaseSolver {
7441
7449
  const outward = dir(label.orientation);
7442
7450
  if (outward.x === 0 && outward.y === 0) return null;
7443
7451
  for (let step = 1; step <= LABEL_MAX_OUTWARD_STEPS; step++) {
7444
- const distance4 = step * LABEL_OUTWARD_STEP;
7452
+ const distance5 = step * LABEL_OUTWARD_STEP;
7445
7453
  const candidate = {
7446
7454
  ...label,
7447
7455
  anchorPoint: {
7448
- x: label.anchorPoint.x + outward.x * distance4,
7449
- y: label.anchorPoint.y + outward.y * distance4
7456
+ x: label.anchorPoint.x + outward.x * distance5,
7457
+ y: label.anchorPoint.y + outward.y * distance5
7450
7458
  },
7451
7459
  center: {
7452
- x: label.center.x + outward.x * distance4,
7453
- y: label.center.y + outward.y * distance4
7460
+ x: label.center.x + outward.x * distance5,
7461
+ y: label.center.y + outward.y * distance5
7454
7462
  }
7455
7463
  };
7456
7464
  const candidateWithClearance = {
@@ -8024,10 +8032,10 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8024
8032
  phase = "shift",
8025
8033
  stopOnTraceCollision = true
8026
8034
  } = params;
8027
- for (let distance4 = LABEL_SEARCH_STEP; distance4 <= maxSearchDistance + EPS11; distance4 += LABEL_SEARCH_STEP) {
8035
+ for (let distance5 = LABEL_SEARCH_STEP; distance5 <= maxSearchDistance + EPS11; distance5 += LABEL_SEARCH_STEP) {
8028
8036
  const anchorPoint = {
8029
- x: baseAnchor.x + direction.x * distance4,
8030
- y: baseAnchor.y + direction.y * distance4
8037
+ x: baseAnchor.x + direction.x * distance5,
8038
+ y: baseAnchor.y + direction.y * distance5
8031
8039
  };
8032
8040
  const candidate = this.createCandidate(label, anchorPoint, orientation);
8033
8041
  const result = this.evaluateCandidate(
@@ -8035,7 +8043,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8035
8043
  label,
8036
8044
  labelIndex,
8037
8045
  phase,
8038
- distance4,
8046
+ distance5,
8039
8047
  outwardDistance
8040
8048
  );
8041
8049
  this.currentCandidateResults.push(result);
@@ -8047,11 +8055,11 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8047
8055
  }
8048
8056
  return null;
8049
8057
  }
8050
- evaluateCandidate(candidate, label, labelIndex, phase, distance4, outwardDistance) {
8058
+ evaluateCandidate(candidate, label, labelIndex, phase, distance5, outwardDistance) {
8051
8059
  return {
8052
8060
  ...candidate,
8053
8061
  phase,
8054
- distance: distance4,
8062
+ distance: distance5,
8055
8063
  outwardDistance,
8056
8064
  selected: false,
8057
8065
  status: this.getCandidateStatus({
@@ -8389,10 +8397,10 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8389
8397
  if (point.x < bounds.minX - EPS11 || point.x > bounds.maxX + EPS11 || point.y < bounds.minY - EPS11 || point.y > bounds.maxY + EPS11) {
8390
8398
  continue;
8391
8399
  }
8392
- for (const [side, distance4] of getSideDistances(point, bounds)) {
8393
- if (distance4 < nearestDistance) {
8400
+ for (const [side, distance5] of getSideDistances(point, bounds)) {
8401
+ if (distance5 < nearestDistance) {
8394
8402
  nearestSide = side;
8395
- nearestDistance = distance4;
8403
+ nearestDistance = distance5;
8396
8404
  }
8397
8405
  }
8398
8406
  }
@@ -8421,10 +8429,10 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8421
8429
  let nearestSide = null;
8422
8430
  let nearestDistance = Number.POSITIVE_INFINITY;
8423
8431
  for (const chip of this.chipObstacleSpatialIndex.chips) {
8424
- for (const [side, distance4] of getSideDistances(point, chip.bounds)) {
8425
- if (distance4 < nearestDistance) {
8432
+ for (const [side, distance5] of getSideDistances(point, chip.bounds)) {
8433
+ if (distance5 < nearestDistance) {
8426
8434
  nearestSide = side;
8427
- nearestDistance = distance4;
8435
+ nearestDistance = distance5;
8428
8436
  }
8429
8437
  }
8430
8438
  }
@@ -8825,17 +8833,17 @@ var getTraceLength = (trace) => {
8825
8833
  }
8826
8834
  return length;
8827
8835
  };
8828
- var getPointAtTraceDistance = (trace, distance4) => {
8836
+ var getPointAtTraceDistance = (trace, distance5) => {
8829
8837
  let pathDistance = 0;
8830
8838
  for (let i = 0; i < trace.tracePath.length - 1; i++) {
8831
8839
  const start = trace.tracePath[i];
8832
8840
  const end = trace.tracePath[i + 1];
8833
8841
  const segmentLength = getManhattanDistance(start, end);
8834
8842
  const nextDistance = pathDistance + segmentLength;
8835
- if (distance4 <= nextDistance + EPS12) {
8843
+ if (distance5 <= nextDistance + EPS12) {
8836
8844
  const offset = Math.max(
8837
8845
  0,
8838
- Math.min(segmentLength, distance4 - pathDistance)
8846
+ Math.min(segmentLength, distance5 - pathDistance)
8839
8847
  );
8840
8848
  const direction = getSegmentDirection(start, end);
8841
8849
  return {
@@ -8960,13 +8968,13 @@ var getCandidateDistances = (traceLength, vertexDistances) => {
8960
8968
  const distances = /* @__PURE__ */ new Set();
8961
8969
  const maxSteps = Math.ceil(traceLength / CANDIDATE_STEP);
8962
8970
  for (let i = 0; i <= maxSteps; i++) {
8963
- const distance4 = Math.min(traceLength, i * CANDIDATE_STEP);
8964
- distances.add(roundDistance(distance4));
8971
+ const distance5 = Math.min(traceLength, i * CANDIDATE_STEP);
8972
+ distances.add(roundDistance(distance5));
8965
8973
  }
8966
- for (const distance4 of vertexDistances) {
8967
- distances.add(roundDistance(distance4));
8974
+ for (const distance5 of vertexDistances) {
8975
+ distances.add(roundDistance(distance5));
8968
8976
  }
8969
- return [...distances].filter((distance4) => distance4 >= -EPS12 && distance4 <= traceLength + EPS12).sort((a, b) => a - b);
8977
+ return [...distances].filter((distance5) => distance5 >= -EPS12 && distance5 <= traceLength + EPS12).sort((a, b) => a - b);
8970
8978
  };
8971
8979
  var getOrientationsForPoint = (params) => {
8972
8980
  const { inputProblem, label, point, orientationConstraint } = params;
@@ -9144,7 +9152,7 @@ var getNetLabelHeight = (inputProblem, label) => {
9144
9152
  (nc) => nc.pinIds.some((pid) => label.pinIds.includes(pid))
9145
9153
  )?.netLabelHeight;
9146
9154
  };
9147
- var roundDistance = (distance4) => Number(distance4.toFixed(6));
9155
+ var roundDistance = (distance5) => Number(distance5.toFixed(6));
9148
9156
  var isSamePlacement = (label, point, orientation) => Math.abs(point.x - label.anchorPoint.x) <= EPS12 && Math.abs(point.y - label.anchorPoint.y) <= EPS12 && orientation === label.orientation;
9149
9157
 
9150
9158
  // lib/solvers/TraceAnchoredNetLabelOverlapSolver/visualize.ts
@@ -10110,6 +10118,457 @@ ${c.status}`
10110
10118
  }
10111
10119
  };
10112
10120
 
10121
+ // lib/solvers/UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver.ts
10122
+ import { distance as distance4, doSegmentsIntersect as doSegmentsIntersect3 } from "@tscircuit/math-utils";
10123
+ var ROUTE_CLEARANCE = 0.2;
10124
+ var COORDINATE_TOLERANCE = 1e-9;
10125
+ var GROUND_NET_ID = "GND";
10126
+ var pointsAreEqual = (firstPoint, secondPoint) => {
10127
+ return Math.abs(firstPoint.x - secondPoint.x) <= COORDINATE_TOLERANCE && Math.abs(firstPoint.y - secondPoint.y) <= COORDINATE_TOLERANCE;
10128
+ };
10129
+ var removeConsecutiveDuplicatePoints = (path) => {
10130
+ const filteredPath = [];
10131
+ for (const point of path) {
10132
+ const previousPoint = filteredPath.at(-1);
10133
+ if (!previousPoint || !pointsAreEqual(previousPoint, point)) {
10134
+ filteredPath.push(point);
10135
+ }
10136
+ }
10137
+ return filteredPath;
10138
+ };
10139
+ var getPathLength2 = (path) => {
10140
+ let pathLength = 0;
10141
+ for (let pointIndex = 0; pointIndex < path.length - 1; pointIndex++) {
10142
+ const startPoint = path[pointIndex];
10143
+ const endPoint = path[pointIndex + 1];
10144
+ pathLength += Math.abs(endPoint.x - startPoint.x) + Math.abs(endPoint.y - startPoint.y);
10145
+ }
10146
+ return pathLength;
10147
+ };
10148
+ var getEscapePoint = ({
10149
+ pin,
10150
+ facingDirection
10151
+ }) => {
10152
+ const escapePoint = { x: pin.x, y: pin.y };
10153
+ if (facingDirection === "x+") {
10154
+ escapePoint.x += ROUTE_CLEARANCE;
10155
+ }
10156
+ if (facingDirection === "x-") {
10157
+ escapePoint.x -= ROUTE_CLEARANCE;
10158
+ }
10159
+ if (facingDirection === "y+") {
10160
+ escapePoint.y += ROUTE_CLEARANCE;
10161
+ }
10162
+ if (facingDirection === "y-") {
10163
+ escapePoint.y -= ROUTE_CLEARANCE;
10164
+ }
10165
+ return escapePoint;
10166
+ };
10167
+ var getOuterBounds = (obstacles) => {
10168
+ return {
10169
+ minX: Math.min(...obstacles.map((obstacle) => obstacle.minX)),
10170
+ minY: Math.min(...obstacles.map((obstacle) => obstacle.minY)),
10171
+ maxX: Math.max(...obstacles.map((obstacle) => obstacle.maxX)),
10172
+ maxY: Math.max(...obstacles.map((obstacle) => obstacle.maxY))
10173
+ };
10174
+ };
10175
+ var getPerimeterCandidates = ({
10176
+ connectionPair,
10177
+ obstacles
10178
+ }) => {
10179
+ const [firstPin, secondPin] = connectionPair.pins;
10180
+ const firstEscapePoint = getEscapePoint({
10181
+ pin: firstPin,
10182
+ facingDirection: firstPin._facingDirection
10183
+ });
10184
+ const secondEscapePoint = getEscapePoint({
10185
+ pin: secondPin,
10186
+ facingDirection: secondPin._facingDirection
10187
+ });
10188
+ const outerBounds = getOuterBounds(obstacles);
10189
+ const horizontalChannels = [
10190
+ outerBounds.minY - ROUTE_CLEARANCE,
10191
+ outerBounds.maxY + ROUTE_CLEARANCE
10192
+ ];
10193
+ const verticalChannels = [
10194
+ outerBounds.minX - ROUTE_CLEARANCE,
10195
+ outerBounds.maxX + ROUTE_CLEARANCE
10196
+ ];
10197
+ const candidates = [];
10198
+ for (const channelY of horizontalChannels) {
10199
+ candidates.push(
10200
+ removeConsecutiveDuplicatePoints([
10201
+ firstPin,
10202
+ firstEscapePoint,
10203
+ { x: firstEscapePoint.x, y: channelY },
10204
+ { x: secondEscapePoint.x, y: channelY },
10205
+ secondEscapePoint,
10206
+ secondPin
10207
+ ])
10208
+ );
10209
+ }
10210
+ for (const channelX of verticalChannels) {
10211
+ candidates.push(
10212
+ removeConsecutiveDuplicatePoints([
10213
+ firstPin,
10214
+ firstEscapePoint,
10215
+ { x: channelX, y: firstEscapePoint.y },
10216
+ { x: channelX, y: secondEscapePoint.y },
10217
+ secondEscapePoint,
10218
+ secondPin
10219
+ ])
10220
+ );
10221
+ }
10222
+ return candidates.sort(
10223
+ (firstPath, secondPath) => getPathLength2(firstPath) - getPathLength2(secondPath)
10224
+ );
10225
+ };
10226
+ var getSegmentMidpoint = (startPoint, endPoint) => {
10227
+ return {
10228
+ x: (startPoint.x + endPoint.x) / 2,
10229
+ y: (startPoint.y + endPoint.y) / 2
10230
+ };
10231
+ };
10232
+ var getJunctionPoints = (sameNetTraces) => {
10233
+ const junctionPoints = [];
10234
+ for (const trace of sameNetTraces) {
10235
+ for (let pointIndex = 0; pointIndex < trace.tracePath.length - 1; pointIndex++) {
10236
+ const startPoint = trace.tracePath[pointIndex];
10237
+ const endPoint = trace.tracePath[pointIndex + 1];
10238
+ junctionPoints.push(startPoint);
10239
+ junctionPoints.push(getSegmentMidpoint(startPoint, endPoint));
10240
+ }
10241
+ const lastPoint = trace.tracePath.at(-1);
10242
+ if (lastPoint) {
10243
+ junctionPoints.push(lastPoint);
10244
+ }
10245
+ }
10246
+ return junctionPoints;
10247
+ };
10248
+ var getUnconnectedPins = ({
10249
+ connectionPair,
10250
+ sameNetTraces
10251
+ }) => {
10252
+ const connectedPinIds = new Set(
10253
+ sameNetTraces.flatMap((trace) => trace.pinIds)
10254
+ );
10255
+ return connectionPair.pins.filter((pin) => !connectedPinIds.has(pin.pinId));
10256
+ };
10257
+ var getJunctionCandidates = ({
10258
+ connectionPair,
10259
+ sameNetTraces,
10260
+ obstacles,
10261
+ maxConnectionDistance
10262
+ }) => {
10263
+ const outerBounds = getOuterBounds(obstacles);
10264
+ const horizontalChannels = [
10265
+ outerBounds.minY - ROUTE_CLEARANCE,
10266
+ outerBounds.maxY + ROUTE_CLEARANCE
10267
+ ];
10268
+ const verticalChannels = [
10269
+ outerBounds.minX - ROUTE_CLEARANCE,
10270
+ outerBounds.maxX + ROUTE_CLEARANCE
10271
+ ];
10272
+ const candidates = [];
10273
+ const junctionPoints = getJunctionPoints(sameNetTraces);
10274
+ const unconnectedPins = getUnconnectedPins({
10275
+ connectionPair,
10276
+ sameNetTraces
10277
+ });
10278
+ for (const pin of unconnectedPins) {
10279
+ const escapePoint = getEscapePoint({
10280
+ pin,
10281
+ facingDirection: pin._facingDirection
10282
+ });
10283
+ for (const junctionPoint of junctionPoints) {
10284
+ if (distance4(pin, junctionPoint) > maxConnectionDistance) {
10285
+ continue;
10286
+ }
10287
+ candidates.push(
10288
+ removeConsecutiveDuplicatePoints([
10289
+ pin,
10290
+ escapePoint,
10291
+ { x: escapePoint.x, y: junctionPoint.y },
10292
+ junctionPoint
10293
+ ])
10294
+ );
10295
+ candidates.push(
10296
+ removeConsecutiveDuplicatePoints([
10297
+ pin,
10298
+ escapePoint,
10299
+ { x: junctionPoint.x, y: escapePoint.y },
10300
+ junctionPoint
10301
+ ])
10302
+ );
10303
+ for (const channelY of horizontalChannels) {
10304
+ candidates.push(
10305
+ removeConsecutiveDuplicatePoints([
10306
+ pin,
10307
+ escapePoint,
10308
+ { x: escapePoint.x, y: channelY },
10309
+ { x: junctionPoint.x, y: channelY },
10310
+ junctionPoint
10311
+ ])
10312
+ );
10313
+ }
10314
+ for (const channelX of verticalChannels) {
10315
+ candidates.push(
10316
+ removeConsecutiveDuplicatePoints([
10317
+ pin,
10318
+ escapePoint,
10319
+ { x: channelX, y: escapePoint.y },
10320
+ { x: channelX, y: junctionPoint.y },
10321
+ junctionPoint
10322
+ ])
10323
+ );
10324
+ }
10325
+ }
10326
+ }
10327
+ return candidates.sort(
10328
+ (firstPath, secondPath) => getPathLength2(firstPath) - getPathLength2(secondPath)
10329
+ );
10330
+ };
10331
+ var pathCollidesWithObstacles = ({
10332
+ path,
10333
+ obstacles,
10334
+ connectionPair,
10335
+ rejectComponentBoundaryTravel
10336
+ }) => {
10337
+ const firstPathPoint = path[0];
10338
+ const lastPathPoint = path.at(-1);
10339
+ const firstPathPin = connectionPair.pins.find(
10340
+ (pin) => pointsAreEqual(pin, firstPathPoint)
10341
+ );
10342
+ const lastPathPin = connectionPair.pins.find(
10343
+ (pin) => pointsAreEqual(pin, lastPathPoint)
10344
+ );
10345
+ const pathConnectsPairPins = firstPathPin !== void 0 && lastPathPin !== void 0;
10346
+ const firstChipObstacle = obstacles.find(
10347
+ (obstacle) => obstacle.kind === "chip" && obstacle.chipId === firstPathPin?.chipId
10348
+ );
10349
+ const secondChipObstacle = obstacles.find(
10350
+ (obstacle) => obstacle.kind === "chip" && obstacle.chipId === lastPathPin?.chipId
10351
+ );
10352
+ if (rejectComponentBoundaryTravel && firstChipObstacle && segmentOverlapsRectBoundary(path[0], path[1], firstChipObstacle)) {
10353
+ return true;
10354
+ }
10355
+ if (rejectComponentBoundaryTravel && secondChipObstacle && segmentOverlapsRectBoundary(
10356
+ path[path.length - 2],
10357
+ path[path.length - 1],
10358
+ secondChipObstacle
10359
+ )) {
10360
+ return true;
10361
+ }
10362
+ const collision = findFirstCollision(path, obstacles, {
10363
+ excludeRectsForSegment: (segmentIndex) => {
10364
+ const excludedObstacles = /* @__PURE__ */ new Set();
10365
+ if (segmentIndex === 0 && firstChipObstacle) {
10366
+ excludedObstacles.add(firstChipObstacle);
10367
+ }
10368
+ if (pathConnectsPairPins && segmentIndex === path.length - 2 && secondChipObstacle) {
10369
+ excludedObstacles.add(secondChipObstacle);
10370
+ }
10371
+ return excludedObstacles;
10372
+ }
10373
+ });
10374
+ return collision !== null;
10375
+ };
10376
+ var hasParallelFailedConnection = ({
10377
+ connectionPair,
10378
+ failedConnectionPairs
10379
+ }) => {
10380
+ const connectionChipIds = new Set(
10381
+ connectionPair.pins.map((pin) => pin.chipId)
10382
+ );
10383
+ return failedConnectionPairs.some((otherConnectionPair) => {
10384
+ if (otherConnectionPair.mspPairId === connectionPair.mspPairId) {
10385
+ return false;
10386
+ }
10387
+ return otherConnectionPair.pins.every(
10388
+ (pin) => connectionChipIds.has(pin.chipId)
10389
+ );
10390
+ });
10391
+ };
10392
+ var segmentsOverlapBeyondEndpoint = ({
10393
+ firstStart,
10394
+ firstEnd,
10395
+ secondStart,
10396
+ secondEnd
10397
+ }) => {
10398
+ if (isHorizontal(firstStart, firstEnd) && isHorizontal(secondStart, secondEnd)) {
10399
+ const overlapLength = Math.min(
10400
+ Math.max(firstStart.x, firstEnd.x),
10401
+ Math.max(secondStart.x, secondEnd.x)
10402
+ ) - Math.max(
10403
+ Math.min(firstStart.x, firstEnd.x),
10404
+ Math.min(secondStart.x, secondEnd.x)
10405
+ );
10406
+ return overlapLength > COORDINATE_TOLERANCE;
10407
+ }
10408
+ if (isVertical(firstStart, firstEnd) && isVertical(secondStart, secondEnd)) {
10409
+ const overlapLength = Math.min(
10410
+ Math.max(firstStart.y, firstEnd.y),
10411
+ Math.max(secondStart.y, secondEnd.y)
10412
+ ) - Math.max(
10413
+ Math.min(firstStart.y, firstEnd.y),
10414
+ Math.min(secondStart.y, secondEnd.y)
10415
+ );
10416
+ return overlapLength > COORDINATE_TOLERANCE;
10417
+ }
10418
+ return false;
10419
+ };
10420
+ var getAllowedJunctionPoints = ({
10421
+ connectionPair,
10422
+ existingTrace
10423
+ }) => {
10424
+ const existingPinIds = new Set(existingTrace.pinIds);
10425
+ return connectionPair.pins.filter((pin) => existingPinIds.has(pin.pinId));
10426
+ };
10427
+ var pathCrossesExistingTraces = ({
10428
+ path,
10429
+ connectionPair,
10430
+ existingTraces
10431
+ }) => {
10432
+ for (const existingTrace of existingTraces) {
10433
+ if (existingTrace.globalConnNetId === connectionPair.globalConnNetId) {
10434
+ continue;
10435
+ }
10436
+ const allowedJunctionPoints = getAllowedJunctionPoints({
10437
+ connectionPair,
10438
+ existingTrace
10439
+ });
10440
+ for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) {
10441
+ const pathStart = path[pathIndex];
10442
+ const pathEnd = path[pathIndex + 1];
10443
+ for (let traceIndex = 0; traceIndex < existingTrace.tracePath.length - 1; traceIndex++) {
10444
+ const traceStart = existingTrace.tracePath[traceIndex];
10445
+ const traceEnd = existingTrace.tracePath[traceIndex + 1];
10446
+ if (!doSegmentsIntersect3(pathStart, pathEnd, traceStart, traceEnd)) {
10447
+ continue;
10448
+ }
10449
+ const intersectionIsAllowedJunction = allowedJunctionPoints.some(
10450
+ (junctionPoint) => (pointsAreEqual(pathStart, junctionPoint) || pointsAreEqual(pathEnd, junctionPoint)) && (pointsAreEqual(traceStart, junctionPoint) || pointsAreEqual(traceEnd, junctionPoint))
10451
+ );
10452
+ if (intersectionIsAllowedJunction && !segmentsOverlapBeyondEndpoint({
10453
+ firstStart: pathStart,
10454
+ firstEnd: pathEnd,
10455
+ secondStart: traceStart,
10456
+ secondEnd: traceEnd
10457
+ })) {
10458
+ continue;
10459
+ }
10460
+ return true;
10461
+ }
10462
+ }
10463
+ }
10464
+ return false;
10465
+ };
10466
+ var UnroutedTraceRecoverySolver = class extends BaseSolver {
10467
+ constructor(params) {
10468
+ super();
10469
+ this.params = params;
10470
+ this.inputProblem = params.inputProblem;
10471
+ this.alreadySolvedTraces = params.alreadySolvedTraces;
10472
+ this.failedConnectionPairs = params.failedConnectionPairs;
10473
+ this.queuedConnectionPairs = [...params.failedConnectionPairs];
10474
+ this.maxConnectionDistance = this.inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE;
10475
+ const { netConnMap } = getConnectivityMapsFromInputProblem(
10476
+ this.inputProblem
10477
+ );
10478
+ this.groundGlobalConnNetId = netConnMap.getNetConnectedToId(GROUND_NET_ID) ?? void 0;
10479
+ }
10480
+ params;
10481
+ inputProblem;
10482
+ alreadySolvedTraces;
10483
+ failedConnectionPairs;
10484
+ queuedConnectionPairs;
10485
+ maxConnectionDistance;
10486
+ groundGlobalConnNetId;
10487
+ solvedUnroutedTraces = [];
10488
+ getConstructorParams() {
10489
+ return this.params;
10490
+ }
10491
+ _step() {
10492
+ const connectionPair = this.queuedConnectionPairs.shift();
10493
+ if (!connectionPair) {
10494
+ this.solved = true;
10495
+ return;
10496
+ }
10497
+ if (connectionPair.globalConnNetId === this.groundGlobalConnNetId) {
10498
+ return;
10499
+ }
10500
+ if (distance4(connectionPair.pins[0], connectionPair.pins[1]) > this.maxConnectionDistance) {
10501
+ return;
10502
+ }
10503
+ const obstacles = getObstacleRects(this.inputProblem);
10504
+ const existingTraces = [
10505
+ ...this.alreadySolvedTraces,
10506
+ ...this.solvedUnroutedTraces
10507
+ ];
10508
+ const sameNetTraces = existingTraces.filter(
10509
+ (trace) => trace.globalConnNetId === connectionPair.globalConnNetId
10510
+ );
10511
+ const junctionCandidates = getJunctionCandidates({
10512
+ connectionPair,
10513
+ sameNetTraces,
10514
+ obstacles,
10515
+ maxConnectionDistance: this.maxConnectionDistance
10516
+ });
10517
+ const perimeterCandidates = getPerimeterCandidates({
10518
+ connectionPair,
10519
+ obstacles
10520
+ });
10521
+ const candidates = [...junctionCandidates, ...perimeterCandidates];
10522
+ const rejectComponentBoundaryTravel = hasParallelFailedConnection({
10523
+ connectionPair,
10524
+ failedConnectionPairs: this.failedConnectionPairs
10525
+ });
10526
+ for (const tracePath of candidates) {
10527
+ if (pathCollidesWithObstacles({
10528
+ path: tracePath,
10529
+ obstacles,
10530
+ connectionPair,
10531
+ rejectComponentBoundaryTravel
10532
+ })) {
10533
+ continue;
10534
+ }
10535
+ if (pathCrossesExistingTraces({
10536
+ path: tracePath,
10537
+ connectionPair,
10538
+ existingTraces
10539
+ })) {
10540
+ continue;
10541
+ }
10542
+ this.solvedUnroutedTraces.push({
10543
+ ...connectionPair,
10544
+ tracePath,
10545
+ mspConnectionPairIds: [connectionPair.mspPairId],
10546
+ pinIds: connectionPair.pins.map((pin) => pin.pinId)
10547
+ });
10548
+ return;
10549
+ }
10550
+ }
10551
+ getOutput() {
10552
+ return {
10553
+ newTraces: this.solvedUnroutedTraces,
10554
+ allTracesMerged: [
10555
+ ...this.alreadySolvedTraces,
10556
+ ...this.solvedUnroutedTraces
10557
+ ]
10558
+ };
10559
+ }
10560
+ visualize() {
10561
+ const graphics = visualizeInputProblem(this.inputProblem);
10562
+ for (const trace of this.solvedUnroutedTraces) {
10563
+ graphics.lines.push({
10564
+ points: trace.tracePath,
10565
+ strokeColor: "blue"
10566
+ });
10567
+ }
10568
+ return graphics;
10569
+ }
10570
+ };
10571
+
10113
10572
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
10114
10573
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
10115
10574
  return {
@@ -10125,6 +10584,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
10125
10584
  // guidelinesSolver?: GuidelinesSolver
10126
10585
  schematicTraceLinesSolver;
10127
10586
  longDistancePairSolver;
10587
+ unroutedTraceRecoverySolver;
10128
10588
  traceOverlapShiftSolver;
10129
10589
  netLabelPlacementSolver;
10130
10590
  labelMergingSolver;
@@ -10195,13 +10655,24 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
10195
10655
  }
10196
10656
  }
10197
10657
  ),
10658
+ definePipelineStep(
10659
+ "unroutedTraceRecoverySolver",
10660
+ UnroutedTraceRecoverySolver,
10661
+ (instance) => [
10662
+ {
10663
+ inputProblem: instance.inputProblem,
10664
+ failedConnectionPairs: instance.schematicTraceLinesSolver.failedConnectionPairs,
10665
+ alreadySolvedTraces: instance.longDistancePairSolver.getOutput().allTracesMerged
10666
+ }
10667
+ ]
10668
+ ),
10198
10669
  definePipelineStep(
10199
10670
  "traceOverlapShiftSolver",
10200
10671
  TraceOverlapShiftSolver,
10201
10672
  () => [
10202
10673
  {
10203
10674
  inputProblem: this.inputProblem,
10204
- inputTracePaths: this.longDistancePairSolver?.getOutput().allTracesMerged,
10675
+ inputTracePaths: this.unroutedTraceRecoverySolver?.getOutput().allTracesMerged,
10205
10676
  globalConnMap: this.mspConnectionPairSolver.globalConnMap
10206
10677
  }
10207
10678
  ],
@@ -10217,7 +10688,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
10217
10688
  {
10218
10689
  inputProblem: this.inputProblem,
10219
10690
  inputTraceMap: this.traceOverlapShiftSolver?.correctedTraceMap ?? Object.fromEntries(
10220
- this.longDistancePairSolver.getOutput().allTracesMerged.map(
10691
+ this.unroutedTraceRecoverySolver.getOutput().allTracesMerged.map(
10221
10692
  (p) => [p.mspPairId, p]
10222
10693
  )
10223
10694
  )
@@ -10233,7 +10704,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
10233
10704
  TraceLabelOverlapAvoidanceSolver,
10234
10705
  (instance) => {
10235
10706
  const traceMap = instance.traceOverlapShiftSolver?.correctedTraceMap ?? Object.fromEntries(
10236
- instance.longDistancePairSolver.getOutput().allTracesMerged.map((p) => [p.mspPairId, p])
10707
+ instance.unroutedTraceRecoverySolver.getOutput().allTracesMerged.map((p) => [p.mspPairId, p])
10237
10708
  );
10238
10709
  const traces = Object.values(traceMap);
10239
10710
  const netLabelPlacements = instance.netLabelPlacementSolver.netLabelPlacements;