altium-toolkit 1.0.7 → 1.0.9

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 (93) hide show
  1. package/README.md +18 -6
  2. package/docs/api.md +78 -16
  3. package/docs/model-format.md +229 -8
  4. package/docs/schemas/altium_toolkit/netlist_a1.schema.json +47 -0
  5. package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +1661 -104
  6. package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +59 -0
  7. package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +57 -0
  8. package/docs/schemas/altium_toolkit/schematic_svg_semantics_a1.schema.json +50 -0
  9. package/docs/testing.md +9 -3
  10. package/package.json +1 -1
  11. package/spec/library-scope.md +7 -1
  12. package/src/core/altium/AltiumLayoutParser.mjs +104 -8
  13. package/src/core/altium/AltiumParser.mjs +191 -45
  14. package/src/core/altium/EmbeddedFileInventoryBuilder.mjs +255 -0
  15. package/src/core/altium/IntLibModelParser.mjs +240 -0
  16. package/src/core/altium/IntLibStreamExtractor.mjs +366 -0
  17. package/src/core/altium/LibraryRenderManifestBuilder.mjs +417 -0
  18. package/src/core/altium/LibrarySearchIndex.mjs +215 -0
  19. package/src/core/altium/NormalizedModelSchema.mjs +36 -0
  20. package/src/core/altium/PcbCustomPadShapeParser.mjs +244 -0
  21. package/src/core/altium/PcbDefaultsParser.mjs +171 -0
  22. package/src/core/altium/PcbDimensionParser.mjs +229 -0
  23. package/src/core/altium/PcbEmbeddedModelExtractor.mjs +232 -6
  24. package/src/core/altium/PcbExtendedPrimitiveInformationParser.mjs +256 -0
  25. package/src/core/altium/PcbLibModelParser.mjs +235 -14
  26. package/src/core/altium/PcbLibStreamExtractor.mjs +62 -4
  27. package/src/core/altium/PcbMaskPasteResolver.mjs +354 -0
  28. package/src/core/altium/PcbMechanicalLayerPairParser.mjs +204 -0
  29. package/src/core/altium/PcbModelParser.mjs +466 -28
  30. package/src/core/altium/PcbOwnershipGraphBuilder.mjs +245 -0
  31. package/src/core/altium/PcbPadPrimitiveParser.mjs +78 -65
  32. package/src/core/altium/PcbPadStackParser.mjs +58 -0
  33. package/src/core/altium/PcbPickPlacePositionResolver.mjs +217 -0
  34. package/src/core/altium/PcbPrimitiveParameterParser.mjs +3 -2
  35. package/src/core/altium/PcbRawRecordRegistry.mjs +121 -130
  36. package/src/core/altium/PcbRegionPrimitiveParser.mjs +5 -1
  37. package/src/core/altium/PcbRuleParser.mjs +354 -33
  38. package/src/core/altium/PcbSidecarRecordParser.mjs +177 -0
  39. package/src/core/altium/PcbSpecialStringResolver.mjs +220 -0
  40. package/src/core/altium/PcbStatisticsBuilder.mjs +532 -0
  41. package/src/core/altium/PcbStreamExtractor.mjs +111 -4
  42. package/src/core/altium/PcbTextPrimitiveParser.mjs +60 -0
  43. package/src/core/altium/PcbUnionParser.mjs +307 -0
  44. package/src/core/altium/PcbViaStackParser.mjs +98 -10
  45. package/src/core/altium/PcbViaStructureParser.mjs +335 -0
  46. package/src/core/altium/PrintableTextDecoder.mjs +53 -3
  47. package/src/core/altium/PrjPcbModelParser.mjs +257 -5
  48. package/src/core/altium/ProjectAnnotationParser.mjs +205 -0
  49. package/src/core/altium/ProjectDesignBundleBuilder.mjs +477 -0
  50. package/src/core/altium/ProjectNetlistExporter.mjs +499 -0
  51. package/src/core/altium/ProjectOutJobDigestBuilder.mjs +109 -0
  52. package/src/core/altium/ProjectVariantViewBuilder.mjs +334 -0
  53. package/src/core/altium/SchematicBindingProvenanceParser.mjs +223 -0
  54. package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +312 -0
  55. package/src/core/altium/SchematicComponentTextResolver.mjs +72 -19
  56. package/src/core/altium/SchematicConnectivityQaBuilder.mjs +271 -0
  57. package/src/core/altium/SchematicCrossSheetConnectorParser.mjs +140 -0
  58. package/src/core/altium/SchematicDirectiveParser.mjs +312 -0
  59. package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +231 -0
  60. package/src/core/altium/SchematicHarnessParser.mjs +302 -0
  61. package/src/core/altium/SchematicImageParser.mjs +474 -3
  62. package/src/core/altium/SchematicImplementationParser.mjs +518 -0
  63. package/src/core/altium/SchematicNetlistBuilder.mjs +15 -2
  64. package/src/core/altium/SchematicOwnershipGraphParser.mjs +195 -0
  65. package/src/core/altium/SchematicPinParser.mjs +84 -1
  66. package/src/core/altium/SchematicPrimitiveParser.mjs +301 -0
  67. package/src/core/altium/SchematicProjectParameterResolver.mjs +361 -0
  68. package/src/core/altium/SchematicQaReportBuilder.mjs +284 -0
  69. package/src/core/altium/SchematicRecordTypeRegistry.mjs +137 -0
  70. package/src/core/altium/SchematicRepeatedChannelParser.mjs +229 -0
  71. package/src/core/altium/SchematicStreamExtractor.mjs +10 -1
  72. package/src/core/altium/SchematicTemplateParser.mjs +256 -0
  73. package/src/core/altium/SchematicTextParser.mjs +123 -0
  74. package/src/core/ole/OleCompoundDocument.mjs +20 -0
  75. package/src/parser.mjs +29 -0
  76. package/src/renderers.mjs +3 -0
  77. package/src/styles/altium-renderers.css +25 -0
  78. package/src/ui/PcbBarcodeTextRenderer.mjs +436 -0
  79. package/src/ui/PcbInteractionGeometry.mjs +350 -0
  80. package/src/ui/PcbInteractionIndex.mjs +593 -0
  81. package/src/ui/PcbInteractionItemRegistry.mjs +66 -0
  82. package/src/ui/PcbInteractionLayerModel.mjs +99 -0
  83. package/src/ui/PcbScene3dBoardOutlineRefiner.mjs +74 -9
  84. package/src/ui/PcbScene3dBuilder.mjs +169 -7
  85. package/src/ui/PcbScene3dModelRegistry.mjs +74 -0
  86. package/src/ui/PcbSvgRenderer.mjs +1187 -34
  87. package/src/ui/PcbTextPrimitiveRenderer.mjs +193 -7
  88. package/src/ui/SchematicNoteRenderer.mjs +9 -2
  89. package/src/ui/SchematicOwnerPinLabelLayout.mjs +206 -0
  90. package/src/ui/SchematicShapeRenderer.mjs +362 -0
  91. package/src/ui/SchematicSvgRenderer.mjs +1442 -92
  92. package/src/ui/SchematicTypography.mjs +48 -5
  93. package/src/ui/TextGeometrySidecarBuilder.mjs +147 -0
@@ -0,0 +1,436 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import { SchematicSvgUtils } from './SchematicSvgUtils.mjs'
6
+
7
+ /**
8
+ * Renders PCB barcode text primitives as deterministic SVG bar groups.
9
+ */
10
+ export class PcbBarcodeTextRenderer {
11
+ static #DEFAULT_MODULE_WIDTH = 2
12
+ static #DEFAULT_HEIGHT_RATIO = 1.8
13
+ static #CAPTION_RATIO = 0.34
14
+
15
+ static #CODE39_PATTERNS = new Map([
16
+ ['0', '101001101101'],
17
+ ['1', '110100101011'],
18
+ ['2', '101100101011'],
19
+ ['3', '110110010101'],
20
+ ['4', '101001101011'],
21
+ ['5', '110100110101'],
22
+ ['6', '101100110101'],
23
+ ['7', '101001011011'],
24
+ ['8', '110100101101'],
25
+ ['9', '101100101101'],
26
+ ['A', '110101001011'],
27
+ ['B', '101101001011'],
28
+ ['C', '110110100101'],
29
+ ['D', '101011001011'],
30
+ ['E', '110101100101'],
31
+ ['F', '101101100101'],
32
+ ['G', '101010011011'],
33
+ ['H', '110101001101'],
34
+ ['I', '101101001101'],
35
+ ['J', '101011001101'],
36
+ ['K', '110101010011'],
37
+ ['L', '101101010011'],
38
+ ['M', '110110101001'],
39
+ ['N', '101011010011'],
40
+ ['O', '110101101001'],
41
+ ['P', '101101101001'],
42
+ ['Q', '101010110011'],
43
+ ['R', '110101011001'],
44
+ ['S', '101101011001'],
45
+ ['T', '101011011001'],
46
+ ['U', '110010101011'],
47
+ ['V', '100110101011'],
48
+ ['W', '110011010101'],
49
+ ['X', '100101101011'],
50
+ ['Y', '110010110101'],
51
+ ['Z', '100110110101'],
52
+ ['-', '100101011011'],
53
+ ['.', '110010101101'],
54
+ [' ', '100110101101'],
55
+ ['$', '100100100101'],
56
+ ['/', '100100101001'],
57
+ ['+', '100101001001'],
58
+ ['%', '101001001001'],
59
+ ['*', '100101101101']
60
+ ])
61
+
62
+ static #CODE128_PATTERNS = [
63
+ '11011001100',
64
+ '11001101100',
65
+ '11001100110',
66
+ '10010011000',
67
+ '10010001100',
68
+ '10001001100',
69
+ '10011001000',
70
+ '10011000100',
71
+ '10001100100',
72
+ '11001001000',
73
+ '11001000100',
74
+ '11000100100',
75
+ '10110011100',
76
+ '10011011100',
77
+ '10011001110',
78
+ '10111001100',
79
+ '10011101100',
80
+ '10011100110',
81
+ '11001110010',
82
+ '11001011100',
83
+ '11001001110',
84
+ '11011100100',
85
+ '11001110100',
86
+ '11101101110',
87
+ '11101001100',
88
+ '11100101100',
89
+ '11100100110',
90
+ '11101100100',
91
+ '11100110100',
92
+ '11100110010',
93
+ '11011011000',
94
+ '11011000110',
95
+ '11000110110',
96
+ '10100011000',
97
+ '10001011000',
98
+ '10001000110',
99
+ '10110001000',
100
+ '10001101000',
101
+ '10001100010',
102
+ '11010001000',
103
+ '11000101000',
104
+ '11000100010',
105
+ '10110111000',
106
+ '10110001110',
107
+ '10001101110',
108
+ '10111011000',
109
+ '10111000110',
110
+ '10001110110',
111
+ '11101110110',
112
+ '11010001110',
113
+ '11000101110',
114
+ '11011101000',
115
+ '11011100010',
116
+ '11011101110',
117
+ '11101011000',
118
+ '11101000110',
119
+ '11100010110',
120
+ '11101101000',
121
+ '11101100010',
122
+ '11100011010',
123
+ '11101111010',
124
+ '11001000010',
125
+ '11110001010',
126
+ '10100110000',
127
+ '10100001100',
128
+ '10010110000',
129
+ '10010000110',
130
+ '10000101100',
131
+ '10000100110',
132
+ '10110010000',
133
+ '10110000100',
134
+ '10011010000',
135
+ '10011000010',
136
+ '10000110100',
137
+ '10000110010',
138
+ '11000010010',
139
+ '11001010000',
140
+ '11110111010',
141
+ '11000010100',
142
+ '10001111010',
143
+ '10100111100',
144
+ '10010111100',
145
+ '10010011110',
146
+ '10111100100',
147
+ '10011110100',
148
+ '10011110010',
149
+ '11110100100',
150
+ '11110010100',
151
+ '11110010010',
152
+ '11011011110',
153
+ '11011110110',
154
+ '11110110110',
155
+ '10101111000',
156
+ '10100011110',
157
+ '10001011110',
158
+ '10111101000',
159
+ '10111100010',
160
+ '11110101000',
161
+ '11110100010',
162
+ '10111011110',
163
+ '10111101110',
164
+ '11101011110',
165
+ '11110101110',
166
+ '11010000100',
167
+ '11010010000',
168
+ '11010011100',
169
+ '1100011101011'
170
+ ]
171
+
172
+ /**
173
+ * Renders one barcode text primitive.
174
+ * @param {{ text: string, layerId?: number, barcode?: object }} text Text primitive.
175
+ * @param {{ transform: string, fontSize: number, semanticAttributes: string }} options Render options.
176
+ * @returns {string}
177
+ */
178
+ static render(text, options) {
179
+ const encoding = PcbBarcodeTextRenderer.#encoding(text)
180
+ const layout = PcbBarcodeTextRenderer.#layout(
181
+ text,
182
+ options.fontSize,
183
+ encoding.pattern.length
184
+ )
185
+ const bars = PcbBarcodeTextRenderer.#bars(encoding.pattern, layout)
186
+ const className =
187
+ 'pcb-text pcb-text--layer-' +
188
+ SchematicSvgUtils.escapeHtml(String(Number(text.layerId || 0))) +
189
+ ' pcb-text--barcode' +
190
+ (text?.barcode?.inverted ? ' pcb-text--barcode-inverted' : '')
191
+ const background = text?.barcode?.inverted
192
+ ? '<rect class="pcb-barcode__background" x="0" y="0" width="' +
193
+ SchematicSvgUtils.formatNumber(layout.width) +
194
+ '" height="' +
195
+ SchematicSvgUtils.formatNumber(layout.height) +
196
+ '" />'
197
+ : ''
198
+ const caption = text?.barcode?.showText
199
+ ? PcbBarcodeTextRenderer.#caption(text, layout)
200
+ : ''
201
+
202
+ return (
203
+ '<g class="' +
204
+ className +
205
+ '" transform="' +
206
+ options.transform +
207
+ '"' +
208
+ options.semanticAttributes +
209
+ ' data-barcode-symbology="' +
210
+ SchematicSvgUtils.escapeHtml(encoding.symbology) +
211
+ '" data-barcode-module-count="' +
212
+ SchematicSvgUtils.escapeHtml(String(encoding.pattern.length)) +
213
+ '"' +
214
+ '>' +
215
+ background +
216
+ '<g class="pcb-barcode__bars" transform="translate(' +
217
+ SchematicSvgUtils.formatNumber(layout.marginX) +
218
+ ' ' +
219
+ SchematicSvgUtils.formatNumber(layout.marginY) +
220
+ ')">' +
221
+ bars +
222
+ '</g>' +
223
+ caption +
224
+ '</g>'
225
+ )
226
+ }
227
+
228
+ /**
229
+ * Resolves barcode layout dimensions.
230
+ * @param {{ text: string, height?: number, barcode?: object }} text Text primitive.
231
+ * @param {number} fontSize Resolved text font size.
232
+ * @param {number} patternLength Encoded module count.
233
+ * @returns {{ width: number, height: number, barHeight: number, marginX: number, marginY: number, moduleWidth: number }}
234
+ */
235
+ static #layout(text, fontSize, patternLength) {
236
+ const barcode = text?.barcode || {}
237
+ const moduleWidth = Math.max(
238
+ Number(barcode.minBarWidth) ||
239
+ PcbBarcodeTextRenderer.#DEFAULT_MODULE_WIDTH,
240
+ 0.1
241
+ )
242
+ const contentWidth = patternLength * moduleWidth
243
+ const marginX = Math.max(Number(barcode.marginX) || moduleWidth, 0)
244
+ const marginY = Math.max(Number(barcode.marginY) || moduleWidth, 0)
245
+ const width = Math.max(
246
+ Number(barcode.fullWidth) || 0,
247
+ contentWidth + marginX * 2
248
+ )
249
+ const height = Math.max(
250
+ Number(barcode.fullHeight) || 0,
251
+ fontSize * PcbBarcodeTextRenderer.#DEFAULT_HEIGHT_RATIO
252
+ )
253
+ const captionHeight = barcode.showText
254
+ ? fontSize * PcbBarcodeTextRenderer.#CAPTION_RATIO
255
+ : 0
256
+
257
+ return {
258
+ width,
259
+ height,
260
+ barHeight: Math.max(height - marginY * 2 - captionHeight, 1),
261
+ marginX,
262
+ marginY,
263
+ moduleWidth:
264
+ contentWidth > 0
265
+ ? Math.max((width - marginX * 2) / patternLength, 0.1)
266
+ : moduleWidth
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Renders barcode bars.
272
+ * @param {string} pattern Encoded barcode module pattern.
273
+ * @param {{ barHeight: number, moduleWidth: number }} layout Barcode layout.
274
+ * @returns {string}
275
+ */
276
+ static #bars(pattern, layout) {
277
+ const runs = []
278
+ let cursor = 0
279
+
280
+ while (cursor < pattern.length) {
281
+ const value = pattern[cursor]
282
+ let length = 1
283
+ while (pattern[cursor + length] === value) {
284
+ length += 1
285
+ }
286
+ if (value === '1') {
287
+ runs.push({ offset: cursor, length })
288
+ }
289
+ cursor += length
290
+ }
291
+
292
+ return runs
293
+ .map(
294
+ (run) =>
295
+ '<rect class="pcb-barcode__bar" x="' +
296
+ SchematicSvgUtils.formatNumber(
297
+ run.offset * layout.moduleWidth
298
+ ) +
299
+ '" y="0" width="' +
300
+ SchematicSvgUtils.formatNumber(
301
+ run.length * layout.moduleWidth
302
+ ) +
303
+ '" height="' +
304
+ SchematicSvgUtils.formatNumber(layout.barHeight) +
305
+ '" />'
306
+ )
307
+ .join('')
308
+ }
309
+
310
+ /**
311
+ * Renders optional human-readable barcode text.
312
+ * @param {{ text: string }} text Text primitive.
313
+ * @param {{ width: number, height: number, marginY: number }} layout Barcode layout.
314
+ * @returns {string}
315
+ */
316
+ static #caption(text, layout) {
317
+ const fontSize = Math.max(layout.height * 0.16, 4)
318
+
319
+ return (
320
+ '<text class="pcb-barcode__caption" x="' +
321
+ SchematicSvgUtils.formatNumber(layout.width / 2) +
322
+ '" y="' +
323
+ SchematicSvgUtils.formatNumber(layout.height - layout.marginY) +
324
+ '" font-size="' +
325
+ SchematicSvgUtils.formatNumber(fontSize) +
326
+ '" text-anchor="middle" dominant-baseline="alphabetic">' +
327
+ SchematicSvgUtils.escapeHtml(String(text.text || '')) +
328
+ '</text>'
329
+ )
330
+ }
331
+
332
+ /**
333
+ * Encodes one barcode payload.
334
+ * @param {{ text: string, barcode?: object }} text Text primitive.
335
+ * @returns {{ pattern: string, symbology: string }}
336
+ */
337
+ static #encoding(text) {
338
+ const kind = String(text?.barcode?.kindName || '').toLowerCase()
339
+ if (kind === 'code39') {
340
+ return PcbBarcodeTextRenderer.#encodeCode39(text)
341
+ }
342
+ if (kind === 'code128') {
343
+ return PcbBarcodeTextRenderer.#encodeCode128B(text)
344
+ }
345
+
346
+ return {
347
+ pattern: PcbBarcodeTextRenderer.#fallbackPattern(text),
348
+ symbology: 'deterministic'
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Encodes valid Code 39 content.
354
+ * @param {{ text: string }} text Text primitive.
355
+ * @returns {{ pattern: string, symbology: string }}
356
+ */
357
+ static #encodeCode39(text) {
358
+ const content = String(text?.text || '').toUpperCase()
359
+ const encoded = '*' + content + '*'
360
+ const parts = []
361
+
362
+ for (const character of encoded) {
363
+ const pattern =
364
+ PcbBarcodeTextRenderer.#CODE39_PATTERNS.get(character)
365
+ if (!pattern) {
366
+ return {
367
+ pattern: PcbBarcodeTextRenderer.#fallbackPattern(text),
368
+ symbology: 'deterministic'
369
+ }
370
+ }
371
+ parts.push(pattern)
372
+ }
373
+
374
+ return {
375
+ pattern: parts.join('0'),
376
+ symbology: 'Code 39'
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Encodes printable ASCII content as Code 128 set B.
382
+ * @param {{ text: string }} text Text primitive.
383
+ * @returns {{ pattern: string, symbology: string }}
384
+ */
385
+ static #encodeCode128B(text) {
386
+ const values = [104]
387
+ for (const character of String(text?.text || '')) {
388
+ const codePoint = character.codePointAt(0) || 63
389
+ const value =
390
+ codePoint >= 32 && codePoint <= 127 ? codePoint - 32 : 31
391
+ values.push(value)
392
+ }
393
+
394
+ let checksum = values[0]
395
+ for (let index = 1; index < values.length; index += 1) {
396
+ checksum += values[index] * index
397
+ }
398
+ values.push(checksum % 103)
399
+ values.push(106)
400
+
401
+ return {
402
+ pattern: values
403
+ .map((value) => PcbBarcodeTextRenderer.#CODE128_PATTERNS[value])
404
+ .join(''),
405
+ symbology: 'Code 128B'
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Builds a deterministic fallback module pattern for unsupported content.
411
+ * @param {{ text: string }} text Text primitive.
412
+ * @returns {string}
413
+ */
414
+ static #fallbackPattern(text) {
415
+ const value = String(text?.text || '')
416
+ const parts = ['11010010000']
417
+ for (const character of value) {
418
+ parts.push(
419
+ PcbBarcodeTextRenderer.#fallbackCharacterPattern(character)
420
+ )
421
+ }
422
+ parts.push('1100011101011')
423
+ return parts.join('0')
424
+ }
425
+
426
+ /**
427
+ * Builds a stable 11-module fallback pattern for one character.
428
+ * @param {string} character Barcode character.
429
+ * @returns {string}
430
+ */
431
+ static #fallbackCharacterPattern(character) {
432
+ const code = character.codePointAt(0) || 0
433
+ const mixed = (code * 1103515245 + 12345) >>> 0
434
+ return mixed.toString(2).padStart(32, '0').slice(0, 11)
435
+ }
436
+ }