@tscircuit/rectdiff 0.0.23 → 0.0.25
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/AGENTS.md +23 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +195 -144
- package/lib/RectDiffPipeline.ts +4 -37
- package/lib/buildFinalRectDiffVisualization.ts +46 -0
- package/lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts +23 -48
- package/lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts +21 -12
- package/lib/solvers/RectDiffSeedingSolver/computeEdgeCandidates3D.ts +68 -25
- package/lib/solvers/RectDiffSeedingSolver/longestFreeSpanAroundZ.ts +1 -1
- package/lib/utils/buildOutlineGraphics.ts +39 -0
- package/lib/utils/expandRectFromSeed.ts +19 -11
- package/lib/utils/isFullyOccupiedAtPoint.ts +2 -2
- package/lib/utils/rectdiff-geometry.ts +13 -20
- package/package.json +2 -1
- package/scripts/benchmark-slow-problem.ts +94 -0
- package/test-assets/keyboard4.json +16165 -0
- package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +1 -1
- package/tests/solver/bugreport01-be84eb/__snapshots__/bugreport01-be84eb.snap.svg +2 -2
- package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +2 -2
- package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
- package/tests/solver/bugreport07-d3f3be/__snapshots__/bugreport07-d3f3be.snap.svg +1 -1
- package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
- package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +2 -2
- package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +2 -2
- package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +1 -1
- package/tests/solver/bugreport11-b2de3c/bugreport11-b2de3c-clearance-equivalence.test.ts +52 -0
- package/tests/solver/bugreport12-35ce1c/__snapshots__/bugreport12-35ce1c.snap.svg +1 -1
- package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +2 -2
- package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +2 -2
- package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
- package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
- package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +1 -1
- package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
- 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 +1 -1
- 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
- package/tests/solver/interaction/__snapshots__/interaction.snap.svg +1 -1
- package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
- 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/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c-clearance.snap.svg +0 -44
- package/tests/solver/bugreport11-b2de3c/bugreport11-b2de3c-clearance.test.ts +0 -97
package/AGENTS.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
This is an algorithm solver repo designed to be integrated into the tscircuit
|
|
2
|
+
autorouter.
|
|
3
|
+
|
|
4
|
+
It uses the SimpleRouteJson type as input (see lib/types/srj-types.ts)
|
|
5
|
+
|
|
6
|
+
We use svg snapshots to create visual snapshots as our primary method of
|
|
7
|
+
ensuring the algorithm works correctly. We use `export BUN_UPDATE_SNAPSHOTS=1`
|
|
8
|
+
and `bun test path/to/test.test.ts` to update the snapshots.
|
|
9
|
+
|
|
10
|
+
We use the `graphics-debug` package to turn `GraphicsObject` into SVGs using
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { getSvgFromGraphicsObject } from "graphics-debug"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
We use `cosmos` `*.page.tsx` files inside the `pages` directory to create
|
|
17
|
+
examples that can be debugged for a human.
|
|
18
|
+
|
|
19
|
+
We discovered the algorithm via `experiments/rect3d_visualizer.html` and we're
|
|
20
|
+
now formalizing it into a solver pattern and binding to our normal types.
|
|
21
|
+
|
|
22
|
+
The `test-assets` directory contains assets for test, typically simple route
|
|
23
|
+
json files with test cases that can be importing into pages or tests.
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -688,24 +688,18 @@ function containsPoint(r, p) {
|
|
|
688
688
|
return p.x >= r.x - EPS4 && p.x <= r.x + r.width + EPS4 && p.y >= r.y - EPS4 && p.y <= r.y + r.height + EPS4;
|
|
689
689
|
}
|
|
690
690
|
function distancePointToRectEdges(p, r) {
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
t = clamp(t, 0, 1);
|
|
704
|
-
const xx = x1 + t * C;
|
|
705
|
-
const yy = y1 + t * D;
|
|
706
|
-
best = Math.min(best, Math.hypot(p.x - xx, p.y - yy));
|
|
707
|
-
}
|
|
708
|
-
return best;
|
|
691
|
+
const minX = r.x;
|
|
692
|
+
const maxX = r.x + r.width;
|
|
693
|
+
const minY = r.y;
|
|
694
|
+
const maxY = r.y + r.height;
|
|
695
|
+
if (p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY) {
|
|
696
|
+
return Math.min(p.x - minX, maxX - p.x, p.y - minY, maxY - p.y);
|
|
697
|
+
}
|
|
698
|
+
const dx = p.x < minX ? minX - p.x : p.x > maxX ? p.x - maxX : 0;
|
|
699
|
+
const dy = p.y < minY ? minY - p.y : p.y > maxY ? p.y - maxY : 0;
|
|
700
|
+
if (dx === 0) return dy;
|
|
701
|
+
if (dy === 0) return dx;
|
|
702
|
+
return Math.hypot(dx, dy);
|
|
709
703
|
}
|
|
710
704
|
function intersect1D(r1, r2) {
|
|
711
705
|
const lo = Math.max(r1[0], r2[0]);
|
|
@@ -960,6 +954,13 @@ var searchStripUp = ({
|
|
|
960
954
|
});
|
|
961
955
|
|
|
962
956
|
// lib/utils/expandRectFromSeed.ts
|
|
957
|
+
var quantize = (value, precision = 1e-6) => Math.round(value / precision) * precision;
|
|
958
|
+
var quantizeRect = (rect) => ({
|
|
959
|
+
x: quantize(rect.x),
|
|
960
|
+
y: quantize(rect.y),
|
|
961
|
+
width: quantize(rect.width),
|
|
962
|
+
height: quantize(rect.height)
|
|
963
|
+
});
|
|
963
964
|
function maxExpandRight(params) {
|
|
964
965
|
const { r, bounds, blockers, maxAspect } = params;
|
|
965
966
|
let maxWidth = bounds.x + bounds.width - r.x;
|
|
@@ -1044,17 +1045,10 @@ function maxExpandUp(params) {
|
|
|
1044
1045
|
}
|
|
1045
1046
|
return Math.max(0, e);
|
|
1046
1047
|
}
|
|
1047
|
-
var toRect = (tree) => ({
|
|
1048
|
-
x: tree.minX,
|
|
1049
|
-
y: tree.minY,
|
|
1050
|
-
width: tree.maxX - tree.minX,
|
|
1051
|
-
height: tree.maxY - tree.minY
|
|
1052
|
-
});
|
|
1053
1048
|
var addBlocker = (params) => {
|
|
1054
1049
|
const { rect, seen, blockers } = params;
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
seen.add(key);
|
|
1050
|
+
if (seen.has(rect)) return;
|
|
1051
|
+
seen.add(rect);
|
|
1058
1052
|
blockers.push(rect);
|
|
1059
1053
|
};
|
|
1060
1054
|
var toQueryRect = (params) => {
|
|
@@ -1091,23 +1085,22 @@ function expandRectFromSeed(params) {
|
|
|
1091
1085
|
const blockersIndex = obsticalIndexByLayer[z];
|
|
1092
1086
|
if (blockersIndex) {
|
|
1093
1087
|
for (const entry of blockersIndex.search(query))
|
|
1094
|
-
addBlocker({ rect:
|
|
1088
|
+
addBlocker({ rect: entry, seen, blockers });
|
|
1095
1089
|
}
|
|
1096
1090
|
const placedLayer = placedIndexByLayer[z];
|
|
1097
1091
|
if (placedLayer) {
|
|
1098
1092
|
for (const entry of placedLayer.search(query)) {
|
|
1099
1093
|
const isFullStack = entry.zLayers.length >= totalLayers;
|
|
1100
1094
|
if (!isFullStack) continue;
|
|
1101
|
-
const rect = toRect(entry);
|
|
1102
1095
|
if (isSelfRect({
|
|
1103
|
-
rect,
|
|
1096
|
+
rect: entry,
|
|
1104
1097
|
startX,
|
|
1105
1098
|
startY,
|
|
1106
1099
|
initialW,
|
|
1107
1100
|
initialH
|
|
1108
1101
|
}))
|
|
1109
1102
|
continue;
|
|
1110
|
-
addBlocker({ rect, seen, blockers });
|
|
1103
|
+
addBlocker({ rect: entry, seen, blockers });
|
|
1111
1104
|
}
|
|
1112
1105
|
}
|
|
1113
1106
|
}
|
|
@@ -1173,7 +1166,7 @@ function expandRectFromSeed(params) {
|
|
|
1173
1166
|
if (r.width + EPS4 >= minReq.width && r.height + EPS4 >= minReq.height) {
|
|
1174
1167
|
const area = r.width * r.height;
|
|
1175
1168
|
if (area > bestArea) {
|
|
1176
|
-
best = r;
|
|
1169
|
+
best = quantizeRect(r);
|
|
1177
1170
|
bestArea = area;
|
|
1178
1171
|
}
|
|
1179
1172
|
}
|
|
@@ -1198,9 +1191,9 @@ function isFullyOccupiedAtPoint(params) {
|
|
|
1198
1191
|
};
|
|
1199
1192
|
for (let z = 0; z < params.layerCount; z++) {
|
|
1200
1193
|
const obstacleIdx = params.obstacleIndexByLayer[z];
|
|
1201
|
-
const hasObstacle = !!obstacleIdx && obstacleIdx.
|
|
1194
|
+
const hasObstacle = !!obstacleIdx && obstacleIdx.collides(query);
|
|
1202
1195
|
const placedIdx = params.placedIndexByLayer[z];
|
|
1203
|
-
const hasPlaced = !!placedIdx && placedIdx.
|
|
1196
|
+
const hasPlaced = !!placedIdx && placedIdx.collides(query);
|
|
1204
1197
|
if (!hasObstacle && !hasPlaced) return false;
|
|
1205
1198
|
}
|
|
1206
1199
|
return true;
|
|
@@ -1226,7 +1219,7 @@ function longestFreeSpanAroundZ(params) {
|
|
|
1226
1219
|
maxY: y
|
|
1227
1220
|
};
|
|
1228
1221
|
const obstacleIdx = obstacleIndexByLayer[layer];
|
|
1229
|
-
if (obstacleIdx && obstacleIdx.
|
|
1222
|
+
if (obstacleIdx && obstacleIdx.collides(query)) return false;
|
|
1230
1223
|
const extras = additionalBlockersByLayer?.[layer] ?? [];
|
|
1231
1224
|
return !extras.some((b) => containsPoint(b, { x, y }));
|
|
1232
1225
|
};
|
|
@@ -1247,6 +1240,7 @@ function longestFreeSpanAroundZ(params) {
|
|
|
1247
1240
|
}
|
|
1248
1241
|
|
|
1249
1242
|
// lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts
|
|
1243
|
+
var quantize2 = (value, precision = 1e-6) => Math.round(value / precision) * precision;
|
|
1250
1244
|
function computeCandidates3D(params) {
|
|
1251
1245
|
const {
|
|
1252
1246
|
bounds,
|
|
@@ -1257,6 +1251,10 @@ function computeCandidates3D(params) {
|
|
|
1257
1251
|
hardPlacedByLayer
|
|
1258
1252
|
} = params;
|
|
1259
1253
|
const out = /* @__PURE__ */ new Map();
|
|
1254
|
+
const hardRectsByLayer = Array.from({ length: layerCount }, (_, z) => [
|
|
1255
|
+
...obstacleIndexByLayer[z]?.all() ?? [],
|
|
1256
|
+
...hardPlacedByLayer[z] ?? []
|
|
1257
|
+
]);
|
|
1260
1258
|
for (let x = bounds.x; x < bounds.x + bounds.width; x += gridSize) {
|
|
1261
1259
|
for (let y = bounds.y; y < bounds.y + bounds.height; y += gridSize) {
|
|
1262
1260
|
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) {
|
|
@@ -1288,20 +1286,18 @@ function computeCandidates3D(params) {
|
|
|
1288
1286
|
}
|
|
1289
1287
|
}
|
|
1290
1288
|
const anchorZ = bestSpan.length ? bestSpan[Math.floor(bestSpan.length / 2)] : bestZ;
|
|
1291
|
-
const hardAtZ = [
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
...hardAtZ.length ? hardAtZ.map((b) => distancePointToRectEdges({ x, y }, b)) : [Infinity]
|
|
1298
|
-
);
|
|
1289
|
+
const hardAtZ = hardRectsByLayer[anchorZ] ?? [];
|
|
1290
|
+
let d = distancePointToRectEdges({ x, y }, bounds);
|
|
1291
|
+
for (const blocker of hardAtZ) {
|
|
1292
|
+
d = Math.min(d, distancePointToRectEdges({ x, y }, blocker));
|
|
1293
|
+
}
|
|
1294
|
+
const distance = quantize2(d);
|
|
1299
1295
|
const k = `${x.toFixed(6)}|${y.toFixed(6)}`;
|
|
1300
1296
|
const cand = {
|
|
1301
1297
|
x,
|
|
1302
1298
|
y,
|
|
1303
1299
|
z: anchorZ,
|
|
1304
|
-
distance
|
|
1300
|
+
distance,
|
|
1305
1301
|
zSpanLen: bestSpan.length
|
|
1306
1302
|
};
|
|
1307
1303
|
const prev = out.get(k);
|
|
@@ -1311,18 +1307,34 @@ function computeCandidates3D(params) {
|
|
|
1311
1307
|
}
|
|
1312
1308
|
}
|
|
1313
1309
|
const arr = Array.from(out.values());
|
|
1314
|
-
arr.sort(
|
|
1310
|
+
arr.sort(
|
|
1311
|
+
(a, b) => b.zSpanLen - a.zSpanLen || b.distance - a.distance || a.z - b.z || a.x - b.x || a.y - b.y
|
|
1312
|
+
);
|
|
1315
1313
|
return arr;
|
|
1316
1314
|
}
|
|
1317
1315
|
|
|
1318
1316
|
// lib/solvers/RectDiffSeedingSolver/computeEdgeCandidates3D.ts
|
|
1317
|
+
var quantize3 = (value, precision = 1e-6) => Math.round(value / precision) * precision;
|
|
1318
|
+
var toRect = (rect) => "minX" in rect ? {
|
|
1319
|
+
x: rect.minX,
|
|
1320
|
+
y: rect.minY,
|
|
1321
|
+
width: rect.maxX - rect.minX,
|
|
1322
|
+
height: rect.maxY - rect.minY
|
|
1323
|
+
} : rect;
|
|
1319
1324
|
function computeUncoveredSegments(params) {
|
|
1320
1325
|
const { lineStart, lineEnd, coveringIntervals, minSegmentLength } = params;
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
+
const lineStartQ = quantize3(lineStart);
|
|
1327
|
+
const lineEndQ = quantize3(lineEnd);
|
|
1328
|
+
const normalizedIntervals = coveringIntervals.map((i) => {
|
|
1329
|
+
const s = quantize3(i.start);
|
|
1330
|
+
const e = quantize3(i.end);
|
|
1331
|
+
return { start: Math.min(s, e), end: Math.max(s, e) };
|
|
1332
|
+
}).filter((i) => i.end > i.start + EPS4);
|
|
1333
|
+
if (normalizedIntervals.length === 0) {
|
|
1334
|
+
const center = (lineStartQ + lineEndQ) / 2;
|
|
1335
|
+
return [{ start: lineStartQ, end: lineEndQ, center }];
|
|
1336
|
+
}
|
|
1337
|
+
const sorted = [...normalizedIntervals].sort((a, b) => a.start - b.start);
|
|
1326
1338
|
const merged = [];
|
|
1327
1339
|
let current = { ...sorted[0] };
|
|
1328
1340
|
for (let i = 1; i < sorted.length; i++) {
|
|
@@ -1336,8 +1348,8 @@ function computeUncoveredSegments(params) {
|
|
|
1336
1348
|
}
|
|
1337
1349
|
merged.push(current);
|
|
1338
1350
|
const uncovered = [];
|
|
1339
|
-
if (merged[0].start >
|
|
1340
|
-
const start =
|
|
1351
|
+
if (merged[0].start > lineStartQ + EPS4) {
|
|
1352
|
+
const start = lineStartQ;
|
|
1341
1353
|
const end = merged[0].start;
|
|
1342
1354
|
if (end - start >= minSegmentLength) {
|
|
1343
1355
|
uncovered.push({ start, end, center: (start + end) / 2 });
|
|
@@ -1350,9 +1362,9 @@ function computeUncoveredSegments(params) {
|
|
|
1350
1362
|
uncovered.push({ start, end, center: (start + end) / 2 });
|
|
1351
1363
|
}
|
|
1352
1364
|
}
|
|
1353
|
-
if (merged[merged.length - 1].end <
|
|
1365
|
+
if (merged[merged.length - 1].end < lineEndQ - EPS4) {
|
|
1354
1366
|
const start = merged[merged.length - 1].end;
|
|
1355
|
-
const end =
|
|
1367
|
+
const end = lineEndQ;
|
|
1356
1368
|
if (end - start >= minSegmentLength) {
|
|
1357
1369
|
uncovered.push({ start, end, center: (start + end) / 2 });
|
|
1358
1370
|
}
|
|
@@ -1371,6 +1383,13 @@ function computeEdgeCandidates3D(params) {
|
|
|
1371
1383
|
const out = [];
|
|
1372
1384
|
const \u03B4 = Math.max(minSize * 0.15, EPS4 * 3);
|
|
1373
1385
|
const dedup = /* @__PURE__ */ new Set();
|
|
1386
|
+
const hardRectsByLayer = Array.from(
|
|
1387
|
+
{ length: layerCount },
|
|
1388
|
+
(_, z) => [
|
|
1389
|
+
...obstacleIndexByLayer[z]?.all() ?? [],
|
|
1390
|
+
...hardPlacedByLayer[z] ?? []
|
|
1391
|
+
].map(toRect)
|
|
1392
|
+
);
|
|
1374
1393
|
const key = (p) => `${p.z}|${p.x.toFixed(6)}|${p.y.toFixed(6)}`;
|
|
1375
1394
|
function fullyOcc(p) {
|
|
1376
1395
|
return isFullyOccupiedAtPoint({
|
|
@@ -1381,18 +1400,20 @@ function computeEdgeCandidates3D(params) {
|
|
|
1381
1400
|
});
|
|
1382
1401
|
}
|
|
1383
1402
|
function pushIfFree(p) {
|
|
1384
|
-
const
|
|
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
|
-
const hard = [
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
...hard.length ? hard.map((b) => distancePointToRectEdges({ x, y }, b)) : [Infinity]
|
|
1395
|
-
);
|
|
1411
|
+
const hard = hardRectsByLayer[z] ?? [];
|
|
1412
|
+
let d = distancePointToRectEdges({ x, y }, bounds);
|
|
1413
|
+
for (const blocker of hard) {
|
|
1414
|
+
d = Math.min(d, distancePointToRectEdges({ x, y }, blocker));
|
|
1415
|
+
}
|
|
1416
|
+
const distance = quantize3(d);
|
|
1396
1417
|
const k = key({ x, y, z });
|
|
1397
1418
|
if (dedup.has(k)) return;
|
|
1398
1419
|
dedup.add(k);
|
|
@@ -1406,13 +1427,22 @@ function computeEdgeCandidates3D(params) {
|
|
|
1406
1427
|
obstacleIndexByLayer,
|
|
1407
1428
|
additionalBlockersByLayer: hardPlacedByLayer
|
|
1408
1429
|
});
|
|
1409
|
-
out.push({
|
|
1430
|
+
out.push({
|
|
1431
|
+
x,
|
|
1432
|
+
y,
|
|
1433
|
+
z,
|
|
1434
|
+
distance,
|
|
1435
|
+
zSpanLen: span.length,
|
|
1436
|
+
isEdgeSeed: true
|
|
1437
|
+
});
|
|
1410
1438
|
}
|
|
1411
1439
|
for (let z = 0; z < layerCount; z++) {
|
|
1412
|
-
const blockers = [
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1440
|
+
const blockers = (hardRectsByLayer[z] ?? []).map((b) => ({
|
|
1441
|
+
x: quantize3(b.x),
|
|
1442
|
+
y: quantize3(b.y),
|
|
1443
|
+
width: quantize3(b.width),
|
|
1444
|
+
height: quantize3(b.height)
|
|
1445
|
+
}));
|
|
1416
1446
|
const corners = [
|
|
1417
1447
|
{ x: bounds.x + \u03B4, y: bounds.y + \u03B4 },
|
|
1418
1448
|
// top-left
|
|
@@ -1585,7 +1615,9 @@ function computeEdgeCandidates3D(params) {
|
|
|
1585
1615
|
}
|
|
1586
1616
|
}
|
|
1587
1617
|
}
|
|
1588
|
-
out.sort(
|
|
1618
|
+
out.sort(
|
|
1619
|
+
(a, b) => b.zSpanLen - a.zSpanLen || b.distance - a.distance || a.z - b.z || a.x - b.x || a.y - b.y
|
|
1620
|
+
);
|
|
1589
1621
|
return out;
|
|
1590
1622
|
}
|
|
1591
1623
|
|
|
@@ -1703,6 +1735,7 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
|
|
|
1703
1735
|
candidates;
|
|
1704
1736
|
placed;
|
|
1705
1737
|
placedIndexByLayer;
|
|
1738
|
+
hardPlacedByLayer;
|
|
1706
1739
|
expansionIndex;
|
|
1707
1740
|
edgeAnalysisDone;
|
|
1708
1741
|
totalSeedsThisGrid;
|
|
@@ -1758,6 +1791,7 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
|
|
|
1758
1791
|
{ length: layerCount },
|
|
1759
1792
|
() => new RBush3()
|
|
1760
1793
|
);
|
|
1794
|
+
this.hardPlacedByLayer = Array.from({ length: layerCount }, () => []);
|
|
1761
1795
|
this.expansionIndex = 0;
|
|
1762
1796
|
this.edgeAnalysisDone = false;
|
|
1763
1797
|
this.totalSeedsThisGrid = 0;
|
|
@@ -1786,25 +1820,22 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
|
|
|
1786
1820
|
maxMultiLayerSpan
|
|
1787
1821
|
} = this.options;
|
|
1788
1822
|
const grid = gridSizes[this.gridIndex];
|
|
1789
|
-
const hardPlacedByLayer = allLayerNode({
|
|
1790
|
-
layerCount: this.layerCount,
|
|
1791
|
-
placed: this.placed
|
|
1792
|
-
});
|
|
1793
1823
|
if (this.candidates.length === 0 && this.consumedSeedsThisGrid === 0) {
|
|
1794
1824
|
this.candidates = computeCandidates3D({
|
|
1795
1825
|
bounds: this.bounds,
|
|
1796
1826
|
gridSize: grid,
|
|
1797
1827
|
layerCount: this.layerCount,
|
|
1798
|
-
hardPlacedByLayer,
|
|
1828
|
+
hardPlacedByLayer: this.hardPlacedByLayer,
|
|
1799
1829
|
obstacleIndexByLayer: this.input.obstacleIndexByLayer,
|
|
1800
1830
|
placedIndexByLayer: this.placedIndexByLayer
|
|
1801
1831
|
});
|
|
1802
1832
|
this.totalSeedsThisGrid = this.candidates.length;
|
|
1803
1833
|
this.consumedSeedsThisGrid = 0;
|
|
1804
1834
|
}
|
|
1805
|
-
if (this.candidates.length
|
|
1835
|
+
if (this.consumedSeedsThisGrid >= this.candidates.length) {
|
|
1806
1836
|
if (this.gridIndex + 1 < gridSizes.length) {
|
|
1807
1837
|
this.gridIndex += 1;
|
|
1838
|
+
this.candidates = [];
|
|
1808
1839
|
this.totalSeedsThisGrid = 0;
|
|
1809
1840
|
this.consumedSeedsThisGrid = 0;
|
|
1810
1841
|
return;
|
|
@@ -1817,20 +1848,28 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
|
|
|
1817
1848
|
layerCount: this.layerCount,
|
|
1818
1849
|
obstacleIndexByLayer: this.input.obstacleIndexByLayer,
|
|
1819
1850
|
placedIndexByLayer: this.placedIndexByLayer,
|
|
1820
|
-
hardPlacedByLayer
|
|
1851
|
+
hardPlacedByLayer: this.hardPlacedByLayer
|
|
1821
1852
|
});
|
|
1822
1853
|
this.edgeAnalysisDone = true;
|
|
1823
1854
|
this.totalSeedsThisGrid = this.candidates.length;
|
|
1824
1855
|
this.consumedSeedsThisGrid = 0;
|
|
1825
1856
|
return;
|
|
1826
1857
|
}
|
|
1858
|
+
this.candidates = [];
|
|
1827
1859
|
this.solved = true;
|
|
1828
1860
|
this.expansionIndex = 0;
|
|
1829
1861
|
return;
|
|
1830
1862
|
}
|
|
1831
1863
|
}
|
|
1832
|
-
const cand = this.candidates.
|
|
1833
|
-
|
|
1864
|
+
const cand = this.candidates[this.consumedSeedsThisGrid++];
|
|
1865
|
+
if (isFullyOccupiedAtPoint({
|
|
1866
|
+
layerCount: this.layerCount,
|
|
1867
|
+
obstacleIndexByLayer: this.input.obstacleIndexByLayer,
|
|
1868
|
+
placedIndexByLayer: this.placedIndexByLayer,
|
|
1869
|
+
point: { x: cand.x, y: cand.y }
|
|
1870
|
+
})) {
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1834
1873
|
const span = longestFreeSpanAroundZ({
|
|
1835
1874
|
x: cand.x,
|
|
1836
1875
|
y: cand.y,
|
|
@@ -1839,7 +1878,7 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
|
|
|
1839
1878
|
minSpan: minMulti.minLayers,
|
|
1840
1879
|
maxSpan: maxMultiLayerSpan,
|
|
1841
1880
|
obstacleIndexByLayer: this.input.obstacleIndexByLayer,
|
|
1842
|
-
additionalBlockersByLayer: hardPlacedByLayer
|
|
1881
|
+
additionalBlockersByLayer: this.hardPlacedByLayer
|
|
1843
1882
|
});
|
|
1844
1883
|
const attempts = [];
|
|
1845
1884
|
if (span.length >= minMulti.minLayers) {
|
|
@@ -1886,14 +1925,10 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
|
|
|
1886
1925
|
},
|
|
1887
1926
|
newIndex
|
|
1888
1927
|
);
|
|
1889
|
-
this.
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
placedIndexByLayer: this.placedIndexByLayer,
|
|
1894
|
-
point: { x: c.x, y: c.y }
|
|
1895
|
-
})
|
|
1896
|
-
);
|
|
1928
|
+
this.hardPlacedByLayer = allLayerNode({
|
|
1929
|
+
layerCount: this.layerCount,
|
|
1930
|
+
placed: this.placed
|
|
1931
|
+
});
|
|
1897
1932
|
return;
|
|
1898
1933
|
}
|
|
1899
1934
|
}
|
|
@@ -1976,29 +2011,6 @@ var RectDiffSeedingSolver = class extends BaseSolver3 {
|
|
|
1976
2011
|
});
|
|
1977
2012
|
}
|
|
1978
2013
|
}
|
|
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
|
-
}
|
|
2002
2014
|
if (this.boardVoidRects) {
|
|
2003
2015
|
let outlineBBox = null;
|
|
2004
2016
|
if (srj.outline && srj.outline.length > 0) {
|
|
@@ -2524,6 +2536,42 @@ z:${layerLabel}`
|
|
|
2524
2536
|
};
|
|
2525
2537
|
}
|
|
2526
2538
|
|
|
2539
|
+
// lib/buildFinalRectDiffVisualization.ts
|
|
2540
|
+
import { mergeGraphics } from "graphics-debug";
|
|
2541
|
+
|
|
2542
|
+
// lib/utils/buildOutlineGraphics.ts
|
|
2543
|
+
var buildOutlineGraphics = ({
|
|
2544
|
+
srj
|
|
2545
|
+
}) => {
|
|
2546
|
+
const hasOutline = srj.outline && srj.outline.length > 1;
|
|
2547
|
+
const lines = hasOutline ? [
|
|
2548
|
+
{
|
|
2549
|
+
points: [...srj.outline, srj.outline[0]],
|
|
2550
|
+
strokeColor: "#111827",
|
|
2551
|
+
strokeWidth: 0.1,
|
|
2552
|
+
label: "outline"
|
|
2553
|
+
}
|
|
2554
|
+
] : [
|
|
2555
|
+
{
|
|
2556
|
+
points: [
|
|
2557
|
+
{ x: srj.bounds.minX, y: srj.bounds.minY },
|
|
2558
|
+
{ x: srj.bounds.maxX, y: srj.bounds.minY },
|
|
2559
|
+
{ x: srj.bounds.maxX, y: srj.bounds.maxY },
|
|
2560
|
+
{ x: srj.bounds.minX, y: srj.bounds.maxY },
|
|
2561
|
+
{ x: srj.bounds.minX, y: srj.bounds.minY }
|
|
2562
|
+
],
|
|
2563
|
+
strokeColor: "#111827",
|
|
2564
|
+
strokeWidth: 0.1,
|
|
2565
|
+
label: "bounds"
|
|
2566
|
+
}
|
|
2567
|
+
];
|
|
2568
|
+
return {
|
|
2569
|
+
title: "SimpleRoute Outline",
|
|
2570
|
+
coordinateSystem: "cartesian",
|
|
2571
|
+
lines
|
|
2572
|
+
};
|
|
2573
|
+
};
|
|
2574
|
+
|
|
2527
2575
|
// lib/utils/renderObstacleClearance.ts
|
|
2528
2576
|
var buildObstacleClearanceGraphics = (params) => {
|
|
2529
2577
|
const { srj, clearance } = params;
|
|
@@ -2565,8 +2613,40 @@ z:${(obstacle.zLayers ?? []).join(",") || "all"}`
|
|
|
2565
2613
|
};
|
|
2566
2614
|
};
|
|
2567
2615
|
|
|
2616
|
+
// lib/buildFinalRectDiffVisualization.ts
|
|
2617
|
+
var buildFinalRectDiffVisualization = ({
|
|
2618
|
+
srj,
|
|
2619
|
+
meshNodes,
|
|
2620
|
+
obstacleClearance
|
|
2621
|
+
}) => {
|
|
2622
|
+
const outline = buildOutlineGraphics({ srj });
|
|
2623
|
+
const clearance = buildObstacleClearanceGraphics({
|
|
2624
|
+
srj,
|
|
2625
|
+
clearance: obstacleClearance
|
|
2626
|
+
});
|
|
2627
|
+
const rects = meshNodes.map((node) => ({
|
|
2628
|
+
center: node.center,
|
|
2629
|
+
width: node.width,
|
|
2630
|
+
height: node.height,
|
|
2631
|
+
stroke: getColorForZLayer(node.availableZ).stroke,
|
|
2632
|
+
fill: node._containsObstacle ? "#fca5a5" : getColorForZLayer(node.availableZ).fill,
|
|
2633
|
+
layer: `z${node.availableZ.join(",")}`,
|
|
2634
|
+
label: `node ${node.capacityMeshNodeId}
|
|
2635
|
+
z:${node.availableZ.join(",")}`
|
|
2636
|
+
}));
|
|
2637
|
+
const nodesGraphic = {
|
|
2638
|
+
title: "RectDiffPipeline - Final",
|
|
2639
|
+
coordinateSystem: "cartesian",
|
|
2640
|
+
rects,
|
|
2641
|
+
lines: [],
|
|
2642
|
+
points: [],
|
|
2643
|
+
texts: []
|
|
2644
|
+
};
|
|
2645
|
+
return mergeGraphics(mergeGraphics(nodesGraphic, outline), clearance);
|
|
2646
|
+
};
|
|
2647
|
+
|
|
2568
2648
|
// lib/RectDiffPipeline.ts
|
|
2569
|
-
import { mergeGraphics } from "graphics-debug";
|
|
2649
|
+
import { mergeGraphics as mergeGraphics2 } from "graphics-debug";
|
|
2570
2650
|
var RectDiffPipeline = class extends BasePipelineSolver3 {
|
|
2571
2651
|
rectDiffGridSolverPipeline;
|
|
2572
2652
|
gapFillSolver;
|
|
@@ -2665,43 +2745,14 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
|
|
|
2665
2745
|
].join("\n")
|
|
2666
2746
|
}))
|
|
2667
2747
|
};
|
|
2668
|
-
return
|
|
2748
|
+
return mergeGraphics2(mergeGraphics2(base, clearance), nodeRects);
|
|
2669
2749
|
}
|
|
2670
2750
|
finalVisualize() {
|
|
2671
|
-
|
|
2672
|
-
this.inputProblem.simpleRouteJson,
|
|
2673
|
-
"RectDiffPipeline - Final"
|
|
2674
|
-
);
|
|
2675
|
-
const clearance = buildObstacleClearanceGraphics({
|
|
2751
|
+
return buildFinalRectDiffVisualization({
|
|
2676
2752
|
srj: this.inputProblem.simpleRouteJson,
|
|
2677
|
-
|
|
2753
|
+
meshNodes: this.getOutput().meshNodes,
|
|
2754
|
+
obstacleClearance: this.inputProblem.obstacleClearance
|
|
2678
2755
|
});
|
|
2679
|
-
const { meshNodes: outputNodes } = this.getOutput();
|
|
2680
|
-
const initialNodeIds = new Set(
|
|
2681
|
-
(this.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? []).map(
|
|
2682
|
-
(n) => n.capacityMeshNodeId
|
|
2683
|
-
)
|
|
2684
|
-
);
|
|
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);
|
|
2705
2756
|
}
|
|
2706
2757
|
};
|
|
2707
2758
|
export {
|
package/lib/RectDiffPipeline.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { GraphicsObject } from "graphics-debug"
|
|
|
10
10
|
import { GapFillSolverPipeline } from "./solvers/GapFillSolver/GapFillSolverPipeline"
|
|
11
11
|
import { RectDiffGridSolverPipeline } from "./solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline"
|
|
12
12
|
import { createBaseVisualization } from "./rectdiff-visualization"
|
|
13
|
+
import { buildFinalRectDiffVisualization } from "./buildFinalRectDiffVisualization"
|
|
13
14
|
import { computeInverseRects } from "./solvers/RectDiffSeedingSolver/computeInverseRects"
|
|
14
15
|
import { buildZIndexMap } from "./solvers/RectDiffSeedingSolver/layers"
|
|
15
16
|
import { buildObstacleClearanceGraphics } from "./utils/renderObstacleClearance"
|
|
@@ -144,44 +145,10 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
override finalVisualize(): GraphicsObject {
|
|
147
|
-
|
|
148
|
-
this.inputProblem.simpleRouteJson,
|
|
149
|
-
"RectDiffPipeline - Final",
|
|
150
|
-
)
|
|
151
|
-
const clearance = buildObstacleClearanceGraphics({
|
|
148
|
+
return buildFinalRectDiffVisualization({
|
|
152
149
|
srj: this.inputProblem.simpleRouteJson,
|
|
153
|
-
|
|
150
|
+
meshNodes: this.getOutput().meshNodes,
|
|
151
|
+
obstacleClearance: this.inputProblem.obstacleClearance,
|
|
154
152
|
})
|
|
155
|
-
|
|
156
|
-
const { meshNodes: outputNodes } = this.getOutput()
|
|
157
|
-
const initialNodeIds = new Set(
|
|
158
|
-
(this.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? []).map(
|
|
159
|
-
(n) => n.capacityMeshNodeId,
|
|
160
|
-
),
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
const nodeRects: GraphicsObject = {
|
|
164
|
-
title: "Final Nodes",
|
|
165
|
-
coordinateSystem: "cartesian",
|
|
166
|
-
rects: outputNodes.map((node) => {
|
|
167
|
-
const isExpanded = !initialNodeIds.has(node.capacityMeshNodeId)
|
|
168
|
-
return {
|
|
169
|
-
center: node.center,
|
|
170
|
-
width: node.width,
|
|
171
|
-
height: node.height,
|
|
172
|
-
stroke: isExpanded ? "rgba(0, 128, 0, 0.8)" : "rgba(0, 0, 0, 0.3)",
|
|
173
|
-
fill: isExpanded
|
|
174
|
-
? "rgba(0, 200, 0, 0.3)"
|
|
175
|
-
: "rgba(100, 100, 100, 0.1)",
|
|
176
|
-
layer: `z${node.availableZ.join(",")}`,
|
|
177
|
-
label: [
|
|
178
|
-
`${isExpanded ? "[expanded] " : ""}node ${node.capacityMeshNodeId}`,
|
|
179
|
-
`z:${node.availableZ.join(",")}`,
|
|
180
|
-
].join("\n"),
|
|
181
|
-
}
|
|
182
|
-
}),
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return mergeGraphics(mergeGraphics(base, clearance), nodeRects)
|
|
186
153
|
}
|
|
187
154
|
}
|