@tscircuit/rectdiff 0.0.7 → 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.
@@ -560,7 +560,7 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
560
560
  defaultWireframeOutput = false,
561
561
  style,
562
562
  }) => {
563
- const [show3d, setShow3d] = useState(false)
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
- <GenericSolverDebugger
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: 8,
622
+ gap: 12,
628
623
  alignItems: "center",
629
- flexWrap: "wrap",
624
+ padding: "12px 16px",
625
+ background: "#f8fafc",
626
+ borderRadius: 8,
627
+ border: "1px solid #e2e8f0",
630
628
  }}
631
629
  >
632
- <button
633
- onClick={toggle3d}
634
- style={{
635
- padding: "8px 10px",
636
- borderRadius: 6,
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: "8px 10px",
636
+ padding: "6px 12px",
650
637
  borderRadius: 6,
651
638
  border: "1px solid #cbd5e1",
652
- background: "#0f766e",
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
- Rebuild 3D
659
- </button>
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
- {/* Shrink boxes option */}
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: 12,
672
+ fontSize: 13,
744
673
  }}
745
674
  >
746
675
  <input
747
676
  type="checkbox"
748
- checked={shrinkBoxes}
749
- onChange={(e) => setShrinkBoxes(e.target.checked)}
677
+ checked={showRoot}
678
+ onChange={(e) => setShowRoot(e.target.checked)}
750
679
  />
751
- Shrink boxes
680
+ Root
752
681
  </label>
753
- {shrinkBoxes && (
754
- <label
755
- style={{
756
- display: "inline-flex",
757
- gap: 4,
758
- alignItems: "center",
759
- fontSize: 12,
760
- }}
761
- >
762
- amt
763
- <input
764
- type="number"
765
- value={boxShrinkAmount}
766
- step={0.05}
767
- style={{ width: 60 }}
768
- onChange={(e) => {
769
- const v = parseFloat(e.target.value)
770
- if (Number.isNaN(v)) return
771
- setBoxShrinkAmount(Math.max(0, v))
772
- }}
773
- />
774
- </label>
775
- )}
776
- </>
777
- )}
778
-
779
- {/* Show borders option */}
780
- {show3d && (
781
- <label
782
- style={{
783
- display: "inline-flex",
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
- opacity: wireframeOutput ? 0.5 : 1,
714
+ display: "inline-flex",
715
+ gap: 6,
716
+ alignItems: "center",
717
+ fontSize: 13,
798
718
  }}
799
719
  >
800
- Show borders
801
- </span>
802
- </label>
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
- {show3d && (
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 { BaseSolver } from '@tscircuit/solver-utils';
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
- export { type GridFill3DOptions, RectDiffSolver };
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
- RectDiffSolver
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 "./solvers/RectDiffSolver"
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.7",
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.3",
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 { RectDiffSolver } from "../lib/solvers/RectDiffSolver"
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 RectDiffSolver({ simpleRouteJson }), [])
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 { RectDiffSolver } from "../lib/solvers/RectDiffSolver"
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 RectDiffSolver({
9
+ new RectDiffPipeline({
10
10
  simpleRouteJson: simpleRouteJson.simple_route_json,
11
11
  }),
12
12
  [],
13
13
  )
14
14
 
15
- return <SolverDebugger3d solver={solver} />
15
+ return (
16
+ <SolverDebugger3d
17
+ solver={solver}
18
+ simpleRouteJson={simpleRouteJson.simple_route_json}
19
+ />
20
+ )
16
21
  }