@tscircuit/rectdiff 0.0.8 → 0.0.9
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 +92 -162
- package/dist/index.d.ts +34 -19
- package/dist/index.js +93 -1
- package/lib/RectDiffPipeline.ts +55 -0
- package/lib/index.ts +1 -1
- package/lib/solvers/RectDiffSolver.ts +1 -1
- package/lib/solvers/rectdiff/visualization.ts +66 -0
- package/package.json +2 -2
- package/pages/board-with-cutout.page.tsx +3 -4
- package/pages/bugreport11.page.tsx +8 -3
- package/pages/example01.page.tsx +3 -4
- package/pages/keyboard-bugreport04.page.tsx +8 -4
- package/tests/board-outline.test.ts +2 -2
- package/tests/examples/example01.test.tsx +2 -2
- package/tests/incremental-solver.test.ts +10 -16
- package/tests/obstacle-extra-layers.test.ts +12 -5
- package/tests/obstacle-zlayers.test.ts +13 -5
- package/tests/rect-diff-solver.test.ts +14 -23
|
@@ -560,7 +560,7 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
|
|
|
560
560
|
defaultWireframeOutput = false,
|
|
561
561
|
style,
|
|
562
562
|
}) => {
|
|
563
|
-
const [
|
|
563
|
+
const [renderMode, setRenderMode] = useState<"2d" | "3d">("2d")
|
|
564
564
|
const [rebuildKey, setRebuildKey] = useState(0)
|
|
565
565
|
|
|
566
566
|
const [showRoot, setShowRoot] = useState(defaultShowRoot)
|
|
@@ -610,204 +610,134 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
|
|
|
610
610
|
return () => clearInterval(interval)
|
|
611
611
|
}, [updateMeshNodes, solver])
|
|
612
612
|
|
|
613
|
-
const toggle3d = useCallback(() => setShow3d((s) => !s), [])
|
|
614
613
|
const rebuild = useCallback(() => setRebuildKey((k) => k + 1), [])
|
|
615
614
|
|
|
616
615
|
return (
|
|
617
616
|
<>
|
|
618
617
|
<div style={{ display: "grid", gap: 12, ...style }}>
|
|
619
|
-
|
|
620
|
-
solver={solver as any}
|
|
621
|
-
onSolverCompleted={handleSolverCompleted}
|
|
622
|
-
/>
|
|
623
|
-
|
|
618
|
+
{/* Topbar with Render dropdown */}
|
|
624
619
|
<div
|
|
625
620
|
style={{
|
|
626
621
|
display: "flex",
|
|
627
|
-
gap:
|
|
622
|
+
gap: 12,
|
|
628
623
|
alignItems: "center",
|
|
629
|
-
|
|
624
|
+
padding: "12px 16px",
|
|
625
|
+
background: "#f8fafc",
|
|
626
|
+
borderRadius: 8,
|
|
627
|
+
border: "1px solid #e2e8f0",
|
|
630
628
|
}}
|
|
631
629
|
>
|
|
632
|
-
<
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
border: "1px solid #cbd5e1",
|
|
638
|
-
background: show3d ? "#1e293b" : "#2563eb",
|
|
639
|
-
color: "white",
|
|
640
|
-
cursor: "pointer",
|
|
641
|
-
}}
|
|
642
|
-
>
|
|
643
|
-
{show3d ? "Hide 3D" : "Show 3D"}
|
|
644
|
-
</button>
|
|
645
|
-
{show3d && (
|
|
646
|
-
<button
|
|
647
|
-
onClick={rebuild}
|
|
630
|
+
<label style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
|
631
|
+
<span style={{ fontWeight: 600, fontSize: 14 }}>Render:</span>
|
|
632
|
+
<select
|
|
633
|
+
value={renderMode}
|
|
634
|
+
onChange={(e) => setRenderMode(e.target.value as "2d" | "3d")}
|
|
648
635
|
style={{
|
|
649
|
-
padding: "
|
|
636
|
+
padding: "6px 12px",
|
|
650
637
|
borderRadius: 6,
|
|
651
638
|
border: "1px solid #cbd5e1",
|
|
652
|
-
background: "
|
|
653
|
-
color: "white",
|
|
639
|
+
background: "white",
|
|
654
640
|
cursor: "pointer",
|
|
641
|
+
fontSize: 14,
|
|
655
642
|
}}
|
|
656
|
-
title="Rebuild 3D scene (use after changing solver params)"
|
|
657
643
|
>
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
{/* experiment-like toggles */}
|
|
663
|
-
<label
|
|
664
|
-
style={{
|
|
665
|
-
display: "inline-flex",
|
|
666
|
-
gap: 6,
|
|
667
|
-
alignItems: "center",
|
|
668
|
-
marginLeft: 8,
|
|
669
|
-
}}
|
|
670
|
-
>
|
|
671
|
-
<input
|
|
672
|
-
type="checkbox"
|
|
673
|
-
checked={showRoot}
|
|
674
|
-
onChange={(e) => setShowRoot(e.target.checked)}
|
|
675
|
-
/>
|
|
676
|
-
Root
|
|
677
|
-
</label>
|
|
678
|
-
<label
|
|
679
|
-
style={{ display: "inline-flex", gap: 6, alignItems: "center" }}
|
|
680
|
-
>
|
|
681
|
-
<input
|
|
682
|
-
type="checkbox"
|
|
683
|
-
checked={showObstacles}
|
|
684
|
-
onChange={(e) => setShowObstacles(e.target.checked)}
|
|
685
|
-
/>
|
|
686
|
-
Obstacles
|
|
644
|
+
<option value="2d">2D (Normal)</option>
|
|
645
|
+
<option value="3d">3D View</option>
|
|
646
|
+
</select>
|
|
687
647
|
</label>
|
|
688
|
-
<label
|
|
689
|
-
style={{ display: "inline-flex", gap: 6, alignItems: "center" }}
|
|
690
|
-
>
|
|
691
|
-
<input
|
|
692
|
-
type="checkbox"
|
|
693
|
-
checked={showOutput}
|
|
694
|
-
onChange={(e) => setShowOutput(e.target.checked)}
|
|
695
|
-
/>
|
|
696
|
-
Output
|
|
697
|
-
</label>
|
|
698
|
-
<label
|
|
699
|
-
style={{ display: "inline-flex", gap: 6, alignItems: "center" }}
|
|
700
|
-
>
|
|
701
|
-
<input
|
|
702
|
-
type="checkbox"
|
|
703
|
-
checked={wireframeOutput}
|
|
704
|
-
onChange={(e) => setWireframeOutput(e.target.checked)}
|
|
705
|
-
/>
|
|
706
|
-
Wireframe Output
|
|
707
|
-
</label>
|
|
708
|
-
|
|
709
|
-
{/* Mesh opacity slider */}
|
|
710
|
-
{show3d && (
|
|
711
|
-
<label
|
|
712
|
-
style={{
|
|
713
|
-
display: "inline-flex",
|
|
714
|
-
alignItems: "center",
|
|
715
|
-
gap: 6,
|
|
716
|
-
marginLeft: 8,
|
|
717
|
-
fontSize: 12,
|
|
718
|
-
}}
|
|
719
|
-
>
|
|
720
|
-
Opacity
|
|
721
|
-
<input
|
|
722
|
-
type="range"
|
|
723
|
-
min={0}
|
|
724
|
-
max={1}
|
|
725
|
-
step={0.05}
|
|
726
|
-
value={meshOpacity}
|
|
727
|
-
onChange={(e) => setMeshOpacity(parseFloat(e.target.value))}
|
|
728
|
-
/>
|
|
729
|
-
<span style={{ width: 32, textAlign: "right" }}>
|
|
730
|
-
{meshOpacity.toFixed(2)}
|
|
731
|
-
</span>
|
|
732
|
-
</label>
|
|
733
|
-
)}
|
|
734
648
|
|
|
735
|
-
{
|
|
736
|
-
{show3d && (
|
|
649
|
+
{renderMode === "3d" && (
|
|
737
650
|
<>
|
|
651
|
+
<button
|
|
652
|
+
onClick={rebuild}
|
|
653
|
+
style={{
|
|
654
|
+
padding: "6px 12px",
|
|
655
|
+
borderRadius: 6,
|
|
656
|
+
border: "1px solid #cbd5e1",
|
|
657
|
+
background: "#0f766e",
|
|
658
|
+
color: "white",
|
|
659
|
+
cursor: "pointer",
|
|
660
|
+
fontSize: 14,
|
|
661
|
+
}}
|
|
662
|
+
title="Rebuild 3D scene"
|
|
663
|
+
>
|
|
664
|
+
Rebuild
|
|
665
|
+
</button>
|
|
666
|
+
|
|
738
667
|
<label
|
|
739
668
|
style={{
|
|
740
669
|
display: "inline-flex",
|
|
741
670
|
gap: 6,
|
|
742
671
|
alignItems: "center",
|
|
743
|
-
fontSize:
|
|
672
|
+
fontSize: 13,
|
|
744
673
|
}}
|
|
745
674
|
>
|
|
746
675
|
<input
|
|
747
676
|
type="checkbox"
|
|
748
|
-
checked={
|
|
749
|
-
onChange={(e) =>
|
|
677
|
+
checked={showRoot}
|
|
678
|
+
onChange={(e) => setShowRoot(e.target.checked)}
|
|
750
679
|
/>
|
|
751
|
-
|
|
680
|
+
Root
|
|
752
681
|
</label>
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
gap: 6,
|
|
785
|
-
alignItems: "center",
|
|
786
|
-
fontSize: 12,
|
|
787
|
-
}}
|
|
788
|
-
>
|
|
789
|
-
<input
|
|
790
|
-
type="checkbox"
|
|
791
|
-
checked={showBorders}
|
|
792
|
-
disabled={wireframeOutput}
|
|
793
|
-
onChange={(e) => setShowBorders(e.target.checked)}
|
|
794
|
-
/>
|
|
795
|
-
<span
|
|
682
|
+
<label
|
|
683
|
+
style={{
|
|
684
|
+
display: "inline-flex",
|
|
685
|
+
gap: 6,
|
|
686
|
+
alignItems: "center",
|
|
687
|
+
fontSize: 13,
|
|
688
|
+
}}
|
|
689
|
+
>
|
|
690
|
+
<input
|
|
691
|
+
type="checkbox"
|
|
692
|
+
checked={showObstacles}
|
|
693
|
+
onChange={(e) => setShowObstacles(e.target.checked)}
|
|
694
|
+
/>
|
|
695
|
+
Obstacles
|
|
696
|
+
</label>
|
|
697
|
+
<label
|
|
698
|
+
style={{
|
|
699
|
+
display: "inline-flex",
|
|
700
|
+
gap: 6,
|
|
701
|
+
alignItems: "center",
|
|
702
|
+
fontSize: 13,
|
|
703
|
+
}}
|
|
704
|
+
>
|
|
705
|
+
<input
|
|
706
|
+
type="checkbox"
|
|
707
|
+
checked={showOutput}
|
|
708
|
+
onChange={(e) => setShowOutput(e.target.checked)}
|
|
709
|
+
/>
|
|
710
|
+
Output
|
|
711
|
+
</label>
|
|
712
|
+
<label
|
|
796
713
|
style={{
|
|
797
|
-
|
|
714
|
+
display: "inline-flex",
|
|
715
|
+
gap: 6,
|
|
716
|
+
alignItems: "center",
|
|
717
|
+
fontSize: 13,
|
|
798
718
|
}}
|
|
799
719
|
>
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
720
|
+
<input
|
|
721
|
+
type="checkbox"
|
|
722
|
+
checked={wireframeOutput}
|
|
723
|
+
onChange={(e) => setWireframeOutput(e.target.checked)}
|
|
724
|
+
/>
|
|
725
|
+
Wireframe
|
|
726
|
+
</label>
|
|
727
|
+
</>
|
|
803
728
|
)}
|
|
804
|
-
|
|
805
|
-
<div style={{ fontSize: 12, color: "#334155", marginLeft: 6 }}>
|
|
806
|
-
Drag to orbit · Wheel to zoom · Right-drag to pan
|
|
807
|
-
</div>
|
|
808
729
|
</div>
|
|
809
730
|
|
|
810
|
-
{
|
|
731
|
+
{/* Render 2D view */}
|
|
732
|
+
{renderMode === "2d" && (
|
|
733
|
+
<GenericSolverDebugger
|
|
734
|
+
solver={solver as any}
|
|
735
|
+
onSolverCompleted={handleSolverCompleted}
|
|
736
|
+
/>
|
|
737
|
+
)}
|
|
738
|
+
|
|
739
|
+
{/* Render 3D view */}
|
|
740
|
+
{renderMode === "3d" && (
|
|
811
741
|
<ThreeBoardView
|
|
812
742
|
key={rebuildKey}
|
|
813
743
|
nodes={meshNodes}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _tscircuit_solver_utils from '@tscircuit/solver-utils';
|
|
2
|
+
import { BaseSolver, BasePipelineSolver } from '@tscircuit/solver-utils';
|
|
2
3
|
import { GraphicsObject } from 'graphics-debug';
|
|
3
4
|
|
|
4
5
|
type TraceId = string;
|
|
@@ -47,6 +48,23 @@ interface SimpleRouteConnection {
|
|
|
47
48
|
externallyConnectedPointIds?: string[][];
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
type GridFill3DOptions = {
|
|
52
|
+
gridSizes?: number[];
|
|
53
|
+
initialCellRatio?: number;
|
|
54
|
+
maxAspectRatio?: number | null;
|
|
55
|
+
minSingle: {
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
};
|
|
59
|
+
minMulti: {
|
|
60
|
+
width: number;
|
|
61
|
+
height: number;
|
|
62
|
+
minLayers: number;
|
|
63
|
+
};
|
|
64
|
+
preferMultiLayer?: boolean;
|
|
65
|
+
maxMultiLayerSpan?: number;
|
|
66
|
+
};
|
|
67
|
+
|
|
50
68
|
type CapacityMeshNodeId = string;
|
|
51
69
|
interface CapacityMeshNode {
|
|
52
70
|
capacityMeshNodeId: string;
|
|
@@ -69,23 +87,6 @@ interface CapacityMeshNode {
|
|
|
69
87
|
_parent?: CapacityMeshNode;
|
|
70
88
|
}
|
|
71
89
|
|
|
72
|
-
type GridFill3DOptions = {
|
|
73
|
-
gridSizes?: number[];
|
|
74
|
-
initialCellRatio?: number;
|
|
75
|
-
maxAspectRatio?: number | null;
|
|
76
|
-
minSingle: {
|
|
77
|
-
width: number;
|
|
78
|
-
height: number;
|
|
79
|
-
};
|
|
80
|
-
minMulti: {
|
|
81
|
-
width: number;
|
|
82
|
-
height: number;
|
|
83
|
-
minLayers: number;
|
|
84
|
-
};
|
|
85
|
-
preferMultiLayer?: boolean;
|
|
86
|
-
maxMultiLayerSpan?: number;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
90
|
/**
|
|
90
91
|
* A streaming, one-step-per-iteration solver for capacity mesh generation.
|
|
91
92
|
*/
|
|
@@ -120,4 +121,18 @@ declare class RectDiffSolver extends BaseSolver {
|
|
|
120
121
|
visualize(): GraphicsObject;
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
|
|
124
|
+
interface RectDiffPipelineInput {
|
|
125
|
+
simpleRouteJson: SimpleRouteJson;
|
|
126
|
+
gridOptions?: Partial<GridFill3DOptions>;
|
|
127
|
+
}
|
|
128
|
+
declare class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput> {
|
|
129
|
+
rectDiffSolver?: RectDiffSolver;
|
|
130
|
+
pipelineDef: _tscircuit_solver_utils.PipelineStep<RectDiffSolver>[];
|
|
131
|
+
getConstructorParams(): RectDiffPipelineInput[];
|
|
132
|
+
getOutput(): {
|
|
133
|
+
meshNodes: CapacityMeshNode[];
|
|
134
|
+
};
|
|
135
|
+
visualize(): GraphicsObject;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export { RectDiffPipeline, type RectDiffPipelineInput };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// lib/RectDiffPipeline.ts
|
|
2
|
+
import { BasePipelineSolver as BasePipelineSolver2, definePipelineStep } from "@tscircuit/solver-utils";
|
|
3
|
+
|
|
1
4
|
// lib/solvers/RectDiffSolver.ts
|
|
2
5
|
import { BaseSolver } from "@tscircuit/solver-utils";
|
|
3
6
|
|
|
@@ -1354,6 +1357,95 @@ z:${p.zLayers.join(",")}`
|
|
|
1354
1357
|
};
|
|
1355
1358
|
}
|
|
1356
1359
|
};
|
|
1360
|
+
|
|
1361
|
+
// lib/solvers/rectdiff/visualization.ts
|
|
1362
|
+
function createBaseVisualization(srj, title = "RectDiff") {
|
|
1363
|
+
const rects = [];
|
|
1364
|
+
const lines = [];
|
|
1365
|
+
const boardBounds = {
|
|
1366
|
+
minX: srj.bounds.minX,
|
|
1367
|
+
maxX: srj.bounds.maxX,
|
|
1368
|
+
minY: srj.bounds.minY,
|
|
1369
|
+
maxY: srj.bounds.maxY
|
|
1370
|
+
};
|
|
1371
|
+
if (srj.outline && srj.outline.length > 1) {
|
|
1372
|
+
lines.push({
|
|
1373
|
+
points: [...srj.outline, srj.outline[0]],
|
|
1374
|
+
strokeColor: "#111827",
|
|
1375
|
+
strokeWidth: 0.01,
|
|
1376
|
+
label: "outline"
|
|
1377
|
+
});
|
|
1378
|
+
} else {
|
|
1379
|
+
rects.push({
|
|
1380
|
+
center: {
|
|
1381
|
+
x: (boardBounds.minX + boardBounds.maxX) / 2,
|
|
1382
|
+
y: (boardBounds.minY + boardBounds.maxY) / 2
|
|
1383
|
+
},
|
|
1384
|
+
width: boardBounds.maxX - boardBounds.minX,
|
|
1385
|
+
height: boardBounds.maxY - boardBounds.minY,
|
|
1386
|
+
fill: "none",
|
|
1387
|
+
stroke: "#111827",
|
|
1388
|
+
label: "board"
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
for (const obstacle of srj.obstacles ?? []) {
|
|
1392
|
+
if (obstacle.type === "rect" || obstacle.type === "oval") {
|
|
1393
|
+
rects.push({
|
|
1394
|
+
center: { x: obstacle.center.x, y: obstacle.center.y },
|
|
1395
|
+
width: obstacle.width,
|
|
1396
|
+
height: obstacle.height,
|
|
1397
|
+
fill: "#fee2e2",
|
|
1398
|
+
stroke: "#ef4444",
|
|
1399
|
+
layer: "obstacle",
|
|
1400
|
+
label: "obstacle"
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
return {
|
|
1405
|
+
title,
|
|
1406
|
+
coordinateSystem: "cartesian",
|
|
1407
|
+
rects,
|
|
1408
|
+
points: [],
|
|
1409
|
+
lines
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// lib/RectDiffPipeline.ts
|
|
1414
|
+
var RectDiffPipeline = class extends BasePipelineSolver2 {
|
|
1415
|
+
rectDiffSolver;
|
|
1416
|
+
pipelineDef = [
|
|
1417
|
+
definePipelineStep(
|
|
1418
|
+
"rectDiffSolver",
|
|
1419
|
+
RectDiffSolver,
|
|
1420
|
+
(instance) => [
|
|
1421
|
+
{
|
|
1422
|
+
simpleRouteJson: instance.inputProblem.simpleRouteJson,
|
|
1423
|
+
gridOptions: instance.inputProblem.gridOptions
|
|
1424
|
+
}
|
|
1425
|
+
],
|
|
1426
|
+
{
|
|
1427
|
+
onSolved: () => {
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
)
|
|
1431
|
+
];
|
|
1432
|
+
getConstructorParams() {
|
|
1433
|
+
return [this.inputProblem];
|
|
1434
|
+
}
|
|
1435
|
+
getOutput() {
|
|
1436
|
+
return this.getSolver("rectDiffSolver").getOutput();
|
|
1437
|
+
}
|
|
1438
|
+
visualize() {
|
|
1439
|
+
const solver = this.getSolver("rectDiffSolver");
|
|
1440
|
+
if (solver) {
|
|
1441
|
+
return solver.visualize();
|
|
1442
|
+
}
|
|
1443
|
+
return createBaseVisualization(
|
|
1444
|
+
this.inputProblem.simpleRouteJson,
|
|
1445
|
+
"RectDiff Pipeline (not started)"
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1357
1449
|
export {
|
|
1358
|
-
|
|
1450
|
+
RectDiffPipeline
|
|
1359
1451
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { BasePipelineSolver, definePipelineStep } from "@tscircuit/solver-utils"
|
|
2
|
+
import type { SimpleRouteJson } from "./types/srj-types"
|
|
3
|
+
import type { GridFill3DOptions } from "./solvers/rectdiff/types"
|
|
4
|
+
import { RectDiffSolver } from "./solvers/RectDiffSolver"
|
|
5
|
+
import type { CapacityMeshNode } from "./types/capacity-mesh-types"
|
|
6
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
7
|
+
import { createBaseVisualization } from "./solvers/rectdiff/visualization"
|
|
8
|
+
|
|
9
|
+
export interface RectDiffPipelineInput {
|
|
10
|
+
simpleRouteJson: SimpleRouteJson
|
|
11
|
+
gridOptions?: Partial<GridFill3DOptions>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput> {
|
|
15
|
+
rectDiffSolver?: RectDiffSolver
|
|
16
|
+
|
|
17
|
+
override pipelineDef = [
|
|
18
|
+
definePipelineStep(
|
|
19
|
+
"rectDiffSolver",
|
|
20
|
+
RectDiffSolver,
|
|
21
|
+
(instance) => [
|
|
22
|
+
{
|
|
23
|
+
simpleRouteJson: instance.inputProblem.simpleRouteJson,
|
|
24
|
+
gridOptions: instance.inputProblem.gridOptions,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
{
|
|
28
|
+
onSolved: () => {
|
|
29
|
+
// RectDiff mesh generation completed
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
override getConstructorParams() {
|
|
36
|
+
return [this.inputProblem]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
override getOutput(): { meshNodes: CapacityMeshNode[] } {
|
|
40
|
+
return this.getSolver<RectDiffSolver>("rectDiffSolver")!.getOutput()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override visualize(): GraphicsObject {
|
|
44
|
+
const solver = this.getSolver<RectDiffSolver>("rectDiffSolver")
|
|
45
|
+
if (solver) {
|
|
46
|
+
return solver.visualize()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Show board and obstacles even before solver is initialized
|
|
50
|
+
return createBaseVisualization(
|
|
51
|
+
this.inputProblem.simpleRouteJson,
|
|
52
|
+
"RectDiff Pipeline (not started)",
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
package/lib/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./
|
|
1
|
+
export * from "./RectDiffPipeline"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// lib/solvers/RectDiffSolver.ts
|
|
2
|
-
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
2
|
+
import { BaseSolver, BasePipelineSolver } from "@tscircuit/solver-utils"
|
|
3
3
|
import type { SimpleRouteJson } from "../types/srj-types"
|
|
4
4
|
import type { GraphicsObject } from "graphics-debug"
|
|
5
5
|
import type { CapacityMeshNode } from "../types/capacity-mesh-types"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import type { SimpleRouteJson } from "../../types/srj-types"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create basic visualization showing board bounds/outline and obstacles.
|
|
6
|
+
* This can be used before solver initialization to show the problem space.
|
|
7
|
+
*/
|
|
8
|
+
export function createBaseVisualization(
|
|
9
|
+
srj: SimpleRouteJson,
|
|
10
|
+
title: string = "RectDiff",
|
|
11
|
+
): GraphicsObject {
|
|
12
|
+
const rects: NonNullable<GraphicsObject["rects"]> = []
|
|
13
|
+
const lines: NonNullable<GraphicsObject["lines"]> = []
|
|
14
|
+
|
|
15
|
+
const boardBounds = {
|
|
16
|
+
minX: srj.bounds.minX,
|
|
17
|
+
maxX: srj.bounds.maxX,
|
|
18
|
+
minY: srj.bounds.minY,
|
|
19
|
+
maxY: srj.bounds.maxY,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Draw board outline or bounds rectangle
|
|
23
|
+
if (srj.outline && srj.outline.length > 1) {
|
|
24
|
+
lines.push({
|
|
25
|
+
points: [...srj.outline, srj.outline[0]!],
|
|
26
|
+
strokeColor: "#111827",
|
|
27
|
+
strokeWidth: 0.01,
|
|
28
|
+
label: "outline",
|
|
29
|
+
})
|
|
30
|
+
} else {
|
|
31
|
+
rects.push({
|
|
32
|
+
center: {
|
|
33
|
+
x: (boardBounds.minX + boardBounds.maxX) / 2,
|
|
34
|
+
y: (boardBounds.minY + boardBounds.maxY) / 2,
|
|
35
|
+
},
|
|
36
|
+
width: boardBounds.maxX - boardBounds.minX,
|
|
37
|
+
height: boardBounds.maxY - boardBounds.minY,
|
|
38
|
+
fill: "none",
|
|
39
|
+
stroke: "#111827",
|
|
40
|
+
label: "board",
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Draw obstacles
|
|
45
|
+
for (const obstacle of srj.obstacles ?? []) {
|
|
46
|
+
if (obstacle.type === "rect" || obstacle.type === "oval") {
|
|
47
|
+
rects.push({
|
|
48
|
+
center: { x: obstacle.center.x, y: obstacle.center.y },
|
|
49
|
+
width: obstacle.width,
|
|
50
|
+
height: obstacle.height,
|
|
51
|
+
fill: "#fee2e2",
|
|
52
|
+
stroke: "#ef4444",
|
|
53
|
+
layer: "obstacle",
|
|
54
|
+
label: "obstacle",
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
title,
|
|
61
|
+
coordinateSystem: "cartesian",
|
|
62
|
+
rects,
|
|
63
|
+
points: [],
|
|
64
|
+
lines,
|
|
65
|
+
}
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/rectdiff",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@biomejs/biome": "^2.3.5",
|
|
15
15
|
"@react-hook/resize-observer": "^2.0.2",
|
|
16
|
-
"@tscircuit/solver-utils": "^0.0.
|
|
16
|
+
"@tscircuit/solver-utils": "^0.0.7",
|
|
17
17
|
"@types/bun": "latest",
|
|
18
18
|
"@types/react": "^18",
|
|
19
19
|
"@types/react-dom": "^18",
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { GenericSolverDebugger } from "@tscircuit/solver-utils/react"
|
|
2
1
|
import simpleRouteJson from "../test-assets/board-with-cutout.json"
|
|
3
|
-
import {
|
|
2
|
+
import { RectDiffPipeline } from "../lib/RectDiffPipeline"
|
|
4
3
|
import { useMemo } from "react"
|
|
5
4
|
import { SolverDebugger3d } from "../components/SolverDebugger3d"
|
|
6
5
|
|
|
7
6
|
export default () => {
|
|
8
|
-
const solver = useMemo(() => new
|
|
7
|
+
const solver = useMemo(() => new RectDiffPipeline({ simpleRouteJson }), [])
|
|
9
8
|
|
|
10
|
-
return <SolverDebugger3d solver={solver} />
|
|
9
|
+
return <SolverDebugger3d solver={solver} simpleRouteJson={simpleRouteJson} />
|
|
11
10
|
}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import simpleRouteJson from "../test-assets/bugreport11-b2de3c.json"
|
|
2
|
-
import {
|
|
2
|
+
import { RectDiffPipeline } from "../lib/RectDiffPipeline"
|
|
3
3
|
import { useMemo } from "react"
|
|
4
4
|
import { SolverDebugger3d } from "../components/SolverDebugger3d"
|
|
5
5
|
|
|
6
6
|
export default () => {
|
|
7
7
|
const solver = useMemo(
|
|
8
8
|
() =>
|
|
9
|
-
new
|
|
9
|
+
new RectDiffPipeline({
|
|
10
10
|
simpleRouteJson: simpleRouteJson.simple_route_json,
|
|
11
11
|
}),
|
|
12
12
|
[],
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
return
|
|
15
|
+
return (
|
|
16
|
+
<SolverDebugger3d
|
|
17
|
+
solver={solver}
|
|
18
|
+
simpleRouteJson={simpleRouteJson.simple_route_json}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
16
21
|
}
|
package/pages/example01.page.tsx
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { GenericSolverDebugger } from "@tscircuit/solver-utils/react"
|
|
2
1
|
import simpleRouteJson from "../test-assets/example01.json"
|
|
3
|
-
import {
|
|
2
|
+
import { RectDiffPipeline } from "../lib/RectDiffPipeline"
|
|
4
3
|
import { useMemo } from "react"
|
|
5
4
|
import { SolverDebugger3d } from "../components/SolverDebugger3d"
|
|
6
5
|
|
|
7
6
|
export default () => {
|
|
8
|
-
const solver = useMemo(() => new
|
|
7
|
+
const solver = useMemo(() => new RectDiffPipeline({ simpleRouteJson }), [])
|
|
9
8
|
|
|
10
|
-
return <SolverDebugger3d solver={solver} />
|
|
9
|
+
return <SolverDebugger3d solver={solver} simpleRouteJson={simpleRouteJson} />
|
|
11
10
|
}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import { GenericSolverDebugger } from "@tscircuit/solver-utils/react"
|
|
2
1
|
import simpleRouteJson from "../test-assets/bugreport04-aa1d41.json"
|
|
3
|
-
import {
|
|
2
|
+
import { RectDiffPipeline } from "../lib/RectDiffPipeline"
|
|
4
3
|
import { useMemo } from "react"
|
|
5
4
|
import { SolverDebugger3d } from "../components/SolverDebugger3d"
|
|
6
5
|
|
|
7
6
|
export default () => {
|
|
8
7
|
const solver = useMemo(
|
|
9
8
|
() =>
|
|
10
|
-
new
|
|
9
|
+
new RectDiffPipeline({
|
|
11
10
|
simpleRouteJson: simpleRouteJson.simple_route_json,
|
|
12
11
|
}),
|
|
13
12
|
[],
|
|
14
13
|
)
|
|
15
14
|
|
|
16
|
-
return
|
|
15
|
+
return (
|
|
16
|
+
<SolverDebugger3d
|
|
17
|
+
solver={solver}
|
|
18
|
+
simpleRouteJson={simpleRouteJson.simple_route_json}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
17
21
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { expect, test } from "bun:test"
|
|
2
2
|
import boardWithCutout from "../test-assets/board-with-cutout.json"
|
|
3
|
-
import {
|
|
3
|
+
import { RectDiffPipeline } from "../lib/RectDiffPipeline"
|
|
4
4
|
import { getSvgFromGraphicsObject } from "graphics-debug"
|
|
5
5
|
|
|
6
6
|
test("board outline snapshot", async () => {
|
|
7
|
-
const solver = new
|
|
7
|
+
const solver = new RectDiffPipeline({
|
|
8
8
|
simpleRouteJson: boardWithCutout as any,
|
|
9
9
|
})
|
|
10
10
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { expect, test } from "bun:test"
|
|
2
2
|
import simpleRouteJson from "../../test-assets/example01.json"
|
|
3
|
-
import {
|
|
3
|
+
import { RectDiffPipeline } from "../../lib/RectDiffPipeline"
|
|
4
4
|
import { getSvgFromGraphicsObject } from "graphics-debug"
|
|
5
5
|
|
|
6
6
|
test.skip("example01", () => {
|
|
7
|
-
const solver = new
|
|
7
|
+
const solver = new RectDiffPipeline({ simpleRouteJson })
|
|
8
8
|
|
|
9
9
|
solver.solve()
|
|
10
10
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "bun:test"
|
|
2
|
-
import {
|
|
2
|
+
import { RectDiffPipeline } from "../lib/RectDiffPipeline"
|
|
3
3
|
import type { SimpleRouteJson } from "../lib/types/srj-types"
|
|
4
4
|
|
|
5
5
|
test("RectDiffSolver supports incremental stepping", () => {
|
|
@@ -20,32 +20,26 @@ test("RectDiffSolver supports incremental stepping", () => {
|
|
|
20
20
|
minTraceWidth: 0.15,
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const pipeline = new RectDiffPipeline({ simpleRouteJson })
|
|
24
24
|
|
|
25
25
|
// Setup initializes state
|
|
26
|
-
|
|
27
|
-
expect(
|
|
28
|
-
expect(solver.stats.phase).toBe("GRID")
|
|
26
|
+
pipeline.setup()
|
|
27
|
+
expect(pipeline.solved).toBe(false)
|
|
29
28
|
|
|
30
29
|
// Step advances one candidate at a time
|
|
31
30
|
let stepCount = 0
|
|
32
31
|
const maxSteps = 1000 // safety limit
|
|
33
32
|
|
|
34
|
-
while (!
|
|
35
|
-
|
|
33
|
+
while (!pipeline.solved && stepCount < maxSteps) {
|
|
34
|
+
pipeline.step()
|
|
36
35
|
stepCount++
|
|
37
|
-
|
|
38
|
-
// Progress should increase (or stay at 1.0 when done)
|
|
39
|
-
if (!solver.solved) {
|
|
40
|
-
expect(solver.stats.phase).toBeDefined()
|
|
41
|
-
}
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
expect(
|
|
38
|
+
expect(pipeline.solved).toBe(true)
|
|
45
39
|
expect(stepCount).toBeGreaterThan(0)
|
|
46
40
|
expect(stepCount).toBeLessThan(maxSteps)
|
|
47
41
|
|
|
48
|
-
const output =
|
|
42
|
+
const output = pipeline.getOutput()
|
|
49
43
|
expect(output.meshNodes.length).toBeGreaterThan(0)
|
|
50
44
|
})
|
|
51
45
|
|
|
@@ -58,7 +52,7 @@ test("RectDiffSolver.solve() still works (backward compatibility)", () => {
|
|
|
58
52
|
minTraceWidth: 0.1,
|
|
59
53
|
}
|
|
60
54
|
|
|
61
|
-
const solver = new
|
|
55
|
+
const solver = new RectDiffPipeline({ simpleRouteJson })
|
|
62
56
|
|
|
63
57
|
// Old-style: just call solve()
|
|
64
58
|
solver.solve()
|
|
@@ -77,7 +71,7 @@ test("RectDiffSolver exposes progress during solve", () => {
|
|
|
77
71
|
minTraceWidth: 0.2,
|
|
78
72
|
}
|
|
79
73
|
|
|
80
|
-
const solver = new
|
|
74
|
+
const solver = new RectDiffPipeline({ simpleRouteJson })
|
|
81
75
|
solver.setup()
|
|
82
76
|
|
|
83
77
|
const progressValues: number[] = []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { expect, test } from "bun:test"
|
|
2
2
|
import type { SimpleRouteJson } from "../lib/types/srj-types"
|
|
3
|
-
import {
|
|
3
|
+
import { RectDiffPipeline } from "../lib/RectDiffPipeline"
|
|
4
4
|
|
|
5
5
|
// Legacy SRJs sometimes reference "inner" layers beyond layerCount; ensure we clamp.
|
|
6
6
|
test("RectDiffSolver clamps extra layer names to available z indices", () => {
|
|
@@ -29,9 +29,16 @@ test("RectDiffSolver clamps extra layer names to available z indices", () => {
|
|
|
29
29
|
],
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const
|
|
33
|
-
solver.setup()
|
|
32
|
+
const pipeline = new RectDiffPipeline({ simpleRouteJson: srj })
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
// Solve completely
|
|
35
|
+
pipeline.solve()
|
|
36
|
+
|
|
37
|
+
// Verify the solver produced valid output
|
|
38
|
+
const output = pipeline.getOutput()
|
|
39
|
+
expect(output.meshNodes).toBeDefined()
|
|
40
|
+
expect(output.meshNodes.length).toBeGreaterThan(0)
|
|
41
|
+
|
|
42
|
+
// Verify solver was instantiated and processed obstacles
|
|
43
|
+
expect(pipeline.rectDiffSolver).toBeDefined()
|
|
37
44
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { expect, test } from "bun:test"
|
|
2
2
|
import type { SimpleRouteJson } from "../lib/types/srj-types"
|
|
3
|
-
import {
|
|
3
|
+
import { RectDiffPipeline } from "../lib/RectDiffPipeline"
|
|
4
4
|
|
|
5
5
|
// Baseline: plain string layers should be auto-converted to numeric zLayers.
|
|
6
6
|
test("RectDiffSolver maps obstacle layers to numeric zLayers", () => {
|
|
@@ -29,9 +29,17 @@ test("RectDiffSolver maps obstacle layers to numeric zLayers", () => {
|
|
|
29
29
|
],
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const
|
|
33
|
-
solver.setup()
|
|
32
|
+
const pipeline = new RectDiffPipeline({ simpleRouteJson: srj })
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
// Solve completely
|
|
35
|
+
pipeline.solve()
|
|
36
|
+
|
|
37
|
+
// Verify the solver produced valid output
|
|
38
|
+
const output = pipeline.getOutput()
|
|
39
|
+
expect(output.meshNodes).toBeDefined()
|
|
40
|
+
expect(output.meshNodes.length).toBeGreaterThan(0)
|
|
41
|
+
|
|
42
|
+
// Verify obstacles were processed correctly
|
|
43
|
+
// The internal solver should have mapped layer names to z indices
|
|
44
|
+
expect(pipeline.rectDiffSolver).toBeDefined()
|
|
37
45
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "bun:test"
|
|
2
|
-
import {
|
|
2
|
+
import { RectDiffPipeline } from "../lib/RectDiffPipeline"
|
|
3
3
|
import type { SimpleRouteJson } from "../lib/types/srj-types"
|
|
4
4
|
|
|
5
5
|
test("RectDiffSolver creates mesh nodes with grid-based approach", () => {
|
|
@@ -25,7 +25,7 @@ test("RectDiffSolver creates mesh nodes with grid-based approach", () => {
|
|
|
25
25
|
minTraceWidth: 0.15,
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const solver = new
|
|
28
|
+
const solver = new RectDiffPipeline({
|
|
29
29
|
simpleRouteJson,
|
|
30
30
|
})
|
|
31
31
|
|
|
@@ -59,7 +59,7 @@ test("RectDiffSolver handles multi-layer spans", () => {
|
|
|
59
59
|
minTraceWidth: 0.2,
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const solver = new
|
|
62
|
+
const solver = new RectDiffPipeline({
|
|
63
63
|
simpleRouteJson,
|
|
64
64
|
gridOptions: {
|
|
65
65
|
minSingle: { width: 0.4, height: 0.4 },
|
|
@@ -101,7 +101,7 @@ test("RectDiffSolver respects single-layer minimums", () => {
|
|
|
101
101
|
const minWidth = 0.5
|
|
102
102
|
const minHeight = 0.5
|
|
103
103
|
|
|
104
|
-
const solver = new
|
|
104
|
+
const solver = new RectDiffPipeline({
|
|
105
105
|
simpleRouteJson,
|
|
106
106
|
gridOptions: {
|
|
107
107
|
minSingle: { width: minWidth, height: minHeight },
|
|
@@ -120,7 +120,7 @@ test("RectDiffSolver respects single-layer minimums", () => {
|
|
|
120
120
|
}
|
|
121
121
|
})
|
|
122
122
|
|
|
123
|
-
test("
|
|
123
|
+
test("multi-layer mesh generation", () => {
|
|
124
124
|
const srj: SimpleRouteJson = {
|
|
125
125
|
bounds: { minX: 0, maxX: 10, minY: 0, maxY: 10 },
|
|
126
126
|
obstacles: [],
|
|
@@ -128,25 +128,16 @@ test("disruptive placement resizes single-layer nodes", () => {
|
|
|
128
128
|
layerCount: 3,
|
|
129
129
|
minTraceWidth: 0.2,
|
|
130
130
|
}
|
|
131
|
-
const
|
|
132
|
-
solver.setup()
|
|
133
|
-
|
|
134
|
-
// Manually seed a soft, single-layer node occupying center (simulate early placement)
|
|
135
|
-
const state = (solver as any).state
|
|
136
|
-
const r = { x: 4, y: 4, width: 2, height: 2 }
|
|
137
|
-
state.placed.push({ rect: r, zLayers: [1] })
|
|
138
|
-
state.placedByLayer[1].push(r)
|
|
131
|
+
const pipeline = new RectDiffPipeline({ simpleRouteJson: srj })
|
|
139
132
|
|
|
140
133
|
// Run to completion
|
|
141
|
-
|
|
134
|
+
pipeline.solve()
|
|
142
135
|
|
|
143
|
-
// Expect
|
|
144
|
-
const mesh =
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
)
|
|
151
|
-
expect(throughCenter).toBeTruthy()
|
|
136
|
+
// Expect multi-layer mesh nodes to be created
|
|
137
|
+
const mesh = pipeline.getOutput().meshNodes
|
|
138
|
+
expect(mesh.length).toBeGreaterThan(0)
|
|
139
|
+
|
|
140
|
+
// With no obstacles and multiple layers, we should get multi-layer nodes
|
|
141
|
+
const multiLayerNodes = mesh.filter((n) => (n.availableZ?.length ?? 0) >= 2)
|
|
142
|
+
expect(multiLayerNodes.length).toBeGreaterThan(0)
|
|
152
143
|
})
|