altium-toolkit 1.1.22 → 1.1.23

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 (99) hide show
  1. package/README.md +34 -5
  2. package/docs/api.md +134 -23
  3. package/docs/model-format.md +174 -20
  4. package/docs/schemas/altium_toolkit/embedded_assets_a1.schema.json +56 -0
  5. package/docs/schemas/altium_toolkit/fixture_coverage_matrix_a1.schema.json +89 -0
  6. package/docs/schemas/altium_toolkit/geometry_bounds_a1.schema.json +86 -0
  7. package/docs/schemas/altium_toolkit/library_catalog_a1.schema.json +65 -0
  8. package/docs/schemas/altium_toolkit/library_diff_a1.schema.json +54 -0
  9. package/docs/schemas/altium_toolkit/library_inspection_a1.schema.json +94 -0
  10. package/docs/schemas/altium_toolkit/library_qa_a1.schema.json +4 -0
  11. package/docs/schemas/altium_toolkit/native_stream_inventory_a1.schema.json +66 -0
  12. package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +511 -1
  13. package/docs/schemas/altium_toolkit/parameter_record_inventory_a1.schema.json +84 -0
  14. package/docs/schemas/altium_toolkit/parser_diagnostics_a1.schema.json +63 -0
  15. package/docs/schemas/altium_toolkit/parser_value_verification_a1.schema.json +74 -0
  16. package/docs/schemas/altium_toolkit/pcb_class_report_a1.schema.json +79 -0
  17. package/docs/schemas/altium_toolkit/pcb_inspection_a1.schema.json +65 -0
  18. package/docs/schemas/altium_toolkit/pcb_net_membership_a1.schema.json +98 -0
  19. package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +3 -0
  20. package/docs/schemas/altium_toolkit/project_hierarchy_a1.schema.json +79 -0
  21. package/docs/schemas/altium_toolkit/unsupported_features_a1.schema.json +212 -0
  22. package/docs/testing.md +2 -0
  23. package/examples/README.md +21 -0
  24. package/examples/cli-utils.mjs +148 -0
  25. package/examples/corpus-smoke.mjs +523 -0
  26. package/examples/extract-bom.mjs +47 -0
  27. package/examples/generate-pnp.mjs +59 -0
  28. package/examples/inspect-board.mjs +70 -0
  29. package/examples/inspect-schematic.mjs +406 -0
  30. package/examples/library-catalog.mjs +115 -0
  31. package/examples/net-report.mjs +61 -0
  32. package/examples/validate-library.mjs +59 -0
  33. package/package.json +1 -1
  34. package/src/core/BinaryReader.mjs +213 -2
  35. package/src/core/altium/AltiumParser.mjs +352 -14
  36. package/src/core/altium/AltiumUnits.mjs +205 -0
  37. package/src/core/altium/AsciiRecordParser.mjs +9 -0
  38. package/src/core/altium/EmbeddedAssetReportBuilder.mjs +383 -0
  39. package/src/core/altium/FixtureCoverageMatrixBuilder.mjs +304 -0
  40. package/src/core/altium/GeometryBoundsReportBuilder.mjs +935 -0
  41. package/src/core/altium/LibraryCatalogArtifactBuilder.mjs +296 -0
  42. package/src/core/altium/LibraryDiffReportBuilder.mjs +260 -0
  43. package/src/core/altium/LibraryInspectionReportBuilder.mjs +156 -0
  44. package/src/core/altium/LibraryQaReportBuilder.mjs +374 -1
  45. package/src/core/altium/NativeStreamInventoryBuilder.mjs +177 -0
  46. package/src/core/altium/NormalizedModelSchema.mjs +3 -31
  47. package/src/core/altium/ParameterCollection.mjs +431 -0
  48. package/src/core/altium/ParameterRecordInventoryBuilder.mjs +274 -0
  49. package/src/core/altium/ParserCompatibilityFuzzer.mjs +106 -2
  50. package/src/core/altium/ParserDiagnosticNormalizer.mjs +213 -0
  51. package/src/core/altium/ParserErrors.mjs +90 -0
  52. package/src/core/altium/ParserFieldCoverageReportBuilder.mjs +656 -0
  53. package/src/core/altium/ParserUtils.mjs +24 -0
  54. package/src/core/altium/ParserValueVerificationReportBuilder.mjs +323 -0
  55. package/src/core/altium/PcbClassReportBuilder.mjs +366 -0
  56. package/src/core/altium/PcbInspectionReportBuilder.mjs +313 -0
  57. package/src/core/altium/PcbLayerGroups.mjs +308 -0
  58. package/src/core/altium/PcbLayerStackCustomDataParser.mjs +183 -0
  59. package/src/core/altium/PcbLayerStackInterchangeParser.mjs +473 -4
  60. package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +83 -15
  61. package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +74 -4
  62. package/src/core/altium/PcbLibModelParser.mjs +20 -4
  63. package/src/core/altium/PcbLibStreamExtractor.mjs +49 -6
  64. package/src/core/altium/PcbModelParser.mjs +223 -4
  65. package/src/core/altium/PcbNetMembershipReportBuilder.mjs +270 -0
  66. package/src/core/altium/PcbStreamExtractor.mjs +130 -6
  67. package/src/core/altium/PcbTrackPrimitiveParser.mjs +66 -2
  68. package/src/core/altium/ProjectDesignBundleBuilder.mjs +15 -0
  69. package/src/core/altium/ProjectHierarchyReportBuilder.mjs +660 -0
  70. package/src/core/altium/ProjectNetlistExporter.mjs +2 -0
  71. package/src/core/altium/RawDataPreservationReportBuilder.mjs +348 -0
  72. package/src/core/altium/SchLibModelParser.mjs +840 -0
  73. package/src/core/altium/SchLibStreamExtractor.mjs +586 -0
  74. package/src/core/altium/SchematicBusEntryParser.mjs +3 -2
  75. package/src/core/altium/SchematicCodeSymbolParser.mjs +663 -0
  76. package/src/core/altium/SchematicConnectivityQaBuilder.mjs +177 -2
  77. package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +10 -1
  78. package/src/core/altium/SchematicFieldCoverageReportBuilder.mjs +549 -0
  79. package/src/core/altium/SchematicHarnessParser.mjs +9 -3
  80. package/src/core/altium/SchematicHyperlinkParser.mjs +122 -0
  81. package/src/core/altium/SchematicNetlistBuilder.mjs +271 -8
  82. package/src/core/altium/SchematicOwnershipGraphParser.mjs +102 -3
  83. package/src/core/altium/SchematicPinParser.mjs +12 -45
  84. package/src/core/altium/SchematicPrimitiveParser.mjs +9 -14
  85. package/src/core/altium/SchematicQaReportBuilder.mjs +2 -0
  86. package/src/core/altium/SchematicRecordStreamParser.mjs +183 -0
  87. package/src/core/altium/SchematicRecordTypeRegistry.mjs +6 -1
  88. package/src/core/altium/SchematicSheetParser.mjs +8 -2
  89. package/src/core/altium/SchematicStreamExtractor.mjs +64 -25
  90. package/src/core/altium/SchematicTextOrientationResolver.mjs +76 -0
  91. package/src/core/altium/SchematicTextParser.mjs +28 -12
  92. package/src/core/altium/SchematicTextRunParser.mjs +81 -0
  93. package/src/core/altium/SchematicThumbnailParser.mjs +425 -0
  94. package/src/core/altium/UnsupportedFeatureReportBuilder.mjs +380 -0
  95. package/src/parser.mjs +35 -1
  96. package/src/renderers.mjs +1 -0
  97. package/src/ui/SchematicShapeRenderer.mjs +49 -6
  98. package/src/ui/SchematicSvgRenderer.mjs +37 -8
  99. package/src/ui/SchematicTypography.mjs +4 -3
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { AsciiRecordParser } from './AsciiRecordParser.mjs'
6
6
  import { EmbeddedFileInventoryBuilder } from './EmbeddedFileInventoryBuilder.mjs'
7
+ import { NativeStreamInventoryBuilder } from './NativeStreamInventoryBuilder.mjs'
7
8
  import { PcbBinaryPrimitiveParser } from './PcbBinaryPrimitiveParser.mjs'
8
9
  import { PcbCustomPadShapeParser } from './PcbCustomPadShapeParser.mjs'
9
10
  import { PcbEmbeddedFontExtractor } from './PcbEmbeddedFontExtractor.mjs'
@@ -11,12 +12,45 @@ import { PcbEmbeddedModelExtractor } from './PcbEmbeddedModelExtractor.mjs'
11
12
  import { PcbExtendedPrimitiveInformationParser } from './PcbExtendedPrimitiveInformationParser.mjs'
12
13
  import { PcbPrimitiveParameterParser } from './PcbPrimitiveParameterParser.mjs'
13
14
  import { PcbRawRecordRegistry } from './PcbRawRecordRegistry.mjs'
15
+ import { PcbSidecarRecordParser } from './PcbSidecarRecordParser.mjs'
14
16
  import { PcbUnionParser } from './PcbUnionParser.mjs'
15
17
  import { PcbViaStructureParser } from './PcbViaStructureParser.mjs'
16
18
  import { PcbWideStringTableParser } from './PcbWideStringTableParser.mjs'
17
19
  import { OleCompoundDocument } from '../ole/OleCompoundDocument.mjs'
18
20
  import { OleConstants } from '../ole/OleConstants.mjs'
19
21
 
22
+ const KNOWN_PCB_STREAM_STORAGES = new Set([
23
+ 'Board6',
24
+ 'Nets6',
25
+ 'Arcs6',
26
+ 'Pads6',
27
+ 'Vias6',
28
+ 'Tracks6',
29
+ 'Texts',
30
+ 'Texts6',
31
+ 'Fills6',
32
+ 'Regions6',
33
+ 'ShapeBasedRegions6',
34
+ 'BoardRegions',
35
+ 'ComponentBodies6',
36
+ 'Polygons6',
37
+ 'Components6',
38
+ 'Classes6',
39
+ 'DifferentialPairs6',
40
+ 'EmbeddedBoards6',
41
+ 'Rooms6',
42
+ 'Rules6',
43
+ 'WideStrings6',
44
+ 'PrimitiveParameters',
45
+ 'ViaStructures',
46
+ 'ViaStructureManager',
47
+ 'ExtendedPrimitiveInformation',
48
+ 'CustomShapes',
49
+ 'UnionNames',
50
+ 'SmartUnions',
51
+ 'Models'
52
+ ])
53
+
20
54
  /**
21
55
  * Extracts stream-scoped printable and binary PCB content from OLE-backed
22
56
  * PcbDoc containers.
@@ -99,12 +133,15 @@ export class PcbStreamExtractor {
99
133
  continue
100
134
  }
101
135
 
102
- const recordBuffer = PcbStreamExtractor.#toArrayBuffer(bytes)
103
- const streamRecords = AsciiRecordParser.parse(recordBuffer).map(
104
- (record) => ({
105
- ...record,
106
- sourceStream: name
107
- })
136
+ if (
137
+ PcbStreamExtractor.#isSupersededLegacyDataStream(name, streams)
138
+ ) {
139
+ continue
140
+ }
141
+
142
+ const streamRecords = PcbStreamExtractor.#parseDataStreamRecords(
143
+ name,
144
+ bytes
108
145
  )
109
146
 
110
147
  if (!streamRecords.length) {
@@ -335,11 +372,23 @@ export class PcbStreamExtractor {
335
372
  usedStreamNames.add(diagnostic.sourceStream)
336
373
  )
337
374
 
375
+ const nativeStreams = NativeStreamInventoryBuilder.buildFromStreams(
376
+ streams,
377
+ {
378
+ source: 'pcbdoc',
379
+ consumedStreamNames: usedStreamNames,
380
+ knownStreamNames: [...streams.keys()].filter((streamName) =>
381
+ PcbStreamExtractor.#isKnownNativeStream(streamName)
382
+ )
383
+ }
384
+ )
385
+
338
386
  return {
339
387
  records,
340
388
  streamNames: [...usedStreamNames].sort((left, right) =>
341
389
  left.localeCompare(right)
342
390
  ),
391
+ nativeStreams,
343
392
  binaryPrimitives,
344
393
  primitiveParameters,
345
394
  wideStrings,
@@ -395,6 +444,38 @@ export class PcbStreamExtractor {
395
444
  )
396
445
  }
397
446
 
447
+ /**
448
+ * Parses a data stream as length-prefixed sidecar records when needed, with
449
+ * printable-record fallback for regular pipe-delimited streams.
450
+ * @param {string} name Stream path.
451
+ * @param {Uint8Array} bytes Stream bytes.
452
+ * @returns {Array<{ raw?: string, fields: Record<string, string | string[]>, sourceStream: string, recordIndex?: number }>}
453
+ */
454
+ static #parseDataStreamRecords(name, bytes) {
455
+ if (PcbStreamExtractor.#isLengthPrefixedPrintableStream(name)) {
456
+ const sidecarRecords =
457
+ PcbSidecarRecordParser.parseLengthPrefixedRecords(bytes, name)
458
+ if (sidecarRecords.length) {
459
+ return sidecarRecords
460
+ }
461
+ }
462
+
463
+ const recordBuffer = PcbStreamExtractor.#toArrayBuffer(bytes)
464
+ return AsciiRecordParser.parse(recordBuffer).map((record) => ({
465
+ ...record,
466
+ sourceStream: name
467
+ }))
468
+ }
469
+
470
+ /**
471
+ * Returns true for printable sidecar streams that may use length prefixes.
472
+ * @param {string} name Stream path.
473
+ * @returns {boolean}
474
+ */
475
+ static #isLengthPrefixedPrintableStream(name) {
476
+ return name === 'EmbeddedBoards6/Data' || name === 'Rooms6/Data'
477
+ }
478
+
398
479
  /**
399
480
  * Returns true for binary sidecar streams with printable-looking payloads.
400
481
  * @param {string} name
@@ -413,6 +494,34 @@ export class PcbStreamExtractor {
413
494
  )
414
495
  }
415
496
 
497
+ /**
498
+ * Returns true when a legacy printable stream has a versioned companion.
499
+ * @param {string} name Stream path.
500
+ * @param {Map<string, Uint8Array>} streams Source stream map.
501
+ * @returns {boolean}
502
+ */
503
+ static #isSupersededLegacyDataStream(name, streams) {
504
+ const suffix = '/Data'
505
+ if (!name.endsWith(suffix)) {
506
+ return false
507
+ }
508
+
509
+ const storagePath = name.slice(0, -suffix.length)
510
+ const separatorIndex = storagePath.lastIndexOf('/')
511
+ const parentPath =
512
+ separatorIndex >= 0 ? storagePath.slice(0, separatorIndex + 1) : ''
513
+ const storageName =
514
+ separatorIndex >= 0
515
+ ? storagePath.slice(separatorIndex + 1)
516
+ : storagePath
517
+
518
+ if (!storageName || /\d$/u.test(storageName)) {
519
+ return false
520
+ }
521
+
522
+ return streams.has(parentPath + storageName + '6' + suffix)
523
+ }
524
+
416
525
  /**
417
526
  * Collects sidecar source stream names that produced via-structure data.
418
527
  * @param {{ structures?: { sourceStream?: string }[], links?: { sourceStream?: string }[] }} viaStructures
@@ -437,4 +546,19 @@ export class PcbStreamExtractor {
437
546
  .map((record) => record.sourceStream)
438
547
  .filter(Boolean)
439
548
  }
549
+
550
+ /**
551
+ * Returns true when a native stream name belongs to a recognized PCB
552
+ * storage.
553
+ * @param {string} streamName Native stream name.
554
+ * @returns {boolean}
555
+ */
556
+ static #isKnownNativeStream(streamName) {
557
+ if (streamName === 'FileHeader') {
558
+ return true
559
+ }
560
+
561
+ const storageName = String(streamName || '').split('/')[0]
562
+ return KNOWN_PCB_STREAM_STORAGES.has(storageName)
563
+ }
440
564
  }
@@ -19,7 +19,7 @@ export class PcbTrackPrimitiveParser {
19
19
  * Decodes one track stream.
20
20
  * @param {Uint8Array | ArrayBuffer} headerBytes
21
21
  * @param {Uint8Array | ArrayBuffer} dataBytes
22
- * @returns {{ x1: number, y1: number, x2: number, y2: number, width: number, componentIndex: number | null, netIndex: number | null, polygonIndex: number | null, layerCode: number, layerId: number }[]}
22
+ * @returns {{ x1: number, y1: number, x2: number, y2: number, width: number, componentIndex: number | null, netIndex: number | null, polygonIndex: number | null, layerCode: number, layerId: number, [key: string]: unknown }[]}
23
23
  */
24
24
  static parseTrackStream(headerBytes, dataBytes) {
25
25
  return PcbTrackPrimitiveParser.#sliceTrackRecords(
@@ -31,7 +31,7 @@ export class PcbTrackPrimitiveParser {
31
31
  /**
32
32
  * Decodes one track record view into a normalized primitive.
33
33
  * @param {DataView} view
34
- * @returns {{ x1: number, y1: number, x2: number, y2: number, width: number, componentIndex: number | null, netIndex: number | null, polygonIndex: number | null, layerCode: number, layerId: number }}
34
+ * @returns {{ x1: number, y1: number, x2: number, y2: number, width: number, componentIndex: number | null, netIndex: number | null, polygonIndex: number | null, layerCode: number, layerId: number, [key: string]: unknown }}
35
35
  */
36
36
  static #parseTrackRecord(view) {
37
37
  const layerId = view.getUint8(0)
@@ -49,11 +49,61 @@ export class PcbTrackPrimitiveParser {
49
49
  y2: PcbTrackPrimitiveParser.#readMil(view, 25),
50
50
  width: PcbTrackPrimitiveParser.#readMil(view, 29),
51
51
  ...ownershipIndexes,
52
+ ...PcbTrackPrimitiveParser.#parseTrackMetadata(view),
52
53
  layerCode: layerId,
53
54
  layerId
54
55
  }
55
56
  }
56
57
 
58
+ /**
59
+ * Decodes optional route-state metadata from one track record.
60
+ * @param {DataView} view Track record view.
61
+ * @returns {{ trackFlags?: number, isSelected?: boolean, isLocked?: boolean, isPartOfComponent?: boolean, isKeepout?: boolean, unionIndex?: number, isLengthTuning?: boolean, isUserRouted?: boolean }}
62
+ */
63
+ static #parseTrackMetadata(view) {
64
+ const flags = PcbTrackPrimitiveParser.#readByteIfAvailable(view, 1)
65
+ const keepout = PcbTrackPrimitiveParser.#readByteIfAvailable(view, 2)
66
+ const unionIndex = PcbTrackPrimitiveParser.#readByteIfAvailable(
67
+ view,
68
+ 36
69
+ )
70
+ const lengthTuning = PcbTrackPrimitiveParser.#readByteIfAvailable(
71
+ view,
72
+ 37
73
+ )
74
+ const userRouted = PcbTrackPrimitiveParser.#readByteIfAvailable(
75
+ view,
76
+ 44
77
+ )
78
+
79
+ if (!flags && !keepout && !unionIndex && !lengthTuning && !userRouted) {
80
+ return {}
81
+ }
82
+
83
+ const result = {}
84
+
85
+ if (flags) {
86
+ result.trackFlags = flags
87
+ result.isSelected = (flags & 0x01) !== 0
88
+ result.isLocked = (flags & 0x04) === 0
89
+ result.isPartOfComponent = (flags & 0x10) !== 0
90
+ }
91
+ if (keepout) {
92
+ result.isKeepout = true
93
+ }
94
+ if (unionIndex) {
95
+ result.unionIndex = unionIndex
96
+ }
97
+ if (lengthTuning) {
98
+ result.isLengthTuning = true
99
+ }
100
+ if (userRouted) {
101
+ result.isUserRouted = true
102
+ }
103
+
104
+ return result
105
+ }
106
+
57
107
  /**
58
108
  * Splits a track stream into record views, preserving variable payload
59
109
  * lengths when tracks are stored with object-id and payload-length
@@ -84,4 +134,18 @@ export class PcbTrackPrimitiveParser {
84
134
  static #readMil(view, offset) {
85
135
  return view.getInt32(offset, true) / 10000
86
136
  }
137
+
138
+ /**
139
+ * Reads one byte when it is present in a variable-length record.
140
+ * @param {DataView} view Record view.
141
+ * @param {number} offset Byte offset.
142
+ * @returns {number}
143
+ */
144
+ static #readByteIfAvailable(view, offset) {
145
+ if (!view || offset >= view.byteLength) {
146
+ return 0
147
+ }
148
+
149
+ return view.getUint8(offset)
150
+ }
87
151
  }
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { NormalizedModelSchema } from './NormalizedModelSchema.mjs'
6
6
  import { ProjectBomPnpReconciliationBuilder } from './ProjectBomPnpReconciliationBuilder.mjs'
7
+ import { ProjectHierarchyReportBuilder } from './ProjectHierarchyReportBuilder.mjs'
7
8
  import { ProjectVariantViewBuilder } from './ProjectVariantViewBuilder.mjs'
8
9
 
9
10
  /**
@@ -102,6 +103,10 @@ export class ProjectDesignBundleBuilder {
102
103
  schematicModels,
103
104
  documents
104
105
  ),
106
+ schematicHierarchyReport: ProjectHierarchyReportBuilder.build({
107
+ projectModel,
108
+ documentModels: schematicModels
109
+ }),
105
110
  pnp,
106
111
  nets,
107
112
  annotations,
@@ -262,7 +267,17 @@ export class ProjectDesignBundleBuilder {
262
267
  fileName: model.fileName,
263
268
  pins: net.pins || [],
264
269
  labels: net.labels || [],
270
+ powerPorts: net.powerPorts || [],
271
+ ports: net.ports || [],
265
272
  segments: net.segments || [],
273
+ sheetEntries: net.sheetEntries || [],
274
+ ...(net.autoName ? { autoName: net.autoName } : {}),
275
+ ...(net.autoNameSource
276
+ ? { autoNameSource: net.autoNameSource }
277
+ : {}),
278
+ ...(net.aliasCandidates?.length
279
+ ? { aliasCandidates: net.aliasCandidates }
280
+ : {}),
266
281
  ...(model.schematic?.harnesses
267
282
  ? { harnesses: model.schematic.harnesses.connectors }
268
283
  : {})