altium-toolkit 1.1.3 → 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 (125) hide show
  1. package/README.md +34 -5
  2. package/docs/api.md +171 -23
  3. package/docs/model-format.md +192 -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 +513 -3
  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 +7 -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/spec/library-scope.md +5 -0
  35. package/src/core/BinaryReader.mjs +213 -2
  36. package/src/core/altium/AltiumLibraryBatchExporter.mjs +206 -0
  37. package/src/core/altium/AltiumLibraryRecordBuilder.mjs +293 -0
  38. package/src/core/altium/AltiumParser.mjs +357 -16
  39. package/src/core/altium/AltiumPcbLibExporter.mjs +101 -0
  40. package/src/core/altium/AltiumSchLibExporter.mjs +57 -0
  41. package/src/core/altium/AltiumUnits.mjs +205 -0
  42. package/src/core/altium/AsciiRecordParser.mjs +52 -11
  43. package/src/core/altium/EmbeddedAssetReportBuilder.mjs +383 -0
  44. package/src/core/altium/FixtureCoverageMatrixBuilder.mjs +304 -0
  45. package/src/core/altium/GeometryBoundsReportBuilder.mjs +935 -0
  46. package/src/core/altium/LibraryCatalogArtifactBuilder.mjs +296 -0
  47. package/src/core/altium/LibraryDiffReportBuilder.mjs +260 -0
  48. package/src/core/altium/LibraryInspectionReportBuilder.mjs +156 -0
  49. package/src/core/altium/LibraryQaReportBuilder.mjs +374 -1
  50. package/src/core/altium/NativeStreamInventoryBuilder.mjs +177 -0
  51. package/src/core/altium/NormalizedModelSchema.mjs +3 -31
  52. package/src/core/altium/ParameterCollection.mjs +431 -0
  53. package/src/core/altium/ParameterRecordInventoryBuilder.mjs +274 -0
  54. package/src/core/altium/ParserCompatibilityFuzzer.mjs +106 -2
  55. package/src/core/altium/ParserDiagnosticNormalizer.mjs +213 -0
  56. package/src/core/altium/ParserErrors.mjs +90 -0
  57. package/src/core/altium/ParserFieldCoverageReportBuilder.mjs +656 -0
  58. package/src/core/altium/ParserUtils.mjs +24 -0
  59. package/src/core/altium/ParserValueVerificationReportBuilder.mjs +323 -0
  60. package/src/core/altium/PcbClassReportBuilder.mjs +366 -0
  61. package/src/core/altium/PcbComponentKindPolicy.mjs +9 -9
  62. package/src/core/altium/PcbEmbeddedModelExtractor.mjs +22 -3
  63. package/src/core/altium/PcbInspectionReportBuilder.mjs +313 -0
  64. package/src/core/altium/PcbLayerGroups.mjs +308 -0
  65. package/src/core/altium/PcbLayerStackCustomDataParser.mjs +183 -0
  66. package/src/core/altium/PcbLayerStackInterchangeParser.mjs +473 -4
  67. package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +83 -15
  68. package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +74 -4
  69. package/src/core/altium/PcbLibModelParser.mjs +20 -4
  70. package/src/core/altium/PcbLibStreamExtractor.mjs +49 -6
  71. package/src/core/altium/PcbModelParser.mjs +223 -4
  72. package/src/core/altium/PcbNetMembershipReportBuilder.mjs +270 -0
  73. package/src/core/altium/PcbOutlineRecovery.mjs +94 -0
  74. package/src/core/altium/PcbStreamExtractor.mjs +130 -6
  75. package/src/core/altium/PcbTrackPrimitiveParser.mjs +66 -2
  76. package/src/core/altium/ProjectDesignBundleBuilder.mjs +15 -0
  77. package/src/core/altium/ProjectHierarchyReportBuilder.mjs +660 -0
  78. package/src/core/altium/ProjectNetlistExporter.mjs +2 -0
  79. package/src/core/altium/RawDataPreservationReportBuilder.mjs +348 -0
  80. package/src/core/altium/SchLibModelParser.mjs +840 -0
  81. package/src/core/altium/SchLibStreamExtractor.mjs +586 -0
  82. package/src/core/altium/SchematicBusEntryParser.mjs +3 -2
  83. package/src/core/altium/SchematicCodeSymbolParser.mjs +663 -0
  84. package/src/core/altium/SchematicConnectivityQaBuilder.mjs +177 -2
  85. package/src/core/altium/SchematicDirectiveParser.mjs +5 -17
  86. package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +10 -1
  87. package/src/core/altium/SchematicFieldCoverageReportBuilder.mjs +549 -0
  88. package/src/core/altium/SchematicHarnessParser.mjs +9 -3
  89. package/src/core/altium/SchematicHyperlinkParser.mjs +122 -0
  90. package/src/core/altium/SchematicNetlistBuilder.mjs +271 -8
  91. package/src/core/altium/SchematicNoErcSymbolResolver.mjs +36 -0
  92. package/src/core/altium/SchematicOwnershipGraphParser.mjs +102 -3
  93. package/src/core/altium/SchematicPinParser.mjs +99 -65
  94. package/src/core/altium/SchematicPrimitiveParser.mjs +125 -22
  95. package/src/core/altium/SchematicQaReportBuilder.mjs +2 -0
  96. package/src/core/altium/SchematicRecordStreamParser.mjs +183 -0
  97. package/src/core/altium/SchematicRecordTypeRegistry.mjs +6 -1
  98. package/src/core/altium/SchematicSheetParser.mjs +8 -2
  99. package/src/core/altium/SchematicStreamExtractor.mjs +107 -21
  100. package/src/core/altium/SchematicTextOrientationResolver.mjs +76 -0
  101. package/src/core/altium/SchematicTextParser.mjs +28 -12
  102. package/src/core/altium/SchematicTextRunParser.mjs +81 -0
  103. package/src/core/altium/SchematicThumbnailParser.mjs +425 -0
  104. package/src/core/altium/SourceBundleExporter.mjs +156 -0
  105. package/src/core/altium/SourceComponentBundleNormalizer.mjs +295 -0
  106. package/src/core/altium/SourceComponentClient.mjs +239 -0
  107. package/src/core/altium/UnsupportedFeatureReportBuilder.mjs +380 -0
  108. package/src/core/ole/OleCompoundDocumentWriter.mjs +449 -0
  109. package/src/parser.mjs +43 -1
  110. package/src/renderers.mjs +1 -0
  111. package/src/styles/altium-renderers.css +6 -6
  112. package/src/ui/PcbArcUtils.mjs +19 -2
  113. package/src/ui/PcbScene3dBuilder.mjs +202 -20
  114. package/src/ui/PcbScene3dModelRegistry.mjs +28 -18
  115. package/src/ui/PcbScene3dPlacementSideResolver.mjs +48 -6
  116. package/src/ui/SchematicColorResolver.mjs +185 -0
  117. package/src/ui/SchematicDirectiveRenderer.mjs +133 -22
  118. package/src/ui/SchematicLineColorResolver.mjs +88 -0
  119. package/src/ui/SchematicNoteRenderer.mjs +5 -1
  120. package/src/ui/SchematicOwnerPinLabelLayout.mjs +269 -8
  121. package/src/ui/SchematicOwnerPinMarkerLineThemer.mjs +155 -0
  122. package/src/ui/SchematicPinSvgRenderer.mjs +229 -62
  123. package/src/ui/SchematicShapeRenderer.mjs +86 -17
  124. package/src/ui/SchematicSvgRenderer.mjs +980 -58
  125. package/src/ui/SchematicTypography.mjs +4 -3
@@ -0,0 +1,348 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ /**
6
+ * Builds compact reports for preserved raw and unknown parser data.
7
+ */
8
+ export class RawDataPreservationReportBuilder {
9
+ static SCHEMA = 'altium-toolkit.raw-data-preservation.a1'
10
+
11
+ /**
12
+ * Builds a raw-data preservation report.
13
+ * @param {{ models?: object[], rawRecords?: object[], unknownRecords?: object[], opaqueRecords?: object[], nativeStreams?: object[] }} [input]
14
+ * @returns {object}
15
+ */
16
+ static build(input = {}) {
17
+ const rawRecords = RawDataPreservationReportBuilder.#rawRecords(input)
18
+ const unknownRecords =
19
+ RawDataPreservationReportBuilder.#unknownRecords(input)
20
+ const opaqueRecords =
21
+ RawDataPreservationReportBuilder.#opaqueRecords(input)
22
+ const nativeStreams =
23
+ RawDataPreservationReportBuilder.#nativeStreams(input)
24
+ const records = rawRecords.map((record) =>
25
+ RawDataPreservationReportBuilder.#recordRow(record)
26
+ )
27
+ const unknownRows = unknownRecords.map((record) =>
28
+ RawDataPreservationReportBuilder.#unknownRecordRow(record)
29
+ )
30
+ const opaqueRows = opaqueRecords.map((record) =>
31
+ RawDataPreservationReportBuilder.#opaqueRecordRow(record)
32
+ )
33
+ const nativeStreamRows = nativeStreams.map((stream) =>
34
+ RawDataPreservationReportBuilder.#nativeStreamRow(stream)
35
+ )
36
+ const preservedByteCount = [
37
+ ...records,
38
+ ...unknownRows,
39
+ ...opaqueRows
40
+ ].reduce((sum, record) => sum + Number(record.byteLength || 0), 0)
41
+ const nativeStreamSummary = nativeStreamRows.length
42
+ ? {
43
+ nativeStreamCount: nativeStreamRows.length,
44
+ unknownNativeStreamCount: nativeStreamRows.filter(
45
+ (stream) => !stream.known
46
+ ).length,
47
+ unconsumedNativeStreamCount: nativeStreamRows.filter(
48
+ (stream) => !stream.consumed
49
+ ).length,
50
+ nativeStreamByteCount: nativeStreamRows.reduce(
51
+ (sum, stream) => sum + Number(stream.byteLength || 0),
52
+ 0
53
+ )
54
+ }
55
+ : {}
56
+
57
+ return {
58
+ schema: RawDataPreservationReportBuilder.SCHEMA,
59
+ summary: {
60
+ rawRecordCount: records.length,
61
+ unknownRecordCount: unknownRows.length,
62
+ opaqueRecordCount: opaqueRows.length,
63
+ supportedRawRecordCount: records.filter(
64
+ (record) => record.supported
65
+ ).length,
66
+ unsupportedRawRecordCount: records.filter(
67
+ (record) => !record.supported
68
+ ).length,
69
+ parsedRawRecordCount: records.filter((record) => record.parsed)
70
+ .length,
71
+ unparsedRawRecordCount: records.filter(
72
+ (record) => !record.parsed
73
+ ).length,
74
+ ...nativeStreamSummary,
75
+ preservedByteCount
76
+ },
77
+ records,
78
+ unknownRecords: unknownRows,
79
+ opaqueRecords: opaqueRows,
80
+ nativeStreams: nativeStreamRows,
81
+ families: RawDataPreservationReportBuilder.#familyRows(records)
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Collects raw records from direct input and parser roots.
87
+ * @param {object} input Build input.
88
+ * @returns {object[]}
89
+ */
90
+ static #rawRecords(input) {
91
+ return [
92
+ ...(Array.isArray(input?.rawRecords) ? input.rawRecords : []),
93
+ ...RawDataPreservationReportBuilder.#models(input).flatMap(
94
+ (model) => [
95
+ ...(model?.pcb?.rawRecords || []),
96
+ ...((model?.pcbLibrary?.footprints || []).flatMap(
97
+ (footprint) => footprint.rawRecords || []
98
+ ) || [])
99
+ ]
100
+ )
101
+ ]
102
+ }
103
+
104
+ /**
105
+ * Collects unknown records from direct input and parser roots.
106
+ * @param {object} input Build input.
107
+ * @returns {object[]}
108
+ */
109
+ static #unknownRecords(input) {
110
+ return [
111
+ ...(Array.isArray(input?.unknownRecords)
112
+ ? input.unknownRecords
113
+ : []),
114
+ ...RawDataPreservationReportBuilder.#models(input).flatMap(
115
+ (model) =>
116
+ (model?.pcbLibrary?.footprints || []).flatMap(
117
+ (footprint) => footprint.unknownRecords || []
118
+ )
119
+ )
120
+ ]
121
+ }
122
+
123
+ /**
124
+ * Collects opaque records from direct input and parser roots.
125
+ * @param {object} input Build input.
126
+ * @returns {object[]}
127
+ */
128
+ static #opaqueRecords(input) {
129
+ return [
130
+ ...(Array.isArray(input?.opaqueRecords) ? input.opaqueRecords : []),
131
+ ...RawDataPreservationReportBuilder.#models(input).flatMap(
132
+ (model) => [
133
+ ...(model?.schematic?.opaqueRecords || []),
134
+ ...(model?.schematicLibrary?.opaqueRecords || []),
135
+ ...((model?.schematicLibrary?.components || []).flatMap(
136
+ (component) => component.opaqueRecords || []
137
+ ) || []),
138
+ ...((model?.integratedLibrary?.sources || []).flatMap(
139
+ (source) => source.opaqueRecords || []
140
+ ) || [])
141
+ ]
142
+ )
143
+ ]
144
+ }
145
+
146
+ /**
147
+ * Collects native stream inventory rows from direct input and parser roots.
148
+ * @param {object} input Build input.
149
+ * @returns {object[]}
150
+ */
151
+ static #nativeStreams(input) {
152
+ return [
153
+ ...(Array.isArray(input?.nativeStreams) ? input.nativeStreams : []),
154
+ ...RawDataPreservationReportBuilder.#models(input).flatMap(
155
+ (model) => [
156
+ ...(model?.schematic?.nativeStreams?.streams || []),
157
+ ...(model?.pcb?.nativeStreams?.streams || []),
158
+ ...(model?.schematicLibrary?.nativeStreams?.streams || []),
159
+ ...(model?.pcbLibrary?.nativeStreams?.streams || []),
160
+ ...((model?.integratedLibrary?.sources || []).flatMap(
161
+ (source) => source.nativeStreams?.streams || []
162
+ ) || [])
163
+ ]
164
+ )
165
+ ]
166
+ }
167
+
168
+ /**
169
+ * Normalizes models input.
170
+ * @param {object} input Build input.
171
+ * @returns {object[]}
172
+ */
173
+ static #models(input) {
174
+ return Array.isArray(input?.models) ? input.models : []
175
+ }
176
+
177
+ /**
178
+ * Builds a public raw-record row without copying raw payload bytes.
179
+ * @param {object} record Raw record.
180
+ * @returns {object}
181
+ */
182
+ static #recordRow(record) {
183
+ return RawDataPreservationReportBuilder.#stripUndefined({
184
+ source: record?.source,
185
+ sourceStorage: record?.sourceStorage,
186
+ sourceStream: record?.sourceStream,
187
+ family: record?.family || 'unknown',
188
+ type: record?.type || 'unknown',
189
+ typeId: RawDataPreservationReportBuilder.#finiteOrUndefined(
190
+ record?.typeId
191
+ ),
192
+ recordIndex: RawDataPreservationReportBuilder.#finiteOrUndefined(
193
+ record?.recordIndex
194
+ ),
195
+ byteLength: RawDataPreservationReportBuilder.#byteLength(record),
196
+ supported: Boolean(record?.supported),
197
+ parsed: Boolean(record?.parsed),
198
+ hasRawPayload: Boolean(record?.rawBase64)
199
+ })
200
+ }
201
+
202
+ /**
203
+ * Builds a public unknown-record row.
204
+ * @param {object} record Unknown record.
205
+ * @returns {object}
206
+ */
207
+ static #unknownRecordRow(record) {
208
+ return RawDataPreservationReportBuilder.#stripUndefined({
209
+ sourceStorage: record?.sourceStorage,
210
+ sourceStream: record?.sourceStream,
211
+ typeId: RawDataPreservationReportBuilder.#finiteOrUndefined(
212
+ record?.typeId
213
+ ),
214
+ recordIndex: RawDataPreservationReportBuilder.#finiteOrUndefined(
215
+ record?.recordIndex
216
+ ),
217
+ byteLength: RawDataPreservationReportBuilder.#byteLength(record)
218
+ })
219
+ }
220
+
221
+ /**
222
+ * Builds a public opaque-record row.
223
+ * @param {object} record Opaque record.
224
+ * @returns {object}
225
+ */
226
+ static #opaqueRecordRow(record) {
227
+ return RawDataPreservationReportBuilder.#stripUndefined({
228
+ source: record?.source,
229
+ sourceStorage: record?.sourceStorage,
230
+ sourceStream: record?.sourceStream,
231
+ frameType: RawDataPreservationReportBuilder.#finiteOrUndefined(
232
+ record?.frameType
233
+ ),
234
+ typeId: RawDataPreservationReportBuilder.#finiteOrUndefined(
235
+ record?.typeId
236
+ ),
237
+ recordIndex: RawDataPreservationReportBuilder.#finiteOrUndefined(
238
+ record?.recordIndex
239
+ ),
240
+ byteLength: RawDataPreservationReportBuilder.#byteLength(record),
241
+ hasRawPayload: Boolean(record?.rawBase64)
242
+ })
243
+ }
244
+
245
+ /**
246
+ * Builds a public native-stream inventory row.
247
+ * @param {object} stream Native stream row.
248
+ * @returns {object}
249
+ */
250
+ static #nativeStreamRow(stream) {
251
+ return RawDataPreservationReportBuilder.#stripUndefined({
252
+ source: stream?.source,
253
+ sourceStorage: stream?.sourceStorage,
254
+ sourceStream: stream?.sourceStream,
255
+ leafName: stream?.leafName,
256
+ byteLength: RawDataPreservationReportBuilder.#finiteOrUndefined(
257
+ stream?.byteLength
258
+ ),
259
+ known: Boolean(stream?.known),
260
+ consumed: Boolean(stream?.consumed),
261
+ classification: stream?.classification,
262
+ consumedBy: stream?.consumedBy,
263
+ checksum: stream?.checksum
264
+ })
265
+ }
266
+
267
+ /**
268
+ * Builds per-family summary rows.
269
+ * @param {object[]} records Raw record rows.
270
+ * @returns {object[]}
271
+ */
272
+ static #familyRows(records) {
273
+ const byFamily = new Map()
274
+ for (const record of records) {
275
+ const key = record.family || 'unknown'
276
+ const row = byFamily.get(key) || {
277
+ family: key,
278
+ rawRecordCount: 0,
279
+ supportedRawRecordCount: 0,
280
+ unsupportedRawRecordCount: 0,
281
+ preservedByteCount: 0
282
+ }
283
+ row.rawRecordCount += 1
284
+ row.preservedByteCount += Number(record.byteLength || 0)
285
+ if (record.supported) row.supportedRawRecordCount += 1
286
+ else row.unsupportedRawRecordCount += 1
287
+ byFamily.set(key, row)
288
+ }
289
+
290
+ return [...byFamily.values()].sort((left, right) =>
291
+ left.family.localeCompare(right.family)
292
+ )
293
+ }
294
+
295
+ /**
296
+ * Resolves a byte length from explicit metadata or payload size.
297
+ * @param {object} record Raw record.
298
+ * @returns {number}
299
+ */
300
+ static #byteLength(record) {
301
+ const explicit = Number(record?.byteLength)
302
+ if (Number.isFinite(explicit) && explicit >= 0) return explicit
303
+ if (typeof record?.rawBase64 === 'string') {
304
+ return RawDataPreservationReportBuilder.#base64ByteLength(
305
+ record.rawBase64
306
+ )
307
+ }
308
+ return 0
309
+ }
310
+
311
+ /**
312
+ * Computes decoded byte length for a base64 payload without decoding it.
313
+ * @param {string} value Base64 payload.
314
+ * @returns {number}
315
+ */
316
+ static #base64ByteLength(value) {
317
+ const normalized = String(value || '').replace(/\s/gu, '')
318
+ if (!normalized) return 0
319
+
320
+ const padding = normalized.endsWith('==')
321
+ ? 2
322
+ : normalized.endsWith('=')
323
+ ? 1
324
+ : 0
325
+ return Math.max(Math.floor((normalized.length * 3) / 4) - padding, 0)
326
+ }
327
+
328
+ /**
329
+ * Returns a finite number or undefined.
330
+ * @param {unknown} value Numeric value.
331
+ * @returns {number | undefined}
332
+ */
333
+ static #finiteOrUndefined(value) {
334
+ const numeric = Number(value)
335
+ return Number.isFinite(numeric) ? numeric : undefined
336
+ }
337
+
338
+ /**
339
+ * Removes undefined values from a row.
340
+ * @param {object} row Row to strip.
341
+ * @returns {object}
342
+ */
343
+ static #stripUndefined(row) {
344
+ return Object.fromEntries(
345
+ Object.entries(row).filter(([, value]) => value !== undefined)
346
+ )
347
+ }
348
+ }