altium-toolkit 1.0.10 → 1.1.0

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.
Files changed (63) hide show
  1. package/docs/api.md +6 -2
  2. package/docs/model-format.md +29 -4
  3. package/docs/schemas/altium_toolkit/ci_artifact_bundle_a1.schema.json +4 -0
  4. package/docs/schemas/altium_toolkit/contract_gate_a1.schema.json +34 -0
  5. package/docs/schemas/altium_toolkit/draftsman_board_view_cache_a1.schema.json +115 -0
  6. package/docs/schemas/altium_toolkit/draftsman_digest_a1.schema.json +132 -1
  7. package/docs/schemas/altium_toolkit/host_capabilities_a1.schema.json +39 -0
  8. package/docs/schemas/altium_toolkit/library_merge_plan_a1.schema.json +56 -0
  9. package/docs/schemas/altium_toolkit/library_qa_a1.schema.json +70 -0
  10. package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +692 -2
  11. package/docs/schemas/altium_toolkit/pcb_bom_profile_a1.schema.json +48 -0
  12. package/docs/schemas/altium_toolkit/pcb_layer_stack_a1.schema.json +98 -0
  13. package/docs/schemas/altium_toolkit/pcb_layer_stack_fidelity_a1.schema.json +66 -0
  14. package/docs/schemas/altium_toolkit/pcb_placed_footprint_extraction_a1.schema.json +31 -0
  15. package/docs/schemas/altium_toolkit/pcb_review_metadata_a1.schema.json +62 -0
  16. package/docs/schemas/altium_toolkit/pcb_rigid_flex_topology_a1.schema.json +52 -0
  17. package/docs/schemas/altium_toolkit/pcblib_parity_a1.schema.json +24 -0
  18. package/docs/schemas/altium_toolkit/project_bom_pnp_reconciliation_a1.schema.json +63 -0
  19. package/docs/schemas/altium_toolkit/project_outjob_digest_a1.schema.json +46 -0
  20. package/docs/schemas/altium_toolkit/project_script_a1.schema.json +50 -0
  21. package/docs/schemas/altium_toolkit/schematic_render_ops_a1.schema.json +55 -0
  22. package/docs/schemas/altium_toolkit/schematic_template_extraction_a1.schema.json +37 -0
  23. package/package.json +1 -1
  24. package/src/core/altium/AltiumParser.mjs +7 -2
  25. package/src/core/altium/CiArtifactBundleBuilder.mjs +16 -5
  26. package/src/core/altium/ContractGateReportBuilder.mjs +351 -0
  27. package/src/core/altium/DraftsmanBoardViewMetadataBuilder.mjs +653 -0
  28. package/src/core/altium/DraftsmanDigestParser.mjs +246 -7
  29. package/src/core/altium/DraftsmanImagePayloadManifestBuilder.mjs +178 -0
  30. package/src/core/altium/HostCapabilityDiagnosticsBuilder.mjs +271 -0
  31. package/src/core/altium/LibraryQaReportBuilder.mjs +504 -0
  32. package/src/core/altium/LibraryRenderManifestBuilder.mjs +172 -2
  33. package/src/core/altium/PcbBomProfileBuilder.mjs +263 -0
  34. package/src/core/altium/PcbComponentKindPolicy.mjs +146 -0
  35. package/src/core/altium/PcbLayerStackFidelityReportBuilder.mjs +141 -0
  36. package/src/core/altium/PcbLayerStackInterchangeParser.mjs +453 -0
  37. package/src/core/altium/PcbLayerStackQueryHelper.mjs +195 -0
  38. package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +906 -0
  39. package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +488 -0
  40. package/src/core/altium/PcbLibModelParser.mjs +2 -0
  41. package/src/core/altium/PcbLibParityReportBuilder.mjs +242 -0
  42. package/src/core/altium/PcbModelParser.mjs +182 -18
  43. package/src/core/altium/PcbPickPlacePositionResolver.mjs +3 -0
  44. package/src/core/altium/PcbPlacedFootprintManifestBuilder.mjs +338 -0
  45. package/src/core/altium/PcbPolygonRecordParser.mjs +120 -0
  46. package/src/core/altium/PcbReviewDrillMetadataBuilder.mjs +301 -0
  47. package/src/core/altium/PcbReviewMetadataBuilder.mjs +373 -0
  48. package/src/core/altium/PcbReviewPolygonRealizationBuilder.mjs +269 -0
  49. package/src/core/altium/PcbReviewRouteHighlightProfileBuilder.mjs +298 -0
  50. package/src/core/altium/PcbRigidFlexTopologyBuilder.mjs +171 -0
  51. package/src/core/altium/PrintableTextDecoder.mjs +70 -6
  52. package/src/core/altium/PrjPcbModelParser.mjs +45 -0
  53. package/src/core/altium/PrjScrModelParser.mjs +386 -0
  54. package/src/core/altium/ProjectBomPnpReconciliationBuilder.mjs +237 -0
  55. package/src/core/altium/ProjectDesignBundleBuilder.mjs +61 -2
  56. package/src/core/altium/ProjectOutJobDigestBuilder.mjs +424 -13
  57. package/src/core/altium/SvgModelCrossLinkValidator.mjs +35 -2
  58. package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +164 -0
  59. package/src/parser.mjs +15 -0
  60. package/src/ui/PcbFootprintPrimitiveSelector.mjs +13 -1
  61. package/src/ui/PcbScene3dBuilder.mjs +26 -4
  62. package/src/ui/SchematicRenderOpsSidecarBuilder.mjs +554 -0
  63. package/src/ui/SchematicSvgRenderer.mjs +48 -2
@@ -0,0 +1,488 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import { ParserUtils } from './ParserUtils.mjs'
6
+
7
+ /**
8
+ * Parses source-only layer-stack metadata that is not part of core geometry.
9
+ */
10
+ export class PcbLayerStackSourceMetadataParser {
11
+ /**
12
+ * Parses source-aware extras for one layer-stack row.
13
+ * @param {Record<string, string | string[]>} fields Source fields.
14
+ * @param {number} index Layer-stack index.
15
+ * @returns {object}
16
+ */
17
+ static layerSourceFields(fields, index) {
18
+ const prefixes = ['V9_STACK_LAYER', 'STACK_LAYER']
19
+ const layerField = (suffixes) =>
20
+ PcbLayerStackSourceMetadataParser.#indexedField(
21
+ fields,
22
+ prefixes,
23
+ index,
24
+ suffixes
25
+ )
26
+
27
+ return PcbLayerStackSourceMetadataParser.#stripUndefined({
28
+ family: layerField(['FAMILY', 'LAYERFAMILY']),
29
+ sourceFamily: layerField(['SOURCEFAMILY', 'SOURCE_FAMILY']),
30
+ sourceRecordId: layerField([
31
+ 'SOURCE_RECORD_ID',
32
+ 'SOURCERECORDID',
33
+ 'RECORDID'
34
+ ]),
35
+ sourceKeys: PcbLayerStackSourceMetadataParser.optionalArray(
36
+ PcbLayerStackSourceMetadataParser.#list(
37
+ layerField(['SOURCE_KEYS', 'SOURCEKEYS'])
38
+ )
39
+ ),
40
+ registryRef: layerField(['REGISTRYREF', 'REGISTRY_REF']),
41
+ modelId: layerField(['MODELID', 'MODEL_ID']),
42
+ aliases: PcbLayerStackSourceMetadataParser.optionalArray(
43
+ PcbLayerStackSourceMetadataParser.#list(
44
+ layerField(['ALIASES', 'DISPLAYALIASES'])
45
+ )
46
+ ),
47
+ materialColor: layerField(['MATERIALCOLOR', 'MATERIAL_COLOR']),
48
+ surfaceFinish: layerField(['SURFACEFINISH', 'SURFACE_FINISH']),
49
+ plating: layerField(['PLATING']),
50
+ coverlayExpansion: layerField([
51
+ 'COVERLAYEXPANSION',
52
+ 'COVERLAY_EXPANSION'
53
+ ]),
54
+ isStiffener: PcbLayerStackSourceMetadataParser.#optionalBoolean(
55
+ layerField(['ISSTIFFENER', 'IS_STIFFENER'])
56
+ ),
57
+ isAdhesive: PcbLayerStackSourceMetadataParser.#optionalBoolean(
58
+ layerField(['ISADHESIVE', 'IS_ADHESIVE'])
59
+ ),
60
+ stackupxShared: PcbLayerStackSourceMetadataParser.#optionalBoolean(
61
+ layerField(['SHARED', 'STACKUPX_SHARED'])
62
+ ),
63
+ stackupxProperties: PcbLayerStackSourceMetadataParser.#keyValueMap(
64
+ layerField(['STACKUPX_PROPERTIES', 'PROPERTIES'])
65
+ ),
66
+ substackEnablement: PcbLayerStackSourceMetadataParser.optionalArray(
67
+ PcbLayerStackSourceMetadataParser.#substackEnablement(
68
+ fields,
69
+ index
70
+ )
71
+ )
72
+ })
73
+ }
74
+
75
+ /**
76
+ * Returns undefined for optional empty arrays.
77
+ * @param {object[]} values Source values.
78
+ * @returns {object[] | undefined}
79
+ */
80
+ static optionalArray(values) {
81
+ return values.length ? values : undefined
82
+ }
83
+
84
+ /**
85
+ * Parses branch-section rows.
86
+ * @param {Record<string, string | string[]>} fields Board fields.
87
+ * @param {number} branchIndex Branch index.
88
+ * @returns {object[]}
89
+ */
90
+ static branchSections(fields, branchIndex) {
91
+ const sectionIndexes = PcbLayerStackSourceMetadataParser.#nestedIndexes(
92
+ fields,
93
+ 'STACKBRANCH' + branchIndex + '_SECTION',
94
+ '_ID'
95
+ )
96
+
97
+ return sectionIndexes.map((sectionIndex) => {
98
+ const prefix =
99
+ 'STACKBRANCH' + branchIndex + '_SECTION' + sectionIndex
100
+
101
+ return PcbLayerStackSourceMetadataParser.#stripUndefined({
102
+ index: sectionIndex,
103
+ id: PcbLayerStackSourceMetadataParser.#field(
104
+ fields,
105
+ prefix + '_ID'
106
+ ),
107
+ name: PcbLayerStackSourceMetadataParser.#field(
108
+ fields,
109
+ prefix + '_NAME'
110
+ ),
111
+ parentSectionId: PcbLayerStackSourceMetadataParser.#field(
112
+ fields,
113
+ prefix + '_PARENTID'
114
+ ),
115
+ stacks: PcbLayerStackSourceMetadataParser.#branchSectionStacks(
116
+ fields,
117
+ prefix
118
+ )
119
+ })
120
+ })
121
+ }
122
+
123
+ /**
124
+ * Parses top-level board bend-line cache entries.
125
+ * @param {Record<string, string | string[]>} fields Board fields.
126
+ * @returns {object[]}
127
+ */
128
+ static topLevelBendLines(fields) {
129
+ return PcbLayerStackSourceMetadataParser.#indexedRows(fields, [
130
+ /^BOARD_BENDLINE(\d+)$/iu,
131
+ /^BENDLINE(\d+)$/iu
132
+ ]).map((index) => {
133
+ const raw =
134
+ PcbLayerStackSourceMetadataParser.#field(
135
+ fields,
136
+ 'BOARD_BENDLINE' + index
137
+ ) ||
138
+ PcbLayerStackSourceMetadataParser.#field(
139
+ fields,
140
+ 'BENDLINE' + index
141
+ )
142
+ const tokens = raw.split(';').map((token) => token.trim())
143
+ const radiusRaw = PcbLayerStackSourceMetadataParser.#numberToken(
144
+ tokens[1]
145
+ )
146
+
147
+ return PcbLayerStackSourceMetadataParser.#stripUndefined({
148
+ index,
149
+ raw,
150
+ angleDeg: PcbLayerStackSourceMetadataParser.#numberToken(
151
+ tokens[0]
152
+ ),
153
+ radiusRaw,
154
+ radiusMil:
155
+ radiusRaw === undefined
156
+ ? undefined
157
+ : Number((radiusRaw / 10000).toFixed(6)),
158
+ foldIndex: PcbLayerStackSourceMetadataParser.#numberToken(
159
+ tokens[2]
160
+ ),
161
+ name: tokens[7],
162
+ stateRaw: tokens[8],
163
+ regionName: tokens[9]
164
+ })
165
+ })
166
+ }
167
+
168
+ /**
169
+ * Builds cavity/stiffener reporting metadata.
170
+ * @param {object[]} layers Layer rows.
171
+ * @param {object[]} boardRegions Board-region rows.
172
+ * @returns {object}
173
+ */
174
+ static cavityReport(layers, boardRegions) {
175
+ const cavityRegions = boardRegions
176
+ .map((region, regionIndex) => ({ region, regionIndex }))
177
+ .filter(({ region }) => Boolean(region.cavityHeight))
178
+ .map(({ region, regionIndex }) =>
179
+ PcbLayerStackSourceMetadataParser.#stripUndefined({
180
+ regionIndex,
181
+ name: region.name,
182
+ layerStackId: region.layerStackId,
183
+ cavityHeight: region.cavityHeight
184
+ })
185
+ )
186
+ const stiffenerLayers = layers
187
+ .filter((layer) => layer.isStiffener)
188
+ .map((layer) => layer.name)
189
+ .filter(Boolean)
190
+ const adhesiveLayers = layers
191
+ .filter((layer) => layer.isAdhesive)
192
+ .map((layer) => layer.name)
193
+ .filter(Boolean)
194
+
195
+ return {
196
+ cavityRegionCount: cavityRegions.length,
197
+ stiffenerLayerCount: stiffenerLayers.length,
198
+ adhesiveLayerCount: adhesiveLayers.length,
199
+ cavityRegions,
200
+ stiffenerLayers,
201
+ adhesiveLayers
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Builds a compact source-evidence summary.
207
+ * @param {object[]} layers Layer rows.
208
+ * @param {object[]} topLevelBendLines Top-level bend cache rows.
209
+ * @param {object} cavityReport Cavity/stiffener report.
210
+ * @returns {object}
211
+ */
212
+ static sourceMap(layers, topLevelBendLines, cavityReport) {
213
+ return {
214
+ registryEntryCount: layers.filter((layer) => layer.registryRef)
215
+ .length,
216
+ sourceKeyCount: layers.reduce(
217
+ (count, layer) => count + (layer.sourceKeys?.length || 0),
218
+ 0
219
+ ),
220
+ topLevelBendLineCount: topLevelBendLines.length,
221
+ cavityRegionCount: cavityReport.cavityRegionCount,
222
+ stiffenerLayerCount: cavityReport.stiffenerLayerCount,
223
+ adhesiveLayerCount: cavityReport.adhesiveLayerCount,
224
+ surfaceFinishCount: layers.filter((layer) => layer.surfaceFinish)
225
+ .length
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Parses per-substack enablement fields from one layer row.
231
+ * @param {Record<string, string | string[]>} fields Source fields.
232
+ * @param {number} layerIndex Layer-stack index.
233
+ * @returns {object[]}
234
+ */
235
+ static #substackEnablement(fields, layerIndex) {
236
+ const pattern = new RegExp(
237
+ '^V9_STACK_LAYER' + layerIndex + '_SUBSTACK(\\d+)_ENABLED$',
238
+ 'iu'
239
+ )
240
+
241
+ return Object.keys(fields)
242
+ .flatMap((key) => {
243
+ const match = pattern.exec(key)
244
+ if (!match) return []
245
+
246
+ return [
247
+ {
248
+ substackIndex: Number.parseInt(match[1], 10),
249
+ enabled:
250
+ PcbLayerStackSourceMetadataParser.#optionalBoolean(
251
+ PcbLayerStackSourceMetadataParser.#field(
252
+ fields,
253
+ key
254
+ )
255
+ )
256
+ }
257
+ ]
258
+ })
259
+ .sort((left, right) => left.substackIndex - right.substackIndex)
260
+ }
261
+
262
+ /**
263
+ * Parses branch-section stack rows.
264
+ * @param {Record<string, string | string[]>} fields Board fields.
265
+ * @param {string} sectionPrefix Section field prefix.
266
+ * @returns {object[]}
267
+ */
268
+ static #branchSectionStacks(fields, sectionPrefix) {
269
+ const stackIndexes = PcbLayerStackSourceMetadataParser.#nestedIndexes(
270
+ fields,
271
+ sectionPrefix + '_STACK',
272
+ '_REF'
273
+ )
274
+
275
+ return stackIndexes.map((stackIndex) => {
276
+ const prefix = sectionPrefix + '_STACK' + stackIndex
277
+
278
+ return PcbLayerStackSourceMetadataParser.#stripUndefined({
279
+ index: stackIndex,
280
+ stackRef: PcbLayerStackSourceMetadataParser.#field(
281
+ fields,
282
+ prefix + '_REF'
283
+ ),
284
+ materialUsage: PcbLayerStackSourceMetadataParser.#field(
285
+ fields,
286
+ prefix + '_MATERIALUSAGE'
287
+ ),
288
+ source: PcbLayerStackSourceMetadataParser.#field(
289
+ fields,
290
+ prefix + '_SOURCE'
291
+ ),
292
+ parentLayerId: PcbLayerStackSourceMetadataParser.#field(
293
+ fields,
294
+ prefix + '_PARENTLAYERID'
295
+ ),
296
+ parentLayerStackId: PcbLayerStackSourceMetadataParser.#field(
297
+ fields,
298
+ prefix + '_PARENTLAYERSTACKID'
299
+ ),
300
+ sourceLayerId: PcbLayerStackSourceMetadataParser.#field(
301
+ fields,
302
+ prefix + '_SOURCELAYERID'
303
+ ),
304
+ sourceLayerStackId: PcbLayerStackSourceMetadataParser.#field(
305
+ fields,
306
+ prefix + '_SOURCELAYERSTACKID'
307
+ ),
308
+ intrusionLeftBottom: PcbLayerStackSourceMetadataParser.#field(
309
+ fields,
310
+ prefix + '_INTRUSIONLEFTBOTTOM'
311
+ ),
312
+ intrusionLeftTop: PcbLayerStackSourceMetadataParser.#field(
313
+ fields,
314
+ prefix + '_INTRUSIONLEFTTOP'
315
+ ),
316
+ intrusionRightBottom: PcbLayerStackSourceMetadataParser.#field(
317
+ fields,
318
+ prefix + '_INTRUSIONRIGHTBOTTOM'
319
+ ),
320
+ intrusionRightTop: PcbLayerStackSourceMetadataParser.#field(
321
+ fields,
322
+ prefix + '_INTRUSIONRIGHTTOP'
323
+ )
324
+ })
325
+ })
326
+ }
327
+
328
+ /**
329
+ * Finds all indexes matching any row-id pattern.
330
+ * @param {Record<string, string | string[]>} fields Source fields.
331
+ * @param {RegExp[]} patterns Index patterns.
332
+ * @returns {number[]}
333
+ */
334
+ static #indexedRows(fields, patterns) {
335
+ return [
336
+ ...new Set(
337
+ Object.keys(fields).flatMap((key) => {
338
+ for (const pattern of patterns) {
339
+ const match = pattern.exec(key)
340
+ if (match) return [Number.parseInt(match[1], 10)]
341
+ }
342
+ return []
343
+ })
344
+ )
345
+ ].sort((left, right) => left - right)
346
+ }
347
+
348
+ /**
349
+ * Finds nested indexes for fields with a common prefix and suffix.
350
+ * @param {Record<string, string | string[]>} fields Source fields.
351
+ * @param {string} prefix Field prefix before the nested index.
352
+ * @param {string} suffix Field suffix after the nested index.
353
+ * @returns {number[]}
354
+ */
355
+ static #nestedIndexes(fields, prefix, suffix) {
356
+ const pattern = new RegExp(
357
+ '^' +
358
+ prefix.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&') +
359
+ '(\\d+)' +
360
+ suffix.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&') +
361
+ '$',
362
+ 'iu'
363
+ )
364
+
365
+ return [
366
+ ...new Set(
367
+ Object.keys(fields).flatMap((key) => {
368
+ const match = pattern.exec(key)
369
+ return match ? [Number.parseInt(match[1], 10)] : []
370
+ })
371
+ )
372
+ ].sort((left, right) => left - right)
373
+ }
374
+
375
+ /**
376
+ * Reads the first matching indexed field.
377
+ * @param {Record<string, string | string[]>} fields Source fields.
378
+ * @param {string[]} prefixes Row prefixes.
379
+ * @param {number} index Row index.
380
+ * @param {string[]} suffixes Field suffixes.
381
+ * @returns {string}
382
+ */
383
+ static #indexedField(fields, prefixes, index, suffixes) {
384
+ for (const prefix of prefixes) {
385
+ for (const suffix of suffixes) {
386
+ const value = PcbLayerStackSourceMetadataParser.#field(
387
+ fields,
388
+ prefix + index + '_' + suffix
389
+ )
390
+ if (value) return value
391
+ }
392
+ }
393
+
394
+ return ''
395
+ }
396
+
397
+ /**
398
+ * Reads a case-insensitive field value.
399
+ * @param {Record<string, string | string[]>} fields Source fields.
400
+ * @param {string} key Field key.
401
+ * @returns {string}
402
+ */
403
+ static #field(fields, key) {
404
+ if (Object.hasOwn(fields, key)) {
405
+ return ParserUtils.getField(fields, key)
406
+ }
407
+ const upperKey = key.toUpperCase()
408
+ const realKey = Object.keys(fields).find(
409
+ (fieldKey) => fieldKey.toUpperCase() === upperKey
410
+ )
411
+
412
+ return realKey ? ParserUtils.getField(fields, realKey) : ''
413
+ }
414
+
415
+ /**
416
+ * Splits a native list field.
417
+ * @param {string} value Raw list value.
418
+ * @returns {string[]}
419
+ */
420
+ static #list(value) {
421
+ return String(value || '')
422
+ .split(/[;,]/u)
423
+ .map((item) => item.trim())
424
+ .filter(Boolean)
425
+ }
426
+
427
+ /**
428
+ * Parses a native key-value property bag.
429
+ * @param {string} value Raw value.
430
+ * @returns {object | undefined}
431
+ */
432
+ static #keyValueMap(value) {
433
+ const entries = String(value || '')
434
+ .split(/[|;]/u)
435
+ .map((item) => item.trim())
436
+ .filter(Boolean)
437
+ .flatMap((item) => {
438
+ const separator = item.indexOf('=')
439
+ if (separator < 0) return []
440
+ return [
441
+ [
442
+ item.slice(0, separator).trim(),
443
+ item.slice(separator + 1).trim()
444
+ ]
445
+ ]
446
+ })
447
+ .filter(([key]) => key)
448
+
449
+ return entries.length ? Object.fromEntries(entries) : undefined
450
+ }
451
+
452
+ /**
453
+ * Parses an optional boolean value.
454
+ * @param {string} value Raw value.
455
+ * @returns {boolean | undefined}
456
+ */
457
+ static #optionalBoolean(value) {
458
+ const normalized = String(value || '')
459
+ .trim()
460
+ .toLowerCase()
461
+ if (!normalized) return undefined
462
+ return ['true', 't', '1', 'yes'].includes(normalized)
463
+ }
464
+
465
+ /**
466
+ * Parses a numeric token.
467
+ * @param {string | undefined} value Raw token.
468
+ * @returns {number | undefined}
469
+ */
470
+ static #numberToken(value) {
471
+ const parsed = Number.parseFloat(String(value || '').trim())
472
+ return Number.isFinite(parsed) ? parsed : undefined
473
+ }
474
+
475
+ /**
476
+ * Removes undefined and empty string values while keeping false and empty
477
+ * arrays stable.
478
+ * @param {Record<string, unknown>} object Source object.
479
+ * @returns {object}
480
+ */
481
+ static #stripUndefined(object) {
482
+ return Object.fromEntries(
483
+ Object.entries(object).filter(
484
+ ([, value]) => value !== undefined && value !== ''
485
+ )
486
+ )
487
+ }
488
+ }
@@ -8,6 +8,7 @@ import { LibraryRenderManifestBuilder } from './LibraryRenderManifestBuilder.mjs
8
8
  import { PcbCustomPadShapeParser } from './PcbCustomPadShapeParser.mjs'
9
9
  import { PcbDefaultsParser } from './PcbDefaultsParser.mjs'
10
10
  import { PcbExtendedPrimitiveInformationParser } from './PcbExtendedPrimitiveInformationParser.mjs'
11
+ import { PcbLibParityReportBuilder } from './PcbLibParityReportBuilder.mjs'
11
12
  import { PcbMaskPasteResolver } from './PcbMaskPasteResolver.mjs'
12
13
 
13
14
  const { stripExtension } = ParserUtils
@@ -74,6 +75,7 @@ export class PcbLibModelParser {
74
75
  }
75
76
  pcbLibrary.renderManifest =
76
77
  LibraryRenderManifestBuilder.buildPcbLibraryManifest(pcbLibrary)
78
+ pcbLibrary.parityReport = PcbLibParityReportBuilder.build(pcbLibrary)
77
79
 
78
80
  return NormalizedModelSchema.attach({
79
81
  kind: 'pcb-library',