@tscircuit/rectdiff 0.0.22 → 0.0.24
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.
- package/components/SolverDebugger3d.tsx +2 -2
- package/dist/index.d.ts +23 -3
- package/dist/index.js +291 -80
- package/lib/RectDiffPipeline.ts +42 -35
- package/lib/buildFinalRectDiffVisualization.ts +46 -0
- package/lib/fixtures/twoNodeExpansionFixture.ts +10 -2
- package/lib/rectdiff-visualization.ts +2 -1
- package/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts +8 -3
- package/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts +48 -9
- package/lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts +14 -6
- package/lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts +16 -5
- package/lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts +12 -2
- package/lib/solvers/RectDiffSeedingSolver/computeEdgeCandidates3D.ts +53 -13
- package/lib/solvers/RectDiffSeedingSolver/layers.ts +9 -5
- package/lib/utils/buildOutlineGraphics.ts +39 -0
- package/lib/utils/expandRectFromSeed.ts +11 -1
- package/lib/utils/finalizeRects.ts +17 -9
- package/lib/utils/padRect.ts +11 -0
- package/lib/utils/renderObstacleClearance.ts +50 -0
- package/package.json +1 -1
- package/pages/bugreport11.page.tsx +1 -0
- package/tests/board-outline.test.ts +1 -1
- package/tests/fixtures/makeSimpleRouteOutlineGraphics.ts +5 -1
- package/tests/should-expand-node.test.ts +9 -1
- package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +1 -1
- package/tests/solver/bugreport01-be84eb/__snapshots__/bugreport01-be84eb.snap.svg +2 -2
- package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +2 -2
- package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
- package/tests/solver/bugreport07-d3f3be/__snapshots__/bugreport07-d3f3be.snap.svg +1 -1
- package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
- package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +2 -2
- package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +2 -2
- package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +1 -1
- package/tests/solver/bugreport11-b2de3c/bugreport11-b2de3c-clearance-equivalence.test.ts +52 -0
- package/tests/solver/bugreport12-35ce1c/__snapshots__/bugreport12-35ce1c.snap.svg +1 -1
- package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +2 -2
- package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +2 -2
- package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
- package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
- package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +1 -1
- package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
- package/tests/solver/bugreport26-66b0b2/__snapshots__/bugreport26-66b0b2.snap.svg +2 -2
- package/tests/solver/bugreport27-dd3734/__snapshots__/bugreport27-dd3734.snap.svg +2 -2
- package/tests/solver/bugreport28-18a9ef/__snapshots__/bugreport28-18a9ef.snap.svg +2 -2
- package/tests/solver/bugreport29-7deae8/__snapshots__/bugreport29-7deae8.snap.svg +2 -2
- package/tests/solver/bugreport30-2174c8/__snapshots__/bugreport30-2174c8.snap.svg +1 -1
- package/tests/solver/bugreport33-213d45/__snapshots__/bugreport33-213d45.snap.svg +2 -2
- package/tests/solver/bugreport34-e9dea2/__snapshots__/bugreport34-e9dea2.snap.svg +2 -2
- package/tests/solver/bugreport35-191db9/__snapshots__/bugreport35-191db9.snap.svg +2 -2
- package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +1 -1
- package/tests/solver/interaction/__snapshots__/interaction.snap.svg +1 -1
- package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
- package/tests/solver/pcb_trace_id-should-return-root-connection-name/__snapshots__/pcb_trace_id-should-return-root-connection-name.snap.svg +1 -1
|
@@ -21,11 +21,15 @@ export function canonicalizeLayerOrder(names: string[]) {
|
|
|
21
21
|
})
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// TODO: should not take a srj
|
|
25
|
+
export function buildZIndexMap(params: {
|
|
26
|
+
obstacles?: Obstacle[]
|
|
27
|
+
layerCount?: number
|
|
28
|
+
}) {
|
|
25
29
|
const names = canonicalizeLayerOrder(
|
|
26
|
-
(
|
|
30
|
+
(params.obstacles ?? []).flatMap((o) => o.layers ?? []),
|
|
27
31
|
)
|
|
28
|
-
const declaredLayerCount = Math.max(1,
|
|
32
|
+
const declaredLayerCount = Math.max(1, params.layerCount || names.length || 1)
|
|
29
33
|
const fallback = Array.from({ length: declaredLayerCount }, (_, i) =>
|
|
30
34
|
i === 0 ? "top" : i === declaredLayerCount - 1 ? "bottom" : `inner${i}`,
|
|
31
35
|
)
|
|
@@ -78,8 +82,8 @@ export function obstacleZs(ob: Obstacle, zIndexByName: Map<string, number>) {
|
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
export function obstacleToXYRect(ob: Obstacle): XYRect | null {
|
|
81
|
-
const w = ob.width
|
|
82
|
-
const h = ob.height
|
|
85
|
+
const w = ob.width
|
|
86
|
+
const h = ob.height
|
|
83
87
|
if (typeof w !== "number" || typeof h !== "number") return null
|
|
84
88
|
return { x: ob.center.x - w / 2, y: ob.center.y - h / 2, width: w, height: h }
|
|
85
89
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { GraphicsObject, Line } from "graphics-debug"
|
|
2
|
+
import type { SimpleRouteJson } from "../types/srj-types"
|
|
3
|
+
|
|
4
|
+
export type BuildOutlineParams = { srj: SimpleRouteJson }
|
|
5
|
+
|
|
6
|
+
export const buildOutlineGraphics = ({
|
|
7
|
+
srj,
|
|
8
|
+
}: BuildOutlineParams): GraphicsObject => {
|
|
9
|
+
const hasOutline = srj.outline && srj.outline.length > 1
|
|
10
|
+
const lines: NonNullable<Line[]> = hasOutline
|
|
11
|
+
? [
|
|
12
|
+
{
|
|
13
|
+
points: [...srj.outline!, srj.outline![0]!],
|
|
14
|
+
strokeColor: "#111827",
|
|
15
|
+
strokeWidth: 0.1,
|
|
16
|
+
label: "outline",
|
|
17
|
+
},
|
|
18
|
+
]
|
|
19
|
+
: [
|
|
20
|
+
{
|
|
21
|
+
points: [
|
|
22
|
+
{ x: srj.bounds.minX, y: srj.bounds.minY },
|
|
23
|
+
{ x: srj.bounds.maxX, y: srj.bounds.minY },
|
|
24
|
+
{ x: srj.bounds.maxX, y: srj.bounds.maxY },
|
|
25
|
+
{ x: srj.bounds.minX, y: srj.bounds.maxY },
|
|
26
|
+
{ x: srj.bounds.minX, y: srj.bounds.minY },
|
|
27
|
+
],
|
|
28
|
+
strokeColor: "#111827",
|
|
29
|
+
strokeWidth: 0.1,
|
|
30
|
+
label: "bounds",
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
title: "SimpleRoute Outline",
|
|
36
|
+
coordinateSystem: "cartesian",
|
|
37
|
+
lines,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -17,6 +17,16 @@ type ExpandDirectionParams = {
|
|
|
17
17
|
maxAspect: number | null | undefined
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const quantize = (value: number, precision = 1e-6) =>
|
|
21
|
+
Math.round(value / precision) * precision
|
|
22
|
+
|
|
23
|
+
const quantizeRect = (rect: XYRect): XYRect => ({
|
|
24
|
+
x: quantize(rect.x),
|
|
25
|
+
y: quantize(rect.y),
|
|
26
|
+
width: quantize(rect.width),
|
|
27
|
+
height: quantize(rect.height),
|
|
28
|
+
})
|
|
29
|
+
|
|
20
30
|
function maxExpandRight(params: ExpandDirectionParams) {
|
|
21
31
|
const { r, bounds, blockers, maxAspect } = params
|
|
22
32
|
// Start with board boundary
|
|
@@ -333,7 +343,7 @@ export function expandRectFromSeed(params: {
|
|
|
333
343
|
if (r.width + EPS >= minReq.width && r.height + EPS >= minReq.height) {
|
|
334
344
|
const area = r.width * r.height
|
|
335
345
|
if (area > bestArea) {
|
|
336
|
-
best = r
|
|
346
|
+
best = quantizeRect(r)
|
|
337
347
|
bestArea = area
|
|
338
348
|
}
|
|
339
349
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import type { Obstacle } from "lib/types/srj-types"
|
|
1
2
|
import type { Placed3D, Rect3d, XYRect } from "../rectdiff-types"
|
|
2
|
-
import type { SimpleRouteJson } from "../types/srj-types"
|
|
3
3
|
import {
|
|
4
|
-
buildZIndexMap,
|
|
5
4
|
obstacleToXYRect,
|
|
6
5
|
obstacleZs,
|
|
7
6
|
} from "../solvers/RectDiffSeedingSolver/layers"
|
|
8
7
|
|
|
9
8
|
export function finalizeRects(params: {
|
|
10
9
|
placed: Placed3D[]
|
|
11
|
-
|
|
10
|
+
obstacles: Obstacle[]
|
|
12
11
|
boardVoidRects: XYRect[]
|
|
12
|
+
zIndexByName: Map<string, number>
|
|
13
|
+
obstacleClearance?: number
|
|
13
14
|
}): Rect3d[] {
|
|
14
15
|
// Convert all placed (free space) nodes to output format
|
|
15
16
|
const out: Rect3d[] = params.placed.map((p) => ({
|
|
@@ -20,23 +21,30 @@ export function finalizeRects(params: {
|
|
|
20
21
|
zLayers: [...p.zLayers].sort((a, b) => a - b),
|
|
21
22
|
}))
|
|
22
23
|
|
|
23
|
-
const { zIndexByName } = buildZIndexMap(params.srj)
|
|
24
24
|
const layersByKey = new Map<string, { rect: XYRect; layers: Set<number> }>()
|
|
25
25
|
|
|
26
|
-
for (const obstacle of params.
|
|
27
|
-
const
|
|
28
|
-
if (!
|
|
26
|
+
for (const obstacle of params.obstacles ?? []) {
|
|
27
|
+
const baseRect = obstacleToXYRect(obstacle)
|
|
28
|
+
if (!baseRect) continue
|
|
29
|
+
const rect = params.obstacleClearance
|
|
30
|
+
? {
|
|
31
|
+
x: baseRect.x - params.obstacleClearance,
|
|
32
|
+
y: baseRect.y - params.obstacleClearance,
|
|
33
|
+
width: baseRect.width + 2 * params.obstacleClearance,
|
|
34
|
+
height: baseRect.height + 2 * params.obstacleClearance,
|
|
35
|
+
}
|
|
36
|
+
: baseRect
|
|
29
37
|
const zLayers =
|
|
30
38
|
obstacle.zLayers?.length && obstacle.zLayers.length > 0
|
|
31
39
|
? obstacle.zLayers
|
|
32
|
-
: obstacleZs(obstacle
|
|
40
|
+
: obstacleZs(obstacle, params.zIndexByName)
|
|
33
41
|
const key = `${rect.x}:${rect.y}:${rect.width}:${rect.height}`
|
|
34
42
|
let entry = layersByKey.get(key)
|
|
35
43
|
if (!entry) {
|
|
36
44
|
entry = { rect, layers: new Set() }
|
|
37
45
|
layersByKey.set(key, entry)
|
|
38
46
|
}
|
|
39
|
-
zLayers.forEach((layer) => entry!.layers.add(layer))
|
|
47
|
+
zLayers.forEach((layer: number) => entry!.layers.add(layer))
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
for (const { rect, layers } of layersByKey.values()) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { XYRect } from "../rectdiff-types"
|
|
2
|
+
|
|
3
|
+
export const padRect = (rect: XYRect, clearance: number): XYRect => {
|
|
4
|
+
if (!clearance || clearance <= 0) return rect
|
|
5
|
+
return {
|
|
6
|
+
x: rect.x - clearance,
|
|
7
|
+
y: rect.y - clearance,
|
|
8
|
+
width: rect.width + 2 * clearance,
|
|
9
|
+
height: rect.height + 2 * clearance,
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { SimpleRouteJson } from "lib/types/srj-types"
|
|
2
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pure helper that returns clearance rect graphics; does not mutate inputs.
|
|
6
|
+
*/
|
|
7
|
+
export const buildObstacleClearanceGraphics = (params: {
|
|
8
|
+
srj: SimpleRouteJson
|
|
9
|
+
clearance: number | undefined
|
|
10
|
+
}): GraphicsObject => {
|
|
11
|
+
const { srj, clearance } = params
|
|
12
|
+
const c = clearance ?? 0
|
|
13
|
+
if (c <= 0) {
|
|
14
|
+
return {
|
|
15
|
+
title: "Obstacle Clearance",
|
|
16
|
+
coordinateSystem: "cartesian",
|
|
17
|
+
rects: [],
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const rects: NonNullable<GraphicsObject["rects"]> = []
|
|
22
|
+
|
|
23
|
+
for (const obstacle of srj.obstacles ?? []) {
|
|
24
|
+
if (obstacle.type !== "rect" && obstacle.type !== "oval") continue
|
|
25
|
+
const expanded = {
|
|
26
|
+
x: obstacle.center.x - obstacle.width / 2 - c,
|
|
27
|
+
y: obstacle.center.y - obstacle.height / 2 - c,
|
|
28
|
+
width: obstacle.width + 2 * c,
|
|
29
|
+
height: obstacle.height + 2 * c,
|
|
30
|
+
}
|
|
31
|
+
rects.push({
|
|
32
|
+
center: {
|
|
33
|
+
x: expanded.x + expanded.width / 2,
|
|
34
|
+
y: expanded.y + expanded.height / 2,
|
|
35
|
+
},
|
|
36
|
+
width: expanded.width,
|
|
37
|
+
height: expanded.height,
|
|
38
|
+
stroke: "rgba(202, 138, 4, 0.9)",
|
|
39
|
+
fill: "rgba(234, 179, 8, 0.15)",
|
|
40
|
+
layer: "obstacle-clearance",
|
|
41
|
+
label: `clearance\nz:${(obstacle.zLayers ?? []).join(",") || "all"}`,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
title: "Obstacle Clearance",
|
|
47
|
+
coordinateSystem: "cartesian",
|
|
48
|
+
rects,
|
|
49
|
+
}
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ import { makeCapacityMeshNodeWithLayerInfo } from "./fixtures/makeCapacityMeshNo
|
|
|
6
6
|
|
|
7
7
|
test("board outline snapshot", async () => {
|
|
8
8
|
const solver = new RectDiffPipeline({
|
|
9
|
-
simpleRouteJson: boardWithCutout
|
|
9
|
+
simpleRouteJson: boardWithCutout,
|
|
10
10
|
})
|
|
11
11
|
|
|
12
12
|
// Run to completion
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { GraphicsObject, Line } from "graphics-debug"
|
|
2
2
|
import type { SimpleRouteJson } from "lib/types/srj-types"
|
|
3
|
+
export type SimpleRouteOutlineInput = {
|
|
4
|
+
bounds: SimpleRouteJson["bounds"]
|
|
5
|
+
outline?: SimpleRouteJson["outline"]
|
|
6
|
+
}
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* Creates a GraphicsObject that draws the SRJ outline (or bounds fallback).
|
|
6
10
|
*/
|
|
7
11
|
export const makeSimpleRouteOutlineGraphics = (
|
|
8
|
-
srj:
|
|
12
|
+
srj: SimpleRouteOutlineInput,
|
|
9
13
|
): GraphicsObject => {
|
|
10
14
|
const lines: NonNullable<Line[]> = []
|
|
11
15
|
|
|
@@ -15,7 +15,15 @@ test("RectDiff expansion reproduces the two-node gap fixture", async () => {
|
|
|
15
15
|
expect(meshNodes.length).toBeGreaterThanOrEqual(2)
|
|
16
16
|
|
|
17
17
|
const finalGraphics = makeCapacityMeshNodeWithLayerInfo(meshNodes)
|
|
18
|
-
const outline = makeSimpleRouteOutlineGraphics(
|
|
18
|
+
const outline = makeSimpleRouteOutlineGraphics({
|
|
19
|
+
bounds: {
|
|
20
|
+
minX: input.bounds.x,
|
|
21
|
+
maxX: input.bounds.x + input.bounds.width,
|
|
22
|
+
minY: input.bounds.y,
|
|
23
|
+
maxY: input.bounds.y + input.bounds.height,
|
|
24
|
+
},
|
|
25
|
+
outline: undefined,
|
|
26
|
+
})
|
|
19
27
|
const svg = getSvgFromGraphicsObject(
|
|
20
28
|
mergeGraphics({ rects: finalGraphics.values().toArray().flat() }, outline),
|
|
21
29
|
{
|