@tscircuit/rectdiff 0.0.22 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/components/SolverDebugger3d.tsx +2 -2
  2. package/dist/index.d.ts +23 -3
  3. package/dist/index.js +291 -80
  4. package/lib/RectDiffPipeline.ts +42 -35
  5. package/lib/buildFinalRectDiffVisualization.ts +46 -0
  6. package/lib/fixtures/twoNodeExpansionFixture.ts +10 -2
  7. package/lib/rectdiff-visualization.ts +2 -1
  8. package/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts +8 -3
  9. package/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts +48 -9
  10. package/lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts +14 -6
  11. package/lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts +16 -5
  12. package/lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts +12 -2
  13. package/lib/solvers/RectDiffSeedingSolver/computeEdgeCandidates3D.ts +53 -13
  14. package/lib/solvers/RectDiffSeedingSolver/layers.ts +9 -5
  15. package/lib/utils/buildOutlineGraphics.ts +39 -0
  16. package/lib/utils/expandRectFromSeed.ts +11 -1
  17. package/lib/utils/finalizeRects.ts +17 -9
  18. package/lib/utils/padRect.ts +11 -0
  19. package/lib/utils/renderObstacleClearance.ts +50 -0
  20. package/package.json +1 -1
  21. package/pages/bugreport11.page.tsx +1 -0
  22. package/tests/board-outline.test.ts +1 -1
  23. package/tests/fixtures/makeSimpleRouteOutlineGraphics.ts +5 -1
  24. package/tests/should-expand-node.test.ts +9 -1
  25. package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +1 -1
  26. package/tests/solver/bugreport01-be84eb/__snapshots__/bugreport01-be84eb.snap.svg +2 -2
  27. package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +2 -2
  28. package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
  29. package/tests/solver/bugreport07-d3f3be/__snapshots__/bugreport07-d3f3be.snap.svg +1 -1
  30. package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
  31. package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +2 -2
  32. package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +2 -2
  33. package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +1 -1
  34. package/tests/solver/bugreport11-b2de3c/bugreport11-b2de3c-clearance-equivalence.test.ts +52 -0
  35. package/tests/solver/bugreport12-35ce1c/__snapshots__/bugreport12-35ce1c.snap.svg +1 -1
  36. package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +2 -2
  37. package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +2 -2
  38. package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
  39. package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
  40. package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +1 -1
  41. package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
  42. package/tests/solver/bugreport26-66b0b2/__snapshots__/bugreport26-66b0b2.snap.svg +2 -2
  43. package/tests/solver/bugreport27-dd3734/__snapshots__/bugreport27-dd3734.snap.svg +2 -2
  44. package/tests/solver/bugreport28-18a9ef/__snapshots__/bugreport28-18a9ef.snap.svg +2 -2
  45. package/tests/solver/bugreport29-7deae8/__snapshots__/bugreport29-7deae8.snap.svg +2 -2
  46. package/tests/solver/bugreport30-2174c8/__snapshots__/bugreport30-2174c8.snap.svg +1 -1
  47. package/tests/solver/bugreport33-213d45/__snapshots__/bugreport33-213d45.snap.svg +2 -2
  48. package/tests/solver/bugreport34-e9dea2/__snapshots__/bugreport34-e9dea2.snap.svg +2 -2
  49. package/tests/solver/bugreport35-191db9/__snapshots__/bugreport35-191db9.snap.svg +2 -2
  50. package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +1 -1
  51. package/tests/solver/interaction/__snapshots__/interaction.snap.svg +1 -1
  52. package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
  53. package/tests/solver/pcb_trace_id-should-return-root-connection-name/__snapshots__/pcb_trace_id-should-return-root-connection-name.snap.svg +1 -1
package/dist/index.js CHANGED
@@ -859,11 +859,11 @@ function canonicalizeLayerOrder(names) {
859
859
  return a.localeCompare(b);
860
860
  });
861
861
  }
862
- function buildZIndexMap(srj) {
862
+ function buildZIndexMap(params) {
863
863
  const names = canonicalizeLayerOrder(
864
- (srj.obstacles ?? []).flatMap((o) => o.layers ?? [])
864
+ (params.obstacles ?? []).flatMap((o) => o.layers ?? [])
865
865
  );
866
- const declaredLayerCount = Math.max(1, srj.layerCount || names.length || 1);
866
+ const declaredLayerCount = Math.max(1, params.layerCount || names.length || 1);
867
867
  const fallback = Array.from(
868
868
  { length: declaredLayerCount },
869
869
  (_, i) => i === 0 ? "top" : i === declaredLayerCount - 1 ? "bottom" : `inner${i}`
@@ -960,6 +960,13 @@ var searchStripUp = ({
960
960
  });
961
961
 
962
962
  // lib/utils/expandRectFromSeed.ts
963
+ var quantize = (value, precision = 1e-6) => Math.round(value / precision) * precision;
964
+ var quantizeRect = (rect) => ({
965
+ x: quantize(rect.x),
966
+ y: quantize(rect.y),
967
+ width: quantize(rect.width),
968
+ height: quantize(rect.height)
969
+ });
963
970
  function maxExpandRight(params) {
964
971
  const { r, bounds, blockers, maxAspect } = params;
965
972
  let maxWidth = bounds.x + bounds.width - r.x;
@@ -1173,7 +1180,7 @@ function expandRectFromSeed(params) {
1173
1180
  if (r.width + EPS4 >= minReq.width && r.height + EPS4 >= minReq.height) {
1174
1181
  const area = r.width * r.height;
1175
1182
  if (area > bestArea) {
1176
- best = r;
1183
+ best = quantizeRect(r);
1177
1184
  bestArea = area;
1178
1185
  }
1179
1186
  }
@@ -1247,6 +1254,7 @@ function longestFreeSpanAroundZ(params) {
1247
1254
  }
1248
1255
 
1249
1256
  // lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts
1257
+ var quantize2 = (value, precision = 1e-6) => Math.round(value / precision) * precision;
1250
1258
  function computeCandidates3D(params) {
1251
1259
  const {
1252
1260
  bounds,
@@ -1296,12 +1304,13 @@ function computeCandidates3D(params) {
1296
1304
  distancePointToRectEdges({ x, y }, bounds),
1297
1305
  ...hardAtZ.length ? hardAtZ.map((b) => distancePointToRectEdges({ x, y }, b)) : [Infinity]
1298
1306
  );
1307
+ const distance = quantize2(d);
1299
1308
  const k = `${x.toFixed(6)}|${y.toFixed(6)}`;
1300
1309
  const cand = {
1301
1310
  x,
1302
1311
  y,
1303
1312
  z: anchorZ,
1304
- distance: d,
1313
+ distance,
1305
1314
  zSpanLen: bestSpan.length
1306
1315
  };
1307
1316
  const prev = out.get(k);
@@ -1311,18 +1320,28 @@ function computeCandidates3D(params) {
1311
1320
  }
1312
1321
  }
1313
1322
  const arr = Array.from(out.values());
1314
- arr.sort((a, b) => b.zSpanLen - a.zSpanLen || b.distance - a.distance);
1323
+ arr.sort(
1324
+ (a, b) => b.zSpanLen - a.zSpanLen || b.distance - a.distance || a.z - b.z || a.x - b.x || a.y - b.y
1325
+ );
1315
1326
  return arr;
1316
1327
  }
1317
1328
 
1318
1329
  // lib/solvers/RectDiffSeedingSolver/computeEdgeCandidates3D.ts
1330
+ var quantize3 = (value, precision = 1e-6) => Math.round(value / precision) * precision;
1319
1331
  function computeUncoveredSegments(params) {
1320
1332
  const { lineStart, lineEnd, coveringIntervals, minSegmentLength } = params;
1321
- if (coveringIntervals.length === 0) {
1322
- const center = (lineStart + lineEnd) / 2;
1323
- return [{ start: lineStart, end: lineEnd, center }];
1324
- }
1325
- const sorted = [...coveringIntervals].sort((a, b) => a.start - b.start);
1333
+ const lineStartQ = quantize3(lineStart);
1334
+ const lineEndQ = quantize3(lineEnd);
1335
+ const normalizedIntervals = coveringIntervals.map((i) => {
1336
+ const s = quantize3(i.start);
1337
+ const e = quantize3(i.end);
1338
+ return { start: Math.min(s, e), end: Math.max(s, e) };
1339
+ }).filter((i) => i.end > i.start + EPS4);
1340
+ if (normalizedIntervals.length === 0) {
1341
+ const center = (lineStartQ + lineEndQ) / 2;
1342
+ return [{ start: lineStartQ, end: lineEndQ, center }];
1343
+ }
1344
+ const sorted = [...normalizedIntervals].sort((a, b) => a.start - b.start);
1326
1345
  const merged = [];
1327
1346
  let current = { ...sorted[0] };
1328
1347
  for (let i = 1; i < sorted.length; i++) {
@@ -1336,8 +1355,8 @@ function computeUncoveredSegments(params) {
1336
1355
  }
1337
1356
  merged.push(current);
1338
1357
  const uncovered = [];
1339
- if (merged[0].start > lineStart + EPS4) {
1340
- const start = lineStart;
1358
+ if (merged[0].start > lineStartQ + EPS4) {
1359
+ const start = lineStartQ;
1341
1360
  const end = merged[0].start;
1342
1361
  if (end - start >= minSegmentLength) {
1343
1362
  uncovered.push({ start, end, center: (start + end) / 2 });
@@ -1350,9 +1369,9 @@ function computeUncoveredSegments(params) {
1350
1369
  uncovered.push({ start, end, center: (start + end) / 2 });
1351
1370
  }
1352
1371
  }
1353
- if (merged[merged.length - 1].end < lineEnd - EPS4) {
1372
+ if (merged[merged.length - 1].end < lineEndQ - EPS4) {
1354
1373
  const start = merged[merged.length - 1].end;
1355
- const end = lineEnd;
1374
+ const end = lineEndQ;
1356
1375
  if (end - start >= minSegmentLength) {
1357
1376
  uncovered.push({ start, end, center: (start + end) / 2 });
1358
1377
  }
@@ -1381,18 +1400,28 @@ function computeEdgeCandidates3D(params) {
1381
1400
  });
1382
1401
  }
1383
1402
  function pushIfFree(p) {
1384
- const { x, y, z } = p;
1403
+ const qx = quantize3(p.x);
1404
+ const qy = quantize3(p.y);
1405
+ const { z } = p;
1406
+ const x = qx;
1407
+ const y = qy;
1385
1408
  if (x < bounds.x + EPS4 || y < bounds.y + EPS4 || x > bounds.x + bounds.width - EPS4 || y > bounds.y + bounds.height - EPS4)
1386
1409
  return;
1387
1410
  if (fullyOcc({ x, y })) return;
1388
1411
  const hard = [
1389
1412
  ...obstacleIndexByLayer[z]?.all() ?? [],
1390
1413
  ...hardPlacedByLayer[z] ?? []
1391
- ];
1414
+ ].map((b) => ({
1415
+ x: quantize3(b.x),
1416
+ y: quantize3(b.y),
1417
+ width: quantize3(b.width),
1418
+ height: quantize3(b.height)
1419
+ }));
1392
1420
  const d = Math.min(
1393
1421
  distancePointToRectEdges({ x, y }, bounds),
1394
1422
  ...hard.length ? hard.map((b) => distancePointToRectEdges({ x, y }, b)) : [Infinity]
1395
1423
  );
1424
+ const distance = quantize3(d);
1396
1425
  const k = key({ x, y, z });
1397
1426
  if (dedup.has(k)) return;
1398
1427
  dedup.add(k);
@@ -1406,13 +1435,25 @@ function computeEdgeCandidates3D(params) {
1406
1435
  obstacleIndexByLayer,
1407
1436
  additionalBlockersByLayer: hardPlacedByLayer
1408
1437
  });
1409
- out.push({ x, y, z, distance: d, zSpanLen: span.length, isEdgeSeed: true });
1438
+ out.push({
1439
+ x,
1440
+ y,
1441
+ z,
1442
+ distance,
1443
+ zSpanLen: span.length,
1444
+ isEdgeSeed: true
1445
+ });
1410
1446
  }
1411
1447
  for (let z = 0; z < layerCount; z++) {
1412
1448
  const blockers = [
1413
1449
  ...obstacleIndexByLayer[z]?.all() ?? [],
1414
1450
  ...hardPlacedByLayer[z] ?? []
1415
- ];
1451
+ ].map((b) => ({
1452
+ x: quantize3(b.x),
1453
+ y: quantize3(b.y),
1454
+ width: quantize3(b.width),
1455
+ height: quantize3(b.height)
1456
+ }));
1416
1457
  const corners = [
1417
1458
  { x: bounds.x + \u03B4, y: bounds.y + \u03B4 },
1418
1459
  // top-left
@@ -1585,7 +1626,9 @@ function computeEdgeCandidates3D(params) {
1585
1626
  }
1586
1627
  }
1587
1628
  }
1588
- out.sort((a, b) => b.zSpanLen - a.zSpanLen || b.distance - a.distance);
1629
+ out.sort(
1630
+ (a, b) => b.zSpanLen - a.zSpanLen || b.distance - a.distance || a.z - b.z || a.x - b.x || a.y - b.y
1631
+ );
1589
1632
  return out;
1590
1633
  }
1591
1634
 
@@ -1710,7 +1753,14 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1710
1753
  _setup() {
1711
1754
  const srj = this.input.simpleRouteJson;
1712
1755
  const opts = this.input.gridOptions ?? {};
1713
- const { layerNames, zIndexByName } = buildZIndexMap(srj);
1756
+ const precomputed = this.input.layerNames && this.input.zIndexByName;
1757
+ const { layerNames, zIndexByName } = precomputed ? {
1758
+ layerNames: this.input.layerNames,
1759
+ zIndexByName: this.input.zIndexByName
1760
+ } : buildZIndexMap({
1761
+ obstacles: srj.obstacles,
1762
+ layerCount: srj.layerCount
1763
+ });
1714
1764
  const layerCount = Math.max(1, layerNames.length, srj.layerCount || 1);
1715
1765
  const bounds = {
1716
1766
  x: srj.bounds.minX,
@@ -1908,7 +1958,6 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1908
1958
  */
1909
1959
  getOutput() {
1910
1960
  return {
1911
- srj: this.srj,
1912
1961
  layerNames: this.layerNames,
1913
1962
  layerCount: this.layerCount,
1914
1963
  bounds: this.bounds,
@@ -1920,7 +1969,9 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1920
1969
  expansionIndex: this.expansionIndex,
1921
1970
  edgeAnalysisDone: this.edgeAnalysisDone,
1922
1971
  totalSeedsThisGrid: this.totalSeedsThisGrid,
1923
- consumedSeedsThisGrid: this.consumedSeedsThisGrid
1972
+ consumedSeedsThisGrid: this.consumedSeedsThisGrid,
1973
+ obstacles: this.srj.obstacles,
1974
+ obstacleClearance: this.input.obstacleClearance
1924
1975
  };
1925
1976
  }
1926
1977
  /** Visualization focused on the grid seeding phase. */
@@ -2001,8 +2052,6 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
2001
2052
  points.push({
2002
2053
  x: cand.x,
2003
2054
  y: cand.y,
2004
- fill: "#9333ea",
2005
- stroke: "#6b21a8",
2006
2055
  label: `z:${cand.z}`
2007
2056
  });
2008
2057
  }
@@ -2047,12 +2096,17 @@ function finalizeRects(params) {
2047
2096
  maxY: p.rect.y + p.rect.height,
2048
2097
  zLayers: [...p.zLayers].sort((a, b) => a - b)
2049
2098
  }));
2050
- const { zIndexByName } = buildZIndexMap(params.srj);
2051
2099
  const layersByKey = /* @__PURE__ */ new Map();
2052
- for (const obstacle of params.srj.obstacles ?? []) {
2053
- const rect = obstacleToXYRect(obstacle);
2054
- if (!rect) continue;
2055
- const zLayers = obstacle.zLayers?.length && obstacle.zLayers.length > 0 ? obstacle.zLayers : obstacleZs(obstacle, zIndexByName);
2100
+ for (const obstacle of params.obstacles ?? []) {
2101
+ const baseRect = obstacleToXYRect(obstacle);
2102
+ if (!baseRect) continue;
2103
+ const rect = params.obstacleClearance ? {
2104
+ x: baseRect.x - params.obstacleClearance,
2105
+ y: baseRect.y - params.obstacleClearance,
2106
+ width: baseRect.width + 2 * params.obstacleClearance,
2107
+ height: baseRect.height + 2 * params.obstacleClearance
2108
+ } : baseRect;
2109
+ const zLayers = obstacle.zLayers?.length && obstacle.zLayers.length > 0 ? obstacle.zLayers : obstacleZs(obstacle, params.zIndexByName);
2056
2110
  const key = `${rect.x}:${rect.y}:${rect.width}:${rect.height}`;
2057
2111
  let entry = layersByKey.get(key);
2058
2112
  if (!entry) {
@@ -2182,8 +2236,10 @@ var RectDiffExpansionSolver = class extends BaseSolver4 {
2182
2236
  if (this.solved) return;
2183
2237
  const rects = finalizeRects({
2184
2238
  placed: this.input.placed,
2185
- srj: this.input.srj,
2186
- boardVoidRects: this.input.boardVoidRects
2239
+ obstacles: this.input.obstacles,
2240
+ zIndexByName: this.input.zIndexByName,
2241
+ boardVoidRects: this.input.boardVoidRects,
2242
+ obstacleClearance: this.input.obstacleClearance
2187
2243
  });
2188
2244
  this._meshNodes = rectsToMeshNodes(rects);
2189
2245
  this.solved = true;
@@ -2246,9 +2302,25 @@ import "rbush";
2246
2302
 
2247
2303
  // lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
2248
2304
  import RBush5 from "rbush";
2305
+
2306
+ // lib/utils/padRect.ts
2307
+ var padRect = (rect, clearance) => {
2308
+ if (!clearance || clearance <= 0) return rect;
2309
+ return {
2310
+ x: rect.x - clearance,
2311
+ y: rect.y - clearance,
2312
+ width: rect.width + 2 * clearance,
2313
+ height: rect.height + 2 * clearance
2314
+ };
2315
+ };
2316
+
2317
+ // lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
2249
2318
  var buildObstacleIndexesByLayer = (params) => {
2250
- const { srj, boardVoidRects } = params;
2251
- const { layerNames, zIndexByName } = buildZIndexMap(srj);
2319
+ const { srj, boardVoidRects, obstacleClearance } = params;
2320
+ const { layerNames, zIndexByName } = buildZIndexMap({
2321
+ obstacles: srj.obstacles,
2322
+ layerCount: srj.layerCount
2323
+ });
2252
2324
  const layerCount = Math.max(1, layerNames.length, srj.layerCount || 1);
2253
2325
  const bounds = {
2254
2326
  x: srj.bounds.minX,
@@ -2277,8 +2349,9 @@ var buildObstacleIndexesByLayer = (params) => {
2277
2349
  }
2278
2350
  }
2279
2351
  for (const obstacle of srj.obstacles ?? []) {
2280
- const rect = obstacleToXYRect(obstacle);
2281
- if (!rect) continue;
2352
+ const rectBase = obstacleToXYRect(obstacle);
2353
+ if (!rectBase) continue;
2354
+ const rect = padRect(rectBase, obstacleClearance ?? 0);
2282
2355
  const zLayers = obstacleZs(obstacle, zIndexByName);
2283
2356
  const invalidZs = zLayers.filter((z) => z < 0 || z >= layerCount);
2284
2357
  if (invalidZs.length) {
@@ -2291,7 +2364,7 @@ var buildObstacleIndexesByLayer = (params) => {
2291
2364
  }
2292
2365
  for (const z of zLayers) insertObstacle(rect, z);
2293
2366
  }
2294
- return { obstacleIndexByLayer };
2367
+ return { obstacleIndexByLayer, layerNames, zIndexByName };
2295
2368
  };
2296
2369
 
2297
2370
  // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
@@ -2299,13 +2372,25 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2299
2372
  rectDiffSeedingSolver;
2300
2373
  rectDiffExpansionSolver;
2301
2374
  obstacleIndexByLayer;
2375
+ layerNames;
2376
+ zIndexByName;
2302
2377
  constructor(inputProblem) {
2303
2378
  super(inputProblem);
2304
- const { obstacleIndexByLayer } = buildObstacleIndexesByLayer({
2305
- srj: inputProblem.simpleRouteJson,
2306
- boardVoidRects: inputProblem.boardVoidRects
2379
+ const { obstacleIndexByLayer, layerNames, zIndexByName } = buildObstacleIndexesByLayer({
2380
+ srj: {
2381
+ bounds: inputProblem.bounds,
2382
+ obstacles: inputProblem.obstacles,
2383
+ connections: inputProblem.connections,
2384
+ outline: inputProblem.outline?.outline,
2385
+ layerCount: inputProblem.layerCount,
2386
+ minTraceWidth: inputProblem.minTraceWidth
2387
+ },
2388
+ boardVoidRects: inputProblem.boardVoidRects,
2389
+ obstacleClearance: inputProblem.obstacleClearance
2307
2390
  });
2308
2391
  this.obstacleIndexByLayer = obstacleIndexByLayer;
2392
+ this.layerNames = inputProblem.layerNames ?? layerNames;
2393
+ this.zIndexByName = inputProblem.zIndexByName ?? zIndexByName;
2309
2394
  }
2310
2395
  pipelineDef = [
2311
2396
  definePipelineStep2(
@@ -2313,10 +2398,20 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2313
2398
  RectDiffSeedingSolver,
2314
2399
  (pipeline) => [
2315
2400
  {
2316
- simpleRouteJson: pipeline.inputProblem.simpleRouteJson,
2401
+ simpleRouteJson: {
2402
+ bounds: pipeline.inputProblem.bounds,
2403
+ obstacles: pipeline.inputProblem.obstacles,
2404
+ connections: pipeline.inputProblem.connections,
2405
+ outline: pipeline.inputProblem.outline?.outline,
2406
+ layerCount: pipeline.inputProblem.layerCount,
2407
+ minTraceWidth: pipeline.inputProblem.minTraceWidth
2408
+ },
2317
2409
  gridOptions: pipeline.inputProblem.gridOptions,
2318
2410
  obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
2319
- boardVoidRects: pipeline.inputProblem.boardVoidRects
2411
+ boardVoidRects: pipeline.inputProblem.boardVoidRects,
2412
+ layerNames: pipeline.layerNames,
2413
+ zIndexByName: pipeline.zIndexByName,
2414
+ obstacleClearance: pipeline.inputProblem.obstacleClearance
2320
2415
  }
2321
2416
  ]
2322
2417
  ),
@@ -2330,10 +2425,9 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2330
2425
  }
2331
2426
  return [
2332
2427
  {
2333
- srj: pipeline.inputProblem.simpleRouteJson,
2334
2428
  layerNames: output.layerNames ?? [],
2335
2429
  boardVoidRects: pipeline.inputProblem.boardVoidRects ?? [],
2336
- layerCount: pipeline.inputProblem.simpleRouteJson.layerCount,
2430
+ layerCount: pipeline.inputProblem.layerCount,
2337
2431
  bounds: output.bounds,
2338
2432
  candidates: output.candidates,
2339
2433
  consumedSeedsThisGrid: output.placed.length,
@@ -2343,7 +2437,11 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2343
2437
  gridIndex: output.gridIndex,
2344
2438
  expansionIndex: output.expansionIndex,
2345
2439
  obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
2346
- options: output.options
2440
+ options: output.options,
2441
+ zIndexByName: pipeline.zIndexByName,
2442
+ layerNamesCanonical: pipeline.layerNames,
2443
+ obstacles: pipeline.inputProblem.obstacles,
2444
+ obstacleClearance: pipeline.inputProblem.obstacleClearance
2347
2445
  }
2348
2446
  ];
2349
2447
  }
@@ -2424,6 +2522,7 @@ function createBaseVisualization(srj, title = "RectDiff") {
2424
2522
  }
2425
2523
  for (const obstacle of srj.obstacles ?? []) {
2426
2524
  if (obstacle.type === "rect" || obstacle.type === "oval") {
2525
+ const layerLabel = (obstacle.zLayers ?? []).join(",") || "all";
2427
2526
  rects.push({
2428
2527
  center: { x: obstacle.center.x, y: obstacle.center.y },
2429
2528
  width: obstacle.width,
@@ -2431,7 +2530,8 @@ function createBaseVisualization(srj, title = "RectDiff") {
2431
2530
  fill: "#fee2e2",
2432
2531
  stroke: "#ef4444",
2433
2532
  layer: "obstacle",
2434
- label: "obstacle"
2533
+ label: `obstacle
2534
+ z:${layerLabel}`
2435
2535
  });
2436
2536
  }
2437
2537
  }
@@ -2444,20 +2544,140 @@ function createBaseVisualization(srj, title = "RectDiff") {
2444
2544
  };
2445
2545
  }
2446
2546
 
2547
+ // lib/buildFinalRectDiffVisualization.ts
2548
+ import { mergeGraphics } from "graphics-debug";
2549
+
2550
+ // lib/utils/buildOutlineGraphics.ts
2551
+ var buildOutlineGraphics = ({
2552
+ srj
2553
+ }) => {
2554
+ const hasOutline = srj.outline && srj.outline.length > 1;
2555
+ const lines = hasOutline ? [
2556
+ {
2557
+ points: [...srj.outline, srj.outline[0]],
2558
+ strokeColor: "#111827",
2559
+ strokeWidth: 0.1,
2560
+ label: "outline"
2561
+ }
2562
+ ] : [
2563
+ {
2564
+ points: [
2565
+ { x: srj.bounds.minX, y: srj.bounds.minY },
2566
+ { x: srj.bounds.maxX, y: srj.bounds.minY },
2567
+ { x: srj.bounds.maxX, y: srj.bounds.maxY },
2568
+ { x: srj.bounds.minX, y: srj.bounds.maxY },
2569
+ { x: srj.bounds.minX, y: srj.bounds.minY }
2570
+ ],
2571
+ strokeColor: "#111827",
2572
+ strokeWidth: 0.1,
2573
+ label: "bounds"
2574
+ }
2575
+ ];
2576
+ return {
2577
+ title: "SimpleRoute Outline",
2578
+ coordinateSystem: "cartesian",
2579
+ lines
2580
+ };
2581
+ };
2582
+
2583
+ // lib/utils/renderObstacleClearance.ts
2584
+ var buildObstacleClearanceGraphics = (params) => {
2585
+ const { srj, clearance } = params;
2586
+ const c = clearance ?? 0;
2587
+ if (c <= 0) {
2588
+ return {
2589
+ title: "Obstacle Clearance",
2590
+ coordinateSystem: "cartesian",
2591
+ rects: []
2592
+ };
2593
+ }
2594
+ const rects = [];
2595
+ for (const obstacle of srj.obstacles ?? []) {
2596
+ if (obstacle.type !== "rect" && obstacle.type !== "oval") continue;
2597
+ const expanded = {
2598
+ x: obstacle.center.x - obstacle.width / 2 - c,
2599
+ y: obstacle.center.y - obstacle.height / 2 - c,
2600
+ width: obstacle.width + 2 * c,
2601
+ height: obstacle.height + 2 * c
2602
+ };
2603
+ rects.push({
2604
+ center: {
2605
+ x: expanded.x + expanded.width / 2,
2606
+ y: expanded.y + expanded.height / 2
2607
+ },
2608
+ width: expanded.width,
2609
+ height: expanded.height,
2610
+ stroke: "rgba(202, 138, 4, 0.9)",
2611
+ fill: "rgba(234, 179, 8, 0.15)",
2612
+ layer: "obstacle-clearance",
2613
+ label: `clearance
2614
+ z:${(obstacle.zLayers ?? []).join(",") || "all"}`
2615
+ });
2616
+ }
2617
+ return {
2618
+ title: "Obstacle Clearance",
2619
+ coordinateSystem: "cartesian",
2620
+ rects
2621
+ };
2622
+ };
2623
+
2624
+ // lib/buildFinalRectDiffVisualization.ts
2625
+ var buildFinalRectDiffVisualization = ({
2626
+ srj,
2627
+ meshNodes,
2628
+ obstacleClearance
2629
+ }) => {
2630
+ const outline = buildOutlineGraphics({ srj });
2631
+ const clearance = buildObstacleClearanceGraphics({
2632
+ srj,
2633
+ clearance: obstacleClearance
2634
+ });
2635
+ const rects = meshNodes.map((node) => ({
2636
+ center: node.center,
2637
+ width: node.width,
2638
+ height: node.height,
2639
+ stroke: getColorForZLayer(node.availableZ).stroke,
2640
+ fill: node._containsObstacle ? "#fca5a5" : getColorForZLayer(node.availableZ).fill,
2641
+ layer: `z${node.availableZ.join(",")}`,
2642
+ label: `node ${node.capacityMeshNodeId}
2643
+ z:${node.availableZ.join(",")}`
2644
+ }));
2645
+ const nodesGraphic = {
2646
+ title: "RectDiffPipeline - Final",
2647
+ coordinateSystem: "cartesian",
2648
+ rects,
2649
+ lines: [],
2650
+ points: [],
2651
+ texts: []
2652
+ };
2653
+ return mergeGraphics(mergeGraphics(nodesGraphic, outline), clearance);
2654
+ };
2655
+
2447
2656
  // lib/RectDiffPipeline.ts
2657
+ import { mergeGraphics as mergeGraphics2 } from "graphics-debug";
2448
2658
  var RectDiffPipeline = class extends BasePipelineSolver3 {
2449
2659
  rectDiffGridSolverPipeline;
2450
2660
  gapFillSolver;
2451
2661
  boardVoidRects;
2662
+ zIndexByName;
2663
+ layerNames;
2452
2664
  pipelineDef = [
2453
2665
  definePipelineStep3(
2454
2666
  "rectDiffGridSolverPipeline",
2455
2667
  RectDiffGridSolverPipeline,
2456
2668
  (rectDiffPipeline) => [
2457
2669
  {
2458
- simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
2670
+ bounds: rectDiffPipeline.inputProblem.simpleRouteJson.bounds,
2671
+ obstacles: rectDiffPipeline.inputProblem.simpleRouteJson.obstacles,
2672
+ connections: rectDiffPipeline.inputProblem.simpleRouteJson.connections,
2673
+ outline: rectDiffPipeline.inputProblem.simpleRouteJson.outline ? { outline: rectDiffPipeline.inputProblem.simpleRouteJson.outline } : void 0,
2674
+ layerCount: rectDiffPipeline.inputProblem.simpleRouteJson.layerCount,
2459
2675
  gridOptions: rectDiffPipeline.inputProblem.gridOptions,
2460
- boardVoidRects: rectDiffPipeline.boardVoidRects
2676
+ boardVoidRects: rectDiffPipeline.boardVoidRects,
2677
+ layerNames: rectDiffPipeline.layerNames,
2678
+ zIndexByName: rectDiffPipeline.zIndexByName,
2679
+ minTraceWidth: rectDiffPipeline.inputProblem.simpleRouteJson.minTraceWidth,
2680
+ obstacleClearance: rectDiffPipeline.inputProblem.obstacleClearance
2461
2681
  }
2462
2682
  ]
2463
2683
  ),
@@ -2476,6 +2696,12 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2476
2696
  )
2477
2697
  ];
2478
2698
  _setup() {
2699
+ const { zIndexByName, layerNames } = buildZIndexMap({
2700
+ obstacles: this.inputProblem.simpleRouteJson.obstacles,
2701
+ layerCount: this.inputProblem.simpleRouteJson.layerCount
2702
+ });
2703
+ this.zIndexByName = zIndexByName;
2704
+ this.layerNames = layerNames;
2479
2705
  if (this.inputProblem.simpleRouteJson.outline) {
2480
2706
  this.boardVoidRects = computeInverseRects(
2481
2707
  {
@@ -2502,13 +2728,19 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2502
2728
  return { meshNodes: [] };
2503
2729
  }
2504
2730
  initialVisualize() {
2505
- const graphics = createBaseVisualization(
2731
+ const base = createBaseVisualization(
2506
2732
  this.inputProblem.simpleRouteJson,
2507
2733
  "RectDiffPipeline - Initial"
2508
2734
  );
2735
+ const clearance = buildObstacleClearanceGraphics({
2736
+ srj: this.inputProblem.simpleRouteJson,
2737
+ clearance: this.inputProblem.obstacleClearance
2738
+ });
2509
2739
  const initialNodes = this.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [];
2510
- for (const node of initialNodes) {
2511
- graphics.rects.push({
2740
+ const nodeRects = {
2741
+ title: "Initial Nodes",
2742
+ coordinateSystem: "cartesian",
2743
+ rects: initialNodes.map((node) => ({
2512
2744
  center: node.center,
2513
2745
  width: node.width,
2514
2746
  height: node.height,
@@ -2519,37 +2751,16 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2519
2751
  `node ${node.capacityMeshNodeId}`,
2520
2752
  `z:${node.availableZ.join(",")}`
2521
2753
  ].join("\n")
2522
- });
2523
- }
2524
- return graphics;
2754
+ }))
2755
+ };
2756
+ return mergeGraphics2(mergeGraphics2(base, clearance), nodeRects);
2525
2757
  }
2526
2758
  finalVisualize() {
2527
- const graphics = createBaseVisualization(
2528
- this.inputProblem.simpleRouteJson,
2529
- "RectDiffPipeline - Final"
2530
- );
2531
- const { meshNodes: outputNodes } = this.getOutput();
2532
- const initialNodeIds = new Set(
2533
- (this.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? []).map(
2534
- (n) => n.capacityMeshNodeId
2535
- )
2536
- );
2537
- for (const node of outputNodes) {
2538
- const isExpanded = !initialNodeIds.has(node.capacityMeshNodeId);
2539
- graphics.rects.push({
2540
- center: node.center,
2541
- width: node.width,
2542
- height: node.height,
2543
- stroke: isExpanded ? "rgba(0, 128, 0, 0.8)" : "rgba(0, 0, 0, 0.3)",
2544
- fill: isExpanded ? "rgba(0, 200, 0, 0.3)" : "rgba(100, 100, 100, 0.1)",
2545
- layer: `z${node.availableZ.join(",")}`,
2546
- label: [
2547
- `${isExpanded ? "[expanded] " : ""}node ${node.capacityMeshNodeId}`,
2548
- `z:${node.availableZ.join(",")}`
2549
- ].join("\n")
2550
- });
2551
- }
2552
- return graphics;
2759
+ return buildFinalRectDiffVisualization({
2760
+ srj: this.inputProblem.simpleRouteJson,
2761
+ meshNodes: this.getOutput().meshNodes,
2762
+ obstacleClearance: this.inputProblem.obstacleClearance
2763
+ });
2553
2764
  }
2554
2765
  };
2555
2766
  export {