@tscircuit/schematic-trace-solver 0.0.108 → 0.0.109

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