@tscircuit/capacity-autorouter 0.0.42 → 0.0.44

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.
package/dist/index.js CHANGED
@@ -68,6 +68,9 @@ var BaseSolver = class {
68
68
  }
69
69
  _step() {
70
70
  }
71
+ getConstructorParams() {
72
+ throw new Error("getConstructorParams not implemented");
73
+ }
71
74
  solve() {
72
75
  const startTime = Date.now();
73
76
  while (!this.solved && !this.failed) {
@@ -1639,30 +1642,40 @@ var CapacityEdgeToPortSegmentSolver = class extends BaseSolver {
1639
1642
  const node = this.nodeMap.get(nodeId);
1640
1643
  segments.forEach((segment) => {
1641
1644
  const isVertical = segment.start.x === segment.end.x;
1642
- const THICKNESS = 0.1 / segment.connectionNames.length;
1645
+ const THICKNESS = 0.05;
1643
1646
  for (let i = 0; i < segment.connectionNames.length; i++) {
1644
- const offsetAmount = (i / (segment.connectionNames.length - 1 + 1e-6) - 0.5) * THICKNESS;
1645
1647
  const offset = {
1646
- x: isVertical ? offsetAmount : 0,
1647
- y: isVertical ? 0 : offsetAmount
1648
+ x: 0.05 * Math.max(...segment.availableZ),
1649
+ y: 0.05 * Math.max(...segment.availableZ)
1648
1650
  };
1649
1651
  const trueSegmentCenter = {
1650
- x: (segment.start.x + segment.end.x) / 2 + offset.x,
1651
- y: (segment.start.y + segment.end.y) / 2 + offset.y
1652
+ x: (segment.start.x + segment.end.x) / 2,
1653
+ y: (segment.start.y + segment.end.y) / 2
1652
1654
  };
1653
- graphics.rects.push({
1654
- center: {
1655
- x: (trueSegmentCenter.x * 6 + node.center.x) / 7,
1656
- y: (trueSegmentCenter.y * 6 + node.center.y) / 7
1657
- },
1658
- width: isVertical ? THICKNESS : Math.abs(segment.end.x - segment.start.x),
1659
- height: isVertical ? Math.abs(segment.end.y - segment.start.y) : THICKNESS,
1660
- fill: safeTransparentize(
1655
+ const segmentCenter = {
1656
+ x: trueSegmentCenter.x + offset.x,
1657
+ y: trueSegmentCenter.y + offset.y
1658
+ };
1659
+ if (offset.x > 0) {
1660
+ graphics.lines.push({
1661
+ points: [trueSegmentCenter, segmentCenter],
1662
+ strokeColor: "rgba(0, 0, 0, 0.25)",
1663
+ strokeDash: "5 5"
1664
+ });
1665
+ }
1666
+ graphics.points.push({
1667
+ x: segmentCenter.x,
1668
+ y: segmentCenter.y,
1669
+ label: `${nodeId}: ${segment.connectionNames.join(", ")}
1670
+ availableZ: ${segment.availableZ.join(",")}
1671
+ nodePortSegmentId: ${segment.nodePortSegmentId}`
1672
+ });
1673
+ graphics.lines.push({
1674
+ points: [segment.start, segment.end],
1675
+ strokeColor: safeTransparentize(
1661
1676
  this.colorMap[segment.connectionNames[i]],
1662
1677
  0.6
1663
- ),
1664
- label: `${nodeId}: ${segment.connectionNames.join(", ")}
1665
- availableZ: ${segment.availableZ.join(",")}`
1678
+ )
1666
1679
  });
1667
1680
  }
1668
1681
  });
@@ -1707,19 +1720,50 @@ function findOverlappingSegment(node, adjNode) {
1707
1720
  };
1708
1721
  }
1709
1722
  }
1723
+ var EPSILON = 1e-9;
1724
+ function coordsAreEqual(p1, p2) {
1725
+ return Math.abs(p1.x - p2.x) < EPSILON && Math.abs(p1.y - p2.y) < EPSILON;
1726
+ }
1727
+ function availableZAreEqual(zA1, zA2) {
1728
+ if (zA1.length !== zA2.length) {
1729
+ return false;
1730
+ }
1731
+ for (let i = 0; i < zA1.length; i++) {
1732
+ if (zA1[i] !== zA2[i]) {
1733
+ return false;
1734
+ }
1735
+ }
1736
+ return true;
1737
+ }
1710
1738
  function combineSegments(segments) {
1711
1739
  const mergedSegments = [];
1712
- const remainingSegments = [...segments];
1740
+ const remainingSegments = segments.map((s) => ({
1741
+ ...s,
1742
+ connectionNames: [...s.connectionNames],
1743
+ availableZ: [...s.availableZ].sort((a, b) => a - b)
1744
+ // Ensure Z is sorted for comparison
1745
+ }));
1713
1746
  while (remainingSegments.length > 0) {
1714
1747
  const segmentUnderTest = remainingSegments.pop();
1715
- const overlappingMergedSegment = mergedSegments.find((segment) => {
1716
- return segment.start.x === segmentUnderTest.start.x && segment.start.y === segmentUnderTest.start.y && segment.end.x === segmentUnderTest.end.x && segment.end.y === segmentUnderTest.end.y;
1717
- });
1718
- if (overlappingMergedSegment) {
1719
- overlappingMergedSegment.connectionNames.push(
1720
- ...segmentUnderTest.connectionNames
1748
+ let foundMatch = false;
1749
+ for (let i = 0; i < mergedSegments.length; i++) {
1750
+ const mergedSegment = mergedSegments[i];
1751
+ const geometryMatch = coordsAreEqual(mergedSegment.start, segmentUnderTest.start) && coordsAreEqual(mergedSegment.end, segmentUnderTest.end) || coordsAreEqual(mergedSegment.start, segmentUnderTest.end) && coordsAreEqual(mergedSegment.end, segmentUnderTest.start);
1752
+ const zMatch = availableZAreEqual(
1753
+ mergedSegment.availableZ,
1754
+ segmentUnderTest.availableZ
1721
1755
  );
1722
- } else {
1756
+ if (geometryMatch && zMatch) {
1757
+ const currentConnections = new Set(mergedSegment.connectionNames);
1758
+ segmentUnderTest.connectionNames.forEach(
1759
+ (cn) => currentConnections.add(cn)
1760
+ );
1761
+ mergedSegment.connectionNames = Array.from(currentConnections);
1762
+ foundMatch = true;
1763
+ break;
1764
+ }
1765
+ }
1766
+ if (!foundMatch) {
1723
1767
  mergedSegments.push(segmentUnderTest);
1724
1768
  }
1725
1769
  }
@@ -1844,25 +1888,49 @@ var CapacitySegmentToPointSolver = class extends BaseSolver {
1844
1888
  */
1845
1889
  visualize() {
1846
1890
  const graphics = {
1847
- points: this.solvedSegments.flatMap(
1848
- (seg) => seg.assignedPoints.map((ap) => ({
1849
- x: ap.point.x,
1850
- y: ap.point.y,
1851
- label: [
1852
- `${seg.capacityMeshNodeId}-${ap.connectionName}`,
1853
- `z: ${seg.availableZ.join(",")}`
1854
- ].join("\n"),
1855
- color: this.colorMap[ap.connectionName],
1856
- step: 4
1857
- }))
1858
- ),
1891
+ points: [],
1859
1892
  lines: this.solvedSegments.map((seg) => ({
1860
1893
  points: [seg.start, seg.end],
1861
1894
  step: 4
1862
1895
  })),
1863
1896
  rects: [],
1864
- circles: []
1897
+ circles: [],
1898
+ coordinateSystem: "cartesian",
1899
+ title: "Capacity Segment to Point Solver"
1865
1900
  };
1901
+ for (let i = 0; i < this.solvedSegments.length; i++) {
1902
+ const seg = this.solvedSegments[i];
1903
+ for (let j = 0; j < seg.assignedPoints.length; j++) {
1904
+ const ap = seg.assignedPoints[j];
1905
+ const truePoint = {
1906
+ x: ap.point.x,
1907
+ y: ap.point.y
1908
+ };
1909
+ const offsetPoint = {
1910
+ x: ap.point.x + ap.point.z * 0.05,
1911
+ y: ap.point.y + ap.point.z * 0.05
1912
+ };
1913
+ if (ap.point.z !== 0) {
1914
+ graphics.lines.push({
1915
+ points: [truePoint, offsetPoint],
1916
+ strokeColor: "rgba(0, 0, 0, 0.25)",
1917
+ strokeDash: "5 5",
1918
+ step: 4
1919
+ });
1920
+ }
1921
+ graphics.points.push({
1922
+ x: offsetPoint.x,
1923
+ y: offsetPoint.y,
1924
+ label: [
1925
+ `${seg.capacityMeshNodeId}-${ap.connectionName}`,
1926
+ `z: ${seg.availableZ.join(",")}`,
1927
+ `nodePortSegmentId: ${seg.nodePortSegmentId}`
1928
+ ].join("\n"),
1929
+ color: this.colorMap[ap.connectionName],
1930
+ step: 4
1931
+ });
1932
+ }
1933
+ }
1866
1934
  const dashedLines = [];
1867
1935
  const nodeConnections = {};
1868
1936
  for (const seg of this.solvedSegments) {
@@ -1938,6 +2006,86 @@ function distance(p1, p2) {
1938
2006
  return Math.sqrt(dx * dx + dy * dy);
1939
2007
  }
1940
2008
 
2009
+ // node_modules/@tscircuit/math-utils/dist/chunk-MHHTZHOJ.js
2010
+ function getBoundingBox(box) {
2011
+ const halfWidth = box.width / 2;
2012
+ const halfHeight = box.height / 2;
2013
+ return {
2014
+ minX: box.center.x - halfWidth,
2015
+ maxX: box.center.x + halfWidth,
2016
+ minY: box.center.y - halfHeight,
2017
+ maxY: box.center.y + halfHeight
2018
+ };
2019
+ }
2020
+ function computeDistanceBetweenBoxes(boxA, boxB) {
2021
+ const a = getBoundingBox(boxA);
2022
+ const b = getBoundingBox(boxB);
2023
+ const dx = Math.max(a.minX - b.maxX, b.minX - a.maxX, 0);
2024
+ const dy = Math.max(a.minY - b.maxY, b.minY - a.maxY, 0);
2025
+ const pointA = { x: 0, y: 0 };
2026
+ const pointB = { x: 0, y: 0 };
2027
+ if (dx === 0 && dy === 0) {
2028
+ return { distance: 0, pointA: boxA.center, pointB: boxB.center };
2029
+ }
2030
+ pointA.x = clamp(boxA.center.x, b.minX, b.maxX);
2031
+ pointA.y = clamp(boxA.center.y, b.minY, b.maxY);
2032
+ pointB.x = clamp(boxB.center.x, a.minX, a.maxX);
2033
+ pointB.y = clamp(boxB.center.y, a.minY, a.maxY);
2034
+ const distance6 = Math.hypot(pointA.x - pointB.x, pointA.y - pointB.y);
2035
+ return { distance: distance6, pointA, pointB };
2036
+ }
2037
+ function clamp(value, min, max) {
2038
+ return Math.max(min, Math.min(max, value));
2039
+ }
2040
+
2041
+ // node_modules/@tscircuit/math-utils/dist/chunk-OMVVSGKD.js
2042
+ function segmentToBoundsMinDistance(a, b, bounds) {
2043
+ const topLeft = { x: bounds.minX, y: bounds.minY };
2044
+ const topRight = { x: bounds.maxX, y: bounds.minY };
2045
+ const bottomLeft = { x: bounds.minX, y: bounds.maxY };
2046
+ const bottomRight = { x: bounds.maxX, y: bounds.maxY };
2047
+ if (doSegmentsIntersect(a, b, topLeft, topRight) || doSegmentsIntersect(a, b, topRight, bottomRight) || doSegmentsIntersect(a, b, bottomRight, bottomLeft) || doSegmentsIntersect(a, b, bottomLeft, topLeft)) {
2048
+ return 0;
2049
+ }
2050
+ if (a.x >= bounds.minX && a.x <= bounds.maxX && a.y >= bounds.minY && a.y <= bounds.maxY && b.x >= bounds.minX && b.x <= bounds.maxX && b.y >= bounds.minY && b.y <= bounds.maxY) {
2051
+ return 0;
2052
+ }
2053
+ const distances = [
2054
+ pointToSegmentDistance(topLeft, a, b),
2055
+ pointToSegmentDistance(topRight, a, b),
2056
+ pointToSegmentDistance(bottomLeft, a, b),
2057
+ pointToSegmentDistance(bottomRight, a, b)
2058
+ ];
2059
+ if (a.x >= bounds.minX && a.x <= bounds.maxX && a.y >= bounds.minY && a.y <= bounds.maxY) {
2060
+ return 0;
2061
+ }
2062
+ if (b.x >= bounds.minX && b.x <= bounds.maxX && b.y >= bounds.minY && b.y <= bounds.maxY) {
2063
+ return 0;
2064
+ }
2065
+ if (a.x < bounds.minX || a.x > bounds.maxX || a.y < bounds.minY || a.y > bounds.maxY) {
2066
+ const closestX = clamp(a.x, bounds.minX, bounds.maxX);
2067
+ const closestY = clamp(a.y, bounds.minY, bounds.maxY);
2068
+ distances.push(distance(a, { x: closestX, y: closestY }));
2069
+ }
2070
+ if (b.x < bounds.minX || b.x > bounds.maxX || b.y < bounds.minY || b.y > bounds.maxY) {
2071
+ const closestX = clamp(b.x, bounds.minX, bounds.maxX);
2072
+ const closestY = clamp(b.y, bounds.minY, bounds.maxY);
2073
+ distances.push(distance(b, { x: closestX, y: closestY }));
2074
+ }
2075
+ return Math.min(...distances);
2076
+ }
2077
+ function segmentToBoxMinDistance(a, b, box) {
2078
+ const halfWidth = box.width / 2;
2079
+ const halfHeight = box.height / 2;
2080
+ const bounds = {
2081
+ minX: box.center.x - halfWidth,
2082
+ maxX: box.center.x + halfWidth,
2083
+ minY: box.center.y - halfHeight,
2084
+ maxY: box.center.y + halfHeight
2085
+ };
2086
+ return segmentToBoundsMinDistance(a, b, bounds);
2087
+ }
2088
+
1941
2089
  // lib/data-structures/SingleRouteCandidatePriorityQueue.ts
1942
2090
  var SingleRouteCandidatePriorityQueue = class {
1943
2091
  heap = [];
@@ -1992,6 +2140,12 @@ var SingleRouteCandidatePriorityQueue = class {
1992
2140
  this.heapifyDown();
1993
2141
  return item;
1994
2142
  }
2143
+ peek() {
2144
+ if (this.heap.length === 0) {
2145
+ return null;
2146
+ }
2147
+ return this.heap[0];
2148
+ }
1995
2149
  enqueue(item) {
1996
2150
  this.heap.push(item);
1997
2151
  this.heapifyUp();
@@ -2074,16 +2228,6 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
2074
2228
  this.obstacleMargin = opts.obstacleMargin ?? 0.2;
2075
2229
  this.layerCount = opts.layerCount ?? 2;
2076
2230
  this.exploredNodes = /* @__PURE__ */ new Set();
2077
- this.candidates = new SingleRouteCandidatePriorityQueue([
2078
- {
2079
- ...opts.A,
2080
- z: opts.A.z ?? 0,
2081
- g: 0,
2082
- h: 0,
2083
- f: 0,
2084
- parent: null
2085
- }
2086
- ]);
2087
2231
  this.straightLineDistance = distance(this.A, this.B);
2088
2232
  this.futureConnections = opts.futureConnections ?? [];
2089
2233
  this.MAX_ITERATIONS = 5e3;
@@ -2106,6 +2250,25 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
2106
2250
  if (this.futureConnections && this.futureConnections.length === 0 && this.obstacleRoutes.length === 0) {
2107
2251
  this.handleSimpleCases();
2108
2252
  }
2253
+ this.candidates = new SingleRouteCandidatePriorityQueue([
2254
+ {
2255
+ ...opts.A,
2256
+ x: Math.floor(opts.A.x / this.cellStep) * this.cellStep,
2257
+ y: Math.floor(opts.A.y / this.cellStep) * this.cellStep,
2258
+ z: opts.A.z ?? 0,
2259
+ g: 0,
2260
+ h: 0,
2261
+ f: 0,
2262
+ parent: {
2263
+ ...opts.A,
2264
+ z: opts.A.z ?? 0,
2265
+ g: 0,
2266
+ h: 0,
2267
+ f: 0,
2268
+ parent: null
2269
+ }
2270
+ }
2271
+ ]);
2109
2272
  }
2110
2273
  handleSimpleCases() {
2111
2274
  this.solved = true;
@@ -2385,6 +2548,19 @@ z: ${this.B.z}`,
2385
2548
  label: `Explored (z=${z})`
2386
2549
  });
2387
2550
  }
2551
+ if (this.candidates.peek()) {
2552
+ const nextNode = this.candidates.peek();
2553
+ graphics.rects.push({
2554
+ center: {
2555
+ x: nextNode.x + nextNode.z * this.cellStep / 20,
2556
+ y: nextNode.y + nextNode.z * this.cellStep / 20
2557
+ },
2558
+ fill: "rgba(0, 255, 0, 0.8)",
2559
+ width: this.cellStep * 0.9,
2560
+ height: this.cellStep * 0.9,
2561
+ label: `Next (z=${nextNode.z})`
2562
+ });
2563
+ }
2388
2564
  for (const route of this.obstacleRoutes) {
2389
2565
  for (const via of route.vias) {
2390
2566
  graphics.circles.push({
@@ -3573,13 +3749,13 @@ function findClosestPointToABCWithinBounds(A, B, C, radius, bounds) {
3573
3749
  x: (A.x + B.x + C.x) / 3,
3574
3750
  y: (A.y + B.y + C.y) / 3
3575
3751
  };
3576
- const distance5 = (p1, p2) => {
3752
+ const distance6 = (p1, p2) => {
3577
3753
  return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
3578
3754
  };
3579
3755
  const isValidPoint = (point) => {
3580
- const distToA = distance5(point, A);
3581
- const distToB = distance5(point, B);
3582
- const distToC = distance5(point, C);
3756
+ const distToA = distance6(point, A);
3757
+ const distToB = distance6(point, B);
3758
+ const distToC = distance6(point, C);
3583
3759
  const withinBounds = point.x >= bounds.minX && point.x <= bounds.maxX && point.y >= bounds.minY && point.y <= bounds.maxY;
3584
3760
  return distToA >= radius && distToB >= radius && distToC >= radius && withinBounds;
3585
3761
  };
@@ -3623,10 +3799,10 @@ function findClosestPointToABCWithinBounds(A, B, C, radius, bounds) {
3623
3799
  };
3624
3800
  const result = [];
3625
3801
  const epsilon = 1e-6;
3626
- if (Math.abs(distance5(intersection1, c1) - r) < epsilon && Math.abs(distance5(intersection1, c2) - r) < epsilon) {
3802
+ if (Math.abs(distance6(intersection1, c1) - r) < epsilon && Math.abs(distance6(intersection1, c2) - r) < epsilon) {
3627
3803
  result.push(intersection1);
3628
3804
  }
3629
- if (Math.abs(distance5(intersection2, c1) - r) < epsilon && Math.abs(distance5(intersection2, c2) - r) < epsilon) {
3805
+ if (Math.abs(distance6(intersection2, c1) - r) < epsilon && Math.abs(distance6(intersection2, c2) - r) < epsilon) {
3630
3806
  result.push(intersection2);
3631
3807
  }
3632
3808
  return result;
@@ -3650,7 +3826,7 @@ function findClosestPointToABCWithinBounds(A, B, C, radius, bounds) {
3650
3826
  const interiorCandidates = validCandidates.filter((p) => !isOnBoundary(p));
3651
3827
  if (interiorCandidates.length > 0) {
3652
3828
  interiorCandidates.sort(
3653
- (a, b) => distance5(a, avgPoint) - distance5(b, avgPoint)
3829
+ (a, b) => distance6(a, avgPoint) - distance6(b, avgPoint)
3654
3830
  );
3655
3831
  return interiorCandidates[0];
3656
3832
  }
@@ -3662,7 +3838,7 @@ function findClosestPointToABCWithinBounds(A, B, C, radius, bounds) {
3662
3838
  for (let y = bounds.minY + 1; y < bounds.maxY; y += gridStep) {
3663
3839
  const point = { x, y };
3664
3840
  if (isValidPoint(point)) {
3665
- const dist = distance5(point, avgPoint);
3841
+ const dist = distance6(point, avgPoint);
3666
3842
  if (dist < bestDistance) {
3667
3843
  bestDistance = dist;
3668
3844
  bestPoint = point;
@@ -3697,7 +3873,7 @@ function findClosestPointToABCWithinBounds(A, B, C, radius, bounds) {
3697
3873
  const validBoundaryPoints = boundaryPoints.filter(isValidPoint);
3698
3874
  if (validBoundaryPoints.length > 0) {
3699
3875
  validBoundaryPoints.sort(
3700
- (a, b) => distance5(a, avgPoint) - distance5(b, avgPoint)
3876
+ (a, b) => distance6(a, avgPoint) - distance6(b, avgPoint)
3701
3877
  );
3702
3878
  return validBoundaryPoints[0];
3703
3879
  }
@@ -3705,9 +3881,9 @@ function findClosestPointToABCWithinBounds(A, B, C, radius, bounds) {
3705
3881
  let leastBadPoint = { x: bounds.minX, y: bounds.minY };
3706
3882
  for (const point of [...allCandidates, ...boundaryPoints]) {
3707
3883
  if (point.x >= bounds.minX && point.x <= bounds.maxX && point.y >= bounds.minY && point.y <= bounds.maxY) {
3708
- const violationA = Math.max(0, radius - distance5(point, A));
3709
- const violationB = Math.max(0, radius - distance5(point, B));
3710
- const violationC = Math.max(0, radius - distance5(point, C));
3884
+ const violationA = Math.max(0, radius - distance6(point, A));
3885
+ const violationB = Math.max(0, radius - distance6(point, B));
3886
+ const violationC = Math.max(0, radius - distance6(point, C));
3711
3887
  const totalViolation = violationA + violationB + violationC;
3712
3888
  if (totalViolation < minViolation) {
3713
3889
  minViolation = totalViolation;
@@ -3722,12 +3898,62 @@ function findClosestPointToABCWithinBounds(A, B, C, radius, bounds) {
3722
3898
  function findPointToGetAroundCircle(A, C, Q) {
3723
3899
  const B = computeTangentPoint(C, A, Q.center, Q.radius);
3724
3900
  const D = computeTangentPoint(A, C, Q.center, Q.radius);
3725
- const E = computeIntersection(
3726
- { x: C.x, y: C.y },
3727
- { x: B.x, y: B.y },
3728
- { x: A.x, y: A.y },
3729
- { x: D.x, y: D.y }
3730
- );
3901
+ const distBC = distance2(B, C);
3902
+ const distAD = distance2(A, D);
3903
+ const minDistThreshold = 1e-6;
3904
+ const BIsValid = distBC > minDistThreshold;
3905
+ const DIsValid = distAD > minDistThreshold;
3906
+ let E;
3907
+ if (!BIsValid || !DIsValid) {
3908
+ const midAC = {
3909
+ x: (A.x + C.x) / 2,
3910
+ y: (A.y + C.y) / 2
3911
+ };
3912
+ const distFromCenter = distance2(midAC, Q.center);
3913
+ if (distFromCenter < Q.radius * 1.1) {
3914
+ const dirFromCenter = {
3915
+ x: (midAC.x - Q.center.x) / distFromCenter,
3916
+ y: (midAC.y - Q.center.y) / distFromCenter
3917
+ };
3918
+ E = {
3919
+ x: Q.center.x + dirFromCenter.x * Q.radius * 1.2,
3920
+ y: Q.center.y + dirFromCenter.y * Q.radius * 1.2
3921
+ };
3922
+ } else {
3923
+ E = midAC;
3924
+ }
3925
+ } else {
3926
+ E = {
3927
+ x: (B.x + D.x) / 2,
3928
+ y: (B.y + D.y) / 2
3929
+ };
3930
+ const distBE = distance2(B, E);
3931
+ const distDE = distance2(D, E);
3932
+ if (Math.abs(distBE - distDE) > Math.min(distBE, distDE) * 0.5) {
3933
+ const distAB = distance2(A, B);
3934
+ const distCD = distance2(C, D);
3935
+ const totalDist = distAB + distCD;
3936
+ if (totalDist > minDistThreshold) {
3937
+ const weightB = distCD / totalDist;
3938
+ const weightD = distAB / totalDist;
3939
+ E = {
3940
+ x: B.x * weightB + D.x * weightD,
3941
+ y: B.y * weightB + D.y * weightD
3942
+ };
3943
+ }
3944
+ }
3945
+ const distEToCenter = distance2(E, Q.center);
3946
+ if (distEToCenter < Q.radius * 1.05) {
3947
+ const dirFromCenter = {
3948
+ x: (E.x - Q.center.x) / distEToCenter,
3949
+ y: (E.y - Q.center.y) / distEToCenter
3950
+ };
3951
+ E = {
3952
+ x: Q.center.x + dirFromCenter.x * Q.radius * 1.2,
3953
+ y: Q.center.y + dirFromCenter.y * Q.radius * 1.2
3954
+ };
3955
+ }
3956
+ }
3731
3957
  return { B, D, E };
3732
3958
  }
3733
3959
  function computeTangentPoint(observationPoint, referencePoint, circleCenter, radius) {
@@ -3736,8 +3962,29 @@ function computeTangentPoint(observationPoint, referencePoint, circleCenter, rad
3736
3962
  circleCenter.y - observationPoint.y
3737
3963
  ];
3738
3964
  const CQLength = Math.sqrt(CQ[0] * CQ[0] + CQ[1] * CQ[1]);
3739
- if (CQLength < radius) {
3740
- return observationPoint;
3965
+ if (CQLength <= radius) {
3966
+ if (CQLength < 1e-8) {
3967
+ const refVec = [
3968
+ referencePoint.x - observationPoint.x,
3969
+ referencePoint.y - observationPoint.y
3970
+ ];
3971
+ const refLength = Math.sqrt(refVec[0] * refVec[0] + refVec[1] * refVec[1]);
3972
+ if (refLength < 1e-8) {
3973
+ return {
3974
+ x: circleCenter.x + radius,
3975
+ y: circleCenter.y
3976
+ };
3977
+ }
3978
+ return {
3979
+ x: circleCenter.x + refVec[0] / refLength * radius,
3980
+ y: circleCenter.y + refVec[1] / refLength * radius
3981
+ };
3982
+ }
3983
+ const CQUnit2 = [CQ[0] / CQLength, CQ[1] / CQLength];
3984
+ return {
3985
+ x: circleCenter.x - CQUnit2[0] * radius,
3986
+ y: circleCenter.y - CQUnit2[1] * radius
3987
+ };
3741
3988
  }
3742
3989
  const CR = [
3743
3990
  referencePoint.x - observationPoint.x,
@@ -3761,23 +4008,108 @@ function computeTangentPoint(observationPoint, referencePoint, circleCenter, rad
3761
4008
  y: observationPoint.y + d * unitToTangent[1]
3762
4009
  };
3763
4010
  }
3764
- function computeIntersection(p1, p2, p3, p4) {
3765
- const a1 = p2.y - p1.y;
3766
- const b1 = p1.x - p2.x;
3767
- const c1 = a1 * p1.x + b1 * p1.y;
3768
- const a2 = p4.y - p3.y;
3769
- const b2 = p3.x - p4.x;
3770
- const c2 = a2 * p3.x + b2 * p3.y;
3771
- const det = a1 * b2 - a2 * b1;
3772
- if (Math.abs(det) < 1e-8) {
3773
- return {
3774
- x: (p1.x + p3.x) / 2,
3775
- y: (p1.y + p3.y) / 2
3776
- };
4011
+ function distance2(p1, p2) {
4012
+ const dx = p2.x - p1.x;
4013
+ const dy = p2.y - p1.y;
4014
+ return Math.sqrt(dx * dx + dy * dy);
4015
+ }
4016
+
4017
+ // lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/calculateSideTraversal.ts
4018
+ var EPSILON2 = 1e-9;
4019
+ function calculateSegmentTraversal(startPoint, endPoint, bounds) {
4020
+ const startAngle = pointToAngle(startPoint, bounds);
4021
+ let endAngle = pointToAngle(endPoint, bounds);
4022
+ if (endAngle < startAngle) {
4023
+ endAngle += 2 * Math.PI;
4024
+ }
4025
+ if (Math.abs(endAngle - startAngle) < EPSILON2) {
4026
+ return { left: 0, top: 0, right: 0, bottom: 0 };
4027
+ }
4028
+ return calculateSidePercentages(startAngle, endAngle, bounds);
4029
+ }
4030
+ function calculateTraversalPercentages(A, B, C, bounds) {
4031
+ const percentagesAB = calculateSegmentTraversal(A, B, bounds);
4032
+ const percentagesBC = calculateSegmentTraversal(B, C, bounds);
4033
+ const totalPercentages = {
4034
+ left: Math.min(1, percentagesAB.left + percentagesBC.left),
4035
+ top: Math.min(1, percentagesAB.top + percentagesBC.top),
4036
+ right: Math.min(1, percentagesAB.right + percentagesBC.right),
4037
+ bottom: Math.min(1, percentagesAB.bottom + percentagesBC.bottom)
4038
+ };
4039
+ for (const key in totalPercentages) {
4040
+ if (Math.abs(totalPercentages[key]) < EPSILON2) {
4041
+ totalPercentages[key] = 0;
4042
+ }
4043
+ }
4044
+ return totalPercentages;
4045
+ }
4046
+ function pointToAngle(point, bounds) {
4047
+ const width = bounds.maxX - bounds.minX;
4048
+ const height = bounds.maxY - bounds.minY;
4049
+ if (width < EPSILON2 && height < EPSILON2) return 0;
4050
+ const perimeter = 2 * (width + height);
4051
+ if (perimeter < EPSILON2) return 0;
4052
+ let distance6 = 0;
4053
+ if (Math.abs(point.y - bounds.maxY) < EPSILON2 && point.x >= bounds.minX - EPSILON2 && point.x <= bounds.maxX + EPSILON2) {
4054
+ distance6 = Math.max(0, Math.min(width, point.x - bounds.minX));
4055
+ } else if (Math.abs(point.x - bounds.maxX) < EPSILON2 && point.y >= bounds.minY - EPSILON2 && point.y <= bounds.maxY + EPSILON2) {
4056
+ distance6 = width + Math.max(0, Math.min(height, bounds.maxY - point.y));
4057
+ } else if (Math.abs(point.y - bounds.minY) < EPSILON2 && point.x >= bounds.minX - EPSILON2 && point.x <= bounds.maxX + EPSILON2) {
4058
+ distance6 = width + height + Math.max(0, Math.min(width, bounds.maxX - point.x));
4059
+ } else if (Math.abs(point.x - bounds.minX) < EPSILON2 && point.y >= bounds.minY - EPSILON2 && point.y <= bounds.maxY + EPSILON2) {
4060
+ distance6 = width + height + width + Math.max(0, Math.min(height, point.y - bounds.minY));
4061
+ } else {
4062
+ throw new Error(
4063
+ `Point (${point.x}, ${point.y}) does not lie on the boundary defined by ${JSON.stringify(bounds)}`
4064
+ );
4065
+ }
4066
+ distance6 = Math.max(0, Math.min(perimeter, distance6));
4067
+ return perimeter > EPSILON2 ? distance6 / perimeter * (2 * Math.PI) : 0;
4068
+ }
4069
+ function calculateSidePercentages(startAngle, endAngle, bounds) {
4070
+ const width = bounds.maxX - bounds.minX;
4071
+ const height = bounds.maxY - bounds.minY;
4072
+ if (width < EPSILON2 && height < EPSILON2)
4073
+ return { left: 0, top: 0, right: 0, bottom: 0 };
4074
+ const perimeter = 2 * (width + height);
4075
+ if (perimeter < EPSILON2) return { left: 0, top: 0, right: 0, bottom: 0 };
4076
+ const angleTopEnd = width / perimeter * (2 * Math.PI);
4077
+ const angleRightEnd = (width + height) / perimeter * (2 * Math.PI);
4078
+ const angleBottomEnd = (width + width + height) / perimeter * (2 * Math.PI);
4079
+ const angleLeftEnd = 2 * Math.PI;
4080
+ const sides = [
4081
+ { name: "top", start: 0, end: angleTopEnd, length: width },
4082
+ { name: "right", start: angleTopEnd, end: angleRightEnd, length: height },
4083
+ {
4084
+ name: "bottom",
4085
+ start: angleRightEnd,
4086
+ end: angleBottomEnd,
4087
+ length: width
4088
+ },
4089
+ { name: "left", start: angleBottomEnd, end: angleLeftEnd, length: height }
4090
+ // Ends at 2PI
4091
+ ];
4092
+ const result = { left: 0, top: 0, right: 0, bottom: 0 };
4093
+ const totalAngleTraversal = endAngle - startAngle;
4094
+ if (totalAngleTraversal < EPSILON2) return result;
4095
+ for (const side of sides) {
4096
+ const sideAngleRange = side.end - side.start;
4097
+ if (sideAngleRange < EPSILON2 || side.length < EPSILON2) continue;
4098
+ const overlapStart = Math.max(startAngle, side.start);
4099
+ const overlapEnd = Math.min(endAngle, side.end);
4100
+ if (overlapStart < overlapEnd - EPSILON2) {
4101
+ const traversedAngleOnSide = overlapEnd - overlapStart;
4102
+ const percentage = traversedAngleOnSide / sideAngleRange;
4103
+ result[side.name] += Math.max(0, percentage);
4104
+ }
4105
+ }
4106
+ for (const key in result) {
4107
+ result[key] = Math.max(
4108
+ 0,
4109
+ Math.min(1, result[key])
4110
+ );
3777
4111
  }
3778
- const x = (b2 * c1 - b1 * c2) / det;
3779
- const y = (a1 * c2 - a2 * c1) / det;
3780
- return { x, y };
4112
+ return result;
3781
4113
  }
3782
4114
 
3783
4115
  // lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/SingleTransitionCrossingRouteSolver.ts
@@ -3864,17 +4196,30 @@ var SingleTransitionCrossingRouteSolver = class extends BaseSolver {
3864
4196
  const ntrP1 = transitionRoute.A.z !== flatRouteZ ? transitionRoute.A : transitionRoute.B;
3865
4197
  const marginFromBorderWithTrace = this.obstacleMargin * 2 + this.viaDiameter / 2 + this.traceThickness;
3866
4198
  const marginFromBorderWithoutTrace = this.obstacleMargin + this.viaDiameter / 2;
4199
+ const A = flatRoute.A;
4200
+ const B = ntrP1;
4201
+ const C = flatRoute.B;
4202
+ const sideTraversal = calculateTraversalPercentages(A, B, C, this.bounds);
4203
+ const viaBounds = {
4204
+ minX: this.bounds.minX + (sideTraversal.left > 0.5 ? marginFromBorderWithTrace : marginFromBorderWithoutTrace),
4205
+ minY: this.bounds.minY + (sideTraversal.bottom > 0.5 ? marginFromBorderWithTrace : marginFromBorderWithoutTrace),
4206
+ maxX: this.bounds.maxX - (sideTraversal.right > 0.5 ? marginFromBorderWithTrace : marginFromBorderWithoutTrace),
4207
+ maxY: this.bounds.maxY - (sideTraversal.top > 0.5 ? marginFromBorderWithTrace : marginFromBorderWithoutTrace)
4208
+ };
4209
+ if (viaBounds.maxY < viaBounds.minY) {
4210
+ viaBounds.minY = (viaBounds.minY + viaBounds.maxY) / 2;
4211
+ viaBounds.maxY = viaBounds.minY;
4212
+ }
4213
+ if (viaBounds.maxX < viaBounds.minX) {
4214
+ viaBounds.minX = (viaBounds.minX + viaBounds.maxX) / 2;
4215
+ viaBounds.maxX = viaBounds.minX;
4216
+ }
3867
4217
  return findClosestPointToABCWithinBounds(
3868
- flatRoute.A,
3869
- flatRoute.B,
3870
- ntrP1,
4218
+ A,
4219
+ B,
4220
+ C,
3871
4221
  marginFromBorderWithTrace,
3872
- {
3873
- minX: this.bounds.minX + marginFromBorderWithoutTrace,
3874
- minY: this.bounds.minY + marginFromBorderWithoutTrace,
3875
- maxX: this.bounds.maxX - marginFromBorderWithTrace,
3876
- maxY: this.bounds.maxY - marginFromBorderWithTrace
3877
- }
4222
+ viaBounds
3878
4223
  );
3879
4224
  }
3880
4225
  /**
@@ -4662,13 +5007,13 @@ function buildMinimumSpanningTree(points) {
4662
5007
  if (point.x === neighbor.x && point.y === neighbor.y) {
4663
5008
  continue;
4664
5009
  }
4665
- const distance5 = Math.sqrt(
5010
+ const distance6 = Math.sqrt(
4666
5011
  (point.x - neighbor.x) ** 2 + (point.y - neighbor.y) ** 2
4667
5012
  );
4668
5013
  edges.push({
4669
5014
  from: point,
4670
5015
  to: neighbor,
4671
- weight: distance5
5016
+ weight: distance6
4672
5017
  });
4673
5018
  }
4674
5019
  }
@@ -5137,6 +5482,7 @@ var convertSrjToGraphicsObject = (srj) => {
5137
5482
  const circles = [];
5138
5483
  const points = [];
5139
5484
  const colorMap = getColorMap(srj);
5485
+ const layerCount = 2;
5140
5486
  if (srj.connections) {
5141
5487
  for (const connection of srj.connections) {
5142
5488
  for (const point of connection.pointsToConnect) {
@@ -5144,6 +5490,7 @@ var convertSrjToGraphicsObject = (srj) => {
5144
5490
  x: point.x,
5145
5491
  y: point.y,
5146
5492
  color: colorMap[connection.name],
5493
+ layer: point.layer ?? ("z" in point ? mapZToLayerName(point.z, layerCount) : "top"),
5147
5494
  label: `${connection.name} (${point.layer})`
5148
5495
  });
5149
5496
  }
@@ -5160,7 +5507,8 @@ var convertSrjToGraphicsObject = (srj) => {
5160
5507
  radius: 0.3,
5161
5508
  // 0.6 via diameter
5162
5509
  fill: "blue",
5163
- stroke: "none"
5510
+ stroke: "none",
5511
+ layer: "z0,1"
5164
5512
  });
5165
5513
  } else if (routePoint.route_type === "wire" && nextRoutePoint.route_type === "wire" && nextRoutePoint.layer === routePoint.layer) {
5166
5514
  lines.push({
@@ -5168,6 +5516,7 @@ var convertSrjToGraphicsObject = (srj) => {
5168
5516
  { x: routePoint.x, y: routePoint.y },
5169
5517
  { x: nextRoutePoint.x, y: nextRoutePoint.y }
5170
5518
  ],
5519
+ layer: `z${mapLayerNameToZ(routePoint.layer, layerCount)}`,
5171
5520
  strokeWidth: 0.15,
5172
5521
  strokeColor: safeTransparentize(
5173
5522
  {
@@ -5191,7 +5540,8 @@ var convertSrjToGraphicsObject = (srj) => {
5191
5540
  center: o.center,
5192
5541
  width: o.width,
5193
5542
  height: o.height,
5194
- fill: "rgba(255,0,0,0.5)"
5543
+ fill: "rgba(255,0,0,0.5)",
5544
+ layer: `z${o.layers.map(mapLayerNameToZ).join(",")}`
5195
5545
  })
5196
5546
  ),
5197
5547
  circles,
@@ -5204,23 +5554,26 @@ var convertSrjToGraphicsObject = (srj) => {
5204
5554
  function getNodesNearNode(params) {
5205
5555
  const { nodeId, nodeIdToSegmentIds, segmentIdToNodeIds, hops } = params;
5206
5556
  if (hops === 0) return [nodeId];
5207
- const segments = nodeIdToSegmentIds.get(nodeId);
5208
- const nodes = /* @__PURE__ */ new Set();
5209
- for (const segmentId of segments) {
5210
- const adjacentNodeIds = segmentIdToNodeIds.get(segmentId);
5211
- for (const adjacentNodeId of adjacentNodeIds) {
5212
- const ancestors = getNodesNearNode({
5213
- nodeId: adjacentNodeId,
5214
- nodeIdToSegmentIds,
5215
- segmentIdToNodeIds,
5216
- hops: hops - 1
5217
- });
5218
- for (const ancestor of ancestors) {
5219
- nodes.add(ancestor);
5557
+ const visitedNodes = /* @__PURE__ */ new Set([nodeId]);
5558
+ const exploreQueue = [{ nodeId, remainingHops: hops }];
5559
+ while (exploreQueue.length > 0) {
5560
+ const { nodeId: node, remainingHops } = exploreQueue.shift();
5561
+ if (remainingHops === 0) continue;
5562
+ const segments = nodeIdToSegmentIds.get(node) || [];
5563
+ for (const segmentId of segments) {
5564
+ const adjacentNodeIds = segmentIdToNodeIds.get(segmentId) || [];
5565
+ for (const adjacentNodeId of adjacentNodeIds) {
5566
+ if (!visitedNodes.has(adjacentNodeId)) {
5567
+ visitedNodes.add(adjacentNodeId);
5568
+ exploreQueue.push({
5569
+ nodeId: adjacentNodeId,
5570
+ remainingHops: remainingHops - 1
5571
+ });
5572
+ }
5220
5573
  }
5221
5574
  }
5222
5575
  }
5223
- return Array.from(nodes);
5576
+ return Array.from(visitedNodes);
5224
5577
  }
5225
5578
 
5226
5579
  // lib/solvers/UnravelSolver/createPointModificationsHash.ts
@@ -6005,7 +6358,7 @@ var getDedupedSegments = (assignedSegments) => {
6005
6358
  const dedupedSegPointMap = /* @__PURE__ */ new Map();
6006
6359
  let highestSegmentId = -1;
6007
6360
  for (const seg of assignedSegments) {
6008
- const segKey = `${seg.start.x}-${seg.start.y}-${seg.end.x}-${seg.end.y}`;
6361
+ const segKey = `${seg.start.x}-${seg.start.y}-${seg.end.x}-${seg.end.y}-${seg.availableZ.join(",")}`;
6009
6362
  const existingSeg = dedupedSegPointMap.get(segKey);
6010
6363
  if (!existingSeg) {
6011
6364
  highestSegmentId++;
@@ -6019,36 +6372,48 @@ var getDedupedSegments = (assignedSegments) => {
6019
6372
  return dedupedSegments;
6020
6373
  };
6021
6374
 
6022
- // lib/utils/getIntraNodeCrossingsFromSegments.ts
6023
- var getIntraNodeCrossingsFromSegments = (segments) => {
6375
+ // lib/solvers/UnravelSolver/calculateCrossingProbabilityOfFailure.ts
6376
+ var calculateNodeProbabilityOfFailure = (node, numSameLayerCrossings, numEntryExitLayerChanges, numTransitionCrossings) => {
6377
+ if (node?._containsTarget) return 0;
6378
+ const totalCapacity = getTunedTotalCapacity1(node);
6379
+ const estNumVias = numSameLayerCrossings * 0.82 + numEntryExitLayerChanges * 0.41 + numTransitionCrossings * 0.2;
6380
+ const estUsedCapacity = (estNumVias / 2) ** 1.1;
6381
+ const approxProb = estUsedCapacity / totalCapacity;
6382
+ return approxProb;
6383
+ };
6384
+
6385
+ // lib/utils/getIntraNodeCrossingsFromSegmentPoints.ts
6386
+ var getIntraNodeCrossingsFromSegmentPoints = (segmentPoints) => {
6024
6387
  let numSameLayerCrossings = 0;
6025
- const pointPairs = [];
6026
- const transitionPairPoints = [];
6027
6388
  let numEntryExitLayerChanges = 0;
6028
- const portPoints = segments.flatMap((seg) => seg.assignedPoints);
6029
- for (const { connectionName: aConnName, point: A } of portPoints) {
6030
- if (pointPairs.some((p) => p.connectionName === aConnName)) {
6031
- continue;
6032
- }
6033
- if (transitionPairPoints.some((p) => p.connectionName === aConnName)) {
6034
- continue;
6389
+ let numTransitionCrossings = 0;
6390
+ const connectionGroups = /* @__PURE__ */ new Map();
6391
+ for (const point of segmentPoints) {
6392
+ if (!connectionGroups.has(point.connectionName)) {
6393
+ connectionGroups.set(point.connectionName, []);
6035
6394
  }
6036
- const pointPair = {
6037
- connectionName: aConnName,
6038
- z: A.z,
6039
- points: [A]
6040
- };
6041
- for (const { connectionName: bConnName, point: B } of portPoints) {
6042
- if (aConnName !== bConnName) continue;
6043
- if (A === B) continue;
6044
- pointPair.points.push(B);
6045
- if (pointPair.points.some((p) => p.z !== pointPair.z)) {
6395
+ connectionGroups.get(point.connectionName).push(point);
6396
+ }
6397
+ const pointPairs = [];
6398
+ const transitionPairPoints = [];
6399
+ for (const [connectionName, points] of connectionGroups.entries()) {
6400
+ if (points.length < 2) continue;
6401
+ const firstPoint = points[0];
6402
+ for (let i = 1; i < points.length; i++) {
6403
+ const secondPoint = points[i];
6404
+ const pointPair = {
6405
+ connectionName,
6406
+ z: firstPoint.z,
6407
+ points: [firstPoint, secondPoint]
6408
+ };
6409
+ if (firstPoint.z !== secondPoint.z) {
6046
6410
  numEntryExitLayerChanges++;
6047
- transitionPairPoints.push(pointPair);
6048
- break;
6411
+ transitionPairPoints.push({
6412
+ connectionName,
6413
+ points: [firstPoint, secondPoint]
6414
+ });
6049
6415
  } else {
6050
6416
  pointPairs.push(pointPair);
6051
- break;
6052
6417
  }
6053
6418
  }
6054
6419
  }
@@ -6066,7 +6431,6 @@ var getIntraNodeCrossingsFromSegments = (segments) => {
6066
6431
  }
6067
6432
  }
6068
6433
  }
6069
- let numTransitionCrossings = 0;
6070
6434
  for (let i = 0; i < transitionPairPoints.length; i++) {
6071
6435
  for (let j = i + 1; j < transitionPairPoints.length; j++) {
6072
6436
  const pair1 = transitionPairPoints[i];
@@ -6102,16 +6466,6 @@ var getIntraNodeCrossingsFromSegments = (segments) => {
6102
6466
  };
6103
6467
  };
6104
6468
 
6105
- // lib/solvers/UnravelSolver/calculateCrossingProbabilityOfFailure.ts
6106
- var calculateNodeProbabilityOfFailure = (node, numSameLayerCrossings, numEntryExitLayerChanges, numTransitionCrossings) => {
6107
- if (node?._containsTarget) return 0;
6108
- const totalCapacity = getTunedTotalCapacity1(node);
6109
- const estNumVias = numSameLayerCrossings * 0.82 + numEntryExitLayerChanges * 0.41 + numTransitionCrossings * 0.2;
6110
- const estUsedCapacity = (estNumVias / 2) ** 1.1;
6111
- const approxProb = estUsedCapacity / totalCapacity;
6112
- return approxProb;
6113
- };
6114
-
6115
6469
  // lib/solvers/UnravelSolver/UnravelMultiSectionSolver.ts
6116
6470
  var UnravelMultiSectionSolver = class extends BaseSolver {
6117
6471
  nodeMap;
@@ -6185,8 +6539,10 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
6185
6539
  numSameLayerCrossings,
6186
6540
  numEntryExitLayerChanges,
6187
6541
  numTransitionCrossings
6188
- } = getIntraNodeCrossingsFromSegments(
6189
- this.nodeIdToSegmentIds.get(node.capacityMeshNodeId)?.map((segId) => this.dedupedSegmentMap.get(segId)) || []
6542
+ } = getIntraNodeCrossingsFromSegmentPoints(
6543
+ (this.nodeToSegmentPointMap.get(node.capacityMeshNodeId) ?? []).map(
6544
+ (segPointId) => this.segmentPointMap.get(segPointId)
6545
+ )
6190
6546
  );
6191
6547
  const probabilityOfFailure = calculateNodeProbabilityOfFailure(
6192
6548
  node,
@@ -6249,12 +6605,18 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
6249
6605
  segmentPoint.y = pointModification.y ?? segmentPoint.y;
6250
6606
  segmentPoint.z = pointModification.z ?? segmentPoint.z;
6251
6607
  }
6252
- }
6253
- for (const nodeId of this.activeSolver.unravelSection.allNodeIds) {
6254
- this.nodePfMap.set(
6255
- nodeId,
6256
- this.computeNodePf(this.nodeMap.get(nodeId))
6257
- );
6608
+ const possiblyImpactedNodeIds = getNodesNearNode({
6609
+ hops: this.activeSolver.MUTABLE_HOPS + 2,
6610
+ nodeId: this.activeSolver.rootNodeId,
6611
+ nodeIdToSegmentIds: this.nodeIdToSegmentIds,
6612
+ segmentIdToNodeIds: this.segmentIdToNodeIds
6613
+ });
6614
+ for (const nodeId of possiblyImpactedNodeIds) {
6615
+ this.nodePfMap.set(
6616
+ nodeId,
6617
+ this.computeNodePf(this.nodeMap.get(nodeId))
6618
+ );
6619
+ }
6258
6620
  }
6259
6621
  this.activeSolver = null;
6260
6622
  }
@@ -6489,11 +6851,11 @@ var CapacityPathingSolver = class extends BaseSolver {
6489
6851
  let closestNode = this.nodes[0];
6490
6852
  let minDistance = Number.MAX_VALUE;
6491
6853
  for (const node of nodesWithTargets) {
6492
- const distance5 = Math.sqrt(
6854
+ const distance6 = Math.sqrt(
6493
6855
  (node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
6494
6856
  );
6495
- if (distance5 < minDistance) {
6496
- minDistance = distance5;
6857
+ if (distance6 < minDistance) {
6858
+ minDistance = distance6;
6497
6859
  closestNode = node;
6498
6860
  }
6499
6861
  }
@@ -7085,7 +7447,7 @@ var CapacityNodeTree = class {
7085
7447
  };
7086
7448
 
7087
7449
  // lib/solvers/SingleLayerNodeMerger/SingleLayerNodeMergerSolver.ts
7088
- var EPSILON = 5e-3;
7450
+ var EPSILON3 = 5e-3;
7089
7451
  var SingleLayerNodeMergerSolver = class extends BaseSolver {
7090
7452
  nodeMap;
7091
7453
  currentBatchNodeIds;
@@ -7244,7 +7606,7 @@ var SingleLayerNodeMergerSolver = class extends BaseSolver {
7244
7606
  adjacentNodesToLeft.reduce((acc, adjNode) => {
7245
7607
  return acc + adjNode.height;
7246
7608
  }, 0) - rootNode.height
7247
- ) < EPSILON;
7609
+ ) < EPSILON3;
7248
7610
  if (leftAdjNodesTakeUpEntireHeight && leftAdjNodesAreAllSameSize) {
7249
7611
  rootNode.width += leftAdjNodeWidth;
7250
7612
  rootNode.center.x = rootNode.center.x - leftAdjNodeWidth / 2;
@@ -7264,7 +7626,7 @@ var SingleLayerNodeMergerSolver = class extends BaseSolver {
7264
7626
  adjacentNodesToRight.reduce((acc, adjNode) => {
7265
7627
  return acc + adjNode.height;
7266
7628
  }, 0) - rootNode.height
7267
- ) < EPSILON;
7629
+ ) < EPSILON3;
7268
7630
  if (rightAdjNodesTakeUpEntireHeight && rightAdjNodesAreAllSameSize) {
7269
7631
  rootNode.width += rightAdjNodeWidth;
7270
7632
  rootNode.center.x = rootNode.center.x + rightAdjNodeWidth / 2;
@@ -7284,7 +7646,7 @@ var SingleLayerNodeMergerSolver = class extends BaseSolver {
7284
7646
  adjacentNodesToTop.reduce((acc, adjNode) => {
7285
7647
  return acc + adjNode.width;
7286
7648
  }, 0) - rootNode.width
7287
- ) < EPSILON;
7649
+ ) < EPSILON3;
7288
7650
  if (topAdjNodesTakeUpEntireWidth && topAdjNodesAreAllSameSize) {
7289
7651
  rootNode.height += topAdjNodeHeight;
7290
7652
  rootNode.center.y = rootNode.center.y + topAdjNodeHeight / 2;
@@ -7304,7 +7666,7 @@ var SingleLayerNodeMergerSolver = class extends BaseSolver {
7304
7666
  adjacentNodesToBottom.reduce((acc, adjNode) => {
7305
7667
  return acc + adjNode.width;
7306
7668
  }, 0) - rootNode.width
7307
- ) < EPSILON;
7669
+ ) < EPSILON3;
7308
7670
  if (bottomAdjNodesTakeUpEntireWidth && bottomAdjNodesAreAllSameSize) {
7309
7671
  rootNode.height += bottomAdjNodeHeight;
7310
7672
  rootNode.center.y = rootNode.center.y - bottomAdjNodeHeight / 2;
@@ -7401,6 +7763,15 @@ var SingleSimplifiedPathSolver = class extends BaseSolver {
7401
7763
  this.newRoute = [this.inputRoute.route[0]];
7402
7764
  this.newVias = [];
7403
7765
  }
7766
+ getConstructorParams() {
7767
+ return {
7768
+ inputRoute: this.inputRoute,
7769
+ otherHdRoutes: this.otherHdRoutes,
7770
+ obstacles: this.obstacles,
7771
+ connMap: this.connMap.netMap,
7772
+ colorMap: this.colorMap
7773
+ };
7774
+ }
7404
7775
  get simplifiedRoute() {
7405
7776
  return {
7406
7777
  connectionName: this.inputRoute.connectionName,
@@ -7489,6 +7860,14 @@ var SingleSimplifiedPathSolver = class extends BaseSolver {
7489
7860
  });
7490
7861
  }
7491
7862
  }
7863
+ if ("filteredObstaclePathSegments" in this) {
7864
+ const filteredObstaclePathSegments = this.filteredObstaclePathSegments;
7865
+ for (const [start, end] of filteredObstaclePathSegments) {
7866
+ graphics.lines.push({
7867
+ points: [start, end]
7868
+ });
7869
+ }
7870
+ }
7492
7871
  return graphics;
7493
7872
  }
7494
7873
  };
@@ -7541,23 +7920,23 @@ function pointToSegmentDistance4(P, Q1, Q2) {
7541
7920
  const w = { x: P.x - Q1.x, y: P.y - Q1.y };
7542
7921
  const c1 = dotProduct(w, v);
7543
7922
  if (c1 <= 0) {
7544
- return distance4(P, Q1);
7923
+ return distance5(P, Q1);
7545
7924
  }
7546
7925
  const c2 = dotProduct(v, v);
7547
7926
  if (c2 <= c1) {
7548
- return distance4(P, Q2);
7927
+ return distance5(P, Q2);
7549
7928
  }
7550
7929
  const b = c1 / c2;
7551
7930
  const Pb = {
7552
7931
  x: Q1.x + b * v.x,
7553
7932
  y: Q1.y + b * v.y
7554
7933
  };
7555
- return distance4(P, Pb);
7934
+ return distance5(P, Pb);
7556
7935
  }
7557
7936
  function dotProduct(v1, v2) {
7558
7937
  return v1.x * v2.x + v1.y * v2.y;
7559
7938
  }
7560
- function distance4(p1, p2) {
7939
+ function distance5(p1, p2) {
7561
7940
  const dx = p2.x - p1.x;
7562
7941
  const dy = p2.y - p1.y;
7563
7942
  return Math.sqrt(dx * dx + dy * dy);
@@ -7593,23 +7972,33 @@ var getSegmentBounds = (segment) => {
7593
7972
  };
7594
7973
  };
7595
7974
  var SegmentTree = class {
7975
+ // traceThickness + obstacleMargin
7596
7976
  constructor(segments) {
7597
7977
  this.segments = segments;
7598
7978
  this.buckets = /* @__PURE__ */ new Map();
7979
+ const segmentsById = /* @__PURE__ */ new Map();
7599
7980
  for (const segment of segments) {
7981
+ const segmentKey = this.getSegmentKey(segment);
7982
+ if (segmentsById.has(segmentKey)) continue;
7983
+ segmentsById.set(segmentKey, segment);
7600
7984
  const bounds = getSegmentBounds(segment);
7601
- const bucketMinX = Math.floor(bounds.minX / this.CELL_SIZE) * this.CELL_SIZE;
7602
- const bucketMinY = Math.floor(bounds.minY / this.CELL_SIZE) * this.CELL_SIZE;
7603
- for (let x = bucketMinX; x <= bounds.maxX; x += this.CELL_SIZE) {
7604
- for (let y = bucketMinY; y <= bounds.maxY; y += this.CELL_SIZE) {
7605
- const bucketKey = this.getBucketKey(x, y);
7985
+ const minIndexX = Math.floor(bounds.minX / this.CELL_SIZE);
7986
+ const maxIndexX = Math.floor(bounds.maxX / this.CELL_SIZE);
7987
+ const minIndexY = Math.floor(bounds.minY / this.CELL_SIZE);
7988
+ const maxIndexY = Math.floor(bounds.maxY / this.CELL_SIZE);
7989
+ for (let ix = minIndexX; ix <= maxIndexX; ix++) {
7990
+ for (let iy = minIndexY; iy <= maxIndexY; iy++) {
7991
+ const bucketKey = `${ix}x${iy}`;
7606
7992
  const bucket = this.buckets.get(bucketKey);
7993
+ const segmentWithId = [
7994
+ segment[0],
7995
+ segment[1],
7996
+ segmentKey
7997
+ ];
7607
7998
  if (!bucket) {
7608
- this.buckets.set(bucketKey, [
7609
- [segment[0], segment[1], this.getSegmentKey(segment)]
7610
- ]);
7999
+ this.buckets.set(bucketKey, [segmentWithId]);
7611
8000
  } else {
7612
- bucket.push([segment[0], segment[1], this.getSegmentKey(segment)]);
8001
+ bucket.push(segmentWithId);
7613
8002
  }
7614
8003
  }
7615
8004
  }
@@ -7617,6 +8006,7 @@ var SegmentTree = class {
7617
8006
  }
7618
8007
  buckets;
7619
8008
  CELL_SIZE = 0.4;
8009
+ SEGMENT_MARGIN = 0.4;
7620
8010
  getBucketKey(x, y) {
7621
8011
  return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
7622
8012
  }
@@ -7626,21 +8016,26 @@ var SegmentTree = class {
7626
8016
  getSegmentsThatCouldIntersect(A, B) {
7627
8017
  const segments = [];
7628
8018
  const alreadyAddedSegments = /* @__PURE__ */ new Set();
7629
- const minX = Math.min(A.x, B.x);
7630
- const minY = Math.min(A.y, B.y);
7631
- const maxX = Math.max(A.x, B.x);
7632
- const maxY = Math.max(A.y, B.y);
7633
- const bucketMinX = Math.floor(minX / this.CELL_SIZE) * this.CELL_SIZE;
7634
- const bucketMinY = Math.floor(minY / this.CELL_SIZE) * this.CELL_SIZE;
7635
- for (let x = bucketMinX; x <= maxX; x += this.CELL_SIZE) {
7636
- for (let y = bucketMinY; y <= maxY; y += this.CELL_SIZE) {
7637
- const bucketKey = this.getBucketKey(x, y);
7638
- const bucket = this.buckets.get(bucketKey) || [];
7639
- for (const segment of bucket) {
7640
- const key = segment[2];
7641
- if (alreadyAddedSegments.has(key)) continue;
7642
- alreadyAddedSegments.add(key);
7643
- segments.push(segment);
8019
+ const minX = Math.min(A.x, B.x) - this.SEGMENT_MARGIN;
8020
+ const minY = Math.min(A.y, B.y) - this.SEGMENT_MARGIN;
8021
+ const maxX = Math.max(A.x, B.x) + this.SEGMENT_MARGIN;
8022
+ const maxY = Math.max(A.y, B.y) + this.SEGMENT_MARGIN;
8023
+ const minIndexX = Math.floor(minX / this.CELL_SIZE);
8024
+ const maxIndexX = Math.floor(maxX / this.CELL_SIZE);
8025
+ const minIndexY = Math.floor(minY / this.CELL_SIZE);
8026
+ const maxIndexY = Math.floor(maxY / this.CELL_SIZE);
8027
+ for (let ix = minIndexX; ix <= maxIndexX; ix++) {
8028
+ for (let iy = minIndexY; iy <= maxIndexY; iy++) {
8029
+ const bucketKey = `${ix}x${iy}`;
8030
+ const bucket = this.buckets.get(bucketKey);
8031
+ if (bucket) {
8032
+ for (const segment of bucket) {
8033
+ const key = segment[2];
8034
+ if (!alreadyAddedSegments.has(key)) {
8035
+ alreadyAddedSegments.add(key);
8036
+ segments.push(segment);
8037
+ }
8038
+ }
7644
8039
  }
7645
8040
  }
7646
8041
  }
@@ -7669,7 +8064,8 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
7669
8064
  filteredObstaclePathSegments = [];
7670
8065
  filteredVias = [];
7671
8066
  segmentTree;
7672
- OBSTACLE_MARGIN = 0.15;
8067
+ OBSTACLE_MARGIN = 0.1;
8068
+ TRACE_THICKNESS = 0.15;
7673
8069
  TAIL_JUMP_RATIO = 0.8;
7674
8070
  constructor(params) {
7675
8071
  super(params);
@@ -7689,6 +8085,14 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
7689
8085
  },
7690
8086
  { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity }
7691
8087
  );
8088
+ const boundsBox = {
8089
+ center: {
8090
+ x: (bounds.minX + bounds.maxX) / 2,
8091
+ y: (bounds.minY + bounds.maxY) / 2
8092
+ },
8093
+ width: bounds.maxX - bounds.minX,
8094
+ height: bounds.maxY - bounds.minY
8095
+ };
7692
8096
  this.filteredObstacles = this.obstacles.filter(
7693
8097
  (obstacle) => !obstacle.connectedTo.some(
7694
8098
  (id) => this.connMap.areIdsConnected(this.inputRoute.connectionName, id)
@@ -7699,11 +8103,11 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
7699
8103
  )) {
7700
8104
  return false;
7701
8105
  }
7702
- const obstacleMinX = obstacle.center.x - obstacle.width / 2 - this.OBSTACLE_MARGIN;
7703
- const obstacleMaxX = obstacle.center.x + obstacle.width / 2 + this.OBSTACLE_MARGIN;
7704
- const obstacleMinY = obstacle.center.y - obstacle.height / 2 - this.OBSTACLE_MARGIN;
7705
- const obstacleMaxY = obstacle.center.y + obstacle.height / 2 + this.OBSTACLE_MARGIN;
7706
- return obstacleMinX <= bounds.maxX && obstacleMaxX >= bounds.minX && obstacleMinY <= bounds.maxY && obstacleMaxY >= bounds.minY;
8106
+ const { distance: distance6 } = computeDistanceBetweenBoxes(boundsBox, obstacle);
8107
+ if (distance6 < this.OBSTACLE_MARGIN + 0.5) {
8108
+ return true;
8109
+ }
8110
+ return false;
7707
8111
  });
7708
8112
  this.filteredObstaclePathSegments = this.otherHdRoutes.flatMap(
7709
8113
  (hdRoute) => {
@@ -7775,15 +8179,15 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
7775
8179
  return p1.x === p2.x && p1.y === p2.y && p1.z === p2.z;
7776
8180
  }
7777
8181
  // Get point at a specific distance along the path
7778
- getPointAtDistance(distance5) {
7779
- distance5 = Math.max(0, Math.min(distance5, this.totalPathLength));
8182
+ getPointAtDistance(distance6) {
8183
+ distance6 = Math.max(0, Math.min(distance6, this.totalPathLength));
7780
8184
  const segment = this.pathSegments.find(
7781
- (seg) => distance5 >= seg.startDistance && distance5 <= seg.endDistance
8185
+ (seg) => distance6 >= seg.startDistance && distance6 <= seg.endDistance
7782
8186
  );
7783
8187
  if (!segment) {
7784
8188
  return this.inputRoute.route[this.inputRoute.route.length - 1];
7785
8189
  }
7786
- const factor = (distance5 - segment.startDistance) / segment.length;
8190
+ const factor = (distance6 - segment.startDistance) / segment.length;
7787
8191
  return {
7788
8192
  x: segment.start.x + factor * (segment.end.x - segment.start.x),
7789
8193
  y: segment.start.y + factor * (segment.end.y - segment.start.y),
@@ -7792,17 +8196,17 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
7792
8196
  };
7793
8197
  }
7794
8198
  // Find nearest index in the original route for a given distance
7795
- getNearestIndexForDistance(distance5) {
7796
- if (distance5 <= 0) return 0;
7797
- if (distance5 >= this.totalPathLength)
8199
+ getNearestIndexForDistance(distance6) {
8200
+ if (distance6 <= 0) return 0;
8201
+ if (distance6 >= this.totalPathLength)
7798
8202
  return this.inputRoute.route.length - 1;
7799
8203
  const segmentIndex = this.pathSegments.findIndex(
7800
- (seg) => distance5 >= seg.startDistance && distance5 <= seg.endDistance
8204
+ (seg) => distance6 >= seg.startDistance && distance6 <= seg.endDistance
7801
8205
  );
7802
8206
  if (segmentIndex === -1) return 0;
7803
8207
  const segment = this.pathSegments[segmentIndex];
7804
8208
  const midDistance = (segment.startDistance + segment.endDistance) / 2;
7805
- return distance5 > midDistance ? segmentIndex + 1 : segmentIndex;
8209
+ return distance6 > midDistance ? segmentIndex + 1 : segmentIndex;
7806
8210
  }
7807
8211
  // Check if a path segment is valid
7808
8212
  isValidPathSegment(start, end) {
@@ -7810,49 +8214,27 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
7810
8214
  if (!obstacle.zLayers?.includes(start.z)) {
7811
8215
  continue;
7812
8216
  }
7813
- const obstacleLeft = obstacle.center.x - obstacle.width / 2 - this.OBSTACLE_MARGIN;
7814
- const obstacleRight = obstacle.center.x + obstacle.width / 2 + this.OBSTACLE_MARGIN;
7815
- const obstacleTop = obstacle.center.y - obstacle.height / 2 - this.OBSTACLE_MARGIN;
7816
- const obstacleBottom = obstacle.center.y + obstacle.height / 2 + this.OBSTACLE_MARGIN;
7817
- if (doSegmentsIntersect(
7818
- { x: start.x, y: start.y },
7819
- { x: end.x, y: end.y },
7820
- { x: obstacleLeft, y: obstacleTop },
7821
- { x: obstacleRight, y: obstacleTop }
7822
- ) || doSegmentsIntersect(
7823
- { x: start.x, y: start.y },
7824
- { x: end.x, y: end.y },
7825
- { x: obstacleRight, y: obstacleTop },
7826
- { x: obstacleRight, y: obstacleBottom }
7827
- ) || doSegmentsIntersect(
7828
- { x: start.x, y: start.y },
7829
- { x: end.x, y: end.y },
7830
- { x: obstacleRight, y: obstacleBottom },
7831
- { x: obstacleLeft, y: obstacleBottom }
7832
- ) || doSegmentsIntersect(
7833
- { x: start.x, y: start.y },
7834
- { x: end.x, y: end.y },
7835
- { x: obstacleLeft, y: obstacleBottom },
7836
- { x: obstacleLeft, y: obstacleTop }
7837
- )) {
8217
+ const distToObstacle = segmentToBoxMinDistance(start, end, obstacle);
8218
+ if (distToObstacle < this.OBSTACLE_MARGIN + this.TRACE_THICKNESS / 2) {
7838
8219
  return false;
7839
8220
  }
7840
8221
  }
7841
8222
  const segmentsThatCouldIntersect = this.segmentTree.getSegmentsThatCouldIntersect(start, end);
7842
- for (const [otherSegA, otherSegB] of segmentsThatCouldIntersect) {
8223
+ for (const [otherSegA, otherSegB, segId] of segmentsThatCouldIntersect) {
7843
8224
  if (otherSegA.z === start.z && otherSegB.z === start.z) {
7844
- if (minimumDistanceBetweenSegments(
8225
+ const distBetweenSegments = minimumDistanceBetweenSegments(
7845
8226
  { x: start.x, y: start.y },
7846
8227
  { x: end.x, y: end.y },
7847
8228
  { x: otherSegA.x, y: otherSegA.y },
7848
8229
  { x: otherSegB.x, y: otherSegB.y }
7849
- ) < this.OBSTACLE_MARGIN) {
8230
+ );
8231
+ if (distBetweenSegments < this.OBSTACLE_MARGIN + this.TRACE_THICKNESS) {
7850
8232
  return false;
7851
8233
  }
7852
8234
  }
7853
8235
  }
7854
8236
  for (const via of this.filteredVias) {
7855
- if (pointToSegmentDistance(via, start, end) < this.OBSTACLE_MARGIN + via.diameter / 2) {
8237
+ if (pointToSegmentDistance(via, start, end) < this.OBSTACLE_MARGIN + via.diameter / 2 + this.TRACE_THICKNESS / 2) {
7856
8238
  return false;
7857
8239
  }
7858
8240
  }
@@ -7904,10 +8286,10 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
7904
8286
  }
7905
8287
  this.currentStepSize = this.maxStepSize;
7906
8288
  }
7907
- moveHead(distance5) {
7908
- this.lastHeadMoveDistance = distance5;
8289
+ moveHead(distance6) {
8290
+ this.lastHeadMoveDistance = distance6;
7909
8291
  this.headDistanceAlongPath = Math.min(
7910
- this.headDistanceAlongPath + distance5,
8292
+ this.headDistanceAlongPath + distance6,
7911
8293
  this.totalPathLength
7912
8294
  );
7913
8295
  }
@@ -8045,9 +8427,9 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
8045
8427
  color: "red",
8046
8428
  label: ["Tentative Head", `z: ${tentativeHead.z}`].join("\n")
8047
8429
  });
8048
- let distance5 = 0;
8049
- while (distance5 < this.totalPathLength) {
8050
- const point = this.getPointAtDistance(distance5);
8430
+ let distance6 = 0;
8431
+ while (distance6 < this.totalPathLength) {
8432
+ const point = this.getPointAtDistance(distance6);
8051
8433
  graphics.circles.push({
8052
8434
  center: {
8053
8435
  x: point.x,
@@ -8056,7 +8438,7 @@ var SingleSimplifiedPathSolver5 = class extends SingleSimplifiedPathSolver {
8056
8438
  radius: 0.05,
8057
8439
  fill: "rgba(100, 100, 100, 0.5)"
8058
8440
  });
8059
- distance5 += this.totalPathLength / 20;
8441
+ distance6 += this.totalPathLength / 20;
8060
8442
  }
8061
8443
  if (this.lastValidPath && this.lastValidPath.length > 1) {
8062
8444
  for (let i = 0; i < this.lastValidPath.length - 1; i++) {