@tscircuit/rectdiff 0.0.13 → 0.0.15

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
@@ -1038,12 +1038,20 @@ function computeDefaultGridSizes(bounds) {
1038
1038
  }
1039
1039
 
1040
1040
  // lib/utils/isFullyOccupiedAtPoint.ts
1041
- function isFullyOccupiedAtPoint(params, point) {
1041
+ import "rbush";
1042
+ function isFullyOccupiedAtPoint(params) {
1043
+ const query = {
1044
+ minX: params.point.x,
1045
+ minY: params.point.y,
1046
+ maxX: params.point.x,
1047
+ maxY: params.point.y
1048
+ };
1042
1049
  for (let z = 0; z < params.layerCount; z++) {
1043
- const obs = params.obstaclesByLayer[z] ?? [];
1044
- const placed = params.placedByLayer[z] ?? [];
1045
- const occ = obs.some((b) => containsPoint(b, point)) || placed.some((b) => containsPoint(b, point));
1046
- if (!occ) return false;
1050
+ const obstacleIdx = params.obstacleIndexByLayer[z];
1051
+ const hasObstacle = !!obstacleIdx && obstacleIdx.search(query).length > 0;
1052
+ const placedIdx = params.placedIndexByLayer[z];
1053
+ const hasPlaced = !!placedIdx && placedIdx.search(query).length > 0;
1054
+ if (!hasObstacle && !hasPlaced) return false;
1047
1055
  }
1048
1056
  return true;
1049
1057
  }
@@ -1057,15 +1065,20 @@ function longestFreeSpanAroundZ(params) {
1057
1065
  layerCount,
1058
1066
  minSpan,
1059
1067
  maxSpan,
1060
- obstaclesByLayer,
1061
- placedByLayer
1068
+ obstacleIndexByLayer,
1069
+ additionalBlockersByLayer
1062
1070
  } = params;
1063
1071
  const isFreeAt = (layer) => {
1064
- const blockers = [
1065
- ...obstaclesByLayer[layer] ?? [],
1066
- ...placedByLayer[layer] ?? []
1067
- ];
1068
- return !blockers.some((b) => containsPoint(b, { x, y }));
1072
+ const query = {
1073
+ minX: x,
1074
+ minY: y,
1075
+ maxX: x,
1076
+ maxY: y
1077
+ };
1078
+ const obstacleIdx = obstacleIndexByLayer[layer];
1079
+ if (obstacleIdx && obstacleIdx.search(query).length > 0) return false;
1080
+ const extras = additionalBlockersByLayer?.[layer] ?? [];
1081
+ return !extras.some((b) => containsPoint(b, { x, y }));
1069
1082
  };
1070
1083
  let lo = z;
1071
1084
  let hi = z;
@@ -1089,8 +1102,8 @@ function computeCandidates3D(params) {
1089
1102
  bounds,
1090
1103
  gridSize,
1091
1104
  layerCount,
1092
- obstaclesByLayer,
1093
- placedByLayer,
1105
+ obstacleIndexByLayer,
1106
+ placedIndexByLayer,
1094
1107
  hardPlacedByLayer
1095
1108
  } = params;
1096
1109
  const out = /* @__PURE__ */ new Map();
@@ -1099,14 +1112,12 @@ function computeCandidates3D(params) {
1099
1112
  if (Math.abs(x - bounds.x) < EPS4 || Math.abs(y - bounds.y) < EPS4 || x > bounds.x + bounds.width - gridSize - EPS4 || y > bounds.y + bounds.height - gridSize - EPS4) {
1100
1113
  continue;
1101
1114
  }
1102
- if (isFullyOccupiedAtPoint(
1103
- {
1104
- layerCount,
1105
- obstaclesByLayer,
1106
- placedByLayer
1107
- },
1108
- { x, y }
1109
- ))
1115
+ if (isFullyOccupiedAtPoint({
1116
+ layerCount,
1117
+ obstacleIndexByLayer,
1118
+ placedIndexByLayer,
1119
+ point: { x, y }
1120
+ }))
1110
1121
  continue;
1111
1122
  let bestSpan = [];
1112
1123
  let bestZ = 0;
@@ -1118,8 +1129,8 @@ function computeCandidates3D(params) {
1118
1129
  layerCount,
1119
1130
  minSpan: 1,
1120
1131
  maxSpan: void 0,
1121
- obstaclesByLayer,
1122
- placedByLayer: hardPlacedByLayer
1132
+ obstacleIndexByLayer,
1133
+ additionalBlockersByLayer: hardPlacedByLayer
1123
1134
  });
1124
1135
  if (s.length > bestSpan.length) {
1125
1136
  bestSpan = s;
@@ -1128,7 +1139,7 @@ function computeCandidates3D(params) {
1128
1139
  }
1129
1140
  const anchorZ = bestSpan.length ? bestSpan[Math.floor(bestSpan.length / 2)] : bestZ;
1130
1141
  const hardAtZ = [
1131
- ...obstaclesByLayer[anchorZ] ?? [],
1142
+ ...obstacleIndexByLayer[anchorZ]?.all() ?? [],
1132
1143
  ...hardPlacedByLayer[anchorZ] ?? []
1133
1144
  ];
1134
1145
  const d = Math.min(
@@ -1203,8 +1214,8 @@ function computeEdgeCandidates3D(params) {
1203
1214
  bounds,
1204
1215
  minSize,
1205
1216
  layerCount,
1206
- obstaclesByLayer,
1207
- placedByLayer,
1217
+ obstacleIndexByLayer,
1218
+ placedIndexByLayer,
1208
1219
  hardPlacedByLayer
1209
1220
  } = params;
1210
1221
  const out = [];
@@ -1212,14 +1223,12 @@ function computeEdgeCandidates3D(params) {
1212
1223
  const dedup = /* @__PURE__ */ new Set();
1213
1224
  const key = (p) => `${p.z}|${p.x.toFixed(6)}|${p.y.toFixed(6)}`;
1214
1225
  function fullyOcc(p) {
1215
- return isFullyOccupiedAtPoint(
1216
- {
1217
- layerCount,
1218
- obstaclesByLayer,
1219
- placedByLayer
1220
- },
1221
- p
1222
- );
1226
+ return isFullyOccupiedAtPoint({
1227
+ layerCount,
1228
+ obstacleIndexByLayer,
1229
+ placedIndexByLayer,
1230
+ point: p
1231
+ });
1223
1232
  }
1224
1233
  function pushIfFree(p) {
1225
1234
  const { x, y, z } = p;
@@ -1227,7 +1236,7 @@ function computeEdgeCandidates3D(params) {
1227
1236
  return;
1228
1237
  if (fullyOcc({ x, y })) return;
1229
1238
  const hard = [
1230
- ...obstaclesByLayer[z] ?? [],
1239
+ ...obstacleIndexByLayer[z]?.all() ?? [],
1231
1240
  ...hardPlacedByLayer[z] ?? []
1232
1241
  ];
1233
1242
  const d = Math.min(
@@ -1244,14 +1253,14 @@ function computeEdgeCandidates3D(params) {
1244
1253
  layerCount,
1245
1254
  minSpan: 1,
1246
1255
  maxSpan: void 0,
1247
- obstaclesByLayer,
1248
- placedByLayer: hardPlacedByLayer
1256
+ obstacleIndexByLayer,
1257
+ additionalBlockersByLayer: hardPlacedByLayer
1249
1258
  });
1250
1259
  out.push({ x, y, z, distance: d, zSpanLen: span.length, isEdgeSeed: true });
1251
1260
  }
1252
1261
  for (let z = 0; z < layerCount; z++) {
1253
1262
  const blockers = [
1254
- ...obstaclesByLayer[z] ?? [],
1263
+ ...obstacleIndexByLayer[z]?.all() ?? [],
1255
1264
  ...hardPlacedByLayer[z] ?? []
1256
1265
  ];
1257
1266
  const corners = [
@@ -1475,21 +1484,58 @@ function resizeSoftOverlaps(params, newIndex) {
1475
1484
  }
1476
1485
  }
1477
1486
  }
1487
+ const rectToTree2 = (rect) => ({
1488
+ ...rect,
1489
+ minX: rect.x,
1490
+ minY: rect.y,
1491
+ maxX: rect.x + rect.width,
1492
+ maxY: rect.y + rect.height
1493
+ });
1494
+ const sameRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
1478
1495
  removeIdx.sort((a, b) => b - a).forEach((idx) => {
1479
1496
  const rem = params.placed.splice(idx, 1)[0];
1480
- for (const z of rem.zLayers) {
1481
- const arr = params.placedByLayer[z];
1482
- const j = arr.findIndex((r) => r === rem.rect);
1483
- if (j >= 0) arr.splice(j, 1);
1497
+ if (params.placedIndexByLayer) {
1498
+ for (const z of rem.zLayers) {
1499
+ const tree = params.placedIndexByLayer[z];
1500
+ if (tree) tree.remove(rectToTree2(rem.rect), sameRect);
1501
+ }
1484
1502
  }
1485
1503
  });
1486
1504
  for (const p of toAdd) {
1487
1505
  params.placed.push(p);
1488
- for (const z of p.zLayers) params.placedByLayer[z].push(p.rect);
1506
+ for (const z of p.zLayers) {
1507
+ if (params.placedIndexByLayer) {
1508
+ const idx = params.placedIndexByLayer[z];
1509
+ if (idx) {
1510
+ idx.insert({
1511
+ ...p.rect,
1512
+ minX: p.rect.x,
1513
+ minY: p.rect.y,
1514
+ maxX: p.rect.x + p.rect.width,
1515
+ maxY: p.rect.y + p.rect.height
1516
+ });
1517
+ }
1518
+ }
1519
+ }
1489
1520
  }
1490
1521
  }
1491
1522
 
1523
+ // lib/utils/getColorForZLayer.ts
1524
+ var getColorForZLayer = (zLayers) => {
1525
+ const minZ = Math.min(...zLayers);
1526
+ const colors = [
1527
+ { fill: "#dbeafe", stroke: "#3b82f6" },
1528
+ { fill: "#fef3c7", stroke: "#f59e0b" },
1529
+ { fill: "#d1fae5", stroke: "#10b981" },
1530
+ { fill: "#e9d5ff", stroke: "#a855f7" },
1531
+ { fill: "#fed7aa", stroke: "#f97316" },
1532
+ { fill: "#fecaca", stroke: "#ef4444" }
1533
+ ];
1534
+ return colors[minZ % colors.length];
1535
+ };
1536
+
1492
1537
  // lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
1538
+ import RBush3 from "rbush";
1493
1539
  var RectDiffSeedingSolver = class extends BaseSolver3 {
1494
1540
  constructor(input) {
1495
1541
  super();
@@ -1501,12 +1547,11 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1501
1547
  layerCount;
1502
1548
  bounds;
1503
1549
  options;
1504
- obstaclesByLayer;
1505
1550
  boardVoidRects;
1506
1551
  gridIndex;
1507
1552
  candidates;
1508
1553
  placed;
1509
- placedByLayer;
1554
+ placedIndexByLayer;
1510
1555
  expansionIndex;
1511
1556
  edgeAnalysisDone;
1512
1557
  totalSeedsThisGrid;
@@ -1522,34 +1567,6 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1522
1567
  width: srj.bounds.maxX - srj.bounds.minX,
1523
1568
  height: srj.bounds.maxY - srj.bounds.minY
1524
1569
  };
1525
- const obstaclesByLayer = Array.from(
1526
- { length: layerCount },
1527
- () => []
1528
- );
1529
- let boardVoidRects = [];
1530
- if (srj.outline && srj.outline.length > 2) {
1531
- boardVoidRects = computeInverseRects(bounds, srj.outline);
1532
- for (const voidR of boardVoidRects) {
1533
- for (let z = 0; z < layerCount; z++) {
1534
- obstaclesByLayer[z].push(voidR);
1535
- }
1536
- }
1537
- }
1538
- for (const obstacle of srj.obstacles ?? []) {
1539
- const rect = obstacleToXYRect(obstacle);
1540
- if (!rect) continue;
1541
- const zLayers = obstacleZs(obstacle, zIndexByName);
1542
- const invalidZs = zLayers.filter((z) => z < 0 || z >= layerCount);
1543
- if (invalidZs.length) {
1544
- throw new Error(
1545
- `RectDiff: obstacle uses z-layer indices ${invalidZs.join(",")} outside 0-${layerCount - 1}`
1546
- );
1547
- }
1548
- if ((!obstacle.zLayers || obstacle.zLayers.length === 0) && zLayers.length) {
1549
- obstacle.zLayers = zLayers;
1550
- }
1551
- for (const z of zLayers) obstaclesByLayer[z].push(rect);
1552
- }
1553
1570
  const trace = Math.max(0.01, srj.minTraceWidth || 0.15);
1554
1571
  const defaults = {
1555
1572
  gridSizes: [],
@@ -1570,21 +1587,19 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1570
1587
  gridSizes: opts.gridSizes ?? // re-use the helper that was previously in engine
1571
1588
  computeDefaultGridSizes(bounds)
1572
1589
  };
1573
- const placedByLayer = Array.from(
1574
- { length: layerCount },
1575
- () => []
1576
- );
1577
1590
  this.srj = srj;
1578
1591
  this.layerNames = layerNames;
1579
1592
  this.layerCount = layerCount;
1580
1593
  this.bounds = bounds;
1581
1594
  this.options = options;
1582
- this.obstaclesByLayer = obstaclesByLayer;
1583
- this.boardVoidRects = boardVoidRects;
1595
+ this.boardVoidRects = this.input.boardVoidRects;
1584
1596
  this.gridIndex = 0;
1585
1597
  this.candidates = [];
1586
1598
  this.placed = [];
1587
- this.placedByLayer = placedByLayer;
1599
+ this.placedIndexByLayer = Array.from(
1600
+ { length: layerCount },
1601
+ () => new RBush3()
1602
+ );
1588
1603
  this.expansionIndex = 0;
1589
1604
  this.edgeAnalysisDone = false;
1590
1605
  this.totalSeedsThisGrid = 0;
@@ -1622,9 +1637,9 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1622
1637
  bounds: this.bounds,
1623
1638
  gridSize: grid,
1624
1639
  layerCount: this.layerCount,
1625
- obstaclesByLayer: this.obstaclesByLayer,
1626
- placedByLayer: this.placedByLayer,
1627
- hardPlacedByLayer
1640
+ hardPlacedByLayer,
1641
+ obstacleIndexByLayer: this.input.obstacleIndexByLayer,
1642
+ placedIndexByLayer: this.placedIndexByLayer
1628
1643
  });
1629
1644
  this.totalSeedsThisGrid = this.candidates.length;
1630
1645
  this.consumedSeedsThisGrid = 0;
@@ -1642,8 +1657,8 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1642
1657
  bounds: this.bounds,
1643
1658
  minSize,
1644
1659
  layerCount: this.layerCount,
1645
- obstaclesByLayer: this.obstaclesByLayer,
1646
- placedByLayer: this.placedByLayer,
1660
+ obstacleIndexByLayer: this.input.obstacleIndexByLayer,
1661
+ placedIndexByLayer: this.placedIndexByLayer,
1647
1662
  hardPlacedByLayer
1648
1663
  });
1649
1664
  this.edgeAnalysisDone = true;
@@ -1665,8 +1680,8 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1665
1680
  layerCount: this.layerCount,
1666
1681
  minSpan: minMulti.minLayers,
1667
1682
  maxSpan: maxMultiLayerSpan,
1668
- obstaclesByLayer: this.obstaclesByLayer,
1669
- placedByLayer: hardPlacedByLayer
1683
+ obstacleIndexByLayer: this.input.obstacleIndexByLayer,
1684
+ additionalBlockersByLayer: hardPlacedByLayer
1670
1685
  });
1671
1686
  const attempts = [];
1672
1687
  if (span.length >= minMulti.minLayers) {
@@ -1685,8 +1700,8 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1685
1700
  for (const attempt of ordered) {
1686
1701
  const hardBlockers = [];
1687
1702
  for (const z of attempt.layers) {
1688
- if (this.obstaclesByLayer[z])
1689
- hardBlockers.push(...this.obstaclesByLayer[z]);
1703
+ const obstacleLayer = this.input.obstacleIndexByLayer[z];
1704
+ if (obstacleLayer) hardBlockers.push(...obstacleLayer.all());
1690
1705
  if (hardPlacedByLayer[z]) hardBlockers.push(...hardPlacedByLayer[z]);
1691
1706
  }
1692
1707
  const rect = expandRectFromSeed({
@@ -1702,25 +1717,34 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1702
1717
  if (!rect) continue;
1703
1718
  const placed = { rect, zLayers: [...attempt.layers] };
1704
1719
  const newIndex = this.placed.push(placed) - 1;
1705
- for (const z of attempt.layers) this.placedByLayer[z].push(rect);
1720
+ for (const z of attempt.layers) {
1721
+ const idx = this.placedIndexByLayer[z];
1722
+ if (idx) {
1723
+ idx.insert({
1724
+ ...rect,
1725
+ minX: rect.x,
1726
+ minY: rect.y,
1727
+ maxX: rect.x + rect.width,
1728
+ maxY: rect.y + rect.height
1729
+ });
1730
+ }
1731
+ }
1706
1732
  resizeSoftOverlaps(
1707
1733
  {
1708
1734
  layerCount: this.layerCount,
1709
1735
  placed: this.placed,
1710
- placedByLayer: this.placedByLayer,
1711
- options: this.options
1736
+ options: this.options,
1737
+ placedIndexByLayer: this.placedIndexByLayer
1712
1738
  },
1713
1739
  newIndex
1714
1740
  );
1715
1741
  this.candidates = this.candidates.filter(
1716
- (c) => !isFullyOccupiedAtPoint(
1717
- {
1718
- layerCount: this.layerCount,
1719
- obstaclesByLayer: this.obstaclesByLayer,
1720
- placedByLayer: this.placedByLayer
1721
- },
1722
- { x: c.x, y: c.y }
1723
- )
1742
+ (c) => !isFullyOccupiedAtPoint({
1743
+ layerCount: this.layerCount,
1744
+ obstacleIndexByLayer: this.input.obstacleIndexByLayer,
1745
+ placedIndexByLayer: this.placedIndexByLayer,
1746
+ point: { x: c.x, y: c.y }
1747
+ })
1724
1748
  );
1725
1749
  return;
1726
1750
  }
@@ -1748,31 +1772,16 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1748
1772
  layerCount: this.layerCount,
1749
1773
  bounds: this.bounds,
1750
1774
  options: this.options,
1751
- obstaclesByLayer: this.obstaclesByLayer,
1752
1775
  boardVoidRects: this.boardVoidRects,
1753
1776
  gridIndex: this.gridIndex,
1754
1777
  candidates: this.candidates,
1755
1778
  placed: this.placed,
1756
- placedByLayer: this.placedByLayer,
1757
1779
  expansionIndex: this.expansionIndex,
1758
1780
  edgeAnalysisDone: this.edgeAnalysisDone,
1759
1781
  totalSeedsThisGrid: this.totalSeedsThisGrid,
1760
1782
  consumedSeedsThisGrid: this.consumedSeedsThisGrid
1761
1783
  };
1762
1784
  }
1763
- /** Get color based on z layer for visualization. */
1764
- getColorForZLayer(zLayers) {
1765
- const minZ = Math.min(...zLayers);
1766
- const colors = [
1767
- { fill: "#dbeafe", stroke: "#3b82f6" },
1768
- { fill: "#fef3c7", stroke: "#f59e0b" },
1769
- { fill: "#d1fae5", stroke: "#10b981" },
1770
- { fill: "#e9d5ff", stroke: "#a855f7" },
1771
- { fill: "#fed7aa", stroke: "#f97316" },
1772
- { fill: "#fecaca", stroke: "#ef4444" }
1773
- ];
1774
- return colors[minZ % colors.length];
1775
- }
1776
1785
  /** Visualization focused on the grid seeding phase. */
1777
1786
  visualize() {
1778
1787
  const rects = [];
@@ -1859,7 +1868,7 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1859
1868
  }
1860
1869
  if (this.placed?.length) {
1861
1870
  for (const placement of this.placed) {
1862
- const colors = this.getColorForZLayer(placement.zLayers);
1871
+ const colors = getColorForZLayer(placement.zLayers);
1863
1872
  rects.push({
1864
1873
  center: {
1865
1874
  x: placement.rect.x + placement.rect.width / 2,
@@ -1897,23 +1906,27 @@ function finalizeRects(params) {
1897
1906
  maxY: p.rect.y + p.rect.height,
1898
1907
  zLayers: [...p.zLayers].sort((a, b) => a - b)
1899
1908
  }));
1900
- const layersByObstacleRect = /* @__PURE__ */ new Map();
1901
- params.obstaclesByLayer.forEach((layerObs, z) => {
1902
- for (const rect of layerObs) {
1903
- const layerIndices = layersByObstacleRect.get(rect) ?? [];
1904
- layerIndices.push(z);
1905
- layersByObstacleRect.set(rect, layerIndices);
1906
- }
1907
- });
1908
- const voidSet = new Set(params.boardVoidRects || []);
1909
- for (const [rect, layerIndices] of layersByObstacleRect.entries()) {
1910
- if (voidSet.has(rect)) continue;
1909
+ const { zIndexByName } = buildZIndexMap(params.srj);
1910
+ const layersByKey = /* @__PURE__ */ new Map();
1911
+ for (const obstacle of params.srj.obstacles ?? []) {
1912
+ const rect = obstacleToXYRect(obstacle);
1913
+ if (!rect) continue;
1914
+ const zLayers = obstacle.zLayers?.length && obstacle.zLayers.length > 0 ? obstacle.zLayers : obstacleZs(obstacle, zIndexByName);
1915
+ const key = `${rect.x}:${rect.y}:${rect.width}:${rect.height}`;
1916
+ let entry = layersByKey.get(key);
1917
+ if (!entry) {
1918
+ entry = { rect, layers: /* @__PURE__ */ new Set() };
1919
+ layersByKey.set(key, entry);
1920
+ }
1921
+ zLayers.forEach((layer) => entry.layers.add(layer));
1922
+ }
1923
+ for (const { rect, layers } of layersByKey.values()) {
1911
1924
  out.push({
1912
1925
  minX: rect.x,
1913
1926
  minY: rect.y,
1914
1927
  maxX: rect.x + rect.width,
1915
1928
  maxY: rect.y + rect.height,
1916
- zLayers: layerIndices.sort((a, b) => a - b),
1929
+ zLayers: Array.from(layers).sort((a, b) => a - b),
1917
1930
  isObstacle: true
1918
1931
  });
1919
1932
  }
@@ -1942,6 +1955,21 @@ function rectsToMeshNodes(rects) {
1942
1955
  return out;
1943
1956
  }
1944
1957
 
1958
+ // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
1959
+ import RBush4 from "rbush";
1960
+
1961
+ // lib/utils/rectToTree.ts
1962
+ var rectToTree = (rect) => ({
1963
+ ...rect,
1964
+ minX: rect.x,
1965
+ minY: rect.y,
1966
+ maxX: rect.x + rect.width,
1967
+ maxY: rect.y + rect.height
1968
+ });
1969
+
1970
+ // lib/utils/sameTreeRect.ts
1971
+ var sameTreeRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
1972
+
1945
1973
  // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
1946
1974
  var RectDiffExpansionSolver = class extends BaseSolver4 {
1947
1975
  constructor(input) {
@@ -1955,12 +1983,11 @@ var RectDiffExpansionSolver = class extends BaseSolver4 {
1955
1983
  layerCount;
1956
1984
  bounds;
1957
1985
  options;
1958
- obstaclesByLayer;
1959
1986
  boardVoidRects;
1960
1987
  gridIndex;
1961
1988
  candidates;
1962
1989
  placed;
1963
- placedByLayer;
1990
+ placedIndexByLayer;
1964
1991
  expansionIndex;
1965
1992
  edgeAnalysisDone;
1966
1993
  totalSeedsThisGrid;
@@ -1970,6 +1997,39 @@ var RectDiffExpansionSolver = class extends BaseSolver4 {
1970
1997
  this.stats = {
1971
1998
  gridIndex: this.gridIndex
1972
1999
  };
2000
+ if (this.input.obstacleIndexByLayer) {
2001
+ } else {
2002
+ const { zIndexByName } = buildZIndexMap(this.srj);
2003
+ this.input.obstacleIndexByLayer = Array.from(
2004
+ { length: this.layerCount },
2005
+ () => new RBush4()
2006
+ );
2007
+ const insertObstacle = (rect, z) => {
2008
+ const tree = this.input.obstacleIndexByLayer[z];
2009
+ if (tree) tree.insert(rectToTree(rect));
2010
+ };
2011
+ for (const voidRect of this.boardVoidRects ?? []) {
2012
+ for (let z = 0; z < this.layerCount; z++) insertObstacle(voidRect, z);
2013
+ }
2014
+ for (const obstacle of this.srj.obstacles ?? []) {
2015
+ const rect = obstacleToXYRect(obstacle);
2016
+ if (!rect) continue;
2017
+ const zLayers = obstacle.zLayers?.length && obstacle.zLayers.length > 0 ? obstacle.zLayers : obstacleZs(obstacle, zIndexByName);
2018
+ zLayers.forEach((z) => {
2019
+ if (z >= 0 && z < this.layerCount) insertObstacle(rect, z);
2020
+ });
2021
+ }
2022
+ }
2023
+ this.placedIndexByLayer = Array.from(
2024
+ { length: this.layerCount },
2025
+ () => new RBush4()
2026
+ );
2027
+ for (const placement of this.placed ?? []) {
2028
+ for (const z of placement.zLayers) {
2029
+ const tree = this.placedIndexByLayer[z];
2030
+ if (tree) tree.insert(rectToTree(placement.rect));
2031
+ }
2032
+ }
1973
2033
  }
1974
2034
  _step() {
1975
2035
  if (this.solved) return;
@@ -1993,7 +2053,8 @@ var RectDiffExpansionSolver = class extends BaseSolver4 {
1993
2053
  });
1994
2054
  const hardBlockers = [];
1995
2055
  for (const z of p.zLayers) {
1996
- hardBlockers.push(...this.obstaclesByLayer[z] ?? []);
2056
+ const obstacleTree = this.input.obstacleIndexByLayer[z];
2057
+ if (obstacleTree) hardBlockers.push(...obstacleTree.all());
1997
2058
  hardBlockers.push(...hardPlacedByLayer[z] ?? []);
1998
2059
  }
1999
2060
  const oldRect = p.rect;
@@ -2010,16 +2071,18 @@ var RectDiffExpansionSolver = class extends BaseSolver4 {
2010
2071
  if (expanded) {
2011
2072
  this.placed[idx] = { rect: expanded, zLayers: p.zLayers };
2012
2073
  for (const z of p.zLayers) {
2013
- const arr = this.placedByLayer[z];
2014
- const j = arr.findIndex((r) => r === oldRect);
2015
- if (j >= 0) arr[j] = expanded;
2074
+ const tree = this.placedIndexByLayer[z];
2075
+ if (tree) {
2076
+ tree.remove(rectToTree(oldRect), sameTreeRect);
2077
+ tree.insert(rectToTree(expanded));
2078
+ }
2016
2079
  }
2017
2080
  resizeSoftOverlaps(
2018
2081
  {
2019
2082
  layerCount: this.layerCount,
2020
2083
  placed: this.placed,
2021
- placedByLayer: this.placedByLayer,
2022
- options: this.options
2084
+ options: this.options,
2085
+ placedIndexByLayer: this.placedIndexByLayer
2023
2086
  },
2024
2087
  idx
2025
2088
  );
@@ -2030,7 +2093,7 @@ var RectDiffExpansionSolver = class extends BaseSolver4 {
2030
2093
  if (this.solved) return;
2031
2094
  const rects = finalizeRects({
2032
2095
  placed: this.placed,
2033
- obstaclesByLayer: this.obstaclesByLayer,
2096
+ srj: this.srj,
2034
2097
  boardVoidRects: this.boardVoidRects
2035
2098
  });
2036
2099
  this._meshNodes = rectsToMeshNodes(rects);
@@ -2078,10 +2141,73 @@ z:${placement.zLayers.join(",")}`
2078
2141
  }
2079
2142
  };
2080
2143
 
2144
+ // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
2145
+ import "rbush";
2146
+
2147
+ // lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
2148
+ import RBush5 from "rbush";
2149
+ var buildObstacleIndexes = (srj) => {
2150
+ const { layerNames, zIndexByName } = buildZIndexMap(srj);
2151
+ const layerCount = Math.max(1, layerNames.length, srj.layerCount || 1);
2152
+ const bounds = {
2153
+ x: srj.bounds.minX,
2154
+ y: srj.bounds.minY,
2155
+ width: srj.bounds.maxX - srj.bounds.minX,
2156
+ height: srj.bounds.maxY - srj.bounds.minY
2157
+ };
2158
+ const obstacleIndexByLayer = Array.from(
2159
+ { length: layerCount },
2160
+ () => new RBush5()
2161
+ );
2162
+ const insertObstacle = (rect, z) => {
2163
+ const treeRect = {
2164
+ ...rect,
2165
+ minX: rect.x,
2166
+ minY: rect.y,
2167
+ maxX: rect.x + rect.width,
2168
+ maxY: rect.y + rect.height
2169
+ };
2170
+ obstacleIndexByLayer[z]?.insert(treeRect);
2171
+ };
2172
+ let boardVoidRects = [];
2173
+ if (srj.outline && srj.outline.length > 2) {
2174
+ boardVoidRects = computeInverseRects(bounds, srj.outline);
2175
+ for (const voidRect of boardVoidRects) {
2176
+ for (let z = 0; z < layerCount; z++) insertObstacle(voidRect, z);
2177
+ }
2178
+ }
2179
+ for (const obstacle of srj.obstacles ?? []) {
2180
+ const rect = obstacleToXYRect(obstacle);
2181
+ if (!rect) continue;
2182
+ const zLayers = obstacleZs(obstacle, zIndexByName);
2183
+ const invalidZs = zLayers.filter((z) => z < 0 || z >= layerCount);
2184
+ if (invalidZs.length) {
2185
+ throw new Error(
2186
+ `RectDiff: obstacle uses z-layer indices ${invalidZs.join(",")} outside 0-${layerCount - 1}`
2187
+ );
2188
+ }
2189
+ if ((!obstacle.zLayers || obstacle.zLayers.length === 0) && zLayers.length) {
2190
+ obstacle.zLayers = zLayers;
2191
+ }
2192
+ for (const z of zLayers) insertObstacle(rect, z);
2193
+ }
2194
+ return { obstacleIndexByLayer, boardVoidRects };
2195
+ };
2196
+
2081
2197
  // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
2082
2198
  var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2083
2199
  rectDiffSeedingSolver;
2084
2200
  rectDiffExpansionSolver;
2201
+ boardVoidRects;
2202
+ obstacleIndexByLayer;
2203
+ constructor(inputProblem) {
2204
+ super(inputProblem);
2205
+ const { obstacleIndexByLayer, boardVoidRects } = buildObstacleIndexes(
2206
+ inputProblem.simpleRouteJson
2207
+ );
2208
+ this.obstacleIndexByLayer = obstacleIndexByLayer;
2209
+ this.boardVoidRects = boardVoidRects;
2210
+ }
2085
2211
  pipelineDef = [
2086
2212
  definePipelineStep2(
2087
2213
  "rectDiffSeedingSolver",
@@ -2089,7 +2215,9 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2089
2215
  (pipeline) => [
2090
2216
  {
2091
2217
  simpleRouteJson: pipeline.inputProblem.simpleRouteJson,
2092
- gridOptions: pipeline.inputProblem.gridOptions
2218
+ gridOptions: pipeline.inputProblem.gridOptions,
2219
+ obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
2220
+ boardVoidRects: pipeline.boardVoidRects
2093
2221
  }
2094
2222
  ]
2095
2223
  ),
@@ -2098,7 +2226,11 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2098
2226
  RectDiffExpansionSolver,
2099
2227
  (pipeline) => [
2100
2228
  {
2101
- initialSnapshot: pipeline.rectDiffSeedingSolver.getOutput()
2229
+ initialSnapshot: {
2230
+ ...pipeline.rectDiffSeedingSolver.getOutput(),
2231
+ boardVoidRects: pipeline.boardVoidRects ?? []
2232
+ },
2233
+ obstacleIndexByLayer: pipeline.obstacleIndexByLayer
2102
2234
  }
2103
2235
  ]
2104
2236
  )
@@ -2237,7 +2369,6 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2237
2369
  return { meshNodes: [] };
2238
2370
  }
2239
2371
  initialVisualize() {
2240
- console.log("RectDiffPipeline - initialVisualize");
2241
2372
  const graphics = createBaseVisualization(
2242
2373
  this.inputProblem.simpleRouteJson,
2243
2374
  "RectDiffPipeline - Initial"