@tscircuit/rectdiff 0.0.21 → 0.0.23

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 (36) hide show
  1. package/components/SolverDebugger3d.tsx +2 -2
  2. package/dist/index.d.ts +23 -3
  3. package/dist/index.js +236 -60
  4. package/lib/RectDiffPipeline.ts +62 -22
  5. package/lib/fixtures/twoNodeExpansionFixture.ts +10 -2
  6. package/lib/rectdiff-visualization.ts +2 -1
  7. package/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts +8 -3
  8. package/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts +48 -9
  9. package/lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts +14 -6
  10. package/lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts +41 -5
  11. package/lib/solvers/RectDiffSeedingSolver/computeInverseRects.ts +37 -1
  12. package/lib/solvers/RectDiffSeedingSolver/layers.ts +9 -5
  13. package/lib/utils/expandRectFromSeed.ts +11 -5
  14. package/lib/utils/finalizeRects.ts +17 -9
  15. package/lib/utils/padRect.ts +11 -0
  16. package/lib/utils/renderObstacleClearance.ts +50 -0
  17. package/package.json +1 -1
  18. package/pages/bugreport11.page.tsx +1 -0
  19. package/test-assets/bugreport-c7537683-stalling.json +1107 -0
  20. package/tests/board-outline.test.ts +1 -1
  21. package/tests/bugreport-stalling.test.ts +102 -0
  22. package/tests/fixtures/makeSimpleRouteOutlineGraphics.ts +5 -1
  23. package/tests/should-expand-node.test.ts +9 -1
  24. package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +2 -2
  25. package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c-clearance.snap.svg +44 -0
  26. package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +2 -2
  27. package/tests/solver/bugreport11-b2de3c/bugreport11-b2de3c-clearance.test.ts +97 -0
  28. package/tests/solver/bugreport26-66b0b2/__snapshots__/bugreport26-66b0b2.snap.svg +2 -2
  29. package/tests/solver/bugreport27-dd3734/__snapshots__/bugreport27-dd3734.snap.svg +2 -2
  30. package/tests/solver/bugreport28-18a9ef/__snapshots__/bugreport28-18a9ef.snap.svg +2 -2
  31. package/tests/solver/bugreport29-7deae8/__snapshots__/bugreport29-7deae8.snap.svg +2 -2
  32. package/tests/solver/bugreport30-2174c8/__snapshots__/bugreport30-2174c8.snap.svg +2 -2
  33. package/tests/solver/bugreport33-213d45/__snapshots__/bugreport33-213d45.snap.svg +2 -2
  34. package/tests/solver/bugreport34-e9dea2/__snapshots__/bugreport34-e9dea2.snap.svg +2 -2
  35. package/tests/solver/bugreport35-191db9/__snapshots__/bugreport35-191db9.snap.svg +2 -2
  36. package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +1 -1
@@ -602,7 +602,7 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
602
602
  useEffect(() => {
603
603
  const interval = setInterval(() => {
604
604
  // Only update if solver has output available
605
- if (solver.solved || (solver as any).stats?.placed > 0) {
605
+ if (solver.solved || solver.stats?.placed > 0) {
606
606
  updateMeshNodes()
607
607
  }
608
608
  }, 100) // Poll every 100ms during active solving
@@ -731,7 +731,7 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
731
731
  {/* Render 2D view */}
732
732
  {renderMode === "2d" && (
733
733
  <GenericSolverDebugger
734
- solver={solver as any}
734
+ solver={solver}
735
735
  onSolverCompleted={handleSolverCompleted}
736
736
  />
737
737
  )}
package/dist/index.d.ts CHANGED
@@ -220,6 +220,9 @@ type RectDiffSeedingSolverInput = {
220
220
  obstacleIndexByLayer: Array<RBush<RTreeRect>>;
221
221
  gridOptions?: Partial<GridFill3DOptions>;
222
222
  boardVoidRects?: XYRect[];
223
+ layerNames: string[];
224
+ zIndexByName: Map<string, number>;
225
+ obstacleClearance?: number;
223
226
  };
224
227
  /**
225
228
  * First phase of RectDiff: grid-based seeding and placement.
@@ -258,7 +261,6 @@ declare class RectDiffSeedingSolver extends BaseSolver {
258
261
  * expansion phase solver.
259
262
  */
260
263
  getOutput(): {
261
- srj: SimpleRouteJson;
262
264
  layerNames: string[];
263
265
  layerCount: number;
264
266
  bounds: XYRect;
@@ -274,13 +276,14 @@ declare class RectDiffSeedingSolver extends BaseSolver {
274
276
  edgeAnalysisDone: boolean;
275
277
  totalSeedsThisGrid: number;
276
278
  consumedSeedsThisGrid: number;
279
+ obstacles: Obstacle[];
280
+ obstacleClearance: number | undefined;
277
281
  };
278
282
  /** Visualization focused on the grid seeding phase. */
279
283
  visualize(): GraphicsObject;
280
284
  }
281
285
 
282
286
  type RectDiffExpansionSolverInput = {
283
- srj: SimpleRouteJson;
284
287
  layerNames: string[];
285
288
  layerCount: number;
286
289
  bounds: XYRect;
@@ -297,6 +300,10 @@ type RectDiffExpansionSolverInput = {
297
300
  totalSeedsThisGrid: number;
298
301
  consumedSeedsThisGrid: number;
299
302
  obstacleIndexByLayer: Array<RBush<RTreeRect>>;
303
+ zIndexByName: Map<string, number>;
304
+ layerNamesCanonical: string[];
305
+ obstacles: Obstacle[];
306
+ obstacleClearance?: number;
300
307
  };
301
308
  /**
302
309
  * Second phase of RectDiff: expand placed rects to their maximal extents.
@@ -322,14 +329,24 @@ declare class RectDiffExpansionSolver extends BaseSolver {
322
329
  }
323
330
 
324
331
  type RectDiffGridSolverPipelineInput = {
325
- simpleRouteJson: SimpleRouteJson;
332
+ bounds: Bounds;
333
+ obstacles: Obstacle[];
334
+ connections: SimpleRouteConnection[];
335
+ outline?: Pick<SimpleRouteJson, "outline">;
336
+ layerCount: number;
337
+ minTraceWidth: number;
338
+ obstacleClearance?: number;
326
339
  gridOptions?: Partial<GridFill3DOptions>;
327
340
  boardVoidRects?: XYRect[];
341
+ layerNames?: string[];
342
+ zIndexByName?: Map<string, number>;
328
343
  };
329
344
  declare class RectDiffGridSolverPipeline extends BasePipelineSolver<RectDiffGridSolverPipelineInput> {
330
345
  rectDiffSeedingSolver?: RectDiffSeedingSolver;
331
346
  rectDiffExpansionSolver?: RectDiffExpansionSolver;
332
347
  private obstacleIndexByLayer;
348
+ private layerNames;
349
+ private zIndexByName;
333
350
  constructor(inputProblem: RectDiffGridSolverPipelineInput);
334
351
  pipelineDef: PipelineStep<any>[];
335
352
  getConstructorParams(): RectDiffGridSolverPipelineInput[];
@@ -342,11 +359,14 @@ declare class RectDiffGridSolverPipeline extends BasePipelineSolver<RectDiffGrid
342
359
  interface RectDiffPipelineInput {
343
360
  simpleRouteJson: SimpleRouteJson;
344
361
  gridOptions?: Partial<GridFill3DOptions>;
362
+ obstacleClearance?: number;
345
363
  }
346
364
  declare class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput> {
347
365
  rectDiffGridSolverPipeline?: RectDiffGridSolverPipeline;
348
366
  gapFillSolver?: GapFillSolverPipeline;
349
367
  boardVoidRects: XYRect[] | undefined;
368
+ zIndexByName?: Map<string, number>;
369
+ layerNames?: string[];
350
370
  pipelineDef: PipelineStep<any>[];
351
371
  _setup(): void;
352
372
  getConstructorParams(): RectDiffPipelineInput[];
package/dist/index.js CHANGED
@@ -749,11 +749,31 @@ function isPointInPolygon(p, polygon) {
749
749
  }
750
750
 
751
751
  // lib/solvers/RectDiffSeedingSolver/computeInverseRects.ts
752
+ function simplifyPolygon(polygon, precision) {
753
+ const round = (v) => Math.round(v / precision) * precision;
754
+ const seen = /* @__PURE__ */ new Set();
755
+ const result = [];
756
+ for (const p of polygon) {
757
+ const rx = round(p.x);
758
+ const ry = round(p.y);
759
+ const key = `${rx},${ry}`;
760
+ if (!seen.has(key)) {
761
+ seen.add(key);
762
+ result.push({ x: rx, y: ry });
763
+ }
764
+ }
765
+ return result;
766
+ }
752
767
  function computeInverseRects(bounds, polygon) {
753
768
  if (!polygon || polygon.length < 3) return [];
769
+ const MAX_POLYGON_POINTS = 100;
770
+ const workingPolygon = polygon.length > MAX_POLYGON_POINTS ? simplifyPolygon(
771
+ polygon,
772
+ Math.max(bounds.width, bounds.height) / MAX_POLYGON_POINTS
773
+ ) : polygon;
754
774
  const xs = /* @__PURE__ */ new Set([bounds.x, bounds.x + bounds.width]);
755
775
  const ys = /* @__PURE__ */ new Set([bounds.y, bounds.y + bounds.height]);
756
- for (const p of polygon) {
776
+ for (const p of workingPolygon) {
757
777
  xs.add(p.x);
758
778
  ys.add(p.y);
759
779
  }
@@ -839,11 +859,11 @@ function canonicalizeLayerOrder(names) {
839
859
  return a.localeCompare(b);
840
860
  });
841
861
  }
842
- function buildZIndexMap(srj) {
862
+ function buildZIndexMap(params) {
843
863
  const names = canonicalizeLayerOrder(
844
- (srj.obstacles ?? []).flatMap((o) => o.layers ?? [])
864
+ (params.obstacles ?? []).flatMap((o) => o.layers ?? [])
845
865
  );
846
- const declaredLayerCount = Math.max(1, srj.layerCount || names.length || 1);
866
+ const declaredLayerCount = Math.max(1, params.layerCount || names.length || 1);
847
867
  const fallback = Array.from(
848
868
  { length: declaredLayerCount },
849
869
  (_, i) => i === 0 ? "top" : i === declaredLayerCount - 1 ? "bottom" : `inner${i}`
@@ -1113,34 +1133,38 @@ function expandRectFromSeed(params) {
1113
1133
  continue;
1114
1134
  }
1115
1135
  for (const b of blockers) if (overlaps(r, b)) continue STRATS;
1136
+ const MIN_EXPANSION = 1e-6;
1137
+ const MAX_ITERATIONS = 1e3;
1116
1138
  let improved = true;
1117
- while (improved) {
1139
+ let iterations = 0;
1140
+ while (improved && iterations < MAX_ITERATIONS) {
1141
+ iterations++;
1118
1142
  improved = false;
1119
1143
  const commonParams = { bounds, blockers, maxAspect: maxAspectRatio };
1120
1144
  collectBlockers(searchStripRight({ rect: r, bounds }));
1121
1145
  const eR = maxExpandRight({ ...commonParams, r });
1122
- if (eR > 0) {
1146
+ if (eR > MIN_EXPANSION) {
1123
1147
  r = { ...r, width: r.width + eR };
1124
1148
  collectBlockers(r);
1125
1149
  improved = true;
1126
1150
  }
1127
1151
  collectBlockers(searchStripDown({ rect: r, bounds }));
1128
1152
  const eD = maxExpandDown({ ...commonParams, r });
1129
- if (eD > 0) {
1153
+ if (eD > MIN_EXPANSION) {
1130
1154
  r = { ...r, height: r.height + eD };
1131
1155
  collectBlockers(r);
1132
1156
  improved = true;
1133
1157
  }
1134
1158
  collectBlockers(searchStripLeft({ rect: r, bounds }));
1135
1159
  const eL = maxExpandLeft({ ...commonParams, r });
1136
- if (eL > 0) {
1160
+ if (eL > MIN_EXPANSION) {
1137
1161
  r = { x: r.x - eL, y: r.y, width: r.width + eL, height: r.height };
1138
1162
  collectBlockers(r);
1139
1163
  improved = true;
1140
1164
  }
1141
1165
  collectBlockers(searchStripUp({ rect: r, bounds }));
1142
1166
  const eU = maxExpandUp({ ...commonParams, r });
1143
- if (eU > 0) {
1167
+ if (eU > MIN_EXPANSION) {
1144
1168
  r = { x: r.x, y: r.y - eU, width: r.width, height: r.height + eU };
1145
1169
  collectBlockers(r);
1146
1170
  improved = true;
@@ -1686,7 +1710,14 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1686
1710
  _setup() {
1687
1711
  const srj = this.input.simpleRouteJson;
1688
1712
  const opts = this.input.gridOptions ?? {};
1689
- const { layerNames, zIndexByName } = buildZIndexMap(srj);
1713
+ const precomputed = this.input.layerNames && this.input.zIndexByName;
1714
+ const { layerNames, zIndexByName } = precomputed ? {
1715
+ layerNames: this.input.layerNames,
1716
+ zIndexByName: this.input.zIndexByName
1717
+ } : buildZIndexMap({
1718
+ obstacles: srj.obstacles,
1719
+ layerCount: srj.layerCount
1720
+ });
1690
1721
  const layerCount = Math.max(1, layerNames.length, srj.layerCount || 1);
1691
1722
  const bounds = {
1692
1723
  x: srj.bounds.minX,
@@ -1884,7 +1915,6 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1884
1915
  */
1885
1916
  getOutput() {
1886
1917
  return {
1887
- srj: this.srj,
1888
1918
  layerNames: this.layerNames,
1889
1919
  layerCount: this.layerCount,
1890
1920
  bounds: this.bounds,
@@ -1896,7 +1926,9 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1896
1926
  expansionIndex: this.expansionIndex,
1897
1927
  edgeAnalysisDone: this.edgeAnalysisDone,
1898
1928
  totalSeedsThisGrid: this.totalSeedsThisGrid,
1899
- consumedSeedsThisGrid: this.consumedSeedsThisGrid
1929
+ consumedSeedsThisGrid: this.consumedSeedsThisGrid,
1930
+ obstacles: this.srj.obstacles,
1931
+ obstacleClearance: this.input.obstacleClearance
1900
1932
  };
1901
1933
  }
1902
1934
  /** Visualization focused on the grid seeding phase. */
@@ -1944,6 +1976,29 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1944
1976
  });
1945
1977
  }
1946
1978
  }
1979
+ if (this.input.obstacleClearance && this.input.obstacleClearance > 0) {
1980
+ for (const obstacle of srj.obstacles ?? []) {
1981
+ const pad = this.input.obstacleClearance;
1982
+ const expanded = {
1983
+ x: obstacle.center.x - obstacle.width / 2 - pad,
1984
+ y: obstacle.center.y - obstacle.height / 2 - pad,
1985
+ width: obstacle.width + 2 * pad,
1986
+ height: obstacle.height + 2 * pad
1987
+ };
1988
+ rects.push({
1989
+ center: {
1990
+ x: expanded.x + expanded.width / 2,
1991
+ y: expanded.y + expanded.height / 2
1992
+ },
1993
+ width: expanded.width,
1994
+ height: expanded.height,
1995
+ fill: "rgba(234, 179, 8, 0.15)",
1996
+ stroke: "rgba(202, 138, 4, 0.9)",
1997
+ layer: "obstacle-clearance",
1998
+ label: "clearance"
1999
+ });
2000
+ }
2001
+ }
1947
2002
  if (this.boardVoidRects) {
1948
2003
  let outlineBBox = null;
1949
2004
  if (srj.outline && srj.outline.length > 0) {
@@ -1977,8 +2032,6 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
1977
2032
  points.push({
1978
2033
  x: cand.x,
1979
2034
  y: cand.y,
1980
- fill: "#9333ea",
1981
- stroke: "#6b21a8",
1982
2035
  label: `z:${cand.z}`
1983
2036
  });
1984
2037
  }
@@ -2023,12 +2076,17 @@ function finalizeRects(params) {
2023
2076
  maxY: p.rect.y + p.rect.height,
2024
2077
  zLayers: [...p.zLayers].sort((a, b) => a - b)
2025
2078
  }));
2026
- const { zIndexByName } = buildZIndexMap(params.srj);
2027
2079
  const layersByKey = /* @__PURE__ */ new Map();
2028
- for (const obstacle of params.srj.obstacles ?? []) {
2029
- const rect = obstacleToXYRect(obstacle);
2030
- if (!rect) continue;
2031
- const zLayers = obstacle.zLayers?.length && obstacle.zLayers.length > 0 ? obstacle.zLayers : obstacleZs(obstacle, zIndexByName);
2080
+ for (const obstacle of params.obstacles ?? []) {
2081
+ const baseRect = obstacleToXYRect(obstacle);
2082
+ if (!baseRect) continue;
2083
+ const rect = params.obstacleClearance ? {
2084
+ x: baseRect.x - params.obstacleClearance,
2085
+ y: baseRect.y - params.obstacleClearance,
2086
+ width: baseRect.width + 2 * params.obstacleClearance,
2087
+ height: baseRect.height + 2 * params.obstacleClearance
2088
+ } : baseRect;
2089
+ const zLayers = obstacle.zLayers?.length && obstacle.zLayers.length > 0 ? obstacle.zLayers : obstacleZs(obstacle, params.zIndexByName);
2032
2090
  const key = `${rect.x}:${rect.y}:${rect.width}:${rect.height}`;
2033
2091
  let entry = layersByKey.get(key);
2034
2092
  if (!entry) {
@@ -2158,8 +2216,10 @@ var RectDiffExpansionSolver = class extends BaseSolver4 {
2158
2216
  if (this.solved) return;
2159
2217
  const rects = finalizeRects({
2160
2218
  placed: this.input.placed,
2161
- srj: this.input.srj,
2162
- boardVoidRects: this.input.boardVoidRects
2219
+ obstacles: this.input.obstacles,
2220
+ zIndexByName: this.input.zIndexByName,
2221
+ boardVoidRects: this.input.boardVoidRects,
2222
+ obstacleClearance: this.input.obstacleClearance
2163
2223
  });
2164
2224
  this._meshNodes = rectsToMeshNodes(rects);
2165
2225
  this.solved = true;
@@ -2222,9 +2282,25 @@ import "rbush";
2222
2282
 
2223
2283
  // lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
2224
2284
  import RBush5 from "rbush";
2285
+
2286
+ // lib/utils/padRect.ts
2287
+ var padRect = (rect, clearance) => {
2288
+ if (!clearance || clearance <= 0) return rect;
2289
+ return {
2290
+ x: rect.x - clearance,
2291
+ y: rect.y - clearance,
2292
+ width: rect.width + 2 * clearance,
2293
+ height: rect.height + 2 * clearance
2294
+ };
2295
+ };
2296
+
2297
+ // lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
2225
2298
  var buildObstacleIndexesByLayer = (params) => {
2226
- const { srj, boardVoidRects } = params;
2227
- const { layerNames, zIndexByName } = buildZIndexMap(srj);
2299
+ const { srj, boardVoidRects, obstacleClearance } = params;
2300
+ const { layerNames, zIndexByName } = buildZIndexMap({
2301
+ obstacles: srj.obstacles,
2302
+ layerCount: srj.layerCount
2303
+ });
2228
2304
  const layerCount = Math.max(1, layerNames.length, srj.layerCount || 1);
2229
2305
  const bounds = {
2230
2306
  x: srj.bounds.minX,
@@ -2253,8 +2329,9 @@ var buildObstacleIndexesByLayer = (params) => {
2253
2329
  }
2254
2330
  }
2255
2331
  for (const obstacle of srj.obstacles ?? []) {
2256
- const rect = obstacleToXYRect(obstacle);
2257
- if (!rect) continue;
2332
+ const rectBase = obstacleToXYRect(obstacle);
2333
+ if (!rectBase) continue;
2334
+ const rect = padRect(rectBase, obstacleClearance ?? 0);
2258
2335
  const zLayers = obstacleZs(obstacle, zIndexByName);
2259
2336
  const invalidZs = zLayers.filter((z) => z < 0 || z >= layerCount);
2260
2337
  if (invalidZs.length) {
@@ -2267,7 +2344,7 @@ var buildObstacleIndexesByLayer = (params) => {
2267
2344
  }
2268
2345
  for (const z of zLayers) insertObstacle(rect, z);
2269
2346
  }
2270
- return { obstacleIndexByLayer };
2347
+ return { obstacleIndexByLayer, layerNames, zIndexByName };
2271
2348
  };
2272
2349
 
2273
2350
  // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
@@ -2275,13 +2352,25 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2275
2352
  rectDiffSeedingSolver;
2276
2353
  rectDiffExpansionSolver;
2277
2354
  obstacleIndexByLayer;
2355
+ layerNames;
2356
+ zIndexByName;
2278
2357
  constructor(inputProblem) {
2279
2358
  super(inputProblem);
2280
- const { obstacleIndexByLayer } = buildObstacleIndexesByLayer({
2281
- srj: inputProblem.simpleRouteJson,
2282
- boardVoidRects: inputProblem.boardVoidRects
2359
+ const { obstacleIndexByLayer, layerNames, zIndexByName } = buildObstacleIndexesByLayer({
2360
+ srj: {
2361
+ bounds: inputProblem.bounds,
2362
+ obstacles: inputProblem.obstacles,
2363
+ connections: inputProblem.connections,
2364
+ outline: inputProblem.outline?.outline,
2365
+ layerCount: inputProblem.layerCount,
2366
+ minTraceWidth: inputProblem.minTraceWidth
2367
+ },
2368
+ boardVoidRects: inputProblem.boardVoidRects,
2369
+ obstacleClearance: inputProblem.obstacleClearance
2283
2370
  });
2284
2371
  this.obstacleIndexByLayer = obstacleIndexByLayer;
2372
+ this.layerNames = inputProblem.layerNames ?? layerNames;
2373
+ this.zIndexByName = inputProblem.zIndexByName ?? zIndexByName;
2285
2374
  }
2286
2375
  pipelineDef = [
2287
2376
  definePipelineStep2(
@@ -2289,10 +2378,20 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2289
2378
  RectDiffSeedingSolver,
2290
2379
  (pipeline) => [
2291
2380
  {
2292
- simpleRouteJson: pipeline.inputProblem.simpleRouteJson,
2381
+ simpleRouteJson: {
2382
+ bounds: pipeline.inputProblem.bounds,
2383
+ obstacles: pipeline.inputProblem.obstacles,
2384
+ connections: pipeline.inputProblem.connections,
2385
+ outline: pipeline.inputProblem.outline?.outline,
2386
+ layerCount: pipeline.inputProblem.layerCount,
2387
+ minTraceWidth: pipeline.inputProblem.minTraceWidth
2388
+ },
2293
2389
  gridOptions: pipeline.inputProblem.gridOptions,
2294
2390
  obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
2295
- boardVoidRects: pipeline.inputProblem.boardVoidRects
2391
+ boardVoidRects: pipeline.inputProblem.boardVoidRects,
2392
+ layerNames: pipeline.layerNames,
2393
+ zIndexByName: pipeline.zIndexByName,
2394
+ obstacleClearance: pipeline.inputProblem.obstacleClearance
2296
2395
  }
2297
2396
  ]
2298
2397
  ),
@@ -2306,10 +2405,9 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2306
2405
  }
2307
2406
  return [
2308
2407
  {
2309
- srj: pipeline.inputProblem.simpleRouteJson,
2310
2408
  layerNames: output.layerNames ?? [],
2311
2409
  boardVoidRects: pipeline.inputProblem.boardVoidRects ?? [],
2312
- layerCount: pipeline.inputProblem.simpleRouteJson.layerCount,
2410
+ layerCount: pipeline.inputProblem.layerCount,
2313
2411
  bounds: output.bounds,
2314
2412
  candidates: output.candidates,
2315
2413
  consumedSeedsThisGrid: output.placed.length,
@@ -2319,7 +2417,11 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
2319
2417
  gridIndex: output.gridIndex,
2320
2418
  expansionIndex: output.expansionIndex,
2321
2419
  obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
2322
- options: output.options
2420
+ options: output.options,
2421
+ zIndexByName: pipeline.zIndexByName,
2422
+ layerNamesCanonical: pipeline.layerNames,
2423
+ obstacles: pipeline.inputProblem.obstacles,
2424
+ obstacleClearance: pipeline.inputProblem.obstacleClearance
2323
2425
  }
2324
2426
  ];
2325
2427
  }
@@ -2400,6 +2502,7 @@ function createBaseVisualization(srj, title = "RectDiff") {
2400
2502
  }
2401
2503
  for (const obstacle of srj.obstacles ?? []) {
2402
2504
  if (obstacle.type === "rect" || obstacle.type === "oval") {
2505
+ const layerLabel = (obstacle.zLayers ?? []).join(",") || "all";
2403
2506
  rects.push({
2404
2507
  center: { x: obstacle.center.x, y: obstacle.center.y },
2405
2508
  width: obstacle.width,
@@ -2407,7 +2510,8 @@ function createBaseVisualization(srj, title = "RectDiff") {
2407
2510
  fill: "#fee2e2",
2408
2511
  stroke: "#ef4444",
2409
2512
  layer: "obstacle",
2410
- label: "obstacle"
2513
+ label: `obstacle
2514
+ z:${layerLabel}`
2411
2515
  });
2412
2516
  }
2413
2517
  }
@@ -2420,20 +2524,72 @@ function createBaseVisualization(srj, title = "RectDiff") {
2420
2524
  };
2421
2525
  }
2422
2526
 
2527
+ // lib/utils/renderObstacleClearance.ts
2528
+ var buildObstacleClearanceGraphics = (params) => {
2529
+ const { srj, clearance } = params;
2530
+ const c = clearance ?? 0;
2531
+ if (c <= 0) {
2532
+ return {
2533
+ title: "Obstacle Clearance",
2534
+ coordinateSystem: "cartesian",
2535
+ rects: []
2536
+ };
2537
+ }
2538
+ const rects = [];
2539
+ for (const obstacle of srj.obstacles ?? []) {
2540
+ if (obstacle.type !== "rect" && obstacle.type !== "oval") continue;
2541
+ const expanded = {
2542
+ x: obstacle.center.x - obstacle.width / 2 - c,
2543
+ y: obstacle.center.y - obstacle.height / 2 - c,
2544
+ width: obstacle.width + 2 * c,
2545
+ height: obstacle.height + 2 * c
2546
+ };
2547
+ rects.push({
2548
+ center: {
2549
+ x: expanded.x + expanded.width / 2,
2550
+ y: expanded.y + expanded.height / 2
2551
+ },
2552
+ width: expanded.width,
2553
+ height: expanded.height,
2554
+ stroke: "rgba(202, 138, 4, 0.9)",
2555
+ fill: "rgba(234, 179, 8, 0.15)",
2556
+ layer: "obstacle-clearance",
2557
+ label: `clearance
2558
+ z:${(obstacle.zLayers ?? []).join(",") || "all"}`
2559
+ });
2560
+ }
2561
+ return {
2562
+ title: "Obstacle Clearance",
2563
+ coordinateSystem: "cartesian",
2564
+ rects
2565
+ };
2566
+ };
2567
+
2423
2568
  // lib/RectDiffPipeline.ts
2569
+ import { mergeGraphics } from "graphics-debug";
2424
2570
  var RectDiffPipeline = class extends BasePipelineSolver3 {
2425
2571
  rectDiffGridSolverPipeline;
2426
2572
  gapFillSolver;
2427
2573
  boardVoidRects;
2574
+ zIndexByName;
2575
+ layerNames;
2428
2576
  pipelineDef = [
2429
2577
  definePipelineStep3(
2430
2578
  "rectDiffGridSolverPipeline",
2431
2579
  RectDiffGridSolverPipeline,
2432
2580
  (rectDiffPipeline) => [
2433
2581
  {
2434
- simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
2582
+ bounds: rectDiffPipeline.inputProblem.simpleRouteJson.bounds,
2583
+ obstacles: rectDiffPipeline.inputProblem.simpleRouteJson.obstacles,
2584
+ connections: rectDiffPipeline.inputProblem.simpleRouteJson.connections,
2585
+ outline: rectDiffPipeline.inputProblem.simpleRouteJson.outline ? { outline: rectDiffPipeline.inputProblem.simpleRouteJson.outline } : void 0,
2586
+ layerCount: rectDiffPipeline.inputProblem.simpleRouteJson.layerCount,
2435
2587
  gridOptions: rectDiffPipeline.inputProblem.gridOptions,
2436
- boardVoidRects: rectDiffPipeline.boardVoidRects
2588
+ boardVoidRects: rectDiffPipeline.boardVoidRects,
2589
+ layerNames: rectDiffPipeline.layerNames,
2590
+ zIndexByName: rectDiffPipeline.zIndexByName,
2591
+ minTraceWidth: rectDiffPipeline.inputProblem.simpleRouteJson.minTraceWidth,
2592
+ obstacleClearance: rectDiffPipeline.inputProblem.obstacleClearance
2437
2593
  }
2438
2594
  ]
2439
2595
  ),
@@ -2452,6 +2608,12 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2452
2608
  )
2453
2609
  ];
2454
2610
  _setup() {
2611
+ const { zIndexByName, layerNames } = buildZIndexMap({
2612
+ obstacles: this.inputProblem.simpleRouteJson.obstacles,
2613
+ layerCount: this.inputProblem.simpleRouteJson.layerCount
2614
+ });
2615
+ this.zIndexByName = zIndexByName;
2616
+ this.layerNames = layerNames;
2455
2617
  if (this.inputProblem.simpleRouteJson.outline) {
2456
2618
  this.boardVoidRects = computeInverseRects(
2457
2619
  {
@@ -2478,13 +2640,19 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2478
2640
  return { meshNodes: [] };
2479
2641
  }
2480
2642
  initialVisualize() {
2481
- const graphics = createBaseVisualization(
2643
+ const base = createBaseVisualization(
2482
2644
  this.inputProblem.simpleRouteJson,
2483
2645
  "RectDiffPipeline - Initial"
2484
2646
  );
2647
+ const clearance = buildObstacleClearanceGraphics({
2648
+ srj: this.inputProblem.simpleRouteJson,
2649
+ clearance: this.inputProblem.obstacleClearance
2650
+ });
2485
2651
  const initialNodes = this.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [];
2486
- for (const node of initialNodes) {
2487
- graphics.rects.push({
2652
+ const nodeRects = {
2653
+ title: "Initial Nodes",
2654
+ coordinateSystem: "cartesian",
2655
+ rects: initialNodes.map((node) => ({
2488
2656
  center: node.center,
2489
2657
  width: node.width,
2490
2658
  height: node.height,
@@ -2495,37 +2663,45 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2495
2663
  `node ${node.capacityMeshNodeId}`,
2496
2664
  `z:${node.availableZ.join(",")}`
2497
2665
  ].join("\n")
2498
- });
2499
- }
2500
- return graphics;
2666
+ }))
2667
+ };
2668
+ return mergeGraphics(mergeGraphics(base, clearance), nodeRects);
2501
2669
  }
2502
2670
  finalVisualize() {
2503
- const graphics = createBaseVisualization(
2671
+ const base = createBaseVisualization(
2504
2672
  this.inputProblem.simpleRouteJson,
2505
2673
  "RectDiffPipeline - Final"
2506
2674
  );
2675
+ const clearance = buildObstacleClearanceGraphics({
2676
+ srj: this.inputProblem.simpleRouteJson,
2677
+ clearance: this.inputProblem.obstacleClearance
2678
+ });
2507
2679
  const { meshNodes: outputNodes } = this.getOutput();
2508
2680
  const initialNodeIds = new Set(
2509
2681
  (this.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? []).map(
2510
2682
  (n) => n.capacityMeshNodeId
2511
2683
  )
2512
2684
  );
2513
- for (const node of outputNodes) {
2514
- const isExpanded = !initialNodeIds.has(node.capacityMeshNodeId);
2515
- graphics.rects.push({
2516
- center: node.center,
2517
- width: node.width,
2518
- height: node.height,
2519
- stroke: isExpanded ? "rgba(0, 128, 0, 0.8)" : "rgba(0, 0, 0, 0.3)",
2520
- fill: isExpanded ? "rgba(0, 200, 0, 0.3)" : "rgba(100, 100, 100, 0.1)",
2521
- layer: `z${node.availableZ.join(",")}`,
2522
- label: [
2523
- `${isExpanded ? "[expanded] " : ""}node ${node.capacityMeshNodeId}`,
2524
- `z:${node.availableZ.join(",")}`
2525
- ].join("\n")
2526
- });
2527
- }
2528
- return graphics;
2685
+ const nodeRects = {
2686
+ title: "Final Nodes",
2687
+ coordinateSystem: "cartesian",
2688
+ rects: outputNodes.map((node) => {
2689
+ const isExpanded = !initialNodeIds.has(node.capacityMeshNodeId);
2690
+ return {
2691
+ center: node.center,
2692
+ width: node.width,
2693
+ height: node.height,
2694
+ stroke: isExpanded ? "rgba(0, 128, 0, 0.8)" : "rgba(0, 0, 0, 0.3)",
2695
+ fill: isExpanded ? "rgba(0, 200, 0, 0.3)" : "rgba(100, 100, 100, 0.1)",
2696
+ layer: `z${node.availableZ.join(",")}`,
2697
+ label: [
2698
+ `${isExpanded ? "[expanded] " : ""}node ${node.capacityMeshNodeId}`,
2699
+ `z:${node.availableZ.join(",")}`
2700
+ ].join("\n")
2701
+ };
2702
+ })
2703
+ };
2704
+ return mergeGraphics(mergeGraphics(base, clearance), nodeRects);
2529
2705
  }
2530
2706
  };
2531
2707
  export {