@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,237 @@
|
|
|
1
|
+
import type { AnyCircuitElement } from "circuit-json"
|
|
2
|
+
import type { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
|
|
3
|
+
import { getElementId } from "@tscircuit/circuit-json-util"
|
|
4
|
+
import { getElementSubcircuitConnectivityKey } from "./getElementSubcircuitConnectivityKey"
|
|
5
|
+
|
|
6
|
+
const getElementSubcircuitId = (
|
|
7
|
+
element: AnyCircuitElement,
|
|
8
|
+
): string | undefined => {
|
|
9
|
+
const subcircuitId = (element as any).subcircuit_id
|
|
10
|
+
return typeof subcircuitId === "string" && subcircuitId.length > 0
|
|
11
|
+
? subcircuitId
|
|
12
|
+
: undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const getScopedSubcircuitConnectivityKey = (
|
|
16
|
+
subcircuitId: string | undefined,
|
|
17
|
+
subcircuitConnectivityMapKey: string,
|
|
18
|
+
): string =>
|
|
19
|
+
subcircuitId
|
|
20
|
+
? `subcircuit:${subcircuitId}:connectivity:${subcircuitConnectivityMapKey}`
|
|
21
|
+
: subcircuitConnectivityMapKey
|
|
22
|
+
|
|
23
|
+
const getDescendantSubcircuitIds = (
|
|
24
|
+
circuitJson: AnyCircuitElement[],
|
|
25
|
+
rootSubcircuitId: string | undefined,
|
|
26
|
+
): Set<string> | undefined => {
|
|
27
|
+
if (!rootSubcircuitId) return undefined
|
|
28
|
+
|
|
29
|
+
const sourceGroupIdToSubcircuitId: Record<string, string> = {}
|
|
30
|
+
for (const element of circuitJson) {
|
|
31
|
+
if (element.type !== "source_group") continue
|
|
32
|
+
|
|
33
|
+
const sourceGroupId = (element as any).source_group_id
|
|
34
|
+
const subcircuitId = getElementSubcircuitId(element)
|
|
35
|
+
if (typeof sourceGroupId === "string" && subcircuitId) {
|
|
36
|
+
sourceGroupIdToSubcircuitId[sourceGroupId] = subcircuitId
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const descendantSubcircuitIds = new Set([rootSubcircuitId])
|
|
41
|
+
let changed = true
|
|
42
|
+
while (changed) {
|
|
43
|
+
changed = false
|
|
44
|
+
for (const element of circuitJson) {
|
|
45
|
+
if (element.type !== "source_group") continue
|
|
46
|
+
|
|
47
|
+
const subcircuitId = getElementSubcircuitId(element)
|
|
48
|
+
if (!subcircuitId || descendantSubcircuitIds.has(subcircuitId)) continue
|
|
49
|
+
|
|
50
|
+
const parentSubcircuitId = (element as any).parent_subcircuit_id
|
|
51
|
+
const parentSourceGroupId = (element as any).parent_source_group_id
|
|
52
|
+
const parentSourceGroupSubcircuitId =
|
|
53
|
+
typeof parentSourceGroupId === "string"
|
|
54
|
+
? sourceGroupIdToSubcircuitId[parentSourceGroupId]
|
|
55
|
+
: undefined
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
(typeof parentSubcircuitId === "string" &&
|
|
59
|
+
descendantSubcircuitIds.has(parentSubcircuitId)) ||
|
|
60
|
+
(parentSourceGroupSubcircuitId &&
|
|
61
|
+
descendantSubcircuitIds.has(parentSourceGroupSubcircuitId))
|
|
62
|
+
) {
|
|
63
|
+
descendantSubcircuitIds.add(subcircuitId)
|
|
64
|
+
changed = true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return descendantSubcircuitIds
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const buildSubcircuitConnectivityLookup = (
|
|
73
|
+
circuitJson: AnyCircuitElement[],
|
|
74
|
+
globalConnectivityMap: ReturnType<
|
|
75
|
+
typeof getFullConnectivityMapFromCircuitJson
|
|
76
|
+
>,
|
|
77
|
+
rootSubcircuitId?: string,
|
|
78
|
+
) => {
|
|
79
|
+
const descendantSubcircuitIds = getDescendantSubcircuitIds(
|
|
80
|
+
circuitJson,
|
|
81
|
+
rootSubcircuitId,
|
|
82
|
+
)
|
|
83
|
+
const idToSubcircuitConnectivityKey: Record<string, string> = {}
|
|
84
|
+
const idToSubcircuitId: Record<string, string | undefined> = {}
|
|
85
|
+
const scopedKeyToIds: Record<string, string[]> = {}
|
|
86
|
+
const localSubcircuitConnectivityKeys = new Set<string>()
|
|
87
|
+
|
|
88
|
+
for (const element of circuitJson) {
|
|
89
|
+
const id = getElementId(element)
|
|
90
|
+
const key = getElementSubcircuitConnectivityKey(element)
|
|
91
|
+
const subcircuitId = getElementSubcircuitId(element)
|
|
92
|
+
if (id) {
|
|
93
|
+
idToSubcircuitId[id] = subcircuitId
|
|
94
|
+
}
|
|
95
|
+
if (
|
|
96
|
+
descendantSubcircuitIds &&
|
|
97
|
+
(!subcircuitId || !descendantSubcircuitIds.has(subcircuitId))
|
|
98
|
+
) {
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (id && key) {
|
|
103
|
+
const scopedKey = getScopedSubcircuitConnectivityKey(subcircuitId, key)
|
|
104
|
+
idToSubcircuitConnectivityKey[id] = scopedKey
|
|
105
|
+
localSubcircuitConnectivityKeys.add(key)
|
|
106
|
+
scopedKeyToIds[scopedKey] ??= []
|
|
107
|
+
scopedKeyToIds[scopedKey].push(id)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const generatedNetIdToSubcircuitConnectivityKey: Record<string, string> = {}
|
|
112
|
+
for (const [generatedNetId, connectedIds] of Object.entries(
|
|
113
|
+
globalConnectivityMap.netMap,
|
|
114
|
+
)) {
|
|
115
|
+
const connectedSubcircuitKeys = new Set(
|
|
116
|
+
connectedIds
|
|
117
|
+
.map((id) => idToSubcircuitConnectivityKey[id])
|
|
118
|
+
.filter((key): key is string => Boolean(key)),
|
|
119
|
+
)
|
|
120
|
+
const subcircuitKey = Array.from(connectedSubcircuitKeys).sort()[0]
|
|
121
|
+
if (subcircuitKey) {
|
|
122
|
+
generatedNetIdToSubcircuitConnectivityKey[generatedNetId] = subcircuitKey
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const subcircuitConnectivityMap: Record<string, string> = {}
|
|
127
|
+
for (const [scopedKey, ids] of Object.entries(scopedKeyToIds)) {
|
|
128
|
+
const resolvedKeys = new Set<string>()
|
|
129
|
+
for (const id of ids) {
|
|
130
|
+
const generatedNetId = globalConnectivityMap.getNetConnectedToId(id)
|
|
131
|
+
const resolvedKey = generatedNetId
|
|
132
|
+
? generatedNetIdToSubcircuitConnectivityKey[generatedNetId]
|
|
133
|
+
: undefined
|
|
134
|
+
resolvedKeys.add(resolvedKey ?? scopedKey)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (resolvedKeys.size > 1) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`subcircuit_connectivity_map_key "${scopedKey}" maps to multiple global connectivity keys: ${Array.from(resolvedKeys).join(", ")}`,
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const resolvedKey = resolvedKeys.values().next().value
|
|
144
|
+
if (resolvedKey) {
|
|
145
|
+
subcircuitConnectivityMap[scopedKey] = resolvedKey
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
knownSubcircuitConnectivityKeys: new Set([
|
|
151
|
+
...Object.keys(scopedKeyToIds),
|
|
152
|
+
...localSubcircuitConnectivityKeys,
|
|
153
|
+
]),
|
|
154
|
+
descendantSubcircuitIds,
|
|
155
|
+
getScopedSubcircuitConnectivityKey,
|
|
156
|
+
getElementSubcircuitId,
|
|
157
|
+
resolveSubcircuitConnectivityKey(
|
|
158
|
+
subcircuitConnectivityMapKey: string,
|
|
159
|
+
subcircuitId?: string,
|
|
160
|
+
): string {
|
|
161
|
+
const matchingScopedKeys = Object.keys(scopedKeyToIds).filter(
|
|
162
|
+
(scopedKey) =>
|
|
163
|
+
scopedKey ===
|
|
164
|
+
getScopedSubcircuitConnectivityKey(
|
|
165
|
+
subcircuitId,
|
|
166
|
+
subcircuitConnectivityMapKey,
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if (
|
|
171
|
+
subcircuitId &&
|
|
172
|
+
matchingScopedKeys.length === 0 &&
|
|
173
|
+
descendantSubcircuitIds
|
|
174
|
+
) {
|
|
175
|
+
matchingScopedKeys.push(
|
|
176
|
+
...Object.keys(scopedKeyToIds).filter((scopedKey) =>
|
|
177
|
+
scopedKey.endsWith(`:connectivity:${subcircuitConnectivityMapKey}`),
|
|
178
|
+
),
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!subcircuitId) {
|
|
183
|
+
matchingScopedKeys.push(
|
|
184
|
+
...Object.keys(scopedKeyToIds).filter((scopedKey) =>
|
|
185
|
+
scopedKey.endsWith(`:connectivity:${subcircuitConnectivityMapKey}`),
|
|
186
|
+
),
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const uniqueMatchingScopedKeys = Array.from(new Set(matchingScopedKeys))
|
|
191
|
+
if (uniqueMatchingScopedKeys.length === 0) {
|
|
192
|
+
if (subcircuitId && descendantSubcircuitIds) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`No subcircuit_connectivity_map_key "${subcircuitConnectivityMapKey}" found in subcircuit "${subcircuitId}" or its child subcircuits.`,
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return getScopedSubcircuitConnectivityKey(
|
|
199
|
+
subcircuitId,
|
|
200
|
+
subcircuitConnectivityMapKey,
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
if (uniqueMatchingScopedKeys.length > 1) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`subcircuit_connectivity_map_key "${subcircuitConnectivityMapKey}" exists in multiple subcircuits. Pass subcircuit_id to disambiguate.`,
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
subcircuitConnectivityMap[uniqueMatchingScopedKeys[0]!] ??
|
|
211
|
+
uniqueMatchingScopedKeys[0]!
|
|
212
|
+
)
|
|
213
|
+
},
|
|
214
|
+
getSubcircuitConnectivityKeyForId(id: string): string | undefined {
|
|
215
|
+
if (descendantSubcircuitIds) {
|
|
216
|
+
const subcircuitId = idToSubcircuitId[id]
|
|
217
|
+
if (!subcircuitId || !descendantSubcircuitIds.has(subcircuitId)) {
|
|
218
|
+
return undefined
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const directKey = idToSubcircuitConnectivityKey[id]
|
|
223
|
+
const generatedNetId = globalConnectivityMap.getNetConnectedToId(id)
|
|
224
|
+
if (!generatedNetId) {
|
|
225
|
+
return directKey
|
|
226
|
+
? (subcircuitConnectivityMap[directKey] ?? directKey)
|
|
227
|
+
: undefined
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
generatedNetIdToSubcircuitConnectivityKey[generatedNetId] ??
|
|
232
|
+
(directKey ? subcircuitConnectivityMap[directKey] : undefined) ??
|
|
233
|
+
directKey
|
|
234
|
+
)
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AnyCircuitElement,
|
|
3
|
-
LayerRef,
|
|
4
3
|
PcbBoard,
|
|
5
4
|
PcbHole,
|
|
6
5
|
PcbPlatedHole,
|
|
7
|
-
PcbPort,
|
|
8
6
|
PcbSmtPad,
|
|
9
7
|
PcbTrace,
|
|
10
8
|
PcbVia,
|
|
11
9
|
Point,
|
|
12
|
-
SourceNet,
|
|
13
|
-
SourcePort,
|
|
14
|
-
SourceTrace,
|
|
15
10
|
} from "circuit-json"
|
|
16
11
|
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
|
|
17
12
|
import type {
|
|
@@ -23,18 +18,13 @@ import type {
|
|
|
23
18
|
InputRectPad,
|
|
24
19
|
InputTracePad,
|
|
25
20
|
} from "lib/types"
|
|
21
|
+
import type { ConvertCircuitJsonToInputProblemOptions } from "./ConvertCircuitJsonToInputProblemOptions"
|
|
22
|
+
import { buildSubcircuitConnectivityLookup } from "./buildSubcircuitConnectivityLookup"
|
|
23
|
+
import { resolvePourConnectivityKey } from "./resolvePourConnectivityKey"
|
|
26
24
|
|
|
27
25
|
export const convertCircuitJsonToInputProblem = (
|
|
28
26
|
circuitJson: AnyCircuitElement[],
|
|
29
|
-
options:
|
|
30
|
-
layer: LayerRef
|
|
31
|
-
pour_connectivity_key: string
|
|
32
|
-
pad_margin: number
|
|
33
|
-
trace_margin: number
|
|
34
|
-
board_edge_margin?: number
|
|
35
|
-
cutout_margin?: number
|
|
36
|
-
outline?: Point[]
|
|
37
|
-
},
|
|
27
|
+
options: ConvertCircuitJsonToInputProblemOptions,
|
|
38
28
|
): InputProblem => {
|
|
39
29
|
const pcb_board = circuitJson.find((e) => e.type === "pcb_board") as
|
|
40
30
|
| PcbBoard
|
|
@@ -42,7 +32,19 @@ export const convertCircuitJsonToInputProblem = (
|
|
|
42
32
|
|
|
43
33
|
if (!pcb_board) throw new Error("No pcb_board found in circuit json")
|
|
44
34
|
|
|
45
|
-
const
|
|
35
|
+
const globalConnectivityMap =
|
|
36
|
+
getFullConnectivityMapFromCircuitJson(circuitJson)
|
|
37
|
+
const subcircuitConnectivityMap = buildSubcircuitConnectivityLookup(
|
|
38
|
+
circuitJson,
|
|
39
|
+
globalConnectivityMap,
|
|
40
|
+
options.subcircuit_id,
|
|
41
|
+
)
|
|
42
|
+
const pourConnectivityKey = resolvePourConnectivityKey(
|
|
43
|
+
circuitJson,
|
|
44
|
+
options,
|
|
45
|
+
subcircuitConnectivityMap,
|
|
46
|
+
)
|
|
47
|
+
const { getSubcircuitConnectivityKeyForId } = subcircuitConnectivityMap
|
|
46
48
|
|
|
47
49
|
const pads: InputPad[] = []
|
|
48
50
|
|
|
@@ -52,9 +54,7 @@ export const convertCircuitJsonToInputProblem = (
|
|
|
52
54
|
if (smtpad.layer !== options.layer) continue
|
|
53
55
|
|
|
54
56
|
let connectivityKey: string | undefined
|
|
55
|
-
connectivityKey =
|
|
56
|
-
smtpad.pcb_smtpad_id,
|
|
57
|
-
)
|
|
57
|
+
connectivityKey = getSubcircuitConnectivityKeyForId(smtpad.pcb_smtpad_id)
|
|
58
58
|
if (!connectivityKey) {
|
|
59
59
|
connectivityKey = `unconnected:${smtpad.pcb_smtpad_id}`
|
|
60
60
|
}
|
|
@@ -101,7 +101,7 @@ export const convertCircuitJsonToInputProblem = (
|
|
|
101
101
|
const platedHole = elm as PcbPlatedHole
|
|
102
102
|
if (!platedHole.layers.includes(options.layer)) continue
|
|
103
103
|
|
|
104
|
-
let connectivityKey =
|
|
104
|
+
let connectivityKey = getSubcircuitConnectivityKeyForId(
|
|
105
105
|
platedHole.pcb_plated_hole_id,
|
|
106
106
|
)
|
|
107
107
|
if (!connectivityKey) {
|
|
@@ -189,7 +189,7 @@ export const convertCircuitJsonToInputProblem = (
|
|
|
189
189
|
if (!via.layers.includes(options.layer)) continue
|
|
190
190
|
|
|
191
191
|
const connectivityKey: string =
|
|
192
|
-
|
|
192
|
+
getSubcircuitConnectivityKeyForId(via.pcb_via_id) ??
|
|
193
193
|
`unconnected-via:${via.pcb_via_id}`
|
|
194
194
|
|
|
195
195
|
pads.push({
|
|
@@ -203,7 +203,7 @@ export const convertCircuitJsonToInputProblem = (
|
|
|
203
203
|
} as InputCircularPad)
|
|
204
204
|
} else if (elm.type === "pcb_trace") {
|
|
205
205
|
const trace = elm as PcbTrace
|
|
206
|
-
const connectivityKey =
|
|
206
|
+
const connectivityKey = getSubcircuitConnectivityKeyForId(
|
|
207
207
|
trace.pcb_trace_id,
|
|
208
208
|
)
|
|
209
209
|
if (!connectivityKey) continue
|
|
@@ -271,7 +271,7 @@ export const convertCircuitJsonToInputProblem = (
|
|
|
271
271
|
layer: options.layer,
|
|
272
272
|
bounds,
|
|
273
273
|
outline,
|
|
274
|
-
connectivityKey:
|
|
274
|
+
connectivityKey: pourConnectivityKey,
|
|
275
275
|
padMargin: options.pad_margin,
|
|
276
276
|
traceMargin: options.trace_margin,
|
|
277
277
|
board_edge_margin: options.board_edge_margin ?? 0,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AnyCircuitElement } from "circuit-json"
|
|
2
|
+
|
|
3
|
+
export const getElementSubcircuitConnectivityKey = (
|
|
4
|
+
element: AnyCircuitElement,
|
|
5
|
+
): string | undefined => {
|
|
6
|
+
const key = (element as any).subcircuit_connectivity_map_key
|
|
7
|
+
return typeof key === "string" && key.length > 0 ? key : undefined
|
|
8
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { AnyCircuitElement, SourceNet } from "circuit-json"
|
|
2
|
+
import type { buildSubcircuitConnectivityLookup } from "./buildSubcircuitConnectivityLookup"
|
|
3
|
+
import type { ConvertCircuitJsonToInputProblemOptions } from "./ConvertCircuitJsonToInputProblemOptions"
|
|
4
|
+
|
|
5
|
+
type SubcircuitConnectivityLookup = ReturnType<
|
|
6
|
+
typeof buildSubcircuitConnectivityLookup
|
|
7
|
+
>
|
|
8
|
+
|
|
9
|
+
export const resolvePourConnectivityKey = (
|
|
10
|
+
circuitJson: AnyCircuitElement[],
|
|
11
|
+
options: ConvertCircuitJsonToInputProblemOptions,
|
|
12
|
+
subcircuitConnectivityMap: SubcircuitConnectivityLookup,
|
|
13
|
+
): string => {
|
|
14
|
+
if (options.subcircuit_connectivity_map_key) {
|
|
15
|
+
return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
|
|
16
|
+
options.subcircuit_connectivity_map_key,
|
|
17
|
+
options.subcircuit_id,
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (options.source_net_id) {
|
|
22
|
+
const sourceNet = circuitJson.find(
|
|
23
|
+
(element): element is SourceNet =>
|
|
24
|
+
element.type === "source_net" &&
|
|
25
|
+
element.source_net_id === options.source_net_id,
|
|
26
|
+
)
|
|
27
|
+
if (!sourceNet) {
|
|
28
|
+
throw new Error(`No source_net found with id "${options.source_net_id}"`)
|
|
29
|
+
}
|
|
30
|
+
if (!sourceNet.subcircuit_connectivity_map_key) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`source_net "${options.source_net_id}" has no subcircuit_connectivity_map_key`,
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
|
|
36
|
+
sourceNet.subcircuit_connectivity_map_key,
|
|
37
|
+
sourceNet.subcircuit_id ?? options.subcircuit_id,
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (options.source_net_name) {
|
|
42
|
+
const sourceNet = circuitJson.find(
|
|
43
|
+
(element): element is SourceNet =>
|
|
44
|
+
element.type === "source_net" &&
|
|
45
|
+
element.name === options.source_net_name &&
|
|
46
|
+
(!options.subcircuit_id ||
|
|
47
|
+
Boolean(
|
|
48
|
+
subcircuitConnectivityMap.descendantSubcircuitIds?.has(
|
|
49
|
+
element.subcircuit_id ?? "",
|
|
50
|
+
),
|
|
51
|
+
)),
|
|
52
|
+
)
|
|
53
|
+
if (!sourceNet) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`No source_net found with name "${options.source_net_name}"`,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
if (!sourceNet.subcircuit_connectivity_map_key) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`source_net "${options.source_net_name}" has no subcircuit_connectivity_map_key`,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
|
|
64
|
+
sourceNet.subcircuit_connectivity_map_key,
|
|
65
|
+
sourceNet.subcircuit_id ?? options.subcircuit_id,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (options.pour_connectivity_key) {
|
|
70
|
+
if (
|
|
71
|
+
!subcircuitConnectivityMap.knownSubcircuitConnectivityKeys.has(
|
|
72
|
+
options.pour_connectivity_key,
|
|
73
|
+
)
|
|
74
|
+
) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`pour_connectivity_key must be a subcircuit_connectivity_map_key. Use subcircuit_connectivity_map_key, source_net_id, or source_net_name instead of a generated connectivity-map id.`,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
|
|
80
|
+
options.pour_connectivity_key,
|
|
81
|
+
options.subcircuit_id,
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
throw new Error(
|
|
86
|
+
"Copper pour requires source_net_id, source_net_name, or subcircuit_connectivity_map_key",
|
|
87
|
+
)
|
|
88
|
+
}
|
package/lib/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from "./solvers/CopperPourPipelineSolver"
|
|
2
2
|
export { initializeManifoldGeometry } from "./solvers/copper-pour/manifold-runtime"
|
|
3
|
+
export type { ConvertCircuitJsonToInputProblemOptions } from "./circuit-json/ConvertCircuitJsonToInputProblemOptions"
|
|
3
4
|
export * from "./circuit-json/convert-circuit-json-to-input-problem"
|
|
4
5
|
export * from "./types"
|
package/package.json
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/copper-pour-solver",
|
|
3
3
|
"main": "dist/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.35",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"format": "biome format . --write",
|
|
7
7
|
"format:check": "biome format .",
|
|
8
|
+
"start": "cosmos",
|
|
9
|
+
"cosmos": "react-cosmos",
|
|
10
|
+
"build:site": "cosmos-export",
|
|
8
11
|
"build": "tsup-node lib/index.ts --dts --format esm"
|
|
9
12
|
},
|
|
10
13
|
"type": "module",
|
|
11
14
|
"devDependencies": {
|
|
12
15
|
"@biomejs/biome": "^2.2.4",
|
|
13
16
|
"@flatten-js/core": "^1.6.2",
|
|
14
|
-
"@tscircuit/
|
|
17
|
+
"@tscircuit/circuit-json-util": "^0.0.77",
|
|
18
|
+
"@tscircuit/core": "^0.0.988",
|
|
19
|
+
"@tscircuit/math-utils": "^0.0.29",
|
|
15
20
|
"@tscircuit/solver-utils": "^0.0.14",
|
|
16
|
-
"circuit-json-to-connectivity-map": "^0.0.23",
|
|
17
21
|
"@types/bun": "latest",
|
|
18
22
|
"bun-match-svg": "^0.0.13",
|
|
19
23
|
"circuit-json": "^0.0.432",
|
|
24
|
+
"circuit-json-to-connectivity-map": "^0.0.23",
|
|
20
25
|
"circuit-to-svg": "^0.0.350",
|
|
21
26
|
"react-cosmos": "^7.0.0",
|
|
22
27
|
"react-cosmos-plugin-vite": "^7.0.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
|
+
}
|