altium-toolkit 1.1.24 → 1.1.26

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 (35) hide show
  1. package/README.md +5 -3
  2. package/docs/api.md +43 -17
  3. package/docs/model-format.md +51 -11
  4. package/docs/schemas/altium_toolkit/library_compatibility_a1.schema.json +116 -0
  5. package/docs/schemas/altium_toolkit/library_qa_a1.schema.json +3 -0
  6. package/docs/schemas/altium_toolkit/unsupported_features_a1.schema.json +58 -1
  7. package/package.json +1 -1
  8. package/spec/library-compatibility.md +65 -0
  9. package/src/core/altium/AltiumParser.mjs +8 -1
  10. package/src/core/altium/LibraryCompatibilityGeometry.mjs +618 -0
  11. package/src/core/altium/LibraryCompatibilityModelHintBuilder.mjs +296 -0
  12. package/src/core/altium/LibraryCompatibilityReportBuilder.mjs +930 -0
  13. package/src/core/altium/LibraryQaReportBuilder.mjs +29 -1
  14. package/src/core/altium/PcbDimensionParser.mjs +1 -0
  15. package/src/core/altium/PcbDimensionReportBuilder.mjs +190 -0
  16. package/src/core/altium/PcbEmbeddedModelExtractor.mjs +140 -10
  17. package/src/core/altium/PcbFabricationReadinessReportBuilder.mjs +607 -0
  18. package/src/core/altium/PcbInspectionReportBuilder.mjs +56 -3
  19. package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +138 -3
  20. package/src/core/altium/PcbLibModelParser.mjs +24 -2
  21. package/src/core/altium/PcbLibStorageNameResolver.mjs +171 -0
  22. package/src/core/altium/PcbLibStreamExtractor.mjs +15 -93
  23. package/src/core/altium/PcbPrimitiveFieldOffsetReportBuilder.mjs +352 -0
  24. package/src/core/altium/PcbRuleImpactReportBuilder.mjs +313 -0
  25. package/src/core/altium/PcbShapeBasedBodyGeometryParser.mjs +514 -0
  26. package/src/core/altium/SchematicConnectivityQaBuilder.mjs +442 -2
  27. package/src/core/altium/SchematicImageDiagnosticsBuilder.mjs +214 -0
  28. package/src/core/altium/UnsupportedFeatureReportBuilder.mjs +523 -3
  29. package/src/parser.mjs +8 -0
  30. package/src/styles/altium-renderers.css +36 -0
  31. package/src/ui/PcbDimensionPrimitiveRenderer.mjs +422 -0
  32. package/src/ui/PcbScene3dBoardOutlineRefiner.mjs +6 -0
  33. package/src/ui/PcbScene3dBuilder.mjs +10 -2
  34. package/src/ui/PcbScene3dStaticBodyPlacementBuilder.mjs +225 -0
  35. package/src/ui/PcbSvgRenderer.mjs +78 -2
@@ -0,0 +1,607 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ /**
6
+ * Builds deterministic fabrication-readiness review items from PCB pads/vias.
7
+ */
8
+ export class PcbFabricationReadinessReportBuilder {
9
+ static SCHEMA = 'altium-toolkit.pcb.fabrication-readiness.a1'
10
+
11
+ /**
12
+ * Builds a fabrication-readiness report from a parser root or PCB model.
13
+ * @param {object} input Parser root, PCB model, or options object.
14
+ * @returns {object}
15
+ */
16
+ static build(input = {}) {
17
+ const pcb = PcbFabricationReadinessReportBuilder.#pcb(input)
18
+ const pads = Array.isArray(pcb?.pads) ? pcb.pads : []
19
+ const vias = Array.isArray(pcb?.vias) ? pcb.vias : []
20
+ const items = [
21
+ ...PcbFabricationReadinessReportBuilder.#padItems(pads),
22
+ ...PcbFabricationReadinessReportBuilder.#viaItems(vias)
23
+ ]
24
+
25
+ return {
26
+ schema: PcbFabricationReadinessReportBuilder.SCHEMA,
27
+ summary: PcbFabricationReadinessReportBuilder.#summary(
28
+ pads,
29
+ vias,
30
+ items
31
+ ),
32
+ items,
33
+ byCode: PcbFabricationReadinessReportBuilder.#byCode(items)
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Resolves the PCB payload from a parser root or direct PCB model.
39
+ * @param {object} input Parser root or PCB model.
40
+ * @returns {object}
41
+ */
42
+ static #pcb(input) {
43
+ return input?.pcb || input || {}
44
+ }
45
+
46
+ /**
47
+ * Builds fabrication review items for pads.
48
+ * @param {object[]} pads Normalized pad rows.
49
+ * @returns {object[]}
50
+ */
51
+ static #padItems(pads) {
52
+ return (pads || []).flatMap((pad, index) => {
53
+ const items = []
54
+
55
+ if (
56
+ PcbFabricationReadinessReportBuilder.#hasNonSimplePadMode(pad)
57
+ ) {
58
+ items.push(
59
+ PcbFabricationReadinessReportBuilder.#item(
60
+ 'pcb.fabrication.pad.non-simple-mode',
61
+ 'info',
62
+ 'pad',
63
+ index,
64
+ pad,
65
+ {
66
+ padMode: pad.padMode,
67
+ padModeName: pad.padModeName
68
+ }
69
+ )
70
+ )
71
+ }
72
+ if (PcbFabricationReadinessReportBuilder.#hasPadOffset(pad)) {
73
+ items.push(
74
+ PcbFabricationReadinessReportBuilder.#item(
75
+ 'pcb.fabrication.pad.offset-center',
76
+ 'warning',
77
+ 'pad',
78
+ index,
79
+ pad,
80
+ {
81
+ offsets:
82
+ PcbFabricationReadinessReportBuilder.#padOffsets(
83
+ pad
84
+ )
85
+ }
86
+ )
87
+ )
88
+ }
89
+ if (PcbFabricationReadinessReportBuilder.#hasSlotHole(pad)) {
90
+ items.push(
91
+ PcbFabricationReadinessReportBuilder.#item(
92
+ 'pcb.fabrication.pad.slotted-hole',
93
+ 'info',
94
+ 'pad',
95
+ index,
96
+ pad,
97
+ {
98
+ holeGeometry: pad.holeGeometry || null,
99
+ holeSlotLength: pad.holeSlotLength
100
+ }
101
+ )
102
+ )
103
+ }
104
+ if (PcbFabricationReadinessReportBuilder.#hasNonPlatedHole(pad)) {
105
+ items.push(
106
+ PcbFabricationReadinessReportBuilder.#item(
107
+ 'pcb.fabrication.pad.non-plated-hole',
108
+ 'warning',
109
+ 'pad',
110
+ index,
111
+ pad,
112
+ {}
113
+ )
114
+ )
115
+ }
116
+ if (
117
+ PcbFabricationReadinessReportBuilder.#hasMaskOverride(
118
+ pad,
119
+ 'paste'
120
+ )
121
+ ) {
122
+ items.push(
123
+ PcbFabricationReadinessReportBuilder.#item(
124
+ 'pcb.fabrication.pad.paste-mask-override',
125
+ 'info',
126
+ 'pad',
127
+ index,
128
+ pad,
129
+ {
130
+ expansion: pad.pasteMaskExpansion,
131
+ mode: pad.pasteMaskExpansionMode,
132
+ source: pad.pasteMaskExpansionSource
133
+ }
134
+ )
135
+ )
136
+ }
137
+ if (
138
+ PcbFabricationReadinessReportBuilder.#hasMaskOverride(
139
+ pad,
140
+ 'solder'
141
+ )
142
+ ) {
143
+ items.push(
144
+ PcbFabricationReadinessReportBuilder.#item(
145
+ 'pcb.fabrication.pad.solder-mask-override',
146
+ 'info',
147
+ 'pad',
148
+ index,
149
+ pad,
150
+ {
151
+ expansion: pad.solderMaskExpansion,
152
+ mode: pad.solderMaskExpansionMode,
153
+ source: pad.solderMaskExpansionSource
154
+ }
155
+ )
156
+ )
157
+ }
158
+ if (PcbFabricationReadinessReportBuilder.#hasThermalRelief(pad)) {
159
+ items.push(
160
+ PcbFabricationReadinessReportBuilder.#item(
161
+ 'pcb.fabrication.pad.thermal-relief',
162
+ 'info',
163
+ 'pad',
164
+ index,
165
+ pad,
166
+ PcbFabricationReadinessReportBuilder.#thermalDetails(
167
+ pad
168
+ )
169
+ )
170
+ )
171
+ }
172
+
173
+ return items
174
+ })
175
+ }
176
+
177
+ /**
178
+ * Builds fabrication review items for vias.
179
+ * @param {object[]} vias Normalized via rows.
180
+ * @returns {object[]}
181
+ */
182
+ static #viaItems(vias) {
183
+ return (vias || []).flatMap((via, index) => {
184
+ const items = []
185
+
186
+ if (PcbFabricationReadinessReportBuilder.#hasViaSpan(via)) {
187
+ items.push(
188
+ PcbFabricationReadinessReportBuilder.#item(
189
+ 'pcb.fabrication.via.layer-span',
190
+ 'warning',
191
+ 'via',
192
+ index,
193
+ via,
194
+ {
195
+ drillLayerPairType: via.drillLayerPairType,
196
+ diameterByLayer: via.diameterByLayer || [],
197
+ externalStackEntries: via.externalStackEntries || []
198
+ }
199
+ )
200
+ )
201
+ }
202
+ if (PcbFabricationReadinessReportBuilder.#hasViaProtection(via)) {
203
+ items.push(
204
+ PcbFabricationReadinessReportBuilder.#item(
205
+ 'pcb.fabrication.via.protected-hole',
206
+ 'info',
207
+ 'via',
208
+ index,
209
+ via,
210
+ {
211
+ ipc4761Type:
212
+ via.ipc4761Type ??
213
+ via.viaProtection?.ipc4761Type,
214
+ structureType: via.viaProtection?.structureType
215
+ }
216
+ )
217
+ )
218
+ }
219
+ if (
220
+ PcbFabricationReadinessReportBuilder.#hasMaskOverride(
221
+ via,
222
+ 'solder'
223
+ )
224
+ ) {
225
+ items.push(
226
+ PcbFabricationReadinessReportBuilder.#item(
227
+ 'pcb.fabrication.via.solder-mask-override',
228
+ 'info',
229
+ 'via',
230
+ index,
231
+ via,
232
+ {
233
+ expansion: via.solderMaskExpansion,
234
+ mode: via.solderMaskExpansionMode,
235
+ fromHoleEdge: via.solderMaskExpansionFromHoleEdge
236
+ }
237
+ )
238
+ )
239
+ }
240
+ if (PcbFabricationReadinessReportBuilder.#hasThermalRelief(via)) {
241
+ items.push(
242
+ PcbFabricationReadinessReportBuilder.#item(
243
+ 'pcb.fabrication.via.thermal-relief',
244
+ 'info',
245
+ 'via',
246
+ index,
247
+ via,
248
+ PcbFabricationReadinessReportBuilder.#thermalDetails(
249
+ via
250
+ )
251
+ )
252
+ )
253
+ }
254
+ if (PcbFabricationReadinessReportBuilder.#isMicroviaLike(via)) {
255
+ items.push(
256
+ PcbFabricationReadinessReportBuilder.#item(
257
+ 'pcb.fabrication.via.microvia-like',
258
+ 'warning',
259
+ 'via',
260
+ index,
261
+ via,
262
+ {
263
+ diameter: via.diameter,
264
+ holeDiameter: via.holeDiameter,
265
+ drillLayerPairType: via.drillLayerPairType
266
+ }
267
+ )
268
+ )
269
+ }
270
+
271
+ return items
272
+ })
273
+ }
274
+
275
+ /**
276
+ * Builds the deterministic report summary.
277
+ * @param {object[]} pads Normalized pad rows.
278
+ * @param {object[]} vias Normalized via rows.
279
+ * @param {object[]} items Review items.
280
+ * @returns {object}
281
+ */
282
+ static #summary(pads, vias, items) {
283
+ return {
284
+ padCount: pads.length,
285
+ viaCount: vias.length,
286
+ reviewItemCount: items.length,
287
+ nonSimplePadModeCount:
288
+ PcbFabricationReadinessReportBuilder.#countCode(
289
+ items,
290
+ 'pcb.fabrication.pad.non-simple-mode'
291
+ ),
292
+ offsetPadCount: PcbFabricationReadinessReportBuilder.#countCode(
293
+ items,
294
+ 'pcb.fabrication.pad.offset-center'
295
+ ),
296
+ slottedHoleCount: PcbFabricationReadinessReportBuilder.#countCode(
297
+ items,
298
+ 'pcb.fabrication.pad.slotted-hole'
299
+ ),
300
+ nonPlatedHoleCount: PcbFabricationReadinessReportBuilder.#countCode(
301
+ items,
302
+ 'pcb.fabrication.pad.non-plated-hole'
303
+ ),
304
+ pasteOverrideCount:
305
+ PcbFabricationReadinessReportBuilder.#countSuffix(
306
+ items,
307
+ '.paste-mask-override'
308
+ ),
309
+ maskOverrideCount:
310
+ PcbFabricationReadinessReportBuilder.#countSuffix(
311
+ items,
312
+ '.solder-mask-override'
313
+ ),
314
+ thermalReliefCount:
315
+ PcbFabricationReadinessReportBuilder.#countSuffix(
316
+ items,
317
+ '.thermal-relief'
318
+ ),
319
+ viaSpanCount: PcbFabricationReadinessReportBuilder.#countCode(
320
+ items,
321
+ 'pcb.fabrication.via.layer-span'
322
+ ),
323
+ protectedViaCount: PcbFabricationReadinessReportBuilder.#countCode(
324
+ items,
325
+ 'pcb.fabrication.via.protected-hole'
326
+ ),
327
+ microviaLikeCount: PcbFabricationReadinessReportBuilder.#countCode(
328
+ items,
329
+ 'pcb.fabrication.via.microvia-like'
330
+ )
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Builds one normalized review item.
336
+ * @param {string} code Stable issue code.
337
+ * @param {'info' | 'warning'} severity Issue severity.
338
+ * @param {'pad' | 'via'} ownerKind Primitive kind.
339
+ * @param {number} index Primitive index.
340
+ * @param {object} owner Primitive row.
341
+ * @param {object} details Additional issue details.
342
+ * @returns {object}
343
+ */
344
+ static #item(code, severity, ownerKind, index, owner, details) {
345
+ return PcbFabricationReadinessReportBuilder.#stripEmpty({
346
+ code,
347
+ severity,
348
+ ownerKind,
349
+ ownerKey: ownerKind + '-' + index,
350
+ index,
351
+ designator: owner?.designator,
352
+ netName: owner?.netName,
353
+ layerId: owner?.layerId,
354
+ ...details
355
+ })
356
+ }
357
+
358
+ /**
359
+ * Checks whether a pad uses a non-simple local stack mode.
360
+ * @param {object} pad Pad row.
361
+ * @returns {boolean}
362
+ */
363
+ static #hasNonSimplePadMode(pad) {
364
+ const modeName = String(pad?.padModeName || '').trim()
365
+ if (modeName) {
366
+ return modeName !== 'simple'
367
+ }
368
+
369
+ const mode = Number(pad?.padMode)
370
+ return Number.isFinite(mode) && mode !== 0
371
+ }
372
+
373
+ /**
374
+ * Checks whether a pad has non-zero per-layer center offsets.
375
+ * @param {object} pad Pad row.
376
+ * @returns {boolean}
377
+ */
378
+ static #hasPadOffset(pad) {
379
+ return PcbFabricationReadinessReportBuilder.#padOffsets(pad).length > 0
380
+ }
381
+
382
+ /**
383
+ * Collects non-zero pad offsets from local-stack or extension metadata.
384
+ * @param {object} pad Pad row.
385
+ * @returns {object[]}
386
+ */
387
+ static #padOffsets(pad) {
388
+ const offsets = []
389
+
390
+ for (const layer of pad?.localStack?.layers || []) {
391
+ const offsetX = Number(layer?.offsetX || 0)
392
+ const offsetY = Number(layer?.offsetY || 0)
393
+ if (offsetX || offsetY) {
394
+ offsets.push(
395
+ PcbFabricationReadinessReportBuilder.#stripEmpty({
396
+ layerKey: layer.layerKey,
397
+ role: layer.role,
398
+ offsetX,
399
+ offsetY
400
+ })
401
+ )
402
+ }
403
+ }
404
+ for (const offset of pad?.layerOffsets || []) {
405
+ const offsetX = Number(offset?.x || offset?.offsetX || 0)
406
+ const offsetY = Number(offset?.y || offset?.offsetY || 0)
407
+ if (offsetX || offsetY) {
408
+ offsets.push(
409
+ PcbFabricationReadinessReportBuilder.#stripEmpty({
410
+ layerNumber: offset.layerNumber,
411
+ layerKey: offset.layerKey,
412
+ offsetX,
413
+ offsetY
414
+ })
415
+ )
416
+ }
417
+ }
418
+ for (const keyPair of [
419
+ ['offsetTopX', 'offsetTopY'],
420
+ ['offsetMidX', 'offsetMidY'],
421
+ ['offsetBottomX', 'offsetBottomY']
422
+ ]) {
423
+ const offsetX = Number(pad?.[keyPair[0]] || 0)
424
+ const offsetY = Number(pad?.[keyPair[1]] || 0)
425
+ if (offsetX || offsetY) {
426
+ offsets.push({
427
+ field: keyPair[0].replace(/X$/u, ''),
428
+ offsetX,
429
+ offsetY
430
+ })
431
+ }
432
+ }
433
+
434
+ return offsets
435
+ }
436
+
437
+ /**
438
+ * Checks whether a pad has slot-hole geometry.
439
+ * @param {object} pad Pad row.
440
+ * @returns {boolean}
441
+ */
442
+ static #hasSlotHole(pad) {
443
+ return (
444
+ pad?.holeGeometry?.shapeName === 'slot' ||
445
+ pad?.holeShapeName === 'slot' ||
446
+ Number(pad?.holeShape) === 2 ||
447
+ Number(pad?.holeSlotLength || 0) > Number(pad?.holeDiameter || 0)
448
+ )
449
+ }
450
+
451
+ /**
452
+ * Checks whether a pad has a non-plated drilled hole.
453
+ * @param {object} pad Pad row.
454
+ * @returns {boolean}
455
+ */
456
+ static #hasNonPlatedHole(pad) {
457
+ const hasHole =
458
+ Number(pad?.holeDiameter || 0) > 0 ||
459
+ PcbFabricationReadinessReportBuilder.#hasSlotHole(pad)
460
+
461
+ return hasHole && pad?.isPlated === false
462
+ }
463
+
464
+ /**
465
+ * Checks whether a primitive has a manual mask expansion override.
466
+ * @param {object} primitive Primitive row.
467
+ * @param {'paste' | 'solder'} kind Mask kind.
468
+ * @returns {boolean}
469
+ */
470
+ static #hasMaskOverride(primitive, kind) {
471
+ const prefix = kind === 'paste' ? 'paste' : 'solder'
472
+ const source = String(
473
+ primitive?.[prefix + 'MaskExpansionSource'] || ''
474
+ ).trim()
475
+ const mode = Number(primitive?.[prefix + 'MaskExpansionMode'])
476
+ const expansion = Number(primitive?.[prefix + 'MaskExpansion'])
477
+
478
+ return (
479
+ source === 'manual' ||
480
+ mode === 2 ||
481
+ (!Number.isNaN(expansion) && expansion !== 0 && !source)
482
+ )
483
+ }
484
+
485
+ /**
486
+ * Checks whether a pad or via has thermal-relief metadata.
487
+ * @param {object} primitive Primitive row.
488
+ * @returns {boolean}
489
+ */
490
+ static #hasThermalRelief(primitive) {
491
+ return [
492
+ 'thermalReliefAirGap',
493
+ 'thermalReliefConductorWidth',
494
+ 'thermalReliefConductorCount',
495
+ 'powerPlaneReliefExpansion'
496
+ ].some((key) => Number(primitive?.[key] || 0) !== 0)
497
+ }
498
+
499
+ /**
500
+ * Builds compact thermal-relief details.
501
+ * @param {object} primitive Primitive row.
502
+ * @returns {object}
503
+ */
504
+ static #thermalDetails(primitive) {
505
+ return PcbFabricationReadinessReportBuilder.#stripEmpty({
506
+ planeConnectionStyle: primitive?.planeConnectionStyle,
507
+ thermalReliefAirGap: primitive?.thermalReliefAirGap,
508
+ thermalReliefConductorWidth: primitive?.thermalReliefConductorWidth,
509
+ thermalReliefConductorCount: primitive?.thermalReliefConductorCount,
510
+ powerPlaneReliefExpansion: primitive?.powerPlaneReliefExpansion
511
+ })
512
+ }
513
+
514
+ /**
515
+ * Checks whether a via carries non-default layer-span metadata.
516
+ * @param {object} via Via row.
517
+ * @returns {boolean}
518
+ */
519
+ static #hasViaSpan(via) {
520
+ return (
521
+ Number(via?.drillLayerPairType || 0) !== 0 ||
522
+ (Array.isArray(via?.diameterByLayer) &&
523
+ via.diameterByLayer.length > 0) ||
524
+ (Array.isArray(via?.externalStackEntries) &&
525
+ via.externalStackEntries.length > 0)
526
+ )
527
+ }
528
+
529
+ /**
530
+ * Checks whether a via has protection or filled/capped metadata.
531
+ * @param {object} via Via row.
532
+ * @returns {boolean}
533
+ */
534
+ static #hasViaProtection(via) {
535
+ return (
536
+ via?.viaProtection !== undefined ||
537
+ via?.ipc4761Type !== undefined ||
538
+ via?.drill?.renderState === 'covered' ||
539
+ via?.drill?.renderState === 'filled' ||
540
+ via?.drill?.renderState === 'capped'
541
+ )
542
+ }
543
+
544
+ /**
545
+ * Checks whether via geometry resembles a small layer-span microvia.
546
+ * @param {object} via Via row.
547
+ * @returns {boolean}
548
+ */
549
+ static #isMicroviaLike(via) {
550
+ const holeDiameter = Number(via?.holeDiameter || 0)
551
+
552
+ return (
553
+ holeDiameter > 0 &&
554
+ holeDiameter <= 6 &&
555
+ PcbFabricationReadinessReportBuilder.#hasViaSpan(via)
556
+ )
557
+ }
558
+
559
+ /**
560
+ * Counts items with one exact issue code.
561
+ * @param {object[]} items Review items.
562
+ * @param {string} code Issue code.
563
+ * @returns {number}
564
+ */
565
+ static #countCode(items, code) {
566
+ return items.filter((item) => item.code === code).length
567
+ }
568
+
569
+ /**
570
+ * Counts items whose code ends with a suffix.
571
+ * @param {object[]} items Review items.
572
+ * @param {string} suffix Issue-code suffix.
573
+ * @returns {number}
574
+ */
575
+ static #countSuffix(items, suffix) {
576
+ return items.filter((item) => item.code.endsWith(suffix)).length
577
+ }
578
+
579
+ /**
580
+ * Builds code counters for report consumers.
581
+ * @param {object[]} items Review items.
582
+ * @returns {object[]}
583
+ */
584
+ static #byCode(items) {
585
+ const counts = new Map()
586
+
587
+ for (const item of items) {
588
+ counts.set(item.code, Number(counts.get(item.code) || 0) + 1)
589
+ }
590
+
591
+ return [...counts.entries()].map(([code, count]) => ({ code, count }))
592
+ }
593
+
594
+ /**
595
+ * Removes empty fields while preserving zeros and false.
596
+ * @param {Record<string, unknown>} row Input row.
597
+ * @returns {Record<string, unknown>}
598
+ */
599
+ static #stripEmpty(row) {
600
+ return Object.fromEntries(
601
+ Object.entries(row || {}).filter(([, value]) => {
602
+ if (Array.isArray(value)) return value.length > 0
603
+ return value !== undefined && value !== null && value !== ''
604
+ })
605
+ )
606
+ }
607
+ }
@@ -3,8 +3,12 @@
3
3
  // SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
5
  import { PcbClassReportBuilder } from './PcbClassReportBuilder.mjs'
6
+ import { PcbDimensionReportBuilder } from './PcbDimensionReportBuilder.mjs'
7
+ import { PcbFabricationReadinessReportBuilder } from './PcbFabricationReadinessReportBuilder.mjs'
6
8
  import { PcbNetMembershipReportBuilder } from './PcbNetMembershipReportBuilder.mjs'
9
+ import { PcbReviewMetadataBuilder } from './PcbReviewMetadataBuilder.mjs'
7
10
  import { PcbRouteAnalysisBuilder } from './PcbRouteAnalysisBuilder.mjs'
11
+ import { PcbRuleImpactReportBuilder } from './PcbRuleImpactReportBuilder.mjs'
8
12
  import { PcbStatisticsBuilder } from './PcbStatisticsBuilder.mjs'
9
13
 
10
14
  /**
@@ -37,6 +41,25 @@ export class PcbInspectionReportBuilder {
37
41
  input.classReport ||
38
42
  pcb.classReport ||
39
43
  PcbClassReportBuilder.build(pcb)
44
+ const dimensions =
45
+ input.dimensionReport ||
46
+ pcb.dimensionReport ||
47
+ PcbDimensionReportBuilder.build(pcb)
48
+ const ruleImpact =
49
+ input.ruleImpact ||
50
+ pcb.ruleImpact ||
51
+ PcbRuleImpactReportBuilder.build(pcb)
52
+ const reviewMetadata =
53
+ input.reviewMetadata ||
54
+ pcb.reviewMetadata ||
55
+ PcbReviewMetadataBuilder.build({
56
+ ...pcb,
57
+ routeAnalysis
58
+ })
59
+ const fabricationReadiness =
60
+ input.fabricationReadiness ||
61
+ pcb.fabricationReadiness ||
62
+ PcbFabricationReadinessReportBuilder.build(pcb)
40
63
  const rules = PcbInspectionReportBuilder.#rules(pcb?.rules || [])
41
64
  const primitives = PcbInspectionReportBuilder.#primitives(pcb)
42
65
  const diagnosticSummary =
@@ -49,7 +72,11 @@ export class PcbInspectionReportBuilder {
49
72
  rules,
50
73
  diagnosticSummary,
51
74
  netMembership,
52
- classes
75
+ classes,
76
+ dimensions,
77
+ ruleImpact,
78
+ reviewMetadata,
79
+ fabricationReadiness
53
80
  })
54
81
 
55
82
  return {
@@ -72,6 +99,22 @@ export class PcbInspectionReportBuilder {
72
99
  },
73
100
  netMembership,
74
101
  classes,
102
+ dimensions: {
103
+ schema: dimensions.schema,
104
+ summary: dimensions.summary || {}
105
+ },
106
+ ruleImpact: {
107
+ schema: ruleImpact.schema,
108
+ summary: ruleImpact.summary || {}
109
+ },
110
+ reviewMetadata: {
111
+ schema: reviewMetadata.schema,
112
+ summary: reviewMetadata.summary || {}
113
+ },
114
+ fabricationReadiness: {
115
+ schema: fabricationReadiness.schema,
116
+ summary: fabricationReadiness.summary || {}
117
+ },
75
118
  routeAnalysis: {
76
119
  schema: routeAnalysis.schema,
77
120
  summary: routeAnalysis.summary || {}
@@ -261,7 +304,9 @@ export class PcbInspectionReportBuilder {
261
304
  parts.diagnosticSummary.warningCount +
262
305
  Number(parts.netMembership.summary.undeclaredNetCount || 0) +
263
306
  Number(parts.netMembership.summary.unownedPrimitiveCount || 0) +
264
- Number(parts.classes.summary.unresolvedMemberCount || 0)
307
+ Number(parts.classes.summary.unresolvedMemberCount || 0) +
308
+ Number(parts.dimensions.summary.unresolvedCount || 0) +
309
+ Number(parts.fabricationReadiness.summary.reviewItemCount || 0)
265
310
 
266
311
  return {
267
312
  fileName: parts.input.fileName || '',
@@ -282,7 +327,15 @@ export class PcbInspectionReportBuilder {
282
327
  warningCount: parts.diagnosticSummary.warningCount,
283
328
  possibleUnroutedNetCount:
284
329
  parts.netMembership.summary.possibleUnroutedNetCount || 0,
285
- classIssueCount: parts.classes.summary.issueCount || 0
330
+ classIssueCount: parts.classes.summary.issueCount || 0,
331
+ dimensionCount: parts.dimensions.summary.dimensionCount || 0,
332
+ dimensionIssueCount: parts.dimensions.summary.unresolvedCount || 0,
333
+ manufacturingRuleCount:
334
+ parts.ruleImpact.summary.manufacturingRuleCount || 0,
335
+ polygonRealizationCount:
336
+ parts.reviewMetadata.summary.polygonRealizationCount || 0,
337
+ fabricationReviewItemCount:
338
+ parts.fabricationReadiness.summary.reviewItemCount || 0
286
339
  }
287
340
  }
288
341