altium-toolkit 1.1.23 → 1.1.25

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.
@@ -10,7 +10,7 @@ export class UnsupportedFeatureReportBuilder {
10
10
 
11
11
  /**
12
12
  * Builds an unsupported feature report.
13
- * @param {{ models?: object[], recordTypes?: object[], rawRecords?: object[], opaqueRecords?: object[], diagnostics?: object[] }} [input] Report input.
13
+ * @param {{ models?: object[], recordTypes?: object[], rawRecords?: object[], opaqueRecords?: object[], diagnostics?: object[], edgeCases?: object[] }} [input] Report input.
14
14
  * @returns {object}
15
15
  */
16
16
  static build(input = {}) {
@@ -32,11 +32,16 @@ export class UnsupportedFeatureReportBuilder {
32
32
  input,
33
33
  models
34
34
  )
35
+ const edgeCases = UnsupportedFeatureReportBuilder.#edgeCases(
36
+ input,
37
+ models
38
+ )
35
39
  const itemCount =
36
40
  recordTypes.length +
37
41
  rawRecords.length +
38
42
  opaqueRecords.length +
39
- diagnostics.length
43
+ diagnostics.length +
44
+ edgeCases.length
40
45
 
41
46
  return {
42
47
  schema: UnsupportedFeatureReportBuilder.SCHEMA,
@@ -46,13 +51,15 @@ export class UnsupportedFeatureReportBuilder {
46
51
  rawRecordCount: rawRecords.length,
47
52
  opaqueRecordCount: opaqueRecords.length,
48
53
  diagnosticCount: diagnostics.length,
54
+ edgeCaseCount: edgeCases.length,
49
55
  itemCount,
50
56
  status: itemCount ? 'unsupported' : 'supported'
51
57
  },
52
58
  recordTypes,
53
59
  rawRecords,
54
60
  opaqueRecords,
55
- diagnostics
61
+ diagnostics,
62
+ edgeCases
56
63
  }
57
64
  }
58
65
 
@@ -344,6 +351,506 @@ export class UnsupportedFeatureReportBuilder {
344
351
  )
345
352
  }
346
353
 
354
+ /**
355
+ * Collects parser edge-case coverage rows.
356
+ * @param {object} input Report input.
357
+ * @param {object[]} models Parser roots.
358
+ * @returns {object[]}
359
+ */
360
+ static #edgeCases(input, models) {
361
+ return UnsupportedFeatureReportBuilder.#dedupeEdgeCases(
362
+ [
363
+ ...(Array.isArray(input?.edgeCases)
364
+ ? input.edgeCases.map((edgeCase) => ({
365
+ edgeCase: edgeCase || {},
366
+ fileName: edgeCase?.fileName || '',
367
+ domain: edgeCase?.domain || ''
368
+ }))
369
+ : []),
370
+ ...models.flatMap((model) =>
371
+ (model?.diagnostics || [])
372
+ .filter((diagnostic) =>
373
+ UnsupportedFeatureReportBuilder.#isEdgeCaseDiagnostic(
374
+ diagnostic
375
+ )
376
+ )
377
+ .map((diagnostic) => ({
378
+ edgeCase: diagnostic,
379
+ fileName:
380
+ model.fileName || diagnostic.fileName || '',
381
+ domain: diagnostic.domain || ''
382
+ }))
383
+ ),
384
+ ...models.flatMap((model) =>
385
+ UnsupportedFeatureReportBuilder.#modelDerivedEdgeCases(
386
+ model
387
+ )
388
+ )
389
+ ].map(({ edgeCase, fileName, domain }) =>
390
+ UnsupportedFeatureReportBuilder.#stripUndefined({
391
+ fileName,
392
+ domain,
393
+ code: edgeCase.code,
394
+ feature: edgeCase.feature,
395
+ supportState: edgeCase.supportState,
396
+ severity: edgeCase.severity,
397
+ message: edgeCase.message,
398
+ source: edgeCase.source,
399
+ sourceStream: edgeCase.sourceStream,
400
+ sourceStorage: edgeCase.sourceStorage,
401
+ recordIndex:
402
+ UnsupportedFeatureReportBuilder.#finiteOrUndefined(
403
+ edgeCase.recordIndex
404
+ ),
405
+ recordType:
406
+ UnsupportedFeatureReportBuilder.#finiteOrUndefined(
407
+ edgeCase.recordType
408
+ ),
409
+ errorKind: edgeCase.errorKind
410
+ })
411
+ )
412
+ )
413
+ }
414
+
415
+ /**
416
+ * Collects compatibility edge cases derivable from parsed model data.
417
+ * @param {object} model Parser root.
418
+ * @returns {{ edgeCase: object, fileName: string, domain: string }[]}
419
+ */
420
+ static #modelDerivedEdgeCases(model) {
421
+ const fileName = String(model?.fileName || '')
422
+ const pcb = model?.pcb || {}
423
+
424
+ return [
425
+ ...UnsupportedFeatureReportBuilder.#padEdgeCases(
426
+ pcb.pads,
427
+ fileName
428
+ ),
429
+ ...UnsupportedFeatureReportBuilder.#arcEdgeCases(
430
+ pcb.arcs,
431
+ fileName
432
+ ),
433
+ ...UnsupportedFeatureReportBuilder.#regionEdgeCases(
434
+ pcb.regions,
435
+ fileName
436
+ ),
437
+ ...UnsupportedFeatureReportBuilder.#componentBodyEdgeCases(
438
+ pcb.componentBodies,
439
+ fileName
440
+ ),
441
+ ...UnsupportedFeatureReportBuilder.#embeddedModelIntegrityEdgeCases(
442
+ pcb.embeddedModelIntegrity?.issues,
443
+ fileName
444
+ )
445
+ ]
446
+ }
447
+
448
+ /**
449
+ * Collects pad compatibility rows.
450
+ * @param {object[] | undefined} pads Pads.
451
+ * @param {string} fileName Source file.
452
+ * @returns {{ edgeCase: object, fileName: string, domain: string }[]}
453
+ */
454
+ static #padEdgeCases(pads, fileName) {
455
+ return (Array.isArray(pads) ? pads : []).flatMap((pad) => {
456
+ const rows = []
457
+
458
+ if (UnsupportedFeatureReportBuilder.#hasOctagonalPadShape(pad)) {
459
+ rows.push(
460
+ UnsupportedFeatureReportBuilder.#pcbEdgeCase(fileName, {
461
+ code: 'pcb.pad.octagonal',
462
+ feature: 'octagonal-pad',
463
+ supportState: 'inspection-required',
464
+ severity: 'warning',
465
+ message:
466
+ 'Pad uses an octagonal shape that should be inspected in downstream views.',
467
+ ...UnsupportedFeatureReportBuilder.#recordLocation(pad)
468
+ })
469
+ )
470
+ }
471
+
472
+ if (Number(pad?.padMode) > 0) {
473
+ rows.push(
474
+ UnsupportedFeatureReportBuilder.#pcbEdgeCase(fileName, {
475
+ code: 'pcb.pad.non-simple-stack',
476
+ feature: 'non-simple-pad-stack',
477
+ supportState: 'inspection-required',
478
+ severity: 'warning',
479
+ message:
480
+ 'Pad uses layer-specific pad-stack mode metadata.',
481
+ ...UnsupportedFeatureReportBuilder.#recordLocation(pad)
482
+ })
483
+ )
484
+ }
485
+
486
+ if (
487
+ UnsupportedFeatureReportBuilder.#hasLayerSpecificPadGeometry(
488
+ pad
489
+ )
490
+ ) {
491
+ rows.push(
492
+ UnsupportedFeatureReportBuilder.#pcbEdgeCase(fileName, {
493
+ code: 'pcb.pad.layer-specific-geometry',
494
+ feature: 'layer-specific-pad-geometry',
495
+ supportState: 'preserved',
496
+ severity: 'info',
497
+ message:
498
+ 'Pad has different top, middle, or bottom geometry.',
499
+ ...UnsupportedFeatureReportBuilder.#recordLocation(pad)
500
+ })
501
+ )
502
+ }
503
+
504
+ if (UnsupportedFeatureReportBuilder.#hasSlotHole(pad)) {
505
+ rows.push(
506
+ UnsupportedFeatureReportBuilder.#pcbEdgeCase(fileName, {
507
+ code: 'pcb.pad.slot-hole',
508
+ feature: 'slot-hole',
509
+ supportState: 'preserved',
510
+ severity: 'info',
511
+ message: 'Pad uses slot-hole geometry.',
512
+ ...UnsupportedFeatureReportBuilder.#recordLocation(pad)
513
+ })
514
+ )
515
+ }
516
+
517
+ if (UnsupportedFeatureReportBuilder.#hasManualMaskPaste(pad)) {
518
+ rows.push(
519
+ UnsupportedFeatureReportBuilder.#pcbEdgeCase(fileName, {
520
+ code: 'pcb.pad.manual-mask-paste',
521
+ feature: 'manual-mask-paste-expansion',
522
+ supportState: 'inspection-required',
523
+ severity: 'warning',
524
+ message:
525
+ 'Pad carries manual solder-mask or paste-mask expansion metadata.',
526
+ ...UnsupportedFeatureReportBuilder.#recordLocation(pad)
527
+ })
528
+ )
529
+ }
530
+
531
+ if (
532
+ UnsupportedFeatureReportBuilder.#hasMissingCustomShapeGeometry(
533
+ pad
534
+ )
535
+ ) {
536
+ rows.push(
537
+ UnsupportedFeatureReportBuilder.#pcbEdgeCase(fileName, {
538
+ code: 'pcb.custom-shape.missing-linked-geometry',
539
+ feature: 'custom-pad-shape-linkage',
540
+ supportState: 'inspection-required',
541
+ severity: 'warning',
542
+ message:
543
+ 'Custom pad shape sidecar has no linked primitive geometry.',
544
+ ...UnsupportedFeatureReportBuilder.#recordLocation(pad)
545
+ })
546
+ )
547
+ }
548
+
549
+ return rows
550
+ })
551
+ }
552
+
553
+ /**
554
+ * Collects arc compatibility rows.
555
+ * @param {object[] | undefined} arcs Arcs.
556
+ * @param {string} fileName Source file.
557
+ * @returns {{ edgeCase: object, fileName: string, domain: string }[]}
558
+ */
559
+ static #arcEdgeCases(arcs, fileName) {
560
+ return (Array.isArray(arcs) ? arcs : [])
561
+ .filter((arc) => {
562
+ const radius = Math.abs(Number(arc?.radius))
563
+ const width = Math.abs(Number(arc?.width))
564
+ return Number.isFinite(radius) && radius > 0 && width >= radius
565
+ })
566
+ .map((arc) =>
567
+ UnsupportedFeatureReportBuilder.#pcbEdgeCase(fileName, {
568
+ code: 'pcb.arc.width-radius-conflict',
569
+ feature: 'wide-arc',
570
+ supportState: 'inspection-required',
571
+ severity: 'warning',
572
+ message:
573
+ 'Arc stroke width is greater than or equal to its radius.',
574
+ ...UnsupportedFeatureReportBuilder.#recordLocation(arc)
575
+ })
576
+ )
577
+ }
578
+
579
+ /**
580
+ * Collects region compatibility rows.
581
+ * @param {object[] | undefined} regions Regions.
582
+ * @param {string} fileName Source file.
583
+ * @returns {{ edgeCase: object, fileName: string, domain: string }[]}
584
+ */
585
+ static #regionEdgeCases(regions, fileName) {
586
+ return (Array.isArray(regions) ? regions : [])
587
+ .filter(
588
+ (region) =>
589
+ Number(region?.holeCount || region?.HOLECOUNT || 0) > 0
590
+ )
591
+ .map((region) =>
592
+ UnsupportedFeatureReportBuilder.#pcbEdgeCase(fileName, {
593
+ code: 'pcb.region.holes',
594
+ feature: 'region-with-holes',
595
+ supportState: 'preserved',
596
+ severity: 'info',
597
+ message: 'Region contains one or more holes.',
598
+ ...UnsupportedFeatureReportBuilder.#recordLocation(region)
599
+ })
600
+ )
601
+ }
602
+
603
+ /**
604
+ * Collects component-body compatibility rows.
605
+ * @param {object[] | undefined} componentBodies Component bodies.
606
+ * @param {string} fileName Source file.
607
+ * @returns {{ edgeCase: object, fileName: string, domain: string }[]}
608
+ */
609
+ static #componentBodyEdgeCases(componentBodies, fileName) {
610
+ return (Array.isArray(componentBodies) ? componentBodies : [])
611
+ .filter((componentBody) =>
612
+ UnsupportedFeatureReportBuilder.#missingStaticBodyGeometry(
613
+ componentBody
614
+ )
615
+ )
616
+ .map((componentBody) =>
617
+ UnsupportedFeatureReportBuilder.#pcbEdgeCase(fileName, {
618
+ code: 'pcb.model.shape-body-static-geometry-missing',
619
+ feature: 'shape-based-3d-body',
620
+ supportState: 'inspection-required',
621
+ severity: 'warning',
622
+ message:
623
+ 'Shape-based 3D body has no complete static geometry description.',
624
+ ...UnsupportedFeatureReportBuilder.#recordLocation(
625
+ componentBody
626
+ )
627
+ })
628
+ )
629
+ }
630
+
631
+ /**
632
+ * Collects embedded-model integrity rows.
633
+ * @param {object[] | undefined} issues Integrity issues.
634
+ * @param {string} fileName Source file.
635
+ * @returns {{ edgeCase: object, fileName: string, domain: string }[]}
636
+ */
637
+ static #embeddedModelIntegrityEdgeCases(issues, fileName) {
638
+ return (Array.isArray(issues) ? issues : []).map((issue) =>
639
+ UnsupportedFeatureReportBuilder.#pcbEdgeCase(fileName, {
640
+ code: issue.code,
641
+ feature: 'embedded-model-integrity',
642
+ supportState: 'diagnostic',
643
+ severity: issue.severity || 'warning',
644
+ message: issue.message,
645
+ ...UnsupportedFeatureReportBuilder.#recordLocation(issue)
646
+ })
647
+ )
648
+ }
649
+
650
+ /**
651
+ * Wraps one PCB edge-case row.
652
+ * @param {string} fileName Source file.
653
+ * @param {object} edgeCase Edge-case row.
654
+ * @returns {{ edgeCase: object, fileName: string, domain: string }}
655
+ */
656
+ static #pcbEdgeCase(fileName, edgeCase) {
657
+ return {
658
+ edgeCase,
659
+ fileName,
660
+ domain: 'pcb'
661
+ }
662
+ }
663
+
664
+ /**
665
+ * Extracts common record location fields.
666
+ * @param {object} row Source row.
667
+ * @returns {object}
668
+ */
669
+ static #recordLocation(row) {
670
+ return UnsupportedFeatureReportBuilder.#stripUndefined({
671
+ source: row?.source,
672
+ sourceStream: row?.sourceStream,
673
+ sourceStorage: row?.sourceStorage,
674
+ recordIndex: UnsupportedFeatureReportBuilder.#finiteOrUndefined(
675
+ row?.recordIndex
676
+ ),
677
+ recordType: UnsupportedFeatureReportBuilder.#finiteOrUndefined(
678
+ row?.recordType
679
+ )
680
+ })
681
+ }
682
+
683
+ /**
684
+ * Returns true when one pad uses an octagonal pad shape.
685
+ * @param {object} pad Pad row.
686
+ * @returns {boolean}
687
+ */
688
+ static #hasOctagonalPadShape(pad) {
689
+ const shapeNames = [
690
+ pad?.shapeTopName,
691
+ pad?.shapeMidName,
692
+ pad?.shapeBottomName,
693
+ pad?.padShapeNames?.top,
694
+ pad?.padShapeNames?.middle,
695
+ pad?.padShapeNames?.bottom
696
+ ].map((value) => String(value || '').toLowerCase())
697
+ const shapeCodes = [pad?.shapeTop, pad?.shapeMid, pad?.shapeBottom].map(
698
+ (value) => Number(value)
699
+ )
700
+
701
+ return (
702
+ shapeNames.includes('octagonal') || shapeCodes.includes(Number(3))
703
+ )
704
+ }
705
+
706
+ /**
707
+ * Returns true when top, middle, or bottom pad geometry differs.
708
+ * @param {object} pad Pad row.
709
+ * @returns {boolean}
710
+ */
711
+ static #hasLayerSpecificPadGeometry(pad) {
712
+ const sizes = [
713
+ [pad?.sizeTopX, pad?.sizeTopY],
714
+ [pad?.sizeMidX, pad?.sizeMidY],
715
+ [pad?.sizeBottomX, pad?.sizeBottomY]
716
+ ].map(([x, y]) => [Number(x), Number(y)])
717
+ const finiteSizes = sizes.filter(([x, y]) =>
718
+ [x, y].every(Number.isFinite)
719
+ )
720
+ const shapes = [pad?.shapeTop, pad?.shapeMid, pad?.shapeBottom]
721
+ .map((value) => Number(value))
722
+ .filter(Number.isFinite)
723
+
724
+ return (
725
+ UnsupportedFeatureReportBuilder.#hasDistinctPairs(finiteSizes) ||
726
+ new Set(shapes).size > 1
727
+ )
728
+ }
729
+
730
+ /**
731
+ * Returns true when an array of numeric pairs has more than one value.
732
+ * @param {number[][]} pairs Numeric pairs.
733
+ * @returns {boolean}
734
+ */
735
+ static #hasDistinctPairs(pairs) {
736
+ return new Set(pairs.map((pair) => pair.join(','))).size > 1
737
+ }
738
+
739
+ /**
740
+ * Returns true when one pad uses slot-hole geometry.
741
+ * @param {object} pad Pad row.
742
+ * @returns {boolean}
743
+ */
744
+ static #hasSlotHole(pad) {
745
+ return (
746
+ String(
747
+ pad?.holeShapeName || pad?.holeGeometry?.shapeName || ''
748
+ ).toLowerCase() === 'slot'
749
+ )
750
+ }
751
+
752
+ /**
753
+ * Returns true when one pad uses manual mask or paste expansion metadata.
754
+ * @param {object} pad Pad row.
755
+ * @returns {boolean}
756
+ */
757
+ static #hasManualMaskPaste(pad) {
758
+ const sources = [
759
+ pad?.pasteMaskExpansionSource,
760
+ pad?.solderMaskExpansionSource
761
+ ].map((value) => String(value || '').toLowerCase())
762
+ const modes = [
763
+ pad?.pasteMaskExpansionMode,
764
+ pad?.solderMaskExpansionMode
765
+ ].map((value) => Number(value))
766
+
767
+ return sources.includes('manual') || modes.includes(Number(2))
768
+ }
769
+
770
+ /**
771
+ * Returns true when custom-shape layers have no linked geometry.
772
+ * @param {object} pad Pad row.
773
+ * @returns {boolean}
774
+ */
775
+ static #hasMissingCustomShapeGeometry(pad) {
776
+ const layers = Array.isArray(pad?.customShape?.layers)
777
+ ? pad.customShape.layers
778
+ : []
779
+
780
+ return (
781
+ layers.length > 0 &&
782
+ layers.some(
783
+ (layer) =>
784
+ !UnsupportedFeatureReportBuilder.#customShapeLayerHasGeometry(
785
+ layer
786
+ )
787
+ )
788
+ )
789
+ }
790
+
791
+ /**
792
+ * Returns true when a custom-shape layer links to any primitive geometry.
793
+ * @param {object} layer Custom shape layer.
794
+ * @returns {boolean}
795
+ */
796
+ static #customShapeLayerHasGeometry(layer) {
797
+ return ['regions', 'shapeRegions', 'arcs', 'tracks', 'fills'].some(
798
+ (key) => Array.isArray(layer?.[key]) && layer[key].length > 0
799
+ )
800
+ }
801
+
802
+ /**
803
+ * Returns true when a known shape body lacks complete static geometry.
804
+ * @param {object} componentBody Component body.
805
+ * @returns {boolean}
806
+ */
807
+ static #missingStaticBodyGeometry(componentBody) {
808
+ const typeName = String(componentBody?.modelTypeName || '')
809
+ const isShapeBody = [
810
+ 'extruded-polygon',
811
+ 'cone',
812
+ 'cylinder',
813
+ 'sphere'
814
+ ].includes(typeName)
815
+
816
+ if (!isShapeBody) {
817
+ return false
818
+ }
819
+
820
+ return componentBody?.staticGeometry?.status !== 'complete'
821
+ }
822
+
823
+ /**
824
+ * Removes repeated edge-case rows.
825
+ * @param {object[]} edgeCases Edge cases.
826
+ * @returns {object[]}
827
+ */
828
+ static #dedupeEdgeCases(edgeCases) {
829
+ const seen = new Set()
830
+ const deduped = []
831
+
832
+ for (const edgeCase of edgeCases) {
833
+ const row = edgeCase?.edgeCase || edgeCase || {}
834
+ const key = [
835
+ edgeCase?.fileName || row.fileName || '',
836
+ edgeCase?.domain || row.domain || '',
837
+ row.code || '',
838
+ row.sourceStream || '',
839
+ row.sourceStorage || '',
840
+ row.recordIndex ?? ''
841
+ ].join('\u0000')
842
+
843
+ if (seen.has(key)) {
844
+ continue
845
+ }
846
+
847
+ seen.add(key)
848
+ deduped.push(edgeCase)
849
+ }
850
+
851
+ return deduped
852
+ }
853
+
347
854
  /**
348
855
  * Returns true for unsupported-feature diagnostics.
349
856
  * @param {object} diagnostic Diagnostic row.
@@ -357,6 +864,19 @@ export class UnsupportedFeatureReportBuilder {
357
864
  .includes('unsupported')
358
865
  }
359
866
 
867
+ /**
868
+ * Returns true for diagnostics that describe parser edge-case coverage.
869
+ * @param {object} diagnostic Diagnostic row.
870
+ * @returns {boolean}
871
+ */
872
+ static #isEdgeCaseDiagnostic(diagnostic) {
873
+ if (!diagnostic || typeof diagnostic !== 'object') return false
874
+ if (diagnostic.errorKind === 'edge-case') return true
875
+ return String(diagnostic.code || '')
876
+ .toLowerCase()
877
+ .includes('edge-case')
878
+ }
879
+
360
880
  /**
361
881
  * Returns a finite number or undefined.
362
882
  * @param {unknown} value Candidate value.
package/src/parser.mjs CHANGED
@@ -68,9 +68,11 @@ export { PcbComponentKindPolicy } from './core/altium/PcbComponentKindPolicy.mjs
68
68
  export { PcbComponentPrimitiveIndexer } from './core/altium/PcbComponentPrimitiveIndexer.mjs'
69
69
  export { PcbCustomPadShapeParser } from './core/altium/PcbCustomPadShapeParser.mjs'
70
70
  export { PcbDimensionParser } from './core/altium/PcbDimensionParser.mjs'
71
+ export { PcbDimensionReportBuilder } from './core/altium/PcbDimensionReportBuilder.mjs'
71
72
  export { PcbEmbeddedFontExtractor } from './core/altium/PcbEmbeddedFontExtractor.mjs'
72
73
  export { PcbEmbeddedModelExtractor } from './core/altium/PcbEmbeddedModelExtractor.mjs'
73
74
  export { PcbExtendedPrimitiveInformationParser } from './core/altium/PcbExtendedPrimitiveInformationParser.mjs'
75
+ export { PcbFabricationReadinessReportBuilder } from './core/altium/PcbFabricationReadinessReportBuilder.mjs'
74
76
  export { PcbFontMetricsParser } from './core/altium/PcbFontMetricsParser.mjs'
75
77
  export { PcbLayerIdCodec } from './core/altium/PcbLayerIdCodec.mjs'
76
78
  export { PcbLayerGroups } from './core/altium/PcbLayerGroups.mjs'
@@ -89,12 +91,15 @@ export { PcbPadStackParser } from './core/altium/PcbPadStackParser.mjs'
89
91
  export { PcbPlacedFootprintManifestBuilder } from './core/altium/PcbPlacedFootprintManifestBuilder.mjs'
90
92
  export { PcbPickPlacePositionResolver } from './core/altium/PcbPickPlacePositionResolver.mjs'
91
93
  export { PcbPolygonRecordParser } from './core/altium/PcbPolygonRecordParser.mjs'
94
+ export { PcbPrimitiveFieldOffsetReportBuilder } from './core/altium/PcbPrimitiveFieldOffsetReportBuilder.mjs'
92
95
  export { PcbRawRecordRegistry } from './core/altium/PcbRawRecordRegistry.mjs'
93
96
  export { PcbReviewMetadataBuilder } from './core/altium/PcbReviewMetadataBuilder.mjs'
97
+ export { PcbReviewPolygonRealizationBuilder } from './core/altium/PcbReviewPolygonRealizationBuilder.mjs'
94
98
  export { PcbClassReportBuilder } from './core/altium/PcbClassReportBuilder.mjs'
95
99
  export { PcbInspectionReportBuilder } from './core/altium/PcbInspectionReportBuilder.mjs'
96
100
  export { PcbNetMembershipReportBuilder } from './core/altium/PcbNetMembershipReportBuilder.mjs'
97
101
  export { PcbRouteAnalysisBuilder } from './core/altium/PcbRouteAnalysisBuilder.mjs'
102
+ export { PcbRuleImpactReportBuilder } from './core/altium/PcbRuleImpactReportBuilder.mjs'
98
103
  export { PcbRuleParser } from './core/altium/PcbRuleParser.mjs'
99
104
  export { PcbSidecarRecordParser } from './core/altium/PcbSidecarRecordParser.mjs'
100
105
  export { PcbSpecialStringResolver } from './core/altium/PcbSpecialStringResolver.mjs'
@@ -113,8 +118,10 @@ export { SchLibStreamExtractor } from './core/altium/SchLibStreamExtractor.mjs'
113
118
  export { SchematicMultipartOwnerMatcher } from './core/altium/SchematicMultipartOwnerMatcher.mjs'
114
119
  export { SchematicCrossSheetConnectorParser } from './core/altium/SchematicCrossSheetConnectorParser.mjs'
115
120
  export { SchematicCodeSymbolParser } from './core/altium/SchematicCodeSymbolParser.mjs'
121
+ export { SchematicConnectivityQaBuilder } from './core/altium/SchematicConnectivityQaBuilder.mjs'
116
122
  export { SchematicHarnessParser } from './core/altium/SchematicHarnessParser.mjs'
117
123
  export { SchematicImplementationParser } from './core/altium/SchematicImplementationParser.mjs'
124
+ export { SchematicImageDiagnosticsBuilder } from './core/altium/SchematicImageDiagnosticsBuilder.mjs'
118
125
  export { SchematicOwnershipGraphParser } from './core/altium/SchematicOwnershipGraphParser.mjs'
119
126
  export { SchematicProjectParameterResolver } from './core/altium/SchematicProjectParameterResolver.mjs'
120
127
  export { SchematicQaReportBuilder } from './core/altium/SchematicQaReportBuilder.mjs'
@@ -142,6 +142,8 @@
142
142
  --pcb-via-hole-fill: #0f746c;
143
143
  --pcb-footprint-fill: rgba(247, 230, 117, 0.14);
144
144
  --pcb-footprint-track-color: rgba(237, 172, 36, 1);
145
+ --pcb-dimension-color: rgba(39, 88, 111, 0.78);
146
+ --pcb-dimension-text: #22343b;
145
147
  --pcb-text-knockout-fill: rgba(248, 246, 239, 0.96);
146
148
  --pcb-component-top-fill: rgba(244, 219, 198, 0.92);
147
149
  --pcb-component-bottom-fill: rgba(15, 116, 108, 0.84);
@@ -269,6 +271,40 @@
269
271
  stroke: var(--pcb-footprint-track-color);
270
272
  }
271
273
 
274
+ .pcb-dimensions {
275
+ pointer-events: none;
276
+ }
277
+
278
+ .pcb-dimension__measure,
279
+ .pcb-dimension__extension,
280
+ .pcb-dimension__leader,
281
+ .pcb-dimension__arc {
282
+ fill: none;
283
+ stroke: var(--pcb-dimension-color);
284
+ stroke-width: 5;
285
+ stroke-linecap: round;
286
+ stroke-linejoin: round;
287
+ }
288
+
289
+ .pcb-dimension__extension,
290
+ .pcb-dimension__leader {
291
+ stroke-width: 3;
292
+ stroke-dasharray: 16 10;
293
+ }
294
+
295
+ .pcb-dimension__center {
296
+ fill: var(--pcb-dimension-color);
297
+ }
298
+
299
+ .pcb-dimension__label {
300
+ fill: var(--pcb-dimension-text);
301
+ font-family: Arial, sans-serif;
302
+ font-size: 30px;
303
+ font-weight: 700;
304
+ text-anchor: middle;
305
+ dominant-baseline: central;
306
+ }
307
+
272
308
  .pcb-component__body {
273
309
  fill: var(--pcb-component-top-fill);
274
310
  stroke: var(--pcb-component-stroke);