@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.
Files changed (53) hide show
  1. package/components/SolverDebugger3d.tsx +2 -2
  2. package/dist/index.d.ts +23 -3
  3. package/dist/index.js +291 -80
  4. package/lib/RectDiffPipeline.ts +42 -35
  5. package/lib/buildFinalRectDiffVisualization.ts +46 -0
  6. package/lib/fixtures/twoNodeExpansionFixture.ts +10 -2
  7. package/lib/rectdiff-visualization.ts +2 -1
  8. package/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts +8 -3
  9. package/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts +48 -9
  10. package/lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts +14 -6
  11. package/lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts +16 -5
  12. package/lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts +12 -2
  13. package/lib/solvers/RectDiffSeedingSolver/computeEdgeCandidates3D.ts +53 -13
  14. package/lib/solvers/RectDiffSeedingSolver/layers.ts +9 -5
  15. package/lib/utils/buildOutlineGraphics.ts +39 -0
  16. package/lib/utils/expandRectFromSeed.ts +11 -1
  17. package/lib/utils/finalizeRects.ts +17 -9
  18. package/lib/utils/padRect.ts +11 -0
  19. package/lib/utils/renderObstacleClearance.ts +50 -0
  20. package/package.json +1 -1
  21. package/pages/bugreport11.page.tsx +1 -0
  22. package/tests/board-outline.test.ts +1 -1
  23. package/tests/fixtures/makeSimpleRouteOutlineGraphics.ts +5 -1
  24. package/tests/should-expand-node.test.ts +9 -1
  25. package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +1 -1
  26. package/tests/solver/bugreport01-be84eb/__snapshots__/bugreport01-be84eb.snap.svg +2 -2
  27. package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +2 -2
  28. package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
  29. package/tests/solver/bugreport07-d3f3be/__snapshots__/bugreport07-d3f3be.snap.svg +1 -1
  30. package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
  31. package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +2 -2
  32. package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +2 -2
  33. package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +1 -1
  34. package/tests/solver/bugreport11-b2de3c/bugreport11-b2de3c-clearance-equivalence.test.ts +52 -0
  35. package/tests/solver/bugreport12-35ce1c/__snapshots__/bugreport12-35ce1c.snap.svg +1 -1
  36. package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +2 -2
  37. package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +2 -2
  38. package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
  39. package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
  40. package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +1 -1
  41. package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
  42. package/tests/solver/bugreport26-66b0b2/__snapshots__/bugreport26-66b0b2.snap.svg +2 -2
  43. package/tests/solver/bugreport27-dd3734/__snapshots__/bugreport27-dd3734.snap.svg +2 -2
  44. package/tests/solver/bugreport28-18a9ef/__snapshots__/bugreport28-18a9ef.snap.svg +2 -2
  45. package/tests/solver/bugreport29-7deae8/__snapshots__/bugreport29-7deae8.snap.svg +2 -2
  46. package/tests/solver/bugreport30-2174c8/__snapshots__/bugreport30-2174c8.snap.svg +1 -1
  47. package/tests/solver/bugreport33-213d45/__snapshots__/bugreport33-213d45.snap.svg +2 -2
  48. package/tests/solver/bugreport34-e9dea2/__snapshots__/bugreport34-e9dea2.snap.svg +2 -2
  49. package/tests/solver/bugreport35-191db9/__snapshots__/bugreport35-191db9.snap.svg +2 -2
  50. package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +1 -1
  51. package/tests/solver/interaction/__snapshots__/interaction.snap.svg +1 -1
  52. package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
  53. 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
- export function buildZIndexMap(srj: SimpleRouteJson) {
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
- (srj.obstacles ?? []).flatMap((o) => o.layers ?? []),
30
+ (params.obstacles ?? []).flatMap((o) => o.layers ?? []),
27
31
  )
28
- const declaredLayerCount = Math.max(1, srj.layerCount || names.length || 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 as any
82
- const h = ob.height as any
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
- srj: SimpleRouteJson
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.srj.obstacles ?? []) {
27
- const rect = obstacleToXYRect(obstacle as any)
28
- if (!rect) continue
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 as any, zIndexByName)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/rectdiff",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -8,6 +8,7 @@ export default () => {
8
8
  () =>
9
9
  new RectDiffPipeline({
10
10
  simpleRouteJson: simpleRouteJson.simple_route_json,
11
+ obstacleClearance: 0.015,
11
12
  }),
12
13
  [],
13
14
  )
@@ -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 as any,
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: SimpleRouteJson,
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(input.srj)
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
  {