@tscircuit/rectdiff 0.0.27 → 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 (46) 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 -774
  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 -311
  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/solver/bugreport49-634662/__snapshots__/bugreport49-634662.snap.svg +0 -44
  44. package/tests/solver/bugreport49-634662/bugreport49-634662.test.ts +0 -134
  45. package/tests/solver/bugreport50-multi-support-layer-merge/__snapshots__/bugreport50-multi-support-layer-merge.snap.svg +0 -44
  46. package/tests/solver/bugreport50-multi-support-layer-merge/bugreport50-multi-support-layer-merge.json +0 -972
@@ -1,311 +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
-
32
- const cloneNode = (node: CapacityMeshNode): CapacityMeshNode => ({
33
- ...node,
34
- center: { ...node.center },
35
- availableZ: [...node.availableZ],
36
- })
37
-
38
- const cloneNodeWithRect = (
39
- node: CapacityMeshNode,
40
- rect: XYRect,
41
- capacityMeshNodeId: string,
42
- ): CapacityMeshNode => ({
43
- ...node,
44
- capacityMeshNodeId,
45
- center: {
46
- x: rect.x + rect.width / 2,
47
- y: rect.y + rect.height / 2,
48
- },
49
- width: rect.width,
50
- height: rect.height,
51
- availableZ: [...node.availableZ],
52
- layer: `z${node.availableZ.join(",")}`,
53
- })
54
-
55
- const isFreeNode = (node: CapacityMeshNode) =>
56
- !node._containsObstacle && !node._containsTarget
57
-
58
- const isSingletonOuterNode = (node: CapacityMeshNode, outerZ: number) =>
59
- node.availableZ.length === 1 && node.availableZ[0] === outerZ
60
-
61
- const sameRect = (a: XYRect, b: XYRect) =>
62
- Math.abs(a.x - b.x) <= EPS &&
63
- Math.abs(a.y - b.y) <= EPS &&
64
- Math.abs(a.width - b.width) <= EPS &&
65
- Math.abs(a.height - b.height) <= EPS
66
-
67
- const subtractRects = (target: XYRect, cutters: XYRect[]) => {
68
- let remaining: XYRect[] = [target]
69
-
70
- for (const cutter of cutters) {
71
- if (remaining.length === 0) return remaining
72
-
73
- const nextRemaining: XYRect[] = []
74
- for (const piece of remaining) {
75
- nextRemaining.push(...subtractRect2D(piece, cutter))
76
- }
77
- remaining = nextRemaining
78
- }
79
-
80
- return remaining
81
- }
82
-
83
- const isFullyCoveredByRects = (target: XYRect, coveringRects: XYRect[]) => {
84
- return subtractRects(target, coveringRects).length === 0
85
- }
86
-
87
- export class OuterLayerContainmentMergeSolver extends BaseSolver {
88
- private outputNodes: CapacityMeshNode[] = []
89
- private promotedNodeIds = new Set<string>()
90
- private residualNodeIds = new Set<string>()
91
-
92
- constructor(private input: OuterLayerContainmentMergeSolverInput) {
93
- super()
94
- }
95
-
96
- override _setup() {
97
- this.outputNodes = this.input.meshNodes.map(cloneNode)
98
- this.promotedNodeIds.clear()
99
- this.residualNodeIds.clear()
100
- }
101
-
102
- override _step() {
103
- this.outputNodes = this.processOuterLayerContainmentMerges()
104
- this.solved = true
105
- }
106
-
107
- private processOuterLayerContainmentMerges(): CapacityMeshNode[] {
108
- const srj = this.input.simpleRouteJson
109
- const layerCount = Math.max(1, srj.layerCount || 1)
110
- if (layerCount < 3) {
111
- return this.input.meshNodes.map(cloneNode)
112
- }
113
-
114
- const topZ = 0
115
- const bottomZ = layerCount - 1
116
- const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0)
117
- const originalNodes = this.input.meshNodes.map(cloneNode)
118
- const obstaclesByLayer = this.buildObstaclesByLayer(layerCount)
119
- const mutableOuterNodes = originalNodes.filter(
120
- (node) =>
121
- isFreeNode(node) &&
122
- (isSingletonOuterNode(node, topZ) ||
123
- isSingletonOuterNode(node, bottomZ)),
124
- )
125
- const immutableNodes = originalNodes.filter(
126
- (node) => !mutableOuterNodes.includes(node),
127
- )
128
- const freeSupportRectsByOuterLayer = new Map<number, XYRect[]>()
129
- freeSupportRectsByOuterLayer.set(
130
- topZ,
131
- originalNodes
132
- .filter((node) => isFreeNode(node) && node.availableZ.includes(topZ))
133
- .map(nodeToRect),
134
- )
135
- freeSupportRectsByOuterLayer.set(
136
- bottomZ,
137
- originalNodes
138
- .filter((node) => isFreeNode(node) && node.availableZ.includes(bottomZ))
139
- .map(nodeToRect),
140
- )
141
-
142
- const promotedNodes: CapacityMeshNode[] = []
143
- const promotedRects: XYRect[] = []
144
- const candidateNodes = mutableOuterNodes
145
- .filter(
146
- (node) =>
147
- node.width + EPS >= viaMinSize && node.height + EPS >= viaMinSize,
148
- )
149
- .sort((a, b) => rectArea(nodeToRect(b)) - rectArea(nodeToRect(a)))
150
-
151
- for (const candidate of candidateNodes) {
152
- const candidateZ = candidate.availableZ[0]!
153
- const oppositeZ = candidateZ === topZ ? bottomZ : topZ
154
- const candidateRect = nodeToRect(candidate)
155
- const oppositeSupportRects =
156
- freeSupportRectsByOuterLayer.get(oppositeZ) ?? []
157
-
158
- if (
159
- !this.isTransitCompatibleAcrossIntermediateLayers({
160
- rect: candidateRect,
161
- fromZ: candidateZ,
162
- toZ: oppositeZ,
163
- obstaclesByLayer,
164
- })
165
- ) {
166
- continue
167
- }
168
- if (!isFullyCoveredByRects(candidateRect, oppositeSupportRects)) {
169
- continue
170
- }
171
-
172
- promotedNodes.push({
173
- ...candidate,
174
- availableZ: [topZ, bottomZ],
175
- layer: `z${topZ},${bottomZ}`,
176
- })
177
- promotedRects.push(candidateRect)
178
- this.promotedNodeIds.add(candidate.capacityMeshNodeId)
179
- }
180
-
181
- let nextResidualId = 0
182
- const residualNodes: CapacityMeshNode[] = []
183
-
184
- for (const node of mutableOuterNodes) {
185
- if (this.promotedNodeIds.has(node.capacityMeshNodeId)) {
186
- continue
187
- }
188
-
189
- const nodeRect = nodeToRect(node)
190
- const remainingPieces = subtractRects(nodeRect, promotedRects)
191
-
192
- if (
193
- remainingPieces.length === 1 &&
194
- sameRect(remainingPieces[0]!, nodeRect)
195
- ) {
196
- residualNodes.push(node)
197
- continue
198
- }
199
-
200
- for (const piece of remainingPieces) {
201
- const residualNode = cloneNodeWithRect(
202
- node,
203
- piece,
204
- `${node.capacityMeshNodeId}-outer-merge-${nextResidualId++}`,
205
- )
206
- residualNodes.push(residualNode)
207
- this.residualNodeIds.add(residualNode.capacityMeshNodeId)
208
- }
209
- }
210
-
211
- return [...immutableNodes, ...promotedNodes, ...residualNodes]
212
- }
213
-
214
- private buildObstaclesByLayer(layerCount: number): ObstacleWithRect[][] {
215
- const out = Array.from(
216
- { length: layerCount },
217
- () => [] as ObstacleWithRect[],
218
- )
219
-
220
- for (const obstacle of this.input.simpleRouteJson.obstacles ?? []) {
221
- const baseRect = obstacleToXYRect(obstacle)
222
- if (!baseRect) continue
223
- const rect = padRect(baseRect, this.input.obstacleClearance ?? 0)
224
- const zLayers = obstacleZs(obstacle, this.input.zIndexByName)
225
-
226
- for (const z of zLayers) {
227
- if (z < 0 || z >= layerCount) continue
228
- out[z]!.push({ obstacle, rect })
229
- }
230
- }
231
-
232
- return out
233
- }
234
-
235
- private isTransitCompatibleAcrossIntermediateLayers(params: {
236
- rect: XYRect
237
- fromZ: number
238
- toZ: number
239
- obstaclesByLayer: ObstacleWithRect[][]
240
- }) {
241
- const { rect, fromZ, toZ, obstaclesByLayer } = params
242
- const lo = Math.min(fromZ, toZ)
243
- const hi = Math.max(fromZ, toZ)
244
-
245
- if (hi - lo < 2) return false
246
-
247
- for (let z = lo + 1; z < hi; z++) {
248
- const overlapping = (obstaclesByLayer[z] ?? []).filter((entry) =>
249
- overlaps(entry.rect, rect),
250
- )
251
- if (overlapping.length === 0) return false
252
-
253
- const nonCopperOverlap = overlapping.some(
254
- (entry) => !entry.obstacle.isCopperPour,
255
- )
256
- if (nonCopperOverlap) return false
257
-
258
- const copperRects = overlapping
259
- .filter((entry) => entry.obstacle.isCopperPour)
260
- .map((entry) => entry.rect)
261
-
262
- if (!isFullyCoveredByRects(rect, copperRects)) {
263
- return false
264
- }
265
- }
266
-
267
- return true
268
- }
269
-
270
- override getOutput(): { outputNodes: CapacityMeshNode[] } {
271
- return { outputNodes: this.outputNodes }
272
- }
273
-
274
- override visualize(): GraphicsObject {
275
- return {
276
- title: "OuterLayerContainmentMergeSolver",
277
- coordinateSystem: "cartesian",
278
- rects: this.outputNodes.map((node) => {
279
- const colors = getColorForZLayer(node.availableZ)
280
- const isPromoted = this.promotedNodeIds.has(node.capacityMeshNodeId)
281
- const isResidual = this.residualNodeIds.has(node.capacityMeshNodeId)
282
-
283
- return {
284
- center: node.center,
285
- width: node.width,
286
- height: node.height,
287
- stroke: isPromoted
288
- ? "rgba(22, 163, 74, 0.95)"
289
- : isResidual
290
- ? "rgba(37, 99, 235, 0.95)"
291
- : colors.stroke,
292
- fill: node._containsObstacle
293
- ? "rgba(239, 68, 68, 0.35)"
294
- : isPromoted
295
- ? "rgba(34, 197, 94, 0.28)"
296
- : isResidual
297
- ? "rgba(59, 130, 246, 0.18)"
298
- : colors.fill,
299
- layer: `z${node.availableZ.join(",")}`,
300
- label: [
301
- `node ${node.capacityMeshNodeId}`,
302
- `z:${node.availableZ.join(",")}`,
303
- ].join("\n"),
304
- }
305
- }),
306
- points: [],
307
- lines: [],
308
- texts: [],
309
- }
310
- }
311
- }
@@ -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
- }