@tscircuit/rectdiff 0.0.18 → 0.0.20

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.
@@ -3,28 +3,21 @@ import type { GraphicsObject } from "graphics-debug"
3
3
  import type { CapacityMeshNode, RTreeRect } from "lib/types/capacity-mesh-types"
4
4
  import { expandRectFromSeed } from "../../utils/expandRectFromSeed"
5
5
  import { finalizeRects } from "../../utils/finalizeRects"
6
- import { allLayerNode } from "../../utils/buildHardPlacedByLayer"
7
6
  import { resizeSoftOverlaps } from "../../utils/resizeSoftOverlaps"
8
7
  import { rectsToMeshNodes } from "./rectsToMeshNodes"
9
8
  import type { XYRect, Candidate3D, Placed3D } from "../../rectdiff-types"
10
9
  import type { SimpleRouteJson } from "lib/types/srj-types"
11
- import {
12
- buildZIndexMap,
13
- obstacleToXYRect,
14
- obstacleZs,
15
- } from "../RectDiffSeedingSolver/layers"
16
10
  import RBush from "rbush"
17
11
  import { rectToTree } from "../../utils/rectToTree"
18
12
  import { sameTreeRect } from "../../utils/sameTreeRect"
19
13
 
20
- export type RectDiffExpansionSolverSnapshot = {
14
+ export type RectDiffExpansionSolverInput = {
21
15
  srj: SimpleRouteJson
22
16
  layerNames: string[]
23
17
  layerCount: number
24
18
  bounds: XYRect
25
19
  options: {
26
20
  gridSizes: number[]
27
- // the engine only uses gridSizes here, other options are ignored
28
21
  [key: string]: any
29
22
  }
30
23
  boardVoidRects: XYRect[]
@@ -35,10 +28,6 @@ export type RectDiffExpansionSolverSnapshot = {
35
28
  edgeAnalysisDone: boolean
36
29
  totalSeedsThisGrid: number
37
30
  consumedSeedsThisGrid: number
38
- }
39
-
40
- export type RectDiffExpansionSolverInput = {
41
- initialSnapshot: RectDiffExpansionSolverSnapshot
42
31
  obstacleIndexByLayer: Array<RBush<RTreeRect>>
43
32
  }
44
33
 
@@ -49,74 +38,28 @@ export type RectDiffExpansionSolverInput = {
49
38
  * and runs the EXPANSION phase, then finalizes to capacity mesh nodes.
50
39
  */
51
40
  export class RectDiffExpansionSolver extends BaseSolver {
52
- // Engine fields (same shape used by rectdiff/engine.ts)
53
- private srj!: SimpleRouteJson
54
- private layerNames!: string[]
55
- private layerCount!: number
56
- private bounds!: XYRect
57
- private options!: {
58
- gridSizes: number[]
59
- // the engine only uses gridSizes here, other options are ignored
60
- [key: string]: any
61
- }
62
- private boardVoidRects!: XYRect[]
63
- private gridIndex!: number
64
- private candidates!: Candidate3D[]
65
- private placed!: Placed3D[]
66
- private placedIndexByLayer!: Array<RBush<RTreeRect>>
67
- private expansionIndex!: number
68
- private edgeAnalysisDone!: boolean
69
- private totalSeedsThisGrid!: number
70
- private consumedSeedsThisGrid!: number
71
-
72
- private _meshNodes: CapacityMeshNode[] = []
73
-
41
+ placedIndexByLayer: Array<RBush<RTreeRect>> = []
42
+ _meshNodes: CapacityMeshNode[] = []
74
43
  constructor(private input: RectDiffExpansionSolverInput) {
75
44
  super()
76
- // Copy engine snapshot fields directly onto this solver instance
77
- Object.assign(this, this.input.initialSnapshot)
78
45
  }
79
46
 
80
47
  override _setup() {
81
48
  this.stats = {
82
- gridIndex: this.gridIndex,
83
- }
84
-
85
- if (this.input.obstacleIndexByLayer) {
86
- } else {
87
- const { zIndexByName } = buildZIndexMap(this.srj)
88
- this.input.obstacleIndexByLayer = Array.from(
89
- { length: this.layerCount },
90
- () => new RBush<RTreeRect>(),
91
- )
92
- const insertObstacle = (rect: XYRect, z: number) => {
93
- const tree = this.input.obstacleIndexByLayer[z]
94
- if (tree) tree.insert(rectToTree(rect))
95
- }
96
- for (const voidRect of this.boardVoidRects ?? []) {
97
- for (let z = 0; z < this.layerCount; z++) insertObstacle(voidRect, z)
98
- }
99
- for (const obstacle of this.srj.obstacles ?? []) {
100
- const rect = obstacleToXYRect(obstacle as any)
101
- if (!rect) continue
102
- const zLayers =
103
- obstacle.zLayers?.length && obstacle.zLayers.length > 0
104
- ? obstacle.zLayers
105
- : obstacleZs(obstacle as any, zIndexByName)
106
- zLayers.forEach((z) => {
107
- if (z >= 0 && z < this.layerCount) insertObstacle(rect, z)
108
- })
109
- }
49
+ gridIndex: this.input.gridIndex,
110
50
  }
111
51
 
112
52
  this.placedIndexByLayer = Array.from(
113
- { length: this.layerCount },
53
+ { length: this.input.layerCount },
114
54
  () => new RBush<RTreeRect>(),
115
55
  )
116
- for (const placement of this.placed ?? []) {
56
+ for (const placement of this.input.placed) {
117
57
  for (const z of placement.zLayers) {
118
- const tree = this.placedIndexByLayer[z]
119
- if (tree) tree.insert(rectToTree(placement.rect))
58
+ const placedIndex = this.placedIndexByLayer[z]
59
+ if (placedIndex)
60
+ placedIndex.insert(
61
+ rectToTree(placement.rect, { zLayers: placement.zLayers }),
62
+ )
120
63
  }
121
64
  }
122
65
  }
@@ -126,81 +69,71 @@ export class RectDiffExpansionSolver extends BaseSolver {
126
69
 
127
70
  this._stepExpansion()
128
71
 
129
- this.stats.gridIndex = this.gridIndex
130
- this.stats.placed = this.placed.length
72
+ this.stats.gridIndex = this.input.gridIndex
73
+ this.stats.placed = this.input.placed.length
131
74
 
132
- if (this.expansionIndex >= this.placed.length) {
75
+ if (this.input.expansionIndex >= this.input.placed.length) {
133
76
  this.finalizeIfNeeded()
134
77
  }
135
78
  }
136
79
 
137
80
  private _stepExpansion(): void {
138
- if (this.expansionIndex >= this.placed.length) {
81
+ if (this.input.expansionIndex >= this.input.placed.length) {
139
82
  return
140
83
  }
141
84
 
142
- const idx = this.expansionIndex
143
- const p = this.placed[idx]!
144
- const lastGrid = this.options.gridSizes[this.options.gridSizes.length - 1]!
145
-
146
- const hardPlacedByLayer = allLayerNode({
147
- layerCount: this.layerCount,
148
- placed: this.placed,
149
- })
150
-
151
- // HARD blockers only: obstacles on p.zLayers + full-stack nodes
152
- const hardBlockers: XYRect[] = []
153
- for (const z of p.zLayers) {
154
- const obstacleTree = this.input.obstacleIndexByLayer[z]
155
- if (obstacleTree) hardBlockers.push(...obstacleTree.all())
156
- hardBlockers.push(...(hardPlacedByLayer[z] ?? []))
157
- }
85
+ const idx = this.input.expansionIndex
86
+ const p = this.input.placed[idx]!
87
+ const lastGrid =
88
+ this.input.options.gridSizes[this.input.options.gridSizes.length - 1]!
158
89
 
159
90
  const oldRect = p.rect
160
91
  const expanded = expandRectFromSeed({
161
92
  startX: p.rect.x + p.rect.width / 2,
162
93
  startY: p.rect.y + p.rect.height / 2,
163
94
  gridSize: lastGrid,
164
- bounds: this.bounds,
165
- blockers: hardBlockers,
95
+ bounds: this.input.bounds,
96
+ obsticalIndexByLayer: this.input.obstacleIndexByLayer,
97
+ placedIndexByLayer: this.placedIndexByLayer,
166
98
  initialCellRatio: 0,
167
99
  maxAspectRatio: null,
168
100
  minReq: { width: p.rect.width, height: p.rect.height },
101
+ zLayers: p.zLayers,
169
102
  })
170
103
 
171
104
  if (expanded) {
172
105
  // Update placement + per-layer index (replace old rect object)
173
- this.placed[idx] = { rect: expanded, zLayers: p.zLayers }
106
+ this.input.placed[idx] = { rect: expanded, zLayers: p.zLayers }
174
107
  for (const z of p.zLayers) {
175
108
  const tree = this.placedIndexByLayer[z]
176
109
  if (tree) {
177
- tree.remove(rectToTree(oldRect), sameTreeRect)
178
- tree.insert(rectToTree(expanded))
110
+ tree.remove(rectToTree(oldRect, { zLayers: p.zLayers }), sameTreeRect)
111
+ tree.insert(rectToTree(expanded, { zLayers: p.zLayers }))
179
112
  }
180
113
  }
181
114
 
182
115
  // Carve overlapped soft neighbors (respect full-stack nodes)
183
116
  resizeSoftOverlaps(
184
117
  {
185
- layerCount: this.layerCount,
186
- placed: this.placed,
187
- options: this.options,
118
+ layerCount: this.input.layerCount,
119
+ placed: this.input.placed,
120
+ options: this.input.options,
188
121
  placedIndexByLayer: this.placedIndexByLayer,
189
122
  },
190
123
  idx,
191
124
  )
192
125
  }
193
126
 
194
- this.expansionIndex += 1
127
+ this.input.expansionIndex += 1
195
128
  }
196
129
 
197
130
  private finalizeIfNeeded() {
198
131
  if (this.solved) return
199
132
 
200
133
  const rects = finalizeRects({
201
- placed: this.placed,
202
- srj: this.srj,
203
- boardVoidRects: this.boardVoidRects,
134
+ placed: this.input.placed,
135
+ srj: this.input.srj,
136
+ boardVoidRects: this.input.boardVoidRects,
204
137
  })
205
138
  this._meshNodes = rectsToMeshNodes(rects)
206
139
  this.solved = true
@@ -208,25 +141,39 @@ export class RectDiffExpansionSolver extends BaseSolver {
208
141
 
209
142
  computeProgress(): number {
210
143
  if (this.solved) return 1
211
- const grids = this.options.gridSizes.length
144
+ const grids = this.input.options.gridSizes.length
212
145
  const base = grids / (grids + 1)
213
- const denom = Math.max(1, this.placed.length)
214
- const frac = denom ? this.expansionIndex / denom : 1
146
+ const denom = Math.max(1, this.input.placed.length)
147
+ const frac = denom ? this.input.expansionIndex / denom : 1
215
148
  return Math.min(0.999, base + frac * (1 / (grids + 1)))
216
149
  }
217
150
 
218
151
  override getOutput(): { meshNodes: CapacityMeshNode[] } {
219
- if (!this.solved && this._meshNodes.length === 0) {
220
- this.finalizeIfNeeded()
221
- }
222
- return { meshNodes: this._meshNodes }
152
+ if (this.solved) return { meshNodes: this._meshNodes }
153
+
154
+ // Provide a live preview of the placements before finalization so debuggers
155
+ // can inspect intermediary states without forcing the solver to finish.
156
+ const previewNodes: CapacityMeshNode[] = this.input.placed.map(
157
+ (placement, idx) => ({
158
+ capacityMeshNodeId: `expand-preview-${idx}`,
159
+ center: {
160
+ x: placement.rect.x + placement.rect.width / 2,
161
+ y: placement.rect.y + placement.rect.height / 2,
162
+ },
163
+ width: placement.rect.width,
164
+ height: placement.rect.height,
165
+ availableZ: placement.zLayers.slice(),
166
+ layer: `z${placement.zLayers.join(",")}`,
167
+ }),
168
+ )
169
+ return { meshNodes: previewNodes }
223
170
  }
224
171
 
225
172
  /** Simple visualization of expanded placements. */
226
173
  override visualize(): GraphicsObject {
227
174
  const rects: NonNullable<GraphicsObject["rects"]> = []
228
175
 
229
- for (const placement of this.placed ?? []) {
176
+ for (const placement of this.input.placed ?? []) {
230
177
  rects.push({
231
178
  center: {
232
179
  x: placement.rect.x + placement.rect.width / 2,
@@ -48,15 +48,30 @@ export class RectDiffGridSolverPipeline extends BasePipelineSolver<RectDiffGridS
48
48
  definePipelineStep(
49
49
  "rectDiffExpansionSolver",
50
50
  RectDiffExpansionSolver,
51
- (pipeline: RectDiffGridSolverPipeline) => [
52
- {
53
- initialSnapshot: {
54
- ...pipeline.rectDiffSeedingSolver!.getOutput(),
51
+ (pipeline: RectDiffGridSolverPipeline) => {
52
+ const output = pipeline.rectDiffSeedingSolver?.getOutput()
53
+ if (!output) {
54
+ throw new Error("RectDiffSeedingSolver did not produce output")
55
+ }
56
+ return [
57
+ {
58
+ srj: pipeline.inputProblem.simpleRouteJson,
59
+ layerNames: output.layerNames ?? [],
55
60
  boardVoidRects: pipeline.inputProblem.boardVoidRects ?? [],
61
+ layerCount: pipeline.inputProblem.simpleRouteJson.layerCount,
62
+ bounds: output.bounds!,
63
+ candidates: output.candidates,
64
+ consumedSeedsThisGrid: output.placed.length,
65
+ totalSeedsThisGrid: output.candidates.length,
66
+ placed: output.placed,
67
+ edgeAnalysisDone: output.edgeAnalysisDone,
68
+ gridIndex: output.gridIndex,
69
+ expansionIndex: output.expansionIndex,
70
+ obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
71
+ options: output.options,
56
72
  },
57
- obstacleIndexByLayer: pipeline.obstacleIndexByLayer,
58
- },
59
- ],
73
+ ]
74
+ },
60
75
  ),
61
76
  ]
62
77
 
@@ -30,12 +30,13 @@ export const buildObstacleIndexesByLayer = (params: {
30
30
  )
31
31
 
32
32
  const insertObstacle = (rect: XYRect, z: number) => {
33
- const treeRect = {
33
+ const treeRect: RTreeRect = {
34
34
  ...rect,
35
35
  minX: rect.x,
36
36
  minY: rect.y,
37
37
  maxX: rect.x + rect.width,
38
38
  maxY: rect.y + rect.height,
39
+ zLayers: [z],
39
40
  }
40
41
  obstacleIndexByLayer[z]?.insert(treeRect)
41
42
  }
@@ -21,6 +21,7 @@ import { resizeSoftOverlaps } from "../../utils/resizeSoftOverlaps"
21
21
  import { getColorForZLayer } from "lib/utils/getColorForZLayer"
22
22
  import RBush from "rbush"
23
23
  import type { RTreeRect } from "lib/types/capacity-mesh-types"
24
+ import { rectToTree } from "lib/utils/rectToTree"
24
25
 
25
26
  export type RectDiffSeedingSolverInput = {
26
27
  simpleRouteJson: SimpleRouteJson
@@ -238,23 +239,17 @@ export class RectDiffSeedingSolver extends BaseSolver {
238
239
  const ordered = preferMultiLayer ? attempts : attempts.reverse()
239
240
 
240
241
  for (const attempt of ordered) {
241
- // HARD blockers only: obstacles on those layers + full-stack nodes
242
- const hardBlockers: XYRect[] = []
243
- for (const z of attempt.layers) {
244
- const obstacleLayer = this.input.obstacleIndexByLayer[z]
245
- if (obstacleLayer) hardBlockers.push(...obstacleLayer.all())
246
- if (hardPlacedByLayer[z]) hardBlockers.push(...hardPlacedByLayer[z]!)
247
- }
248
-
249
242
  const rect = expandRectFromSeed({
250
243
  startX: cand.x,
251
244
  startY: cand.y,
252
245
  gridSize: grid,
253
246
  bounds: this.bounds,
254
- blockers: hardBlockers,
247
+ obsticalIndexByLayer: this.input.obstacleIndexByLayer,
248
+ placedIndexByLayer: this.placedIndexByLayer,
255
249
  initialCellRatio,
256
250
  maxAspectRatio,
257
251
  minReq: attempt.minReq,
252
+ zLayers: attempt.layers,
258
253
  })
259
254
  if (!rect) continue
260
255
 
@@ -264,13 +259,7 @@ export class RectDiffSeedingSolver extends BaseSolver {
264
259
  for (const z of attempt.layers) {
265
260
  const idx = this.placedIndexByLayer[z]
266
261
  if (idx) {
267
- idx.insert({
268
- ...rect,
269
- minX: rect.x,
270
- minY: rect.y,
271
- maxX: rect.x + rect.width,
272
- maxY: rect.y + rect.height,
273
- })
262
+ idx.insert(rectToTree(rect, { zLayers: placed.zLayers }))
274
263
  }
275
264
  }
276
265
 
@@ -39,4 +39,5 @@ export type RTreeRect = XYRect & {
39
39
  minY: number
40
40
  maxX: number
41
41
  maxY: number
42
+ zLayers: number[]
42
43
  }
@@ -1,5 +1,14 @@
1
+ import type RBush from "rbush"
1
2
  import type { XYRect } from "../rectdiff-types"
2
3
  import { EPS, gt, gte, lt, lte, overlaps } from "./rectdiff-geometry"
4
+ import type { RTreeRect } from "lib/types/capacity-mesh-types"
5
+ import { isSelfRect } from "./isSelfRect"
6
+ import {
7
+ searchStripDown,
8
+ searchStripLeft,
9
+ searchStripRight,
10
+ searchStripUp,
11
+ } from "./searchStrip"
3
12
 
4
13
  type ExpandDirectionParams = {
5
14
  r: XYRect
@@ -148,23 +157,55 @@ function maxExpandUp(params: ExpandDirectionParams) {
148
157
  return Math.max(0, e)
149
158
  }
150
159
 
160
+ const toRect = (tree: RTreeRect): XYRect => ({
161
+ x: tree.minX,
162
+ y: tree.minY,
163
+ width: tree.maxX - tree.minX,
164
+ height: tree.maxY - tree.minY,
165
+ })
166
+
167
+ const addBlocker = (params: {
168
+ rect: XYRect
169
+ seen: Set<string>
170
+ blockers: XYRect[]
171
+ }) => {
172
+ const { rect, seen, blockers } = params
173
+ const key = `${rect.x}|${rect.y}|${rect.width}|${rect.height}`
174
+ if (seen.has(key)) return
175
+ seen.add(key)
176
+ blockers.push(rect)
177
+ }
178
+
179
+ const toQueryRect = (params: { bounds: XYRect; rect: XYRect }) => {
180
+ const { bounds, rect } = params
181
+ const minX = Math.max(bounds.x, rect.x)
182
+ const minY = Math.max(bounds.y, rect.y)
183
+ const maxX = Math.min(bounds.x + bounds.width, rect.x + rect.width)
184
+ const maxY = Math.min(bounds.y + bounds.height, rect.y + rect.height)
185
+ if (maxX <= minX + EPS || maxY <= minY + EPS) return null
186
+ return { minX, minY, maxX, maxY }
187
+ }
188
+
151
189
  /** Grow a rect around a seed point, honoring bounds/blockers/aspect/min sizes. */
152
190
  export function expandRectFromSeed(params: {
153
191
  startX: number
154
192
  startY: number
155
193
  gridSize: number
156
194
  bounds: XYRect
157
- blockers: XYRect[]
195
+ obsticalIndexByLayer: Array<RBush<RTreeRect>>
196
+ placedIndexByLayer: Array<RBush<RTreeRect>>
158
197
  initialCellRatio: number
159
198
  maxAspectRatio: number | null | undefined
160
199
  minReq: { width: number; height: number }
200
+ zLayers: number[]
161
201
  }): XYRect | null {
162
202
  const {
163
203
  startX,
164
204
  startY,
165
205
  gridSize,
166
206
  bounds,
167
- blockers,
207
+ obsticalIndexByLayer,
208
+ placedIndexByLayer,
168
209
  initialCellRatio,
169
210
  maxAspectRatio,
170
211
  minReq,
@@ -173,6 +214,43 @@ export function expandRectFromSeed(params: {
173
214
  const minSide = Math.max(1e-9, gridSize * initialCellRatio)
174
215
  const initialW = Math.max(minSide, minReq.width)
175
216
  const initialH = Math.max(minSide, minReq.height)
217
+ const blockers: XYRect[] = []
218
+ const seen = new Set<string>()
219
+ const totalLayers = placedIndexByLayer.length
220
+
221
+ // Ignore the existing placement we are expanding so it doesn't self-block.
222
+
223
+ const collectBlockers = (searchRect: XYRect) => {
224
+ const query = toQueryRect({ bounds, rect: searchRect })
225
+ if (!query) return
226
+ for (const z of params.zLayers) {
227
+ const blockersIndex = obsticalIndexByLayer[z]
228
+ if (blockersIndex) {
229
+ for (const entry of blockersIndex.search(query))
230
+ addBlocker({ rect: toRect(entry), seen, blockers })
231
+ }
232
+
233
+ const placedLayer = placedIndexByLayer[z]
234
+ if (placedLayer) {
235
+ for (const entry of placedLayer.search(query)) {
236
+ const isFullStack = entry.zLayers.length >= totalLayers
237
+ if (!isFullStack) continue
238
+ const rect = toRect(entry)
239
+ if (
240
+ isSelfRect({
241
+ rect,
242
+ startX,
243
+ startY,
244
+ initialW,
245
+ initialH,
246
+ })
247
+ )
248
+ continue
249
+ addBlocker({ rect, seen, blockers })
250
+ }
251
+ }
252
+ }
253
+ }
176
254
 
177
255
  const strategies = [
178
256
  { ox: 0, oy: 0 },
@@ -192,6 +270,7 @@ export function expandRectFromSeed(params: {
192
270
  width: initialW,
193
271
  height: initialH,
194
272
  }
273
+ collectBlockers(r)
195
274
 
196
275
  // keep initial inside board
197
276
  if (
@@ -212,27 +291,35 @@ export function expandRectFromSeed(params: {
212
291
  improved = false
213
292
  const commonParams = { bounds, blockers, maxAspect: maxAspectRatio }
214
293
 
294
+ collectBlockers(searchStripRight({ rect: r, bounds }))
215
295
  const eR = maxExpandRight({ ...commonParams, r })
216
296
  if (eR > 0) {
217
297
  r = { ...r, width: r.width + eR }
298
+ collectBlockers(r)
218
299
  improved = true
219
300
  }
220
301
 
302
+ collectBlockers(searchStripDown({ rect: r, bounds }))
221
303
  const eD = maxExpandDown({ ...commonParams, r })
222
304
  if (eD > 0) {
223
305
  r = { ...r, height: r.height + eD }
306
+ collectBlockers(r)
224
307
  improved = true
225
308
  }
226
309
 
310
+ collectBlockers(searchStripLeft({ rect: r, bounds }))
227
311
  const eL = maxExpandLeft({ ...commonParams, r })
228
312
  if (eL > 0) {
229
313
  r = { x: r.x - eL, y: r.y, width: r.width + eL, height: r.height }
314
+ collectBlockers(r)
230
315
  improved = true
231
316
  }
232
317
 
318
+ collectBlockers(searchStripUp({ rect: r, bounds }))
233
319
  const eU = maxExpandUp({ ...commonParams, r })
234
320
  if (eU > 0) {
235
321
  r = { x: r.x, y: r.y - eU, width: r.width, height: r.height + eU }
322
+ collectBlockers(r)
236
323
  improved = true
237
324
  }
238
325
  }
@@ -0,0 +1,15 @@
1
+ import type { XYRect } from "lib/rectdiff-types"
2
+
3
+ const EPS = 1e-9
4
+
5
+ export const isSelfRect = (params: {
6
+ rect: XYRect
7
+ startX: number
8
+ startY: number
9
+ initialW: number
10
+ initialH: number
11
+ }) =>
12
+ Math.abs(params.rect.x + params.rect.width / 2 - params.startX) < EPS &&
13
+ Math.abs(params.rect.y + params.rect.height / 2 - params.startY) < EPS &&
14
+ Math.abs(params.rect.width - params.initialW) < EPS &&
15
+ Math.abs(params.rect.height - params.initialH) < EPS
@@ -1,10 +1,14 @@
1
1
  import type { XYRect } from "lib/rectdiff-types"
2
2
  import type { RTreeRect } from "lib/types/capacity-mesh-types"
3
3
 
4
- export const rectToTree = (rect: XYRect): RTreeRect => ({
4
+ export const rectToTree = (
5
+ rect: XYRect,
6
+ opts: { zLayers: number[] },
7
+ ): RTreeRect => ({
5
8
  ...rect,
6
9
  minX: rect.x,
7
10
  minY: rect.y,
8
11
  maxX: rect.x + rect.width,
9
12
  maxY: rect.y + rect.height,
13
+ zLayers: opts.zLayers,
10
14
  })
@@ -1,7 +1,8 @@
1
1
  import type { RTreeRect } from "lib/types/capacity-mesh-types"
2
- import type { Placed3D, XYRect } from "../rectdiff-types"
2
+ import type { Placed3D } from "../rectdiff-types"
3
3
  import { overlaps, subtractRect2D, EPS } from "./rectdiff-geometry"
4
4
  import type RBush from "rbush"
5
+ import { rectToTree } from "./rectToTree"
5
6
 
6
7
  export function resizeSoftOverlaps(
7
8
  params: {
@@ -57,13 +58,6 @@ export function resizeSoftOverlaps(
57
58
  }
58
59
 
59
60
  // 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
61
  const sameRect = (a: RTreeRect, b: RTreeRect) =>
68
62
  a.minX === b.minX &&
69
63
  a.minY === b.minY &&
@@ -77,7 +71,11 @@ export function resizeSoftOverlaps(
77
71
  if (params.placedIndexByLayer) {
78
72
  for (const z of rem.zLayers) {
79
73
  const tree = params.placedIndexByLayer[z]
80
- if (tree) tree.remove(rectToTree(rem.rect), sameRect)
74
+ if (tree)
75
+ tree.remove(
76
+ rectToTree(rem.rect, { zLayers: rem.zLayers }),
77
+ sameRect,
78
+ )
81
79
  }
82
80
  }
83
81
  })
@@ -89,13 +87,7 @@ export function resizeSoftOverlaps(
89
87
  if (params.placedIndexByLayer) {
90
88
  const idx = params.placedIndexByLayer[z]
91
89
  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
- })
90
+ idx.insert(rectToTree(p.rect, { zLayers: p.zLayers.slice() }))
99
91
  }
100
92
  }
101
93
  }
@@ -0,0 +1,50 @@
1
+ import type { XYRect } from "lib/rectdiff-types"
2
+
3
+ export const searchStripRight = ({
4
+ rect,
5
+ bounds,
6
+ }: {
7
+ rect: XYRect
8
+ bounds: XYRect
9
+ }): XYRect => ({
10
+ x: rect.x,
11
+ y: rect.y,
12
+ width: bounds.x + bounds.width - rect.x,
13
+ height: rect.height,
14
+ })
15
+ export const searchStripDown = ({
16
+ rect,
17
+ bounds,
18
+ }: {
19
+ rect: XYRect
20
+ bounds: XYRect
21
+ }): XYRect => ({
22
+ x: rect.x,
23
+ y: rect.y,
24
+ width: rect.width,
25
+ height: bounds.y + bounds.height - rect.y,
26
+ })
27
+ export const searchStripLeft = ({
28
+ rect,
29
+ bounds,
30
+ }: {
31
+ rect: XYRect
32
+ bounds: XYRect
33
+ }): XYRect => ({
34
+ x: bounds.x,
35
+ y: rect.y,
36
+ width: rect.x - bounds.x,
37
+ height: rect.height,
38
+ })
39
+ export const searchStripUp = ({
40
+ rect,
41
+ bounds,
42
+ }: {
43
+ rect: XYRect
44
+ bounds: XYRect
45
+ }): XYRect => ({
46
+ x: rect.x,
47
+ y: bounds.y,
48
+ width: rect.width,
49
+ height: rect.y - bounds.y,
50
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/rectdiff",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {