@tscircuit/copper-pour-solver 0.0.33 → 0.0.35
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 +148 -20
- package/cosmos.config.json +2 -1
- package/dist/index.d.ts +15 -5
- package/dist/index.js +252 -8
- package/lib/circuit-json/ConvertCircuitJsonToInputProblemOptions.ts +19 -0
- package/lib/circuit-json/buildSubcircuitConnectivityLookup.ts +237 -0
- package/lib/circuit-json/convert-circuit-json-to-input-problem.ts +22 -22
- package/lib/circuit-json/getElementSubcircuitConnectivityKey.ts +8 -0
- package/lib/circuit-json/resolvePourConnectivityKey.ts +88 -0
- package/lib/index.ts +1 -0
- package/package.json +8 -3
- package/site/Welcome.page.tsx +214 -0
- package/tests/__snapshots__/tsx-subcircuit-connectivity01.snap.svg +1 -0
- package/tests/__snapshots__/tsx-subcircuit-connectivity02.snap.svg +1 -0
- package/tests/assets/subcircuit-connectivity-scope.json +126 -0
- package/tests/connectivity-key-api.test.ts +146 -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/tsx-subcircuit-connectivity.test.tsx +223 -0
- package/tests/utils/run-solver-and-render-to-svg.ts +5 -13
- package/vercel.json +5 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { Circuit } from "@tscircuit/core"
|
|
3
|
+
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg"
|
|
4
|
+
import type {
|
|
5
|
+
AnyCircuitElement,
|
|
6
|
+
PcbCopperPourBRep,
|
|
7
|
+
SourceNet,
|
|
8
|
+
} from "circuit-json"
|
|
9
|
+
import { CopperPourPipelineSolver } from "lib/index"
|
|
10
|
+
import { convertCircuitJsonToInputProblem } from "lib/circuit-json/convert-circuit-json-to-input-problem"
|
|
11
|
+
|
|
12
|
+
const SubcircuitChild = () => (
|
|
13
|
+
<subcircuit
|
|
14
|
+
name="SubcircuitChild"
|
|
15
|
+
pcbX={2}
|
|
16
|
+
pcbY={0}
|
|
17
|
+
autorouter="sequential_trace"
|
|
18
|
+
>
|
|
19
|
+
<pinheader
|
|
20
|
+
name="J2"
|
|
21
|
+
pinCount={2}
|
|
22
|
+
footprint="pinrow2"
|
|
23
|
+
pcbX={0}
|
|
24
|
+
pcbY={0}
|
|
25
|
+
pinLabels={{ pin1: "B", pin2: "C" }}
|
|
26
|
+
showSilkscreenPinLabels
|
|
27
|
+
connections={{ C: "net.GND" }}
|
|
28
|
+
/>
|
|
29
|
+
</subcircuit>
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
interface SubcircuitConnectivityReproProps {
|
|
33
|
+
includeParentToChildTrace: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SubcircuitConnectivityRepro = ({
|
|
37
|
+
includeParentToChildTrace,
|
|
38
|
+
}: SubcircuitConnectivityReproProps) => (
|
|
39
|
+
<board
|
|
40
|
+
name="SubcircuitParent"
|
|
41
|
+
width="10mm"
|
|
42
|
+
height="7mm"
|
|
43
|
+
autorouter="sequential_trace"
|
|
44
|
+
>
|
|
45
|
+
<pinheader
|
|
46
|
+
name="J1"
|
|
47
|
+
pinCount={1}
|
|
48
|
+
footprint="pinrow1"
|
|
49
|
+
pcbX={-3}
|
|
50
|
+
pcbY={0}
|
|
51
|
+
pinLabels={{ pin1: "A" }}
|
|
52
|
+
showSilkscreenPinLabels
|
|
53
|
+
connections={{ A: "net.GND" }}
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
<SubcircuitChild />
|
|
57
|
+
|
|
58
|
+
{includeParentToChildTrace && (
|
|
59
|
+
<trace from="J1.A" to=".SubcircuitChild > net.GND" />
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
<copperpour
|
|
63
|
+
name="top_gnd_pour"
|
|
64
|
+
layer="top"
|
|
65
|
+
connectsTo="net.GND"
|
|
66
|
+
padMargin="0.25mm"
|
|
67
|
+
traceMargin="0.2mm"
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<pcbnotetext
|
|
71
|
+
text={
|
|
72
|
+
includeParentToChildTrace
|
|
73
|
+
? "Expected: copper pour connects to A and C, but clears around B."
|
|
74
|
+
: "Expected: copper pour connects to A, but clears around B and C."
|
|
75
|
+
}
|
|
76
|
+
pcbX={0}
|
|
77
|
+
pcbY={4.35}
|
|
78
|
+
fontSize="0.22mm"
|
|
79
|
+
anchorAlignment="center"
|
|
80
|
+
color="#ffffff"
|
|
81
|
+
/>
|
|
82
|
+
<pcbnotetext
|
|
83
|
+
text={
|
|
84
|
+
includeParentToChildTrace
|
|
85
|
+
? "A is parent net.GND; C is child net.GND reached through the parent-to-child net."
|
|
86
|
+
: "A is parent net.GND; C is child net.GND with no parent-to-child net."
|
|
87
|
+
}
|
|
88
|
+
pcbX={0}
|
|
89
|
+
pcbY={4.05}
|
|
90
|
+
fontSize="0.22mm"
|
|
91
|
+
anchorAlignment="center"
|
|
92
|
+
color="#ffffff"
|
|
93
|
+
/>
|
|
94
|
+
<pcbnotetext
|
|
95
|
+
text="B is a separate child pin, so the pour should clear around it."
|
|
96
|
+
pcbX={0}
|
|
97
|
+
pcbY={3.75}
|
|
98
|
+
fontSize="0.22mm"
|
|
99
|
+
anchorAlignment="center"
|
|
100
|
+
color="#ffffff"
|
|
101
|
+
/>
|
|
102
|
+
</board>
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const renderCircuitJson = async (
|
|
106
|
+
props: SubcircuitConnectivityReproProps,
|
|
107
|
+
): Promise<AnyCircuitElement[]> => {
|
|
108
|
+
const circuit = new Circuit()
|
|
109
|
+
const originalLog = console.log
|
|
110
|
+
const originalWarn = console.warn
|
|
111
|
+
console.log = () => {}
|
|
112
|
+
console.warn = () => {}
|
|
113
|
+
try {
|
|
114
|
+
circuit.add(<SubcircuitConnectivityRepro {...props} />)
|
|
115
|
+
await circuit.renderUntilSettled()
|
|
116
|
+
return circuit.getCircuitJson()
|
|
117
|
+
} finally {
|
|
118
|
+
console.log = originalLog
|
|
119
|
+
console.warn = originalWarn
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const getPadCenterX = (pad: any) => {
|
|
124
|
+
if (typeof pad.x === "number") return pad.x
|
|
125
|
+
return (pad.bounds.minX + pad.bounds.maxX) / 2
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const getPcbPadConnectivityKeys = (inputProblem: any) => {
|
|
129
|
+
const [aPad, bPad, cPad] = inputProblem.pads
|
|
130
|
+
.filter((pad: any) => pad.shape === "circle" || pad.shape === "rect")
|
|
131
|
+
.sort((a: any, b: any) => getPadCenterX(a) - getPadCenterX(b))
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
a: aPad?.connectivityKey,
|
|
135
|
+
b: bPad?.connectivityKey,
|
|
136
|
+
c: cPad?.connectivityKey,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const runSubcircuitConnectivityCase = async ({
|
|
141
|
+
includeParentToChildTrace,
|
|
142
|
+
snapshotName,
|
|
143
|
+
expectedSourceNetConnectivityKeyCount,
|
|
144
|
+
expectChildGndConnectedToPour,
|
|
145
|
+
}: {
|
|
146
|
+
includeParentToChildTrace: boolean
|
|
147
|
+
snapshotName: string
|
|
148
|
+
expectedSourceNetConnectivityKeyCount: number
|
|
149
|
+
expectChildGndConnectedToPour: boolean
|
|
150
|
+
}) => {
|
|
151
|
+
const circuitJson = await renderCircuitJson({ includeParentToChildTrace })
|
|
152
|
+
const gndSourceNets = circuitJson.filter(
|
|
153
|
+
(element): element is SourceNet =>
|
|
154
|
+
element.type === "source_net" && element.name === "GND",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
expect(gndSourceNets).toHaveLength(2)
|
|
158
|
+
expect(
|
|
159
|
+
new Set(gndSourceNets.map((net) => net.subcircuit_connectivity_map_key))
|
|
160
|
+
.size,
|
|
161
|
+
).toBe(expectedSourceNetConnectivityKeyCount)
|
|
162
|
+
expect(new Set(gndSourceNets.map((net) => net.subcircuit_id)).size).toBe(2)
|
|
163
|
+
|
|
164
|
+
const parentGndSourceNet = gndSourceNets.find((net) =>
|
|
165
|
+
net.subcircuit_connectivity_map_key?.includes("SubcircuitParent"),
|
|
166
|
+
)
|
|
167
|
+
expect(parentGndSourceNet).toBeDefined()
|
|
168
|
+
|
|
169
|
+
const inputProblem = convertCircuitJsonToInputProblem(circuitJson, {
|
|
170
|
+
layer: "top",
|
|
171
|
+
source_net_id: parentGndSourceNet!.source_net_id,
|
|
172
|
+
pad_margin: 0.25,
|
|
173
|
+
trace_margin: 0.2,
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const pourConnectivityKey = inputProblem.regionsForPour[0]?.connectivityKey
|
|
177
|
+
const padConnectivityKeys = getPcbPadConnectivityKeys(inputProblem)
|
|
178
|
+
|
|
179
|
+
expect(padConnectivityKeys.a).toBe(pourConnectivityKey)
|
|
180
|
+
expect(padConnectivityKeys.b).not.toBe(pourConnectivityKey)
|
|
181
|
+
if (expectChildGndConnectedToPour) {
|
|
182
|
+
expect(padConnectivityKeys.c).toBe(pourConnectivityKey)
|
|
183
|
+
} else {
|
|
184
|
+
expect(padConnectivityKeys.c).not.toBe(pourConnectivityKey)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const output = new CopperPourPipelineSolver(inputProblem).getOutput()
|
|
188
|
+
const copperPours: PcbCopperPourBRep[] = output.brep_shapes.map(
|
|
189
|
+
(brep_shape, i) => ({
|
|
190
|
+
type: "pcb_copper_pour",
|
|
191
|
+
shape: "brep",
|
|
192
|
+
pcb_copper_pour_id: `pcb_copper_pour_tsx_subcircuit_${i}`,
|
|
193
|
+
layer: "top",
|
|
194
|
+
source_net_id: parentGndSourceNet!.source_net_id,
|
|
195
|
+
brep_shape,
|
|
196
|
+
covered_with_solder_mask: true,
|
|
197
|
+
}),
|
|
198
|
+
)
|
|
199
|
+
const svg = convertCircuitJsonToPcbSvg([
|
|
200
|
+
...circuitJson.filter((element) => element.type !== "pcb_copper_pour"),
|
|
201
|
+
...copperPours,
|
|
202
|
+
] as any)
|
|
203
|
+
|
|
204
|
+
await expect(svg).toMatchSvgSnapshot(import.meta.path, snapshotName)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
test("tsx subcircuit connectivity 01 connects child net through parent-to-child trace", async () => {
|
|
208
|
+
await runSubcircuitConnectivityCase({
|
|
209
|
+
includeParentToChildTrace: true,
|
|
210
|
+
snapshotName: "tsx-subcircuit-connectivity01",
|
|
211
|
+
expectedSourceNetConnectivityKeyCount: 1,
|
|
212
|
+
expectChildGndConnectedToPour: true,
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test("tsx subcircuit connectivity 02 keeps child net separate without parent-to-child trace", async () => {
|
|
217
|
+
await runSubcircuitConnectivityCase({
|
|
218
|
+
includeParentToChildTrace: false,
|
|
219
|
+
snapshotName: "tsx-subcircuit-connectivity02",
|
|
220
|
+
expectedSourceNetConnectivityKeyCount: 2,
|
|
221
|
+
expectChildGndConnectedToPour: false,
|
|
222
|
+
})
|
|
223
|
+
})
|
|
@@ -8,7 +8,6 @@ import type {
|
|
|
8
8
|
PcbCopperPourBRep,
|
|
9
9
|
SourceNet,
|
|
10
10
|
} from "circuit-json"
|
|
11
|
-
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
|
|
12
11
|
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg"
|
|
13
12
|
import type { Point } from "@tscircuit/math-utils"
|
|
14
13
|
|
|
@@ -30,7 +29,6 @@ export const runSolverAndRenderToSvg = (
|
|
|
30
29
|
? pour_options
|
|
31
30
|
: [pour_options]
|
|
32
31
|
|
|
33
|
-
const connectivityMap = getFullConnectivityMapFromCircuitJson(circuitJson)
|
|
34
32
|
const allCopperPours: PcbCopperPourBRep[] = []
|
|
35
33
|
|
|
36
34
|
for (const options of pourOptionsArray) {
|
|
@@ -43,21 +41,15 @@ export const runSolverAndRenderToSvg = (
|
|
|
43
41
|
throw new Error(`Net with name "${options.net_name}" not found`)
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (!pour_connectivity_key && source_net.subcircuit_connectivity_map_key) {
|
|
51
|
-
pour_connectivity_key = source_net.subcircuit_connectivity_map_key
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!pour_connectivity_key) {
|
|
55
|
-
throw new Error(`Net "${options.net_name}" has no connectivity mapping`)
|
|
44
|
+
if (!source_net.subcircuit_connectivity_map_key) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Net "${options.net_name}" has no subcircuit_connectivity_map_key`,
|
|
47
|
+
)
|
|
56
48
|
}
|
|
57
49
|
|
|
58
50
|
const inputProblem = convertCircuitJsonToInputProblem(circuitJson, {
|
|
59
51
|
...options,
|
|
60
|
-
|
|
52
|
+
source_net_id: source_net.source_net_id,
|
|
61
53
|
})
|
|
62
54
|
|
|
63
55
|
const solver = new CopperPourPipelineSolver(inputProblem)
|