@tscircuit/rectdiff 0.0.2 → 0.0.4
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/dist/index.js +47 -7
- package/index.html +2 -2
- package/lib/solvers/rectdiff/engine.ts +11 -2
- package/lib/solvers/rectdiff/layers.ts +39 -11
- package/main.tsx +1 -0
- package/package.json +11 -8
- package/tests/obstacle-extra-layers.test.ts +37 -0
- package/tests/obstacle-zlayers.test.ts +37 -0
- package/vite.config.ts +7 -0
package/dist/index.js
CHANGED
|
@@ -585,19 +585,51 @@ function buildZIndexMap(srj) {
|
|
|
585
585
|
const names = canonicalizeLayerOrder(
|
|
586
586
|
(srj.obstacles ?? []).flatMap((o) => o.layers ?? [])
|
|
587
587
|
);
|
|
588
|
+
const declaredLayerCount = Math.max(1, srj.layerCount || names.length || 1);
|
|
588
589
|
const fallback = Array.from(
|
|
589
|
-
{ length:
|
|
590
|
-
(_, i) => i === 0 ? "top" : i ===
|
|
590
|
+
{ length: declaredLayerCount },
|
|
591
|
+
(_, i) => i === 0 ? "top" : i === declaredLayerCount - 1 ? "bottom" : `inner${i}`
|
|
591
592
|
);
|
|
592
|
-
const
|
|
593
|
+
const ordered = [];
|
|
594
|
+
const seen = /* @__PURE__ */ new Set();
|
|
595
|
+
const push = (n) => {
|
|
596
|
+
const key = n.toLowerCase();
|
|
597
|
+
if (seen.has(key)) return;
|
|
598
|
+
seen.add(key);
|
|
599
|
+
ordered.push(n);
|
|
600
|
+
};
|
|
601
|
+
fallback.forEach(push);
|
|
602
|
+
names.forEach(push);
|
|
603
|
+
const layerNames = ordered.slice(0, declaredLayerCount);
|
|
604
|
+
const clampIndex = (nameLower) => {
|
|
605
|
+
if (layerNames.length <= 1) return 0;
|
|
606
|
+
if (nameLower === "top") return 0;
|
|
607
|
+
if (nameLower === "bottom") return layerNames.length - 1;
|
|
608
|
+
const m = /^inner(\d+)$/i.exec(nameLower);
|
|
609
|
+
if (m) {
|
|
610
|
+
if (layerNames.length <= 2) return layerNames.length - 1;
|
|
611
|
+
const parsed = parseInt(m[1], 10);
|
|
612
|
+
const maxInner = layerNames.length - 2;
|
|
613
|
+
const clampedInner = Math.min(
|
|
614
|
+
maxInner,
|
|
615
|
+
Math.max(1, Number.isFinite(parsed) ? parsed : 1)
|
|
616
|
+
);
|
|
617
|
+
return clampedInner;
|
|
618
|
+
}
|
|
619
|
+
return 0;
|
|
620
|
+
};
|
|
593
621
|
const map = /* @__PURE__ */ new Map();
|
|
594
|
-
layerNames.forEach((n, i) => map.set(n, i));
|
|
622
|
+
layerNames.forEach((n, i) => map.set(n.toLowerCase(), i));
|
|
623
|
+
ordered.slice(layerNames.length).forEach((n) => {
|
|
624
|
+
const key = n.toLowerCase();
|
|
625
|
+
map.set(key, clampIndex(key));
|
|
626
|
+
});
|
|
595
627
|
return { layerNames, zIndexByName: map };
|
|
596
628
|
}
|
|
597
629
|
function obstacleZs(ob, zIndexByName) {
|
|
598
630
|
if (ob.zLayers?.length)
|
|
599
631
|
return Array.from(new Set(ob.zLayers)).sort((a, b) => a - b);
|
|
600
|
-
const fromNames = (ob.layers ?? []).map((n) => zIndexByName.get(n)).filter((v) => typeof v === "number");
|
|
632
|
+
const fromNames = (ob.layers ?? []).map((n) => zIndexByName.get(n.toLowerCase())).filter((v) => typeof v === "number");
|
|
601
633
|
return Array.from(new Set(fromNames)).sort((a, b) => a - b);
|
|
602
634
|
}
|
|
603
635
|
function obstacleToXYRect(ob) {
|
|
@@ -625,8 +657,16 @@ function initState(srj, opts) {
|
|
|
625
657
|
const r = obstacleToXYRect(ob);
|
|
626
658
|
if (!r) continue;
|
|
627
659
|
const zs = obstacleZs(ob, zIndexByName);
|
|
628
|
-
|
|
629
|
-
|
|
660
|
+
const invalidZs = zs.filter((z) => z < 0 || z >= layerCount);
|
|
661
|
+
if (invalidZs.length) {
|
|
662
|
+
throw new Error(
|
|
663
|
+
`RectDiffSolver: obstacle uses z-layer indices ${invalidZs.join(
|
|
664
|
+
","
|
|
665
|
+
)} outside 0-${layerCount - 1}`
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
if ((!ob.zLayers || ob.zLayers.length === 0) && zs.length) ob.zLayers = zs;
|
|
669
|
+
for (const z of zs) obstaclesByLayer[z].push(r);
|
|
630
670
|
}
|
|
631
671
|
const trace = Math.max(0.01, srj.minTraceWidth || 0.15);
|
|
632
672
|
const defaults = {
|
package/index.html
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>React Cosmos
|
|
6
|
+
<title>React Cosmos</title>
|
|
7
7
|
</head>
|
|
8
8
|
<body>
|
|
9
9
|
<div id="root"></div>
|
|
10
|
-
<script type="module" src="/
|
|
10
|
+
<script type="module" src="/main.tsx"></script>
|
|
11
11
|
</body>
|
|
12
12
|
</html>
|
|
@@ -45,8 +45,17 @@ export function initState(
|
|
|
45
45
|
const r = obstacleToXYRect(ob)
|
|
46
46
|
if (!r) continue
|
|
47
47
|
const zs = obstacleZs(ob, zIndexByName)
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
const invalidZs = zs.filter((z) => z < 0 || z >= layerCount)
|
|
49
|
+
if (invalidZs.length) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`RectDiffSolver: obstacle uses z-layer indices ${invalidZs.join(
|
|
52
|
+
",",
|
|
53
|
+
)} outside 0-${layerCount - 1}`,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
// Persist normalized zLayers back onto the shared SRJ so downstream solvers see them.
|
|
57
|
+
if ((!ob.zLayers || ob.zLayers.length === 0) && zs.length) ob.zLayers = zs
|
|
58
|
+
for (const z of zs) obstaclesByLayer[z]!.push(r)
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
const trace = Math.max(0.01, srj.minTraceWidth || 0.15)
|
|
@@ -24,18 +24,46 @@ export function buildZIndexMap(srj: SimpleRouteJson) {
|
|
|
24
24
|
const names = canonicalizeLayerOrder(
|
|
25
25
|
(srj.obstacles ?? []).flatMap((o) => o.layers ?? []),
|
|
26
26
|
)
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
i === 0
|
|
31
|
-
? "top"
|
|
32
|
-
: i === (srj.layerCount || 1) - 1
|
|
33
|
-
? "bottom"
|
|
34
|
-
: `inner${i}`,
|
|
27
|
+
const declaredLayerCount = Math.max(1, srj.layerCount || names.length || 1)
|
|
28
|
+
const fallback = Array.from({ length: declaredLayerCount }, (_, i) =>
|
|
29
|
+
i === 0 ? "top" : i === declaredLayerCount - 1 ? "bottom" : `inner${i}`,
|
|
35
30
|
)
|
|
36
|
-
const
|
|
31
|
+
const ordered: string[] = []
|
|
32
|
+
const seen = new Set<string>()
|
|
33
|
+
const push = (n: string) => {
|
|
34
|
+
const key = n.toLowerCase()
|
|
35
|
+
if (seen.has(key)) return
|
|
36
|
+
seen.add(key)
|
|
37
|
+
ordered.push(n)
|
|
38
|
+
}
|
|
39
|
+
fallback.forEach(push)
|
|
40
|
+
names.forEach(push)
|
|
41
|
+
const layerNames = ordered.slice(0, declaredLayerCount)
|
|
42
|
+
// Clamp any exotic layer names (extra inner layers, mixed casing, etc.)
|
|
43
|
+
// onto the declared layerCount so every obstacle resolves to a legal z index.
|
|
44
|
+
const clampIndex = (nameLower: string) => {
|
|
45
|
+
if (layerNames.length <= 1) return 0
|
|
46
|
+
if (nameLower === "top") return 0
|
|
47
|
+
if (nameLower === "bottom") return layerNames.length - 1
|
|
48
|
+
const m = /^inner(\d+)$/i.exec(nameLower)
|
|
49
|
+
if (m) {
|
|
50
|
+
if (layerNames.length <= 2) return layerNames.length - 1
|
|
51
|
+
const parsed = parseInt(m[1]!, 10)
|
|
52
|
+
const maxInner = layerNames.length - 2
|
|
53
|
+
const clampedInner = Math.min(
|
|
54
|
+
maxInner,
|
|
55
|
+
Math.max(1, Number.isFinite(parsed) ? parsed : 1),
|
|
56
|
+
)
|
|
57
|
+
return clampedInner
|
|
58
|
+
}
|
|
59
|
+
return 0
|
|
60
|
+
}
|
|
37
61
|
const map = new Map<string, number>()
|
|
38
|
-
layerNames.forEach((n, i) => map.set(n, i))
|
|
62
|
+
layerNames.forEach((n, i) => map.set(n.toLowerCase(), i))
|
|
63
|
+
ordered.slice(layerNames.length).forEach((n) => {
|
|
64
|
+
const key = n.toLowerCase()
|
|
65
|
+
map.set(key, clampIndex(key))
|
|
66
|
+
})
|
|
39
67
|
return { layerNames, zIndexByName: map }
|
|
40
68
|
}
|
|
41
69
|
|
|
@@ -43,7 +71,7 @@ export function obstacleZs(ob: Obstacle, zIndexByName: Map<string, number>) {
|
|
|
43
71
|
if (ob.zLayers?.length)
|
|
44
72
|
return Array.from(new Set(ob.zLayers)).sort((a, b) => a - b)
|
|
45
73
|
const fromNames = (ob.layers ?? [])
|
|
46
|
-
.map((n) => zIndexByName.get(n))
|
|
74
|
+
.map((n) => zIndexByName.get(n.toLowerCase()))
|
|
47
75
|
.filter((v): v is number => typeof v === "number")
|
|
48
76
|
return Array.from(new Set(fromNames)).sort((a, b) => a - b)
|
|
49
77
|
}
|
package/main.tsx
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "react-cosmos/client"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/rectdiff",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -15,23 +15,26 @@
|
|
|
15
15
|
"@react-hook/resize-observer": "^2.0.2",
|
|
16
16
|
"@tscircuit/solver-utils": "^0.0.3",
|
|
17
17
|
"@types/bun": "latest",
|
|
18
|
+
"@types/react": "^18",
|
|
19
|
+
"@types/react-dom": "^18",
|
|
18
20
|
"@types/three": "^0.181.0",
|
|
19
21
|
"biome": "^0.3.3",
|
|
20
22
|
"bun-match-svg": "^0.0.14",
|
|
21
23
|
"graphics-debug": "^0.0.70",
|
|
22
24
|
"rbush": "^4.0.1",
|
|
23
|
-
"react": "
|
|
24
|
-
"react-cosmos": "^
|
|
25
|
-
"react-cosmos-plugin-vite": "^
|
|
26
|
-
"react-dom": "
|
|
25
|
+
"react": "18",
|
|
26
|
+
"react-cosmos": "^6.2.3",
|
|
27
|
+
"react-cosmos-plugin-vite": "^6.2.0",
|
|
28
|
+
"react-dom": "18",
|
|
27
29
|
"three": "^0.181.1",
|
|
28
|
-
"tsup": "^8.5.1"
|
|
30
|
+
"tsup": "^8.5.1",
|
|
31
|
+
"vite": "^6.0.11",
|
|
32
|
+
"@vitejs/plugin-react": "^4"
|
|
29
33
|
},
|
|
30
34
|
"peerDependencies": {
|
|
31
35
|
"typescript": "^5"
|
|
32
36
|
},
|
|
33
37
|
"dependencies": {
|
|
34
|
-
"D": "^1.0.0"
|
|
35
|
-
"vite": "^7.2.2"
|
|
38
|
+
"D": "^1.0.0"
|
|
36
39
|
}
|
|
37
40
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import type { SimpleRouteJson } from "../lib/types/srj-types"
|
|
3
|
+
import { RectDiffSolver } from "../lib/solvers/RectDiffSolver"
|
|
4
|
+
|
|
5
|
+
// Legacy SRJs sometimes reference "inner" layers beyond layerCount; ensure we clamp.
|
|
6
|
+
test("RectDiffSolver clamps extra layer names to available z indices", () => {
|
|
7
|
+
const srj: SimpleRouteJson = {
|
|
8
|
+
bounds: { minX: 0, maxX: 5, minY: 0, maxY: 5 },
|
|
9
|
+
connections: [],
|
|
10
|
+
minTraceWidth: 0.2,
|
|
11
|
+
layerCount: 2,
|
|
12
|
+
obstacles: [
|
|
13
|
+
{
|
|
14
|
+
type: "rect",
|
|
15
|
+
center: { x: 1, y: 1 },
|
|
16
|
+
width: 1,
|
|
17
|
+
height: 1,
|
|
18
|
+
layers: ["inner1"],
|
|
19
|
+
connectedTo: [],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
type: "rect",
|
|
23
|
+
center: { x: 3, y: 3 },
|
|
24
|
+
width: 1,
|
|
25
|
+
height: 1,
|
|
26
|
+
layers: ["inner2"],
|
|
27
|
+
connectedTo: [],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const solver = new RectDiffSolver({ simpleRouteJson: srj, mode: "grid" })
|
|
33
|
+
solver.setup()
|
|
34
|
+
|
|
35
|
+
expect(srj.obstacles[0]?.zLayers).toEqual([1])
|
|
36
|
+
expect(srj.obstacles[1]?.zLayers).toEqual([1])
|
|
37
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import type { SimpleRouteJson } from "../lib/types/srj-types"
|
|
3
|
+
import { RectDiffSolver } from "../lib/solvers/RectDiffSolver"
|
|
4
|
+
|
|
5
|
+
// Baseline: plain string layers should be auto-converted to numeric zLayers.
|
|
6
|
+
test("RectDiffSolver maps obstacle layers to numeric zLayers", () => {
|
|
7
|
+
const srj: SimpleRouteJson = {
|
|
8
|
+
bounds: { minX: 0, maxX: 10, minY: 0, maxY: 10 },
|
|
9
|
+
connections: [],
|
|
10
|
+
minTraceWidth: 0.2,
|
|
11
|
+
layerCount: 3,
|
|
12
|
+
obstacles: [
|
|
13
|
+
{
|
|
14
|
+
type: "rect",
|
|
15
|
+
center: { x: 1, y: 1 },
|
|
16
|
+
width: 1,
|
|
17
|
+
height: 1,
|
|
18
|
+
layers: ["top"],
|
|
19
|
+
connectedTo: [],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
type: "rect",
|
|
23
|
+
center: { x: 2, y: 2 },
|
|
24
|
+
width: 1,
|
|
25
|
+
height: 1,
|
|
26
|
+
layers: ["inner1", "bottom"],
|
|
27
|
+
connectedTo: [],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const solver = new RectDiffSolver({ simpleRouteJson: srj, mode: "grid" })
|
|
33
|
+
solver.setup()
|
|
34
|
+
|
|
35
|
+
expect(srj.obstacles[0]?.zLayers).toEqual([0])
|
|
36
|
+
expect(srj.obstacles[1]?.zLayers).toEqual([1, 2])
|
|
37
|
+
})
|