circuitjson-toolkit 1.0.3 → 1.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/AGENTS.md +5 -3
- package/README.md +21 -2
- package/docs/api.md +50 -4
- package/docs/model-format.md +21 -3
- package/package.json +3 -2
- package/spec/library-scope.md +4 -1
- package/src/core/CircuitJsonBomBuilder.mjs +143 -0
- package/src/core/CircuitJsonDocument.mjs +46 -13
- package/src/core/CircuitJsonElementValidator.mjs +990 -0
- package/src/core/CircuitJsonIndexer.mjs +773 -4
- package/src/core/CircuitJsonManufacturingBuilder.mjs +898 -0
- package/src/core/CircuitJsonManufacturingDownloadBuilder.mjs +196 -0
- package/src/core/CircuitJsonParser.mjs +22 -6
- package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +329 -0
- package/src/core/CircuitJsonPcbCopperGeometry.mjs +503 -0
- package/src/core/CircuitJsonPcbDrawingStyle.mjs +88 -0
- package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +172 -0
- package/src/core/CircuitJsonPcbNetMetadata.mjs +247 -0
- package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +70 -0
- package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +992 -0
- package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +872 -0
- package/src/core/CircuitJsonPcbPrimitiveFields.mjs +233 -0
- package/src/core/CircuitJsonPcbPrimitiveGeometry.mjs +142 -0
- package/src/core/CircuitJsonPcbPrimitiveGroups.mjs +305 -0
- package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +65 -0
- package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +895 -0
- package/src/core/CircuitJsonPcbTraceLengthModel.mjs +257 -0
- package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +683 -0
- package/src/core/CircuitJsonSourceMetadata.mjs +233 -0
- package/src/core/CircuitJsonSupportMatrixBuilder.mjs +481 -0
- package/src/core/CircuitJsonUnits.mjs +133 -8
- package/src/core/PcbBoundsSelectionModel.mjs +250 -0
- package/src/core/PcbCandidateSelectionModel.mjs +77 -0
- package/src/core/PcbDiagnosticFocusModel.mjs +423 -0
- package/src/core/PcbInteractionPrimitiveModel.mjs +560 -0
- package/src/core/SelectedPartCircuitJsonExportAdapter.mjs +335 -0
- package/src/core/spice/SpiceCompatibilityPreprocessor.mjs +139 -0
- package/src/core/spice/SpiceDirectiveParser.mjs +231 -0
- package/src/core/spice/SpiceFallbackSimulationEngine.mjs +168 -0
- package/src/core/spice/SpiceSimulationDiagnostics.mjs +234 -0
- package/src/core/spice/SpiceSimulationGraphBuilder.mjs +421 -0
- package/src/core/spice/SpiceSimulationGraphSummary.mjs +90 -0
- package/src/core/spice/SpiceSimulationService.mjs +92 -0
- package/src/core/spice/SpiceTimeSeriesNormalizer.mjs +132 -0
- package/src/index.mjs +8 -0
- package/src/renderers.mjs +29 -0
- package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +128 -0
- package/src/ui/CircuitJsonPcbSvgRenderer.mjs +964 -0
- package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +168 -0
- package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +138 -0
- package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +114 -0
- package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +130 -0
- package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +994 -0
- package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +439 -0
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
import { CircuitJsonIndexer } from './CircuitJsonIndexer.mjs'
|
|
2
|
+
import { CircuitJsonUnits } from './CircuitJsonUnits.mjs'
|
|
3
|
+
import { CircuitJsonPcbPrimitiveGeometry } from './CircuitJsonPcbPrimitiveGeometry.mjs'
|
|
4
|
+
import { CircuitJsonPcbPrimitiveOverlays } from './CircuitJsonPcbPrimitiveOverlays.mjs'
|
|
5
|
+
import { CircuitJsonPcbPrimitiveArtwork } from './CircuitJsonPcbPrimitiveArtwork.mjs'
|
|
6
|
+
import { CircuitJsonPcbPrimitiveGroups } from './CircuitJsonPcbPrimitiveGroups.mjs'
|
|
7
|
+
import { CircuitJsonPcbZonePrimitiveBuilder } from './CircuitJsonPcbZonePrimitiveBuilder.mjs'
|
|
8
|
+
import { CircuitJsonPcbHolePrimitiveModel } from './CircuitJsonPcbHolePrimitiveModel.mjs'
|
|
9
|
+
import { CircuitJsonPcbPadPrimitiveModel } from './CircuitJsonPcbPadPrimitiveModel.mjs'
|
|
10
|
+
import { CircuitJsonPcbPrimitiveFields } from './CircuitJsonPcbPrimitiveFields.mjs'
|
|
11
|
+
import { CircuitJsonPcbPrimitiveIndex } from './CircuitJsonPcbPrimitiveIndex.mjs'
|
|
12
|
+
import { CircuitJsonPcbNetMetadata } from './CircuitJsonPcbNetMetadata.mjs'
|
|
13
|
+
import { CircuitJsonPcbDrawingStyle } from './CircuitJsonPcbDrawingStyle.mjs'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Builds renderer-neutral PCB primitives from standards-native element arrays.
|
|
17
|
+
*/
|
|
18
|
+
export class CircuitJsonPcbPrimitiveBuilder {
|
|
19
|
+
/**
|
|
20
|
+
* Builds a normalized PCB primitive model.
|
|
21
|
+
* @param {object | object[]} documentModel Parsed document model.
|
|
22
|
+
* @returns {{ bounds: object, layers: object[], virtualLayers: object[], components: object[], nets: object[], primitives: object[], anchors: object[], diagnostics: object[], airwires: object[], traceLengths: object[], groups: object[], anchorOffsets: object[] }}
|
|
23
|
+
*/
|
|
24
|
+
static build(documentModel) {
|
|
25
|
+
const elements = CircuitJsonPcbPrimitiveBuilder.elements(documentModel)
|
|
26
|
+
const index = CircuitJsonPcbPrimitiveIndex.build(elements)
|
|
27
|
+
const boards = CircuitJsonPcbPrimitiveBuilder.#all(index, 'pcb_board')
|
|
28
|
+
const board = boards[0] || null
|
|
29
|
+
const components =
|
|
30
|
+
CircuitJsonPcbPrimitiveBuilder.#componentLookups(index)
|
|
31
|
+
const areaModel = CircuitJsonPcbZonePrimitiveBuilder.build(
|
|
32
|
+
index,
|
|
33
|
+
components.byPcbId
|
|
34
|
+
)
|
|
35
|
+
const primitiveRows = [
|
|
36
|
+
...CircuitJsonPcbPrimitiveBuilder.#boardPrimitives(boards),
|
|
37
|
+
...CircuitJsonPcbPrimitiveBuilder.#padPrimitives(
|
|
38
|
+
index,
|
|
39
|
+
components.byPcbId
|
|
40
|
+
),
|
|
41
|
+
...CircuitJsonPcbPrimitiveBuilder.#tracePrimitives(
|
|
42
|
+
index,
|
|
43
|
+
components.byPcbId
|
|
44
|
+
),
|
|
45
|
+
...CircuitJsonPcbPrimitiveBuilder.#viaPrimitives(
|
|
46
|
+
index,
|
|
47
|
+
components.byPcbId
|
|
48
|
+
),
|
|
49
|
+
...areaModel.primitives,
|
|
50
|
+
...CircuitJsonPcbPrimitiveBuilder.#silkscreenPrimitives(
|
|
51
|
+
index,
|
|
52
|
+
components.byPcbId
|
|
53
|
+
),
|
|
54
|
+
...CircuitJsonPcbPrimitiveArtwork.cutoutPrimitives(index),
|
|
55
|
+
...CircuitJsonPcbPrimitiveArtwork.build(index, components.byPcbId)
|
|
56
|
+
].filter(Boolean)
|
|
57
|
+
const groupModel = CircuitJsonPcbPrimitiveGroups.build(
|
|
58
|
+
index,
|
|
59
|
+
primitiveRows,
|
|
60
|
+
components.rows
|
|
61
|
+
)
|
|
62
|
+
const primitives = CircuitJsonPcbNetMetadata.decoratePrimitives(
|
|
63
|
+
groupModel.primitives,
|
|
64
|
+
index
|
|
65
|
+
)
|
|
66
|
+
const bounds =
|
|
67
|
+
CircuitJsonPcbPrimitiveFields.mergedBoardBounds(boards) ||
|
|
68
|
+
CircuitJsonPcbPrimitiveGeometry.mergedPrimitiveBounds(primitives) ||
|
|
69
|
+
CircuitJsonPcbPrimitiveGeometry.bounds(0, 0, 1, 1)
|
|
70
|
+
const overlays = CircuitJsonPcbPrimitiveOverlays.build(
|
|
71
|
+
index,
|
|
72
|
+
components.byPcbId,
|
|
73
|
+
primitives,
|
|
74
|
+
bounds,
|
|
75
|
+
groupModel,
|
|
76
|
+
[
|
|
77
|
+
...CircuitJsonPcbPrimitiveBuilder.#generatedDiagnostics(index),
|
|
78
|
+
...areaModel.diagnostics
|
|
79
|
+
]
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
bounds,
|
|
84
|
+
layers: CircuitJsonPcbPrimitiveFields.layers(board),
|
|
85
|
+
virtualLayers: overlays.virtualLayers,
|
|
86
|
+
components: components.rows,
|
|
87
|
+
nets: CircuitJsonPcbNetMetadata.nets(primitives, index),
|
|
88
|
+
primitives,
|
|
89
|
+
anchors: primitives.flatMap((primitive) =>
|
|
90
|
+
primitive.anchors.map((anchor) => ({ ...anchor, primitive }))
|
|
91
|
+
),
|
|
92
|
+
diagnostics: overlays.diagnostics,
|
|
93
|
+
airwires: overlays.airwires,
|
|
94
|
+
traceLengths: CircuitJsonPcbPrimitiveArtwork.traceLengths(
|
|
95
|
+
primitives,
|
|
96
|
+
index
|
|
97
|
+
),
|
|
98
|
+
groups: groupModel.groups,
|
|
99
|
+
anchorOffsets: groupModel.anchorOffsets
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns element rows from an array or wrapper object.
|
|
105
|
+
* @param {object | object[]} documentModel Parsed document model.
|
|
106
|
+
* @returns {object[]}
|
|
107
|
+
*/
|
|
108
|
+
static elements(documentModel) {
|
|
109
|
+
if (Array.isArray(documentModel)) return documentModel
|
|
110
|
+
if (Array.isArray(documentModel?.elements))
|
|
111
|
+
return documentModel.elements
|
|
112
|
+
if (Array.isArray(documentModel?.circuitJson))
|
|
113
|
+
return documentModel.circuitJson
|
|
114
|
+
return []
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Returns indexed element rows by type.
|
|
119
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
120
|
+
* @param {string} type Element type.
|
|
121
|
+
* @returns {object[]}
|
|
122
|
+
*/
|
|
123
|
+
static #all(index, type) {
|
|
124
|
+
return index.elementsByType.get(type) || []
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Returns generated diagnostics from the shared element index.
|
|
129
|
+
* @param {{ diagnostics?: object[] }} index Element index.
|
|
130
|
+
* @returns {object[]}
|
|
131
|
+
*/
|
|
132
|
+
static #generatedDiagnostics(index) {
|
|
133
|
+
return (
|
|
134
|
+
Array.isArray(index?.diagnostics) ? index.diagnostics : []
|
|
135
|
+
).filter((diagnostic) => diagnostic?.isGenerated === true)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Builds component rows and PCB component lookup maps.
|
|
140
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
141
|
+
* @returns {{ rows: object[], byPcbId: Map<string, object> }}
|
|
142
|
+
*/
|
|
143
|
+
static #componentLookups(index) {
|
|
144
|
+
const sourceRows = CircuitJsonPcbPrimitiveBuilder.#all(
|
|
145
|
+
index,
|
|
146
|
+
'source_component'
|
|
147
|
+
)
|
|
148
|
+
const sourceNames = new Map(
|
|
149
|
+
sourceRows.map((element) => [
|
|
150
|
+
String(element.source_component_id || '').trim(),
|
|
151
|
+
String(
|
|
152
|
+
element.name ||
|
|
153
|
+
element.reference ||
|
|
154
|
+
element.designator ||
|
|
155
|
+
element.source_component_id ||
|
|
156
|
+
''
|
|
157
|
+
).trim()
|
|
158
|
+
])
|
|
159
|
+
)
|
|
160
|
+
const sourceMetadata = new Map(
|
|
161
|
+
sourceRows.map((element) => [
|
|
162
|
+
String(element.source_component_id || '').trim(),
|
|
163
|
+
{
|
|
164
|
+
groupId: String(element.source_group_id || '').trim(),
|
|
165
|
+
subcircuitIds: CircuitJsonPcbPrimitiveFields.uniqueStrings([
|
|
166
|
+
element.subcircuit_id,
|
|
167
|
+
element.subcircuitId
|
|
168
|
+
])
|
|
169
|
+
}
|
|
170
|
+
])
|
|
171
|
+
)
|
|
172
|
+
const rows = []
|
|
173
|
+
const byPcbId = new Map()
|
|
174
|
+
|
|
175
|
+
for (const element of CircuitJsonPcbPrimitiveBuilder.#all(
|
|
176
|
+
index,
|
|
177
|
+
'pcb_component'
|
|
178
|
+
)) {
|
|
179
|
+
const sourceId = String(element.source_component_id || '').trim()
|
|
180
|
+
const pcbId = String(element.pcb_component_id || '').trim()
|
|
181
|
+
const center = CircuitJsonPcbPrimitiveFields.center(element) || {
|
|
182
|
+
x: 0,
|
|
183
|
+
y: 0
|
|
184
|
+
}
|
|
185
|
+
const componentKey = String(
|
|
186
|
+
sourceNames.get(sourceId) ||
|
|
187
|
+
element.name ||
|
|
188
|
+
element.reference ||
|
|
189
|
+
element.designator ||
|
|
190
|
+
pcbId ||
|
|
191
|
+
'Component ' + (rows.length + 1)
|
|
192
|
+
).trim()
|
|
193
|
+
const source = sourceMetadata.get(sourceId) || {}
|
|
194
|
+
const component = {
|
|
195
|
+
...element,
|
|
196
|
+
componentKey,
|
|
197
|
+
designator: componentKey,
|
|
198
|
+
key: componentKey,
|
|
199
|
+
pcbComponentId: pcbId,
|
|
200
|
+
sourceComponentId: sourceId,
|
|
201
|
+
sourceGroupId: String(
|
|
202
|
+
element.source_group_id || source.groupId || ''
|
|
203
|
+
).trim(),
|
|
204
|
+
x: center.x,
|
|
205
|
+
y: center.y,
|
|
206
|
+
layer: CircuitJsonPcbPrimitiveFields.layer(element.layer),
|
|
207
|
+
rotation: CircuitJsonUnits.angle(element.rotation, 0),
|
|
208
|
+
groupIds: CircuitJsonPcbPrimitiveFields.uniqueStrings([
|
|
209
|
+
element.pcb_group_id,
|
|
210
|
+
element.positioned_relative_to_pcb_group_id,
|
|
211
|
+
element.source_group_id,
|
|
212
|
+
source.groupId
|
|
213
|
+
]),
|
|
214
|
+
subcircuitIds: CircuitJsonPcbPrimitiveFields.uniqueStrings([
|
|
215
|
+
element.subcircuit_id,
|
|
216
|
+
element.subcircuitId,
|
|
217
|
+
...(source.subcircuitIds || [])
|
|
218
|
+
])
|
|
219
|
+
}
|
|
220
|
+
rows.push(component)
|
|
221
|
+
if (pcbId) byPcbId.set(pcbId, component)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { rows, byPcbId }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Builds board primitives.
|
|
229
|
+
* @param {object[]} boards Board elements.
|
|
230
|
+
* @returns {object[]}
|
|
231
|
+
*/
|
|
232
|
+
static #boardPrimitives(boards) {
|
|
233
|
+
return boards
|
|
234
|
+
.map((board) =>
|
|
235
|
+
CircuitJsonPcbPrimitiveBuilder.#boardPrimitive(board)
|
|
236
|
+
)
|
|
237
|
+
.filter(Boolean)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Builds a board primitive.
|
|
242
|
+
* @param {object | null} board Board element.
|
|
243
|
+
* @returns {object | null}
|
|
244
|
+
*/
|
|
245
|
+
static #boardPrimitive(board) {
|
|
246
|
+
const bounds = CircuitJsonPcbPrimitiveFields.boardBounds(board)
|
|
247
|
+
if (!bounds) return null
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
id: String(board?.pcb_board_id || 'board'),
|
|
251
|
+
kind: 'board',
|
|
252
|
+
layer: 'board',
|
|
253
|
+
side: '',
|
|
254
|
+
points: CircuitJsonPcbPrimitiveFields.points(board),
|
|
255
|
+
bounds,
|
|
256
|
+
anchors: CircuitJsonPcbPrimitiveGeometry.cornerAnchors(bounds),
|
|
257
|
+
source: board || {}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Builds SMT pad primitives.
|
|
263
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
264
|
+
* @param {Map<string, object>} componentsByPcbId Component lookup.
|
|
265
|
+
* @returns {object[]}
|
|
266
|
+
*/
|
|
267
|
+
static #padPrimitives(index, componentsByPcbId) {
|
|
268
|
+
return CircuitJsonPcbPrimitiveBuilder.#all(index, 'pcb_smtpad')
|
|
269
|
+
.map((element) =>
|
|
270
|
+
CircuitJsonPcbPrimitiveBuilder.#padPrimitive(
|
|
271
|
+
element,
|
|
272
|
+
componentsByPcbId
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
.filter(Boolean)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Builds one SMT pad primitive.
|
|
280
|
+
* @param {object} element Pad element.
|
|
281
|
+
* @param {Map<string, object>} componentsByPcbId Component lookup.
|
|
282
|
+
* @returns {object | null}
|
|
283
|
+
*/
|
|
284
|
+
static #padPrimitive(element, componentsByPcbId) {
|
|
285
|
+
const shape = CircuitJsonPcbPadPrimitiveModel.shape(element)
|
|
286
|
+
const points =
|
|
287
|
+
shape === 'polygon'
|
|
288
|
+
? CircuitJsonPcbPrimitiveFields.points(element)
|
|
289
|
+
: []
|
|
290
|
+
const center =
|
|
291
|
+
CircuitJsonPcbPrimitiveFields.center(element) ||
|
|
292
|
+
CircuitJsonPcbPrimitiveFields.pointsCenter(points)
|
|
293
|
+
if (!center) return null
|
|
294
|
+
|
|
295
|
+
const explicitRadius =
|
|
296
|
+
CircuitJsonPcbPadPrimitiveModel.explicitRadius(element)
|
|
297
|
+
const diameter =
|
|
298
|
+
CircuitJsonUnits.optionalLength(element.diameter) ??
|
|
299
|
+
(explicitRadius === null ? null : explicitRadius * 2)
|
|
300
|
+
const width = CircuitJsonUnits.optionalLength(element.width) ?? diameter
|
|
301
|
+
const height =
|
|
302
|
+
CircuitJsonUnits.optionalLength(element.height) ?? diameter ?? width
|
|
303
|
+
if (!points.length && (width === null || height === null)) return null
|
|
304
|
+
const radius = CircuitJsonPcbPadPrimitiveModel.radius(
|
|
305
|
+
element,
|
|
306
|
+
shape,
|
|
307
|
+
width ?? 0,
|
|
308
|
+
height ?? 0
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
const component = componentsByPcbId.get(
|
|
312
|
+
String(element.pcb_component_id || '').trim()
|
|
313
|
+
)
|
|
314
|
+
const layer = CircuitJsonPcbPrimitiveFields.layer(element.layer)
|
|
315
|
+
const bounds = points.length
|
|
316
|
+
? CircuitJsonPcbPrimitiveGeometry.pointsBounds(points)
|
|
317
|
+
: CircuitJsonPcbPrimitiveGeometry.centerBounds(
|
|
318
|
+
center,
|
|
319
|
+
width,
|
|
320
|
+
height
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
return CircuitJsonPcbPrimitiveBuilder.#primitive({
|
|
324
|
+
id: String(element.pcb_smtpad_id || ''),
|
|
325
|
+
kind: 'pad',
|
|
326
|
+
shape,
|
|
327
|
+
x: center.x,
|
|
328
|
+
y: center.y,
|
|
329
|
+
width: width ?? bounds.width,
|
|
330
|
+
height: height ?? bounds.height,
|
|
331
|
+
radius,
|
|
332
|
+
rotation: CircuitJsonUnits.angle(
|
|
333
|
+
element.ccw_rotation ?? element.rotation,
|
|
334
|
+
0
|
|
335
|
+
),
|
|
336
|
+
points,
|
|
337
|
+
bounds,
|
|
338
|
+
layer,
|
|
339
|
+
component,
|
|
340
|
+
netName: CircuitJsonPcbPrimitiveFields.netName(element, null),
|
|
341
|
+
anchors: [
|
|
342
|
+
{ point: center },
|
|
343
|
+
...(points.length
|
|
344
|
+
? points.map((point) => ({ point }))
|
|
345
|
+
: CircuitJsonPcbPrimitiveGeometry.cornerAnchors(bounds))
|
|
346
|
+
],
|
|
347
|
+
source: element
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Builds trace segment and route-via primitives.
|
|
353
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
354
|
+
* @param {Map<string, object>} componentsByPcbId Component lookup.
|
|
355
|
+
* @returns {object[]}
|
|
356
|
+
*/
|
|
357
|
+
static #tracePrimitives(index, componentsByPcbId) {
|
|
358
|
+
return CircuitJsonPcbPrimitiveBuilder.#all(index, 'pcb_trace').flatMap(
|
|
359
|
+
(trace) =>
|
|
360
|
+
CircuitJsonPcbPrimitiveBuilder.#tracePrimitiveRows(
|
|
361
|
+
trace,
|
|
362
|
+
componentsByPcbId
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Builds rows for one routed trace.
|
|
369
|
+
* @param {object} trace Trace element.
|
|
370
|
+
* @param {Map<string, object>} componentsByPcbId Component lookup.
|
|
371
|
+
* @returns {object[]}
|
|
372
|
+
*/
|
|
373
|
+
static #tracePrimitiveRows(trace, componentsByPcbId) {
|
|
374
|
+
const route = Array.isArray(trace.route) ? trace.route : []
|
|
375
|
+
const component = componentsByPcbId.get(
|
|
376
|
+
String(trace.pcb_component_id || '').trim()
|
|
377
|
+
)
|
|
378
|
+
const rows = []
|
|
379
|
+
let previous = null
|
|
380
|
+
|
|
381
|
+
for (const entry of route) {
|
|
382
|
+
if (entry?.route_type === 'through_pad') {
|
|
383
|
+
rows.push(
|
|
384
|
+
CircuitJsonPcbPrimitiveBuilder.#throughPadSegment(
|
|
385
|
+
trace,
|
|
386
|
+
entry,
|
|
387
|
+
component,
|
|
388
|
+
rows.length
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
previous = null
|
|
392
|
+
continue
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const current = CircuitJsonPcbPrimitiveFields.center(entry)
|
|
396
|
+
if (!current) continue
|
|
397
|
+
if (entry?.route_type === 'via') {
|
|
398
|
+
rows.push(
|
|
399
|
+
CircuitJsonPcbPrimitiveBuilder.#routeViaPrimitive(
|
|
400
|
+
trace,
|
|
401
|
+
entry,
|
|
402
|
+
current,
|
|
403
|
+
rows.length
|
|
404
|
+
)
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
if (
|
|
408
|
+
previous &&
|
|
409
|
+
CircuitJsonPcbPrimitiveBuilder.#sameSegmentLayer(
|
|
410
|
+
previous,
|
|
411
|
+
entry
|
|
412
|
+
)
|
|
413
|
+
) {
|
|
414
|
+
rows.push(
|
|
415
|
+
CircuitJsonPcbPrimitiveBuilder.#segmentPrimitive(
|
|
416
|
+
trace,
|
|
417
|
+
previous,
|
|
418
|
+
current,
|
|
419
|
+
entry,
|
|
420
|
+
component,
|
|
421
|
+
rows.length
|
|
422
|
+
)
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
previous = { ...entry, x: current.x, y: current.y }
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return rows.filter(Boolean)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Returns true when two route points can form a visible planar segment.
|
|
433
|
+
* @param {object} previous Previous route entry.
|
|
434
|
+
* @param {object} current Current route entry.
|
|
435
|
+
* @returns {boolean}
|
|
436
|
+
*/
|
|
437
|
+
static #sameSegmentLayer(previous, current) {
|
|
438
|
+
if (previous?.route_type === 'via' || current?.route_type === 'via') {
|
|
439
|
+
return true
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const previousLayer = CircuitJsonPcbPrimitiveFields.layer(
|
|
443
|
+
previous?.layer
|
|
444
|
+
)
|
|
445
|
+
const currentLayer = CircuitJsonPcbPrimitiveFields.layer(current?.layer)
|
|
446
|
+
return !previousLayer || !currentLayer || previousLayer === currentLayer
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Builds one routed wire segment.
|
|
451
|
+
* @param {object} trace Trace element.
|
|
452
|
+
* @param {object} previous Previous route point.
|
|
453
|
+
* @param {{ x: number, y: number }} current Current point.
|
|
454
|
+
* @param {object} entry Current route entry.
|
|
455
|
+
* @param {object | undefined} component Component row.
|
|
456
|
+
* @param {number} index Segment index.
|
|
457
|
+
* @returns {object}
|
|
458
|
+
*/
|
|
459
|
+
static #segmentPrimitive(
|
|
460
|
+
trace,
|
|
461
|
+
previous,
|
|
462
|
+
current,
|
|
463
|
+
entry,
|
|
464
|
+
component,
|
|
465
|
+
index
|
|
466
|
+
) {
|
|
467
|
+
const width = CircuitJsonUnits.length(
|
|
468
|
+
entry.width ?? previous.width ?? trace.width,
|
|
469
|
+
0.15
|
|
470
|
+
)
|
|
471
|
+
const layer = CircuitJsonPcbPrimitiveFields.layer(
|
|
472
|
+
entry.layer || previous.layer || trace.layer
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
return CircuitJsonPcbPrimitiveBuilder.#primitive({
|
|
476
|
+
id: String(trace.pcb_trace_id || '') + ':segment:' + index,
|
|
477
|
+
kind: 'track',
|
|
478
|
+
x1: previous.x,
|
|
479
|
+
y1: previous.y,
|
|
480
|
+
x2: current.x,
|
|
481
|
+
y2: current.y,
|
|
482
|
+
width,
|
|
483
|
+
bounds: CircuitJsonPcbPrimitiveGeometry.segmentBounds(
|
|
484
|
+
previous,
|
|
485
|
+
current,
|
|
486
|
+
width
|
|
487
|
+
),
|
|
488
|
+
layer,
|
|
489
|
+
component,
|
|
490
|
+
netName: CircuitJsonPcbPrimitiveFields.netName(trace, null),
|
|
491
|
+
anchors: [{ point: previous }, { point: current }],
|
|
492
|
+
source: trace,
|
|
493
|
+
sourceTraceId: String(trace.source_trace_id || '')
|
|
494
|
+
})
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Builds one through-pad trace segment.
|
|
499
|
+
* @param {object} trace Trace element.
|
|
500
|
+
* @param {object} entry Route entry.
|
|
501
|
+
* @param {object | undefined} component Component row.
|
|
502
|
+
* @param {number} index Segment index.
|
|
503
|
+
* @returns {object | null}
|
|
504
|
+
*/
|
|
505
|
+
static #throughPadSegment(trace, entry, component, index) {
|
|
506
|
+
const start = CircuitJsonPcbPrimitiveFields.point(entry.start)
|
|
507
|
+
const end = CircuitJsonPcbPrimitiveFields.point(entry.end)
|
|
508
|
+
if (!start || !end) return null
|
|
509
|
+
return CircuitJsonPcbPrimitiveBuilder.#segmentPrimitive(
|
|
510
|
+
trace,
|
|
511
|
+
start,
|
|
512
|
+
end,
|
|
513
|
+
{
|
|
514
|
+
width: entry.width,
|
|
515
|
+
layer: entry.start_layer || entry.end_layer
|
|
516
|
+
},
|
|
517
|
+
component,
|
|
518
|
+
index
|
|
519
|
+
)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Builds one route via primitive.
|
|
524
|
+
* @param {object} trace Trace element.
|
|
525
|
+
* @param {object} entry Route via entry.
|
|
526
|
+
* @param {{ x: number, y: number }} center Via center.
|
|
527
|
+
* @param {number} index Via index.
|
|
528
|
+
* @returns {object}
|
|
529
|
+
*/
|
|
530
|
+
static #routeViaPrimitive(trace, entry, center, index) {
|
|
531
|
+
const geometry = CircuitJsonPcbHolePrimitiveModel.build(entry, center)
|
|
532
|
+
return {
|
|
533
|
+
...CircuitJsonPcbPrimitiveBuilder.#viaRow(
|
|
534
|
+
{
|
|
535
|
+
...entry,
|
|
536
|
+
pcb_via_id:
|
|
537
|
+
String(trace.pcb_trace_id || '') + ':via:' + index
|
|
538
|
+
},
|
|
539
|
+
center,
|
|
540
|
+
geometry,
|
|
541
|
+
CircuitJsonPcbPrimitiveFields.netName(trace, null)
|
|
542
|
+
),
|
|
543
|
+
source: trace,
|
|
544
|
+
sourceRoute: entry
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Builds standalone via, plated-hole, and hole primitives.
|
|
550
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
551
|
+
* @param {Map<string, object>} componentsByPcbId Component lookup.
|
|
552
|
+
* @returns {object[]}
|
|
553
|
+
*/
|
|
554
|
+
static #viaPrimitives(index, componentsByPcbId) {
|
|
555
|
+
return ['pcb_via', 'pcb_plated_hole', 'pcb_hole']
|
|
556
|
+
.flatMap((type) => CircuitJsonPcbPrimitiveBuilder.#all(index, type))
|
|
557
|
+
.map((element) => {
|
|
558
|
+
const center = CircuitJsonPcbPrimitiveFields.center(element)
|
|
559
|
+
if (!center) return null
|
|
560
|
+
const geometry = CircuitJsonPcbHolePrimitiveModel.build(
|
|
561
|
+
element,
|
|
562
|
+
center
|
|
563
|
+
)
|
|
564
|
+
const component = componentsByPcbId.get(
|
|
565
|
+
String(element.pcb_component_id || '').trim()
|
|
566
|
+
)
|
|
567
|
+
return CircuitJsonPcbPrimitiveBuilder.#primitive({
|
|
568
|
+
...CircuitJsonPcbPrimitiveBuilder.#viaRow(
|
|
569
|
+
element,
|
|
570
|
+
center,
|
|
571
|
+
geometry,
|
|
572
|
+
CircuitJsonPcbPrimitiveFields.netName(element, null)
|
|
573
|
+
),
|
|
574
|
+
component
|
|
575
|
+
})
|
|
576
|
+
})
|
|
577
|
+
.filter(Boolean)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Builds one via-like primitive row.
|
|
582
|
+
* @param {object} element Via-like element.
|
|
583
|
+
* @param {{ x: number, y: number }} center Center point.
|
|
584
|
+
* @param {object} geometry Normalized drilled geometry.
|
|
585
|
+
* @param {string} netName Net name.
|
|
586
|
+
* @returns {object}
|
|
587
|
+
*/
|
|
588
|
+
static #viaRow(element, center, geometry, netName) {
|
|
589
|
+
const layer = CircuitJsonPcbPrimitiveFields.layer(
|
|
590
|
+
element.layer || element.from_layer || element.to_layer
|
|
591
|
+
)
|
|
592
|
+
return {
|
|
593
|
+
id: String(
|
|
594
|
+
element.pcb_via_id ||
|
|
595
|
+
element.pcb_plated_hole_id ||
|
|
596
|
+
element.pcb_hole_id ||
|
|
597
|
+
''
|
|
598
|
+
),
|
|
599
|
+
kind: 'via',
|
|
600
|
+
x: center.x,
|
|
601
|
+
y: center.y,
|
|
602
|
+
...geometry,
|
|
603
|
+
layer,
|
|
604
|
+
side: '',
|
|
605
|
+
netName,
|
|
606
|
+
anchors: [{ point: center }],
|
|
607
|
+
source: element
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Builds silkscreen and text primitives.
|
|
613
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
614
|
+
* @param {Map<string, object>} componentsByPcbId Component lookup.
|
|
615
|
+
* @returns {object[]}
|
|
616
|
+
*/
|
|
617
|
+
static #silkscreenPrimitives(index, componentsByPcbId) {
|
|
618
|
+
return [
|
|
619
|
+
...CircuitJsonPcbPrimitiveBuilder.#textPrimitives(
|
|
620
|
+
index,
|
|
621
|
+
componentsByPcbId
|
|
622
|
+
),
|
|
623
|
+
...CircuitJsonPcbPrimitiveBuilder.#linePrimitives(
|
|
624
|
+
index,
|
|
625
|
+
componentsByPcbId
|
|
626
|
+
)
|
|
627
|
+
]
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Builds text primitives.
|
|
632
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
633
|
+
* @param {Map<string, object>} componentsByPcbId Component lookup.
|
|
634
|
+
* @returns {object[]}
|
|
635
|
+
*/
|
|
636
|
+
static #textPrimitives(index, componentsByPcbId) {
|
|
637
|
+
return [
|
|
638
|
+
'pcb_silkscreen_text',
|
|
639
|
+
'pcb_text',
|
|
640
|
+
'pcb_copper_text',
|
|
641
|
+
'pcb_fabrication_note_text'
|
|
642
|
+
]
|
|
643
|
+
.flatMap((type) => CircuitJsonPcbPrimitiveBuilder.#all(index, type))
|
|
644
|
+
.flatMap((element) => {
|
|
645
|
+
const center =
|
|
646
|
+
CircuitJsonPcbPrimitiveFields.center(element) ||
|
|
647
|
+
CircuitJsonPcbPrimitiveFields.point(element.anchor_position)
|
|
648
|
+
if (!center) return []
|
|
649
|
+
const size = CircuitJsonUnits.length(
|
|
650
|
+
element.font_size ?? element.fontSize ?? element.height,
|
|
651
|
+
1
|
|
652
|
+
)
|
|
653
|
+
const component = componentsByPcbId.get(
|
|
654
|
+
String(element.pcb_component_id || '').trim()
|
|
655
|
+
)
|
|
656
|
+
const bounds = CircuitJsonPcbPrimitiveGeometry.centerBounds(
|
|
657
|
+
center,
|
|
658
|
+
Math.max(
|
|
659
|
+
String(element.text || '').length * size * 0.6,
|
|
660
|
+
size
|
|
661
|
+
),
|
|
662
|
+
size
|
|
663
|
+
)
|
|
664
|
+
const primitive = CircuitJsonPcbPrimitiveBuilder.#primitive({
|
|
665
|
+
id: CircuitJsonIndexer.getElementId(element),
|
|
666
|
+
kind: CircuitJsonPcbPrimitiveBuilder.#textKind(element),
|
|
667
|
+
text: String(element.text || ''),
|
|
668
|
+
x: center.x,
|
|
669
|
+
y: center.y,
|
|
670
|
+
fontSize: size,
|
|
671
|
+
anchorAlignment: String(
|
|
672
|
+
element.anchor_alignment ||
|
|
673
|
+
element.anchorAlignment ||
|
|
674
|
+
''
|
|
675
|
+
).trim(),
|
|
676
|
+
isKnockout:
|
|
677
|
+
element.is_knockout === true ||
|
|
678
|
+
element.isKnockout === true,
|
|
679
|
+
rotation: CircuitJsonUnits.angle(element.ccw_rotation, 0),
|
|
680
|
+
bounds,
|
|
681
|
+
layer: CircuitJsonPcbPrimitiveFields.layer(element.layer),
|
|
682
|
+
component,
|
|
683
|
+
netName: CircuitJsonPcbPrimitiveFields.netName(
|
|
684
|
+
element,
|
|
685
|
+
null
|
|
686
|
+
),
|
|
687
|
+
anchors: [{ point: center }],
|
|
688
|
+
...CircuitJsonPcbDrawingStyle.fromElement(element),
|
|
689
|
+
source: element
|
|
690
|
+
})
|
|
691
|
+
if (element.type !== 'pcb_silkscreen_text') return [primitive]
|
|
692
|
+
|
|
693
|
+
return [
|
|
694
|
+
primitive,
|
|
695
|
+
{
|
|
696
|
+
...primitive,
|
|
697
|
+
id: primitive.id + ':silkscreen',
|
|
698
|
+
kind: 'silkscreen',
|
|
699
|
+
footprintId: primitive.componentKey
|
|
700
|
+
? 'footprint:' +
|
|
701
|
+
primitive.componentKey +
|
|
702
|
+
':silkscreen'
|
|
703
|
+
: ''
|
|
704
|
+
}
|
|
705
|
+
]
|
|
706
|
+
})
|
|
707
|
+
.filter(Boolean)
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Builds line primitives.
|
|
712
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
713
|
+
* @param {Map<string, object>} componentsByPcbId Component lookup.
|
|
714
|
+
* @returns {object[]}
|
|
715
|
+
*/
|
|
716
|
+
static #linePrimitives(index, componentsByPcbId) {
|
|
717
|
+
return [
|
|
718
|
+
'pcb_silkscreen_line',
|
|
719
|
+
'pcb_silkscreen_path',
|
|
720
|
+
'pcb_fabrication_note_line',
|
|
721
|
+
'pcb_fabrication_note_path'
|
|
722
|
+
]
|
|
723
|
+
.flatMap((type) => CircuitJsonPcbPrimitiveBuilder.#all(index, type))
|
|
724
|
+
.flatMap((element) =>
|
|
725
|
+
CircuitJsonPcbPrimitiveBuilder.#linePrimitiveRows(
|
|
726
|
+
element,
|
|
727
|
+
componentsByPcbId
|
|
728
|
+
)
|
|
729
|
+
)
|
|
730
|
+
.filter(Boolean)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Builds renderable segment rows for one documentation path or line.
|
|
735
|
+
* @param {object} element Path or line element.
|
|
736
|
+
* @param {Map<string, object>} componentsByPcbId Component lookup.
|
|
737
|
+
* @returns {object[]}
|
|
738
|
+
*/
|
|
739
|
+
static #linePrimitiveRows(element, componentsByPcbId) {
|
|
740
|
+
const points = CircuitJsonPcbPrimitiveFields.linePoints(element)
|
|
741
|
+
const rows = []
|
|
742
|
+
|
|
743
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
744
|
+
rows.push(
|
|
745
|
+
CircuitJsonPcbPrimitiveBuilder.#linePrimitive(
|
|
746
|
+
element,
|
|
747
|
+
points[index - 1],
|
|
748
|
+
points[index],
|
|
749
|
+
index - 1,
|
|
750
|
+
componentsByPcbId
|
|
751
|
+
)
|
|
752
|
+
)
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return rows.filter(Boolean)
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Builds one silkscreen or fabrication segment primitive.
|
|
760
|
+
* @param {object} element Source element.
|
|
761
|
+
* @param {{ x: number, y: number }} start Segment start.
|
|
762
|
+
* @param {{ x: number, y: number }} end Segment end.
|
|
763
|
+
* @param {number} index Segment index.
|
|
764
|
+
* @param {Map<string, object>} componentsByPcbId Component lookup.
|
|
765
|
+
* @returns {object | null}
|
|
766
|
+
*/
|
|
767
|
+
static #linePrimitive(element, start, end, index, componentsByPcbId) {
|
|
768
|
+
const width = CircuitJsonUnits.length(
|
|
769
|
+
element.width ??
|
|
770
|
+
element.stroke_width ??
|
|
771
|
+
element.strokeWidth ??
|
|
772
|
+
element.line_width,
|
|
773
|
+
0.12
|
|
774
|
+
)
|
|
775
|
+
const component = componentsByPcbId.get(
|
|
776
|
+
String(element.pcb_component_id || '').trim()
|
|
777
|
+
)
|
|
778
|
+
const baseId = CircuitJsonIndexer.getElementId(element)
|
|
779
|
+
|
|
780
|
+
return CircuitJsonPcbPrimitiveBuilder.#primitive({
|
|
781
|
+
id: baseId + ':' + index,
|
|
782
|
+
kind: CircuitJsonPcbPrimitiveBuilder.#lineKind(element),
|
|
783
|
+
x1: start.x,
|
|
784
|
+
y1: start.y,
|
|
785
|
+
x2: end.x,
|
|
786
|
+
y2: end.y,
|
|
787
|
+
width,
|
|
788
|
+
bounds: CircuitJsonPcbPrimitiveGeometry.segmentBounds(
|
|
789
|
+
start,
|
|
790
|
+
end,
|
|
791
|
+
width
|
|
792
|
+
),
|
|
793
|
+
layer: CircuitJsonPcbPrimitiveBuilder.#lineLayer(element),
|
|
794
|
+
component,
|
|
795
|
+
anchors: [{ point: start }, { point: end }],
|
|
796
|
+
...CircuitJsonPcbDrawingStyle.fromElement(element),
|
|
797
|
+
source: element
|
|
798
|
+
})
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Resolves the primitive kind for a documentation line.
|
|
803
|
+
* @param {object} element Source element.
|
|
804
|
+
* @returns {'fabrication' | 'silkscreen'}
|
|
805
|
+
*/
|
|
806
|
+
static #lineKind(element) {
|
|
807
|
+
return String(element.type || '').includes('fabrication')
|
|
808
|
+
? 'fabrication'
|
|
809
|
+
: 'silkscreen'
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Resolves the display layer for a documentation line.
|
|
814
|
+
* @param {object} element Source element.
|
|
815
|
+
* @returns {string}
|
|
816
|
+
*/
|
|
817
|
+
static #lineLayer(element) {
|
|
818
|
+
if (element.layer) {
|
|
819
|
+
return CircuitJsonPcbPrimitiveFields.layer(element.layer)
|
|
820
|
+
}
|
|
821
|
+
return CircuitJsonPcbPrimitiveBuilder.#lineKind(element) ===
|
|
822
|
+
'fabrication'
|
|
823
|
+
? 'top_fabrication'
|
|
824
|
+
: 'top_silkscreen'
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Resolves a normalized text primitive kind.
|
|
829
|
+
* @param {object} element Text element.
|
|
830
|
+
* @returns {string}
|
|
831
|
+
*/
|
|
832
|
+
static #textKind(element) {
|
|
833
|
+
if (element.type === 'pcb_copper_text') return 'copper-text'
|
|
834
|
+
if (element.type === 'pcb_fabrication_note_text') return 'fabrication'
|
|
835
|
+
return 'silkscreen_text'
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Adds common primitive metadata.
|
|
840
|
+
* @param {object} primitive Primitive data.
|
|
841
|
+
* @returns {object}
|
|
842
|
+
*/
|
|
843
|
+
static #primitive(primitive) {
|
|
844
|
+
const component = primitive.component || {}
|
|
845
|
+
const layer = CircuitJsonPcbPrimitiveFields.layer(primitive.layer)
|
|
846
|
+
const source = primitive.source || {}
|
|
847
|
+
return {
|
|
848
|
+
...primitive,
|
|
849
|
+
layer,
|
|
850
|
+
side: primitive.side ?? CircuitJsonPcbPrimitiveFields.side(layer),
|
|
851
|
+
componentKey: String(component.componentKey || ''),
|
|
852
|
+
componentId: String(component.pcbComponentId || ''),
|
|
853
|
+
footprintId: component.componentKey
|
|
854
|
+
? 'footprint:' + component.componentKey + ':' + primitive.kind
|
|
855
|
+
: '',
|
|
856
|
+
netName: String(primitive.netName || '').trim(),
|
|
857
|
+
groupIds: CircuitJsonPcbPrimitiveFields.uniqueStrings([
|
|
858
|
+
source.pcb_group_id,
|
|
859
|
+
source.source_group_id,
|
|
860
|
+
component.pcb_group_id,
|
|
861
|
+
component.positioned_relative_to_pcb_group_id,
|
|
862
|
+
component.sourceGroupId
|
|
863
|
+
]),
|
|
864
|
+
subcircuitIds: CircuitJsonPcbPrimitiveFields.uniqueStrings([
|
|
865
|
+
source.subcircuit_id,
|
|
866
|
+
source.subcircuitId,
|
|
867
|
+
...(component.subcircuitIds || [])
|
|
868
|
+
]),
|
|
869
|
+
sourceComponentId: String(component.sourceComponentId || '')
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|