@tscircuit/rectdiff 0.0.12 → 0.0.14

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.
Files changed (37) hide show
  1. package/dist/index.d.ts +169 -27
  2. package/dist/index.js +2012 -1672
  3. package/lib/RectDiffPipeline.ts +18 -17
  4. package/lib/{solvers/rectdiff/types.ts → rectdiff-types.ts} +0 -34
  5. package/lib/{solvers/rectdiff/visualization.ts → rectdiff-visualization.ts} +2 -1
  6. package/lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts +9 -12
  7. package/lib/solvers/GapFillSolver/visuallyOffsetLine.ts +5 -2
  8. package/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts +252 -0
  9. package/lib/solvers/{rectdiff → RectDiffExpansionSolver}/rectsToMeshNodes.ts +1 -2
  10. package/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts +106 -0
  11. package/lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts +70 -0
  12. package/lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts +487 -0
  13. package/lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts +109 -0
  14. package/lib/solvers/RectDiffSeedingSolver/computeDefaultGridSizes.ts +9 -0
  15. package/lib/solvers/{rectdiff/candidates.ts → RectDiffSeedingSolver/computeEdgeCandidates3D.ts} +44 -225
  16. package/lib/solvers/{rectdiff/geometry → RectDiffSeedingSolver}/computeInverseRects.ts +6 -2
  17. package/lib/solvers/{rectdiff → RectDiffSeedingSolver}/layers.ts +4 -3
  18. package/lib/solvers/RectDiffSeedingSolver/longestFreeSpanAroundZ.ts +60 -0
  19. package/lib/types/capacity-mesh-types.ts +9 -0
  20. package/lib/utils/buildHardPlacedByLayer.ts +14 -0
  21. package/lib/{solvers/rectdiff/geometry.ts → utils/expandRectFromSeed.ts} +21 -120
  22. package/lib/utils/finalizeRects.ts +54 -0
  23. package/lib/utils/isFullyOccupiedAtPoint.ts +28 -0
  24. package/lib/utils/rectToTree.ts +10 -0
  25. package/lib/utils/rectdiff-geometry.ts +94 -0
  26. package/lib/utils/resizeSoftOverlaps.ts +103 -0
  27. package/lib/utils/sameTreeRect.ts +7 -0
  28. package/package.json +1 -1
  29. package/tests/board-outline.test.ts +2 -1
  30. package/tests/examples/example01.test.tsx +18 -1
  31. package/tests/obstacle-extra-layers.test.ts +1 -1
  32. package/tests/obstacle-zlayers.test.ts +1 -1
  33. package/utils/rectsEqual.ts +2 -2
  34. package/utils/rectsOverlap.ts +2 -2
  35. package/lib/solvers/RectDiffSolver.ts +0 -231
  36. package/lib/solvers/rectdiff/engine.ts +0 -481
  37. /package/lib/solvers/{rectdiff/geometry → RectDiffSeedingSolver}/isPointInPolygon.ts +0 -0
@@ -1,64 +1,15 @@
1
- // lib/solvers/rectdiff/geometry.ts
2
- import type { XYRect } from "./types"
1
+ import type { XYRect } from "../rectdiff-types"
2
+ import { EPS, gt, gte, lt, lte, overlaps } from "./rectdiff-geometry"
3
3
 
4
- export const EPS = 1e-9
5
- export const clamp = (v: number, lo: number, hi: number) =>
6
- Math.max(lo, Math.min(hi, v))
7
- export const gt = (a: number, b: number) => a > b + EPS
8
- export const gte = (a: number, b: number) => a > b - EPS
9
- export const lt = (a: number, b: number) => a < b - EPS
10
- export const lte = (a: number, b: number) => a < b + EPS
11
-
12
- export function overlaps(a: XYRect, b: XYRect) {
13
- return !(
14
- a.x + a.width <= b.x + EPS ||
15
- b.x + b.width <= a.x + EPS ||
16
- a.y + a.height <= b.y + EPS ||
17
- b.y + b.height <= a.y + EPS
18
- )
19
- }
20
-
21
- export function containsPoint(r: XYRect, x: number, y: number) {
22
- return (
23
- x >= r.x - EPS &&
24
- x <= r.x + r.width + EPS &&
25
- y >= r.y - EPS &&
26
- y <= r.y + r.height + EPS
27
- )
28
- }
29
-
30
- export function distancePointToRectEdges(px: number, py: number, r: XYRect) {
31
- const edges: [number, number, number, number][] = [
32
- [r.x, r.y, r.x + r.width, r.y],
33
- [r.x + r.width, r.y, r.x + r.width, r.y + r.height],
34
- [r.x + r.width, r.y + r.height, r.x, r.y + r.height],
35
- [r.x, r.y + r.height, r.x, r.y],
36
- ]
37
- let best = Infinity
38
- for (const [x1, y1, x2, y2] of edges) {
39
- const A = px - x1,
40
- B = py - y1,
41
- C = x2 - x1,
42
- D = y2 - y1
43
- const dot = A * C + B * D
44
- const lenSq = C * C + D * D
45
- let t = lenSq !== 0 ? dot / lenSq : 0
46
- t = clamp(t, 0, 1)
47
- const xx = x1 + t * C
48
- const yy = y1 + t * D
49
- best = Math.min(best, Math.hypot(px - xx, py - yy))
50
- }
51
- return best
4
+ type ExpandDirectionParams = {
5
+ r: XYRect
6
+ bounds: XYRect
7
+ blockers: XYRect[]
8
+ maxAspect: number | null | undefined
52
9
  }
53
10
 
54
- // --- directional expansion caps (respect board + blockers + aspect) ---
55
-
56
- function maxExpandRight(
57
- r: XYRect,
58
- bounds: XYRect,
59
- blockers: XYRect[],
60
- maxAspect: number | null | undefined,
61
- ) {
11
+ function maxExpandRight(params: ExpandDirectionParams) {
12
+ const { r, bounds, blockers, maxAspect } = params
62
13
  // Start with board boundary
63
14
  let maxWidth = bounds.x + bounds.width - r.x
64
15
 
@@ -94,12 +45,8 @@ function maxExpandRight(
94
45
  return Math.max(0, e)
95
46
  }
96
47
 
97
- function maxExpandDown(
98
- r: XYRect,
99
- bounds: XYRect,
100
- blockers: XYRect[],
101
- maxAspect: number | null | undefined,
102
- ) {
48
+ function maxExpandDown(params: ExpandDirectionParams) {
49
+ const { r, bounds, blockers, maxAspect } = params
103
50
  // Start with board boundary
104
51
  let maxHeight = bounds.y + bounds.height - r.y
105
52
 
@@ -134,12 +81,8 @@ function maxExpandDown(
134
81
  return Math.max(0, e)
135
82
  }
136
83
 
137
- function maxExpandLeft(
138
- r: XYRect,
139
- bounds: XYRect,
140
- blockers: XYRect[],
141
- maxAspect: number | null | undefined,
142
- ) {
84
+ function maxExpandLeft(params: ExpandDirectionParams) {
85
+ const { r, bounds, blockers, maxAspect } = params
143
86
  // Start with board boundary
144
87
  let minX = bounds.x
145
88
 
@@ -172,12 +115,8 @@ function maxExpandLeft(
172
115
  return Math.max(0, e)
173
116
  }
174
117
 
175
- function maxExpandUp(
176
- r: XYRect,
177
- bounds: XYRect,
178
- blockers: XYRect[],
179
- maxAspect: number | null | undefined,
180
- ) {
118
+ function maxExpandUp(params: ExpandDirectionParams) {
119
+ const { r, bounds, blockers, maxAspect } = params
181
120
  // Start with board boundary
182
121
  let minY = bounds.y
183
122
 
@@ -271,25 +210,27 @@ export function expandRectFromSeed(params: {
271
210
  let improved = true
272
211
  while (improved) {
273
212
  improved = false
274
- const eR = maxExpandRight(r, bounds, blockers, maxAspectRatio)
213
+ const commonParams = { bounds, blockers, maxAspect: maxAspectRatio }
214
+
215
+ const eR = maxExpandRight({ ...commonParams, r })
275
216
  if (eR > 0) {
276
217
  r = { ...r, width: r.width + eR }
277
218
  improved = true
278
219
  }
279
220
 
280
- const eD = maxExpandDown(r, bounds, blockers, maxAspectRatio)
221
+ const eD = maxExpandDown({ ...commonParams, r })
281
222
  if (eD > 0) {
282
223
  r = { ...r, height: r.height + eD }
283
224
  improved = true
284
225
  }
285
226
 
286
- const eL = maxExpandLeft(r, bounds, blockers, maxAspectRatio)
227
+ const eL = maxExpandLeft({ ...commonParams, r })
287
228
  if (eL > 0) {
288
229
  r = { x: r.x - eL, y: r.y, width: r.width + eL, height: r.height }
289
230
  improved = true
290
231
  }
291
232
 
292
- const eU = maxExpandUp(r, bounds, blockers, maxAspectRatio)
233
+ const eU = maxExpandUp({ ...commonParams, r })
293
234
  if (eU > 0) {
294
235
  r = { x: r.x, y: r.y - eU, width: r.width, height: r.height + eU }
295
236
  improved = true
@@ -307,43 +248,3 @@ export function expandRectFromSeed(params: {
307
248
 
308
249
  return best
309
250
  }
310
-
311
- /** Find the intersection of two 1D intervals, or null if they don't overlap. */
312
- export function intersect1D(a0: number, a1: number, b0: number, b1: number) {
313
- const lo = Math.max(a0, b0)
314
- const hi = Math.min(a1, b1)
315
- return hi > lo + EPS ? ([lo, hi] as const) : null
316
- }
317
-
318
- /** Return A \ B as up to 4 non-overlapping rectangles (or [A] if no overlap). */
319
- export function subtractRect2D(A: XYRect, B: XYRect): XYRect[] {
320
- if (!overlaps(A, B)) return [A]
321
-
322
- const Xi = intersect1D(A.x, A.x + A.width, B.x, B.x + B.width)
323
- const Yi = intersect1D(A.y, A.y + A.height, B.y, B.y + B.height)
324
- if (!Xi || !Yi) return [A]
325
-
326
- const [X0, X1] = Xi
327
- const [Y0, Y1] = Yi
328
- const out: XYRect[] = []
329
-
330
- // Left strip
331
- if (X0 > A.x + EPS) {
332
- out.push({ x: A.x, y: A.y, width: X0 - A.x, height: A.height })
333
- }
334
- // Right strip
335
- if (A.x + A.width > X1 + EPS) {
336
- out.push({ x: X1, y: A.y, width: A.x + A.width - X1, height: A.height })
337
- }
338
- // Top wedge in the middle band
339
- const midW = Math.max(0, X1 - X0)
340
- if (midW > EPS && Y0 > A.y + EPS) {
341
- out.push({ x: X0, y: A.y, width: midW, height: Y0 - A.y })
342
- }
343
- // Bottom wedge in the middle band
344
- if (midW > EPS && A.y + A.height > Y1 + EPS) {
345
- out.push({ x: X0, y: Y1, width: midW, height: A.y + A.height - Y1 })
346
- }
347
-
348
- return out.filter((r) => r.width > EPS && r.height > EPS)
349
- }
@@ -0,0 +1,54 @@
1
+ import type { Placed3D, Rect3d, XYRect } from "../rectdiff-types"
2
+ import type { SimpleRouteJson } from "../types/srj-types"
3
+ import {
4
+ buildZIndexMap,
5
+ obstacleToXYRect,
6
+ obstacleZs,
7
+ } from "../solvers/RectDiffSeedingSolver/layers"
8
+
9
+ export function finalizeRects(params: {
10
+ placed: Placed3D[]
11
+ srj: SimpleRouteJson
12
+ boardVoidRects: XYRect[]
13
+ }): Rect3d[] {
14
+ // Convert all placed (free space) nodes to output format
15
+ const out: Rect3d[] = params.placed.map((p) => ({
16
+ minX: p.rect.x,
17
+ minY: p.rect.y,
18
+ maxX: p.rect.x + p.rect.width,
19
+ maxY: p.rect.y + p.rect.height,
20
+ zLayers: [...p.zLayers].sort((a, b) => a - b),
21
+ }))
22
+
23
+ const { zIndexByName } = buildZIndexMap(params.srj)
24
+ const layersByKey = new Map<string, { rect: XYRect; layers: Set<number> }>()
25
+
26
+ for (const obstacle of params.srj.obstacles ?? []) {
27
+ const rect = obstacleToXYRect(obstacle as any)
28
+ if (!rect) continue
29
+ const zLayers =
30
+ obstacle.zLayers?.length && obstacle.zLayers.length > 0
31
+ ? obstacle.zLayers
32
+ : obstacleZs(obstacle as any, zIndexByName)
33
+ const key = `${rect.x}:${rect.y}:${rect.width}:${rect.height}`
34
+ let entry = layersByKey.get(key)
35
+ if (!entry) {
36
+ entry = { rect, layers: new Set() }
37
+ layersByKey.set(key, entry)
38
+ }
39
+ zLayers.forEach((layer) => entry!.layers.add(layer))
40
+ }
41
+
42
+ for (const { rect, layers } of layersByKey.values()) {
43
+ out.push({
44
+ minX: rect.x,
45
+ minY: rect.y,
46
+ maxX: rect.x + rect.width,
47
+ maxY: rect.y + rect.height,
48
+ zLayers: Array.from(layers).sort((a, b) => a - b),
49
+ isObstacle: true,
50
+ })
51
+ }
52
+
53
+ return out
54
+ }
@@ -0,0 +1,28 @@
1
+ import type { RTreeRect } from "lib/types/capacity-mesh-types"
2
+ import RBush from "rbush"
3
+
4
+ export type OccupancyParams = {
5
+ layerCount: number
6
+ obstacleIndexByLayer: Array<RBush<RTreeRect> | undefined>
7
+ placedIndexByLayer: Array<RBush<RTreeRect> | undefined>
8
+ point: { x: number; y: number }
9
+ }
10
+
11
+ export function isFullyOccupiedAtPoint(params: OccupancyParams): boolean {
12
+ const query = {
13
+ minX: params.point.x,
14
+ minY: params.point.y,
15
+ maxX: params.point.x,
16
+ maxY: params.point.y,
17
+ }
18
+ for (let z = 0; z < params.layerCount; z++) {
19
+ const obstacleIdx = params.obstacleIndexByLayer[z]
20
+ const hasObstacle = !!obstacleIdx && obstacleIdx.search(query).length > 0
21
+
22
+ const placedIdx = params.placedIndexByLayer[z]
23
+ const hasPlaced = !!placedIdx && placedIdx.search(query).length > 0
24
+
25
+ if (!hasObstacle && !hasPlaced) return false
26
+ }
27
+ return true
28
+ }
@@ -0,0 +1,10 @@
1
+ import type { XYRect } from "lib/rectdiff-types"
2
+ import type { RTreeRect } from "lib/types/capacity-mesh-types"
3
+
4
+ export const rectToTree = (rect: XYRect): RTreeRect => ({
5
+ ...rect,
6
+ minX: rect.x,
7
+ minY: rect.y,
8
+ maxX: rect.x + rect.width,
9
+ maxY: rect.y + rect.height,
10
+ })
@@ -0,0 +1,94 @@
1
+ import type { XYRect } from "../rectdiff-types"
2
+
3
+ export const EPS = 1e-9
4
+ export const clamp = (v: number, lo: number, hi: number) =>
5
+ Math.max(lo, Math.min(hi, v))
6
+ export const gt = (a: number, b: number) => a > b + EPS
7
+ export const gte = (a: number, b: number) => a > b - EPS
8
+ export const lt = (a: number, b: number) => a < b - EPS
9
+ export const lte = (a: number, b: number) => a < b + EPS
10
+
11
+ export function overlaps(a: XYRect, b: XYRect) {
12
+ return !(
13
+ a.x + a.width <= b.x + EPS ||
14
+ b.x + b.width <= a.x + EPS ||
15
+ a.y + a.height <= b.y + EPS ||
16
+ b.y + b.height <= a.y + EPS
17
+ )
18
+ }
19
+
20
+ export function containsPoint(r: XYRect, p: { x: number; y: number }) {
21
+ return (
22
+ p.x >= r.x - EPS &&
23
+ p.x <= r.x + r.width + EPS &&
24
+ p.y >= r.y - EPS &&
25
+ p.y <= r.y + r.height + EPS
26
+ )
27
+ }
28
+
29
+ export function distancePointToRectEdges(
30
+ p: { x: number; y: number },
31
+ r: XYRect,
32
+ ) {
33
+ const edges: [number, number, number, number][] = [
34
+ [r.x, r.y, r.x + r.width, r.y],
35
+ [r.x + r.width, r.y, r.x + r.width, r.y + r.height],
36
+ [r.x + r.width, r.y + r.height, r.x, r.y + r.height],
37
+ [r.x, r.y + r.height, r.x, r.y],
38
+ ]
39
+ let best = Infinity
40
+ for (const [x1, y1, x2, y2] of edges) {
41
+ const A = p.x - x1,
42
+ B = p.y - y1,
43
+ C = x2 - x1,
44
+ D = y2 - y1
45
+ const dot = A * C + B * D
46
+ const lenSq = C * C + D * D
47
+ let t = lenSq !== 0 ? dot / lenSq : 0
48
+ t = clamp(t, 0, 1)
49
+ const xx = x1 + t * C
50
+ const yy = y1 + t * D
51
+ best = Math.min(best, Math.hypot(p.x - xx, p.y - yy))
52
+ }
53
+ return best
54
+ }
55
+
56
+ /** Find the intersection of two 1D intervals, or null if they don't overlap. */
57
+ export function intersect1D(r1: [number, number], r2: [number, number]) {
58
+ const lo = Math.max(r1[0], r2[0])
59
+ const hi = Math.min(r1[1], r2[1])
60
+ return hi > lo + EPS ? ([lo, hi] as const) : null
61
+ }
62
+
63
+ /** Return A \ B as up to 4 non-overlapping rectangles (or [A] if no overlap). */
64
+ export function subtractRect2D(A: XYRect, B: XYRect): XYRect[] {
65
+ if (!overlaps(A, B)) return [A]
66
+
67
+ const Xi = intersect1D([A.x, A.x + A.width], [B.x, B.x + B.width])
68
+ const Yi = intersect1D([A.y, A.y + A.height], [B.y, B.y + B.height])
69
+ if (!Xi || !Yi) return [A]
70
+
71
+ const [X0, X1] = Xi
72
+ const [Y0, Y1] = Yi
73
+ const out: XYRect[] = []
74
+
75
+ // Left strip
76
+ if (X0 > A.x + EPS) {
77
+ out.push({ x: A.x, y: A.y, width: X0 - A.x, height: A.height })
78
+ }
79
+ // Right strip
80
+ if (A.x + A.width > X1 + EPS) {
81
+ out.push({ x: X1, y: A.y, width: A.x + A.width - X1, height: A.height })
82
+ }
83
+ // Top wedge in the middle band
84
+ const midW = Math.max(0, X1 - X0)
85
+ if (midW > EPS && Y0 > A.y + EPS) {
86
+ out.push({ x: X0, y: A.y, width: midW, height: Y0 - A.y })
87
+ }
88
+ // Bottom wedge in the middle band
89
+ if (midW > EPS && A.y + A.height > Y1 + EPS) {
90
+ out.push({ x: X0, y: Y1, width: midW, height: A.y + A.height - Y1 })
91
+ }
92
+
93
+ return out.filter((r) => r.width > EPS && r.height > EPS)
94
+ }
@@ -0,0 +1,103 @@
1
+ import type { RTreeRect } from "lib/types/capacity-mesh-types"
2
+ import type { Placed3D, XYRect } from "../rectdiff-types"
3
+ import { overlaps, subtractRect2D, EPS } from "./rectdiff-geometry"
4
+ import type RBush from "rbush"
5
+
6
+ export function resizeSoftOverlaps(
7
+ params: {
8
+ layerCount: number
9
+ placed: Placed3D[]
10
+ options: any
11
+ placedIndexByLayer?: Array<RBush<RTreeRect> | undefined>
12
+ },
13
+ newIndex: number,
14
+ ) {
15
+ const newcomer = params.placed[newIndex]!
16
+ const { rect: newR, zLayers: newZs } = newcomer
17
+ const layerCount = params.layerCount
18
+
19
+ const removeIdx: number[] = []
20
+ const toAdd: typeof params.placed = []
21
+
22
+ for (let i = 0; i < params.placed.length; i++) {
23
+ if (i === newIndex) continue
24
+ const old = params.placed[i]!
25
+ // Protect full-stack nodes
26
+ if (old.zLayers.length >= layerCount) continue
27
+
28
+ const sharedZ = old.zLayers.filter((z) => newZs.includes(z))
29
+ if (sharedZ.length === 0) continue
30
+ if (!overlaps(old.rect, newR)) continue
31
+
32
+ // Carve the overlap on the shared layers
33
+ const parts = subtractRect2D(old.rect, newR)
34
+
35
+ // We will replace `old` entirely; re-add unaffected layers (same rect object).
36
+ removeIdx.push(i)
37
+
38
+ const unaffectedZ = old.zLayers.filter((z) => !newZs.includes(z))
39
+ if (unaffectedZ.length > 0) {
40
+ toAdd.push({ rect: old.rect, zLayers: unaffectedZ })
41
+ }
42
+
43
+ // Re-add carved pieces for affected layers, dropping tiny slivers
44
+ const minW = Math.min(
45
+ params.options.minSingle.width,
46
+ params.options.minMulti.width,
47
+ )
48
+ const minH = Math.min(
49
+ params.options.minSingle.height,
50
+ params.options.minMulti.height,
51
+ )
52
+ for (const p of parts) {
53
+ if (p.width + EPS >= minW && p.height + EPS >= minH) {
54
+ toAdd.push({ rect: p, zLayers: sharedZ.slice() })
55
+ }
56
+ }
57
+ }
58
+
59
+ // Remove fully overlapped nodes and keep indexes in sync
60
+ const rectToTree = (rect: XYRect): RTreeRect => ({
61
+ ...rect,
62
+ minX: rect.x,
63
+ minY: rect.y,
64
+ maxX: rect.x + rect.width,
65
+ maxY: rect.y + rect.height,
66
+ })
67
+ const sameRect = (a: RTreeRect, b: RTreeRect) =>
68
+ a.minX === b.minX &&
69
+ a.minY === b.minY &&
70
+ a.maxX === b.maxX &&
71
+ a.maxY === b.maxY
72
+
73
+ removeIdx
74
+ .sort((a, b) => b - a)
75
+ .forEach((idx) => {
76
+ const rem = params.placed.splice(idx, 1)[0]!
77
+ if (params.placedIndexByLayer) {
78
+ for (const z of rem.zLayers) {
79
+ const tree = params.placedIndexByLayer[z]
80
+ if (tree) tree.remove(rectToTree(rem.rect), sameRect)
81
+ }
82
+ }
83
+ })
84
+
85
+ // Add replacements
86
+ for (const p of toAdd) {
87
+ params.placed.push(p)
88
+ for (const z of p.zLayers) {
89
+ if (params.placedIndexByLayer) {
90
+ const idx = params.placedIndexByLayer[z]
91
+ if (idx) {
92
+ idx.insert({
93
+ ...p.rect,
94
+ minX: p.rect.x,
95
+ minY: p.rect.y,
96
+ maxX: p.rect.x + p.rect.width,
97
+ maxY: p.rect.y + p.rect.height,
98
+ })
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,7 @@
1
+ import type { RTreeRect } from "lib/types/capacity-mesh-types"
2
+
3
+ export const sameTreeRect = (a: RTreeRect, b: RTreeRect) =>
4
+ a.minX === b.minX &&
5
+ a.minY === b.minY &&
6
+ a.maxX === b.maxX &&
7
+ a.maxY === b.maxY
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/rectdiff",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -11,7 +11,8 @@ test("board outline snapshot", async () => {
11
11
  // Run to completion
12
12
  solver.solve()
13
13
 
14
- const viz = solver.rectDiffSolver!.visualize()
14
+ const viz =
15
+ solver.rectDiffGridSolverPipeline!.rectDiffSeedingSolver!.visualize()
15
16
  const svg = getSvgFromGraphicsObject(viz)
16
17
 
17
18
  await expect(svg).toMatchSvgSnapshot(import.meta.path)
@@ -2,6 +2,11 @@ import { expect, test } from "bun:test"
2
2
  import simpleRouteJson from "../../test-assets/example01.json"
3
3
  import { RectDiffPipeline } from "../../lib/RectDiffPipeline"
4
4
  import { getSvgFromGraphicsObject } from "graphics-debug"
5
+ import {
6
+ buildZIndexMap,
7
+ obstacleToXYRect,
8
+ obstacleZs,
9
+ } from "lib/solvers/RectDiffSeedingSolver/layers"
5
10
 
6
11
  test.skip("example01", () => {
7
12
  const solver = new RectDiffPipeline({ simpleRouteJson })
@@ -17,7 +22,19 @@ test.skip("example01", () => {
17
22
  const step = 0.004
18
23
  const layerCount = simpleRouteJson.layerCount || 2
19
24
  const state = (solver as any).state
20
- const obstacles = state.obstaclesByLayer
25
+ const { zIndexByName } = buildZIndexMap(simpleRouteJson as any)
26
+ const obstacles = Array.from({ length: layerCount }, () => [] as any[])
27
+ for (const obstacle of simpleRouteJson.obstacles ?? []) {
28
+ const rect = obstacleToXYRect(obstacle as any)
29
+ if (!rect) continue
30
+ const zLayers =
31
+ obstacle.zLayers?.length && obstacle.zLayers.length > 0
32
+ ? obstacle.zLayers
33
+ : obstacleZs(obstacle as any, zIndexByName)
34
+ zLayers.forEach((z: number) => {
35
+ if (z >= 0 && z < layerCount) obstacles[z]!.push(rect)
36
+ })
37
+ }
21
38
  const placed = state.placed
22
39
 
23
40
  const gapPoints: Array<{ x: number; y: number; z: number }> = []
@@ -40,5 +40,5 @@ test("RectDiffSolver clamps extra layer names to available z indices", () => {
40
40
  expect(output.meshNodes.length).toBeGreaterThan(0)
41
41
 
42
42
  // Verify solver was instantiated and processed obstacles
43
- expect(pipeline.rectDiffSolver).toBeDefined()
43
+ expect(pipeline.rectDiffGridSolverPipeline).toBeDefined()
44
44
  })
@@ -41,5 +41,5 @@ test("RectDiffSolver maps obstacle layers to numeric zLayers", () => {
41
41
 
42
42
  // Verify obstacles were processed correctly
43
43
  // The internal solver should have mapped layer names to z indices
44
- expect(pipeline.rectDiffSolver).toBeDefined()
44
+ expect(pipeline.rectDiffGridSolverPipeline).toBeDefined()
45
45
  })
@@ -1,6 +1,6 @@
1
1
  // utils/rectsEqual.ts
2
- import type { XYRect } from "../lib/solvers/rectdiff/types"
3
- import { EPS } from "../lib/solvers/rectdiff/geometry"
2
+ import type { XYRect } from "../lib/rectdiff-types"
3
+ import { EPS } from "../lib/utils/rectdiff-geometry"
4
4
 
5
5
  /**
6
6
  * Checks if two rectangles are equal within a small tolerance (EPS).
@@ -1,6 +1,6 @@
1
1
  // utils/rectsOverlap.ts
2
- import type { XYRect } from "../lib/solvers/rectdiff/types"
3
- import { EPS } from "../lib/solvers/rectdiff/geometry"
2
+ import type { XYRect } from "../lib/rectdiff-types"
3
+ import { EPS } from "../lib/utils/rectdiff-geometry"
4
4
 
5
5
  /**
6
6
  * Checks if two rectangles overlap.