@tscircuit/schematic-trace-solver 0.0.15 → 0.0.16

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.d.ts CHANGED
@@ -423,6 +423,7 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
423
423
  inputProblem: InputProblem;
424
424
  pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof GuidelinesSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver>)[];
425
425
  constructor(inputProblem: InputProblem);
426
+ getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>[0];
426
427
  currentPipelineStepIndex: number;
427
428
  private cloneAndCorrectInputProblem;
428
429
  _step(): void;
package/dist/index.js CHANGED
@@ -6162,6 +6162,8 @@ var calculateElbowBends = (p1, p2, overshootAmount) => {
6162
6162
  const last = result[result.length - 1];
6163
6163
  if (last.x !== pt.x || last.y !== pt.y) result.push(pt);
6164
6164
  };
6165
+ const yAligned = Math.abs(p1.y - p2.y) <= Math.max(1e-6, overshootAmount * 0.1);
6166
+ const xAligned = Math.abs(p1.x - p2.x) <= Math.max(1e-6, overshootAmount * 0.1);
6165
6167
  if (startDir === "none" && endDir === "none") {
6166
6168
  push({
6167
6169
  x: midX,
@@ -6188,7 +6190,7 @@ var calculateElbowBends = (p1, p2, overshootAmount) => {
6188
6190
  x: p2.x,
6189
6191
  y: p1.y
6190
6192
  });
6191
- else if (p1.x === p2.x) {
6193
+ else if (xAligned) {
6192
6194
  push({
6193
6195
  x: p1.x + overshootAmount,
6194
6196
  y: p1.y
@@ -6245,7 +6247,7 @@ var calculateElbowBends = (p1, p2, overshootAmount) => {
6245
6247
  y: (p1.y + p2.y) / 2
6246
6248
  });
6247
6249
  }
6248
- else if (startDir === "x+" && endDir === "x+" && p1.y !== p2.y) {
6250
+ else if (startDir === "x+" && endDir === "x+" && !yAligned) {
6249
6251
  const commonX = Math.max(p1.x + overshootAmount, p2Target.x);
6250
6252
  push({
6251
6253
  x: commonX,
@@ -6255,7 +6257,7 @@ var calculateElbowBends = (p1, p2, overshootAmount) => {
6255
6257
  x: commonX,
6256
6258
  y: p2.y
6257
6259
  });
6258
- } else if (startDir === "x+" && endDir === "x+" && p1.y === p2.y) {
6260
+ } else if (startDir === "x+" && endDir === "x+" && yAligned) {
6259
6261
  push({
6260
6262
  x: p1.x + overshootAmount,
6261
6263
  y: p1.y
@@ -6272,7 +6274,7 @@ var calculateElbowBends = (p1, p2, overshootAmount) => {
6272
6274
  x: p2.x + overshootAmount,
6273
6275
  y: p2.y
6274
6276
  });
6275
- } else if (startDir === "x+" && endDir === "y-") if (p1.x === p2.x && p1.y <= p2.y) {
6277
+ } else if (startDir === "x+" && endDir === "y-") if (xAligned && p1.y <= p2.y) {
6276
6278
  push({
6277
6279
  x: p1.x + overshootAmount,
6278
6280
  y: p1.y
@@ -6285,7 +6287,7 @@ var calculateElbowBends = (p1, p2, overshootAmount) => {
6285
6287
  x: p2.x,
6286
6288
  y: midY
6287
6289
  });
6288
- } else if (p1.x === p2.x && p1.y > p2.y) {
6290
+ } else if (xAligned && p1.y > p2.y) {
6289
6291
  push({
6290
6292
  x: p1.x + overshootAmount,
6291
6293
  y: p1.y
@@ -6675,6 +6677,7 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
6675
6677
  overshoot: 0.2
6676
6678
  }
6677
6679
  );
6680
+ console.log("baseElbow", this.baseElbow);
6678
6681
  const { elbowVariants, movableSegments } = generateElbowVariants({
6679
6682
  baseElbow: this.baseElbow,
6680
6683
  guidelines: this.guidelines
@@ -8249,6 +8252,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
8249
8252
  this.timeSpentOnPhase = {};
8250
8253
  this.firstIterationOfPhase = {};
8251
8254
  }
8255
+ getConstructorParams() {
8256
+ return this.inputProblem;
8257
+ }
8252
8258
  currentPipelineStepIndex = 0;
8253
8259
  cloneAndCorrectInputProblem(original) {
8254
8260
  const cloned = structuredClone({
@@ -73,6 +73,7 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
73
73
  overshoot: 0.2,
74
74
  },
75
75
  )
76
+ console.log("baseElbow", this.baseElbow)
76
77
 
77
78
  const { elbowVariants, movableSegments } = generateElbowVariants({
78
79
  baseElbow: this.baseElbow,
@@ -146,6 +146,12 @@ export class SchematicTracePipelineSolver extends BaseSolver {
146
146
  this.firstIterationOfPhase = {}
147
147
  }
148
148
 
149
+ override getConstructorParams(): ConstructorParameters<
150
+ typeof SchematicTracePipelineSolver
151
+ >[0] {
152
+ return this.inputProblem
153
+ }
154
+
149
155
  currentPipelineStepIndex = 0
150
156
 
151
157
  private cloneAndCorrectInputProblem(original: InputProblem): InputProblem {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.15",
4
+ "version": "0.0.16",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -14,7 +14,7 @@
14
14
  "@react-hook/resize-observer": "^2.0.2",
15
15
  "@tscircuit/math-utils": "^0.0.19",
16
16
  "@types/bun": "latest",
17
- "calculate-elbow": "^0.0.10",
17
+ "calculate-elbow": "^0.0.11",
18
18
  "connectivity-map": "^1.0.0",
19
19
  "flatbush": "^4.5.0",
20
20
  "graphics-debug": "^0.0.62",
@@ -26,5 +26,8 @@
26
26
  "peerDependencies": {
27
27
  "typescript": "^5"
28
28
  },
29
- "dependencies": {}
29
+ "dependencies": {
30
+ "react": "^19.1.1",
31
+ "react-dom": "^19.1.1"
32
+ }
30
33
  }
@@ -0,0 +1,83 @@
1
+ import type { InputProblem } from "lib/types/InputProblem"
2
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
3
+ import { useState } from "react"
4
+
5
+ export default () => {
6
+ const [inputText, setInputText] = useState("")
7
+ const [inputProblem, setInputProblem] = useState<InputProblem | null>(null)
8
+ const [error, setError] = useState<string | null>(null)
9
+
10
+ const handleOpenDebugger = () => {
11
+ try {
12
+ setError(null)
13
+ let parsed: InputProblem
14
+
15
+ const trimmedInput = inputText.trim()
16
+ if (trimmedInput.startsWith("{") || trimmedInput.startsWith("[")) {
17
+ parsed = JSON.parse(trimmedInput)
18
+ } else {
19
+ parsed = eval(`(${trimmedInput})`)
20
+ }
21
+
22
+ setInputProblem(parsed)
23
+ } catch (err) {
24
+ setError(err instanceof Error ? err.message : "Invalid input format")
25
+ }
26
+ }
27
+
28
+ if (inputProblem) {
29
+ return <PipelineDebugger inputProblem={inputProblem} />
30
+ }
31
+
32
+ return (
33
+ <div style={{ padding: "20px", maxWidth: "800px", margin: "0 auto" }}>
34
+ <h1>Paste Input Problem</h1>
35
+ <p>Paste a JSON or JavaScript object representing an InputProblem:</p>
36
+
37
+ <textarea
38
+ value={inputText}
39
+ onChange={(e) => setInputText(e.target.value)}
40
+ placeholder="Paste your InputProblem here..."
41
+ style={{
42
+ width: "100%",
43
+ height: "400px",
44
+ fontFamily: "monospace",
45
+ padding: "10px",
46
+ border: "1px solid #ccc",
47
+ borderRadius: "4px",
48
+ marginBottom: "10px",
49
+ }}
50
+ />
51
+
52
+ {error && (
53
+ <div
54
+ style={{
55
+ color: "red",
56
+ marginBottom: "10px",
57
+ padding: "10px",
58
+ backgroundColor: "#fee",
59
+ borderRadius: "4px",
60
+ }}
61
+ >
62
+ Error: {error}
63
+ </div>
64
+ )}
65
+
66
+ <button
67
+ onClick={handleOpenDebugger}
68
+ disabled={!inputText.trim()}
69
+ style={{
70
+ padding: "10px 20px",
71
+ backgroundColor: inputText.trim() ? "#007bff" : "#ccc",
72
+ color: "white",
73
+ border: "none",
74
+ borderRadius: "4px",
75
+ cursor: inputText.trim() ? "pointer" : "not-allowed",
76
+ fontSize: "16px",
77
+ }}
78
+ >
79
+ Open Debugger
80
+ </button>
81
+ </div>
82
+ )
83
+ }
@@ -0,0 +1,172 @@
1
+ import { useState, useRef, useEffect } from "react"
2
+ import type { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
3
+
4
+ interface DownloadDropdownProps {
5
+ solver: BaseSolver
6
+ className?: string
7
+ }
8
+
9
+ export const DownloadDropdown = ({
10
+ solver,
11
+ className = "",
12
+ }: DownloadDropdownProps) => {
13
+ const [isOpen, setIsOpen] = useState(false)
14
+ const dropdownRef = useRef<HTMLDivElement>(null)
15
+
16
+ useEffect(() => {
17
+ const handleClickOutside = (event: MouseEvent) => {
18
+ if (
19
+ dropdownRef.current &&
20
+ !dropdownRef.current.contains(event.target as Node)
21
+ ) {
22
+ setIsOpen(false)
23
+ }
24
+ }
25
+
26
+ document.addEventListener("mousedown", handleClickOutside)
27
+ return () => document.removeEventListener("mousedown", handleClickOutside)
28
+ }, [])
29
+
30
+ const downloadJSON = () => {
31
+ try {
32
+ if (typeof solver.getConstructorParams !== "function") {
33
+ alert(
34
+ `getConstructorParams() is not implemented for ${solver.constructor.name}`,
35
+ )
36
+ return
37
+ }
38
+
39
+ const params = solver.getConstructorParams()
40
+ const blob = new Blob([JSON.stringify(params, null, 2)], {
41
+ type: "application/json",
42
+ })
43
+ const url = URL.createObjectURL(blob)
44
+ const a = document.createElement("a")
45
+ a.href = url
46
+ a.download = `${solver.constructor.name}_params.json`
47
+ a.click()
48
+ URL.revokeObjectURL(url)
49
+ } catch (error) {
50
+ alert(
51
+ `Error downloading params for ${solver.constructor.name}: ${error instanceof Error ? error.message : String(error)}`,
52
+ )
53
+ }
54
+ setIsOpen(false)
55
+ }
56
+
57
+ const downloadPageTsx = () => {
58
+ try {
59
+ const params = solver.getConstructorParams()
60
+ const solverName = solver.constructor.name
61
+ const isSchematicTracePipelineSolver =
62
+ solverName === "SchematicTracePipelineSolver"
63
+
64
+ let content: string
65
+
66
+ if (isSchematicTracePipelineSolver) {
67
+ content = `import { PipelineDebugger } from "site/components/PipelineDebugger"
68
+ import type { InputProblem } from "lib/types/InputProblem"
69
+
70
+ const inputProblem: InputProblem = ${JSON.stringify(params, null, 2)}
71
+
72
+ export default () => <PipelineDebugger inputProblem={inputProblem} />
73
+ `
74
+ } else {
75
+ content = `import { useMemo } from "react"
76
+ import { GenericSolverDebugger } from "../components/GenericSolverDebugger"
77
+ import { ${solverName} } from "lib/solvers/${solverName}/${solverName}"
78
+
79
+ const input = ${JSON.stringify(params, null, 2)}
80
+
81
+ export default () => {
82
+ const solver = useMemo(() => {
83
+ return new ${solverName}(input as any)
84
+ }, [])
85
+ return <GenericSolverDebugger solver={solver} />
86
+ }
87
+ `
88
+ }
89
+
90
+ const blob = new Blob([content], { type: "text/plain" })
91
+ const url = URL.createObjectURL(blob)
92
+ const a = document.createElement("a")
93
+ a.href = url
94
+ a.download = `${solverName}.page.tsx`
95
+ a.click()
96
+ URL.revokeObjectURL(url)
97
+ } catch (error) {
98
+ alert(
99
+ `Error generating page.tsx for ${solver.constructor.name}: ${error instanceof Error ? error.message : String(error)}`,
100
+ )
101
+ }
102
+ setIsOpen(false)
103
+ }
104
+
105
+ const downloadTestTs = () => {
106
+ try {
107
+ const params = solver.getConstructorParams()
108
+ const solverName = solver.constructor.name
109
+
110
+ const content = `import { ${solverName} } from "lib/solvers/${solverName}/${solverName}"
111
+
112
+ test("${solverName} should solve problem correctly", () => {
113
+ const input = ${JSON.stringify(params, null, 2)}
114
+
115
+ const solver = new ${solverName}(input as any)
116
+ const result = solver.solve()
117
+
118
+ expect(result).toBeDefined()
119
+ // Add more specific assertions based on expected output
120
+ })
121
+ `
122
+
123
+ const blob = new Blob([content], { type: "text/plain" })
124
+ const url = URL.createObjectURL(blob)
125
+ const a = document.createElement("a")
126
+ a.href = url
127
+ a.download = `${solverName}.test.ts`
128
+ a.click()
129
+ URL.revokeObjectURL(url)
130
+ } catch (error) {
131
+ alert(
132
+ `Error generating test.ts for ${solver.constructor.name}: ${error instanceof Error ? error.message : String(error)}`,
133
+ )
134
+ }
135
+ setIsOpen(false)
136
+ }
137
+
138
+ return (
139
+ <div className={`relative ${className}`} ref={dropdownRef}>
140
+ <button
141
+ className="px-2 py-1 rounded text-xs cursor-pointer"
142
+ onClick={() => setIsOpen(!isOpen)}
143
+ title={`Download options for ${solver.constructor.name}`}
144
+ >
145
+ {solver.constructor.name}
146
+ </button>
147
+
148
+ {isOpen && (
149
+ <div className="absolute top-full left-0 mt-1 bg-white border border-gray-300 rounded shadow-lg z-10 min-w-[150px]">
150
+ <button
151
+ className="w-full text-left px-3 py-2 hover:bg-gray-100 text-xs"
152
+ onClick={downloadJSON}
153
+ >
154
+ Download JSON
155
+ </button>
156
+ <button
157
+ className="w-full text-left px-3 py-2 hover:bg-gray-100 text-xs"
158
+ onClick={downloadPageTsx}
159
+ >
160
+ Download page.tsx
161
+ </button>
162
+ <button
163
+ className="w-full text-left px-3 py-2 hover:bg-gray-100 text-xs"
164
+ onClick={downloadTestTs}
165
+ >
166
+ Download test.ts
167
+ </button>
168
+ </div>
169
+ )}
170
+ </div>
171
+ )
172
+ }
@@ -1,4 +1,5 @@
1
1
  import type { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
2
+ import { DownloadDropdown } from "./DownloadDropdown"
2
3
 
3
4
  export const getSolverChain = (solver: BaseSolver): BaseSolver[] => {
4
5
  if (!solver.activeSubSolver) {
@@ -17,44 +18,12 @@ export const SolverBreadcrumbInputDownloader = ({
17
18
  }) => {
18
19
  const solverChain = getSolverChain(solver)
19
20
 
20
- const downloadSolverParams = (s: BaseSolver) => {
21
- try {
22
- if (typeof s.getConstructorParams !== "function") {
23
- alert(
24
- `getConstructorParams() is not implemented for ${s.constructor.name}`,
25
- )
26
- return
27
- }
28
-
29
- const params = s.getConstructorParams()
30
- const blob = new Blob([JSON.stringify(params, null, 2)], {
31
- type: "application/json",
32
- })
33
- const url = URL.createObjectURL(blob)
34
- const a = document.createElement("a")
35
- a.href = url
36
- a.download = `${s.constructor.name}_params.json`
37
- a.click()
38
- URL.revokeObjectURL(url)
39
- } catch (error) {
40
- alert(
41
- `Error downloading params for ${s.constructor.name}: ${error instanceof Error ? error.message : String(error)}`,
42
- )
43
- }
44
- }
45
-
46
21
  return (
47
22
  <div className="flex gap-1 items-center text-sm pt-1">
48
23
  {solverChain.map((s, index) => (
49
24
  <div key={s.constructor.name} className="flex items-center">
50
25
  {index > 0 && <span className="text-gray-400 mx-1">→</span>}
51
- <button
52
- className="px-2 py-1 rounded text-xs cursor-pointer"
53
- onClick={() => downloadSolverParams(s)}
54
- title={`Download constructor params for ${s.constructor.name}`}
55
- >
56
- {s.constructor.name}
57
- </button>
26
+ <DownloadDropdown solver={s} />
58
27
  </div>
59
28
  ))}
60
29
  </div>
@@ -0,0 +1,60 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import type { InputProblem } from "lib/types/InputProblem"
3
+
4
+ const inputProblem: InputProblem = {
5
+ chips: [
6
+ {
7
+ chipId: "schematic_component_0",
8
+ center: {
9
+ x: -3,
10
+ y: 0,
11
+ },
12
+ width: 1.102581400000001,
13
+ height: 0.388910699999999,
14
+ pins: [
15
+ {
16
+ pinId: "R1.1",
17
+ x: -3.5512907000000005,
18
+ y: 0.0002732499999993365,
19
+ },
20
+ {
21
+ pinId: "R1.2",
22
+ x: -2.4487092999999995,
23
+ y: -0.0002732499999993365,
24
+ },
25
+ ],
26
+ },
27
+ {
28
+ chipId: "schematic_component_1",
29
+ center: {
30
+ x: 3,
31
+ y: 0,
32
+ },
33
+ width: 1.102418600000001,
34
+ height: 0.8400173000000031,
35
+ pins: [
36
+ {
37
+ pinId: "C1.1",
38
+ x: 2.4487906999999995,
39
+ y: -0.00027334999999961695,
40
+ },
41
+ {
42
+ pinId: "C1.2",
43
+ x: 3.5512093000000005,
44
+ y: 0.00027334999999961695,
45
+ },
46
+ ],
47
+ },
48
+ ],
49
+ directConnections: [
50
+ {
51
+ pinIds: ["R1.1", "C1.1"],
52
+ netId: ".R1 > .pin1 to .C1 > .pin1",
53
+ },
54
+ ],
55
+ netConnections: [],
56
+ availableNetLabelOrientations: {},
57
+ maxMspPairDistance: 2,
58
+ }
59
+
60
+ export default () => <PipelineDebugger inputProblem={inputProblem} />