@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 +14 -0
- package/dist/index.js +23 -6
- package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +14 -1
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +11 -1
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/solvePortOnlyPin.ts +3 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver.ts +1 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +6 -0
- package/package.json +6 -3
- package/site/PasteInput.page.tsx +83 -0
- package/site/components/DownloadDropdown.tsx +172 -0
- package/site/components/SolverBreadcrumbInputDownloader.tsx +2 -33
- package/site/examples/example06.page.tsx +60 -0
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 (
|
|
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+" &&
|
|
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+" &&
|
|
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 (
|
|
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 (
|
|
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,
|
package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/solvePortOnlyPin.ts
CHANGED
|
@@ -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,
|
|
@@ -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.
|
|
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.
|
|
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
|
-
<
|
|
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} />
|