@tscircuit/rectdiff 0.0.24 → 0.0.26

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 (31) hide show
  1. package/.github/workflows/bun-pver-release.yml +45 -24
  2. package/AGENTS.md +23 -0
  3. package/dist/index.d.ts +26 -0
  4. package/dist/index.js +414 -191
  5. package/lib/RectDiffPipeline.ts +23 -0
  6. package/lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts +311 -0
  7. package/lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts +23 -23
  8. package/lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts +9 -10
  9. package/lib/solvers/RectDiffSeedingSolver/computeEdgeCandidates3D.ts +22 -19
  10. package/lib/solvers/RectDiffSeedingSolver/longestFreeSpanAroundZ.ts +1 -1
  11. package/lib/types/srj-types.ts +1 -0
  12. package/lib/utils/expandRectFromSeed.ts +8 -10
  13. package/lib/utils/isFullyOccupiedAtPoint.ts +2 -2
  14. package/lib/utils/rectdiff-geometry.ts +13 -20
  15. package/package.json +3 -1
  16. package/pages/pour.page.tsx +18 -0
  17. package/scripts/benchmark-slow-problem.ts +94 -0
  18. package/test-assets/bugreport49-634662.json +412 -0
  19. package/test-assets/keyboard4.json +16165 -0
  20. package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +1 -1
  21. package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
  22. package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
  23. package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +1 -1
  24. package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +1 -1
  25. package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +1 -1
  26. package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
  27. package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
  28. package/tests/solver/bugreport35-191db9/__snapshots__/bugreport35-191db9.snap.svg +1 -1
  29. package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +1 -1
  30. package/tests/solver/bugreport49-634662/__snapshots__/bugreport49-634662.snap.svg +44 -0
  31. package/tests/solver/bugreport49-634662/bugreport49-634662.test.ts +134 -0
@@ -30,27 +30,20 @@ export function distancePointToRectEdges(
30
30
  p: { x: number; y: number },
31
31
  r: XYRect,
32
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))
33
+ const minX = r.x
34
+ const maxX = r.x + r.width
35
+ const minY = r.y
36
+ const maxY = r.y + r.height
37
+
38
+ if (p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY) {
39
+ return Math.min(p.x - minX, maxX - p.x, p.y - minY, maxY - p.y)
52
40
  }
53
- return best
41
+
42
+ const dx = p.x < minX ? minX - p.x : p.x > maxX ? p.x - maxX : 0
43
+ const dy = p.y < minY ? minY - p.y : p.y > maxY ? p.y - maxY : 0
44
+ if (dx === 0) return dy
45
+ if (dy === 0) return dx
46
+ return Math.hypot(dx, dy)
54
47
  }
55
48
 
56
49
  /** Find the intersection of two 1D intervals, or null if they don't overlap. */
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@tscircuit/rectdiff",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
6
7
  "scripts": {
7
8
  "start": "cosmos",
8
9
  "build": "tsup-node ./lib/index.ts --format esm --dts",
9
10
  "build:site": "cosmos-export",
11
+ "benchmark:slow-problem": "bun scripts/benchmark-slow-problem.ts test-assets/keyboard4.json",
10
12
  "format": "biome format --write .",
11
13
  "format:check": "biome format ."
12
14
  },
@@ -0,0 +1,18 @@
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
+ }
@@ -0,0 +1,94 @@
1
+ import { RectDiffPipeline } from "../lib/RectDiffPipeline"
2
+ import type { SimpleRouteJson } from "../lib/types/srj-types"
3
+
4
+ type BenchmarkInput = {
5
+ simpleRouteJson: SimpleRouteJson
6
+ }
7
+
8
+ const args = Bun.argv.slice(2)
9
+ const filePath = args[0] ?? "test-assets/keyboard4.json"
10
+ const repeatArg = args.find((arg) => arg.startsWith("--repeat="))
11
+ const repeatCount = repeatArg
12
+ ? Number.parseInt(repeatArg.split("=")[1]!, 10)
13
+ : 5
14
+
15
+ const data = JSON.parse(await Bun.file(filePath).text()) as
16
+ | BenchmarkInput
17
+ | BenchmarkInput[]
18
+ const input = Array.isArray(data) ? data[0] : data
19
+
20
+ if (!input?.simpleRouteJson) {
21
+ throw new Error(`Expected ${filePath} to contain { simpleRouteJson } input`)
22
+ }
23
+
24
+ const areaScore = (
25
+ meshNodes: ReturnType<RectDiffPipeline["getOutput"]>["meshNodes"],
26
+ ) =>
27
+ meshNodes.reduce(
28
+ (sum, node) =>
29
+ sum + node.width * node.height * (node.availableZ?.length ?? 1),
30
+ 0,
31
+ )
32
+
33
+ const runs: Array<{
34
+ totalMs: number
35
+ gridMs: number
36
+ gapFillMs: number
37
+ nodeCount: number
38
+ area: number
39
+ }> = []
40
+
41
+ for (let i = 0; i < repeatCount; i++) {
42
+ const solver = new RectDiffPipeline(input)
43
+ const t0 = performance.now()
44
+ solver.solve()
45
+ const t1 = performance.now()
46
+ const meshNodes = solver.getOutput().meshNodes
47
+ const stages = solver.getStageStats()
48
+ runs.push({
49
+ totalMs: t1 - t0,
50
+ gridMs: stages.rectDiffGridSolverPipeline?.timeSpent ?? 0,
51
+ gapFillMs: stages.gapFillSolver?.timeSpent ?? 0,
52
+ nodeCount: meshNodes.length,
53
+ area: areaScore(meshNodes),
54
+ })
55
+ }
56
+
57
+ const mean = (values: number[]) =>
58
+ values.reduce((sum, value) => sum + value, 0) / values.length
59
+
60
+ console.log(
61
+ JSON.stringify(
62
+ {
63
+ filePath,
64
+ repeatCount,
65
+ obstacleCount: input.simpleRouteJson.obstacles?.length ?? 0,
66
+ connectionCount: input.simpleRouteJson.connections?.length ?? 0,
67
+ averages: {
68
+ totalMs: Number.parseFloat(
69
+ mean(runs.map((run) => run.totalMs)).toFixed(2),
70
+ ),
71
+ gridMs: Number.parseFloat(
72
+ mean(runs.map((run) => run.gridMs)).toFixed(2),
73
+ ),
74
+ gapFillMs: Number.parseFloat(
75
+ mean(runs.map((run) => run.gapFillMs)).toFixed(2),
76
+ ),
77
+ nodeCount: Number.parseFloat(
78
+ mean(runs.map((run) => run.nodeCount)).toFixed(2),
79
+ ),
80
+ area: Number.parseFloat(mean(runs.map((run) => run.area)).toFixed(2)),
81
+ },
82
+ runs: runs.map((run, index) => ({
83
+ run: index + 1,
84
+ totalMs: Number.parseFloat(run.totalMs.toFixed(2)),
85
+ gridMs: Number.parseFloat(run.gridMs.toFixed(2)),
86
+ gapFillMs: Number.parseFloat(run.gapFillMs.toFixed(2)),
87
+ nodeCount: run.nodeCount,
88
+ area: Number.parseFloat(run.area.toFixed(2)),
89
+ })),
90
+ },
91
+ null,
92
+ 2,
93
+ ),
94
+ )
@@ -0,0 +1,412 @@
1
+ {
2
+ "autorouting_bug_report_id": "6346629c-bb27-4d63-a798-78e850e8572d",
3
+ "reporter_account_id": "9f398687-69d0-4ef4-a48c-bb6dc990e7df",
4
+ "package_id": null,
5
+ "title": "<board#1853 />",
6
+ "simple_route_json": {
7
+ "bounds": {
8
+ "maxX": 5,
9
+ "maxY": 5,
10
+ "minX": -5,
11
+ "minY": -5
12
+ },
13
+ "obstacles": [
14
+ {
15
+ "type": "rect",
16
+ "width": 1,
17
+ "center": {
18
+ "x": -2.15,
19
+ "y": 1.905
20
+ },
21
+ "height": 0.6,
22
+ "layers": ["top"],
23
+ "connectedTo": [
24
+ "pcb_smtpad_0",
25
+ "connectivity_net8",
26
+ "source_trace_3",
27
+ "source_port_8",
28
+ "source_port_0",
29
+ "pcb_smtpad_0",
30
+ "pcb_port_0",
31
+ "pcb_smtpad_8",
32
+ "pcb_port_8"
33
+ ]
34
+ },
35
+ {
36
+ "type": "rect",
37
+ "width": 1,
38
+ "center": {
39
+ "x": -2.15,
40
+ "y": 0.635
41
+ },
42
+ "height": 0.6,
43
+ "layers": ["top"],
44
+ "connectedTo": [
45
+ "pcb_smtpad_1",
46
+ "connectivity_net16",
47
+ "source_trace_6",
48
+ "source_port_11",
49
+ "source_trace_4",
50
+ "source_port_9",
51
+ "source_trace_2",
52
+ "source_port_3",
53
+ "source_trace_0",
54
+ "source_port_1",
55
+ "source_net_0",
56
+ "pcb_smtpad_1",
57
+ "pcb_port_1",
58
+ "pcb_smtpad_3",
59
+ "pcb_port_3",
60
+ "pcb_smtpad_9",
61
+ "pcb_port_9",
62
+ "pcb_smtpad_11",
63
+ "pcb_port_11"
64
+ ]
65
+ },
66
+ {
67
+ "type": "rect",
68
+ "width": 1,
69
+ "center": {
70
+ "x": -2.15,
71
+ "y": -0.635
72
+ },
73
+ "height": 0.6,
74
+ "layers": ["top"],
75
+ "connectedTo": [
76
+ "pcb_smtpad_2",
77
+ "connectivity_net3",
78
+ "source_trace_1",
79
+ "source_port_2",
80
+ "source_net_1",
81
+ "pcb_smtpad_2",
82
+ "pcb_port_2"
83
+ ]
84
+ },
85
+ {
86
+ "type": "rect",
87
+ "width": 1,
88
+ "center": {
89
+ "x": -2.15,
90
+ "y": -1.905
91
+ },
92
+ "height": 0.6,
93
+ "layers": ["top"],
94
+ "connectedTo": [
95
+ "pcb_smtpad_3",
96
+ "connectivity_net16",
97
+ "source_trace_6",
98
+ "source_port_11",
99
+ "source_trace_4",
100
+ "source_port_9",
101
+ "source_trace_2",
102
+ "source_port_3",
103
+ "source_trace_0",
104
+ "source_port_1",
105
+ "source_net_0",
106
+ "pcb_smtpad_1",
107
+ "pcb_port_1",
108
+ "pcb_smtpad_3",
109
+ "pcb_port_3",
110
+ "pcb_smtpad_9",
111
+ "pcb_port_9",
112
+ "pcb_smtpad_11",
113
+ "pcb_port_11"
114
+ ]
115
+ },
116
+ {
117
+ "type": "rect",
118
+ "width": 1,
119
+ "center": {
120
+ "x": 2.15,
121
+ "y": -1.905
122
+ },
123
+ "height": 0.6,
124
+ "layers": ["top"],
125
+ "connectedTo": [
126
+ "pcb_smtpad_4",
127
+ "connectivity_net42",
128
+ "source_port_4",
129
+ "pcb_smtpad_4",
130
+ "pcb_port_4"
131
+ ]
132
+ },
133
+ {
134
+ "type": "rect",
135
+ "width": 1,
136
+ "center": {
137
+ "x": 2.15,
138
+ "y": -0.635
139
+ },
140
+ "height": 0.6,
141
+ "layers": ["top"],
142
+ "connectedTo": [
143
+ "pcb_smtpad_5",
144
+ "connectivity_net13",
145
+ "source_trace_5",
146
+ "source_port_10",
147
+ "source_port_5",
148
+ "pcb_smtpad_5",
149
+ "pcb_port_5",
150
+ "pcb_smtpad_10",
151
+ "pcb_port_10"
152
+ ]
153
+ },
154
+ {
155
+ "type": "rect",
156
+ "width": 1,
157
+ "center": {
158
+ "x": 2.15,
159
+ "y": 0.635
160
+ },
161
+ "height": 0.6,
162
+ "layers": ["top"],
163
+ "connectedTo": [
164
+ "pcb_smtpad_6",
165
+ "connectivity_net43",
166
+ "source_port_6",
167
+ "pcb_smtpad_6",
168
+ "pcb_port_6"
169
+ ]
170
+ },
171
+ {
172
+ "type": "rect",
173
+ "width": 1,
174
+ "center": {
175
+ "x": 2.15,
176
+ "y": 1.905
177
+ },
178
+ "height": 0.6,
179
+ "layers": ["top"],
180
+ "connectedTo": [
181
+ "pcb_smtpad_7",
182
+ "connectivity_net44",
183
+ "source_port_7",
184
+ "pcb_smtpad_7",
185
+ "pcb_port_7"
186
+ ]
187
+ },
188
+ {
189
+ "type": "rect",
190
+ "width": 0.54,
191
+ "center": {
192
+ "x": -2.1499999999999995,
193
+ "y": 3.525
194
+ },
195
+ "height": 0.64,
196
+ "layers": ["top"],
197
+ "connectedTo": [
198
+ "pcb_smtpad_8",
199
+ "connectivity_net8",
200
+ "source_trace_3",
201
+ "source_port_8",
202
+ "source_port_0",
203
+ "pcb_smtpad_0",
204
+ "pcb_port_0",
205
+ "pcb_smtpad_8",
206
+ "pcb_port_8"
207
+ ]
208
+ },
209
+ {
210
+ "type": "rect",
211
+ "width": 0.54,
212
+ "center": {
213
+ "x": -1.1299999999999997,
214
+ "y": 3.525
215
+ },
216
+ "height": 0.64,
217
+ "layers": ["top"],
218
+ "connectedTo": [
219
+ "pcb_smtpad_9",
220
+ "connectivity_net16",
221
+ "source_trace_6",
222
+ "source_port_11",
223
+ "source_trace_4",
224
+ "source_port_9",
225
+ "source_trace_2",
226
+ "source_port_3",
227
+ "source_trace_0",
228
+ "source_port_1",
229
+ "source_net_0",
230
+ "pcb_smtpad_1",
231
+ "pcb_port_1",
232
+ "pcb_smtpad_3",
233
+ "pcb_port_3",
234
+ "pcb_smtpad_9",
235
+ "pcb_port_9",
236
+ "pcb_smtpad_11",
237
+ "pcb_port_11"
238
+ ]
239
+ },
240
+ {
241
+ "type": "rect",
242
+ "width": 0.64,
243
+ "center": {
244
+ "x": 3.9699999999999998,
245
+ "y": -0.635
246
+ },
247
+ "height": 0.54,
248
+ "layers": ["top"],
249
+ "connectedTo": [
250
+ "pcb_smtpad_10",
251
+ "connectivity_net13",
252
+ "source_trace_5",
253
+ "source_port_10",
254
+ "source_port_5",
255
+ "pcb_smtpad_5",
256
+ "pcb_port_5",
257
+ "pcb_smtpad_10",
258
+ "pcb_port_10"
259
+ ]
260
+ },
261
+ {
262
+ "type": "rect",
263
+ "width": 0.64,
264
+ "center": {
265
+ "x": 3.9699999999999998,
266
+ "y": 0.385
267
+ },
268
+ "height": 0.54,
269
+ "layers": ["top"],
270
+ "connectedTo": [
271
+ "pcb_smtpad_11",
272
+ "connectivity_net16",
273
+ "source_trace_6",
274
+ "source_port_11",
275
+ "source_trace_4",
276
+ "source_port_9",
277
+ "source_trace_2",
278
+ "source_port_3",
279
+ "source_trace_0",
280
+ "source_port_1",
281
+ "source_net_0",
282
+ "pcb_smtpad_1",
283
+ "pcb_port_1",
284
+ "pcb_smtpad_3",
285
+ "pcb_port_3",
286
+ "pcb_smtpad_9",
287
+ "pcb_port_9",
288
+ "pcb_smtpad_11",
289
+ "pcb_port_11"
290
+ ]
291
+ },
292
+ {
293
+ "type": "rect",
294
+ "width": 10,
295
+ "center": {
296
+ "x": 0,
297
+ "y": 0
298
+ },
299
+ "height": 10,
300
+ "layers": ["inner1"],
301
+ "connectedTo": ["net.VCC"],
302
+ "isCopperPour": true
303
+ },
304
+ {
305
+ "type": "rect",
306
+ "width": 10,
307
+ "center": {
308
+ "x": 0,
309
+ "y": 0
310
+ },
311
+ "height": 10,
312
+ "layers": ["inner2"],
313
+ "connectedTo": ["net.GND"],
314
+ "isCopperPour": true
315
+ }
316
+ ],
317
+ "layerCount": 4,
318
+ "connections": [
319
+ {
320
+ "name": "source_trace_3",
321
+ "pointsToConnect": [
322
+ {
323
+ "x": -2.1499999999999995,
324
+ "y": 3.525,
325
+ "layer": "top",
326
+ "pointId": "pcb_port_8",
327
+ "pcb_port_id": "pcb_port_8"
328
+ },
329
+ {
330
+ "x": -2.15,
331
+ "y": 1.905,
332
+ "layer": "top",
333
+ "pointId": "pcb_port_0",
334
+ "pcb_port_id": "pcb_port_0"
335
+ }
336
+ ],
337
+ "source_trace_id": "source_trace_3"
338
+ },
339
+ {
340
+ "name": "source_trace_5",
341
+ "pointsToConnect": [
342
+ {
343
+ "x": 3.9699999999999998,
344
+ "y": -0.635,
345
+ "layer": "top",
346
+ "pointId": "pcb_port_10",
347
+ "pcb_port_id": "pcb_port_10"
348
+ },
349
+ {
350
+ "x": 2.15,
351
+ "y": -0.635,
352
+ "layer": "top",
353
+ "pointId": "pcb_port_5",
354
+ "pcb_port_id": "pcb_port_5"
355
+ }
356
+ ],
357
+ "source_trace_id": "source_trace_5"
358
+ },
359
+ {
360
+ "name": "source_net_0",
361
+ "netConnectionName": "net.VCC",
362
+ "pointsToConnect": [
363
+ {
364
+ "x": -2.15,
365
+ "y": 0.635,
366
+ "layer": "top",
367
+ "pointId": "pcb_port_1",
368
+ "pcb_port_id": "pcb_port_1"
369
+ },
370
+ {
371
+ "x": -2.15,
372
+ "y": -1.905,
373
+ "layer": "top",
374
+ "pointId": "pcb_port_3",
375
+ "pcb_port_id": "pcb_port_3"
376
+ },
377
+ {
378
+ "x": -1.1299999999999997,
379
+ "y": 3.525,
380
+ "layer": "top",
381
+ "pointId": "pcb_port_9",
382
+ "pcb_port_id": "pcb_port_9"
383
+ },
384
+ {
385
+ "x": 3.9699999999999998,
386
+ "y": 0.385,
387
+ "layer": "top",
388
+ "pointId": "pcb_port_11",
389
+ "pcb_port_id": "pcb_port_11"
390
+ }
391
+ ]
392
+ },
393
+ {
394
+ "name": "source_net_1",
395
+ "netConnectionName": "net.GND",
396
+ "pointsToConnect": [
397
+ {
398
+ "x": -2.15,
399
+ "y": -0.635,
400
+ "layer": "top",
401
+ "pointId": "pcb_port_2",
402
+ "pcb_port_id": "pcb_port_2"
403
+ }
404
+ ]
405
+ }
406
+ ],
407
+ "minTraceWidth": 0.15
408
+ },
409
+ "circuit_json": null,
410
+ "subcircuit_id": null,
411
+ "created_at": "2026-04-11T17:08:22.076Z"
412
+ }