@tscircuit/copper-pour-solver 0.0.33 → 0.0.34
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/README.md +139 -20
- package/cosmos.config.json +2 -1
- package/dist/index.d.ts +14 -5
- package/dist/index.js +111 -7
- package/lib/circuit-json/ConvertCircuitJsonToInputProblemOptions.ts +18 -0
- package/lib/circuit-json/buildSubcircuitConnectivityLookup.ts +54 -0
- package/lib/circuit-json/convert-circuit-json-to-input-problem.ts +16 -21
- package/lib/circuit-json/getElementSubcircuitConnectivityKey.ts +8 -0
- package/lib/circuit-json/resolvePourConnectivityKey.ts +61 -0
- package/lib/index.ts +1 -0
- package/package.json +5 -1
- package/site/Welcome.page.tsx +214 -0
- package/tests/connectivity-key-api.test.ts +100 -0
- package/tests/pill-pad-copper-pour.test.ts +1 -1
- package/tests/repro01-business-via-card/__snapshots__/repro01-business-via-card.snap.svg +1 -1
- package/tests/utils/run-solver-and-render-to-svg.ts +5 -13
- package/vercel.json +5 -0
package/site/Welcome.page.tsx
CHANGED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type React from "react"
|
|
2
|
+
|
|
3
|
+
const codeSample = `import {
|
|
4
|
+
CopperPourPipelineSolver,
|
|
5
|
+
convertCircuitJsonToInputProblem,
|
|
6
|
+
initializeManifoldGeometry,
|
|
7
|
+
} from "@tscircuit/copper-pour-solver"
|
|
8
|
+
|
|
9
|
+
await initializeManifoldGeometry()
|
|
10
|
+
|
|
11
|
+
const inputProblem = convertCircuitJsonToInputProblem(circuitJson, {
|
|
12
|
+
layer: "top",
|
|
13
|
+
source_net_name: "GND",
|
|
14
|
+
pad_margin: 0.4,
|
|
15
|
+
trace_margin: 0.2,
|
|
16
|
+
board_edge_margin: 0.1,
|
|
17
|
+
cutout_margin: 0.2,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const solver = new CopperPourPipelineSolver(inputProblem)
|
|
21
|
+
const { brep_shapes } = solver.getOutput()`
|
|
22
|
+
|
|
23
|
+
const developmentSample = `bun install
|
|
24
|
+
bun run build
|
|
25
|
+
bun test
|
|
26
|
+
bun start
|
|
27
|
+
bun run build:site`
|
|
28
|
+
|
|
29
|
+
export default function WelcomePage() {
|
|
30
|
+
return (
|
|
31
|
+
<main style={styles.main}>
|
|
32
|
+
<header style={styles.header}>
|
|
33
|
+
<p style={styles.eyebrow}>tscircuit geometry</p>
|
|
34
|
+
<h1 style={styles.title}>@tscircuit/copper-pour-solver</h1>
|
|
35
|
+
<p style={styles.lede}>
|
|
36
|
+
Generate PCB copper pour B-Rep polygons from Circuit JSON or from a
|
|
37
|
+
direct geometry input format.
|
|
38
|
+
</p>
|
|
39
|
+
<nav style={styles.actions} aria-label="Project links">
|
|
40
|
+
<a style={{ ...styles.button, ...styles.primaryButton }} href="/">
|
|
41
|
+
Cosmos docs
|
|
42
|
+
</a>
|
|
43
|
+
<a
|
|
44
|
+
style={styles.button}
|
|
45
|
+
href="https://github.com/tscircuit/copper-pour-solver"
|
|
46
|
+
>
|
|
47
|
+
GitHub
|
|
48
|
+
</a>
|
|
49
|
+
<a
|
|
50
|
+
style={styles.button}
|
|
51
|
+
href="https://www.npmjs.com/package/@tscircuit/copper-pour-solver"
|
|
52
|
+
>
|
|
53
|
+
npm
|
|
54
|
+
</a>
|
|
55
|
+
</nav>
|
|
56
|
+
</header>
|
|
57
|
+
|
|
58
|
+
<section style={styles.section}>
|
|
59
|
+
<h2 style={styles.heading}>Install</h2>
|
|
60
|
+
<pre style={styles.pre}>
|
|
61
|
+
<code>bun add @tscircuit/copper-pour-solver</code>
|
|
62
|
+
</pre>
|
|
63
|
+
</section>
|
|
64
|
+
|
|
65
|
+
<section style={styles.section}>
|
|
66
|
+
<h2 style={styles.heading}>Use With Circuit JSON</h2>
|
|
67
|
+
<p style={styles.copy}>
|
|
68
|
+
Initialize the Manifold geometry runtime once, convert Circuit JSON
|
|
69
|
+
into a solver input problem by source net name, then read the
|
|
70
|
+
generated B-Rep shapes.
|
|
71
|
+
</p>
|
|
72
|
+
<pre style={styles.pre}>
|
|
73
|
+
<code>{codeSample}</code>
|
|
74
|
+
</pre>
|
|
75
|
+
</section>
|
|
76
|
+
|
|
77
|
+
<section style={styles.section}>
|
|
78
|
+
<h2 style={styles.heading}>What The Converter Reads</h2>
|
|
79
|
+
<div style={styles.grid}>
|
|
80
|
+
<div style={styles.item}>
|
|
81
|
+
<strong>Board geometry</strong>
|
|
82
|
+
<span>
|
|
83
|
+
Board bounds, board outlines, and optional pour outlines.
|
|
84
|
+
</span>
|
|
85
|
+
</div>
|
|
86
|
+
<div style={styles.item}>
|
|
87
|
+
<strong>Obstacles</strong>
|
|
88
|
+
<span>
|
|
89
|
+
SMT pads, plated holes, mechanical holes, vias, traces, and
|
|
90
|
+
cutouts.
|
|
91
|
+
</span>
|
|
92
|
+
</div>
|
|
93
|
+
<div style={styles.item}>
|
|
94
|
+
<strong>Connectivity</strong>
|
|
95
|
+
<span>
|
|
96
|
+
Source net ids, source net names, or stable
|
|
97
|
+
subcircuit_connectivity_map_key values.
|
|
98
|
+
</span>
|
|
99
|
+
</div>
|
|
100
|
+
<div style={styles.item}>
|
|
101
|
+
<strong>Clearances</strong>
|
|
102
|
+
<span>Pad, trace, board edge, and cutout margin controls.</span>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</section>
|
|
106
|
+
|
|
107
|
+
<section style={styles.section}>
|
|
108
|
+
<h2 style={styles.heading}>Development</h2>
|
|
109
|
+
<pre style={styles.pre}>
|
|
110
|
+
<code>{developmentSample}</code>
|
|
111
|
+
</pre>
|
|
112
|
+
</section>
|
|
113
|
+
</main>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const styles: Record<string, React.CSSProperties> = {
|
|
118
|
+
main: {
|
|
119
|
+
width: "min(980px, calc(100% - 32px))",
|
|
120
|
+
margin: "0 auto",
|
|
121
|
+
padding: "48px 0 64px",
|
|
122
|
+
color: "#17202a",
|
|
123
|
+
fontFamily:
|
|
124
|
+
'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
125
|
+
lineHeight: 1.55,
|
|
126
|
+
},
|
|
127
|
+
header: {
|
|
128
|
+
borderBottom: "1px solid #d7dee4",
|
|
129
|
+
paddingBottom: 28,
|
|
130
|
+
marginBottom: 32,
|
|
131
|
+
},
|
|
132
|
+
eyebrow: {
|
|
133
|
+
margin: "0 0 10px",
|
|
134
|
+
color: "#0b766e",
|
|
135
|
+
fontSize: 13,
|
|
136
|
+
fontWeight: 700,
|
|
137
|
+
textTransform: "uppercase",
|
|
138
|
+
letterSpacing: 0,
|
|
139
|
+
},
|
|
140
|
+
title: {
|
|
141
|
+
margin: "0 0 12px",
|
|
142
|
+
fontSize: "clamp(2rem, 7vw, 4rem)",
|
|
143
|
+
lineHeight: 1,
|
|
144
|
+
letterSpacing: 0,
|
|
145
|
+
},
|
|
146
|
+
lede: {
|
|
147
|
+
maxWidth: 760,
|
|
148
|
+
margin: "0 0 16px",
|
|
149
|
+
color: "#5f6f7a",
|
|
150
|
+
fontSize: "1.12rem",
|
|
151
|
+
},
|
|
152
|
+
actions: {
|
|
153
|
+
display: "flex",
|
|
154
|
+
flexWrap: "wrap",
|
|
155
|
+
gap: 10,
|
|
156
|
+
marginTop: 22,
|
|
157
|
+
},
|
|
158
|
+
button: {
|
|
159
|
+
display: "inline-flex",
|
|
160
|
+
alignItems: "center",
|
|
161
|
+
minHeight: 38,
|
|
162
|
+
padding: "7px 12px",
|
|
163
|
+
border: "1px solid #d7dee4",
|
|
164
|
+
borderRadius: 6,
|
|
165
|
+
textDecoration: "none",
|
|
166
|
+
color: "#17202a",
|
|
167
|
+
background: "#ffffff",
|
|
168
|
+
fontWeight: 650,
|
|
169
|
+
},
|
|
170
|
+
primaryButton: {
|
|
171
|
+
color: "#ffffff",
|
|
172
|
+
background: "#0b766e",
|
|
173
|
+
borderColor: "#0b766e",
|
|
174
|
+
},
|
|
175
|
+
section: {
|
|
176
|
+
marginTop: 34,
|
|
177
|
+
},
|
|
178
|
+
heading: {
|
|
179
|
+
margin: "0 0 12px",
|
|
180
|
+
fontSize: "1.35rem",
|
|
181
|
+
letterSpacing: 0,
|
|
182
|
+
},
|
|
183
|
+
copy: {
|
|
184
|
+
maxWidth: 760,
|
|
185
|
+
margin: "0 0 16px",
|
|
186
|
+
color: "#34444f",
|
|
187
|
+
},
|
|
188
|
+
pre: {
|
|
189
|
+
overflowX: "auto",
|
|
190
|
+
margin: "14px 0 24px",
|
|
191
|
+
padding: 16,
|
|
192
|
+
border: "1px solid #d7dee4",
|
|
193
|
+
borderRadius: 8,
|
|
194
|
+
background: "#f7f9fb",
|
|
195
|
+
color: "#10212d",
|
|
196
|
+
fontFamily:
|
|
197
|
+
'"SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace',
|
|
198
|
+
fontSize: 14,
|
|
199
|
+
},
|
|
200
|
+
grid: {
|
|
201
|
+
display: "grid",
|
|
202
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(230px, 1fr))",
|
|
203
|
+
gap: 14,
|
|
204
|
+
margin: "18px 0 8px",
|
|
205
|
+
},
|
|
206
|
+
item: {
|
|
207
|
+
display: "grid",
|
|
208
|
+
gap: 4,
|
|
209
|
+
border: "1px solid #d7dee4",
|
|
210
|
+
borderRadius: 8,
|
|
211
|
+
padding: 14,
|
|
212
|
+
background: "#ffffff",
|
|
213
|
+
},
|
|
214
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import type { AnyCircuitElement } from "circuit-json"
|
|
3
|
+
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
|
|
4
|
+
import { convertCircuitJsonToInputProblem } from "lib/circuit-json/convert-circuit-json-to-input-problem"
|
|
5
|
+
|
|
6
|
+
const circuitJson = [
|
|
7
|
+
{
|
|
8
|
+
type: "pcb_board",
|
|
9
|
+
pcb_board_id: "pcb_board_0",
|
|
10
|
+
center: { x: 0, y: 0 },
|
|
11
|
+
width: 10,
|
|
12
|
+
height: 10,
|
|
13
|
+
thickness: 1.4,
|
|
14
|
+
num_layers: 2,
|
|
15
|
+
material: "fr4",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: "source_net",
|
|
19
|
+
source_net_id: "source_net_gnd",
|
|
20
|
+
name: "GND",
|
|
21
|
+
subcircuit_connectivity_map_key: "stable_subcircuit_net_gnd",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: "source_trace",
|
|
25
|
+
source_trace_id: "source_trace_gnd",
|
|
26
|
+
connected_source_net_ids: ["source_net_gnd"],
|
|
27
|
+
connected_source_port_ids: ["source_port_gnd"],
|
|
28
|
+
subcircuit_connectivity_map_key: "stable_subcircuit_net_gnd",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: "source_port",
|
|
32
|
+
source_port_id: "source_port_gnd",
|
|
33
|
+
source_component_id: "source_component_r1",
|
|
34
|
+
name: "1",
|
|
35
|
+
subcircuit_connectivity_map_key: "stable_subcircuit_net_gnd",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: "pcb_port",
|
|
39
|
+
pcb_port_id: "pcb_port_gnd",
|
|
40
|
+
pcb_component_id: "pcb_component_r1",
|
|
41
|
+
source_port_id: "source_port_gnd",
|
|
42
|
+
x: 0,
|
|
43
|
+
y: 0,
|
|
44
|
+
layers: ["top"],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: "pcb_smtpad",
|
|
48
|
+
pcb_smtpad_id: "pcb_smtpad_gnd",
|
|
49
|
+
pcb_component_id: "pcb_component_r1",
|
|
50
|
+
pcb_port_id: "pcb_port_gnd",
|
|
51
|
+
layer: "top",
|
|
52
|
+
shape: "rect",
|
|
53
|
+
x: 0,
|
|
54
|
+
y: 0,
|
|
55
|
+
width: 1,
|
|
56
|
+
height: 1,
|
|
57
|
+
port_hints: ["1"],
|
|
58
|
+
},
|
|
59
|
+
] as AnyCircuitElement[]
|
|
60
|
+
|
|
61
|
+
test("circuit-json adapter resolves pours to subcircuit connectivity keys", () => {
|
|
62
|
+
const connectivityMap = getFullConnectivityMapFromCircuitJson(circuitJson)
|
|
63
|
+
const generatedConnectivityMapKey =
|
|
64
|
+
connectivityMap.getNetConnectedToId("source_net_gnd")
|
|
65
|
+
|
|
66
|
+
expect(generatedConnectivityMapKey).toBeDefined()
|
|
67
|
+
expect(generatedConnectivityMapKey).not.toBe("stable_subcircuit_net_gnd")
|
|
68
|
+
|
|
69
|
+
const inputProblem = convertCircuitJsonToInputProblem(circuitJson, {
|
|
70
|
+
layer: "top",
|
|
71
|
+
source_net_id: "source_net_gnd",
|
|
72
|
+
pad_margin: 0.2,
|
|
73
|
+
trace_margin: 0.2,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
expect(inputProblem.regionsForPour[0]?.connectivityKey).toBe(
|
|
77
|
+
"stable_subcircuit_net_gnd",
|
|
78
|
+
)
|
|
79
|
+
expect(
|
|
80
|
+
inputProblem.pads.find((pad) => pad.padId === "pcb_smtpad_gnd"),
|
|
81
|
+
).toMatchObject({
|
|
82
|
+
connectivityKey: "stable_subcircuit_net_gnd",
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test("circuit-json adapter rejects generated connectivity map keys", () => {
|
|
87
|
+
const generatedConnectivityMapKey =
|
|
88
|
+
getFullConnectivityMapFromCircuitJson(circuitJson).getNetConnectedToId(
|
|
89
|
+
"source_net_gnd",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
expect(() =>
|
|
93
|
+
convertCircuitJsonToInputProblem(circuitJson, {
|
|
94
|
+
layer: "top",
|
|
95
|
+
pour_connectivity_key: generatedConnectivityMapKey!,
|
|
96
|
+
pad_margin: 0.2,
|
|
97
|
+
trace_margin: 0.2,
|
|
98
|
+
}),
|
|
99
|
+
).toThrow(/subcircuit_connectivity_map_key/)
|
|
100
|
+
})
|
|
@@ -71,7 +71,7 @@ const circuitJson: AnyCircuitElement[] = [
|
|
|
71
71
|
const getInputProblem = () =>
|
|
72
72
|
convertCircuitJsonToInputProblem(circuitJson, {
|
|
73
73
|
layer: "top",
|
|
74
|
-
|
|
74
|
+
subcircuit_connectivity_map_key: "net:GND",
|
|
75
75
|
pad_margin: 0.15,
|
|
76
76
|
trace_margin: 0.15,
|
|
77
77
|
})
|