altium-toolkit 1.0.7 → 1.0.9
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 +18 -6
- package/docs/api.md +78 -16
- package/docs/model-format.md +229 -8
- package/docs/schemas/altium_toolkit/netlist_a1.schema.json +47 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +1661 -104
- package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +59 -0
- package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +57 -0
- package/docs/schemas/altium_toolkit/schematic_svg_semantics_a1.schema.json +50 -0
- package/docs/testing.md +9 -3
- package/package.json +1 -1
- package/spec/library-scope.md +7 -1
- package/src/core/altium/AltiumLayoutParser.mjs +104 -8
- package/src/core/altium/AltiumParser.mjs +191 -45
- package/src/core/altium/EmbeddedFileInventoryBuilder.mjs +255 -0
- package/src/core/altium/IntLibModelParser.mjs +240 -0
- package/src/core/altium/IntLibStreamExtractor.mjs +366 -0
- package/src/core/altium/LibraryRenderManifestBuilder.mjs +417 -0
- package/src/core/altium/LibrarySearchIndex.mjs +215 -0
- package/src/core/altium/NormalizedModelSchema.mjs +36 -0
- package/src/core/altium/PcbCustomPadShapeParser.mjs +244 -0
- package/src/core/altium/PcbDefaultsParser.mjs +171 -0
- package/src/core/altium/PcbDimensionParser.mjs +229 -0
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +232 -6
- package/src/core/altium/PcbExtendedPrimitiveInformationParser.mjs +256 -0
- package/src/core/altium/PcbLibModelParser.mjs +235 -14
- package/src/core/altium/PcbLibStreamExtractor.mjs +62 -4
- package/src/core/altium/PcbMaskPasteResolver.mjs +354 -0
- package/src/core/altium/PcbMechanicalLayerPairParser.mjs +204 -0
- package/src/core/altium/PcbModelParser.mjs +466 -28
- package/src/core/altium/PcbOwnershipGraphBuilder.mjs +245 -0
- package/src/core/altium/PcbPadPrimitiveParser.mjs +78 -65
- package/src/core/altium/PcbPadStackParser.mjs +58 -0
- package/src/core/altium/PcbPickPlacePositionResolver.mjs +217 -0
- package/src/core/altium/PcbPrimitiveParameterParser.mjs +3 -2
- package/src/core/altium/PcbRawRecordRegistry.mjs +121 -130
- package/src/core/altium/PcbRegionPrimitiveParser.mjs +5 -1
- package/src/core/altium/PcbRuleParser.mjs +354 -33
- package/src/core/altium/PcbSidecarRecordParser.mjs +177 -0
- package/src/core/altium/PcbSpecialStringResolver.mjs +220 -0
- package/src/core/altium/PcbStatisticsBuilder.mjs +532 -0
- package/src/core/altium/PcbStreamExtractor.mjs +111 -4
- package/src/core/altium/PcbTextPrimitiveParser.mjs +60 -0
- package/src/core/altium/PcbUnionParser.mjs +307 -0
- package/src/core/altium/PcbViaStackParser.mjs +98 -10
- package/src/core/altium/PcbViaStructureParser.mjs +335 -0
- package/src/core/altium/PrintableTextDecoder.mjs +53 -3
- package/src/core/altium/PrjPcbModelParser.mjs +257 -5
- package/src/core/altium/ProjectAnnotationParser.mjs +205 -0
- package/src/core/altium/ProjectDesignBundleBuilder.mjs +477 -0
- package/src/core/altium/ProjectNetlistExporter.mjs +499 -0
- package/src/core/altium/ProjectOutJobDigestBuilder.mjs +109 -0
- package/src/core/altium/ProjectVariantViewBuilder.mjs +334 -0
- package/src/core/altium/SchematicBindingProvenanceParser.mjs +223 -0
- package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +312 -0
- package/src/core/altium/SchematicComponentTextResolver.mjs +72 -19
- package/src/core/altium/SchematicConnectivityQaBuilder.mjs +271 -0
- package/src/core/altium/SchematicCrossSheetConnectorParser.mjs +140 -0
- package/src/core/altium/SchematicDirectiveParser.mjs +312 -0
- package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +231 -0
- package/src/core/altium/SchematicHarnessParser.mjs +302 -0
- package/src/core/altium/SchematicImageParser.mjs +474 -3
- package/src/core/altium/SchematicImplementationParser.mjs +518 -0
- package/src/core/altium/SchematicNetlistBuilder.mjs +15 -2
- package/src/core/altium/SchematicOwnershipGraphParser.mjs +195 -0
- package/src/core/altium/SchematicPinParser.mjs +84 -1
- package/src/core/altium/SchematicPrimitiveParser.mjs +301 -0
- package/src/core/altium/SchematicProjectParameterResolver.mjs +361 -0
- package/src/core/altium/SchematicQaReportBuilder.mjs +284 -0
- package/src/core/altium/SchematicRecordTypeRegistry.mjs +137 -0
- package/src/core/altium/SchematicRepeatedChannelParser.mjs +229 -0
- package/src/core/altium/SchematicStreamExtractor.mjs +10 -1
- package/src/core/altium/SchematicTemplateParser.mjs +256 -0
- package/src/core/altium/SchematicTextParser.mjs +123 -0
- package/src/core/ole/OleCompoundDocument.mjs +20 -0
- package/src/parser.mjs +29 -0
- package/src/renderers.mjs +3 -0
- package/src/styles/altium-renderers.css +25 -0
- package/src/ui/PcbBarcodeTextRenderer.mjs +436 -0
- package/src/ui/PcbInteractionGeometry.mjs +350 -0
- package/src/ui/PcbInteractionIndex.mjs +593 -0
- package/src/ui/PcbInteractionItemRegistry.mjs +66 -0
- package/src/ui/PcbInteractionLayerModel.mjs +99 -0
- package/src/ui/PcbScene3dBoardOutlineRefiner.mjs +74 -9
- package/src/ui/PcbScene3dBuilder.mjs +169 -7
- package/src/ui/PcbScene3dModelRegistry.mjs +74 -0
- package/src/ui/PcbSvgRenderer.mjs +1187 -34
- package/src/ui/PcbTextPrimitiveRenderer.mjs +193 -7
- package/src/ui/SchematicNoteRenderer.mjs +9 -2
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +206 -0
- package/src/ui/SchematicShapeRenderer.mjs +362 -0
- package/src/ui/SchematicSvgRenderer.mjs +1442 -92
- package/src/ui/SchematicTypography.mjs +48 -5
- package/src/ui/TextGeometrySidecarBuilder.mjs +147 -0
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import { NormalizedModelSchema } from './NormalizedModelSchema.mjs'
|
|
6
6
|
import { ParserUtils } from './ParserUtils.mjs'
|
|
7
|
+
import { LibraryRenderManifestBuilder } from './LibraryRenderManifestBuilder.mjs'
|
|
8
|
+
import { PcbCustomPadShapeParser } from './PcbCustomPadShapeParser.mjs'
|
|
9
|
+
import { PcbDefaultsParser } from './PcbDefaultsParser.mjs'
|
|
10
|
+
import { PcbExtendedPrimitiveInformationParser } from './PcbExtendedPrimitiveInformationParser.mjs'
|
|
11
|
+
import { PcbMaskPasteResolver } from './PcbMaskPasteResolver.mjs'
|
|
7
12
|
|
|
8
13
|
const { stripExtension } = ParserUtils
|
|
9
14
|
|
|
@@ -19,24 +24,56 @@ export class PcbLibModelParser {
|
|
|
19
24
|
*/
|
|
20
25
|
static parse(fileName, extraction) {
|
|
21
26
|
const safeExtraction = extraction || {}
|
|
27
|
+
const embeddedModels = Array.isArray(
|
|
28
|
+
safeExtraction.embeddedModels?.models
|
|
29
|
+
)
|
|
30
|
+
? safeExtraction.embeddedModels.models
|
|
31
|
+
: []
|
|
32
|
+
const componentBodies = Array.isArray(
|
|
33
|
+
safeExtraction.embeddedModels?.componentBodies
|
|
34
|
+
)
|
|
35
|
+
? safeExtraction.embeddedModels.componentBodies
|
|
36
|
+
: []
|
|
22
37
|
const footprints = Array.isArray(safeExtraction.footprints)
|
|
23
38
|
? safeExtraction.footprints.map((footprint) =>
|
|
24
|
-
PcbLibModelParser.#normalizeFootprint(
|
|
39
|
+
PcbLibModelParser.#normalizeFootprint(
|
|
40
|
+
footprint,
|
|
41
|
+
embeddedModels
|
|
42
|
+
)
|
|
25
43
|
)
|
|
26
44
|
: []
|
|
27
45
|
const embeddedFonts = Array.isArray(safeExtraction.embeddedFonts?.fonts)
|
|
28
46
|
? safeExtraction.embeddedFonts.fonts
|
|
29
47
|
: []
|
|
48
|
+
const defaults = PcbDefaultsParser.parse(
|
|
49
|
+
safeExtraction.libraryHeader || {},
|
|
50
|
+
'pcb-library'
|
|
51
|
+
)
|
|
30
52
|
const summary = PcbLibModelParser.#buildSummary(
|
|
31
53
|
fileName,
|
|
32
54
|
footprints,
|
|
33
|
-
embeddedFonts
|
|
55
|
+
embeddedFonts,
|
|
56
|
+
embeddedModels
|
|
34
57
|
)
|
|
35
58
|
const diagnostics = PcbLibModelParser.#buildDiagnostics(
|
|
36
59
|
footprints,
|
|
37
60
|
embeddedFonts,
|
|
61
|
+
embeddedModels,
|
|
38
62
|
safeExtraction
|
|
39
63
|
)
|
|
64
|
+
const pcbLibrary = {
|
|
65
|
+
libraryHeader: safeExtraction.libraryHeader || {},
|
|
66
|
+
componentParamsToc: safeExtraction.componentParamsToc || {},
|
|
67
|
+
sectionKeys: safeExtraction.sectionKeys || {},
|
|
68
|
+
footprints,
|
|
69
|
+
indexes: PcbLibModelParser.#buildIndexes(footprints),
|
|
70
|
+
embeddedFonts,
|
|
71
|
+
embeddedModels,
|
|
72
|
+
componentBodies,
|
|
73
|
+
...(defaults ? { defaults } : {})
|
|
74
|
+
}
|
|
75
|
+
pcbLibrary.renderManifest =
|
|
76
|
+
LibraryRenderManifestBuilder.buildPcbLibraryManifest(pcbLibrary)
|
|
40
77
|
|
|
41
78
|
return NormalizedModelSchema.attach({
|
|
42
79
|
kind: 'pcb-library',
|
|
@@ -44,13 +81,7 @@ export class PcbLibModelParser {
|
|
|
44
81
|
fileName,
|
|
45
82
|
summary,
|
|
46
83
|
diagnostics,
|
|
47
|
-
pcbLibrary
|
|
48
|
-
libraryHeader: safeExtraction.libraryHeader || {},
|
|
49
|
-
componentParamsToc: safeExtraction.componentParamsToc || {},
|
|
50
|
-
sectionKeys: safeExtraction.sectionKeys || {},
|
|
51
|
-
footprints,
|
|
52
|
-
embeddedFonts
|
|
53
|
-
},
|
|
84
|
+
pcbLibrary,
|
|
54
85
|
bom: []
|
|
55
86
|
})
|
|
56
87
|
}
|
|
@@ -60,9 +91,10 @@ export class PcbLibModelParser {
|
|
|
60
91
|
* @param {string} fileName
|
|
61
92
|
* @param {object[]} footprints
|
|
62
93
|
* @param {object[]} embeddedFonts
|
|
94
|
+
* @param {object[]} embeddedModels
|
|
63
95
|
* @returns {Record<string, number | string>}
|
|
64
96
|
*/
|
|
65
|
-
static #buildSummary(fileName, footprints, embeddedFonts) {
|
|
97
|
+
static #buildSummary(fileName, footprints, embeddedFonts, embeddedModels) {
|
|
66
98
|
return {
|
|
67
99
|
title: stripExtension(fileName),
|
|
68
100
|
footprintCount: footprints.length,
|
|
@@ -81,7 +113,8 @@ export class PcbLibModelParser {
|
|
|
81
113
|
footprints,
|
|
82
114
|
'rawRecords'
|
|
83
115
|
),
|
|
84
|
-
embeddedFontCount: embeddedFonts.length
|
|
116
|
+
embeddedFontCount: embeddedFonts.length,
|
|
117
|
+
embeddedModelCount: embeddedModels.length
|
|
85
118
|
}
|
|
86
119
|
}
|
|
87
120
|
|
|
@@ -89,10 +122,16 @@ export class PcbLibModelParser {
|
|
|
89
122
|
* Builds parser diagnostics from extraction metadata.
|
|
90
123
|
* @param {object[]} footprints
|
|
91
124
|
* @param {object[]} embeddedFonts
|
|
125
|
+
* @param {object[]} embeddedModels
|
|
92
126
|
* @param {{ streamNames?: string[], diagnostics?: Record<string, number> }} extraction
|
|
93
127
|
* @returns {{ severity: 'info' | 'warning', message: string }[]}
|
|
94
128
|
*/
|
|
95
|
-
static #buildDiagnostics(
|
|
129
|
+
static #buildDiagnostics(
|
|
130
|
+
footprints,
|
|
131
|
+
embeddedFonts,
|
|
132
|
+
embeddedModels,
|
|
133
|
+
extraction
|
|
134
|
+
) {
|
|
96
135
|
const diagnostics = [
|
|
97
136
|
{
|
|
98
137
|
severity: 'info',
|
|
@@ -123,6 +162,16 @@ export class PcbLibModelParser {
|
|
|
123
162
|
})
|
|
124
163
|
}
|
|
125
164
|
|
|
165
|
+
if (embeddedModels.length) {
|
|
166
|
+
diagnostics.push({
|
|
167
|
+
severity: 'info',
|
|
168
|
+
message:
|
|
169
|
+
'Recovered ' +
|
|
170
|
+
embeddedModels.length +
|
|
171
|
+
' embedded PcbLib model payloads.'
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
126
175
|
if (extraction.diagnostics?.missingFootprintCount) {
|
|
127
176
|
diagnostics.push({
|
|
128
177
|
severity: 'warning',
|
|
@@ -139,9 +188,10 @@ export class PcbLibModelParser {
|
|
|
139
188
|
/**
|
|
140
189
|
* Normalizes one extracted footprint object with stable primitive arrays.
|
|
141
190
|
* @param {object} footprint
|
|
191
|
+
* @param {object[]} libraryEmbeddedModels Library-level embedded models.
|
|
142
192
|
* @returns {object}
|
|
143
193
|
*/
|
|
144
|
-
static #normalizeFootprint(footprint) {
|
|
194
|
+
static #normalizeFootprint(footprint, libraryEmbeddedModels = []) {
|
|
145
195
|
const normalized = {
|
|
146
196
|
name: String(footprint.name || ''),
|
|
147
197
|
dataName: String(footprint.dataName || footprint.name || ''),
|
|
@@ -158,6 +208,13 @@ export class PcbLibModelParser {
|
|
|
158
208
|
unknownRecords: Array.isArray(footprint.unknownRecords)
|
|
159
209
|
? footprint.unknownRecords
|
|
160
210
|
: [],
|
|
211
|
+
implementations: PcbLibModelParser.#array(
|
|
212
|
+
footprint.implementations
|
|
213
|
+
),
|
|
214
|
+
componentModels: PcbLibModelParser.#array(
|
|
215
|
+
footprint.componentModels
|
|
216
|
+
),
|
|
217
|
+
pinDisplayModes: footprint.pinDisplayModes || {},
|
|
161
218
|
rawRecords: PcbLibModelParser.#array(footprint.rawRecords),
|
|
162
219
|
pads: PcbLibModelParser.#array(footprint.pads),
|
|
163
220
|
tracks: PcbLibModelParser.#array(footprint.tracks),
|
|
@@ -165,8 +222,51 @@ export class PcbLibModelParser {
|
|
|
165
222
|
vias: PcbLibModelParser.#array(footprint.vias),
|
|
166
223
|
fills: PcbLibModelParser.#array(footprint.fills),
|
|
167
224
|
texts: PcbLibModelParser.#array(footprint.texts),
|
|
168
|
-
regions: PcbLibModelParser.#array(footprint.regions)
|
|
225
|
+
regions: PcbLibModelParser.#array(footprint.regions),
|
|
226
|
+
shapeBasedRegions: PcbLibModelParser.#array(
|
|
227
|
+
footprint.shapeBasedRegions
|
|
228
|
+
),
|
|
229
|
+
extendedPrimitiveInformation:
|
|
230
|
+
footprint.extendedPrimitiveInformation || {
|
|
231
|
+
entries: [],
|
|
232
|
+
byPrimitiveIndex: {},
|
|
233
|
+
byPrimitiveKey: {}
|
|
234
|
+
},
|
|
235
|
+
customPadShapes: footprint.customPadShapes || {
|
|
236
|
+
entries: [],
|
|
237
|
+
byPrimitiveIndex: {}
|
|
238
|
+
},
|
|
239
|
+
embeddedModels: PcbLibModelParser.#array(footprint.embeddedModels),
|
|
240
|
+
componentBodies: PcbLibModelParser.#array(footprint.componentBodies)
|
|
169
241
|
}
|
|
242
|
+
normalized.defaults = PcbDefaultsParser.parse(
|
|
243
|
+
{
|
|
244
|
+
...(footprint.defaults || {}),
|
|
245
|
+
...(footprint.parameters || {})
|
|
246
|
+
},
|
|
247
|
+
'pcb-library-footprint'
|
|
248
|
+
)
|
|
249
|
+
PcbExtendedPrimitiveInformationParser.attachToPrimitives(
|
|
250
|
+
normalized,
|
|
251
|
+
normalized.extendedPrimitiveInformation
|
|
252
|
+
)
|
|
253
|
+
PcbCustomPadShapeParser.attachToPads(
|
|
254
|
+
normalized.pads,
|
|
255
|
+
normalized.customPadShapes,
|
|
256
|
+
normalized
|
|
257
|
+
)
|
|
258
|
+
normalized.componentBodies = PcbLibModelParser.#annotateComponentBodies(
|
|
259
|
+
normalized.componentBodies,
|
|
260
|
+
normalized.embeddedModels.length
|
|
261
|
+
? normalized.embeddedModels
|
|
262
|
+
: libraryEmbeddedModels
|
|
263
|
+
)
|
|
264
|
+
normalized.maskPaste = PcbMaskPasteResolver.build({
|
|
265
|
+
pads: normalized.pads,
|
|
266
|
+
vias: normalized.vias,
|
|
267
|
+
rules: footprint.rules || [],
|
|
268
|
+
defaults: normalized.defaults
|
|
269
|
+
})
|
|
170
270
|
|
|
171
271
|
return {
|
|
172
272
|
...normalized,
|
|
@@ -176,6 +276,127 @@ export class PcbLibModelParser {
|
|
|
176
276
|
}
|
|
177
277
|
}
|
|
178
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Adds deterministic projection diagnostics to footprint body records.
|
|
281
|
+
* @param {object[]} componentBodies Component body records.
|
|
282
|
+
* @param {object[]} embeddedModels Embedded model records.
|
|
283
|
+
* @returns {object[]}
|
|
284
|
+
*/
|
|
285
|
+
static #annotateComponentBodies(componentBodies, embeddedModels) {
|
|
286
|
+
return componentBodies.map((componentBody) => ({
|
|
287
|
+
...componentBody,
|
|
288
|
+
projectionDiagnostics: PcbLibModelParser.#projectionDiagnostics(
|
|
289
|
+
componentBody,
|
|
290
|
+
embeddedModels
|
|
291
|
+
)
|
|
292
|
+
}))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Resolves one component-body projection diagnostic.
|
|
297
|
+
* @param {object} componentBody Component body record.
|
|
298
|
+
* @param {object[]} embeddedModels Embedded model records.
|
|
299
|
+
* @returns {{ source: string, reason: string }}
|
|
300
|
+
*/
|
|
301
|
+
static #projectionDiagnostics(componentBody, embeddedModels) {
|
|
302
|
+
const matchedModel = (embeddedModels || []).find(
|
|
303
|
+
(model) =>
|
|
304
|
+
PcbLibModelParser.#sameNonEmptyValue(
|
|
305
|
+
model?.id,
|
|
306
|
+
componentBody?.modelId
|
|
307
|
+
) ||
|
|
308
|
+
PcbLibModelParser.#sameNonEmptyValue(
|
|
309
|
+
model?.checksum,
|
|
310
|
+
componentBody?.checksum
|
|
311
|
+
) ||
|
|
312
|
+
PcbLibModelParser.#sameNonEmptyValue(
|
|
313
|
+
model?.name,
|
|
314
|
+
componentBody?.name
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
if (matchedModel) {
|
|
319
|
+
return {
|
|
320
|
+
source: 'embedded-model',
|
|
321
|
+
reason: 'matched embedded model payload'
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
source: 'fallback',
|
|
327
|
+
reason: 'no embedded model payload matched this body'
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Compares two values only when both are present.
|
|
333
|
+
* @param {unknown} left First value.
|
|
334
|
+
* @param {unknown} right Second value.
|
|
335
|
+
* @returns {boolean}
|
|
336
|
+
*/
|
|
337
|
+
static #sameNonEmptyValue(left, right) {
|
|
338
|
+
return (
|
|
339
|
+
left !== null &&
|
|
340
|
+
left !== undefined &&
|
|
341
|
+
left !== '' &&
|
|
342
|
+
right !== null &&
|
|
343
|
+
right !== undefined &&
|
|
344
|
+
right !== '' &&
|
|
345
|
+
String(left) === String(right)
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Builds footprint lookup indexes for library consumers.
|
|
351
|
+
* @param {object[]} footprints Normalized footprints.
|
|
352
|
+
* @returns {object}
|
|
353
|
+
*/
|
|
354
|
+
static #buildIndexes(footprints) {
|
|
355
|
+
const footprintsByName = {}
|
|
356
|
+
|
|
357
|
+
for (const [index, footprint] of footprints.entries()) {
|
|
358
|
+
footprintsByName[footprint.name] =
|
|
359
|
+
PcbLibModelParser.#footprintIndexEntry(footprint, index)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return { footprintsByName }
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Builds one footprint index entry.
|
|
367
|
+
* @param {object} footprint Normalized footprint.
|
|
368
|
+
* @param {number} index Footprint index.
|
|
369
|
+
* @returns {object}
|
|
370
|
+
*/
|
|
371
|
+
static #footprintIndexEntry(footprint, index) {
|
|
372
|
+
return {
|
|
373
|
+
index,
|
|
374
|
+
name: footprint.name,
|
|
375
|
+
dataName: footprint.dataName,
|
|
376
|
+
sourceStorage: footprint.sourceStorage,
|
|
377
|
+
primitiveCount: footprint.primitiveCount,
|
|
378
|
+
padCount: footprint.pads.length,
|
|
379
|
+
textCount: footprint.texts.length,
|
|
380
|
+
keywords: PcbLibModelParser.#buildFootprintKeywords(footprint)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Builds searchable metadata tokens for one footprint.
|
|
386
|
+
* @param {object} footprint Normalized footprint.
|
|
387
|
+
* @returns {string[]}
|
|
388
|
+
*/
|
|
389
|
+
static #buildFootprintKeywords(footprint) {
|
|
390
|
+
return [
|
|
391
|
+
footprint.name,
|
|
392
|
+
footprint.dataName,
|
|
393
|
+
...Object.values(footprint.parameters || {}),
|
|
394
|
+
...Object.values(footprint.componentParams || {})
|
|
395
|
+
]
|
|
396
|
+
.map((value) => String(value || '').trim())
|
|
397
|
+
.filter(Boolean)
|
|
398
|
+
}
|
|
399
|
+
|
|
179
400
|
/**
|
|
180
401
|
* Counts either a scalar footprint field or an array-valued family.
|
|
181
402
|
* @param {object[]} footprints
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
4
|
|
|
5
5
|
import { PcbBinaryPrimitiveParser } from './PcbBinaryPrimitiveParser.mjs'
|
|
6
|
+
import { PcbCustomPadShapeParser } from './PcbCustomPadShapeParser.mjs'
|
|
6
7
|
import { PcbEmbeddedFontExtractor } from './PcbEmbeddedFontExtractor.mjs'
|
|
8
|
+
import { PcbEmbeddedModelExtractor } from './PcbEmbeddedModelExtractor.mjs'
|
|
9
|
+
import { PcbExtendedPrimitiveInformationParser } from './PcbExtendedPrimitiveInformationParser.mjs'
|
|
7
10
|
import { PcbRawRecordRegistry } from './PcbRawRecordRegistry.mjs'
|
|
8
11
|
import { OleCompoundDocument } from '../ole/OleCompoundDocument.mjs'
|
|
9
12
|
import { OleConstants } from '../ole/OleConstants.mjs'
|
|
@@ -159,6 +162,8 @@ export class PcbLibStreamExtractor {
|
|
|
159
162
|
)
|
|
160
163
|
const embeddedFonts =
|
|
161
164
|
PcbEmbeddedFontExtractor.extractFromStreams(streams)
|
|
165
|
+
const embeddedModels =
|
|
166
|
+
PcbEmbeddedModelExtractor.extractFromStreams(streams)
|
|
162
167
|
|
|
163
168
|
return {
|
|
164
169
|
libraryHeader: parsedLibraryData.libraryHeader,
|
|
@@ -168,15 +173,19 @@ export class PcbLibStreamExtractor {
|
|
|
168
173
|
streamNames: PcbLibStreamExtractor.#collectUsedStreamNames(
|
|
169
174
|
footprints,
|
|
170
175
|
streams,
|
|
171
|
-
embeddedFonts
|
|
176
|
+
embeddedFonts,
|
|
177
|
+
embeddedModels
|
|
172
178
|
),
|
|
173
179
|
embeddedFonts,
|
|
180
|
+
embeddedModels,
|
|
174
181
|
diagnostics: {
|
|
175
182
|
declaredFootprintCount: parsedLibraryData.footprintNames.length,
|
|
176
183
|
footprintCount: footprints.length,
|
|
177
184
|
primitiveCount,
|
|
178
185
|
rawRecordCount,
|
|
179
186
|
embeddedFontCount: embeddedFonts.fonts.length,
|
|
187
|
+
embeddedModelCount: embeddedModels.models.length,
|
|
188
|
+
componentBodyCount: embeddedModels.componentBodies.length,
|
|
180
189
|
missingFootprintCount:
|
|
181
190
|
parsedLibraryData.footprintNames.length - footprints.length
|
|
182
191
|
}
|
|
@@ -339,6 +348,21 @@ export class PcbLibStreamExtractor {
|
|
|
339
348
|
const wideStrings = PcbLibStreamExtractor.#parseWideStrings(
|
|
340
349
|
streams.get(storageName + '/WideStrings')
|
|
341
350
|
)
|
|
351
|
+
const extendedPrimitiveInformation =
|
|
352
|
+
PcbExtendedPrimitiveInformationParser.parse(
|
|
353
|
+
PcbLibStreamExtractor.#firstStream(streams, [
|
|
354
|
+
storageName + '/ExtendedPrimitiveInformation/Data',
|
|
355
|
+
storageName + '/ExtendedPrimitiveInformation'
|
|
356
|
+
]),
|
|
357
|
+
storageName + '/ExtendedPrimitiveInformation/Data'
|
|
358
|
+
)
|
|
359
|
+
const customPadShapes = PcbCustomPadShapeParser.parse(
|
|
360
|
+
PcbLibStreamExtractor.#firstStream(streams, [
|
|
361
|
+
storageName + '/CustomShapes/Data',
|
|
362
|
+
storageName + '/CustomShapes'
|
|
363
|
+
]),
|
|
364
|
+
storageName + '/CustomShapes/Data'
|
|
365
|
+
)
|
|
342
366
|
const parsedData = PcbLibStreamExtractor.#parseFootprintData(
|
|
343
367
|
streams.get(storageName + '/Data') || new Uint8Array(),
|
|
344
368
|
declaredPrimitiveCount,
|
|
@@ -364,10 +388,22 @@ export class PcbLibStreamExtractor {
|
|
|
364
388
|
vias: parsedData.vias,
|
|
365
389
|
fills: parsedData.fills,
|
|
366
390
|
texts: parsedData.texts,
|
|
367
|
-
regions: parsedData.regions
|
|
391
|
+
regions: parsedData.regions,
|
|
392
|
+
extendedPrimitiveInformation,
|
|
393
|
+
customPadShapes
|
|
368
394
|
}
|
|
369
395
|
}
|
|
370
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Returns the first present stream from a candidate list.
|
|
399
|
+
* @param {Map<string, Uint8Array>} streams Stream map.
|
|
400
|
+
* @param {string[]} candidates Candidate stream names.
|
|
401
|
+
* @returns {Uint8Array | undefined}
|
|
402
|
+
*/
|
|
403
|
+
static #firstStream(streams, candidates) {
|
|
404
|
+
return candidates.map((name) => streams.get(name)).find(Boolean)
|
|
405
|
+
}
|
|
406
|
+
|
|
371
407
|
/**
|
|
372
408
|
* Parses one footprint Data stream after its leading name block.
|
|
373
409
|
* @param {Uint8Array} bytes
|
|
@@ -903,9 +939,15 @@ export class PcbLibStreamExtractor {
|
|
|
903
939
|
* @param {object[]} footprints
|
|
904
940
|
* @param {Map<string, Uint8Array>} streams
|
|
905
941
|
* @param {{ fonts?: { sourceStream: string }[] }} embeddedFonts
|
|
942
|
+
* @param {{ models?: { sourceStream: string }[], componentBodies?: { sourceStream: string }[] }} embeddedModels
|
|
906
943
|
* @returns {string[]}
|
|
907
944
|
*/
|
|
908
|
-
static #collectUsedStreamNames(
|
|
945
|
+
static #collectUsedStreamNames(
|
|
946
|
+
footprints,
|
|
947
|
+
streams,
|
|
948
|
+
embeddedFonts,
|
|
949
|
+
embeddedModels
|
|
950
|
+
) {
|
|
909
951
|
const names = new Set()
|
|
910
952
|
|
|
911
953
|
for (const baseName of [
|
|
@@ -923,7 +965,11 @@ export class PcbLibStreamExtractor {
|
|
|
923
965
|
'Header',
|
|
924
966
|
'Data',
|
|
925
967
|
'Parameters',
|
|
926
|
-
'WideStrings'
|
|
968
|
+
'WideStrings',
|
|
969
|
+
'ExtendedPrimitiveInformation/Data',
|
|
970
|
+
'ExtendedPrimitiveInformation',
|
|
971
|
+
'CustomShapes/Data',
|
|
972
|
+
'CustomShapes'
|
|
927
973
|
]) {
|
|
928
974
|
const name = footprint.sourceStorage + '/' + suffix
|
|
929
975
|
if (streams.has(name)) {
|
|
@@ -938,6 +984,18 @@ export class PcbLibStreamExtractor {
|
|
|
938
984
|
}
|
|
939
985
|
}
|
|
940
986
|
|
|
987
|
+
for (const model of embeddedModels.models || []) {
|
|
988
|
+
if (streams.has(model.sourceStream)) {
|
|
989
|
+
names.add(model.sourceStream)
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
for (const componentBody of embeddedModels.componentBodies || []) {
|
|
994
|
+
if (streams.has(componentBody.sourceStream)) {
|
|
995
|
+
names.add(componentBody.sourceStream)
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
941
999
|
return [...names].sort((left, right) => left.localeCompare(right))
|
|
942
1000
|
}
|
|
943
1001
|
|