@tscircuit/rectdiff 0.0.15 → 0.0.17
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 +19 -7
- package/dist/index.js +53 -15
- package/lib/RectDiffPipeline.ts +27 -1
- package/lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts +32 -6
- package/lib/solvers/GapFillSolver/GapFillSolverPipeline.ts +10 -3
- package/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts +8 -8
- package/lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts +7 -8
- package/package.json +1 -1
- package/pages/gap-fill-h-shape-should-expand-node.page.tsx +23 -0
- package/test-assets/gap-fill-h-shape-should-expand-node.json +72 -0
- package/tests/__snapshots__/board-outline.snap.svg +1 -16
- package/tests/__snapshots__/should-expand-node.snap.svg +44 -0
- package/tests/board-outline.test.ts +10 -3
- package/tests/should-expand-node.test.ts +34 -0
- package/tests/fixtures/getPerLayerVisualizations.ts +0 -130
package/dist/index.d.ts
CHANGED
|
@@ -159,6 +159,14 @@ declare class FindSegmentsWithAdjacentEmptySpaceSolver extends BaseSolver {
|
|
|
159
159
|
visualize(): Required<GraphicsObject>;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
type ExpandEdgesToEmptySpaceSolverInput = {
|
|
163
|
+
inputMeshNodes: CapacityMeshNode[];
|
|
164
|
+
segmentsWithAdjacentEmptySpace: Array<SegmentWithAdjacentEmptySpace>;
|
|
165
|
+
boardVoid?: {
|
|
166
|
+
boardVoidRects: XYRect[];
|
|
167
|
+
layerCount: number;
|
|
168
|
+
};
|
|
169
|
+
};
|
|
162
170
|
interface ExpandedSegment {
|
|
163
171
|
segment: SegmentWithAdjacentEmptySpace;
|
|
164
172
|
newNode: CapacityMeshNode;
|
|
@@ -180,10 +188,7 @@ declare class ExpandEdgesToEmptySpaceSolver extends BaseSolver {
|
|
|
180
188
|
} | null;
|
|
181
189
|
lastExpandedSegment: ExpandedSegment | null;
|
|
182
190
|
rectSpatialIndex: RBush<CapacityMeshNode>;
|
|
183
|
-
constructor(input:
|
|
184
|
-
inputMeshNodes: CapacityMeshNode[];
|
|
185
|
-
segmentsWithAdjacentEmptySpace: Array<SegmentWithAdjacentEmptySpace>;
|
|
186
|
-
});
|
|
191
|
+
constructor(input: ExpandEdgesToEmptySpaceSolverInput);
|
|
187
192
|
_step(): void;
|
|
188
193
|
getOutput(): {
|
|
189
194
|
expandedSegments: ExpandedSegment[];
|
|
@@ -191,9 +196,14 @@ declare class ExpandEdgesToEmptySpaceSolver extends BaseSolver {
|
|
|
191
196
|
visualize(): Required<GraphicsObject>;
|
|
192
197
|
}
|
|
193
198
|
|
|
194
|
-
|
|
199
|
+
type GapFillSolverInput = {
|
|
195
200
|
meshNodes: CapacityMeshNode[];
|
|
196
|
-
|
|
201
|
+
boardVoid?: {
|
|
202
|
+
boardVoidRects: XYRect[];
|
|
203
|
+
layerCount: number;
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
declare class GapFillSolverPipeline extends BasePipelineSolver<GapFillSolverInput> {
|
|
197
207
|
findSegmentsWithAdjacentEmptySpaceSolver?: FindSegmentsWithAdjacentEmptySpaceSolver;
|
|
198
208
|
expandEdgesToEmptySpaceSolver?: ExpandEdgesToEmptySpaceSolver;
|
|
199
209
|
pipelineDef: PipelineStep<any>[];
|
|
@@ -329,11 +339,11 @@ declare class RectDiffExpansionSolver extends BaseSolver {
|
|
|
329
339
|
type RectDiffGridSolverPipelineInput = {
|
|
330
340
|
simpleRouteJson: SimpleRouteJson;
|
|
331
341
|
gridOptions?: Partial<GridFill3DOptions>;
|
|
342
|
+
boardVoidRects?: XYRect[];
|
|
332
343
|
};
|
|
333
344
|
declare class RectDiffGridSolverPipeline extends BasePipelineSolver<RectDiffGridSolverPipelineInput> {
|
|
334
345
|
rectDiffSeedingSolver?: RectDiffSeedingSolver;
|
|
335
346
|
rectDiffExpansionSolver?: RectDiffExpansionSolver;
|
|
336
|
-
private boardVoidRects?;
|
|
337
347
|
private obstacleIndexByLayer;
|
|
338
348
|
constructor(inputProblem: RectDiffGridSolverPipelineInput);
|
|
339
349
|
pipelineDef: PipelineStep<any>[];
|
|
@@ -351,7 +361,9 @@ interface RectDiffPipelineInput {
|
|
|
351
361
|
declare class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput> {
|
|
352
362
|
rectDiffGridSolverPipeline?: RectDiffGridSolverPipeline;
|
|
353
363
|
gapFillSolver?: GapFillSolverPipeline;
|
|
364
|
+
boardVoidRects: XYRect[] | undefined;
|
|
354
365
|
pipelineDef: PipelineStep<any>[];
|
|
366
|
+
_setup(): void;
|
|
355
367
|
getConstructorParams(): RectDiffPipelineInput[];
|
|
356
368
|
getOutput(): {
|
|
357
369
|
meshNodes: CapacityMeshNode[];
|
package/dist/index.js
CHANGED
|
@@ -331,6 +331,26 @@ var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver2 {
|
|
|
331
331
|
this.input = input;
|
|
332
332
|
this.unprocessedSegments = [...this.input.segmentsWithAdjacentEmptySpace];
|
|
333
333
|
this.rectSpatialIndex = new RBush();
|
|
334
|
+
this.rectSpatialIndex.load(
|
|
335
|
+
this.input.boardVoid?.boardVoidRects.map((rect, index) => ({
|
|
336
|
+
capacityMeshNodeId: `void-rect-${index}`,
|
|
337
|
+
center: {
|
|
338
|
+
x: rect.x + rect.width / 2,
|
|
339
|
+
y: rect.y + rect.height / 2
|
|
340
|
+
},
|
|
341
|
+
width: rect.width,
|
|
342
|
+
height: rect.height,
|
|
343
|
+
availableZ: Array.from(
|
|
344
|
+
{ length: this.input.boardVoid?.layerCount || 0 },
|
|
345
|
+
(_, i) => i
|
|
346
|
+
),
|
|
347
|
+
layer: "void",
|
|
348
|
+
minX: rect.x,
|
|
349
|
+
minY: rect.y,
|
|
350
|
+
maxX: rect.x + rect.width,
|
|
351
|
+
maxY: rect.y + rect.height
|
|
352
|
+
})) || []
|
|
353
|
+
);
|
|
334
354
|
this.rectSpatialIndex.load(
|
|
335
355
|
this.input.inputMeshNodes.map((n) => ({
|
|
336
356
|
...n,
|
|
@@ -566,7 +586,8 @@ var GapFillSolverPipeline = class extends BasePipelineSolver {
|
|
|
566
586
|
(gapFillPipeline) => [
|
|
567
587
|
{
|
|
568
588
|
inputMeshNodes: gapFillPipeline.inputProblem.meshNodes,
|
|
569
|
-
segmentsWithAdjacentEmptySpace: gapFillPipeline.findSegmentsWithAdjacentEmptySpaceSolver.getOutput().segmentsWithAdjacentEmptySpace
|
|
589
|
+
segmentsWithAdjacentEmptySpace: gapFillPipeline.findSegmentsWithAdjacentEmptySpaceSolver.getOutput().segmentsWithAdjacentEmptySpace,
|
|
590
|
+
boardVoid: gapFillPipeline.inputProblem.boardVoid
|
|
570
591
|
}
|
|
571
592
|
],
|
|
572
593
|
{
|
|
@@ -2146,7 +2167,8 @@ import "rbush";
|
|
|
2146
2167
|
|
|
2147
2168
|
// lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
|
|
2148
2169
|
import RBush5 from "rbush";
|
|
2149
|
-
var
|
|
2170
|
+
var buildObstacleIndexesByLayer = (params) => {
|
|
2171
|
+
const { srj, boardVoidRects } = params;
|
|
2150
2172
|
const { layerNames, zIndexByName } = buildZIndexMap(srj);
|
|
2151
2173
|
const layerCount = Math.max(1, layerNames.length, srj.layerCount || 1);
|
|
2152
2174
|
const bounds = {
|
|
@@ -2169,10 +2191,8 @@ var buildObstacleIndexes = (srj) => {
|
|
|
2169
2191
|
};
|
|
2170
2192
|
obstacleIndexByLayer[z]?.insert(treeRect);
|
|
2171
2193
|
};
|
|
2172
|
-
let boardVoidRects = [];
|
|
2173
2194
|
if (srj.outline && srj.outline.length > 2) {
|
|
2174
|
-
boardVoidRects
|
|
2175
|
-
for (const voidRect of boardVoidRects) {
|
|
2195
|
+
for (const voidRect of boardVoidRects ?? []) {
|
|
2176
2196
|
for (let z = 0; z < layerCount; z++) insertObstacle(voidRect, z);
|
|
2177
2197
|
}
|
|
2178
2198
|
}
|
|
@@ -2191,22 +2211,21 @@ var buildObstacleIndexes = (srj) => {
|
|
|
2191
2211
|
}
|
|
2192
2212
|
for (const z of zLayers) insertObstacle(rect, z);
|
|
2193
2213
|
}
|
|
2194
|
-
return { obstacleIndexByLayer
|
|
2214
|
+
return { obstacleIndexByLayer };
|
|
2195
2215
|
};
|
|
2196
2216
|
|
|
2197
2217
|
// lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
|
|
2198
2218
|
var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
|
|
2199
2219
|
rectDiffSeedingSolver;
|
|
2200
2220
|
rectDiffExpansionSolver;
|
|
2201
|
-
boardVoidRects;
|
|
2202
2221
|
obstacleIndexByLayer;
|
|
2203
2222
|
constructor(inputProblem) {
|
|
2204
2223
|
super(inputProblem);
|
|
2205
|
-
const { obstacleIndexByLayer
|
|
2206
|
-
inputProblem.simpleRouteJson
|
|
2207
|
-
|
|
2224
|
+
const { obstacleIndexByLayer } = buildObstacleIndexesByLayer({
|
|
2225
|
+
srj: inputProblem.simpleRouteJson,
|
|
2226
|
+
boardVoidRects: inputProblem.boardVoidRects
|
|
2227
|
+
});
|
|
2208
2228
|
this.obstacleIndexByLayer = obstacleIndexByLayer;
|
|
2209
|
-
this.boardVoidRects = boardVoidRects;
|
|
2210
2229
|
}
|
|
2211
2230
|
pipelineDef = [
|
|
2212
2231
|
definePipelineStep2(
|
|
@@ -2217,7 +2236,7 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
|
|
|
2217
2236
|
simpleRouteJson: pipeline.inputProblem.simpleRouteJson,
|
|
2218
2237
|
gridOptions: pipeline.inputProblem.gridOptions,
|
|
2219
2238
|
obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
|
|
2220
|
-
boardVoidRects: pipeline.boardVoidRects
|
|
2239
|
+
boardVoidRects: pipeline.inputProblem.boardVoidRects
|
|
2221
2240
|
}
|
|
2222
2241
|
]
|
|
2223
2242
|
),
|
|
@@ -2228,7 +2247,7 @@ var RectDiffGridSolverPipeline = class extends BasePipelineSolver2 {
|
|
|
2228
2247
|
{
|
|
2229
2248
|
initialSnapshot: {
|
|
2230
2249
|
...pipeline.rectDiffSeedingSolver.getOutput(),
|
|
2231
|
-
boardVoidRects: pipeline.boardVoidRects ?? []
|
|
2250
|
+
boardVoidRects: pipeline.inputProblem.boardVoidRects ?? []
|
|
2232
2251
|
},
|
|
2233
2252
|
obstacleIndexByLayer: pipeline.obstacleIndexByLayer
|
|
2234
2253
|
}
|
|
@@ -2334,6 +2353,7 @@ function createBaseVisualization(srj, title = "RectDiff") {
|
|
|
2334
2353
|
var RectDiffPipeline = class extends BasePipelineSolver3 {
|
|
2335
2354
|
rectDiffGridSolverPipeline;
|
|
2336
2355
|
gapFillSolver;
|
|
2356
|
+
boardVoidRects;
|
|
2337
2357
|
pipelineDef = [
|
|
2338
2358
|
definePipelineStep3(
|
|
2339
2359
|
"rectDiffGridSolverPipeline",
|
|
@@ -2341,7 +2361,8 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
|
|
|
2341
2361
|
(rectDiffPipeline) => [
|
|
2342
2362
|
{
|
|
2343
2363
|
simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
|
|
2344
|
-
gridOptions: rectDiffPipeline.inputProblem.gridOptions
|
|
2364
|
+
gridOptions: rectDiffPipeline.inputProblem.gridOptions,
|
|
2365
|
+
boardVoidRects: rectDiffPipeline.boardVoidRects
|
|
2345
2366
|
}
|
|
2346
2367
|
]
|
|
2347
2368
|
),
|
|
@@ -2350,11 +2371,28 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
|
|
|
2350
2371
|
GapFillSolverPipeline,
|
|
2351
2372
|
(rectDiffPipeline) => [
|
|
2352
2373
|
{
|
|
2353
|
-
meshNodes: rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? []
|
|
2374
|
+
meshNodes: rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [],
|
|
2375
|
+
boardVoid: {
|
|
2376
|
+
boardVoidRects: rectDiffPipeline.boardVoidRects || [],
|
|
2377
|
+
layerCount: rectDiffPipeline.inputProblem.simpleRouteJson.layerCount || 0
|
|
2378
|
+
}
|
|
2354
2379
|
}
|
|
2355
2380
|
]
|
|
2356
2381
|
)
|
|
2357
2382
|
];
|
|
2383
|
+
_setup() {
|
|
2384
|
+
if (this.inputProblem.simpleRouteJson.outline) {
|
|
2385
|
+
this.boardVoidRects = computeInverseRects(
|
|
2386
|
+
{
|
|
2387
|
+
x: this.inputProblem.simpleRouteJson.bounds.minX,
|
|
2388
|
+
y: this.inputProblem.simpleRouteJson.bounds.minY,
|
|
2389
|
+
width: this.inputProblem.simpleRouteJson.bounds.maxX - this.inputProblem.simpleRouteJson.bounds.minX,
|
|
2390
|
+
height: this.inputProblem.simpleRouteJson.bounds.maxY - this.inputProblem.simpleRouteJson.bounds.minY
|
|
2391
|
+
},
|
|
2392
|
+
this.inputProblem.simpleRouteJson.outline ?? []
|
|
2393
|
+
);
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2358
2396
|
getConstructorParams() {
|
|
2359
2397
|
return [this.inputProblem];
|
|
2360
2398
|
}
|
package/lib/RectDiffPipeline.ts
CHANGED
|
@@ -4,12 +4,13 @@ import {
|
|
|
4
4
|
type PipelineStep,
|
|
5
5
|
} from "@tscircuit/solver-utils"
|
|
6
6
|
import type { SimpleRouteJson } from "./types/srj-types"
|
|
7
|
-
import type { GridFill3DOptions } from "./rectdiff-types"
|
|
7
|
+
import type { GridFill3DOptions, XYRect } from "./rectdiff-types"
|
|
8
8
|
import type { CapacityMeshNode } from "./types/capacity-mesh-types"
|
|
9
9
|
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 { computeInverseRects } from "./solvers/RectDiffSeedingSolver/computeInverseRects"
|
|
13
14
|
|
|
14
15
|
export interface RectDiffPipelineInput {
|
|
15
16
|
simpleRouteJson: SimpleRouteJson
|
|
@@ -19,6 +20,7 @@ export interface RectDiffPipelineInput {
|
|
|
19
20
|
export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput> {
|
|
20
21
|
rectDiffGridSolverPipeline?: RectDiffGridSolverPipeline
|
|
21
22
|
gapFillSolver?: GapFillSolverPipeline
|
|
23
|
+
boardVoidRects: XYRect[] | undefined
|
|
22
24
|
|
|
23
25
|
override pipelineDef: PipelineStep<any>[] = [
|
|
24
26
|
definePipelineStep(
|
|
@@ -28,6 +30,7 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
|
|
|
28
30
|
{
|
|
29
31
|
simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
|
|
30
32
|
gridOptions: rectDiffPipeline.inputProblem.gridOptions,
|
|
33
|
+
boardVoidRects: rectDiffPipeline.boardVoidRects,
|
|
31
34
|
},
|
|
32
35
|
],
|
|
33
36
|
),
|
|
@@ -39,11 +42,34 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
|
|
|
39
42
|
meshNodes:
|
|
40
43
|
rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput()
|
|
41
44
|
.meshNodes ?? [],
|
|
45
|
+
boardVoid: {
|
|
46
|
+
boardVoidRects: rectDiffPipeline.boardVoidRects || [],
|
|
47
|
+
layerCount:
|
|
48
|
+
rectDiffPipeline.inputProblem.simpleRouteJson.layerCount || 0,
|
|
49
|
+
},
|
|
42
50
|
},
|
|
43
51
|
],
|
|
44
52
|
),
|
|
45
53
|
]
|
|
46
54
|
|
|
55
|
+
override _setup(): void {
|
|
56
|
+
if (this.inputProblem.simpleRouteJson.outline) {
|
|
57
|
+
this.boardVoidRects = computeInverseRects(
|
|
58
|
+
{
|
|
59
|
+
x: this.inputProblem.simpleRouteJson.bounds.minX,
|
|
60
|
+
y: this.inputProblem.simpleRouteJson.bounds.minY,
|
|
61
|
+
width:
|
|
62
|
+
this.inputProblem.simpleRouteJson.bounds.maxX -
|
|
63
|
+
this.inputProblem.simpleRouteJson.bounds.minX,
|
|
64
|
+
height:
|
|
65
|
+
this.inputProblem.simpleRouteJson.bounds.maxY -
|
|
66
|
+
this.inputProblem.simpleRouteJson.bounds.minY,
|
|
67
|
+
},
|
|
68
|
+
this.inputProblem.simpleRouteJson.outline ?? [],
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
47
73
|
override getConstructorParams() {
|
|
48
74
|
return [this.inputProblem]
|
|
49
75
|
}
|
|
@@ -7,9 +7,19 @@ import { EDGE_MAP, EDGES } from "./edge-constants"
|
|
|
7
7
|
import { getBoundsFromCorners } from "./getBoundsFromCorners"
|
|
8
8
|
import type { Bounds } from "@tscircuit/math-utils"
|
|
9
9
|
import { midpoint, segmentToBoxMinDistance } from "@tscircuit/math-utils"
|
|
10
|
+
import type { XYRect } from "lib/rectdiff-types"
|
|
10
11
|
|
|
11
12
|
const EPS = 1e-4
|
|
12
13
|
|
|
14
|
+
export type ExpandEdgesToEmptySpaceSolverInput = {
|
|
15
|
+
inputMeshNodes: CapacityMeshNode[]
|
|
16
|
+
segmentsWithAdjacentEmptySpace: Array<SegmentWithAdjacentEmptySpace>
|
|
17
|
+
boardVoid?: {
|
|
18
|
+
boardVoidRects: XYRect[]
|
|
19
|
+
layerCount: number
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
export interface ExpandedSegment {
|
|
14
24
|
segment: SegmentWithAdjacentEmptySpace
|
|
15
25
|
newNode: CapacityMeshNode
|
|
@@ -28,15 +38,31 @@ export class ExpandEdgesToEmptySpaceSolver extends BaseSolver {
|
|
|
28
38
|
|
|
29
39
|
rectSpatialIndex: RBush<CapacityMeshNode>
|
|
30
40
|
|
|
31
|
-
constructor(
|
|
32
|
-
private input: {
|
|
33
|
-
inputMeshNodes: CapacityMeshNode[]
|
|
34
|
-
segmentsWithAdjacentEmptySpace: Array<SegmentWithAdjacentEmptySpace>
|
|
35
|
-
},
|
|
36
|
-
) {
|
|
41
|
+
constructor(private input: ExpandEdgesToEmptySpaceSolverInput) {
|
|
37
42
|
super()
|
|
38
43
|
this.unprocessedSegments = [...this.input.segmentsWithAdjacentEmptySpace]
|
|
39
44
|
this.rectSpatialIndex = new RBush<CapacityMeshNode>()
|
|
45
|
+
// create fake bound for the boardVoidRects
|
|
46
|
+
this.rectSpatialIndex.load(
|
|
47
|
+
this.input.boardVoid?.boardVoidRects.map((rect, index) => ({
|
|
48
|
+
capacityMeshNodeId: `void-rect-${index}`,
|
|
49
|
+
center: {
|
|
50
|
+
x: rect.x + rect.width / 2,
|
|
51
|
+
y: rect.y + rect.height / 2,
|
|
52
|
+
},
|
|
53
|
+
width: rect.width,
|
|
54
|
+
height: rect.height,
|
|
55
|
+
availableZ: Array.from(
|
|
56
|
+
{ length: this.input.boardVoid?.layerCount || 0 },
|
|
57
|
+
(_, i) => i,
|
|
58
|
+
),
|
|
59
|
+
layer: "void",
|
|
60
|
+
minX: rect.x,
|
|
61
|
+
minY: rect.y,
|
|
62
|
+
maxX: rect.x + rect.width,
|
|
63
|
+
maxY: rect.y + rect.height,
|
|
64
|
+
})) || [],
|
|
65
|
+
)
|
|
40
66
|
this.rectSpatialIndex.load(
|
|
41
67
|
this.input.inputMeshNodes.map((n) => ({
|
|
42
68
|
...n,
|
|
@@ -3,15 +3,21 @@ import {
|
|
|
3
3
|
definePipelineStep,
|
|
4
4
|
type PipelineStep,
|
|
5
5
|
} from "@tscircuit/solver-utils"
|
|
6
|
-
import type { SimpleRouteJson } from "lib/types/srj-types"
|
|
7
6
|
import type { CapacityMeshNode } from "lib/types/capacity-mesh-types"
|
|
8
7
|
import type { GraphicsObject } from "graphics-debug"
|
|
9
8
|
import { FindSegmentsWithAdjacentEmptySpaceSolver } from "./FindSegmentsWithAdjacentEmptySpaceSolver"
|
|
10
9
|
import { ExpandEdgesToEmptySpaceSolver } from "./ExpandEdgesToEmptySpaceSolver"
|
|
10
|
+
import type { XYRect } from "lib/rectdiff-types"
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
type GapFillSolverInput = {
|
|
13
13
|
meshNodes: CapacityMeshNode[]
|
|
14
|
-
|
|
14
|
+
boardVoid?: {
|
|
15
|
+
boardVoidRects: XYRect[]
|
|
16
|
+
layerCount: number
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class GapFillSolverPipeline extends BasePipelineSolver<GapFillSolverInput> {
|
|
15
21
|
findSegmentsWithAdjacentEmptySpaceSolver?: FindSegmentsWithAdjacentEmptySpaceSolver
|
|
16
22
|
expandEdgesToEmptySpaceSolver?: ExpandEdgesToEmptySpaceSolver
|
|
17
23
|
|
|
@@ -39,6 +45,7 @@ export class GapFillSolverPipeline extends BasePipelineSolver<{
|
|
|
39
45
|
segmentsWithAdjacentEmptySpace:
|
|
40
46
|
gapFillPipeline.findSegmentsWithAdjacentEmptySpaceSolver!.getOutput()
|
|
41
47
|
.segmentsWithAdjacentEmptySpace,
|
|
48
|
+
boardVoid: gapFillPipeline.inputProblem.boardVoid,
|
|
42
49
|
},
|
|
43
50
|
],
|
|
44
51
|
{
|
|
@@ -10,26 +10,26 @@ import { RectDiffSeedingSolver } from "lib/solvers/RectDiffSeedingSolver/RectDif
|
|
|
10
10
|
import { RectDiffExpansionSolver } from "lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver"
|
|
11
11
|
import type { GraphicsObject } from "graphics-debug"
|
|
12
12
|
import RBush from "rbush"
|
|
13
|
-
import {
|
|
13
|
+
import { buildObstacleIndexesByLayer } from "./buildObstacleIndexes"
|
|
14
14
|
|
|
15
15
|
export type RectDiffGridSolverPipelineInput = {
|
|
16
16
|
simpleRouteJson: SimpleRouteJson
|
|
17
17
|
gridOptions?: Partial<GridFill3DOptions>
|
|
18
|
+
boardVoidRects?: XYRect[]
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export class RectDiffGridSolverPipeline extends BasePipelineSolver<RectDiffGridSolverPipelineInput> {
|
|
21
22
|
rectDiffSeedingSolver?: RectDiffSeedingSolver
|
|
22
23
|
rectDiffExpansionSolver?: RectDiffExpansionSolver
|
|
23
|
-
private boardVoidRects?: XYRect[]
|
|
24
24
|
private obstacleIndexByLayer: Array<RBush<RTreeRect>>
|
|
25
25
|
|
|
26
26
|
constructor(inputProblem: RectDiffGridSolverPipelineInput) {
|
|
27
27
|
super(inputProblem)
|
|
28
|
-
const { obstacleIndexByLayer
|
|
29
|
-
inputProblem.simpleRouteJson,
|
|
30
|
-
|
|
28
|
+
const { obstacleIndexByLayer } = buildObstacleIndexesByLayer({
|
|
29
|
+
srj: inputProblem.simpleRouteJson,
|
|
30
|
+
boardVoidRects: inputProblem.boardVoidRects,
|
|
31
|
+
})
|
|
31
32
|
this.obstacleIndexByLayer = obstacleIndexByLayer
|
|
32
|
-
this.boardVoidRects = boardVoidRects
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
override pipelineDef: PipelineStep<any>[] = [
|
|
@@ -41,7 +41,7 @@ export class RectDiffGridSolverPipeline extends BasePipelineSolver<RectDiffGridS
|
|
|
41
41
|
simpleRouteJson: pipeline.inputProblem.simpleRouteJson,
|
|
42
42
|
gridOptions: pipeline.inputProblem.gridOptions,
|
|
43
43
|
obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
|
|
44
|
-
boardVoidRects: pipeline.boardVoidRects,
|
|
44
|
+
boardVoidRects: pipeline.inputProblem.boardVoidRects,
|
|
45
45
|
},
|
|
46
46
|
],
|
|
47
47
|
),
|
|
@@ -52,7 +52,7 @@ export class RectDiffGridSolverPipeline extends BasePipelineSolver<RectDiffGridS
|
|
|
52
52
|
{
|
|
53
53
|
initialSnapshot: {
|
|
54
54
|
...pipeline.rectDiffSeedingSolver!.getOutput(),
|
|
55
|
-
boardVoidRects: pipeline.boardVoidRects ?? [],
|
|
55
|
+
boardVoidRects: pipeline.inputProblem.boardVoidRects ?? [],
|
|
56
56
|
},
|
|
57
57
|
obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
|
|
58
58
|
},
|
|
@@ -9,12 +9,13 @@ import {
|
|
|
9
9
|
import type { XYRect } from "lib/rectdiff-types"
|
|
10
10
|
import type { RTreeRect } from "lib/types/capacity-mesh-types"
|
|
11
11
|
|
|
12
|
-
export const
|
|
13
|
-
srj: SimpleRouteJson
|
|
14
|
-
|
|
12
|
+
export const buildObstacleIndexesByLayer = (params: {
|
|
13
|
+
srj: SimpleRouteJson
|
|
14
|
+
boardVoidRects?: XYRect[]
|
|
15
|
+
}): {
|
|
15
16
|
obstacleIndexByLayer: Array<RBush<RTreeRect>>
|
|
16
|
-
boardVoidRects: XYRect[]
|
|
17
17
|
} => {
|
|
18
|
+
const { srj, boardVoidRects } = params
|
|
18
19
|
const { layerNames, zIndexByName } = buildZIndexMap(srj)
|
|
19
20
|
const layerCount = Math.max(1, layerNames.length, srj.layerCount || 1)
|
|
20
21
|
const bounds: XYRect = {
|
|
@@ -39,10 +40,8 @@ export const buildObstacleIndexes = (
|
|
|
39
40
|
obstacleIndexByLayer[z]?.insert(treeRect)
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
let boardVoidRects: XYRect[] = []
|
|
43
43
|
if (srj.outline && srj.outline.length > 2) {
|
|
44
|
-
|
|
45
|
-
for (const voidRect of boardVoidRects) {
|
|
44
|
+
for (const voidRect of boardVoidRects ?? []) {
|
|
46
45
|
for (let z = 0; z < layerCount; z++) insertObstacle(voidRect, z)
|
|
47
46
|
}
|
|
48
47
|
}
|
|
@@ -66,5 +65,5 @@ export const buildObstacleIndexes = (
|
|
|
66
65
|
for (const z of zLayers) insertObstacle(rect, z)
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
return { obstacleIndexByLayer
|
|
68
|
+
return { obstacleIndexByLayer }
|
|
70
69
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fixture from "../test-assets/gap-fill-h-shape-should-expand-node.json"
|
|
2
|
+
import { GapFillSolverPipeline } from "../lib/solvers/GapFillSolver/GapFillSolverPipeline"
|
|
3
|
+
import type { CapacityMeshNode } from "../lib/types/capacity-mesh-types"
|
|
4
|
+
import { useMemo } from "react"
|
|
5
|
+
import { SolverDebugger3d } from "../components/SolverDebugger3d"
|
|
6
|
+
|
|
7
|
+
export default () => {
|
|
8
|
+
const solver = useMemo(
|
|
9
|
+
() =>
|
|
10
|
+
new GapFillSolverPipeline({
|
|
11
|
+
meshNodes: fixture.meshNodes as CapacityMeshNode[],
|
|
12
|
+
}),
|
|
13
|
+
[],
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<SolverDebugger3d
|
|
18
|
+
solver={solver}
|
|
19
|
+
defaultShowOutput={true}
|
|
20
|
+
defaultShowRoot={true}
|
|
21
|
+
/>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "H-shaped configuration - two vertical nodes with a horizontal connector in the middle",
|
|
3
|
+
"expectedBehavior": "Should detect segments on all exposed edges and expand into empty spaces around the H shape",
|
|
4
|
+
"meshNodes": [
|
|
5
|
+
{
|
|
6
|
+
"capacityMeshNodeId": "node-left-vertical",
|
|
7
|
+
"center": { "x": -3, "y": 0 },
|
|
8
|
+
"width": 1,
|
|
9
|
+
"height": 6,
|
|
10
|
+
"layer": "top",
|
|
11
|
+
"availableZ": [0]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"capacityMeshNodeId": "node-right-vertical",
|
|
15
|
+
"center": { "x": 3, "y": 0 },
|
|
16
|
+
"width": 1,
|
|
17
|
+
"height": 6,
|
|
18
|
+
"layer": "top",
|
|
19
|
+
"availableZ": [0]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"capacityMeshNodeId": "node-middle-horizontal",
|
|
23
|
+
"center": { "x": 0, "y": 0 },
|
|
24
|
+
"width": 2,
|
|
25
|
+
"height": 1,
|
|
26
|
+
"layer": "top",
|
|
27
|
+
"availableZ": [0]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"expectedSegments": [
|
|
31
|
+
{
|
|
32
|
+
"description": "Left vertical - left edge",
|
|
33
|
+
"parentNodeId": "node-left-vertical",
|
|
34
|
+
"facingDirection": "x-"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"description": "Left vertical - top edge (above horizontal)",
|
|
38
|
+
"parentNodeId": "node-left-vertical",
|
|
39
|
+
"facingDirection": "y+"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"description": "Left vertical - bottom edge (below horizontal)",
|
|
43
|
+
"parentNodeId": "node-left-vertical",
|
|
44
|
+
"facingDirection": "y-"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"description": "Right vertical - right edge",
|
|
48
|
+
"parentNodeId": "node-right-vertical",
|
|
49
|
+
"facingDirection": "x+"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"description": "Right vertical - top edge (above horizontal)",
|
|
53
|
+
"parentNodeId": "node-right-vertical",
|
|
54
|
+
"facingDirection": "y+"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"description": "Right vertical - bottom edge (below horizontal)",
|
|
58
|
+
"parentNodeId": "node-right-vertical",
|
|
59
|
+
"facingDirection": "y-"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"description": "Middle horizontal - top edge",
|
|
63
|
+
"parentNodeId": "node-middle-horizontal",
|
|
64
|
+
"facingDirection": "y+"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"description": "Middle horizontal - bottom edge",
|
|
68
|
+
"parentNodeId": "node-middle-horizontal",
|
|
69
|
+
"facingDirection": "y-"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
@@ -1,19 +1,4 @@
|
|
|
1
|
-
<svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><
|
|
2
|
-
z:0,1" data-x="-5" data-y="1.1600000000000006" x="40" y="110" width="210" height="338.80000000000007" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
3
|
-
z:0,1" data-x="5" data-y="1.1600000000000006" x="390" y="110" width="210" height="338.80000000000007" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
4
|
-
z:0,1" data-x="0" data-y="4" x="250" y="110.00000000000006" width="140" height="140" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
5
|
-
z:0,1" data-x="-5.48" data-y="-5.16" x="40" y="471.20000000000005" width="176.39999999999998" height="58.799999999999955" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
6
|
-
z:0,1" data-x="5.48" data-y="-5.16" x="423.6" y="471.20000000000005" width="176.39999999999998" height="58.799999999999955" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
7
|
-
z:0,1" data-x="-2.4800000000000004" data-y="-4.84" x="216.39999999999998" y="448.80000000000007" width="33.60000000000002" height="81.19999999999993" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
8
|
-
z:0,1" data-x="2.4800000000000004" data-y="-4.84" x="390" y="448.80000000000007" width="33.60000000000002" height="81.19999999999993" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
9
|
-
z:1" data-x="-5.48" data-y="-4" x="40" y="448.80000000000007" width="176.39999999999998" height="22.399999999999977" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
10
|
-
z:1" data-x="5.48" data-y="-4" x="423.6" y="448.80000000000007" width="176.39999999999998" height="22.399999999999977" fill="#fef3c7" stroke="#f59e0b" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
11
|
-
z:0" data-x="6.890000000000001" data-y="-4" x="522.3" y="448.80000000000007" width="77.70000000000005" height="22.399999999999977" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
12
|
-
z:0" data-x="-6.89" data-y="-4" x="40" y="448.80000000000007" width="77.70000000000002" height="22.399999999999977" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
13
|
-
z:0" data-x="-5" data-y="-4" x="136.6" y="448.80000000000007" width="16.80000000000001" height="22.399999999999977" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
14
|
-
z:0" data-x="-3.5900000000000003" data-y="-4" x="172.3" y="448.80000000000007" width="44.099999999999966" height="22.399999999999977" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
15
|
-
z:0" data-x="3.5900000000000007" data-y="-4" x="423.6" y="448.80000000000007" width="44.10000000000002" height="22.399999999999977" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
|
|
16
|
-
z:0" data-x="5" data-y="-4" x="486.6" y="448.80000000000007" width="16.799999999999955" height="22.399999999999977" fill="#dbeafe" stroke="#3b82f6" stroke-width="0.02857142857142857"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
|
|
1
|
+
<svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><rect data-type="rect" data-label="node" data-x="-5" data-y="1.1600000000000008" x="40" y="110" width="210" height="338.80000000000007" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="5" data-y="1.1600000000000008" x="390" y="110" width="210" height="338.80000000000007" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="0" data-y="4" x="250" y="110.00000000000006" width="140" height="140" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="-5.48" data-y="-5.16" x="40" y="471.20000000000005" width="176.39999999999998" height="58.799999999999955" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="5.48" data-y="-5.16" x="423.6" y="471.20000000000005" width="176.39999999999998" height="58.799999999999955" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="-2.4800000000000004" data-y="-4.84" x="216.39999999999998" y="448.80000000000007" width="33.60000000000002" height="81.19999999999993" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="2.4800000000000004" data-y="-4.84" x="390" y="448.80000000000007" width="33.60000000000002" height="81.19999999999993" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="-5.48" data-y="-4" x="40" y="448.80000000000007" width="176.39999999999998" height="22.399999999999977" fill="#fef3c7" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="5.48" data-y="-4" x="423.6" y="448.80000000000007" width="176.39999999999998" height="22.399999999999977" fill="#fef3c7" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="6.890000000000001" data-y="-4" x="522.3000000000001" y="448.80000000000007" width="77.69999999999993" height="22.399999999999977" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="-6.89" data-y="-4" x="40" y="448.80000000000007" width="77.70000000000002" height="22.399999999999977" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="-5" data-y="-4" x="136.6" y="448.80000000000007" width="16.80000000000001" height="22.399999999999977" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="-3.5900000000000003" data-y="-4" x="172.3" y="448.80000000000007" width="44.099999999999966" height="22.399999999999977" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="3.5900000000000007" data-y="-4" x="423.6" y="448.80000000000007" width="44.10000000000002" height="22.399999999999977" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="5" data-y="-4" x="486.6" y="448.80000000000007" width="16.799999999999955" height="22.399999999999977" fill="#dbeafe" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="-5.51" data-y="-4" x="117.70000000000002" y="448.80000000000007" width="18.899999999999977" height="22.399999999999977" fill="red" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="-4.49" data-y="-4" x="153.4" y="448.80000000000007" width="18.899999999999977" height="22.399999999999977" fill="red" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="4.49" data-y="-4" x="467.70000000000005" y="448.80000000000007" width="18.899999999999977" height="22.399999999999977" fill="red" stroke="black" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="node" data-x="5.51" data-y="-4" x="503.4" y="448.80000000000007" width="18.899999999999977" height="22.399999999999977" fill="red" stroke="black" stroke-width="0.02857142857142857"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
|
|
17
2
|
document.currentScript.parentElement.addEventListener('mousemove', (e) => {
|
|
18
3
|
const svg = e.currentTarget;
|
|
19
4
|
const rect = svg.getBoundingClientRect();
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<svg width="640" height="480" viewBox="0 0 640 480" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><rect data-type="rect" data-label="node" data-x="-3" data-y="0" x="86.66666666666666" y="40" width="66.66666666666666" height="400" fill="#dbeafe" stroke="black" stroke-width="0.015"/></g><g><rect data-type="rect" data-label="node" data-x="3" data-y="0" x="486.6666666666667" y="40" width="66.66666666666669" height="400" fill="#dbeafe" stroke="black" stroke-width="0.015"/></g><g><rect data-type="rect" data-label="node" data-x="0" data-y="0" x="253.33333333333331" y="206.66666666666666" width="133.33333333333337" height="66.66666666666666" fill="#dbeafe" stroke="black" stroke-width="0.015"/></g><g><rect data-type="rect" data-label="node" data-x="-1.75" data-y="0" x="153.33333333333331" y="40" width="100" height="400" fill="#dbeafe" stroke="black" stroke-width="0.015"/></g><g><rect data-type="rect" data-label="node" data-x="1.75" data-y="0" x="386.6666666666667" y="40" width="100" height="400" fill="#dbeafe" stroke="black" stroke-width="0.015"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="480" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
|
|
2
|
+
document.currentScript.parentElement.addEventListener('mousemove', (e) => {
|
|
3
|
+
const svg = e.currentTarget;
|
|
4
|
+
const rect = svg.getBoundingClientRect();
|
|
5
|
+
const x = e.clientX - rect.left;
|
|
6
|
+
const y = e.clientY - rect.top;
|
|
7
|
+
const crosshair = svg.getElementById('crosshair');
|
|
8
|
+
const h = svg.getElementById('crosshair-h');
|
|
9
|
+
const v = svg.getElementById('crosshair-v');
|
|
10
|
+
const coords = svg.getElementById('coordinates');
|
|
11
|
+
|
|
12
|
+
crosshair.style.display = 'block';
|
|
13
|
+
h.setAttribute('x1', '0');
|
|
14
|
+
h.setAttribute('x2', '640');
|
|
15
|
+
h.setAttribute('y1', y);
|
|
16
|
+
h.setAttribute('y2', y);
|
|
17
|
+
v.setAttribute('x1', x);
|
|
18
|
+
v.setAttribute('x2', x);
|
|
19
|
+
v.setAttribute('y1', '0');
|
|
20
|
+
v.setAttribute('y2', '480');
|
|
21
|
+
|
|
22
|
+
// Calculate real coordinates using inverse transformation
|
|
23
|
+
const matrix = {"a":66.66666666666667,"c":0,"e":320,"b":0,"d":-66.66666666666667,"f":240};
|
|
24
|
+
// Manually invert and apply the affine transform
|
|
25
|
+
// Since we only use translate and scale, we can directly compute:
|
|
26
|
+
// x' = (x - tx) / sx
|
|
27
|
+
// y' = (y - ty) / sy
|
|
28
|
+
const sx = matrix.a;
|
|
29
|
+
const sy = matrix.d;
|
|
30
|
+
const tx = matrix.e;
|
|
31
|
+
const ty = matrix.f;
|
|
32
|
+
const realPoint = {
|
|
33
|
+
x: (x - tx) / sx,
|
|
34
|
+
y: (y - ty) / sy // Flip y back since we used negative scale
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
|
|
38
|
+
coords.setAttribute('x', (x + 5).toString());
|
|
39
|
+
coords.setAttribute('y', (y - 5).toString());
|
|
40
|
+
});
|
|
41
|
+
document.currentScript.parentElement.addEventListener('mouseleave', () => {
|
|
42
|
+
document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
|
|
43
|
+
});
|
|
44
|
+
]]></script></svg>
|
|
@@ -2,6 +2,7 @@ import { expect, test } from "bun:test"
|
|
|
2
2
|
import boardWithCutout from "../test-assets/board-with-cutout.json"
|
|
3
3
|
import { RectDiffPipeline } from "../lib/RectDiffPipeline"
|
|
4
4
|
import { getSvgFromGraphicsObject } from "graphics-debug"
|
|
5
|
+
import { makeCapacityMeshNodeWithLayerInfo } from "./fixtures/makeCapacityMeshNodeWithLayerInfo"
|
|
5
6
|
|
|
6
7
|
test("board outline snapshot", async () => {
|
|
7
8
|
const solver = new RectDiffPipeline({
|
|
@@ -11,9 +12,15 @@ test("board outline snapshot", async () => {
|
|
|
11
12
|
// Run to completion
|
|
12
13
|
solver.solve()
|
|
13
14
|
|
|
14
|
-
const
|
|
15
|
-
solver
|
|
16
|
-
|
|
15
|
+
const meshNodesGraphics = makeCapacityMeshNodeWithLayerInfo(
|
|
16
|
+
solver?.getOutput().meshNodes || [],
|
|
17
|
+
)
|
|
18
|
+
.values()
|
|
19
|
+
.toArray()
|
|
20
|
+
.flat()
|
|
21
|
+
const svg = getSvgFromGraphicsObject({
|
|
22
|
+
rects: meshNodesGraphics,
|
|
23
|
+
})
|
|
17
24
|
|
|
18
25
|
await expect(svg).toMatchSvgSnapshot(import.meta.path)
|
|
19
26
|
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import middleGapFixture from "test-assets/gap-fill-h-shape-should-expand-node.json"
|
|
3
|
+
import { getSvgFromGraphicsObject } from "graphics-debug"
|
|
4
|
+
import { GapFillSolverPipeline } from "lib/solvers/GapFillSolver/GapFillSolverPipeline"
|
|
5
|
+
import type { CapacityMeshNode } from "lib/types/capacity-mesh-types"
|
|
6
|
+
import { makeCapacityMeshNodeWithLayerInfo } from "./fixtures/makeCapacityMeshNodeWithLayerInfo"
|
|
7
|
+
|
|
8
|
+
test("should expand capacityMeshNode to fill the gap", async () => {
|
|
9
|
+
const solver = new GapFillSolverPipeline({
|
|
10
|
+
meshNodes: middleGapFixture.meshNodes as CapacityMeshNode[],
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
solver.solve()
|
|
14
|
+
|
|
15
|
+
const { outputNodes } = solver.getOutput()
|
|
16
|
+
|
|
17
|
+
expect(outputNodes.length).toBeGreaterThanOrEqual(
|
|
18
|
+
middleGapFixture.meshNodes.length,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const finalGraphics = makeCapacityMeshNodeWithLayerInfo(outputNodes)
|
|
22
|
+
const svg = getSvgFromGraphicsObject(
|
|
23
|
+
{ rects: finalGraphics.values().toArray().flat() },
|
|
24
|
+
{
|
|
25
|
+
svgWidth: 640,
|
|
26
|
+
svgHeight: 480,
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
// More means we have added new nodes to fill the gap
|
|
31
|
+
// expect(outputNodes.length).toEqual(3)
|
|
32
|
+
|
|
33
|
+
await expect(svg).toMatchSvgSnapshot(import.meta.path)
|
|
34
|
+
})
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import type { GraphicsObject, Line, Point, Rect } from "graphics-debug"
|
|
2
|
-
|
|
3
|
-
export function getPerLayerVisualizations(
|
|
4
|
-
graphics: GraphicsObject,
|
|
5
|
-
): Map<string, GraphicsObject> {
|
|
6
|
-
const rects = (graphics.rects ?? []) as NonNullable<Rect[]>
|
|
7
|
-
const lines = (graphics.lines ?? []) as NonNullable<Line[]>
|
|
8
|
-
const points = (graphics.points ?? []) as NonNullable<Point[]>
|
|
9
|
-
|
|
10
|
-
const zValues = new Set<number>()
|
|
11
|
-
|
|
12
|
-
const addZValuesFromLayer = (layer: string) => {
|
|
13
|
-
if (!layer.startsWith("z")) return
|
|
14
|
-
const rest = layer.slice(1)
|
|
15
|
-
if (!rest) return
|
|
16
|
-
for (const part of rest.split(",")) {
|
|
17
|
-
const value = Number.parseInt(part, 10)
|
|
18
|
-
if (!Number.isNaN(value)) zValues.add(value)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
for (const rect of rects) addZValuesFromLayer(rect.layer!)
|
|
23
|
-
for (const line of lines) addZValuesFromLayer(line.layer!)
|
|
24
|
-
for (const point of points) addZValuesFromLayer(point.layer!)
|
|
25
|
-
|
|
26
|
-
const result = new Map<string, GraphicsObject>()
|
|
27
|
-
if (!zValues.size) return result
|
|
28
|
-
|
|
29
|
-
const sortedZ = Array.from(zValues).sort((a, b) => a - b)
|
|
30
|
-
|
|
31
|
-
const commonRects: NonNullable<Rect[]> = []
|
|
32
|
-
const perLayerRects: { layers: number[]; rect: Rect }[] = []
|
|
33
|
-
|
|
34
|
-
for (const rect of rects) {
|
|
35
|
-
const layer = rect.layer!
|
|
36
|
-
if (layer.startsWith("z")) {
|
|
37
|
-
const rest = layer.slice(1)
|
|
38
|
-
if (rest) {
|
|
39
|
-
const layers = rest
|
|
40
|
-
.split(",")
|
|
41
|
-
.map((part) => Number.parseInt(part, 10))
|
|
42
|
-
.filter((value) => !Number.isNaN(value))
|
|
43
|
-
if (layers.length) {
|
|
44
|
-
perLayerRects.push({ layers, rect })
|
|
45
|
-
continue
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
commonRects.push(rect)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const commonLines: NonNullable<Line[]> = []
|
|
53
|
-
const perLayerLines: { layers: number[]; line: Line }[] = []
|
|
54
|
-
|
|
55
|
-
for (const line of lines) {
|
|
56
|
-
const layer = line.layer!
|
|
57
|
-
if (layer.startsWith("z")) {
|
|
58
|
-
const rest = layer.slice(1)
|
|
59
|
-
if (rest) {
|
|
60
|
-
const layers = rest
|
|
61
|
-
.split(",")
|
|
62
|
-
.map((part) => Number.parseInt(part, 10))
|
|
63
|
-
.filter((value) => !Number.isNaN(value))
|
|
64
|
-
if (layers.length) {
|
|
65
|
-
perLayerLines.push({ layers, line })
|
|
66
|
-
continue
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
commonLines.push(line)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const commonPoints: NonNullable<Point[]> = []
|
|
74
|
-
const perLayerPoints: { layers: number[]; point: Point }[] = []
|
|
75
|
-
|
|
76
|
-
for (const point of points) {
|
|
77
|
-
const layer = point.layer!
|
|
78
|
-
if (layer.startsWith("z")) {
|
|
79
|
-
const rest = layer.slice(1)
|
|
80
|
-
if (rest) {
|
|
81
|
-
const layers = rest
|
|
82
|
-
.split(",")
|
|
83
|
-
.map((part) => Number.parseInt(part, 10))
|
|
84
|
-
.filter((value) => !Number.isNaN(value))
|
|
85
|
-
if (layers.length) {
|
|
86
|
-
perLayerPoints.push({ layers, point })
|
|
87
|
-
continue
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
commonPoints.push(point)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const allCombos: number[][] = [[]]
|
|
95
|
-
for (const z of sortedZ) {
|
|
96
|
-
const withZ = allCombos.map((combo) => [...combo, z])
|
|
97
|
-
allCombos.push(...withZ)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
for (const combo of allCombos.filter((c) => c.length > 0)) {
|
|
101
|
-
const key = `z${combo.join(",")}`
|
|
102
|
-
|
|
103
|
-
const layerRects: NonNullable<Rect[]> = [...commonRects]
|
|
104
|
-
const layerLines: NonNullable<Line[]> = [...commonLines]
|
|
105
|
-
const layerPoints: NonNullable<Point[]> = [...commonPoints]
|
|
106
|
-
|
|
107
|
-
const intersects = (layers: number[]) =>
|
|
108
|
-
layers.some((layer) => combo.includes(layer))
|
|
109
|
-
|
|
110
|
-
for (const { layers, rect } of perLayerRects) {
|
|
111
|
-
if (intersects(layers)) layerRects.push(rect)
|
|
112
|
-
}
|
|
113
|
-
for (const { layers, line } of perLayerLines) {
|
|
114
|
-
if (intersects(layers)) layerLines.push(line)
|
|
115
|
-
}
|
|
116
|
-
for (const { layers, point } of perLayerPoints) {
|
|
117
|
-
if (intersects(layers)) layerPoints.push(point)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
result.set(key, {
|
|
121
|
-
title: `${graphics.title ?? ""} - z${combo.join(",")}`,
|
|
122
|
-
coordinateSystem: graphics.coordinateSystem,
|
|
123
|
-
rects: layerRects,
|
|
124
|
-
lines: layerLines,
|
|
125
|
-
points: layerPoints,
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return result
|
|
130
|
-
}
|