@tscircuit/schematic-trace-solver 0.0.181 → 0.0.183

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 (19) hide show
  1. package/dist/index.d.ts +5 -0
  2. package/dist/index.js +318 -113
  3. package/lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts +10 -0
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts +45 -2
  5. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection.ts +58 -10
  6. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +166 -57
  7. package/lib/solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts +108 -3
  8. package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +62 -15
  9. package/lib/solvers/TraceCleanupSolver/turnMinimization.ts +5 -0
  10. package/package.json +1 -1
  11. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +13 -20
  12. package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +10 -10
  13. package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +13 -13
  14. package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +181 -181
  15. package/tests/repros/__snapshots__/repro-mcp73831-charger-traces.snap.svg +10 -10
  16. package/tests/repros/__snapshots__/repro-mouse-switch-ground-unnecessary-jog.snap.svg +2 -2
  17. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +8 -8
  18. package/tests/repros/__snapshots__/repro-rf1-unnecessary-dogleg.snap.svg +5 -5
  19. package/tests/solvers/InlineNetLabelSolver/push-anchored-net-labels-away.test.ts +62 -0
package/dist/index.js CHANGED
@@ -111,7 +111,7 @@ var arePinsInDifferentSchematicSections = (inputProblem, p1, p2) => {
111
111
  };
112
112
 
113
113
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection.ts
114
- var getPinDirection = (pin, chip) => {
114
+ var getPinDirectionCandidates = (pin, chip, connectedPin) => {
115
115
  const { x, y } = pin;
116
116
  const { center, width, height } = chip;
117
117
  const yPlusEdge = center.y + height / 2;
@@ -128,17 +128,34 @@ var getPinDirection = (pin, chip) => {
128
128
  xPlusDistance,
129
129
  xMinusDistance
130
130
  );
131
- if (minDistance === yPlusDistance) {
132
- return "y+";
133
- }
134
- if (minDistance === yMinusDistance) {
135
- return "y-";
131
+ const primaryDirection = minDistance === yPlusDistance ? "y+" : minDistance === yMinusDistance ? "y-" : minDistance === xPlusDistance ? "x+" : "x-";
132
+ const matchingDirections = [
133
+ { direction: "y+", distance: yPlusDistance },
134
+ { direction: "y-", distance: yMinusDistance },
135
+ { direction: "x+", distance: xPlusDistance },
136
+ { direction: "x-", distance: xMinusDistance }
137
+ ].filter(({ distance: distance7 }) => Math.abs(distance7 - minDistance) <= 1e-9).map(({ direction }) => direction);
138
+ const closestDirections = [
139
+ primaryDirection,
140
+ ...matchingDirections.filter((direction) => direction !== primaryDirection)
141
+ ];
142
+ if (!connectedPin || closestDirections.length <= 1) {
143
+ return closestDirections;
136
144
  }
137
- if (minDistance === xPlusDistance) {
138
- return "x+";
145
+ const xDistance = connectedPin.x - pin.x;
146
+ const yDistance = connectedPin.y - pin.y;
147
+ const directionTowardConnectedPin = Math.abs(xDistance) >= Math.abs(yDistance) ? xDistance >= 0 ? "x+" : "x-" : yDistance >= 0 ? "y+" : "y-";
148
+ if (!closestDirections.includes(directionTowardConnectedPin)) {
149
+ return closestDirections;
139
150
  }
140
- return "x-";
151
+ return [
152
+ directionTowardConnectedPin,
153
+ ...closestDirections.filter(
154
+ (direction) => direction !== directionTowardConnectedPin
155
+ )
156
+ ];
141
157
  };
158
+ var getPinDirection = (pin, chip, connectedPin) => getPinDirectionCandidates(pin, chip, connectedPin)[0];
142
159
 
143
160
  // lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts
144
161
  var visualizeInputProblem = (inputProblem, opts = {}) => {
@@ -1353,6 +1370,38 @@ var pinsFaceEachOther = ({
1353
1370
  }
1354
1371
  return yDistance > 0 ? pin1._facingDirection === "y+" && pin2._facingDirection === "y-" : pin1._facingDirection === "y-" && pin2._facingDirection === "y+";
1355
1372
  };
1373
+ var getPathLength = (points) => {
1374
+ let length = 0;
1375
+ for (let i = 0; i < points.length - 1; i++) {
1376
+ length += Math.abs(points[i + 1].x - points[i].x) + Math.abs(points[i + 1].y - points[i].y);
1377
+ }
1378
+ return length;
1379
+ };
1380
+ var getInitialPathForPins = ({
1381
+ pins,
1382
+ obstacles
1383
+ }) => {
1384
+ const [pin1, pin2] = pins;
1385
+ const directShortPath = calculateDirectShortPath(pin1, pin2);
1386
+ const defaultElbow = calculateElbowForPins({
1387
+ pin1,
1388
+ pin2,
1389
+ overshoot: 0.2
1390
+ });
1391
+ const routingDistance = Math.abs(pin1.x - pin2.x) + Math.abs(pin1.y - pin2.y);
1392
+ const adaptiveElbow = calculateElbowForPins({
1393
+ pin1,
1394
+ pin2,
1395
+ overshoot: Math.min(0.2, Math.max(0.02, routingDistance / 4))
1396
+ });
1397
+ const adaptiveElbowIsShorter = getPathLength(adaptiveElbow) < getPathLength(defaultElbow);
1398
+ const defaultElbowBacktracks = getPathLength(defaultElbow) > routingDistance + 1e-9;
1399
+ const shouldUseAdaptiveElbow = findFirstCollision(adaptiveElbow, obstacles) === null && (pinsFaceEachOther({ pin1, pin2 }) && defaultElbowBacktracks && adaptiveElbowIsShorter || findFirstCollision(defaultElbow, obstacles) !== null);
1400
+ return {
1401
+ path: directShortPath ?? (shouldUseAdaptiveElbow ? adaptiveElbow : defaultElbow),
1402
+ isDirectShortPath: directShortPath !== null
1403
+ };
1404
+ };
1356
1405
  var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1357
1406
  pins;
1358
1407
  connectionPair;
@@ -1364,9 +1413,12 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1364
1413
  aabb;
1365
1414
  baseElbow;
1366
1415
  preferExteriorDetours;
1416
+ reserveNetLabelClearance;
1367
1417
  solvedTracePath = null;
1368
1418
  queue = [];
1369
1419
  visited = /* @__PURE__ */ new Set();
1420
+ inferredPinIndexes = /* @__PURE__ */ new Set();
1421
+ hasAmbiguousPinDirections = false;
1370
1422
  constructor(params) {
1371
1423
  super();
1372
1424
  this.pins = params.pins;
@@ -1374,14 +1426,9 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1374
1426
  this.inputProblem = params.inputProblem;
1375
1427
  this.chipMap = params.chipMap;
1376
1428
  this.preferExteriorDetours = params.preferExteriorDetours ?? true;
1377
- for (const pin of this.pins) {
1378
- if (!pin._facingDirection) {
1379
- const chip = this.chipMap[pin.chipId];
1380
- pin._facingDirection = getPinDirection(pin, chip);
1381
- }
1382
- }
1429
+ this.reserveNetLabelClearance = params.reserveNetLabelClearance ?? true;
1383
1430
  this.obstacles = getObstacleRects(this.inputProblem, {
1384
- textBoxPadding: this.getTextBoxPaddingForConnectionPair()
1431
+ textBoxPadding: this.reserveNetLabelClearance ? this.getTextBoxPaddingForConnectionPair() : void 0
1385
1432
  });
1386
1433
  this.textObstacles = new Set(this.obstacles.filter(isTextBoxObstacle));
1387
1434
  const endpointChipIds = new Set(this.pins.map((pin) => pin.chipId));
@@ -1390,36 +1437,68 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1390
1437
  (obstacle) => obstacle.textBox.chipId !== void 0 && endpointChipIds.has(obstacle.textBox.chipId)
1391
1438
  )
1392
1439
  );
1393
- const [pin1, pin2] = this.pins;
1394
- const directShortPath = calculateDirectShortPath(pin1, pin2);
1395
- const defaultElbow = calculateElbowForPins({
1396
- pin1,
1397
- pin2,
1398
- overshoot: 0.2
1399
- });
1400
- const routingDistance = Math.abs(pin1.x - pin2.x) + Math.abs(pin1.y - pin2.y);
1401
- const adaptiveElbow = calculateElbowForPins({
1402
- pin1,
1403
- pin2,
1404
- overshoot: Math.min(0.2, Math.max(0.02, routingDistance / 4))
1440
+ const directionOptions = this.pins.map((pin, pinIndex) => {
1441
+ if (pin._facingDirection) return [pin._facingDirection];
1442
+ this.inferredPinIndexes.add(pinIndex);
1443
+ const connectedPin = this.pins[pinIndex === 0 ? 1 : 0];
1444
+ return getPinDirectionCandidates(
1445
+ pin,
1446
+ this.chipMap[pin.chipId],
1447
+ connectedPin
1448
+ );
1405
1449
  });
1406
- const adaptiveElbowIsShorter = this.pathLength(adaptiveElbow) < this.pathLength(defaultElbow);
1407
- const defaultElbowBacktracks = this.pathLength(defaultElbow) > routingDistance + 1e-9;
1408
- const shouldUseAdaptiveElbow = findFirstCollision(adaptiveElbow, this.obstacles) === null && (pinsFaceEachOther({ pin1, pin2 }) && defaultElbowBacktracks && adaptiveElbowIsShorter || findFirstCollision(defaultElbow, this.obstacles) !== null);
1409
- this.baseElbow = defaultElbow;
1410
- if (shouldUseAdaptiveElbow) {
1411
- this.baseElbow = adaptiveElbow;
1450
+ const directionPairs = directionOptions[0].flatMap(
1451
+ (firstDirection, firstIndex) => directionOptions[1].map((secondDirection, secondIndex) => ({
1452
+ directions: [firstDirection, secondDirection],
1453
+ preferenceIndex: firstIndex + secondIndex
1454
+ }))
1455
+ );
1456
+ this.hasAmbiguousPinDirections = directionOptions.some(
1457
+ (directions) => directions.length > 1
1458
+ );
1459
+ const rankedDirectionPairs = directionPairs.map(({ directions, preferenceIndex }) => {
1460
+ const candidatePins = this.pins.map((pin, index) => ({
1461
+ ...pin,
1462
+ _facingDirection: directions[index]
1463
+ }));
1464
+ const initialPath = getInitialPathForPins({
1465
+ pins: candidatePins,
1466
+ obstacles: this.obstacles
1467
+ });
1468
+ return {
1469
+ directions,
1470
+ baseElbow: initialPath.path,
1471
+ isDirectShortPath: initialPath.isDirectShortPath,
1472
+ preferenceIndex,
1473
+ collisionCount: findFirstCollision(initialPath.path, this.obstacles) === null ? 0 : 1,
1474
+ pathLength: getPathLength(initialPath.path)
1475
+ };
1476
+ }).sort(
1477
+ (first, second) => first.collisionCount - second.collisionCount || first.pathLength - second.pathLength || first.preferenceIndex - second.preferenceIndex
1478
+ );
1479
+ const preferredCandidate = rankedDirectionPairs[0];
1480
+ for (const [pinIndex, pin] of this.pins.entries()) {
1481
+ pin._facingDirection = preferredCandidate.directions[pinIndex];
1412
1482
  }
1413
- if (directShortPath) {
1414
- this.baseElbow = directShortPath;
1483
+ const [pin1, pin2] = this.pins;
1484
+ this.baseElbow = preferredCandidate.baseElbow;
1485
+ if (preferredCandidate.isDirectShortPath) {
1486
+ this.solvedTracePath = preferredCandidate.baseElbow;
1415
1487
  }
1416
- this.solvedTracePath = directShortPath;
1417
1488
  this.aabb = aabbFromPoints(
1418
1489
  { x: pin1.x, y: pin1.y },
1419
1490
  { x: pin2.x, y: pin2.y }
1420
1491
  );
1421
- this.queue.push({ path: this.baseElbow, collisionRects: /* @__PURE__ */ new Set() });
1422
- this.visited.add(pathKey(this.baseElbow));
1492
+ for (const candidate of rankedDirectionPairs) {
1493
+ const key = pathKey(candidate.baseElbow);
1494
+ if (this.visited.has(key)) continue;
1495
+ this.visited.add(key);
1496
+ this.queue.push({
1497
+ path: candidate.baseElbow,
1498
+ collisionRects: /* @__PURE__ */ new Set(),
1499
+ directions: candidate.directions
1500
+ });
1501
+ }
1423
1502
  }
1424
1503
  getConstructorParams() {
1425
1504
  return {
@@ -1427,7 +1506,8 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1427
1506
  pins: this.pins,
1428
1507
  connectionPair: this.connectionPair,
1429
1508
  inputProblem: this.inputProblem,
1430
- preferExteriorDetours: this.preferExteriorDetours
1509
+ preferExteriorDetours: this.preferExteriorDetours,
1510
+ reserveNetLabelClearance: this.reserveNetLabelClearance
1431
1511
  };
1432
1512
  }
1433
1513
  getTextBoxPaddingForConnectionPair() {
@@ -1490,11 +1570,7 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1490
1570
  return null;
1491
1571
  }
1492
1572
  pathLength(pts) {
1493
- let sum = 0;
1494
- for (let i = 0; i < pts.length - 1; i++) {
1495
- sum += Math.abs(pts[i + 1].x - pts[i].x) + Math.abs(pts[i + 1].y - pts[i].y);
1496
- }
1497
- return sum;
1573
+ return getPathLength(pts);
1498
1574
  }
1499
1575
  getPinBandPenalty(path) {
1500
1576
  let penalty = 0;
@@ -1529,7 +1605,7 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1529
1605
  this.error = "No collision-free path found";
1530
1606
  return;
1531
1607
  }
1532
- const { path, collisionRects } = state;
1608
+ const { path, collisionRects, directions } = state;
1533
1609
  const [PA, PB] = this.pins;
1534
1610
  const collision = findFirstCollision(path, this.obstacles, {
1535
1611
  excludeRectsForSegment: (segIndex2) => {
@@ -1583,7 +1659,15 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1583
1659
  const last = path[path.length - 1];
1584
1660
  const EPS17 = 1e-9;
1585
1661
  const samePoint = (p, q) => Math.abs(p.x - q.x) < EPS17 && Math.abs(p.y - q.y) < EPS17;
1586
- if (samePoint(first, { x: PA.x, y: PA.y }) && samePoint(last, { x: PB.x, y: PB.y })) {
1662
+ if (samePoint(first, { x: PA.x, y: PA.y }) && samePoint(last, { x: PB.x, y: PB.y }) && (!this.hasAmbiguousPinDirections || pathMatchesPinDirections({
1663
+ path,
1664
+ pin1: { ...PA, _facingDirection: directions[0] },
1665
+ pin2: { ...PB, _facingDirection: directions[1] }
1666
+ }))) {
1667
+ for (const pinIndex of this.inferredPinIndexes) {
1668
+ const direction = pinIndex === 0 ? segmentDirection(path[0], path[1]) : segmentDirection(path.at(-1), path.at(-2));
1669
+ if (direction) this.pins[pinIndex]._facingDirection = direction;
1670
+ }
1587
1671
  this.solvedTracePath = path;
1588
1672
  this.solved = true;
1589
1673
  }
@@ -1616,7 +1700,8 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1616
1700
  nextCollisionRects.add(rect);
1617
1701
  this.queue.push({
1618
1702
  path: detour,
1619
- collisionRects: nextCollisionRects
1703
+ collisionRects: nextCollisionRects,
1704
+ directions
1620
1705
  });
1621
1706
  }
1622
1707
  return;
@@ -1725,7 +1810,11 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
1725
1810
  (a2, b2) => a2.length - b2.length || a2.pinBandPenalty - b2.pinBandPenalty
1726
1811
  );
1727
1812
  for (const st of newStates) {
1728
- this.queue.push({ path: st.path, collisionRects: st.collisionRects });
1813
+ this.queue.push({
1814
+ path: st.path,
1815
+ collisionRects: st.collisionRects,
1816
+ directions
1817
+ });
1729
1818
  }
1730
1819
  }
1731
1820
  visualize() {
@@ -1789,6 +1878,7 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1789
1878
  queuedConnectionPairs;
1790
1879
  chipMap;
1791
1880
  currentConnectionPair = null;
1881
+ retryingWithoutNetLabelClearance = false;
1792
1882
  solvedTracePaths = [];
1793
1883
  failedConnectionPairs = [];
1794
1884
  constructor(params) {
@@ -1813,6 +1903,7 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1813
1903
  if (this.activeSubSolver?.solved) {
1814
1904
  this.solvedTracePaths.push({
1815
1905
  ...this.currentConnectionPair,
1906
+ pins: this.activeSubSolver.pins,
1816
1907
  tracePath: this.activeSubSolver.solvedTracePath,
1817
1908
  mspConnectionPairIds: [this.currentConnectionPair.mspPairId],
1818
1909
  pinIds: [
@@ -1822,8 +1913,28 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1822
1913
  });
1823
1914
  this.activeSubSolver = null;
1824
1915
  this.currentConnectionPair = null;
1916
+ this.retryingWithoutNetLabelClearance = false;
1825
1917
  }
1826
1918
  if (this.activeSubSolver?.failed) {
1919
+ if (this.currentConnectionPair && !this.retryingWithoutNetLabelClearance && this.activeSubSolver.hasAmbiguousPinDirections) {
1920
+ const connectionPair2 = this.currentConnectionPair;
1921
+ this.retryingWithoutNetLabelClearance = true;
1922
+ this.activeSubSolver = new SchematicTraceSingleLineSolver2({
1923
+ inputProblem: this.inputProblem,
1924
+ pins: connectionPair2.pins.map((pin) => ({
1925
+ ...pin
1926
+ })),
1927
+ connectionPair: connectionPair2,
1928
+ chipMap: this.chipMap,
1929
+ preferExteriorDetours: shouldPreferExteriorDetours({
1930
+ connectionPair: connectionPair2,
1931
+ allConnectionPairs: this.mspConnectionPairs,
1932
+ inputProblem: this.inputProblem
1933
+ }),
1934
+ reserveNetLabelClearance: false
1935
+ });
1936
+ return;
1937
+ }
1827
1938
  if (this.currentConnectionPair) {
1828
1939
  this.failedConnectionPairs.push({
1829
1940
  ...this.currentConnectionPair,
@@ -1832,6 +1943,7 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1832
1943
  }
1833
1944
  this.activeSubSolver = null;
1834
1945
  this.currentConnectionPair = null;
1946
+ this.retryingWithoutNetLabelClearance = false;
1835
1947
  }
1836
1948
  if (this.activeSubSolver) {
1837
1949
  this.activeSubSolver.step();
@@ -1843,7 +1955,15 @@ var SchematicTraceLinesSolver = class extends BaseSolver {
1843
1955
  return;
1844
1956
  }
1845
1957
  this.currentConnectionPair = connectionPair;
1846
- const { pins } = connectionPair;
1958
+ this.retryingWithoutNetLabelClearance = false;
1959
+ const hasAmbiguousCornerPin = connectionPair.pins.some((pin) => {
1960
+ if (pin._facingDirection) return false;
1961
+ const chip = this.chipMap[pin.chipId];
1962
+ return chip && getPinDirectionCandidates(pin, chip).length > 1;
1963
+ });
1964
+ const pins = hasAmbiguousCornerPin ? connectionPair.pins.map((pin) => ({
1965
+ ...pin
1966
+ })) : connectionPair.pins;
1847
1967
  this.activeSubSolver = new SchematicTraceSingleLineSolver2({
1848
1968
  inputProblem: this.inputProblem,
1849
1969
  pins,
@@ -4254,7 +4374,7 @@ var doesPathOverlapTraceStrokes = (path, traces) => {
4254
4374
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
4255
4375
  var MAX_TRIES = 5;
4256
4376
  var PATH_LENGTH_EPSILON = 1e-9;
4257
- var getPathLength = (points) => {
4377
+ var getPathLength2 = (points) => {
4258
4378
  let length = 0;
4259
4379
  for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex++) {
4260
4380
  const point = points[pointIndex];
@@ -4304,7 +4424,7 @@ var SingleOverlapSolver = class extends BaseSolver {
4304
4424
  );
4305
4425
  }
4306
4426
  this.queuedCandidatePaths = [...candidateByPath.values()].sort((a, b) => {
4307
- const pathLengthDifference = getPathLength(a) - getPathLength(b);
4427
+ const pathLengthDifference = getPathLength2(a) - getPathLength2(b);
4308
4428
  if (Math.abs(pathLengthDifference) >= PATH_LENGTH_EPSILON) {
4309
4429
  return pathLengthDifference;
4310
4430
  }
@@ -5238,12 +5358,62 @@ var tryConnectPoints = (start, end) => {
5238
5358
  };
5239
5359
 
5240
5360
  // lib/solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts
5241
- var hasCollisionsWithLabels = (pathSegments, labels) => {
5361
+ var EPSILON = 1e-9;
5362
+ var rangesMeetWithoutOverlap = (firstMin, firstMax, secondMin, secondMax) => firstMax >= secondMin - EPSILON && secondMax >= firstMin - EPSILON && Math.min(firstMax, secondMax) - Math.max(firstMin, secondMin) <= EPSILON;
5363
+ var pointsEqual = (first, second) => Math.abs(first.x - second.x) <= EPSILON && Math.abs(first.y - second.y) <= EPSILON;
5364
+ var continuesExistingBoundarySegment = ({
5365
+ start,
5366
+ end,
5367
+ label,
5368
+ originalPath
5369
+ }) => {
5370
+ const isVertical6 = Math.abs(start.x - end.x) <= EPSILON;
5371
+ const isHorizontal5 = Math.abs(start.y - end.y) <= EPSILON;
5372
+ const isOnBoundary = isVertical6 ? Math.abs(start.x - label.minX) <= EPSILON || Math.abs(start.x - label.maxX) <= EPSILON : isHorizontal5 ? Math.abs(start.y - label.minY) <= EPSILON || Math.abs(start.y - label.maxY) <= EPSILON : false;
5373
+ if (!isOnBoundary) return false;
5374
+ for (let i = 0; i < originalPath.length - 1; i++) {
5375
+ const isEndpointSegment = i === 0 || i === originalPath.length - 2;
5376
+ if (!isEndpointSegment) continue;
5377
+ const originalStart = originalPath[i];
5378
+ const originalEnd = originalPath[i + 1];
5379
+ const innerEndpoint = i === 0 ? originalEnd : originalStart;
5380
+ if (!pointsEqual(start, innerEndpoint) && !pointsEqual(end, innerEndpoint)) {
5381
+ continue;
5382
+ }
5383
+ if (!segmentIntersectsRect(originalStart, originalEnd, label)) continue;
5384
+ if (isVertical6 && Math.abs(originalStart.x - originalEnd.x) <= EPSILON && Math.abs(originalStart.x - start.x) <= EPSILON && rangesMeetWithoutOverlap(
5385
+ Math.min(start.y, end.y),
5386
+ Math.max(start.y, end.y),
5387
+ Math.min(originalStart.y, originalEnd.y),
5388
+ Math.max(originalStart.y, originalEnd.y)
5389
+ )) {
5390
+ return true;
5391
+ }
5392
+ if (isHorizontal5 && Math.abs(originalStart.y - originalEnd.y) <= EPSILON && Math.abs(originalStart.y - start.y) <= EPSILON && rangesMeetWithoutOverlap(
5393
+ Math.min(start.x, end.x),
5394
+ Math.max(start.x, end.x),
5395
+ Math.min(originalStart.x, originalEnd.x),
5396
+ Math.max(originalStart.x, originalEnd.x)
5397
+ )) {
5398
+ return true;
5399
+ }
5400
+ }
5401
+ return false;
5402
+ };
5403
+ var hasCollisionsWithLabels = (pathSegments, labels, options = {}) => {
5242
5404
  for (let i = 0; i < pathSegments.length - 1; i++) {
5243
5405
  const p1 = pathSegments[i];
5244
5406
  const p2 = pathSegments[i + 1];
5245
5407
  for (const label of labels) {
5246
5408
  if (segmentIntersectsRect(p1, p2, label)) {
5409
+ if (options.originalPath && continuesExistingBoundarySegment({
5410
+ start: p1,
5411
+ end: p2,
5412
+ label,
5413
+ originalPath: options.originalPath
5414
+ })) {
5415
+ continue;
5416
+ }
5247
5417
  return true;
5248
5418
  }
5249
5419
  }
@@ -5300,7 +5470,8 @@ var minimizeTurns = ({
5300
5470
  path,
5301
5471
  obstacles,
5302
5472
  labelBounds,
5303
- originalPath
5473
+ originalPath,
5474
+ allowLabelBoundaryExtension = false
5304
5475
  }) => {
5305
5476
  if (path.length <= 2) {
5306
5477
  return path;
@@ -5336,7 +5507,8 @@ var minimizeTurns = ({
5336
5507
  const collidesWithObstacles = hasCollisions(connection, obstacles);
5337
5508
  const collidesWithLabels = hasCollisionsWithLabels(
5338
5509
  connection,
5339
- labelBounds
5510
+ labelBounds,
5511
+ allowLabelBoundaryExtension ? { originalPath } : {}
5340
5512
  );
5341
5513
  if (!collidesWithObstacles && !collidesWithLabels) {
5342
5514
  const newTurns = countTurns(testPath);
@@ -5385,7 +5557,8 @@ var minimizeTurns = ({
5385
5557
  );
5386
5558
  const collidesWithLabels = hasCollisionsWithLabels(
5387
5559
  connectionSegments,
5388
- labelBounds
5560
+ labelBounds,
5561
+ allowLabelBoundaryExtension ? { originalPath } : {}
5389
5562
  );
5390
5563
  if (!collidesWithObstacles && !collidesWithLabels) {
5391
5564
  const newTurns = countTurns(testPath);
@@ -5420,7 +5593,8 @@ var minimizeTurns = ({
5420
5593
  const collidesWithObstacles = hasCollisions([p1, p3], obstacles);
5421
5594
  const collidesWithLabels = hasCollisionsWithLabels(
5422
5595
  [p1, p3],
5423
- labelBounds
5596
+ labelBounds,
5597
+ allowLabelBoundaryExtension ? { originalPath } : {}
5424
5598
  );
5425
5599
  if (!collidesWithObstacles && !collidesWithLabels) {
5426
5600
  optimizedPath = testPath;
@@ -5448,7 +5622,7 @@ var getRailOrientation = (a, b) => {
5448
5622
  return null;
5449
5623
  };
5450
5624
  var rangesTouchOrOverlap = (a, b) => Math.min(a.maxAlong, b.maxAlong) - Math.max(a.minAlong, b.minAlong) >= -RAIL_ALIGNMENT_EPSILON;
5451
- var pointsEqual = (a, b) => nearlyEqual(a.x, b.x) && nearlyEqual(a.y, b.y);
5625
+ var pointsEqual2 = (a, b) => nearlyEqual(a.x, b.x) && nearlyEqual(a.y, b.y);
5452
5626
  var getDistinctCoordinates = (coordinates) => {
5453
5627
  const distinct = [];
5454
5628
  for (const coordinate of coordinates) {
@@ -5570,6 +5744,19 @@ var shortenExcessiveLabelPaddingDetour = ({
5570
5744
  };
5571
5745
 
5572
5746
  // lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts
5747
+ var PATH_LENGTH_EPSILON2 = 1e-9;
5748
+ var getPathLength3 = (path) => path.slice(1).reduce((length, point, pointIndex) => {
5749
+ const previousPoint = path[pointIndex];
5750
+ return length + Math.abs(point.x - previousPoint.x) + Math.abs(point.y - previousPoint.y);
5751
+ }, 0);
5752
+ var chooseLengthPreservingBoundaryRoute = ({
5753
+ strictPath,
5754
+ boundaryPath
5755
+ }) => {
5756
+ const doesNotAddTurns = countTurns(boundaryPath) <= countTurns(strictPath);
5757
+ const preservesLength = Math.abs(getPathLength3(boundaryPath) - getPathLength3(strictPath)) <= PATH_LENGTH_EPSILON2;
5758
+ return doesNotAddTurns && preservesLength ? boundaryPath : strictPath;
5759
+ };
5573
5760
  var minimizeTurnsWithFilteredLabels = ({
5574
5761
  targetMspConnectionPairId,
5575
5762
  traces,
@@ -5632,21 +5819,33 @@ var minimizeTurnsWithFilteredLabels = ({
5632
5819
  minY: nl.center.y - nl.height / 2 - paddingBuffer,
5633
5820
  maxY: nl.center.y + nl.height / 2 + paddingBuffer
5634
5821
  }));
5635
- const strictPath = minimizeTurns({
5636
- path: originalPath,
5637
- obstacles: [...staticObstacles, ...getTraceObstacles2(otherTraces)],
5638
- labelBounds,
5639
- originalPath
5640
- });
5641
- const relaxedPath = minimizeTurns({
5642
- path: originalPath,
5643
- obstacles: [
5644
- ...staticObstacles,
5645
- ...getTraceObstacles2(relaxedObstacleTraces)
5646
- ],
5647
- labelBounds,
5648
- originalPath
5649
- });
5822
+ const minimizeForObstacles = (obstacles) => {
5823
+ const strictPath2 = minimizeTurns({
5824
+ path: originalPath,
5825
+ obstacles,
5826
+ labelBounds,
5827
+ originalPath
5828
+ });
5829
+ const boundaryPath = minimizeTurns({
5830
+ path: originalPath,
5831
+ obstacles,
5832
+ labelBounds,
5833
+ originalPath,
5834
+ allowLabelBoundaryExtension: true
5835
+ });
5836
+ return chooseLengthPreservingBoundaryRoute({
5837
+ strictPath: strictPath2,
5838
+ boundaryPath
5839
+ });
5840
+ };
5841
+ const strictPath = minimizeForObstacles([
5842
+ ...staticObstacles,
5843
+ ...getTraceObstacles2(otherTraces)
5844
+ ]);
5845
+ const relaxedPath = minimizeForObstacles([
5846
+ ...staticObstacles,
5847
+ ...getTraceObstacles2(relaxedObstacleTraces)
5848
+ ]);
5650
5849
  const sameNetTraces = otherTraces.filter(
5651
5850
  (trace) => trace.globalConnNetId === targetTrace.globalConnNetId
5652
5851
  );
@@ -6038,7 +6237,7 @@ var segmentRunsAlongRectBoundary = (start, end, rect) => {
6038
6237
 
6039
6238
  // lib/solvers/Example28Solver/geometry.ts
6040
6239
  var getPathKey = (path) => path.map((point) => `${point.x},${point.y}`).join(";");
6041
- var getPathLength2 = (path) => {
6240
+ var getPathLength4 = (path) => {
6042
6241
  let length = 0;
6043
6242
  for (let i = 0; i < path.length - 1; i++) {
6044
6243
  length += Math.abs(path[i + 1].x - path[i].x) + Math.abs(path[i + 1].y - path[i].y);
@@ -6192,7 +6391,7 @@ var getTraceGeometryMetrics = (traces, allTraces) => ({
6192
6391
  ),
6193
6392
  visibleLength: getVisibleTraceLength(traces),
6194
6393
  pathLength: traces.reduce(
6195
- (sum, trace) => sum + getPathLength2(trace.tracePath),
6394
+ (sum, trace) => sum + getPathLength4(trace.tracePath),
6196
6395
  0
6197
6396
  ),
6198
6397
  otherNetCrossings: countOtherNetCrossings(traces, allTraces)
@@ -6214,7 +6413,7 @@ var isReadabilityImprovement = (candidate, baseline) => candidate.turnCount <= b
6214
6413
 
6215
6414
  // lib/solvers/TraceCleanupSolver/sameNetRailAlignment/evaluateRailGroup.ts
6216
6415
  var tracePathChanged = (original, candidate) => original.tracePath.length !== candidate.tracePath.length || candidate.tracePath.some(
6217
- (point, index) => !pointsEqual(point, original.tracePath[index])
6416
+ (point, index) => !pointsEqual2(point, original.tracePath[index])
6218
6417
  );
6219
6418
  var evaluateRailGroup = ({
6220
6419
  group,
@@ -7165,7 +7364,7 @@ var UntangleTraceSubsolver = class extends BaseSolver {
7165
7364
  candidate.traceId
7166
7365
  ).isColliding && (crossing.isInitialBundleCrossing || !candidate.collision.isColliding)
7167
7366
  ).sort(
7168
- (first, second) => Number(first.collision.isColliding) - Number(second.collision.isColliding) || getPathLength2(first.path) - getPathLength2(second.path)
7367
+ (first, second) => Number(first.collision.isColliding) - Number(second.collision.isColliding) || getPathLength4(first.path) - getPathLength4(second.path)
7169
7368
  );
7170
7369
  const bestCandidate = validCandidates[0];
7171
7370
  if (!bestCandidate) return false;
@@ -7505,7 +7704,7 @@ var rerouteGeneratedNetLabelConnectorCrossings = ({
7505
7704
  chipBounds: [],
7506
7705
  clearance
7507
7706
  }).filter(
7508
- (candidate) => getPathLength2(candidate.path) <= getPathLength2(trace.tracePath) + EPS12 && countTurns(candidate.path) <= countTurns(trace.tracePath) + 2 && !isPathCollidingWithObstacles(
7707
+ (candidate) => getPathLength4(candidate.path) <= getPathLength4(trace.tracePath) + EPS12 && countTurns(candidate.path) <= countTurns(trace.tracePath) + 2 && !isPathCollidingWithObstacles(
7509
7708
  candidate.path,
7510
7709
  componentAndTextObstacles
7511
7710
  ) && !hasCollisionsWithLabels(candidate.path, foreignLabelBounds) && !isPathColliding(candidate.path, foreignTraces, trace.mspPairId).isColliding && !doesPathCoincideWithTraces(candidate.path, foreignTraces)
@@ -7515,7 +7714,7 @@ var rerouteGeneratedNetLabelConnectorCrossings = ({
7515
7714
  }));
7516
7715
  });
7517
7716
  candidates.sort((first, second) => {
7518
- const lengthDifference = getPathLength2(first.path) - getPathLength2(second.path);
7717
+ const lengthDifference = getPathLength4(first.path) - getPathLength4(second.path);
7519
7718
  return Math.abs(lengthDifference) > EPS12 ? lengthDifference : countTurns(first.path) - countTurns(second.path);
7520
7719
  });
7521
7720
  const bestCandidate = candidates[0];
@@ -7715,7 +7914,7 @@ var extendVerticalTracePathAtInteriorPoint = ({
7715
7914
  extensionEndPoint
7716
7915
  }) => {
7717
7916
  const sourceIndex = tracePath.findIndex(
7718
- (point) => pointsEqual(point, sourcePoint)
7917
+ (point) => pointsEqual2(point, sourcePoint)
7719
7918
  );
7720
7919
  if (sourceIndex <= 0 || sourceIndex >= tracePath.length - 1) return null;
7721
7920
  const extensionDirectionY = extensionEndPoint.y - sourcePoint.y;
@@ -8294,7 +8493,7 @@ var scoreTracePath = ({
8294
8493
  labelHugDistance: getLabelHugDistance(tracePath, obstacleLabel),
8295
8494
  traceIntersections: countTraceIntersections(candidateTrace, outputTraces),
8296
8495
  chipBoundaryOverlap: getChipBoundaryOverlap(tracePath, chipObstacles),
8297
- pathLength: getPathLength2(tracePath)
8496
+ pathLength: getPathLength4(tracePath)
8298
8497
  };
8299
8498
  };
8300
8499
  var countTraceIntersections = (trace, outputTraces) => {
@@ -8758,7 +8957,7 @@ var getConnectorTracePath = (source, target, orientation) => simplifyOrthogonalP
8758
8957
  );
8759
8958
  var simplifyOrthogonalPath = (path) => {
8760
8959
  const deduped = path.filter(
8761
- (point, index) => index === 0 || !pointsEqual2(point, path[index - 1])
8960
+ (point, index) => index === 0 || !pointsEqual3(point, path[index - 1])
8762
8961
  );
8763
8962
  if (deduped.length < 3) return deduped;
8764
8963
  const simplified = [deduped[0]];
@@ -8774,7 +8973,7 @@ var simplifyOrthogonalPath = (path) => {
8774
8973
  simplified.push(deduped[deduped.length - 1]);
8775
8974
  return simplified;
8776
8975
  };
8777
- var pointsEqual2 = (a, b) => sameX2(a, b) && sameY2(a, b);
8976
+ var pointsEqual3 = (a, b) => sameX2(a, b) && sameY2(a, b);
8778
8977
  var sameX2 = (a, b) => Math.abs(a.x - b.x) <= EPS13;
8779
8978
  var sameY2 = (a, b) => Math.abs(a.y - b.y) <= EPS13;
8780
8979
  var getMaxSearchDistance = (inputProblem) => {
@@ -12476,7 +12675,7 @@ var removeConsecutiveDuplicatePoints = (path) => {
12476
12675
  }
12477
12676
  return filteredPath;
12478
12677
  };
12479
- var getPathLength3 = (path) => {
12678
+ var getPathLength5 = (path) => {
12480
12679
  let pathLength = 0;
12481
12680
  for (let pointIndex = 0; pointIndex < path.length - 1; pointIndex++) {
12482
12681
  const startPoint = path[pointIndex];
@@ -12560,7 +12759,7 @@ var getPerimeterCandidates = ({
12560
12759
  );
12561
12760
  }
12562
12761
  return candidates.sort(
12563
- (firstPath, secondPath) => getPathLength3(firstPath) - getPathLength3(secondPath)
12762
+ (firstPath, secondPath) => getPathLength5(firstPath) - getPathLength5(secondPath)
12564
12763
  );
12565
12764
  };
12566
12765
  var getSegmentMidpoint = (startPoint, endPoint) => {
@@ -12665,7 +12864,7 @@ var getJunctionCandidates = ({
12665
12864
  }
12666
12865
  }
12667
12866
  return candidates.sort(
12668
- (firstPath, secondPath) => getPathLength3(firstPath) - getPathLength3(secondPath)
12867
+ (firstPath, secondPath) => getPathLength5(firstPath) - getPathLength5(secondPath)
12669
12868
  );
12670
12869
  };
12671
12870
  var pathCollidesWithObstacles = ({
@@ -13885,8 +14084,8 @@ var getTracePathStartingAtPin = (trace, pin) => {
13885
14084
  const pathStart = trace.tracePath[0];
13886
14085
  const pathEnd = trace.tracePath.at(-1);
13887
14086
  if (!pathStart || !pathEnd) return null;
13888
- if (pointsEqual(pathStart, pin)) return trace.tracePath;
13889
- if (pointsEqual(pathEnd, pin)) return [...trace.tracePath].reverse();
14087
+ if (pointsEqual2(pathStart, pin)) return trace.tracePath;
14088
+ if (pointsEqual2(pathEnd, pin)) return [...trace.tracePath].reverse();
13890
14089
  return null;
13891
14090
  };
13892
14091
  var getPerpendicularPathCrossings = (targetPath, donorPath) => {
@@ -13908,7 +14107,7 @@ var getPerpendicularPathCrossings = (targetPath, donorPath) => {
13908
14107
  donorStart,
13909
14108
  donorEnd
13910
14109
  );
13911
- if (!intersectionPoint || pointsEqual(intersectionPoint, sharedEndpoint)) {
14110
+ if (!intersectionPoint || pointsEqual2(intersectionPoint, sharedEndpoint)) {
13912
14111
  continue;
13913
14112
  }
13914
14113
  crossings.push({
@@ -13931,7 +14130,7 @@ var buildCollapsedCyclePath = ({
13931
14130
  crossing.intersectionPoint,
13932
14131
  ...targetPath.slice(crossing.targetSegmentIndex + 1)
13933
14132
  ]);
13934
- if (pointsEqual(targetTrace.tracePath[0], targetPath[0])) {
14133
+ if (pointsEqual2(targetTrace.tracePath[0], targetPath[0])) {
13935
14134
  return pathFromSharedPin;
13936
14135
  }
13937
14136
  return pathFromSharedPin.reverse();
@@ -14379,8 +14578,8 @@ var generateElbowTransitionSimplificationCandidates = ({
14379
14578
  };
14380
14579
 
14381
14580
  // lib/solvers/TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver.ts
14382
- var PATH_LENGTH_EPSILON2 = 1e-9;
14383
- var getPathLength4 = (points) => points.slice(1).reduce((length, point, pointIndex) => {
14581
+ var PATH_LENGTH_EPSILON3 = 1e-9;
14582
+ var getPathLength6 = (points) => points.slice(1).reduce((length, point, pointIndex) => {
14384
14583
  const previousPoint = points[pointIndex];
14385
14584
  return length + Math.abs(point.x - previousPoint.x) + Math.abs(point.y - previousPoint.y);
14386
14585
  }, 0);
@@ -14452,8 +14651,8 @@ var TraceElbowTransitionSimplificationSolver = class extends BaseSolver {
14452
14651
  netLabels: this.input.netLabelPlacements
14453
14652
  }).length;
14454
14653
  const isSimplerEquivalentReroute = candidateOverlapCount < initialRerouteOverlapCount && Math.abs(
14455
- getPathLength4(simplifiedCandidate) - getPathLength4(reroutedPath)
14456
- ) < PATH_LENGTH_EPSILON2 && simplifiedCandidate.length < reroutedPath.length && preservesLabelAnchors(
14654
+ getPathLength6(simplifiedCandidate) - getPathLength6(reroutedPath)
14655
+ ) < PATH_LENGTH_EPSILON3 && simplifiedCandidate.length < reroutedPath.length && preservesLabelAnchors(
14457
14656
  this.input.netLabelPlacements,
14458
14657
  [completedReroute.initialTrace],
14459
14658
  [candidateTrace]
@@ -14471,7 +14670,7 @@ var TraceElbowTransitionSimplificationSolver = class extends BaseSolver {
14471
14670
  ({ label }) => `${label.globalConnNetId}:${label.netId}`
14472
14671
  )
14473
14672
  );
14474
- const initialPathLength = getPathLength4(tracePath);
14673
+ const initialPathLength = getPathLength6(tracePath);
14475
14674
  const validCandidates = [...candidateByPath.values()].filter(
14476
14675
  (candidatePath) => {
14477
14676
  const candidateTrace = { ...trace, tracePath: candidatePath };
@@ -14483,7 +14682,7 @@ var TraceElbowTransitionSimplificationSolver = class extends BaseSolver {
14483
14682
  ({ label }) => initialOverlapIds.has(`${label.globalConnNetId}:${label.netId}`)
14484
14683
  );
14485
14684
  const reducesCollisions = candidateOverlaps.length < initialOverlaps.length;
14486
- const simplifiesGeometry = candidateOverlaps.length === initialOverlaps.length && getPathLength4(candidatePath) <= initialPathLength + PATH_LENGTH_EPSILON2 && candidatePath.length < tracePath.length;
14685
+ const simplifiesGeometry = candidateOverlaps.length === initialOverlaps.length && getPathLength6(candidatePath) <= initialPathLength + PATH_LENGTH_EPSILON3 && candidatePath.length < tracePath.length;
14487
14686
  return candidateOnlyKeepsExistingOverlaps && (reducesCollisions || simplifiesGeometry) && preservesLabelAnchors(
14488
14687
  this.input.netLabelPlacements,
14489
14688
  [trace],
@@ -14498,7 +14697,7 @@ var TraceElbowTransitionSimplificationSolver = class extends BaseSolver {
14498
14697
  validCandidates.sort((a, b) => {
14499
14698
  const overlapDifference = getOverlapCount(a) - getOverlapCount(b);
14500
14699
  if (overlapDifference !== 0) return overlapDifference;
14501
- return getPathLength4(a) - getPathLength4(b) || a.length - b.length;
14700
+ return getPathLength6(a) - getPathLength6(b) || a.length - b.length;
14502
14701
  });
14503
14702
  const bestCandidate = validCandidates[0];
14504
14703
  if (!bestCandidate) return;
@@ -14741,7 +14940,7 @@ var getInlineBounds = (placement) => {
14741
14940
  maxY: placement.center.y + renderedHeight / 2
14742
14941
  };
14743
14942
  };
14744
- var pointsEqual3 = (a, b) => Math.abs(a.x - b.x) <= POINT_EPSILON && Math.abs(a.y - b.y) <= POINT_EPSILON;
14943
+ var pointsEqual4 = (a, b) => Math.abs(a.x - b.x) <= POINT_EPSILON && Math.abs(a.y - b.y) <= POINT_EPSILON;
14745
14944
  var pathIntersectsBounds = (path, bounds) => {
14746
14945
  for (let index = 0; index < path.length - 1; index++) {
14747
14946
  const start = path[index];
@@ -14837,13 +15036,13 @@ var findConnectorTraceIndex = (label, traces) => traces.findIndex((trace) => {
14837
15036
  const first = trace.tracePath[0];
14838
15037
  const last = trace.tracePath.at(-1);
14839
15038
  return Boolean(
14840
- first && pointsEqual3(first, label.anchorPoint) || last && pointsEqual3(last, label.anchorPoint)
15039
+ first && pointsEqual4(first, label.anchorPoint) || last && pointsEqual4(last, label.anchorPoint)
14841
15040
  );
14842
15041
  });
14843
15042
  var canAddConnectorAtAnchor = (label, traces, pinMap) => {
14844
15043
  if (label.pinIds.some((pinId) => {
14845
15044
  const pin = pinMap[pinId];
14846
- return pin && pointsEqual3(pin, label.anchorPoint);
15045
+ return pin && pointsEqual4(pin, label.anchorPoint);
14847
15046
  })) {
14848
15047
  return true;
14849
15048
  }
@@ -14853,8 +15052,8 @@ var canAddConnectorAtAnchor = (label, traces, pinMap) => {
14853
15052
  };
14854
15053
  var moveConnectorEndpoint = (trace, oldAnchor, newAnchor) => {
14855
15054
  const tracePath = trace.tracePath.map((point) => ({ ...point }));
14856
- if (pointsEqual3(tracePath[0], oldAnchor)) tracePath[0] = newAnchor;
14857
- if (pointsEqual3(tracePath.at(-1), oldAnchor)) {
15055
+ if (pointsEqual4(tracePath[0], oldAnchor)) tracePath[0] = newAnchor;
15056
+ if (pointsEqual4(tracePath.at(-1), oldAnchor)) {
14858
15057
  tracePath[tracePath.length - 1] = newAnchor;
14859
15058
  }
14860
15059
  return { ...trace, tracePath };
@@ -15048,7 +15247,13 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
15048
15247
  label.anchorPoint,
15049
15248
  movedLabel.anchorPoint
15050
15249
  );
15051
- const connectorObstructed = inlineBounds.some(
15250
+ const connectorObstructed = outputTraces.some(
15251
+ (trace) => trace.globalConnNetId !== connector.globalConnNetId && findPerpendicularPathCrossings(
15252
+ connector.tracePath,
15253
+ trace.tracePath,
15254
+ { includeTerminalSegments: true }
15255
+ ).length > 0
15256
+ ) || inlineBounds.some(
15052
15257
  (bounds) => pathIntersectsBounds(connector.tracePath, bounds)
15053
15258
  ) || inputProblem.chips.some(
15054
15259
  (chip) => !ownerChipIds.has(chip.chipId) && pathIntersectsBounds(connector.tracePath, {
@@ -15315,14 +15520,14 @@ var pathIntersectsRenderedLabel = (path, label) => {
15315
15520
 
15316
15521
  // lib/solvers/InlineNetLabelSolver/restoreReroutesAroundSupersededLabels.ts
15317
15522
  var EPS15 = 1e-6;
15318
- var getPathLength5 = (path) => path.slice(1).reduce((length, point, pointIndex) => {
15523
+ var getPathLength7 = (path) => path.slice(1).reduce((length, point, pointIndex) => {
15319
15524
  const previousPoint = path[pointIndex];
15320
15525
  return length + Math.abs(point.x - previousPoint.x) + Math.abs(point.y - previousPoint.y);
15321
15526
  }, 0);
15322
15527
  var pathsEqual = (first, second) => first.length === second.length && first.every(
15323
15528
  (point, index) => Math.abs(point.x - second[index].x) <= EPS15 && Math.abs(point.y - second[index].y) <= EPS15
15324
15529
  );
15325
- var isStrictlySimpler = (candidate, current) => candidate.length < current.length && getPathLength5(candidate) <= getPathLength5(current) + EPS15;
15530
+ var isStrictlySimpler = (candidate, current) => candidate.length < current.length && getPathLength7(candidate) <= getPathLength7(current) + EPS15;
15326
15531
  var getIntersectionKeys = (path, otherPath) => {
15327
15532
  const intersections = /* @__PURE__ */ new Set();
15328
15533
  for (let pathIndex = 0; pathIndex < path.length - 1; pathIndex++) {
@@ -16285,22 +16490,22 @@ var getCollisionLimitedTerminalStubEnd = ({
16285
16490
 
16286
16491
  // lib/solvers/NetLabelTraceRecovery/doesTraceRecoveryPathConflict.ts
16287
16492
  import { doSegmentsIntersect as doSegmentsIntersect4 } from "@tscircuit/math-utils";
16288
- var EPSILON = 1e-6;
16493
+ var EPSILON2 = 1e-6;
16289
16494
  var isStrictlyBetween2 = ({
16290
16495
  coordinate,
16291
16496
  segmentStartCoordinate,
16292
16497
  segmentEndCoordinate
16293
- }) => coordinate > Math.min(segmentStartCoordinate, segmentEndCoordinate) + EPSILON && coordinate < Math.max(segmentStartCoordinate, segmentEndCoordinate) - EPSILON;
16498
+ }) => coordinate > Math.min(segmentStartCoordinate, segmentEndCoordinate) + EPSILON2 && coordinate < Math.max(segmentStartCoordinate, segmentEndCoordinate) - EPSILON2;
16294
16499
  var isStrictInteriorPerpendicularCrossing = ({
16295
16500
  firstSegmentStart,
16296
16501
  firstSegmentEnd,
16297
16502
  secondSegmentStart,
16298
16503
  secondSegmentEnd
16299
16504
  }) => {
16300
- const firstIsHorizontal = Math.abs(firstSegmentStart.y - firstSegmentEnd.y) <= EPSILON;
16301
- const firstIsVertical = Math.abs(firstSegmentStart.x - firstSegmentEnd.x) <= EPSILON;
16302
- const secondIsHorizontal = Math.abs(secondSegmentStart.y - secondSegmentEnd.y) <= EPSILON;
16303
- const secondIsVertical = Math.abs(secondSegmentStart.x - secondSegmentEnd.x) <= EPSILON;
16505
+ const firstIsHorizontal = Math.abs(firstSegmentStart.y - firstSegmentEnd.y) <= EPSILON2;
16506
+ const firstIsVertical = Math.abs(firstSegmentStart.x - firstSegmentEnd.x) <= EPSILON2;
16507
+ const secondIsHorizontal = Math.abs(secondSegmentStart.y - secondSegmentEnd.y) <= EPSILON2;
16508
+ const secondIsVertical = Math.abs(secondSegmentStart.x - secondSegmentEnd.x) <= EPSILON2;
16304
16509
  let horizontalStart;
16305
16510
  let horizontalEnd;
16306
16511
  if (firstIsHorizontal) {
@@ -16459,7 +16664,7 @@ var reduceTraceCrossings = ({
16459
16664
  globalConnNetId,
16460
16665
  otherTraces
16461
16666
  });
16462
- let bestPathLength = getPathLength2(tracePath);
16667
+ let bestPathLength = getPathLength4(tracePath);
16463
16668
  for (const candidate of getEndpointAlignedSegmentCandidates(tracePath)) {
16464
16669
  if (!isCandidateValid(candidate)) continue;
16465
16670
  const crossingCount = countOtherNetCrossings2({
@@ -16467,7 +16672,7 @@ var reduceTraceCrossings = ({
16467
16672
  globalConnNetId,
16468
16673
  otherTraces
16469
16674
  });
16470
- const pathLength = getPathLength2(candidate);
16675
+ const pathLength = getPathLength4(candidate);
16471
16676
  if (crossingCount < bestCrossingCount || crossingCount === bestCrossingCount && pathLength < bestPathLength) {
16472
16677
  bestPath = candidate;
16473
16678
  bestCrossingCount = crossingCount;