@tscircuit/schematic-trace-solver 0.0.1
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/.claude/settings.local.json +6 -0
- package/.github/workflows/bun-formatcheck.yml +26 -0
- package/.github/workflows/bun-pver-release.yml +28 -0
- package/.github/workflows/bun-test.yml +28 -0
- package/.github/workflows/bun-typecheck.yml +26 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/biome.json +93 -0
- package/bunfig.toml +5 -0
- package/cosmos.config.json +6 -0
- package/cosmos.decorator.tsx +21 -0
- package/dist/index.d.ts +420 -0
- package/dist/index.js +7863 -0
- package/index.html +17 -0
- package/lib/data-structures/ChipObstacleSpatialIndex.ts +70 -0
- package/lib/index.ts +2 -0
- package/lib/solvers/BaseSolver/BaseSolver.ts +83 -0
- package/lib/solvers/GuidelinesSolver/GuidelinesSolver.ts +138 -0
- package/lib/solvers/GuidelinesSolver/getGeneratorForAllChipPairs.ts +39 -0
- package/lib/solvers/GuidelinesSolver/getHorizontalGuidelineY.ts +18 -0
- package/lib/solvers/GuidelinesSolver/getInputChipBounds.ts +20 -0
- package/lib/solvers/GuidelinesSolver/getVerticalGuidelineX.ts +18 -0
- package/lib/solvers/GuidelinesSolver/visualizeGuidelines.ts +45 -0
- package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +156 -0
- package/lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem.ts +20 -0
- package/lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts +96 -0
- package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +192 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +479 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts +127 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver.ts +191 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants.ts +132 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection.ts +42 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +244 -0
- package/lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts +89 -0
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts +180 -0
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +264 -0
- package/lib/types/InputProblem.ts +40 -0
- package/lib/utils/dir.ts +16 -0
- package/lib/utils/getAllPossibleOrderingsGenerator.ts +47 -0
- package/lib/utils/getColorFromString.ts +7 -0
- package/package.json +29 -0
- package/site/components/GenericSolverDebugger.tsx +0 -0
- package/site/components/PipelineDebugger.tsx +29 -0
- package/site/components/PipelineStageTable.tsx +149 -0
- package/site/components/SolverBreadcrumbInputDownloader.tsx +62 -0
- package/site/components/SolverToolbar.tsx +128 -0
- package/site/examples/example01-basic.page.tsx +104 -0
- package/tests/functions/generateElbowVariants.test.ts +98 -0
- package/tests/functions/getOrthogonalMinimumSpanningTree.test.ts +23 -0
- package/tsconfig.json +37 -0
- package/vite.config.ts +12 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
|
|
2
|
+
|
|
3
|
+
type StageStatus = "Solved" | "Failed" | "Running" | "Not Started"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Displays every stage of the pipeline with the status ("Solved", "Failed", "Running" or "Not Started"),
|
|
7
|
+
* what iteration it started on, what iteration it ended on, and the time it took to solve.
|
|
8
|
+
*
|
|
9
|
+
* The table also has a column "Actions" that has a download icon to download the getConstructorParams()
|
|
10
|
+
* of each stage.
|
|
11
|
+
*/
|
|
12
|
+
export const PipelineStageTable = ({
|
|
13
|
+
pipelineSolver,
|
|
14
|
+
triggerRender,
|
|
15
|
+
}: {
|
|
16
|
+
pipelineSolver: SchematicTracePipelineSolver
|
|
17
|
+
triggerRender: () => void
|
|
18
|
+
}) => {
|
|
19
|
+
const getStageStatus = (stageIndex: number): StageStatus => {
|
|
20
|
+
if (pipelineSolver.currentPipelineStepIndex > stageIndex) {
|
|
21
|
+
return "Solved"
|
|
22
|
+
}
|
|
23
|
+
if (pipelineSolver.currentPipelineStepIndex === stageIndex) {
|
|
24
|
+
if (pipelineSolver.failed) return "Failed"
|
|
25
|
+
if (pipelineSolver.activeSubSolver) return "Running"
|
|
26
|
+
}
|
|
27
|
+
return "Not Started"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const downloadParams = (stageName: string) => {
|
|
31
|
+
const stage = pipelineSolver.pipelineDef.find(
|
|
32
|
+
(s) => s.solverName === stageName,
|
|
33
|
+
)
|
|
34
|
+
if (!stage) return
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const params = stage.getConstructorParams(pipelineSolver)
|
|
38
|
+
const blob = new Blob([JSON.stringify(params, null, 2)], {
|
|
39
|
+
type: "application/json",
|
|
40
|
+
})
|
|
41
|
+
const url = URL.createObjectURL(blob)
|
|
42
|
+
const a = document.createElement("a")
|
|
43
|
+
a.href = url
|
|
44
|
+
a.download = `${stageName}_params.json`
|
|
45
|
+
a.click()
|
|
46
|
+
URL.revokeObjectURL(url)
|
|
47
|
+
} catch (error) {
|
|
48
|
+
alert(
|
|
49
|
+
`Error downloading params for ${stageName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="overflow-x-auto">
|
|
56
|
+
<table className="min-w-full border border-gray-300">
|
|
57
|
+
<thead>
|
|
58
|
+
<tr className="bg-gray-100">
|
|
59
|
+
<th className="border border-gray-300 px-4 py-2 text-left">
|
|
60
|
+
Stage
|
|
61
|
+
</th>
|
|
62
|
+
<th className="border border-gray-300 px-4 py-2 text-left">
|
|
63
|
+
Status
|
|
64
|
+
</th>
|
|
65
|
+
<th className="border border-gray-300 px-4 py-2 text-left">
|
|
66
|
+
Start Iteration
|
|
67
|
+
</th>
|
|
68
|
+
<th className="border border-gray-300 px-4 py-2 text-left">
|
|
69
|
+
End Iteration
|
|
70
|
+
</th>
|
|
71
|
+
<th className="border border-gray-300 px-4 py-2 text-left">
|
|
72
|
+
Time (ms)
|
|
73
|
+
</th>
|
|
74
|
+
<th className="border border-gray-300 px-4 py-2 text-left">
|
|
75
|
+
Actions
|
|
76
|
+
</th>
|
|
77
|
+
</tr>
|
|
78
|
+
</thead>
|
|
79
|
+
<tbody>
|
|
80
|
+
{pipelineSolver.pipelineDef.map((stage, index) => {
|
|
81
|
+
const status = getStageStatus(index)
|
|
82
|
+
const startIteration =
|
|
83
|
+
pipelineSolver.firstIterationOfPhase[stage.solverName]
|
|
84
|
+
const endIteration =
|
|
85
|
+
status === "Solved"
|
|
86
|
+
? (pipelineSolver.firstIterationOfPhase[stage.solverName] ||
|
|
87
|
+
0) +
|
|
88
|
+
((pipelineSolver as any)[stage.solverName]?.iterations || 0)
|
|
89
|
+
: undefined
|
|
90
|
+
const timeSpent = pipelineSolver.timeSpentOnPhase[stage.solverName]
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<tr key={stage.solverName} className="hover:bg-gray-50">
|
|
94
|
+
<td className="border border-gray-300 px-4 py-2 font-mono text-sm">
|
|
95
|
+
{stage.solverName}
|
|
96
|
+
</td>
|
|
97
|
+
<td className="border border-gray-300 px-4 py-2">
|
|
98
|
+
<div className="flex items-center gap-2">
|
|
99
|
+
<span
|
|
100
|
+
className={`px-2 py-1 rounded text-sm ${
|
|
101
|
+
status === "Solved"
|
|
102
|
+
? "bg-green-100 text-green-800"
|
|
103
|
+
: status === "Failed"
|
|
104
|
+
? "bg-red-100 text-red-800"
|
|
105
|
+
: status === "Running"
|
|
106
|
+
? "bg-yellow-100 text-yellow-800"
|
|
107
|
+
: "bg-gray-100 text-gray-800"
|
|
108
|
+
}`}
|
|
109
|
+
>
|
|
110
|
+
{status}
|
|
111
|
+
</span>
|
|
112
|
+
<button
|
|
113
|
+
onClick={() => {
|
|
114
|
+
pipelineSolver.solveUntilPhase(stage.solverName)
|
|
115
|
+
triggerRender()
|
|
116
|
+
}}
|
|
117
|
+
className="hover:bg-green-500 text-gray-600 hover:text-white px-2 py-1 rounded text-sm"
|
|
118
|
+
title={`Run until ${stage.solverName} is active`}
|
|
119
|
+
>
|
|
120
|
+
▶️
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
</td>
|
|
124
|
+
<td className="border border-gray-300 px-4 py-2 text-center">
|
|
125
|
+
{startIteration !== undefined ? startIteration : "-"}
|
|
126
|
+
</td>
|
|
127
|
+
<td className="border border-gray-300 px-4 py-2 text-center">
|
|
128
|
+
{endIteration !== undefined ? endIteration : "-"}
|
|
129
|
+
</td>
|
|
130
|
+
<td className="border border-gray-300 px-4 py-2 text-center">
|
|
131
|
+
{timeSpent !== undefined ? Math.round(timeSpent) : "-"}
|
|
132
|
+
</td>
|
|
133
|
+
<td className="border border-gray-300 px-4 py-2 text-center">
|
|
134
|
+
<button
|
|
135
|
+
onClick={() => downloadParams(stage.solverName)}
|
|
136
|
+
className="bg-blue-500 hover:bg-blue-600 text-white px-2 py-1 rounded text-sm"
|
|
137
|
+
title="Download constructor params"
|
|
138
|
+
>
|
|
139
|
+
⬇️
|
|
140
|
+
</button>
|
|
141
|
+
</td>
|
|
142
|
+
</tr>
|
|
143
|
+
)
|
|
144
|
+
})}
|
|
145
|
+
</tbody>
|
|
146
|
+
</table>
|
|
147
|
+
</div>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
2
|
+
|
|
3
|
+
export const getSolverChain = (solver: BaseSolver): BaseSolver[] => {
|
|
4
|
+
if (!solver.activeSubSolver) {
|
|
5
|
+
return [solver]
|
|
6
|
+
}
|
|
7
|
+
return [solver, ...getSolverChain(solver.activeSubSolver)]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Displays each solver in the chain as a breadcrumb with download functionality
|
|
12
|
+
*/
|
|
13
|
+
export const SolverBreadcrumbInputDownloader = ({
|
|
14
|
+
solver,
|
|
15
|
+
}: {
|
|
16
|
+
solver: BaseSolver
|
|
17
|
+
}) => {
|
|
18
|
+
const solverChain = getSolverChain(solver)
|
|
19
|
+
|
|
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
|
+
return (
|
|
47
|
+
<div className="flex gap-1 items-center text-sm pt-1">
|
|
48
|
+
{solverChain.map((s, index) => (
|
|
49
|
+
<div key={s.constructor.name} className="flex items-center">
|
|
50
|
+
{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>
|
|
58
|
+
</div>
|
|
59
|
+
))}
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
2
|
+
import { useReducer, useRef, useEffect } from "react"
|
|
3
|
+
import { SolverBreadcrumbInputDownloader } from "./SolverBreadcrumbInputDownloader"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The solver toolbar offers various actions for progressing the solver
|
|
7
|
+
* - "Step" - call solver.step()
|
|
8
|
+
* - "Solve" - call solver.solve()
|
|
9
|
+
* - "Animate" - call solver.step() at 40 iterations/second until solved or failed
|
|
10
|
+
* - When animate is pressed, show "Stop" button
|
|
11
|
+
*
|
|
12
|
+
* The toolbar also displays the number of iterations (solver.iterations)
|
|
13
|
+
*
|
|
14
|
+
* After any action, we need to incRenderCount internally to trigger a rerender
|
|
15
|
+
*/
|
|
16
|
+
export const SolverToolbar = ({
|
|
17
|
+
solver,
|
|
18
|
+
triggerRender,
|
|
19
|
+
}: {
|
|
20
|
+
solver: BaseSolver
|
|
21
|
+
triggerRender: Function
|
|
22
|
+
}) => {
|
|
23
|
+
const [isAnimating, setIsAnimating] = useReducer((x) => !x, false)
|
|
24
|
+
const animationRef = useRef<number | undefined>(undefined)
|
|
25
|
+
|
|
26
|
+
const handleStep = () => {
|
|
27
|
+
solver.step()
|
|
28
|
+
triggerRender()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const handleSolve = () => {
|
|
32
|
+
solver.solve()
|
|
33
|
+
triggerRender()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const handleAnimate = () => {
|
|
37
|
+
if (isAnimating) {
|
|
38
|
+
if (animationRef.current) {
|
|
39
|
+
clearInterval(animationRef.current)
|
|
40
|
+
animationRef.current = undefined
|
|
41
|
+
}
|
|
42
|
+
setIsAnimating()
|
|
43
|
+
} else {
|
|
44
|
+
setIsAnimating()
|
|
45
|
+
animationRef.current = window.setInterval(() => {
|
|
46
|
+
if (solver.solved || solver.failed) {
|
|
47
|
+
if (animationRef.current) {
|
|
48
|
+
clearInterval(animationRef.current)
|
|
49
|
+
animationRef.current = undefined
|
|
50
|
+
}
|
|
51
|
+
setIsAnimating()
|
|
52
|
+
triggerRender()
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
solver.step()
|
|
56
|
+
triggerRender()
|
|
57
|
+
}, 25) // 40 iterations/second = 25ms interval
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
return () => {
|
|
63
|
+
if (animationRef.current) {
|
|
64
|
+
clearInterval(animationRef.current)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}, [])
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if ((solver.solved || solver.failed) && isAnimating) {
|
|
71
|
+
if (animationRef.current) {
|
|
72
|
+
clearInterval(animationRef.current)
|
|
73
|
+
animationRef.current = undefined
|
|
74
|
+
}
|
|
75
|
+
setIsAnimating()
|
|
76
|
+
}
|
|
77
|
+
}, [solver.solved, solver.failed, isAnimating])
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className="space-y-1 px-1">
|
|
81
|
+
<SolverBreadcrumbInputDownloader solver={solver} />
|
|
82
|
+
|
|
83
|
+
<div className="flex gap-2 pb-1 items-center">
|
|
84
|
+
<button
|
|
85
|
+
onClick={handleStep}
|
|
86
|
+
disabled={solver.solved || solver.failed || isAnimating}
|
|
87
|
+
className="bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 text-white px-3 py-1 rounded"
|
|
88
|
+
>
|
|
89
|
+
Step
|
|
90
|
+
</button>
|
|
91
|
+
|
|
92
|
+
<button
|
|
93
|
+
onClick={handleSolve}
|
|
94
|
+
disabled={solver.solved || solver.failed || isAnimating}
|
|
95
|
+
className="bg-green-500 hover:bg-green-600 disabled:bg-gray-300 text-white px-3 py-1 rounded"
|
|
96
|
+
>
|
|
97
|
+
Solve
|
|
98
|
+
</button>
|
|
99
|
+
|
|
100
|
+
<button
|
|
101
|
+
onClick={handleAnimate}
|
|
102
|
+
disabled={solver.solved || solver.failed}
|
|
103
|
+
className={`px-3 py-1 rounded text-white ${
|
|
104
|
+
isAnimating
|
|
105
|
+
? "bg-red-500 hover:bg-red-600"
|
|
106
|
+
: "bg-yellow-500 hover:bg-yellow-600"
|
|
107
|
+
} disabled:bg-gray-300`}
|
|
108
|
+
>
|
|
109
|
+
{isAnimating ? "Stop" : "Animate"}
|
|
110
|
+
</button>
|
|
111
|
+
|
|
112
|
+
<div className="ml-4 text-sm text-gray-600">
|
|
113
|
+
Iterations: {solver.iterations}
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
{solver.solved && (
|
|
117
|
+
<div className="ml-2 px-2 py-1 bg-green-100 text-green-800 rounded text-sm">
|
|
118
|
+
Solved
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{solver.failed && (
|
|
124
|
+
<div className="text-red-500">Failed: {solver.error}</div>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
2
|
+
import { PipelineDebugger } from "site/components/PipelineDebugger"
|
|
3
|
+
|
|
4
|
+
const inputProblem: InputProblem = {
|
|
5
|
+
chips: [
|
|
6
|
+
{
|
|
7
|
+
chipId: "U1",
|
|
8
|
+
center: { x: 0, y: 0 },
|
|
9
|
+
width: 1.6,
|
|
10
|
+
height: 0.6,
|
|
11
|
+
pins: [
|
|
12
|
+
{
|
|
13
|
+
pinId: "U1.1",
|
|
14
|
+
x: -0.8,
|
|
15
|
+
y: 0.2,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
pinId: "U1.2",
|
|
19
|
+
x: -0.8,
|
|
20
|
+
y: 0,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
pinId: "U1.3",
|
|
24
|
+
x: -0.8,
|
|
25
|
+
y: -0.2,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
pinId: "U1.4",
|
|
29
|
+
x: 0.8,
|
|
30
|
+
y: -0.2,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
pinId: "U1.5",
|
|
34
|
+
x: 0.8,
|
|
35
|
+
y: 0,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
pinId: "U1.6",
|
|
39
|
+
x: 0.8,
|
|
40
|
+
y: 0.2,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
chipId: "C1",
|
|
46
|
+
center: { x: -2, y: 0 },
|
|
47
|
+
width: 0.5,
|
|
48
|
+
height: 1,
|
|
49
|
+
pins: [
|
|
50
|
+
{
|
|
51
|
+
pinId: "C1.1",
|
|
52
|
+
x: -2,
|
|
53
|
+
y: 0.5,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
pinId: "C1.2",
|
|
57
|
+
x: -2,
|
|
58
|
+
y: -0.5,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
chipId: "C2",
|
|
64
|
+
center: { x: -4, y: 0 },
|
|
65
|
+
width: 0.5,
|
|
66
|
+
height: 1,
|
|
67
|
+
pins: [
|
|
68
|
+
{
|
|
69
|
+
pinId: "C2.1",
|
|
70
|
+
x: -4,
|
|
71
|
+
y: 0.5,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
pinId: "C2.2",
|
|
75
|
+
x: -4,
|
|
76
|
+
y: -0.5,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
directConnections: [
|
|
82
|
+
{
|
|
83
|
+
pinIds: ["U1.1", "C1.1"],
|
|
84
|
+
netId: "VCC",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
pinIds: ["U1.2", "C2.1"],
|
|
88
|
+
netId: "EN",
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
netConnections: [
|
|
92
|
+
{
|
|
93
|
+
pinIds: ["U1.3", "C2.2", "C1.2"],
|
|
94
|
+
netId: "GND",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
availableNetLabelOrientations: {
|
|
98
|
+
VCC: ["y+", "y-"],
|
|
99
|
+
EN: ["x+", "x-"],
|
|
100
|
+
GND: ["y+", "y-"],
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export default () => <PipelineDebugger inputProblem={inputProblem} />
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { generateElbowVariants } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants"
|
|
2
|
+
import { test, expect } from "bun:test"
|
|
3
|
+
import type { Guideline } from "lib/solvers/GuidelinesSolver/GuidelinesSolver"
|
|
4
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
5
|
+
|
|
6
|
+
test("generateElbowVariants - simple horizontal segment", () => {
|
|
7
|
+
const baseElbow: Point[] = [
|
|
8
|
+
{ x: 0, y: 0 },
|
|
9
|
+
{ x: 1, y: 0 },
|
|
10
|
+
{ x: 1, y: 2 },
|
|
11
|
+
{ x: 3, y: 2 },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
const guidelines: Guideline[] = [
|
|
15
|
+
{ orientation: "vertical", x: 0.5, y: undefined },
|
|
16
|
+
{ orientation: "vertical", x: 1.5, y: undefined },
|
|
17
|
+
{ orientation: "horizontal", x: undefined, y: 1 },
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
const result = generateElbowVariants({ baseElbow, guidelines })
|
|
21
|
+
|
|
22
|
+
expect(result.movableSegments).toHaveLength(2)
|
|
23
|
+
expect(result.movableSegments[0].freedom).toBe("y+")
|
|
24
|
+
expect(result.movableSegments[1].freedom).toBe("x+")
|
|
25
|
+
|
|
26
|
+
// Should generate variants for each combination of guideline positions
|
|
27
|
+
expect(result.elbowVariants.length).toBeGreaterThan(1)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test("generateElbowVariants - no movable segments", () => {
|
|
31
|
+
const baseElbow: Point[] = [
|
|
32
|
+
{ x: 0, y: 0 },
|
|
33
|
+
{ x: 1, y: 1 },
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
const guidelines: Guideline[] = [
|
|
37
|
+
{ orientation: "vertical", x: 0.5, y: undefined },
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
const result = generateElbowVariants({ baseElbow, guidelines })
|
|
41
|
+
|
|
42
|
+
expect(result.movableSegments).toHaveLength(0)
|
|
43
|
+
expect(result.elbowVariants).toHaveLength(1)
|
|
44
|
+
expect(result.elbowVariants[0]).toEqual(baseElbow)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test("generateElbowVariants - vertical movable segment", () => {
|
|
48
|
+
const baseElbow: Point[] = [
|
|
49
|
+
{ x: 0, y: 0 },
|
|
50
|
+
{ x: 0, y: 1 },
|
|
51
|
+
{ x: 0, y: 2 },
|
|
52
|
+
{ x: 2, y: 2 },
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
const guidelines: Guideline[] = [
|
|
56
|
+
{ orientation: "horizontal", x: undefined, y: 0.5 },
|
|
57
|
+
{ orientation: "horizontal", x: undefined, y: 1.5 },
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
const result = generateElbowVariants({ baseElbow, guidelines })
|
|
61
|
+
|
|
62
|
+
expect(result.movableSegments).toHaveLength(1)
|
|
63
|
+
expect(result.movableSegments[0].freedom).toBe("x+")
|
|
64
|
+
|
|
65
|
+
// Should include original position plus guideline positions
|
|
66
|
+
expect(result.elbowVariants.length).toBe(3) // Original + 2 guidelines
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test("generateElbowVariants - multiple segments with guidelines", () => {
|
|
70
|
+
const baseElbow: Point[] = [
|
|
71
|
+
{ x: 0, y: 0 },
|
|
72
|
+
{ x: 1, y: 0 },
|
|
73
|
+
{ x: 1, y: 1 },
|
|
74
|
+
{ x: 2, y: 1 },
|
|
75
|
+
{ x: 2, y: 2 },
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
const guidelines: Guideline[] = [
|
|
79
|
+
{ orientation: "vertical", x: 0.5, y: undefined },
|
|
80
|
+
{ orientation: "vertical", x: 1.5, y: undefined },
|
|
81
|
+
{ orientation: "horizontal", x: undefined, y: 0.5 },
|
|
82
|
+
{ orientation: "horizontal", x: undefined, y: 1.5 },
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
const result = generateElbowVariants({ baseElbow, guidelines })
|
|
86
|
+
|
|
87
|
+
expect(result.movableSegments).toHaveLength(3)
|
|
88
|
+
|
|
89
|
+
// First segment moves vertically (y+/y-)
|
|
90
|
+
expect(result.movableSegments[0].freedom).toMatch(/^y[+-]$/)
|
|
91
|
+
// Second segment moves horizontally (x+/x-)
|
|
92
|
+
expect(result.movableSegments[1].freedom).toMatch(/^x[+-]$/)
|
|
93
|
+
// Third segment moves vertically (y+/y-)
|
|
94
|
+
expect(result.movableSegments[2].freedom).toMatch(/^y[+-]$/)
|
|
95
|
+
|
|
96
|
+
// Should generate multiple variants
|
|
97
|
+
expect(result.elbowVariants.length).toBeGreaterThan(1)
|
|
98
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getOrthogonalMinimumSpanningTree } from "lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins"
|
|
2
|
+
import { test, expect } from "bun:test"
|
|
3
|
+
import type { InputPin } from "lib/types/InputProblem"
|
|
4
|
+
|
|
5
|
+
test("getOrthogonalMinimumSpanningTree", () => {
|
|
6
|
+
const pins: InputPin[] = [
|
|
7
|
+
{ x: 0, y: 0, pinId: "A" },
|
|
8
|
+
{ x: 1, y: 1, pinId: "B" },
|
|
9
|
+
{ x: 10, y: 10, pinId: "C" },
|
|
10
|
+
{ x: 11, y: 11, pinId: "D" },
|
|
11
|
+
]
|
|
12
|
+
const msp = getOrthogonalMinimumSpanningTree(pins)
|
|
13
|
+
expect(
|
|
14
|
+
msp
|
|
15
|
+
.map(([a, b]) => `${a}->${b}`)
|
|
16
|
+
.sort()
|
|
17
|
+
.join("\n"),
|
|
18
|
+
).toMatchInlineSnapshot(`
|
|
19
|
+
"B->A
|
|
20
|
+
C->B
|
|
21
|
+
D->C"
|
|
22
|
+
`)
|
|
23
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext", "DOM"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
"paths": {
|
|
12
|
+
"lib/*": ["lib/*"],
|
|
13
|
+
"site/*": ["site/*"],
|
|
14
|
+
"tests/*": ["tests/*"]
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
"baseUrl": ".",
|
|
18
|
+
|
|
19
|
+
// Bundler mode
|
|
20
|
+
"moduleResolution": "bundler",
|
|
21
|
+
"allowImportingTsExtensions": true,
|
|
22
|
+
"verbatimModuleSyntax": true,
|
|
23
|
+
"noEmit": true,
|
|
24
|
+
|
|
25
|
+
// Best practices
|
|
26
|
+
"strict": true,
|
|
27
|
+
"skipLibCheck": true,
|
|
28
|
+
"noFallthroughCasesInSwitch": true,
|
|
29
|
+
"noUncheckedIndexedAccess": false,
|
|
30
|
+
"noImplicitOverride": true,
|
|
31
|
+
|
|
32
|
+
// Some stricter flags (disabled by default)
|
|
33
|
+
"noUnusedLocals": false,
|
|
34
|
+
"noUnusedParameters": false,
|
|
35
|
+
"noPropertyAccessFromIndexSignature": false
|
|
36
|
+
}
|
|
37
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from "vite"
|
|
2
|
+
import path from "path"
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
resolve: {
|
|
6
|
+
alias: {
|
|
7
|
+
lib: path.resolve(__dirname, "lib"),
|
|
8
|
+
site: path.resolve(__dirname, "site"),
|
|
9
|
+
tests: path.resolve(__dirname, "tests"),
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
})
|