@tscircuit/schematic-trace-solver 0.0.14 → 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
@@ -347,6 +347,19 @@ type OverlappingSameNetTraceGroup = {
347
347
  interface NetLabelPlacement {
348
348
  globalConnNetId: string;
349
349
  dcConnNetId?: string;
350
+ /**
351
+ * Optional user-provided net identifier (if present in the input problem).
352
+ */
353
+ netId?: string;
354
+ /**
355
+ * MSP pair ids that the label is associated with. Port-only labels use [].
356
+ */
357
+ mspConnectionPairIds: MspConnectionPairId[];
358
+ /**
359
+ * Pin ids relevant to this label. For a host trace, the two pins of that pair;
360
+ * for a port-only label, the single port pin id.
361
+ */
362
+ pinIds: PinId[];
350
363
  orientation: FacingDirection;
351
364
  /**
352
365
  * The anchor point is the point on the trace where the net label connects
@@ -410,6 +423,7 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
410
423
  inputProblem: InputProblem;
411
424
  pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof GuidelinesSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver>)[];
412
425
  constructor(inputProblem: InputProblem);
426
+ getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>[0];
413
427
  currentPipelineStepIndex: number;
414
428
  private cloneAndCorrectInputProblem;
415
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
@@ -7431,6 +7434,9 @@ function solveNetLabelPlacementForPortOnlyPin(params) {
7431
7434
  const placement = {
7432
7435
  globalConnNetId: overlappingSameNetTraceGroup.globalConnNetId,
7433
7436
  dcConnNetId: void 0,
7437
+ netId: overlappingSameNetTraceGroup.netId,
7438
+ mspConnectionPairIds: [],
7439
+ pinIds: [pinId],
7434
7440
  orientation,
7435
7441
  anchorPoint: anchor,
7436
7442
  width,
@@ -7664,7 +7670,9 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
7664
7670
  height,
7665
7671
  center,
7666
7672
  hostSegIndex: si,
7667
- dcConnNetId: curr.dcConnNetId
7673
+ dcConnNetId: curr.dcConnNetId,
7674
+ mspPairId: curr.mspPairId,
7675
+ pinIds: [curr.pins[0].pinId, curr.pins[1].pinId]
7668
7676
  };
7669
7677
  }
7670
7678
  continue;
@@ -7672,6 +7680,9 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
7672
7680
  this.netLabelPlacement = {
7673
7681
  globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
7674
7682
  dcConnNetId: curr.dcConnNetId,
7683
+ netId: this.overlappingSameNetTraceGroup.netId,
7684
+ mspConnectionPairIds: [curr.mspPairId],
7685
+ pinIds: [curr.pins[0].pinId, curr.pins[1].pinId],
7675
7686
  orientation,
7676
7687
  anchorPoint: anchor,
7677
7688
  width,
@@ -7688,6 +7699,9 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
7688
7699
  this.netLabelPlacement = {
7689
7700
  globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
7690
7701
  dcConnNetId: bestCandidate.dcConnNetId,
7702
+ netId: this.overlappingSameNetTraceGroup.netId,
7703
+ mspConnectionPairIds: [bestCandidate.mspPairId],
7704
+ pinIds: bestCandidate.pinIds,
7691
7705
  orientation: bestCandidate.orientation,
7692
7706
  anchorPoint: bestCandidate.anchor,
7693
7707
  width: bestCandidate.width,
@@ -8238,6 +8252,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
8238
8252
  this.timeSpentOnPhase = {};
8239
8253
  this.firstIterationOfPhase = {};
8240
8254
  }
8255
+ getConstructorParams() {
8256
+ return this.inputProblem;
8257
+ }
8241
8258
  currentPipelineStepIndex = 0;
8242
8259
  cloneAndCorrectInputProblem(original) {
8243
8260
  const cloned = structuredClone({
@@ -1,5 +1,5 @@
1
1
  import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
2
- import type { InputProblem } from "lib/types/InputProblem"
2
+ import type { InputProblem, PinId } from "lib/types/InputProblem"
3
3
  import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
4
  import type { MspConnectionPairId } from "../MspConnectionPairSolver/MspConnectionPairSolver"
5
5
  import { SingleNetLabelPlacementSolver } from "./SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver"
@@ -24,6 +24,19 @@ export type OverlappingSameNetTraceGroup = {
24
24
  export interface NetLabelPlacement {
25
25
  globalConnNetId: string
26
26
  dcConnNetId?: string
27
+ /**
28
+ * Optional user-provided net identifier (if present in the input problem).
29
+ */
30
+ netId?: string
31
+ /**
32
+ * MSP pair ids that the label is associated with. Port-only labels use [].
33
+ */
34
+ mspConnectionPairIds: MspConnectionPairId[]
35
+ /**
36
+ * Pin ids relevant to this label. For a host trace, the two pins of that pair;
37
+ * for a port-only label, the single port pin id.
38
+ */
39
+ pinIds: PinId[]
27
40
  orientation: FacingDirection
28
41
 
29
42
  /**
@@ -3,7 +3,7 @@ import type {
3
3
  NetLabelPlacement,
4
4
  OverlappingSameNetTraceGroup,
5
5
  } from "../NetLabelPlacementSolver"
6
- import type { InputProblem } from "lib/types/InputProblem"
6
+ import type { InputProblem, PinId } from "lib/types/InputProblem"
7
7
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
8
8
  import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
9
9
  import type { FacingDirection } from "lib/utils/dir"
@@ -172,6 +172,8 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
172
172
  center: { x: number; y: number }
173
173
  hostSegIndex: number
174
174
  dcConnNetId: string
175
+ mspPairId: MspConnectionPairId
176
+ pinIds: PinId[]
175
177
  } | null = null
176
178
  let bestScore = -Infinity
177
179
 
@@ -285,6 +287,8 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
285
287
  center,
286
288
  hostSegIndex: si,
287
289
  dcConnNetId: curr.dcConnNetId,
290
+ mspPairId: curr.mspPairId,
291
+ pinIds: [curr.pins[0].pinId, curr.pins[1].pinId],
288
292
  }
289
293
  }
290
294
  // Continue traversing to prioritize the furthest valid point
@@ -295,6 +299,9 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
295
299
  globalConnNetId:
296
300
  this.overlappingSameNetTraceGroup.globalConnNetId,
297
301
  dcConnNetId: curr.dcConnNetId,
302
+ netId: this.overlappingSameNetTraceGroup.netId,
303
+ mspConnectionPairIds: [curr.mspPairId],
304
+ pinIds: [curr.pins[0].pinId, curr.pins[1].pinId],
298
305
  orientation,
299
306
  anchorPoint: anchor,
300
307
  width,
@@ -312,6 +319,9 @@ export class SingleNetLabelPlacementSolver extends BaseSolver {
312
319
  this.netLabelPlacement = {
313
320
  globalConnNetId: this.overlappingSameNetTraceGroup.globalConnNetId,
314
321
  dcConnNetId: bestCandidate.dcConnNetId,
322
+ netId: this.overlappingSameNetTraceGroup.netId,
323
+ mspConnectionPairIds: [bestCandidate.mspPairId],
324
+ pinIds: bestCandidate.pinIds,
315
325
  orientation: bestCandidate.orientation,
316
326
  anchorPoint: bestCandidate.anchor,
317
327
  width: bestCandidate.width,
@@ -159,6 +159,9 @@ export function solveNetLabelPlacementForPortOnlyPin(params: {
159
159
  const placement: NetLabelPlacement = {
160
160
  globalConnNetId: overlappingSameNetTraceGroup.globalConnNetId,
161
161
  dcConnNetId: undefined,
162
+ netId: overlappingSameNetTraceGroup.netId,
163
+ mspConnectionPairIds: [],
164
+ pinIds: [pinId],
162
165
  orientation,
163
166
  anchorPoint: anchor,
164
167
  width,
@@ -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.14",
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} />