@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 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
- declare class GapFillSolverPipeline extends BasePipelineSolver<{
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 buildObstacleIndexes = (srj) => {
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 = computeInverseRects(bounds, srj.outline);
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, boardVoidRects };
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, boardVoidRects } = buildObstacleIndexes(
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
  }
@@ -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
- export class GapFillSolverPipeline extends BasePipelineSolver<{
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 { buildObstacleIndexes } from "./buildObstacleIndexes"
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, boardVoidRects } = buildObstacleIndexes(
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 buildObstacleIndexes = (
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
- boardVoidRects = computeInverseRects(bounds, srj.outline as any)
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, boardVoidRects }
68
+ return { obstacleIndexByLayer }
70
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/rectdiff",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -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><polyline data-points="-8,-6 -2,-6 -2,2 2,2 2,-6 8,-6 8,6 -8,6 -8,-6" data-type="line" data-label="outline" points="40,530 250,530 250,250.00000000000006 390,250.00000000000006 390,530 600,530 600,110.00000000000006 40,110.00000000000006 40,530" fill="none" stroke="#111827" stroke-width="0.35000000000000003"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-5.51" data-y="-4" x="117.70000000000002" y="448.80000000000007" width="18.899999999999977" height="22.399999999999977" fill="#fee2e2" stroke="#ef4444" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="-4.49" data-y="-4" x="153.4" y="448.80000000000007" width="18.899999999999977" height="22.399999999999977" fill="#fee2e2" stroke="#ef4444" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="4.49" data-y="-4" x="467.70000000000005" y="448.80000000000007" width="18.899999999999977" height="22.399999999999977" fill="#fee2e2" stroke="#ef4444" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="obstacle" data-x="5.51" data-y="-4" x="503.4" y="448.80000000000007" width="18.899999999999977" height="22.399999999999977" fill="#fee2e2" stroke="#ef4444" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="void" data-x="0" data-y="-2" x="250" y="250.00000000000006" width="140" height="279.99999999999994" fill="rgba(0, 0, 0, 0.5)" stroke="none" stroke-width="0.02857142857142857"/></g><g><rect data-type="rect" data-label="free
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 viz =
15
- solver.rectDiffGridSolverPipeline!.rectDiffSeedingSolver!.visualize()
16
- const svg = getSvgFromGraphicsObject(viz)
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
- }