diff --git a/test-assets/board109-outline.json b/test-assets/board109-outline.json new file mode 100644 index 00000000..9710e17b --- /dev/null +++ b/test-assets/board109-outline.json @@ -0,0 +1,396 @@ +{ + "bounds": { + "minX": -31, + "maxX": 31, + "minY": -31, + "maxY": 31 + }, + "outline": [ + { + "x": 30, + "y": 0 + }, + { + "x": 29.935767697158106, + "y": 1.962093876904292 + }, + { + "x": 29.74334584121431, + "y": 3.915785766601547 + }, + { + "x": 29.423558412096913, + "y": 5.852709660483847 + }, + { + "x": 28.97777478867205, + "y": 7.764571353075622 + }, + { + "x": 28.40790388485317, + "y": 9.643183959094848 + }, + { + "x": 27.716385975338603, + "y": 11.480502970952694 + }, + { + "x": 26.906182245980652, + "y": 13.268660706570037 + }, + { + "x": 25.98076211353316, + "y": 14.999999999999998 + }, + { + "x": 24.944088369076358, + "y": 16.667106990588067 + }, + { + "x": 23.800600208737055, + "y": 18.26284287026162 + }, + { + "x": 22.555194224369323, + "y": 19.780374453002064 + }, + { + "x": 21.213203435596427, + "y": 21.213203435596423 + }, + { + "x": 19.780374453002064, + "y": 22.555194224369323 + }, + { + "x": 18.26284287026162, + "y": 23.800600208737055 + }, + { + "x": 16.66710699058807, + "y": 24.944088369076354 + }, + { + "x": 15.000000000000004, + "y": 25.980762113533157 + }, + { + "x": 13.268660706570037, + "y": 26.906182245980652 + }, + { + "x": 11.480502970952696, + "y": 27.716385975338603 + }, + { + "x": 9.64318395909485, + "y": 28.407903884853166 + }, + { + "x": 7.764571353075622, + "y": 28.97777478867205 + }, + { + "x": 5.85270966048385, + "y": 29.423558412096913 + }, + { + "x": 3.915785766601551, + "y": 29.74334584121431 + }, + { + "x": 1.9620938769042982, + "y": 29.935767697158106 + }, + { + "x": 1.83697019872103e-15, + "y": 30 + }, + { + "x": -1.9620938769042944, + "y": 29.935767697158106 + }, + { + "x": -3.915785766601548, + "y": 29.74334584121431 + }, + { + "x": -5.852709660483846, + "y": 29.423558412096913 + }, + { + "x": -7.764571353075619, + "y": 28.97777478867205 + }, + { + "x": -9.643183959094848, + "y": 28.40790388485317 + }, + { + "x": -11.480502970952685, + "y": 27.716385975338607 + }, + { + "x": -13.268660706570033, + "y": 26.906182245980652 + }, + { + "x": -14.999999999999995, + "y": 25.98076211353316 + }, + { + "x": -16.66710699058807, + "y": 24.944088369076354 + }, + { + "x": -18.26284287026162, + "y": 23.800600208737055 + }, + { + "x": -19.780374453002064, + "y": 22.555194224369323 + }, + { + "x": -21.213203435596423, + "y": 21.213203435596427 + }, + { + "x": -22.55519422436932, + "y": 19.780374453002068 + }, + { + "x": -23.800600208737052, + "y": 18.262842870261625 + }, + { + "x": -24.94408836907635, + "y": 16.667106990588074 + }, + { + "x": -25.98076211353316, + "y": 14.999999999999998 + }, + { + "x": -26.906182245980645, + "y": 13.268660706570051 + }, + { + "x": -27.716385975338603, + "y": 11.480502970952697 + }, + { + "x": -28.407903884853166, + "y": 9.643183959094852 + }, + { + "x": -28.977774788672047, + "y": 7.76457135307563 + }, + { + "x": -29.423558412096913, + "y": 5.852709660483845 + }, + { + "x": -29.74334584121431, + "y": 3.9157857666015596 + }, + { + "x": -29.935767697158106, + "y": 1.9620938769042935 + }, + { + "x": -30, + "y": 3.67394039744206e-15 + }, + { + "x": -29.935767697158106, + "y": -1.962093876904286 + }, + { + "x": -29.74334584121431, + "y": -3.915785766601553 + }, + { + "x": -29.423558412096916, + "y": -5.852709660483837 + }, + { + "x": -28.97777478867205, + "y": -7.764571353075624 + }, + { + "x": -28.40790388485317, + "y": -9.643183959094847 + }, + { + "x": -27.716385975338607, + "y": -11.48050297095269 + }, + { + "x": -26.906182245980645, + "y": -13.268660706570044 + }, + { + "x": -25.980762113533164, + "y": -14.999999999999993 + }, + { + "x": -24.944088369076365, + "y": -16.66710699058806 + }, + { + "x": -23.800600208737055, + "y": -18.26284287026162 + }, + { + "x": -22.555194224369323, + "y": -19.780374453002064 + }, + { + "x": -21.213203435596437, + "y": -21.213203435596412 + }, + { + "x": -19.78037445300207, + "y": -22.55519422436932 + }, + { + "x": -18.262842870261625, + "y": -23.80060020873705 + }, + { + "x": -16.667106990588067, + "y": -24.944088369076358 + }, + { + "x": -15.000000000000014, + "y": -25.980762113533153 + }, + { + "x": -13.26866070657004, + "y": -26.90618224598065 + }, + { + "x": -11.480502970952685, + "y": -27.716385975338607 + }, + { + "x": -9.643183959094854, + "y": -28.407903884853166 + }, + { + "x": -7.764571353075619, + "y": -28.97777478867205 + }, + { + "x": -5.8527096604838595, + "y": -29.42355841209691 + }, + { + "x": -3.915785766601549, + "y": -29.74334584121431 + }, + { + "x": -1.962093876904282, + "y": -29.935767697158106 + }, + { + "x": -5.510910596163089e-15, + "y": -30 + }, + { + "x": 1.962093876904271, + "y": -29.93576769715811 + }, + { + "x": 3.915785766601538, + "y": -29.743345841214314 + }, + { + "x": 5.852709660483849, + "y": -29.423558412096913 + }, + { + "x": 7.764571353075609, + "y": -28.977774788672054 + }, + { + "x": 9.643183959094845, + "y": -28.40790388485317 + }, + { + "x": 11.480502970952674, + "y": -27.71638597533861 + }, + { + "x": 13.268660706570031, + "y": -26.906182245980656 + }, + { + "x": 15.000000000000004, + "y": -25.980762113533157 + }, + { + "x": 16.667106990588056, + "y": -24.944088369076365 + }, + { + "x": 18.262842870261597, + "y": -23.800600208737073 + }, + { + "x": 19.78037445300207, + "y": -22.555194224369316 + }, + { + "x": 21.21320343559642, + "y": -21.21320343559643 + }, + { + "x": 22.555194224369323, + "y": -19.78037445300206 + }, + { + "x": 23.80060020873705, + "y": -18.262842870261625 + }, + { + "x": 24.944088369076344, + "y": -16.66710699058809 + }, + { + "x": 25.980762113533153, + "y": -15.000000000000014 + }, + { + "x": 26.90618224598065, + "y": -13.268660706570042 + }, + { + "x": 27.716385975338607, + "y": -11.480502970952687 + }, + { + "x": 28.407903884853166, + "y": -9.643183959094856 + }, + { + "x": 28.977774788672043, + "y": -7.764571353075647 + }, + { + "x": 29.42355841209691, + "y": -5.852709660483861 + }, + { + "x": 29.74334584121431, + "y": -3.9157857666015508 + }, + { + "x": 29.935767697158106, + "y": -1.962093876904284 + } + ], + "minTraceWidth": 0.15, + "minBoardEdgeClearance": 0.3 +} diff --git a/tests/__snapshots__/compute-inverse-rects-board109.snap.svg b/tests/__snapshots__/compute-inverse-rects-board109.snap.svg new file mode 100644 index 00000000..356ff432 --- /dev/null +++ b/tests/__snapshots__/compute-inverse-rects-board109.snap.svg @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/tests/compute-inverse-rects-board109.test.ts b/tests/compute-inverse-rects-board109.test.ts new file mode 100644 index 00000000..e94cd41e --- /dev/null +++ b/tests/compute-inverse-rects-board109.test.ts @@ -0,0 +1,45 @@ +import { expect, test } from "bun:test" +import { computeInverseRects } from "../lib/solvers/RectDiffSeedingSolver/computeInverseRects" +import { containsPoint, EPS } from "../lib/utils/rectdiff-geometry" +import board109Outline from "../test-assets/board109-outline.json" +import { getBoard109OutlineSnapshot } from "./fixtures/getBoard109OutlineSnapshot" + +// Captured from tscircuit-autorouter's bugreport109-book-reading-clip-lamp.srj.json. +test("board109's circular outline does not generate floating-point sliver blockers", async () => { + const clearance = board109Outline.minBoardEdgeClearance + const rects = computeInverseRects( + { + x: board109Outline.bounds.minX - clearance, + y: board109Outline.bounds.minY - clearance, + width: + board109Outline.bounds.maxX - + board109Outline.bounds.minX + + clearance * 2, + height: + board109Outline.bounds.maxY - + board109Outline.bounds.minY + + clearance * 2, + }, + board109Outline.outline, + { minGridSize: board109Outline.minTraceWidth }, + ) + + await expect(getBoard109OutlineSnapshot()).toMatchSvgSnapshot( + import.meta.path, + ) + + // The original 96-vertex outline generated 19,985 rectangles, including + // 14,696 thinner than EPS. A compact perimeter decomposition is sufficient. + expect(rects.length).toBeLessThan(512) + expect(rects.every((rect) => rect.width > EPS && rect.height > EPS)).toBe( + true, + ) + + for (let index = 0; index < 96; index++) { + const angle = (index * Math.PI * 2) / 96 + const exterior = { x: 30.8 * Math.cos(angle), y: 30.8 * Math.sin(angle) } + const interior = { x: 29 * Math.cos(angle), y: 29 * Math.sin(angle) } + expect(rects.some((rect) => containsPoint(rect, exterior))).toBe(true) + expect(rects.some((rect) => containsPoint(rect, interior))).toBe(false) + } +}) diff --git a/tests/fixtures/getBoard109OutlineSnapshot.ts b/tests/fixtures/getBoard109OutlineSnapshot.ts new file mode 100644 index 00000000..ad31c1b5 --- /dev/null +++ b/tests/fixtures/getBoard109OutlineSnapshot.ts @@ -0,0 +1,41 @@ +import { getSvgFromGraphicsObject } from "graphics-debug" +import { RectDiffPipeline } from "../../lib/RectDiffPipeline" +import { buildObstacleIndexesByLayer } from "../../lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes" +import { RectDiffSeedingSolver } from "../../lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver" +import type { SimpleRouteJson } from "../../lib/types/srj-types" +import board109Outline from "../../test-assets/board109-outline.json" + +/** Capture the native void-mask visualization before any grid placements. */ +export function getBoard109OutlineSnapshot(): string { + const simpleRouteJson: SimpleRouteJson = { + ...board109Outline, + layerCount: 2, + obstacles: [], + connections: [], + } + const pipeline = new RectDiffPipeline({ + simpleRouteJson, + maxGapFillPasses: 1, + }) + pipeline.setup() + + // Use the pipeline's actual board-edge clearance padding, including the + // overlapping blockers produced by the original floating-point slivers. + const boardVoidRects = pipeline.boardVoidRects + if (!boardVoidRects) { + throw new Error("Board109 did not generate an outline void mask") + } + const solver = new RectDiffSeedingSolver({ + simpleRouteJson, + boardVoidRects, + ...buildObstacleIndexesByLayer({ srj: simpleRouteJson, boardVoidRects }), + }) + solver.setup() + + return getSvgFromGraphicsObject(solver.visualize(), { + backgroundColor: "white", + includeTextLabels: false, + svgWidth: 800, + svgHeight: 800, + }) +}