@tscircuit/rectdiff 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +97 -12
- package/dist/index.js +714 -81
- package/lib/RectDiffPipeline.ts +79 -13
- package/lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts +284 -0
- package/lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts +213 -0
- package/lib/solvers/GapFillSolver/GapFillSolverPipeline.ts +129 -0
- package/lib/solvers/GapFillSolver/edge-constants.ts +48 -0
- package/lib/solvers/GapFillSolver/getBoundsFromCorners.ts +10 -0
- package/lib/solvers/GapFillSolver/projectToUncoveredSegments.ts +92 -0
- package/lib/solvers/GapFillSolver/visuallyOffsetLine.ts +32 -0
- package/lib/solvers/RectDiffSolver.ts +1 -33
- package/package.json +9 -6
- package/tests/board-outline.test.ts +1 -1
- package/tsconfig.json +4 -0
- package/vite.config.ts +6 -0
- package/lib/solvers/rectdiff/gapfill/detection/deduplicateGaps.ts +0 -28
- package/lib/solvers/rectdiff/gapfill/detection/findAllGaps.ts +0 -83
- package/lib/solvers/rectdiff/gapfill/detection/findGapsOnLayer.ts +0 -100
- package/lib/solvers/rectdiff/gapfill/detection/mergeUncoveredCells.ts +0 -75
- package/lib/solvers/rectdiff/gapfill/detection.ts +0 -3
- package/lib/solvers/rectdiff/gapfill/engine/addPlacement.ts +0 -27
- package/lib/solvers/rectdiff/gapfill/engine/calculateCoverage.ts +0 -44
- package/lib/solvers/rectdiff/gapfill/engine/findUncoveredPoints.ts +0 -43
- package/lib/solvers/rectdiff/gapfill/engine/getGapFillProgress.ts +0 -42
- package/lib/solvers/rectdiff/gapfill/engine/initGapFillState.ts +0 -57
- package/lib/solvers/rectdiff/gapfill/engine/stepGapFill.ts +0 -128
- package/lib/solvers/rectdiff/gapfill/engine/tryExpandGap.ts +0 -78
- package/lib/solvers/rectdiff/gapfill/engine.ts +0 -7
- package/lib/solvers/rectdiff/gapfill/types.ts +0 -60
- package/lib/solvers/rectdiff/subsolvers/GapFillSubSolver.ts +0 -253
package/dist/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// lib/RectDiffPipeline.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BasePipelineSolver as BasePipelineSolver3,
|
|
4
|
+
definePipelineStep as definePipelineStep2
|
|
5
|
+
} from "@tscircuit/solver-utils";
|
|
3
6
|
|
|
4
7
|
// lib/solvers/RectDiffSolver.ts
|
|
5
8
|
import { BaseSolver } from "@tscircuit/solver-utils";
|
|
@@ -1117,50 +1120,6 @@ function rectsToMeshNodes(rects) {
|
|
|
1117
1120
|
return out;
|
|
1118
1121
|
}
|
|
1119
1122
|
|
|
1120
|
-
// lib/solvers/rectdiff/gapfill/engine/calculateCoverage.ts
|
|
1121
|
-
function calculateCoverage({ sampleResolution = 0.1 }, ctx) {
|
|
1122
|
-
const { bounds, layerCount, obstaclesByLayer, placedByLayer } = ctx;
|
|
1123
|
-
let totalPoints = 0;
|
|
1124
|
-
let coveredPoints = 0;
|
|
1125
|
-
for (let z = 0; z < layerCount; z++) {
|
|
1126
|
-
const obstacles = obstaclesByLayer[z] ?? [];
|
|
1127
|
-
const placed = placedByLayer[z] ?? [];
|
|
1128
|
-
const allRects = [...obstacles, ...placed];
|
|
1129
|
-
for (let x = bounds.x; x <= bounds.x + bounds.width; x += sampleResolution) {
|
|
1130
|
-
for (let y = bounds.y; y <= bounds.y + bounds.height; y += sampleResolution) {
|
|
1131
|
-
totalPoints++;
|
|
1132
|
-
const isCovered = allRects.some(
|
|
1133
|
-
(r) => x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height
|
|
1134
|
-
);
|
|
1135
|
-
if (isCovered) coveredPoints++;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
return totalPoints > 0 ? coveredPoints / totalPoints : 1;
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// lib/solvers/rectdiff/gapfill/engine/findUncoveredPoints.ts
|
|
1143
|
-
function findUncoveredPoints({ sampleResolution = 0.05 }, ctx) {
|
|
1144
|
-
const { bounds, layerCount, obstaclesByLayer, placedByLayer } = ctx;
|
|
1145
|
-
const uncovered = [];
|
|
1146
|
-
for (let z = 0; z < layerCount; z++) {
|
|
1147
|
-
const obstacles = obstaclesByLayer[z] ?? [];
|
|
1148
|
-
const placed = placedByLayer[z] ?? [];
|
|
1149
|
-
const allRects = [...obstacles, ...placed];
|
|
1150
|
-
for (let x = bounds.x; x <= bounds.x + bounds.width; x += sampleResolution) {
|
|
1151
|
-
for (let y = bounds.y; y <= bounds.y + bounds.height; y += sampleResolution) {
|
|
1152
|
-
const isCovered = allRects.some(
|
|
1153
|
-
(r) => x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height
|
|
1154
|
-
);
|
|
1155
|
-
if (!isCovered) {
|
|
1156
|
-
uncovered.push({ x, y, z });
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
return uncovered;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
1123
|
// lib/solvers/RectDiffSolver.ts
|
|
1165
1124
|
var RectDiffSolver = class extends BaseSolver {
|
|
1166
1125
|
srj;
|
|
@@ -1209,30 +1168,6 @@ var RectDiffSolver = class extends BaseSolver {
|
|
|
1209
1168
|
getOutput() {
|
|
1210
1169
|
return { meshNodes: this._meshNodes };
|
|
1211
1170
|
}
|
|
1212
|
-
/** Get coverage percentage (0-1). */
|
|
1213
|
-
getCoverage(sampleResolution = 0.05) {
|
|
1214
|
-
return calculateCoverage(
|
|
1215
|
-
{ sampleResolution },
|
|
1216
|
-
{
|
|
1217
|
-
bounds: this.state.bounds,
|
|
1218
|
-
layerCount: this.state.layerCount,
|
|
1219
|
-
obstaclesByLayer: this.state.obstaclesByLayer,
|
|
1220
|
-
placedByLayer: this.state.placedByLayer
|
|
1221
|
-
}
|
|
1222
|
-
);
|
|
1223
|
-
}
|
|
1224
|
-
/** Find uncovered points for debugging gaps. */
|
|
1225
|
-
getUncoveredPoints(sampleResolution = 0.05) {
|
|
1226
|
-
return findUncoveredPoints(
|
|
1227
|
-
{ sampleResolution },
|
|
1228
|
-
{
|
|
1229
|
-
bounds: this.state.bounds,
|
|
1230
|
-
layerCount: this.state.layerCount,
|
|
1231
|
-
obstaclesByLayer: this.state.obstaclesByLayer,
|
|
1232
|
-
placedByLayer: this.state.placedByLayer
|
|
1233
|
-
}
|
|
1234
|
-
);
|
|
1235
|
-
}
|
|
1236
1171
|
/** Get color based on z layer for visualization. */
|
|
1237
1172
|
getColorForZLayer(zLayers) {
|
|
1238
1173
|
const minZ = Math.min(...zLayers);
|
|
@@ -1342,6 +1277,7 @@ var RectDiffSolver = class extends BaseSolver {
|
|
|
1342
1277
|
height: p.rect.height,
|
|
1343
1278
|
fill: colors.fill,
|
|
1344
1279
|
stroke: colors.stroke,
|
|
1280
|
+
layer: `z${p.zLayers.join(",")}`,
|
|
1345
1281
|
label: `free
|
|
1346
1282
|
z:${p.zLayers.join(",")}`
|
|
1347
1283
|
});
|
|
@@ -1410,40 +1346,737 @@ function createBaseVisualization(srj, title = "RectDiff") {
|
|
|
1410
1346
|
};
|
|
1411
1347
|
}
|
|
1412
1348
|
|
|
1349
|
+
// lib/solvers/GapFillSolver/GapFillSolverPipeline.ts
|
|
1350
|
+
import {
|
|
1351
|
+
BasePipelineSolver as BasePipelineSolver2,
|
|
1352
|
+
definePipelineStep
|
|
1353
|
+
} from "@tscircuit/solver-utils";
|
|
1354
|
+
|
|
1355
|
+
// lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts
|
|
1356
|
+
import { BaseSolver as BaseSolver2 } from "@tscircuit/solver-utils";
|
|
1357
|
+
import Flatbush from "flatbush";
|
|
1358
|
+
|
|
1359
|
+
// lib/solvers/GapFillSolver/projectToUncoveredSegments.ts
|
|
1360
|
+
var EPS2 = 1e-4;
|
|
1361
|
+
function projectToUncoveredSegments(primaryEdge, overlappingEdges) {
|
|
1362
|
+
const isHorizontal = Math.abs(primaryEdge.start.y - primaryEdge.end.y) < EPS2;
|
|
1363
|
+
const isVertical = Math.abs(primaryEdge.start.x - primaryEdge.end.x) < EPS2;
|
|
1364
|
+
if (!isHorizontal && !isVertical) return [];
|
|
1365
|
+
const axis = isHorizontal ? "x" : "y";
|
|
1366
|
+
const perp = isHorizontal ? "y" : "x";
|
|
1367
|
+
const lineCoord = primaryEdge.start[perp];
|
|
1368
|
+
const p0 = primaryEdge.start[axis];
|
|
1369
|
+
const p1 = primaryEdge.end[axis];
|
|
1370
|
+
const pMin = Math.min(p0, p1);
|
|
1371
|
+
const pMax = Math.max(p0, p1);
|
|
1372
|
+
const clamp2 = (v) => Math.max(pMin, Math.min(pMax, v));
|
|
1373
|
+
const intervals = [];
|
|
1374
|
+
for (const e of overlappingEdges) {
|
|
1375
|
+
if (e === primaryEdge) continue;
|
|
1376
|
+
const eIsHorizontal = Math.abs(e.start.y - e.end.y) < EPS2;
|
|
1377
|
+
const eIsVertical = Math.abs(e.start.x - e.end.x) < EPS2;
|
|
1378
|
+
if (axis === "x" && !eIsHorizontal) continue;
|
|
1379
|
+
if (axis === "y" && !eIsVertical) continue;
|
|
1380
|
+
if (Math.abs(e.start[perp] - lineCoord) > EPS2) continue;
|
|
1381
|
+
const eMin = Math.min(e.start[axis], e.end[axis]);
|
|
1382
|
+
const eMax = Math.max(e.start[axis], e.end[axis]);
|
|
1383
|
+
const s = clamp2(eMin);
|
|
1384
|
+
const t = clamp2(eMax);
|
|
1385
|
+
if (t - s > EPS2) intervals.push({ s, e: t });
|
|
1386
|
+
}
|
|
1387
|
+
if (intervals.length === 0) {
|
|
1388
|
+
return [
|
|
1389
|
+
{
|
|
1390
|
+
...primaryEdge,
|
|
1391
|
+
start: { ...primaryEdge.start },
|
|
1392
|
+
end: { ...primaryEdge.end }
|
|
1393
|
+
}
|
|
1394
|
+
];
|
|
1395
|
+
}
|
|
1396
|
+
intervals.sort((a, b) => a.s - b.s);
|
|
1397
|
+
const merged = [];
|
|
1398
|
+
for (const it of intervals) {
|
|
1399
|
+
const last = merged[merged.length - 1];
|
|
1400
|
+
if (!last || it.s > last.e + EPS2) merged.push({ ...it });
|
|
1401
|
+
else last.e = Math.max(last.e, it.e);
|
|
1402
|
+
}
|
|
1403
|
+
const uncovered = [];
|
|
1404
|
+
let cursor = pMin;
|
|
1405
|
+
for (const m of merged) {
|
|
1406
|
+
if (m.s > cursor + EPS2) uncovered.push({ s: cursor, e: m.s });
|
|
1407
|
+
cursor = Math.max(cursor, m.e);
|
|
1408
|
+
if (cursor >= pMax - EPS2) break;
|
|
1409
|
+
}
|
|
1410
|
+
if (pMax > cursor + EPS2) uncovered.push({ s: cursor, e: pMax });
|
|
1411
|
+
if (uncovered.length === 0) return [];
|
|
1412
|
+
return uncovered.filter((u) => u.e - u.s > EPS2).map((u) => {
|
|
1413
|
+
const start = axis === "x" ? { x: u.s, y: lineCoord } : { x: lineCoord, y: u.s };
|
|
1414
|
+
const end = axis === "x" ? { x: u.e, y: lineCoord } : { x: lineCoord, y: u.e };
|
|
1415
|
+
return {
|
|
1416
|
+
parent: primaryEdge.parent,
|
|
1417
|
+
facingDirection: primaryEdge.facingDirection,
|
|
1418
|
+
start,
|
|
1419
|
+
end,
|
|
1420
|
+
z: primaryEdge.z
|
|
1421
|
+
};
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// lib/solvers/GapFillSolver/edge-constants.ts
|
|
1426
|
+
var EDGES = [
|
|
1427
|
+
{
|
|
1428
|
+
facingDirection: "x-",
|
|
1429
|
+
dx: -1,
|
|
1430
|
+
dy: 0,
|
|
1431
|
+
startX: -0.5,
|
|
1432
|
+
startY: 0.5,
|
|
1433
|
+
endX: -0.5,
|
|
1434
|
+
endY: -0.5
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
facingDirection: "x+",
|
|
1438
|
+
dx: 1,
|
|
1439
|
+
dy: 0,
|
|
1440
|
+
startX: 0.5,
|
|
1441
|
+
startY: 0.5,
|
|
1442
|
+
endX: 0.5,
|
|
1443
|
+
endY: -0.5
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
facingDirection: "y-",
|
|
1447
|
+
dx: 0,
|
|
1448
|
+
dy: -1,
|
|
1449
|
+
startX: -0.5,
|
|
1450
|
+
startY: -0.5,
|
|
1451
|
+
endX: 0.5,
|
|
1452
|
+
endY: -0.5
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
facingDirection: "y+",
|
|
1456
|
+
dx: 0,
|
|
1457
|
+
dy: 1,
|
|
1458
|
+
startX: 0.5,
|
|
1459
|
+
startY: 0.5,
|
|
1460
|
+
endX: -0.5,
|
|
1461
|
+
endY: 0.5
|
|
1462
|
+
}
|
|
1463
|
+
];
|
|
1464
|
+
var EDGE_MAP = {
|
|
1465
|
+
"x-": EDGES.find((e) => e.facingDirection === "x-"),
|
|
1466
|
+
"x+": EDGES.find((e) => e.facingDirection === "x+"),
|
|
1467
|
+
"y-": EDGES.find((e) => e.facingDirection === "y-"),
|
|
1468
|
+
"y+": EDGES.find((e) => e.facingDirection === "y+")
|
|
1469
|
+
};
|
|
1470
|
+
|
|
1471
|
+
// lib/solvers/GapFillSolver/visuallyOffsetLine.ts
|
|
1472
|
+
var OFFSET_DIR_MAP = {
|
|
1473
|
+
"x-": {
|
|
1474
|
+
x: -1,
|
|
1475
|
+
y: 0
|
|
1476
|
+
},
|
|
1477
|
+
"x+": {
|
|
1478
|
+
x: 1,
|
|
1479
|
+
y: 0
|
|
1480
|
+
},
|
|
1481
|
+
"y-": {
|
|
1482
|
+
x: 0,
|
|
1483
|
+
y: -1
|
|
1484
|
+
},
|
|
1485
|
+
"y+": {
|
|
1486
|
+
x: 0,
|
|
1487
|
+
y: 1
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
var visuallyOffsetLine = (line, dir, amt) => {
|
|
1491
|
+
const offset = OFFSET_DIR_MAP[dir];
|
|
1492
|
+
return line.map((p) => ({
|
|
1493
|
+
x: p.x + offset.x * amt,
|
|
1494
|
+
y: p.y + offset.y * amt
|
|
1495
|
+
}));
|
|
1496
|
+
};
|
|
1497
|
+
|
|
1498
|
+
// lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts
|
|
1499
|
+
import "@tscircuit/math-utils";
|
|
1500
|
+
var EPS3 = 1e-4;
|
|
1501
|
+
var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver2 {
|
|
1502
|
+
constructor(input) {
|
|
1503
|
+
super();
|
|
1504
|
+
this.input = input;
|
|
1505
|
+
for (const node of this.input.meshNodes) {
|
|
1506
|
+
for (const edge of EDGES) {
|
|
1507
|
+
let start = {
|
|
1508
|
+
x: node.center.x + node.width * edge.startX,
|
|
1509
|
+
y: node.center.y + node.height * edge.startY
|
|
1510
|
+
};
|
|
1511
|
+
let end = {
|
|
1512
|
+
x: node.center.x + node.width * edge.endX,
|
|
1513
|
+
y: node.center.y + node.height * edge.endY
|
|
1514
|
+
};
|
|
1515
|
+
if (start.x > end.x) {
|
|
1516
|
+
;
|
|
1517
|
+
[start, end] = [end, start];
|
|
1518
|
+
}
|
|
1519
|
+
if (Math.abs(start.x - end.x) < EPS3 && start.y > end.y) {
|
|
1520
|
+
;
|
|
1521
|
+
[start, end] = [end, start];
|
|
1522
|
+
}
|
|
1523
|
+
for (const z of node.availableZ) {
|
|
1524
|
+
this.unprocessedEdges.push({
|
|
1525
|
+
parent: node,
|
|
1526
|
+
start,
|
|
1527
|
+
end,
|
|
1528
|
+
facingDirection: edge.facingDirection,
|
|
1529
|
+
z
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
this.allEdges = [...this.unprocessedEdges];
|
|
1535
|
+
this.edgeSpatialIndex = new Flatbush(this.allEdges.length);
|
|
1536
|
+
for (const edge of this.allEdges) {
|
|
1537
|
+
this.edgeSpatialIndex.add(
|
|
1538
|
+
edge.start.x,
|
|
1539
|
+
edge.start.y,
|
|
1540
|
+
edge.end.x,
|
|
1541
|
+
edge.end.y
|
|
1542
|
+
);
|
|
1543
|
+
}
|
|
1544
|
+
this.edgeSpatialIndex.finish();
|
|
1545
|
+
}
|
|
1546
|
+
allEdges;
|
|
1547
|
+
unprocessedEdges = [];
|
|
1548
|
+
segmentsWithAdjacentEmptySpace = [];
|
|
1549
|
+
edgeSpatialIndex;
|
|
1550
|
+
lastCandidateEdge = null;
|
|
1551
|
+
lastOverlappingEdges = null;
|
|
1552
|
+
lastUncoveredSegments = null;
|
|
1553
|
+
_step() {
|
|
1554
|
+
if (this.unprocessedEdges.length === 0) {
|
|
1555
|
+
this.solved = true;
|
|
1556
|
+
this.lastCandidateEdge = null;
|
|
1557
|
+
this.lastOverlappingEdges = null;
|
|
1558
|
+
this.lastUncoveredSegments = null;
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
const candidateEdge = this.unprocessedEdges.shift();
|
|
1562
|
+
this.lastCandidateEdge = candidateEdge;
|
|
1563
|
+
const nearbyEdges = this.edgeSpatialIndex.search(
|
|
1564
|
+
candidateEdge.start.x - EPS3,
|
|
1565
|
+
candidateEdge.start.y - EPS3,
|
|
1566
|
+
candidateEdge.end.x + EPS3,
|
|
1567
|
+
candidateEdge.end.y + EPS3
|
|
1568
|
+
);
|
|
1569
|
+
const overlappingEdges = nearbyEdges.map((i) => this.allEdges[i]).filter((e) => e.z === candidateEdge.z);
|
|
1570
|
+
this.lastOverlappingEdges = overlappingEdges;
|
|
1571
|
+
const uncoveredSegments = projectToUncoveredSegments(
|
|
1572
|
+
candidateEdge,
|
|
1573
|
+
overlappingEdges
|
|
1574
|
+
);
|
|
1575
|
+
this.lastUncoveredSegments = uncoveredSegments;
|
|
1576
|
+
this.segmentsWithAdjacentEmptySpace.push(...uncoveredSegments);
|
|
1577
|
+
}
|
|
1578
|
+
getOutput() {
|
|
1579
|
+
return {
|
|
1580
|
+
segmentsWithAdjacentEmptySpace: this.segmentsWithAdjacentEmptySpace
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
visualize() {
|
|
1584
|
+
const graphics = {
|
|
1585
|
+
title: "FindSegmentsWithAdjacentEmptySpace",
|
|
1586
|
+
coordinateSystem: "cartesian",
|
|
1587
|
+
rects: [],
|
|
1588
|
+
points: [],
|
|
1589
|
+
lines: [],
|
|
1590
|
+
circles: [],
|
|
1591
|
+
arrows: [],
|
|
1592
|
+
texts: []
|
|
1593
|
+
};
|
|
1594
|
+
for (const node of this.input.meshNodes) {
|
|
1595
|
+
graphics.rects.push({
|
|
1596
|
+
center: node.center,
|
|
1597
|
+
width: node.width,
|
|
1598
|
+
height: node.height,
|
|
1599
|
+
stroke: "rgba(0, 0, 0, 0.1)"
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
for (const unprocessedEdge of this.unprocessedEdges) {
|
|
1603
|
+
graphics.lines.push({
|
|
1604
|
+
points: visuallyOffsetLine(
|
|
1605
|
+
[unprocessedEdge.start, unprocessedEdge.end],
|
|
1606
|
+
unprocessedEdge.facingDirection,
|
|
1607
|
+
-0.1
|
|
1608
|
+
),
|
|
1609
|
+
strokeColor: "rgba(0, 0, 255, 0.5)",
|
|
1610
|
+
strokeDash: "5 5"
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
for (const edge of this.segmentsWithAdjacentEmptySpace) {
|
|
1614
|
+
graphics.lines.push({
|
|
1615
|
+
points: [edge.start, edge.end],
|
|
1616
|
+
strokeColor: "rgba(0,255,0, 0.5)"
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
if (this.lastCandidateEdge) {
|
|
1620
|
+
graphics.lines.push({
|
|
1621
|
+
points: [this.lastCandidateEdge.start, this.lastCandidateEdge.end],
|
|
1622
|
+
strokeColor: "blue"
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
if (this.lastOverlappingEdges) {
|
|
1626
|
+
for (const edge of this.lastOverlappingEdges) {
|
|
1627
|
+
graphics.lines.push({
|
|
1628
|
+
points: visuallyOffsetLine(
|
|
1629
|
+
[edge.start, edge.end],
|
|
1630
|
+
edge.facingDirection,
|
|
1631
|
+
0.05
|
|
1632
|
+
),
|
|
1633
|
+
strokeColor: "red",
|
|
1634
|
+
strokeDash: "2 2"
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
if (this.lastUncoveredSegments) {
|
|
1639
|
+
for (const edge of this.lastUncoveredSegments) {
|
|
1640
|
+
graphics.lines.push({
|
|
1641
|
+
points: visuallyOffsetLine(
|
|
1642
|
+
[edge.start, edge.end],
|
|
1643
|
+
edge.facingDirection,
|
|
1644
|
+
-0.05
|
|
1645
|
+
),
|
|
1646
|
+
strokeColor: "green",
|
|
1647
|
+
strokeDash: "2 2"
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
return graphics;
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
|
|
1655
|
+
// lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts
|
|
1656
|
+
import { BaseSolver as BaseSolver3 } from "@tscircuit/solver-utils";
|
|
1657
|
+
import RBush from "rbush";
|
|
1658
|
+
|
|
1659
|
+
// lib/solvers/GapFillSolver/getBoundsFromCorners.ts
|
|
1660
|
+
var getBoundsFromCorners = (corners) => {
|
|
1661
|
+
return {
|
|
1662
|
+
minX: Math.min(...corners.map((c) => c.x)),
|
|
1663
|
+
minY: Math.min(...corners.map((c) => c.y)),
|
|
1664
|
+
maxX: Math.max(...corners.map((c) => c.x)),
|
|
1665
|
+
maxY: Math.max(...corners.map((c) => c.y))
|
|
1666
|
+
};
|
|
1667
|
+
};
|
|
1668
|
+
|
|
1669
|
+
// lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts
|
|
1670
|
+
import { segmentToBoxMinDistance } from "@tscircuit/math-utils";
|
|
1671
|
+
var EPS4 = 1e-4;
|
|
1672
|
+
var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver3 {
|
|
1673
|
+
constructor(input) {
|
|
1674
|
+
super();
|
|
1675
|
+
this.input = input;
|
|
1676
|
+
this.unprocessedSegments = [...this.input.segmentsWithAdjacentEmptySpace];
|
|
1677
|
+
this.rectSpatialIndex = new RBush();
|
|
1678
|
+
this.rectSpatialIndex.load(
|
|
1679
|
+
this.input.inputMeshNodes.map((n) => ({
|
|
1680
|
+
...n,
|
|
1681
|
+
minX: n.center.x - n.width / 2,
|
|
1682
|
+
minY: n.center.y - n.height / 2,
|
|
1683
|
+
maxX: n.center.x + n.width / 2,
|
|
1684
|
+
maxY: n.center.y + n.height / 2
|
|
1685
|
+
}))
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
unprocessedSegments = [];
|
|
1689
|
+
expandedSegments = [];
|
|
1690
|
+
lastSegment = null;
|
|
1691
|
+
lastSearchBounds = null;
|
|
1692
|
+
lastCollidingNodes = null;
|
|
1693
|
+
lastSearchCorner1 = null;
|
|
1694
|
+
lastSearchCorner2 = null;
|
|
1695
|
+
lastExpandedSegment = null;
|
|
1696
|
+
rectSpatialIndex;
|
|
1697
|
+
_step() {
|
|
1698
|
+
if (this.unprocessedSegments.length === 0) {
|
|
1699
|
+
this.solved = true;
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
const segment = this.unprocessedSegments.shift();
|
|
1703
|
+
this.lastSegment = segment;
|
|
1704
|
+
const { dx, dy } = EDGE_MAP[segment.facingDirection];
|
|
1705
|
+
const deltaStartEnd = {
|
|
1706
|
+
x: segment.end.x - segment.start.x,
|
|
1707
|
+
y: segment.end.y - segment.start.y
|
|
1708
|
+
};
|
|
1709
|
+
const segLength = Math.sqrt(deltaStartEnd.x ** 2 + deltaStartEnd.y ** 2);
|
|
1710
|
+
const normDeltaStartEnd = {
|
|
1711
|
+
x: deltaStartEnd.x / segLength,
|
|
1712
|
+
y: deltaStartEnd.y / segLength
|
|
1713
|
+
};
|
|
1714
|
+
let collidingNodes = null;
|
|
1715
|
+
let searchDistance = 1;
|
|
1716
|
+
const searchCorner1 = {
|
|
1717
|
+
x: segment.start.x + dx * EPS4 + normDeltaStartEnd.x * EPS4 * 10,
|
|
1718
|
+
y: segment.start.y + dy * EPS4 + normDeltaStartEnd.y * EPS4 * 10
|
|
1719
|
+
};
|
|
1720
|
+
const searchCorner2 = {
|
|
1721
|
+
x: segment.end.x + dx * EPS4 - normDeltaStartEnd.x * EPS4 * 10,
|
|
1722
|
+
y: segment.end.y + dy * EPS4 - normDeltaStartEnd.y * EPS4 * 10
|
|
1723
|
+
};
|
|
1724
|
+
this.lastSearchCorner1 = searchCorner1;
|
|
1725
|
+
this.lastSearchCorner2 = searchCorner2;
|
|
1726
|
+
while ((!collidingNodes || collidingNodes.length === 0) && searchDistance < 1e3) {
|
|
1727
|
+
const searchBounds = getBoundsFromCorners([
|
|
1728
|
+
searchCorner1,
|
|
1729
|
+
searchCorner2,
|
|
1730
|
+
{
|
|
1731
|
+
x: searchCorner1.x + dx * searchDistance,
|
|
1732
|
+
y: searchCorner1.y + dy * searchDistance
|
|
1733
|
+
},
|
|
1734
|
+
{
|
|
1735
|
+
x: searchCorner2.x + dx * searchDistance,
|
|
1736
|
+
y: searchCorner2.y + dy * searchDistance
|
|
1737
|
+
}
|
|
1738
|
+
]);
|
|
1739
|
+
this.lastSearchBounds = searchBounds;
|
|
1740
|
+
collidingNodes = this.rectSpatialIndex.search(searchBounds).filter((n) => n.availableZ.includes(segment.z)).filter(
|
|
1741
|
+
(n) => n.capacityMeshNodeId !== segment.parent.capacityMeshNodeId
|
|
1742
|
+
);
|
|
1743
|
+
searchDistance *= 4;
|
|
1744
|
+
}
|
|
1745
|
+
if (!collidingNodes || collidingNodes.length === 0) {
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
this.lastCollidingNodes = collidingNodes;
|
|
1749
|
+
let smallestDistance = Infinity;
|
|
1750
|
+
for (const node of collidingNodes) {
|
|
1751
|
+
const distance = segmentToBoxMinDistance(segment.start, segment.end, node);
|
|
1752
|
+
if (distance < smallestDistance) {
|
|
1753
|
+
smallestDistance = distance;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
const expandDistance = smallestDistance;
|
|
1757
|
+
const nodeBounds = getBoundsFromCorners([
|
|
1758
|
+
segment.start,
|
|
1759
|
+
segment.end,
|
|
1760
|
+
{
|
|
1761
|
+
x: segment.start.x + dx * expandDistance,
|
|
1762
|
+
y: segment.start.y + dy * expandDistance
|
|
1763
|
+
},
|
|
1764
|
+
{
|
|
1765
|
+
x: segment.end.x + dx * expandDistance,
|
|
1766
|
+
y: segment.end.y + dy * expandDistance
|
|
1767
|
+
}
|
|
1768
|
+
]);
|
|
1769
|
+
const nodeCenter = {
|
|
1770
|
+
x: (nodeBounds.minX + nodeBounds.maxX) / 2,
|
|
1771
|
+
y: (nodeBounds.minY + nodeBounds.maxY) / 2
|
|
1772
|
+
};
|
|
1773
|
+
const nodeWidth = nodeBounds.maxX - nodeBounds.minX;
|
|
1774
|
+
const nodeHeight = nodeBounds.maxY - nodeBounds.minY;
|
|
1775
|
+
const expandedSegment = {
|
|
1776
|
+
segment,
|
|
1777
|
+
newNode: {
|
|
1778
|
+
capacityMeshNodeId: `new-${segment.parent.capacityMeshNodeId}-${this.expandedSegments.length}`,
|
|
1779
|
+
center: nodeCenter,
|
|
1780
|
+
width: nodeWidth,
|
|
1781
|
+
height: nodeHeight,
|
|
1782
|
+
availableZ: [segment.z],
|
|
1783
|
+
layer: segment.parent.layer
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
this.lastExpandedSegment = expandedSegment;
|
|
1787
|
+
if (nodeWidth < EPS4 || nodeHeight < EPS4) {
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
this.expandedSegments.push(expandedSegment);
|
|
1791
|
+
this.rectSpatialIndex.insert({
|
|
1792
|
+
...expandedSegment.newNode,
|
|
1793
|
+
...nodeBounds
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
getOutput() {
|
|
1797
|
+
return {
|
|
1798
|
+
expandedSegments: this.expandedSegments
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
visualize() {
|
|
1802
|
+
const graphics = {
|
|
1803
|
+
title: "ExpandEdgesToEmptySpace",
|
|
1804
|
+
coordinateSystem: "cartesian",
|
|
1805
|
+
rects: [],
|
|
1806
|
+
points: [],
|
|
1807
|
+
lines: [],
|
|
1808
|
+
circles: [],
|
|
1809
|
+
arrows: [],
|
|
1810
|
+
texts: []
|
|
1811
|
+
};
|
|
1812
|
+
for (const node of this.input.inputMeshNodes) {
|
|
1813
|
+
graphics.rects.push({
|
|
1814
|
+
center: node.center,
|
|
1815
|
+
width: node.width,
|
|
1816
|
+
height: node.height,
|
|
1817
|
+
stroke: "rgba(0, 0, 0, 0.1)",
|
|
1818
|
+
layer: `z${node.availableZ.join(",")}`,
|
|
1819
|
+
label: [
|
|
1820
|
+
`node ${node.capacityMeshNodeId}`,
|
|
1821
|
+
`z:${node.availableZ.join(",")}`
|
|
1822
|
+
].join("\n")
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
for (const { newNode } of this.expandedSegments) {
|
|
1826
|
+
graphics.rects.push({
|
|
1827
|
+
center: newNode.center,
|
|
1828
|
+
width: newNode.width,
|
|
1829
|
+
height: newNode.height,
|
|
1830
|
+
fill: "green",
|
|
1831
|
+
label: `expandedSegment (z=${newNode.availableZ.join(",")})`,
|
|
1832
|
+
layer: `z${newNode.availableZ.join(",")}`
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
if (this.lastSegment) {
|
|
1836
|
+
graphics.lines.push({
|
|
1837
|
+
points: [this.lastSegment.start, this.lastSegment.end],
|
|
1838
|
+
strokeColor: "rgba(0, 0, 255, 0.5)"
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
if (this.lastSearchBounds) {
|
|
1842
|
+
graphics.rects.push({
|
|
1843
|
+
center: {
|
|
1844
|
+
x: (this.lastSearchBounds.minX + this.lastSearchBounds.maxX) / 2,
|
|
1845
|
+
y: (this.lastSearchBounds.minY + this.lastSearchBounds.maxY) / 2
|
|
1846
|
+
},
|
|
1847
|
+
width: this.lastSearchBounds.maxX - this.lastSearchBounds.minX,
|
|
1848
|
+
height: this.lastSearchBounds.maxY - this.lastSearchBounds.minY,
|
|
1849
|
+
fill: "rgba(0, 0, 255, 0.25)"
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
if (this.lastSearchCorner1 && this.lastSearchCorner2) {
|
|
1853
|
+
graphics.points.push({
|
|
1854
|
+
x: this.lastSearchCorner1.x,
|
|
1855
|
+
y: this.lastSearchCorner1.y,
|
|
1856
|
+
color: "rgba(0, 0, 255, 0.5)",
|
|
1857
|
+
label: ["searchCorner1", `z=${this.lastSegment?.z}`].join("\n")
|
|
1858
|
+
});
|
|
1859
|
+
graphics.points.push({
|
|
1860
|
+
x: this.lastSearchCorner2.x,
|
|
1861
|
+
y: this.lastSearchCorner2.y,
|
|
1862
|
+
color: "rgba(0, 0, 255, 0.5)",
|
|
1863
|
+
label: ["searchCorner2", `z=${this.lastSegment?.z}`].join("\n")
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
if (this.lastExpandedSegment) {
|
|
1867
|
+
graphics.rects.push({
|
|
1868
|
+
center: this.lastExpandedSegment.newNode.center,
|
|
1869
|
+
width: this.lastExpandedSegment.newNode.width,
|
|
1870
|
+
height: this.lastExpandedSegment.newNode.height,
|
|
1871
|
+
fill: "purple",
|
|
1872
|
+
label: `expandedSegment (z=${this.lastExpandedSegment.segment.z})`
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
if (this.lastCollidingNodes) {
|
|
1876
|
+
for (const node of this.lastCollidingNodes) {
|
|
1877
|
+
graphics.rects.push({
|
|
1878
|
+
center: node.center,
|
|
1879
|
+
width: node.width,
|
|
1880
|
+
height: node.height,
|
|
1881
|
+
fill: "rgba(255, 0, 0, 0.5)"
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
return graphics;
|
|
1886
|
+
}
|
|
1887
|
+
};
|
|
1888
|
+
|
|
1889
|
+
// lib/solvers/GapFillSolver/GapFillSolverPipeline.ts
|
|
1890
|
+
var GapFillSolverPipeline = class extends BasePipelineSolver2 {
|
|
1891
|
+
findSegmentsWithAdjacentEmptySpaceSolver;
|
|
1892
|
+
expandEdgesToEmptySpaceSolver;
|
|
1893
|
+
pipelineDef = [
|
|
1894
|
+
definePipelineStep(
|
|
1895
|
+
"findSegmentsWithAdjacentEmptySpaceSolver",
|
|
1896
|
+
FindSegmentsWithAdjacentEmptySpaceSolver,
|
|
1897
|
+
(gapFillPipeline) => [
|
|
1898
|
+
{
|
|
1899
|
+
meshNodes: gapFillPipeline.inputProblem.meshNodes
|
|
1900
|
+
}
|
|
1901
|
+
],
|
|
1902
|
+
{
|
|
1903
|
+
onSolved: () => {
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
),
|
|
1907
|
+
definePipelineStep(
|
|
1908
|
+
"expandEdgesToEmptySpace",
|
|
1909
|
+
ExpandEdgesToEmptySpaceSolver,
|
|
1910
|
+
(gapFillPipeline) => [
|
|
1911
|
+
{
|
|
1912
|
+
inputMeshNodes: gapFillPipeline.inputProblem.meshNodes,
|
|
1913
|
+
segmentsWithAdjacentEmptySpace: gapFillPipeline.findSegmentsWithAdjacentEmptySpaceSolver.getOutput().segmentsWithAdjacentEmptySpace
|
|
1914
|
+
}
|
|
1915
|
+
],
|
|
1916
|
+
{
|
|
1917
|
+
onSolved: () => {
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
)
|
|
1921
|
+
];
|
|
1922
|
+
getOutput() {
|
|
1923
|
+
const expandedSegments = this.expandEdgesToEmptySpaceSolver?.getOutput().expandedSegments ?? [];
|
|
1924
|
+
const expandedNodes = expandedSegments.map((es) => es.newNode);
|
|
1925
|
+
return {
|
|
1926
|
+
outputNodes: [...this.inputProblem.meshNodes, ...expandedNodes]
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
initialVisualize() {
|
|
1930
|
+
const graphics = {
|
|
1931
|
+
title: "GapFillSolverPipeline - Initial",
|
|
1932
|
+
coordinateSystem: "cartesian",
|
|
1933
|
+
rects: [],
|
|
1934
|
+
points: [],
|
|
1935
|
+
lines: [],
|
|
1936
|
+
circles: [],
|
|
1937
|
+
arrows: [],
|
|
1938
|
+
texts: []
|
|
1939
|
+
};
|
|
1940
|
+
for (const node of this.inputProblem.meshNodes) {
|
|
1941
|
+
graphics.rects.push({
|
|
1942
|
+
center: node.center,
|
|
1943
|
+
width: node.width,
|
|
1944
|
+
height: node.height,
|
|
1945
|
+
stroke: "rgba(0, 0, 0, 0.3)",
|
|
1946
|
+
fill: "rgba(100, 100, 100, 0.1)",
|
|
1947
|
+
layer: `z${node.availableZ.join(",")}`,
|
|
1948
|
+
label: [
|
|
1949
|
+
`node ${node.capacityMeshNodeId}`,
|
|
1950
|
+
`z:${node.availableZ.join(",")}`
|
|
1951
|
+
].join("\n")
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
return graphics;
|
|
1955
|
+
}
|
|
1956
|
+
finalVisualize() {
|
|
1957
|
+
const graphics = {
|
|
1958
|
+
title: "GapFillSolverPipeline - Final",
|
|
1959
|
+
coordinateSystem: "cartesian",
|
|
1960
|
+
rects: [],
|
|
1961
|
+
points: [],
|
|
1962
|
+
lines: [],
|
|
1963
|
+
circles: [],
|
|
1964
|
+
arrows: [],
|
|
1965
|
+
texts: []
|
|
1966
|
+
};
|
|
1967
|
+
const { outputNodes } = this.getOutput();
|
|
1968
|
+
const expandedSegments = this.expandEdgesToEmptySpaceSolver?.getOutput().expandedSegments ?? [];
|
|
1969
|
+
const expandedNodeIds = new Set(
|
|
1970
|
+
expandedSegments.map((es) => es.newNode.capacityMeshNodeId)
|
|
1971
|
+
);
|
|
1972
|
+
for (const node of outputNodes) {
|
|
1973
|
+
const isExpanded = expandedNodeIds.has(node.capacityMeshNodeId);
|
|
1974
|
+
graphics.rects.push({
|
|
1975
|
+
center: node.center,
|
|
1976
|
+
width: node.width,
|
|
1977
|
+
height: node.height,
|
|
1978
|
+
stroke: isExpanded ? "rgba(0, 128, 0, 0.8)" : "rgba(0, 0, 0, 0.3)",
|
|
1979
|
+
fill: isExpanded ? "rgba(0, 200, 0, 0.3)" : "rgba(100, 100, 100, 0.1)",
|
|
1980
|
+
layer: `z${node.availableZ.join(",")}`,
|
|
1981
|
+
label: [
|
|
1982
|
+
`${isExpanded ? "[expanded] " : ""}node ${node.capacityMeshNodeId}`,
|
|
1983
|
+
`z:${node.availableZ.join(",")}`
|
|
1984
|
+
].join("\n")
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
return graphics;
|
|
1988
|
+
}
|
|
1989
|
+
};
|
|
1990
|
+
|
|
1413
1991
|
// lib/RectDiffPipeline.ts
|
|
1414
|
-
var RectDiffPipeline = class extends
|
|
1992
|
+
var RectDiffPipeline = class extends BasePipelineSolver3 {
|
|
1415
1993
|
rectDiffSolver;
|
|
1994
|
+
gapFillSolver;
|
|
1416
1995
|
pipelineDef = [
|
|
1417
|
-
|
|
1996
|
+
definePipelineStep2(
|
|
1418
1997
|
"rectDiffSolver",
|
|
1419
1998
|
RectDiffSolver,
|
|
1420
|
-
(
|
|
1999
|
+
(rectDiffPipeline) => [
|
|
1421
2000
|
{
|
|
1422
|
-
simpleRouteJson:
|
|
1423
|
-
gridOptions:
|
|
2001
|
+
simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
|
|
2002
|
+
gridOptions: rectDiffPipeline.inputProblem.gridOptions
|
|
1424
2003
|
}
|
|
1425
2004
|
],
|
|
1426
2005
|
{
|
|
1427
2006
|
onSolved: () => {
|
|
1428
2007
|
}
|
|
1429
2008
|
}
|
|
2009
|
+
),
|
|
2010
|
+
definePipelineStep2(
|
|
2011
|
+
"gapFillSolver",
|
|
2012
|
+
GapFillSolverPipeline,
|
|
2013
|
+
(rectDiffPipeline) => [
|
|
2014
|
+
{
|
|
2015
|
+
meshNodes: rectDiffPipeline.rectDiffSolver?.getOutput().meshNodes ?? []
|
|
2016
|
+
}
|
|
2017
|
+
]
|
|
1430
2018
|
)
|
|
1431
2019
|
];
|
|
1432
2020
|
getConstructorParams() {
|
|
1433
2021
|
return [this.inputProblem];
|
|
1434
2022
|
}
|
|
1435
2023
|
getOutput() {
|
|
1436
|
-
|
|
2024
|
+
const gapFillOutput = this.gapFillSolver?.getOutput();
|
|
2025
|
+
if (gapFillOutput) {
|
|
2026
|
+
return { meshNodes: gapFillOutput.outputNodes };
|
|
2027
|
+
}
|
|
2028
|
+
return this.rectDiffSolver.getOutput();
|
|
1437
2029
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
2030
|
+
initialVisualize() {
|
|
2031
|
+
console.log("RectDiffPipeline - initialVisualize");
|
|
2032
|
+
const graphics = createBaseVisualization(
|
|
2033
|
+
this.inputProblem.simpleRouteJson,
|
|
2034
|
+
"RectDiffPipeline - Initial"
|
|
2035
|
+
);
|
|
2036
|
+
const initialNodes = this.rectDiffSolver?.getOutput().meshNodes ?? [];
|
|
2037
|
+
for (const node of initialNodes) {
|
|
2038
|
+
graphics.rects.push({
|
|
2039
|
+
center: node.center,
|
|
2040
|
+
width: node.width,
|
|
2041
|
+
height: node.height,
|
|
2042
|
+
stroke: "rgba(0, 0, 0, 0.3)",
|
|
2043
|
+
fill: "rgba(100, 100, 100, 0.1)",
|
|
2044
|
+
layer: `z${node.availableZ.join(",")}`,
|
|
2045
|
+
label: [
|
|
2046
|
+
`node ${node.capacityMeshNodeId}`,
|
|
2047
|
+
`z:${node.availableZ.join(",")}`
|
|
2048
|
+
].join("\n")
|
|
2049
|
+
});
|
|
1442
2050
|
}
|
|
1443
|
-
return
|
|
2051
|
+
return graphics;
|
|
2052
|
+
}
|
|
2053
|
+
finalVisualize() {
|
|
2054
|
+
const graphics = createBaseVisualization(
|
|
1444
2055
|
this.inputProblem.simpleRouteJson,
|
|
1445
|
-
"
|
|
2056
|
+
"RectDiffPipeline - Final"
|
|
1446
2057
|
);
|
|
2058
|
+
const { meshNodes: outputNodes } = this.getOutput();
|
|
2059
|
+
const initialNodeIds = new Set(
|
|
2060
|
+
(this.rectDiffSolver?.getOutput().meshNodes ?? []).map(
|
|
2061
|
+
(n) => n.capacityMeshNodeId
|
|
2062
|
+
)
|
|
2063
|
+
);
|
|
2064
|
+
for (const node of outputNodes) {
|
|
2065
|
+
const isExpanded = !initialNodeIds.has(node.capacityMeshNodeId);
|
|
2066
|
+
graphics.rects.push({
|
|
2067
|
+
center: node.center,
|
|
2068
|
+
width: node.width,
|
|
2069
|
+
height: node.height,
|
|
2070
|
+
stroke: isExpanded ? "rgba(0, 128, 0, 0.8)" : "rgba(0, 0, 0, 0.3)",
|
|
2071
|
+
fill: isExpanded ? "rgba(0, 200, 0, 0.3)" : "rgba(100, 100, 100, 0.1)",
|
|
2072
|
+
layer: `z${node.availableZ.join(",")}`,
|
|
2073
|
+
label: [
|
|
2074
|
+
`${isExpanded ? "[expanded] " : ""}node ${node.capacityMeshNodeId}`,
|
|
2075
|
+
`z:${node.availableZ.join(",")}`
|
|
2076
|
+
].join("\n")
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
return graphics;
|
|
1447
2080
|
}
|
|
1448
2081
|
};
|
|
1449
2082
|
export {
|