@tscircuit/rectdiff 0.0.28 → 0.0.29

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 (47) hide show
  1. package/.github/workflows/bun-pver-release.yml +24 -45
  2. package/dist/index.d.ts +0 -46
  3. package/dist/index.js +220 -775
  4. package/lib/RectDiffPipeline.ts +0 -46
  5. package/lib/types/srj-types.ts +0 -1
  6. package/package.json +1 -2
  7. package/pages/repro/merge-single-layer-node.page.tsx +17 -0
  8. package/tests/__snapshots__/board-outline.snap.svg +2 -2
  9. package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +1 -1
  10. package/tests/solver/both-points-equivalent/__snapshots__/both-points-equivalent.snap.svg +1 -1
  11. package/tests/solver/bugreport01-be84eb/__snapshots__/bugreport01-be84eb.snap.svg +1 -1
  12. package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +1 -1
  13. package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
  14. package/tests/solver/bugreport07-d3f3be/__snapshots__/bugreport07-d3f3be.snap.svg +1 -1
  15. package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
  16. package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +1 -1
  17. package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +1 -1
  18. package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +1 -1
  19. package/tests/solver/bugreport12-35ce1c/__snapshots__/bugreport12-35ce1c.snap.svg +1 -1
  20. package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +1 -1
  21. package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +1 -1
  22. package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
  23. package/tests/solver/bugreport20-obstacle-clipping/__snapshots__/bugreport20-obstacle-clipping.snap.svg +1 -1
  24. package/tests/solver/bugreport21-board-outline/__snapshots__/bugreport21-board-outline.snap.svg +2 -2
  25. package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
  26. package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +1 -1
  27. package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
  28. package/tests/solver/bugreport25-4b1d55/__snapshots__/bugreport25-4b1d55.snap.svg +1 -1
  29. package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +1 -1
  30. package/tests/solver/interaction/__snapshots__/interaction.snap.svg +1 -1
  31. package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
  32. package/tests/solver/no-better-path/__snapshots__/no-better-path.snap.svg +1 -1
  33. package/tests/solver/repros/merge-single-layer-node/merge-single-layer-node.json +861 -0
  34. package/tests/solver/{bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.test.ts → repros/merge-single-layer-node/merge-single-layer-node.test.ts} +7 -42
  35. package/tests/solver/transitivity/__snapshots__/transitivity.snap.svg +2 -2
  36. package/tsconfig.json +5 -1
  37. package/vite.config.ts +4 -0
  38. package/lib/solvers/AdjacentLayerContainmentMergeSolver/AdjacentLayerContainmentMergeSolver.ts +0 -456
  39. package/lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts +0 -314
  40. package/pages/bugreports/bugreport50-multi-support-layer-merge.page.tsx +0 -19
  41. package/pages/pour.page.tsx +0 -18
  42. package/test-assets/bugreport49-634662.json +0 -412
  43. package/tests/outer-layer-containment-merge-solver.test.ts +0 -73
  44. package/tests/solver/bugreport49-634662/__snapshots__/bugreport49-634662.snap.svg +0 -44
  45. package/tests/solver/bugreport49-634662/bugreport49-634662.test.ts +0 -130
  46. package/tests/solver/bugreport50-multi-support-layer-merge/__snapshots__/bugreport50-multi-support-layer-merge.snap.svg +0 -44
  47. package/tests/solver/bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.json +0 -972
@@ -1,314 +0,0 @@
1
- import { BaseSolver } from "@tscircuit/solver-utils"
2
- import type { GraphicsObject } from "graphics-debug"
3
- import type { CapacityMeshNode } from "lib/types/capacity-mesh-types"
4
- import type { XYRect } from "lib/rectdiff-types"
5
- import type { Obstacle, SimpleRouteJson } from "lib/types/srj-types"
6
- import { obstacleToXYRect, obstacleZs } from "../RectDiffSeedingSolver/layers"
7
- import { getColorForZLayer } from "lib/utils/getColorForZLayer"
8
- import { subtractRect2D, overlaps, EPS } from "lib/utils/rectdiff-geometry"
9
- import { padRect } from "lib/utils/padRect"
10
-
11
- type OuterLayerContainmentMergeSolverInput = {
12
- meshNodes: CapacityMeshNode[]
13
- simpleRouteJson: SimpleRouteJson
14
- zIndexByName: Map<string, number>
15
- obstacleClearance?: number
16
- }
17
-
18
- type ObstacleWithRect = {
19
- obstacle: Obstacle
20
- rect: XYRect
21
- }
22
-
23
- const nodeToRect = (node: CapacityMeshNode): XYRect => ({
24
- x: node.center.x - node.width / 2,
25
- y: node.center.y - node.height / 2,
26
- width: node.width,
27
- height: node.height,
28
- })
29
-
30
- const rectArea = (rect: XYRect) => rect.width * rect.height
31
- const MIN_OUTER_LAYER_MERGE_AREA_MM2 = 1
32
-
33
- const cloneNode = (node: CapacityMeshNode): CapacityMeshNode => ({
34
- ...node,
35
- center: { ...node.center },
36
- availableZ: [...node.availableZ],
37
- })
38
-
39
- const cloneNodeWithRect = (
40
- node: CapacityMeshNode,
41
- rect: XYRect,
42
- capacityMeshNodeId: string,
43
- ): CapacityMeshNode => ({
44
- ...node,
45
- capacityMeshNodeId,
46
- center: {
47
- x: rect.x + rect.width / 2,
48
- y: rect.y + rect.height / 2,
49
- },
50
- width: rect.width,
51
- height: rect.height,
52
- availableZ: [...node.availableZ],
53
- layer: `z${node.availableZ.join(",")}`,
54
- })
55
-
56
- const isFreeNode = (node: CapacityMeshNode) =>
57
- !node._containsObstacle && !node._containsTarget
58
-
59
- const isSingletonOuterNode = (node: CapacityMeshNode, outerZ: number) =>
60
- node.availableZ.length === 1 && node.availableZ[0] === outerZ
61
-
62
- const sameRect = (a: XYRect, b: XYRect) =>
63
- Math.abs(a.x - b.x) <= EPS &&
64
- Math.abs(a.y - b.y) <= EPS &&
65
- Math.abs(a.width - b.width) <= EPS &&
66
- Math.abs(a.height - b.height) <= EPS
67
-
68
- const subtractRects = (target: XYRect, cutters: XYRect[]) => {
69
- let remaining: XYRect[] = [target]
70
-
71
- for (const cutter of cutters) {
72
- if (remaining.length === 0) return remaining
73
-
74
- const nextRemaining: XYRect[] = []
75
- for (const piece of remaining) {
76
- nextRemaining.push(...subtractRect2D(piece, cutter))
77
- }
78
- remaining = nextRemaining
79
- }
80
-
81
- return remaining
82
- }
83
-
84
- const isFullyCoveredByRects = (target: XYRect, coveringRects: XYRect[]) => {
85
- return subtractRects(target, coveringRects).length === 0
86
- }
87
-
88
- export class OuterLayerContainmentMergeSolver extends BaseSolver {
89
- private outputNodes: CapacityMeshNode[] = []
90
- private promotedNodeIds = new Set<string>()
91
- private residualNodeIds = new Set<string>()
92
-
93
- constructor(private input: OuterLayerContainmentMergeSolverInput) {
94
- super()
95
- }
96
-
97
- override _setup() {
98
- this.outputNodes = this.input.meshNodes.map(cloneNode)
99
- this.promotedNodeIds.clear()
100
- this.residualNodeIds.clear()
101
- }
102
-
103
- override _step() {
104
- this.outputNodes = this.processOuterLayerContainmentMerges()
105
- this.solved = true
106
- }
107
-
108
- private processOuterLayerContainmentMerges(): CapacityMeshNode[] {
109
- const srj = this.input.simpleRouteJson
110
- const layerCount = Math.max(1, srj.layerCount || 1)
111
- if (layerCount < 3) {
112
- return this.input.meshNodes.map(cloneNode)
113
- }
114
-
115
- const topZ = 0
116
- const bottomZ = layerCount - 1
117
- const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0)
118
- const originalNodes = this.input.meshNodes.map(cloneNode)
119
- const obstaclesByLayer = this.buildObstaclesByLayer(layerCount)
120
- const mutableOuterNodes = originalNodes.filter(
121
- (node) =>
122
- isFreeNode(node) &&
123
- (isSingletonOuterNode(node, topZ) ||
124
- isSingletonOuterNode(node, bottomZ)),
125
- )
126
- const immutableNodes = originalNodes.filter(
127
- (node) => !mutableOuterNodes.includes(node),
128
- )
129
- const freeSupportRectsByOuterLayer = new Map<number, XYRect[]>()
130
- freeSupportRectsByOuterLayer.set(
131
- topZ,
132
- originalNodes
133
- .filter((node) => isFreeNode(node) && node.availableZ.includes(topZ))
134
- .map(nodeToRect),
135
- )
136
- freeSupportRectsByOuterLayer.set(
137
- bottomZ,
138
- originalNodes
139
- .filter((node) => isFreeNode(node) && node.availableZ.includes(bottomZ))
140
- .map(nodeToRect),
141
- )
142
-
143
- const promotedNodes: CapacityMeshNode[] = []
144
- const promotedRects: XYRect[] = []
145
- const candidateNodes = mutableOuterNodes
146
- .filter(
147
- (node) =>
148
- node.width + EPS >= viaMinSize &&
149
- node.height + EPS >= viaMinSize &&
150
- rectArea(nodeToRect(node)) > MIN_OUTER_LAYER_MERGE_AREA_MM2 + EPS,
151
- )
152
- .sort((a, b) => rectArea(nodeToRect(b)) - rectArea(nodeToRect(a)))
153
-
154
- for (const candidate of candidateNodes) {
155
- const candidateZ = candidate.availableZ[0]!
156
- const oppositeZ = candidateZ === topZ ? bottomZ : topZ
157
- const candidateRect = nodeToRect(candidate)
158
- const oppositeSupportRects =
159
- freeSupportRectsByOuterLayer.get(oppositeZ) ?? []
160
-
161
- if (
162
- !this.isTransitCompatibleAcrossIntermediateLayers({
163
- rect: candidateRect,
164
- fromZ: candidateZ,
165
- toZ: oppositeZ,
166
- obstaclesByLayer,
167
- })
168
- ) {
169
- continue
170
- }
171
- if (!isFullyCoveredByRects(candidateRect, oppositeSupportRects)) {
172
- continue
173
- }
174
-
175
- promotedNodes.push({
176
- ...candidate,
177
- availableZ: [topZ, bottomZ],
178
- layer: `z${topZ},${bottomZ}`,
179
- })
180
- promotedRects.push(candidateRect)
181
- this.promotedNodeIds.add(candidate.capacityMeshNodeId)
182
- }
183
-
184
- let nextResidualId = 0
185
- const residualNodes: CapacityMeshNode[] = []
186
-
187
- for (const node of mutableOuterNodes) {
188
- if (this.promotedNodeIds.has(node.capacityMeshNodeId)) {
189
- continue
190
- }
191
-
192
- const nodeRect = nodeToRect(node)
193
- const remainingPieces = subtractRects(nodeRect, promotedRects)
194
-
195
- if (
196
- remainingPieces.length === 1 &&
197
- sameRect(remainingPieces[0]!, nodeRect)
198
- ) {
199
- residualNodes.push(node)
200
- continue
201
- }
202
-
203
- for (const piece of remainingPieces) {
204
- const residualNode = cloneNodeWithRect(
205
- node,
206
- piece,
207
- `${node.capacityMeshNodeId}-outer-merge-${nextResidualId++}`,
208
- )
209
- residualNodes.push(residualNode)
210
- this.residualNodeIds.add(residualNode.capacityMeshNodeId)
211
- }
212
- }
213
-
214
- return [...immutableNodes, ...promotedNodes, ...residualNodes]
215
- }
216
-
217
- private buildObstaclesByLayer(layerCount: number): ObstacleWithRect[][] {
218
- const out = Array.from(
219
- { length: layerCount },
220
- () => [] as ObstacleWithRect[],
221
- )
222
-
223
- for (const obstacle of this.input.simpleRouteJson.obstacles ?? []) {
224
- const baseRect = obstacleToXYRect(obstacle)
225
- if (!baseRect) continue
226
- const rect = padRect(baseRect, this.input.obstacleClearance ?? 0)
227
- const zLayers = obstacleZs(obstacle, this.input.zIndexByName)
228
-
229
- for (const z of zLayers) {
230
- if (z < 0 || z >= layerCount) continue
231
- out[z]!.push({ obstacle, rect })
232
- }
233
- }
234
-
235
- return out
236
- }
237
-
238
- private isTransitCompatibleAcrossIntermediateLayers(params: {
239
- rect: XYRect
240
- fromZ: number
241
- toZ: number
242
- obstaclesByLayer: ObstacleWithRect[][]
243
- }) {
244
- const { rect, fromZ, toZ, obstaclesByLayer } = params
245
- const lo = Math.min(fromZ, toZ)
246
- const hi = Math.max(fromZ, toZ)
247
-
248
- if (hi - lo < 2) return false
249
-
250
- for (let z = lo + 1; z < hi; z++) {
251
- const overlapping = (obstaclesByLayer[z] ?? []).filter((entry) =>
252
- overlaps(entry.rect, rect),
253
- )
254
- if (overlapping.length === 0) return false
255
-
256
- const nonCopperOverlap = overlapping.some(
257
- (entry) => !entry.obstacle.isCopperPour,
258
- )
259
- if (nonCopperOverlap) return false
260
-
261
- const copperRects = overlapping
262
- .filter((entry) => entry.obstacle.isCopperPour)
263
- .map((entry) => entry.rect)
264
-
265
- if (!isFullyCoveredByRects(rect, copperRects)) {
266
- return false
267
- }
268
- }
269
-
270
- return true
271
- }
272
-
273
- override getOutput(): { outputNodes: CapacityMeshNode[] } {
274
- return { outputNodes: this.outputNodes }
275
- }
276
-
277
- override visualize(): GraphicsObject {
278
- return {
279
- title: "OuterLayerContainmentMergeSolver",
280
- coordinateSystem: "cartesian",
281
- rects: this.outputNodes.map((node) => {
282
- const colors = getColorForZLayer(node.availableZ)
283
- const isPromoted = this.promotedNodeIds.has(node.capacityMeshNodeId)
284
- const isResidual = this.residualNodeIds.has(node.capacityMeshNodeId)
285
-
286
- return {
287
- center: node.center,
288
- width: node.width,
289
- height: node.height,
290
- stroke: isPromoted
291
- ? "rgba(22, 163, 74, 0.95)"
292
- : isResidual
293
- ? "rgba(37, 99, 235, 0.95)"
294
- : colors.stroke,
295
- fill: node._containsObstacle
296
- ? "rgba(239, 68, 68, 0.35)"
297
- : isPromoted
298
- ? "rgba(34, 197, 94, 0.28)"
299
- : isResidual
300
- ? "rgba(59, 130, 246, 0.18)"
301
- : colors.fill,
302
- layer: `z${node.availableZ.join(",")}`,
303
- label: [
304
- `node ${node.capacityMeshNodeId}`,
305
- `z:${node.availableZ.join(",")}`,
306
- ].join("\n"),
307
- }
308
- }),
309
- points: [],
310
- lines: [],
311
- texts: [],
312
- }
313
- }
314
- }
@@ -1,19 +0,0 @@
1
- import { useMemo } from "react"
2
- import { SolverDebugger3d } from "../../components/SolverDebugger3d"
3
- import { RectDiffPipeline } from "../../lib/RectDiffPipeline"
4
- import srjJson from "../../tests/solver/bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.json"
5
-
6
- const simpleRouteJson =
7
- srjJson.simpleRouteJson ?? srjJson.simple_route_json ?? srjJson
8
-
9
- export default () => {
10
- const solver = useMemo(
11
- () =>
12
- new RectDiffPipeline({
13
- simpleRouteJson,
14
- }),
15
- [],
16
- )
17
-
18
- return <SolverDebugger3d solver={solver} simpleRouteJson={simpleRouteJson} />
19
- }
@@ -1,18 +0,0 @@
1
- import { useMemo } from "react"
2
- import { SolverDebugger3d } from "../components/SolverDebugger3d"
3
- import { RectDiffPipeline } from "../lib/RectDiffPipeline"
4
- import bugreport49 from "../test-assets/bugreport49-634662.json"
5
-
6
- const simpleRouteJson = bugreport49.simple_route_json ?? bugreport49
7
-
8
- export default () => {
9
- const solver = useMemo(
10
- () =>
11
- new RectDiffPipeline({
12
- simpleRouteJson,
13
- }),
14
- [],
15
- )
16
-
17
- return <SolverDebugger3d solver={solver} simpleRouteJson={simpleRouteJson} />
18
- }