@tscircuit/rectdiff 0.0.13 → 0.0.14

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,44 @@ 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
 
1492
1523
  // lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
1524
+ import RBush3 from "rbush";
1493
1525
  var RectDiffSeedingSolver = class extends BaseSolver3 {
1494
1526
  constructor(input) {
1495
1527
  super();
@@ -1501,12 +1533,11 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1501
1533
  layerCount;
1502
1534
  bounds;
1503
1535
  options;
1504
- obstaclesByLayer;
1505
1536
  boardVoidRects;
1506
1537
  gridIndex;
1507
1538
  candidates;
1508
1539
  placed;
1509
- placedByLayer;
1540
+ placedIndexByLayer;
1510
1541
  expansionIndex;
1511
1542
  edgeAnalysisDone;
1512
1543
  totalSeedsThisGrid;
@@ -1522,34 +1553,6 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1522
1553
  width: srj.bounds.maxX - srj.bounds.minX,
1523
1554
  height: srj.bounds.maxY - srj.bounds.minY
1524
1555
  };
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
1556
  const trace = Math.max(0.01, srj.minTraceWidth || 0.15);
1554
1557
  const defaults = {
1555
1558
  gridSizes: [],
@@ -1570,21 +1573,19 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1570
1573
  gridSizes: opts.gridSizes ?? // re-use the helper that was previously in engine
1571
1574
  computeDefaultGridSizes(bounds)
1572
1575
  };
1573
- const placedByLayer = Array.from(
1574
- { length: layerCount },
1575
- () => []
1576
- );
1577
1576
  this.srj = srj;
1578
1577
  this.layerNames = layerNames;
1579
1578
  this.layerCount = layerCount;
1580
1579
  this.bounds = bounds;
1581
1580
  this.options = options;
1582
- this.obstaclesByLayer = obstaclesByLayer;
1583
- this.boardVoidRects = boardVoidRects;
1581
+ this.boardVoidRects = this.input.boardVoidRects;
1584
1582
  this.gridIndex = 0;
1585
1583
  this.candidates = [];
1586
1584
  this.placed = [];
1587
- this.placedByLayer = placedByLayer;
1585
+ this.placedIndexByLayer = Array.from(
1586
+ { length: layerCount },
1587
+ () => new RBush3()
1588
+ );
1588
1589
  this.expansionIndex = 0;
1589
1590
  this.edgeAnalysisDone = false;
1590
1591
  this.totalSeedsThisGrid = 0;
@@ -1622,9 +1623,9 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1622
1623
  bounds: this.bounds,
1623
1624
  gridSize: grid,
1624
1625
  layerCount: this.layerCount,
1625
- obstaclesByLayer: this.obstaclesByLayer,
1626
- placedByLayer: this.placedByLayer,
1627
- hardPlacedByLayer
1626
+ hardPlacedByLayer,
1627
+ obstacleIndexByLayer: this.input.obstacleIndexByLayer,
1628
+ placedIndexByLayer: this.placedIndexByLayer
1628
1629
  });
1629
1630
  this.totalSeedsThisGrid = this.candidates.length;
1630
1631
  this.consumedSeedsThisGrid = 0;
@@ -1642,8 +1643,8 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1642
1643
  bounds: this.bounds,
1643
1644
  minSize,
1644
1645
  layerCount: this.layerCount,
1645
- obstaclesByLayer: this.obstaclesByLayer,
1646
- placedByLayer: this.placedByLayer,
1646
+ obstacleIndexByLayer: this.input.obstacleIndexByLayer,
1647
+ placedIndexByLayer: this.placedIndexByLayer,
1647
1648
  hardPlacedByLayer
1648
1649
  });
1649
1650
  this.edgeAnalysisDone = true;
@@ -1665,8 +1666,8 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1665
1666
  layerCount: this.layerCount,
1666
1667
  minSpan: minMulti.minLayers,
1667
1668
  maxSpan: maxMultiLayerSpan,
1668
- obstaclesByLayer: this.obstaclesByLayer,
1669
- placedByLayer: hardPlacedByLayer
1669
+ obstacleIndexByLayer: this.input.obstacleIndexByLayer,
1670
+ additionalBlockersByLayer: hardPlacedByLayer
1670
1671
  });
1671
1672
  const attempts = [];
1672
1673
  if (span.length >= minMulti.minLayers) {
@@ -1685,8 +1686,8 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1685
1686
  for (const attempt of ordered) {
1686
1687
  const hardBlockers = [];
1687
1688
  for (const z of attempt.layers) {
1688
- if (this.obstaclesByLayer[z])
1689
- hardBlockers.push(...this.obstaclesByLayer[z]);
1689
+ const obstacleLayer = this.input.obstacleIndexByLayer[z];
1690
+ if (obstacleLayer) hardBlockers.push(...obstacleLayer.all());
1690
1691
  if (hardPlacedByLayer[z]) hardBlockers.push(...hardPlacedByLayer[z]);
1691
1692
  }
1692
1693
  const rect = expandRectFromSeed({
@@ -1702,25 +1703,34 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1702
1703
  if (!rect) continue;
1703
1704
  const placed = { rect, zLayers: [...attempt.layers] };
1704
1705
  const newIndex = this.placed.push(placed) - 1;
1705
- for (const z of attempt.layers) this.placedByLayer[z].push(rect);
1706
+ for (const z of attempt.layers) {
1707
+ const idx = this.placedIndexByLayer[z];
1708
+ if (idx) {
1709
+ idx.insert({
1710
+ ...rect,
1711
+ minX: rect.x,
1712
+ minY: rect.y,
1713
+ maxX: rect.x + rect.width,
1714
+ maxY: rect.y + rect.height
1715
+ });
1716
+ }
1717
+ }
1706
1718
  resizeSoftOverlaps(
1707
1719
  {
1708
1720
  layerCount: this.layerCount,
1709
1721
  placed: this.placed,
1710
- placedByLayer: this.placedByLayer,
1711
- options: this.options
1722
+ options: this.options,
1723
+ placedIndexByLayer: this.placedIndexByLayer
1712
1724
  },
1713
1725
  newIndex
1714
1726
  );
1715
1727
  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
- )
1728
+ (c) => !isFullyOccupiedAtPoint({
1729
+ layerCount: this.layerCount,
1730
+ obstacleIndexByLayer: this.input.obstacleIndexByLayer,
1731
+ placedIndexByLayer: this.placedIndexByLayer,
1732
+ point: { x: c.x, y: c.y }
1733
+ })
1724
1734
  );
1725
1735
  return;
1726
1736
  }
@@ -1748,12 +1758,10 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1748
1758
  layerCount: this.layerCount,
1749
1759
  bounds: this.bounds,
1750
1760
  options: this.options,
1751
- obstaclesByLayer: this.obstaclesByLayer,
1752
1761
  boardVoidRects: this.boardVoidRects,
1753
1762
  gridIndex: this.gridIndex,
1754
1763
  candidates: this.candidates,
1755
1764
  placed: this.placed,
1756
- placedByLayer: this.placedByLayer,
1757
1765
  expansionIndex: this.expansionIndex,
1758
1766
  edgeAnalysisDone: this.edgeAnalysisDone,
1759
1767
  totalSeedsThisGrid: this.totalSeedsThisGrid,
@@ -1897,23 +1905,27 @@ function finalizeRects(params) {
1897
1905
  maxY: p.rect.y + p.rect.height,
1898
1906
  zLayers: [...p.zLayers].sort((a, b) => a - b)
1899
1907
  }));
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;
1908
+ const { zIndexByName } = buildZIndexMap(params.srj);
1909
+ const layersByKey = /* @__PURE__ */ new Map();
1910
+ for (const obstacle of params.srj.obstacles ?? []) {
1911
+ const rect = obstacleToXYRect(obstacle);
1912
+ if (!rect) continue;
1913
+ const zLayers = obstacle.zLayers?.length && obstacle.zLayers.length > 0 ? obstacle.zLayers : obstacleZs(obstacle, zIndexByName);
1914
+ const key = `${rect.x}:${rect.y}:${rect.width}:${rect.height}`;
1915
+ let entry = layersByKey.get(key);
1916
+ if (!entry) {
1917
+ entry = { rect, layers: /* @__PURE__ */ new Set() };
1918
+ layersByKey.set(key, entry);
1919
+ }
1920
+ zLayers.forEach((layer) => entry.layers.add(layer));
1921
+ }
1922
+ for (const { rect, layers } of layersByKey.values()) {
1911
1923
  out.push({
1912
1924
  minX: rect.x,
1913
1925
  minY: rect.y,
1914
1926
  maxX: rect.x + rect.width,
1915
1927
  maxY: rect.y + rect.height,
1916
- zLayers: layerIndices.sort((a, b) => a - b),
1928
+ zLayers: Array.from(layers).sort((a, b) => a - b),
1917
1929
  isObstacle: true
1918
1930
  });
1919
1931
  }
@@ -1942,6 +1954,21 @@ function rectsToMeshNodes(rects) {
1942
1954
  return out;
1943
1955
  }
1944
1956
 
1957
+ // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
1958
+ import RBush4 from "rbush";
1959
+
1960
+ // lib/utils/rectToTree.ts
1961
+ var rectToTree = (rect) => ({
1962
+ ...rect,
1963
+ minX: rect.x,
1964
+ minY: rect.y,
1965
+ maxX: rect.x + rect.width,
1966
+ maxY: rect.y + rect.height
1967
+ });
1968
+
1969
+ // lib/utils/sameTreeRect.ts
1970
+ var sameTreeRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
1971
+
1945
1972
  // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
1946
1973
  var RectDiffExpansionSolver = class extends BaseSolver4 {
1947
1974
  constructor(input) {
@@ -1955,12 +1982,11 @@ var RectDiffExpansionSolver = class extends BaseSolver4 {
1955
1982
  layerCount;
1956
1983
  bounds;
1957
1984
  options;
1958
- obstaclesByLayer;
1959
1985
  boardVoidRects;
1960
1986
  gridIndex;
1961
1987
  candidates;
1962
1988
  placed;
1963
- placedByLayer;
1989
+ placedIndexByLayer;
1964
1990
  expansionIndex;
1965
1991
  edgeAnalysisDone;
1966
1992
  totalSeedsThisGrid;
@@ -1970,6 +1996,39 @@ var RectDiffExpansionSolver = class extends BaseSolver4 {
1970
1996
  this.stats = {
1971
1997
  gridIndex: this.gridIndex
1972
1998
  };
1999
+ if (this.input.obstacleIndexByLayer) {
2000
+ } else {
2001
+ const { zIndexByName } = buildZIndexMap(this.srj);
2002
+ this.input.obstacleIndexByLayer = Array.from(
2003
+ { length: this.layerCount },
2004
+ () => new RBush4()
2005
+ );
2006
+ const insertObstacle = (rect, z) => {
2007
+ const tree = this.input.obstacleIndexByLayer[z];
2008
+ if (tree) tree.insert(rectToTree(rect));
2009
+ };
2010
+ for (const voidRect of this.boardVoidRects ?? []) {
2011
+ for (let z = 0; z < this.layerCount; z++) insertObstacle(voidRect, z);
2012
+ }
2013
+ for (const obstacle of this.srj.obstacles ?? []) {
2014
+ const rect = obstacleToXYRect(obstacle);
2015
+ if (!rect) continue;
2016
+ const zLayers = obstacle.zLayers?.length && obstacle.zLayers.length > 0 ? obstacle.zLayers : obstacleZs(obstacle, zIndexByName);
2017
+ zLayers.forEach((z) => {
2018
+ if (z >= 0 && z < this.layerCount) insertObstacle(rect, z);
2019
+ });
2020
+ }
2021
+ }
2022
+ this.placedIndexByLayer = Array.from(
2023
+ { length: this.layerCount },
2024
+ () => new RBush4()
2025
+ );
2026
+ for (const placement of this.placed ?? []) {
2027
+ for (const z of placement.zLayers) {
2028
+ const tree = this.placedIndexByLayer[z];
2029
+ if (tree) tree.insert(rectToTree(placement.rect));
2030
+ }
2031
+ }
1973
2032
  }
1974
2033
  _step() {
1975
2034
  if (this.solved) return;
@@ -1993,7 +2052,8 @@ var RectDiffExpansionSolver = class extends BaseSolver4 {
1993
2052
  });
1994
2053
  const hardBlockers = [];
1995
2054
  for (const z of p.zLayers) {
1996
- hardBlockers.push(...this.obstaclesByLayer[z] ?? []);
2055
+ const obstacleTree = this.input.obstacleIndexByLayer[z];
2056
+ if (obstacleTree) hardBlockers.push(...obstacleTree.all());
1997
2057
  hardBlockers.push(...hardPlacedByLayer[z] ?? []);
1998
2058
  }
1999
2059
  const oldRect = p.rect;
@@ -2010,16 +2070,18 @@ var RectDiffExpansionSolver = class extends BaseSolver4 {
2010
2070
  if (expanded) {
2011
2071
  this.placed[idx] = { rect: expanded, zLayers: p.zLayers };
2012
2072
  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;
2073
+ const tree = this.placedIndexByLayer[z];
2074
+ if (tree) {
2075
+ tree.remove(rectToTree(oldRect), sameTreeRect);
2076
+ tree.insert(rectToTree(expanded));
2077
+ }
2016
2078
  }
2017
2079
  resizeSoftOverlaps(
2018
2080
  {
2019
2081
  layerCount: this.layerCount,
2020
2082
  placed: this.placed,
2021
- placedByLayer: this.placedByLayer,
2022
- options: this.options
2083
+ options: this.options,
2084
+ placedIndexByLayer: this.placedIndexByLayer
2023
2085
  },
2024
2086
  idx
2025
2087
  );
@@ -2030,7 +2092,7 @@ var RectDiffExpansionSolver = class extends BaseSolver4 {
2030
2092
  if (this.solved) return;
2031
2093
  const rects = finalizeRects({
2032
2094
  placed: this.placed,
2033
- obstaclesByLayer: this.obstaclesByLayer,
2095
+ srj: this.srj,
2034
2096
  boardVoidRects: this.boardVoidRects
2035
2097
  });
2036
2098
  this._meshNodes = rectsToMeshNodes(rects);
@@ -2078,10 +2140,73 @@ z:${placement.zLayers.join(",")}`
2078
2140
  }
2079
2141
  };
2080
2142
 
2143
+ // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
2144
+ import "rbush";
2145
+
2146
+ // lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
2147
+ import RBush5 from "rbush";
2148
+ var buildObstacleIndexes = (srj) => {
2149
+ const { layerNames, zIndexByName } = buildZIndexMap(srj);
2150
+ const layerCount = Math.max(1, layerNames.length, srj.layerCount || 1);
2151
+ const bounds = {
2152
+ x: srj.bounds.minX,
2153
+ y: srj.bounds.minY,
2154
+ width: srj.bounds.maxX - srj.bounds.minX,
2155
+ height: srj.bounds.maxY - srj.bounds.minY
2156
+ };
2157
+ const obstacleIndexByLayer = Array.from(
2158
+ { length: layerCount },
2159
+ () => new RBush5()
2160
+ );
2161
+ const insertObstacle = (rect, z) => {
2162
+ const treeRect = {
2163
+ ...rect,
2164
+ minX: rect.x,
2165
+ minY: rect.y,
2166
+ maxX: rect.x + rect.width,
2167
+ maxY: rect.y + rect.height
2168
+ };
2169
+ obstacleIndexByLayer[z]?.insert(treeRect);
2170
+ };
2171
+ let boardVoidRects = [];
2172
+ if (srj.outline && srj.outline.length > 2) {
2173
+ boardVoidRects = computeInverseRects(bounds, srj.outline);
2174
+ for (const voidRect of boardVoidRects) {
2175
+ for (let z = 0; z < layerCount; z++) insertObstacle(voidRect, z);
2176
+ }
2177
+ }
2178
+ for (const obstacle of srj.obstacles ?? []) {
2179
+ const rect = obstacleToXYRect(obstacle);
2180
+ if (!rect) continue;
2181
+ const zLayers = obstacleZs(obstacle, zIndexByName);
2182
+ const invalidZs = zLayers.filter((z) => z < 0 || z >= layerCount);
2183
+ if (invalidZs.length) {
2184
+ throw new Error(
2185
+ `RectDiff: obstacle uses z-layer indices ${invalidZs.join(",")} outside 0-${layerCount - 1}`
2186
+ );
2187
+ }
2188
+ if ((!obstacle.zLayers || obstacle.zLayers.length === 0) && zLayers.length) {
2189
+ obstacle.zLayers = zLayers;
2190
+ }
2191
+ for (const z of zLayers) insertObstacle(rect, z);
2192
+ }
2193
+ return { obstacleIndexByLayer, boardVoidRects };
2194
+ };
2195
+
2081
2196
  // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
2082
2197
  var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2083
2198
  rectDiffSeedingSolver;
2084
2199
  rectDiffExpansionSolver;
2200
+ boardVoidRects;
2201
+ obstacleIndexByLayer;
2202
+ constructor(inputProblem) {
2203
+ super(inputProblem);
2204
+ const { obstacleIndexByLayer, boardVoidRects } = buildObstacleIndexes(
2205
+ inputProblem.simpleRouteJson
2206
+ );
2207
+ this.obstacleIndexByLayer = obstacleIndexByLayer;
2208
+ this.boardVoidRects = boardVoidRects;
2209
+ }
2085
2210
  pipelineDef = [
2086
2211
  definePipelineStep2(
2087
2212
  "rectDiffSeedingSolver",
@@ -2089,7 +2214,9 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2089
2214
  (pipeline) => [
2090
2215
  {
2091
2216
  simpleRouteJson: pipeline.inputProblem.simpleRouteJson,
2092
- gridOptions: pipeline.inputProblem.gridOptions
2217
+ gridOptions: pipeline.inputProblem.gridOptions,
2218
+ obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
2219
+ boardVoidRects: pipeline.boardVoidRects
2093
2220
  }
2094
2221
  ]
2095
2222
  ),
@@ -2098,7 +2225,11 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2098
2225
  RectDiffExpansionSolver,
2099
2226
  (pipeline) => [
2100
2227
  {
2101
- initialSnapshot: pipeline.rectDiffSeedingSolver.getOutput()
2228
+ initialSnapshot: {
2229
+ ...pipeline.rectDiffSeedingSolver.getOutput(),
2230
+ boardVoidRects: pipeline.boardVoidRects ?? []
2231
+ },
2232
+ obstacleIndexByLayer: pipeline.obstacleIndexByLayer
2102
2233
  }
2103
2234
  ]
2104
2235
  )