@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.
- package/components/SolverDebugger3d.tsx +2 -2
- package/dist/index.d.ts +23 -3
- package/dist/index.js +236 -60
- package/lib/RectDiffPipeline.ts +62 -22
- package/lib/fixtures/twoNodeExpansionFixture.ts +10 -2
- package/lib/rectdiff-visualization.ts +2 -1
- package/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts +8 -3
- package/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts +48 -9
- package/lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts +14 -6
- package/lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts +41 -5
- package/lib/solvers/RectDiffSeedingSolver/computeInverseRects.ts +37 -1
- package/lib/solvers/RectDiffSeedingSolver/layers.ts +9 -5
- package/lib/utils/expandRectFromSeed.ts +11 -5
- package/lib/utils/finalizeRects.ts +17 -9
- package/lib/utils/padRect.ts +11 -0
- package/lib/utils/renderObstacleClearance.ts +50 -0
- package/package.json +1 -1
- package/pages/bugreport11.page.tsx +1 -0
- package/test-assets/bugreport-c7537683-stalling.json +1107 -0
- package/tests/board-outline.test.ts +1 -1
- package/tests/bugreport-stalling.test.ts +102 -0
- package/tests/fixtures/makeSimpleRouteOutlineGraphics.ts +5 -1
- package/tests/should-expand-node.test.ts +9 -1
- package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +2 -2
- package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c-clearance.snap.svg +44 -0
- package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +2 -2
- package/tests/solver/bugreport11-b2de3c/bugreport11-b2de3c-clearance.test.ts +97 -0
- package/tests/solver/bugreport26-66b0b2/__snapshots__/bugreport26-66b0b2.snap.svg +2 -2
- package/tests/solver/bugreport27-dd3734/__snapshots__/bugreport27-dd3734.snap.svg +2 -2
- package/tests/solver/bugreport28-18a9ef/__snapshots__/bugreport28-18a9ef.snap.svg +2 -2
- package/tests/solver/bugreport29-7deae8/__snapshots__/bugreport29-7deae8.snap.svg +2 -2
- package/tests/solver/bugreport30-2174c8/__snapshots__/bugreport30-2174c8.snap.svg +2 -2
- package/tests/solver/bugreport33-213d45/__snapshots__/bugreport33-213d45.snap.svg +2 -2
- package/tests/solver/bugreport34-e9dea2/__snapshots__/bugreport34-e9dea2.snap.svg +2 -2
- package/tests/solver/bugreport35-191db9/__snapshots__/bugreport35-191db9.snap.svg +2 -2
- 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 ||
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
862
|
+
function buildZIndexMap(params) {
|
|
843
863
|
const names = canonicalizeLayerOrder(
|
|
844
|
-
(
|
|
864
|
+
(params.obstacles ?? []).flatMap((o) => o.layers ?? [])
|
|
845
865
|
);
|
|
846
|
-
const declaredLayerCount = Math.max(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
|
-
|
|
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 >
|
|
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 >
|
|
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 >
|
|
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 >
|
|
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
|
|
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.
|
|
2029
|
-
const
|
|
2030
|
-
if (!
|
|
2031
|
-
const
|
|
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
|
-
|
|
2162
|
-
|
|
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(
|
|
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
|
|
2257
|
-
if (!
|
|
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:
|
|
2282
|
-
|
|
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:
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2487
|
-
|
|
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
|
|
2666
|
+
}))
|
|
2667
|
+
};
|
|
2668
|
+
return mergeGraphics(mergeGraphics(base, clearance), nodeRects);
|
|
2501
2669
|
}
|
|
2502
2670
|
finalVisualize() {
|
|
2503
|
-
const
|
|
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
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
`z
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
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 {
|