altium-toolkit 0.1.1 → 0.1.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/README.md +24 -6
- package/docs/api.md +42 -4
- package/docs/model-format.md +95 -5
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +553 -0
- package/docs/testing.md +7 -2
- package/package.json +6 -2
- package/spec/library-scope.md +7 -1
- package/src/core/altium/AltiumParser.mjs +22 -325
- package/src/core/altium/NormalizedModelSchema.mjs +28 -0
- package/src/core/altium/PcbArcPrimitiveParser.mjs +87 -0
- package/src/core/altium/PcbBinaryPrimitiveParser.mjs +43 -370
- package/src/core/altium/PcbBoardRegionSemanticsParser.mjs +477 -0
- package/src/core/altium/PcbComponentAnnotationNormalizer.mjs +290 -0
- package/src/core/altium/PcbComponentBodyPlacementNormalizer.mjs +52 -0
- package/src/core/altium/PcbComponentPrimitiveIndexer.mjs +109 -0
- package/src/core/altium/PcbEmbeddedFontExtractor.mjs +484 -0
- package/src/core/altium/PcbFillPrimitiveParser.mjs +84 -0
- package/src/core/altium/PcbFontMetricsParser.mjs +308 -0
- package/src/core/altium/PcbGeometryFlipper.mjs +244 -0
- package/src/core/altium/PcbLayerIdCodec.mjs +136 -0
- package/src/core/altium/PcbLibModelParser.mjs +202 -0
- package/src/core/altium/PcbLibStreamExtractor.mjs +968 -0
- package/src/core/altium/PcbModelParser.mjs +618 -66
- package/src/core/altium/PcbOutlineRecovery.mjs +4 -112
- package/src/core/altium/PcbPadPrimitiveParser.mjs +347 -0
- package/src/core/altium/PcbPadShapeCodec.mjs +158 -0
- package/src/core/altium/PcbPadStackParser.mjs +903 -0
- package/src/core/altium/PcbPrimitiveOwnershipIndexParser.mjs +60 -0
- package/src/core/altium/PcbPrimitiveParameterParser.mjs +212 -0
- package/src/core/altium/PcbPrimitiveRecordSlicer.mjs +243 -0
- package/src/core/altium/PcbRawRecordRegistry.mjs +831 -0
- package/src/core/altium/PcbRegionPrimitiveParser.mjs +317 -0
- package/src/core/altium/PcbRuleParser.mjs +587 -0
- package/src/core/altium/PcbStreamExtractor.mjs +127 -4
- package/src/core/altium/PcbTextPrimitiveParser.mjs +537 -0
- package/src/core/altium/PcbTrackPrimitiveParser.mjs +87 -0
- package/src/core/altium/PcbViaPrimitiveParser.mjs +88 -0
- package/src/core/altium/PcbViaStackParser.mjs +548 -0
- package/src/core/altium/PcbWideStringTableParser.mjs +108 -0
- package/src/core/altium/PrjPcbModelParser.mjs +797 -0
- package/src/core/altium/SchematicComponentTextResolver.mjs +355 -0
- package/src/parser.mjs +13 -0
- package/src/renderers.mjs +5 -0
- package/src/styles/altium-renderers.css +11 -6
- package/src/ui/PcbCopperPrimitiveSplitter.mjs +113 -0
- package/src/ui/PcbEdgeFacingGlyphNormalizer.mjs +6 -5
- package/src/ui/PcbEmbeddedFontFaceRenderer.mjs +126 -0
- package/src/ui/PcbFootprintPrimitiveSelector.mjs +27 -6
- package/src/ui/PcbRegionPrimitiveRenderer.mjs +243 -0
- package/src/ui/PcbSideResolvedRenderModel.mjs +336 -0
- package/src/ui/PcbSvgRenderer.mjs +101 -109
- package/src/ui/PcbTextPrimitiveRenderer.mjs +252 -0
- package/src/ui/SchematicSheetChromeRenderer.mjs +2 -93
- package/src/ui/SchematicSheetZoneRenderer.mjs +104 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Applies component annotations recovered from PCB sidecar streams.
|
|
7
|
+
*/
|
|
8
|
+
export class PcbComponentAnnotationNormalizer {
|
|
9
|
+
/**
|
|
10
|
+
* Enriches component records with Texts6 designators and parameters.
|
|
11
|
+
* @param {{ componentIndex: number, designator: string, uniqueId?: string, description?: string, parameters?: Record<string, string> }[]} components
|
|
12
|
+
* @param {{ text?: string, ownerIndex?: number | null, componentIndex?: number | null, role?: string, isDesignator?: boolean }[]} texts
|
|
13
|
+
* @param {{ byPrimitiveId?: Record<string, Record<string, string>>, groups?: { primitiveId: string, parameters: Record<string, string> }[] } | undefined} primitiveParameters
|
|
14
|
+
* @returns {object[]}
|
|
15
|
+
*/
|
|
16
|
+
static enrichComponents(components, texts, primitiveParameters) {
|
|
17
|
+
return PcbComponentAnnotationNormalizer.#applyPrimitiveParameters(
|
|
18
|
+
PcbComponentAnnotationNormalizer.#applyTextDesignators(
|
|
19
|
+
components,
|
|
20
|
+
texts
|
|
21
|
+
),
|
|
22
|
+
PcbComponentAnnotationNormalizer.#primitiveParameterLookup(
|
|
23
|
+
primitiveParameters
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Marks decoded PCB text primitives as visible or hidden based on linked
|
|
30
|
+
* component display flags.
|
|
31
|
+
* @param {{ text: string, ownerIndex?: number | null, componentIndex?: number | null, kind?: number, visibilityFlags?: number, role?: string, isDesignator?: boolean }[]} texts
|
|
32
|
+
* @param {{ componentIndex: number, designator: string, nameOn: boolean, commentOn: boolean }[]} components
|
|
33
|
+
* @returns {object[]}
|
|
34
|
+
*/
|
|
35
|
+
static normalizeTexts(texts, components) {
|
|
36
|
+
return (texts || []).map((text) => ({
|
|
37
|
+
...text,
|
|
38
|
+
visible: PcbComponentAnnotationNormalizer.#isVisibleText(
|
|
39
|
+
text,
|
|
40
|
+
components
|
|
41
|
+
)
|
|
42
|
+
}))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Applies Texts6-owned designator strings to their native component.
|
|
47
|
+
* @param {{ componentIndex: number, designator: string }[]} components
|
|
48
|
+
* @param {{ text?: string, ownerIndex?: number | null, componentIndex?: number | null, role?: string, isDesignator?: boolean }[]} texts
|
|
49
|
+
* @returns {object[]}
|
|
50
|
+
*/
|
|
51
|
+
static #applyTextDesignators(components, texts) {
|
|
52
|
+
const designatorsByComponentIndex =
|
|
53
|
+
PcbComponentAnnotationNormalizer.#textDesignatorLookup(texts)
|
|
54
|
+
|
|
55
|
+
return (components || []).map((component) => {
|
|
56
|
+
const displayDesignator = designatorsByComponentIndex.get(
|
|
57
|
+
Number(component.componentIndex)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
!displayDesignator ||
|
|
62
|
+
PcbComponentAnnotationNormalizer.#normalizeText(
|
|
63
|
+
displayDesignator
|
|
64
|
+
) ===
|
|
65
|
+
PcbComponentAnnotationNormalizer.#normalizeText(
|
|
66
|
+
component.designator
|
|
67
|
+
)
|
|
68
|
+
) {
|
|
69
|
+
return component
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
...component,
|
|
74
|
+
baseDesignator: component.designator,
|
|
75
|
+
designator: displayDesignator,
|
|
76
|
+
displayDesignator,
|
|
77
|
+
designatorSource: 'Texts6/Data'
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Builds a component-indexed lookup from explicit Texts6 designators.
|
|
84
|
+
* @param {{ text?: string, ownerIndex?: number | null, componentIndex?: number | null, role?: string, isDesignator?: boolean }[]} texts
|
|
85
|
+
* @returns {Map<number, string>}
|
|
86
|
+
*/
|
|
87
|
+
static #textDesignatorLookup(texts) {
|
|
88
|
+
const designatorsByComponentIndex = new Map()
|
|
89
|
+
|
|
90
|
+
for (const text of texts || []) {
|
|
91
|
+
const componentIndex =
|
|
92
|
+
PcbComponentAnnotationNormalizer.#textComponentIndex(text)
|
|
93
|
+
if (
|
|
94
|
+
!Number.isInteger(componentIndex) ||
|
|
95
|
+
!PcbComponentAnnotationNormalizer.#isDesignatorTextPrimitive(
|
|
96
|
+
text
|
|
97
|
+
) ||
|
|
98
|
+
!text.text
|
|
99
|
+
) {
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
designatorsByComponentIndex.set(componentIndex, String(text.text))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return designatorsByComponentIndex
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolves the native component index from a decoded text primitive.
|
|
111
|
+
* @param {{ ownerIndex?: number | null, componentIndex?: number | null }} text
|
|
112
|
+
* @returns {number | null}
|
|
113
|
+
*/
|
|
114
|
+
static #textComponentIndex(text) {
|
|
115
|
+
const componentIndex = Number(text?.componentIndex)
|
|
116
|
+
if (Number.isInteger(componentIndex)) {
|
|
117
|
+
return componentIndex
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const ownerIndex = Number(text?.ownerIndex)
|
|
121
|
+
return Number.isInteger(ownerIndex) ? ownerIndex : null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Returns true when one text primitive explicitly represents a designator.
|
|
126
|
+
* @param {{ role?: string, isDesignator?: boolean }} text
|
|
127
|
+
* @returns {boolean}
|
|
128
|
+
*/
|
|
129
|
+
static #isDesignatorTextPrimitive(text) {
|
|
130
|
+
return text?.isDesignator === true || text?.role === 'designator'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Applies PrimitiveParameters/Data values to components by unique ID.
|
|
135
|
+
* @param {{ uniqueId?: string, description?: string, parameters?: Record<string, string> }[]} components
|
|
136
|
+
* @param {Map<string, Record<string, string>>} parametersByPrimitiveId
|
|
137
|
+
* @returns {object[]}
|
|
138
|
+
*/
|
|
139
|
+
static #applyPrimitiveParameters(components, parametersByPrimitiveId) {
|
|
140
|
+
return (components || []).map((component) => {
|
|
141
|
+
const primitiveId = String(component.uniqueId || '')
|
|
142
|
+
const parameters = primitiveId
|
|
143
|
+
? parametersByPrimitiveId.get(primitiveId)
|
|
144
|
+
: null
|
|
145
|
+
|
|
146
|
+
if (!parameters) {
|
|
147
|
+
return component
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const mergedParameters = {
|
|
151
|
+
...(component.parameters || {}),
|
|
152
|
+
...parameters
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
...component,
|
|
157
|
+
description:
|
|
158
|
+
component.description ||
|
|
159
|
+
PcbComponentAnnotationNormalizer.#firstParameterValue(
|
|
160
|
+
mergedParameters,
|
|
161
|
+
['Description', 'Comment', 'Value']
|
|
162
|
+
),
|
|
163
|
+
parameters: mergedParameters,
|
|
164
|
+
parameterSource: 'PrimitiveParameters/Data'
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Builds a primitive-parameter lookup from supported extraction shapes.
|
|
171
|
+
* @param {{ byPrimitiveId?: Record<string, Record<string, string>>, groups?: { primitiveId: string, parameters: Record<string, string> }[] } | undefined} primitiveParameters
|
|
172
|
+
* @returns {Map<string, Record<string, string>>}
|
|
173
|
+
*/
|
|
174
|
+
static #primitiveParameterLookup(primitiveParameters) {
|
|
175
|
+
const lookup = new Map()
|
|
176
|
+
|
|
177
|
+
for (const [primitiveId, parameters] of Object.entries(
|
|
178
|
+
primitiveParameters?.byPrimitiveId || {}
|
|
179
|
+
)) {
|
|
180
|
+
lookup.set(String(primitiveId), { ...(parameters || {}) })
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (const group of primitiveParameters?.groups || []) {
|
|
184
|
+
if (group?.primitiveId && !lookup.has(String(group.primitiveId))) {
|
|
185
|
+
lookup.set(String(group.primitiveId), {
|
|
186
|
+
...(group.parameters || {})
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return lookup
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Returns the first non-empty parameter value using case-insensitive names.
|
|
196
|
+
* @param {Record<string, string>} parameters
|
|
197
|
+
* @param {string[]} names
|
|
198
|
+
* @returns {string}
|
|
199
|
+
*/
|
|
200
|
+
static #firstParameterValue(parameters, names) {
|
|
201
|
+
const normalizedParameters = new Map(
|
|
202
|
+
Object.entries(parameters || {}).map(([name, value]) => [
|
|
203
|
+
name.toLowerCase(),
|
|
204
|
+
String(value || '')
|
|
205
|
+
])
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
for (const name of names) {
|
|
209
|
+
const value = normalizedParameters.get(name.toLowerCase())
|
|
210
|
+
if (value) {
|
|
211
|
+
return value
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return ''
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Returns true when one PCB text primitive should render in board view.
|
|
220
|
+
* @param {{ text: string, ownerIndex?: number | null, componentIndex?: number | null, kind?: number, visibilityFlags?: number, role?: string, isDesignator?: boolean }} text
|
|
221
|
+
* @param {{ componentIndex: number, designator: string, nameOn: boolean, commentOn: boolean }[]} components
|
|
222
|
+
* @returns {boolean}
|
|
223
|
+
*/
|
|
224
|
+
static #isVisibleText(text, components) {
|
|
225
|
+
const componentIndex =
|
|
226
|
+
PcbComponentAnnotationNormalizer.#textComponentIndex(text)
|
|
227
|
+
|
|
228
|
+
if (!Number.isInteger(componentIndex)) {
|
|
229
|
+
return true
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const component =
|
|
233
|
+
PcbComponentAnnotationNormalizer.#componentByNativeIndex(
|
|
234
|
+
components,
|
|
235
|
+
componentIndex
|
|
236
|
+
)
|
|
237
|
+
if (!component) {
|
|
238
|
+
return (Number(text?.visibilityFlags || 0) & 1) === 0
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (PcbComponentAnnotationNormalizer.#isDesignatorTextPrimitive(text)) {
|
|
242
|
+
return component.nameOn
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (
|
|
246
|
+
PcbComponentAnnotationNormalizer.#normalizeText(text.text) ===
|
|
247
|
+
PcbComponentAnnotationNormalizer.#normalizeText(
|
|
248
|
+
component.designator
|
|
249
|
+
)
|
|
250
|
+
) {
|
|
251
|
+
return component.nameOn
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (Number(text?.kind) === 1) {
|
|
255
|
+
return component.commentOn
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if ((Number(text?.visibilityFlags || 0) & 1) !== 0) {
|
|
259
|
+
return component.nameOn
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return true
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Finds one normalized component by native component table index.
|
|
267
|
+
* @param {{ componentIndex: number }[]} components
|
|
268
|
+
* @param {number} componentIndex
|
|
269
|
+
* @returns {object | null}
|
|
270
|
+
*/
|
|
271
|
+
static #componentByNativeIndex(components, componentIndex) {
|
|
272
|
+
return (
|
|
273
|
+
(components || []).find(
|
|
274
|
+
(component) =>
|
|
275
|
+
Number(component?.componentIndex) === componentIndex
|
|
276
|
+
) || null
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Normalizes text for display-flag comparisons.
|
|
282
|
+
* @param {unknown} text
|
|
283
|
+
* @returns {string}
|
|
284
|
+
*/
|
|
285
|
+
static #normalizeText(text) {
|
|
286
|
+
return String(text || '')
|
|
287
|
+
.trim()
|
|
288
|
+
.toUpperCase()
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalizes embedded component-body placements into viewer coordinates.
|
|
7
|
+
*/
|
|
8
|
+
export class PcbComponentBodyPlacementNormalizer {
|
|
9
|
+
/**
|
|
10
|
+
* Flips embedded component-body placements into the viewer coordinate
|
|
11
|
+
* system.
|
|
12
|
+
* @param {{ sourceStream: string, layer: string, identifier: string, modelId: string, checksum: number | null, embedded: boolean, name: string, positionMil: { x: number, y: number }, rotationDeg: number, modelRotationDeg: { x: number, y: number, z: number }, dzMil: number, overallHeightMil: number | null, standoffHeightMil: number | null }[]} componentBodies
|
|
13
|
+
* @param {{ minY: number, heightMil: number }} boardOutline
|
|
14
|
+
* @returns {{ sourceStream: string, layer: string, identifier: string, modelId: string, checksum: number | null, embedded: boolean, name: string, positionMil: { x: number, y: number }, rotationDeg: number, modelRotationDeg: { x: number, y: number, z: number }, dzMil: number, overallHeightMil: number | null, standoffHeightMil: number | null }[]}
|
|
15
|
+
*/
|
|
16
|
+
static normalizeComponentBodies(componentBodies, boardOutline) {
|
|
17
|
+
const maxY =
|
|
18
|
+
Number(boardOutline?.minY || 0) +
|
|
19
|
+
Number(boardOutline?.heightMil || 0)
|
|
20
|
+
const mirrorY = (value) =>
|
|
21
|
+
Number(boardOutline?.minY || 0) + maxY - Number(value || 0)
|
|
22
|
+
|
|
23
|
+
return componentBodies.map((componentBody) => ({
|
|
24
|
+
...componentBody,
|
|
25
|
+
positionMil: {
|
|
26
|
+
x: Number(componentBody.positionMil?.x || 0),
|
|
27
|
+
y: mirrorY(componentBody.positionMil?.y || 0)
|
|
28
|
+
},
|
|
29
|
+
rotationDeg: PcbComponentBodyPlacementNormalizer.#normalizeAngle(
|
|
30
|
+
360 - Number(componentBody.rotationDeg || 0)
|
|
31
|
+
),
|
|
32
|
+
modelRotationDeg: {
|
|
33
|
+
x: Number(componentBody.modelRotationDeg?.x || 0),
|
|
34
|
+
y: Number(componentBody.modelRotationDeg?.y || 0),
|
|
35
|
+
z: PcbComponentBodyPlacementNormalizer.#normalizeAngle(
|
|
36
|
+
360 - Number(componentBody.modelRotationDeg?.z || 0)
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Normalizes one angle into the range [0, 360).
|
|
44
|
+
* @param {number} angle
|
|
45
|
+
* @returns {number}
|
|
46
|
+
*/
|
|
47
|
+
static #normalizeAngle(angle) {
|
|
48
|
+
const normalized = Number(angle || 0) % 360
|
|
49
|
+
|
|
50
|
+
return normalized < 0 ? normalized + 360 : normalized
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Groups PCB primitives by native Altium component ownership indexes.
|
|
7
|
+
*/
|
|
8
|
+
export class PcbComponentPrimitiveIndexer {
|
|
9
|
+
/**
|
|
10
|
+
* Groups normalized primitives by their native component index.
|
|
11
|
+
* @param {{ componentIndex: number, designator: string }[]} components
|
|
12
|
+
* @param {{ fills?: object[], tracks?: object[], arcs?: object[], vias?: object[], pads?: object[], regions?: object[], shapeBasedRegions?: object[], texts?: object[] }} pcb
|
|
13
|
+
* @param {{ componentIndex?: number | null }[]} componentBodies
|
|
14
|
+
* @returns {{ componentIndex: number, designator: string, pads: object[], tracks: object[], arcs: object[], fills: object[], vias: object[], regions: object[], shapeBasedRegions: object[], texts: object[], componentBodies: object[] }[]}
|
|
15
|
+
*/
|
|
16
|
+
static buildGroups(components, pcb, componentBodies) {
|
|
17
|
+
return (components || []).map((component) => {
|
|
18
|
+
const componentIndex = Number(component.componentIndex)
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
componentIndex,
|
|
22
|
+
designator: component.designator,
|
|
23
|
+
pads: PcbComponentPrimitiveIndexer.#primitivesForComponent(
|
|
24
|
+
pcb.pads,
|
|
25
|
+
componentIndex
|
|
26
|
+
),
|
|
27
|
+
tracks: PcbComponentPrimitiveIndexer.#primitivesForComponent(
|
|
28
|
+
pcb.tracks,
|
|
29
|
+
componentIndex
|
|
30
|
+
),
|
|
31
|
+
arcs: PcbComponentPrimitiveIndexer.#primitivesForComponent(
|
|
32
|
+
pcb.arcs,
|
|
33
|
+
componentIndex
|
|
34
|
+
),
|
|
35
|
+
fills: PcbComponentPrimitiveIndexer.#primitivesForComponent(
|
|
36
|
+
pcb.fills,
|
|
37
|
+
componentIndex
|
|
38
|
+
),
|
|
39
|
+
vias: PcbComponentPrimitiveIndexer.#primitivesForComponent(
|
|
40
|
+
pcb.vias,
|
|
41
|
+
componentIndex
|
|
42
|
+
),
|
|
43
|
+
regions: PcbComponentPrimitiveIndexer.#primitivesForComponent(
|
|
44
|
+
pcb.regions,
|
|
45
|
+
componentIndex
|
|
46
|
+
),
|
|
47
|
+
shapeBasedRegions:
|
|
48
|
+
PcbComponentPrimitiveIndexer.#primitivesForComponent(
|
|
49
|
+
pcb.shapeBasedRegions,
|
|
50
|
+
componentIndex
|
|
51
|
+
),
|
|
52
|
+
texts: (pcb.texts || []).filter(
|
|
53
|
+
(text) => Number(text?.ownerIndex) === componentIndex
|
|
54
|
+
),
|
|
55
|
+
componentBodies:
|
|
56
|
+
PcbComponentPrimitiveIndexer.#primitivesForComponent(
|
|
57
|
+
componentBodies,
|
|
58
|
+
componentIndex
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Indexes component primitive groups by their native component index.
|
|
66
|
+
* @param {{ componentIndex: number, designator: string, pads: object[], tracks: object[], arcs: object[], fills: object[], vias: object[], regions: object[], shapeBasedRegions: object[], texts: object[], componentBodies: object[] }[]} componentPrimitiveGroups
|
|
67
|
+
* @returns {({ componentIndex: number, designator: string, pads: object[], tracks: object[], arcs: object[], fills: object[], vias: object[], regions: object[], shapeBasedRegions: object[], texts: object[], componentBodies: object[] } | null)[]}
|
|
68
|
+
*/
|
|
69
|
+
static indexGroups(componentPrimitiveGroups) {
|
|
70
|
+
const indexedGroups = []
|
|
71
|
+
|
|
72
|
+
for (const group of componentPrimitiveGroups || []) {
|
|
73
|
+
const componentIndex = Number(group.componentIndex)
|
|
74
|
+
|
|
75
|
+
if (!Number.isInteger(componentIndex) || componentIndex < 0) {
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
while (indexedGroups.length <= componentIndex) {
|
|
80
|
+
indexedGroups.push(null)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
indexedGroups[componentIndex] = group
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return indexedGroups
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns primitives linked to a component by native Altium index.
|
|
91
|
+
* @param {{ componentIndex?: number | null }[] | undefined} primitives
|
|
92
|
+
* @param {number} componentIndex
|
|
93
|
+
* @returns {object[]}
|
|
94
|
+
*/
|
|
95
|
+
static #primitivesForComponent(primitives, componentIndex) {
|
|
96
|
+
return (primitives || []).filter((primitive) => {
|
|
97
|
+
const rawComponentIndex = primitive?.componentIndex
|
|
98
|
+
if (
|
|
99
|
+
rawComponentIndex === null ||
|
|
100
|
+
rawComponentIndex === undefined ||
|
|
101
|
+
rawComponentIndex === ''
|
|
102
|
+
) {
|
|
103
|
+
return false
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return Number(rawComponentIndex) === componentIndex
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
}
|