altium-toolkit 1.0.8 → 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 (88) 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/styles/altium-renderers.css +19 -0
  77. package/src/ui/PcbBarcodeTextRenderer.mjs +436 -0
  78. package/src/ui/PcbInteractionIndex.mjs +9 -4
  79. package/src/ui/PcbScene3dBuilder.mjs +137 -3
  80. package/src/ui/PcbScene3dModelRegistry.mjs +74 -0
  81. package/src/ui/PcbSvgRenderer.mjs +1187 -34
  82. package/src/ui/PcbTextPrimitiveRenderer.mjs +193 -7
  83. package/src/ui/SchematicNoteRenderer.mjs +9 -2
  84. package/src/ui/SchematicOwnerPinLabelLayout.mjs +206 -0
  85. package/src/ui/SchematicShapeRenderer.mjs +362 -0
  86. package/src/ui/SchematicSvgRenderer.mjs +1442 -92
  87. package/src/ui/SchematicTypography.mjs +48 -5
  88. package/src/ui/TextGeometrySidecarBuilder.mjs +147 -0
@@ -9,19 +9,34 @@ export class SchematicTypography {
9
9
  /**
10
10
  * Returns true when the schematic already contains a visible designator text
11
11
  * close to one component origin.
12
- * @param {{ x?: number, y?: number }} component
12
+ * @param {{ x?: number, y?: number, designator?: string }} component
13
13
  * @param {{ x: number, y: number, name?: string }[]} texts
14
14
  * @returns {boolean}
15
15
  */
16
16
  static hasNearbyVisibleDesignatorText(component, texts) {
17
- return texts.some(
18
- (text) =>
17
+ const designatorPattern =
18
+ SchematicTypography.#componentDesignatorPattern(
19
+ component?.designator
20
+ )
21
+
22
+ return texts.some((text) => {
23
+ if (
19
24
  String(text.name || '')
20
25
  .trim()
21
- .toLowerCase() === 'designator' &&
26
+ .toLowerCase() !== 'designator'
27
+ ) {
28
+ return false
29
+ }
30
+
31
+ if (designatorPattern?.test(String(text.text || '').trim())) {
32
+ return true
33
+ }
34
+
35
+ return (
22
36
  Math.abs(Number(text.x) - Number(component.x)) <= 80 &&
23
37
  Math.abs(Number(text.y) - Number(component.y)) <= 80
24
- )
38
+ )
39
+ })
25
40
  }
26
41
 
27
42
  /**
@@ -57,6 +72,34 @@ export class SchematicTypography {
57
72
  )
58
73
  }
59
74
 
75
+ /**
76
+ * Builds a strict matcher for a base designator or one multipart suffix.
77
+ * @param {string | undefined} designator Component designator.
78
+ * @returns {RegExp | null}
79
+ */
80
+ static #componentDesignatorPattern(designator) {
81
+ const normalizedDesignator = String(designator || '').trim()
82
+ if (!normalizedDesignator || normalizedDesignator === 'U?') {
83
+ return null
84
+ }
85
+
86
+ return new RegExp(
87
+ '^' +
88
+ SchematicTypography.#escapeRegExp(normalizedDesignator) +
89
+ '[A-Z]?$',
90
+ 'i'
91
+ )
92
+ }
93
+
94
+ /**
95
+ * Escapes user-controlled text for inclusion in a regular expression.
96
+ * @param {string} value Source text.
97
+ * @returns {string}
98
+ */
99
+ static #escapeRegExp(value) {
100
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
101
+ }
102
+
60
103
  /**
61
104
  * Builds render options for one schematic text label, including the signed
62
105
  * SVG rotation derived from the original Altium orientation and mirrored
@@ -0,0 +1,147 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ /**
6
+ * Builds deterministic estimated text geometry sidecars for SVG exports.
7
+ */
8
+ export class TextGeometrySidecarBuilder {
9
+ static SCHEMA_ID = 'altium-toolkit.text-geometry.a1'
10
+ static #DEFAULT_WIDTH_RATIO = 0.6
11
+ static #HEIGHT_PADDING = 2
12
+
13
+ /**
14
+ * Builds sidecar metadata for schematic text rows.
15
+ * @param {object[]} texts Text rows.
16
+ * @param {Map<object, number>} textIndexes Stable text index map.
17
+ * @returns {{ schema: string, entries: object[] }}
18
+ */
19
+ static buildSchematic(texts, textIndexes) {
20
+ return TextGeometrySidecarBuilder.#build(texts, textIndexes, {
21
+ prefix: 'schematic-text-',
22
+ defaultFontSize: 10,
23
+ defaultFontFamily: ''
24
+ })
25
+ }
26
+
27
+ /**
28
+ * Builds sidecar metadata for PCB text rows.
29
+ * @param {object[]} texts Text rows.
30
+ * @param {Map<object, number>} textIndexes Stable text index map.
31
+ * @returns {{ schema: string, entries: object[] }}
32
+ */
33
+ static buildPcb(texts, textIndexes) {
34
+ return TextGeometrySidecarBuilder.#build(texts, textIndexes, {
35
+ prefix: 'pcb-text-',
36
+ defaultFontSize: 10,
37
+ defaultFontFamily: ''
38
+ })
39
+ }
40
+
41
+ /**
42
+ * Builds generic text sidecar metadata.
43
+ * @param {object[]} texts Text rows.
44
+ * @param {Map<object, number>} textIndexes Stable text index map.
45
+ * @param {{ prefix: string, defaultFontSize: number, defaultFontFamily: string }} options Build options.
46
+ * @returns {{ schema: string, entries: object[] }}
47
+ */
48
+ static #build(texts, textIndexes, options) {
49
+ return {
50
+ schema: TextGeometrySidecarBuilder.SCHEMA_ID,
51
+ entries: (texts || [])
52
+ .map((text, fallbackIndex) =>
53
+ TextGeometrySidecarBuilder.#entry(
54
+ text,
55
+ textIndexes,
56
+ fallbackIndex,
57
+ options
58
+ )
59
+ )
60
+ .filter(Boolean)
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Builds one text sidecar entry.
66
+ * @param {object} text Text row.
67
+ * @param {Map<object, number>} textIndexes Stable text index map.
68
+ * @param {number} fallbackIndex Fallback text index.
69
+ * @param {{ prefix: string, defaultFontSize: number, defaultFontFamily: string }} options Build options.
70
+ * @returns {object | null}
71
+ */
72
+ static #entry(text, textIndexes, fallbackIndex, options) {
73
+ const value = String(text?.resolvedText || text?.text || '')
74
+ if (!value.trim()) {
75
+ return null
76
+ }
77
+
78
+ const index = textIndexes?.get(text) ?? fallbackIndex
79
+ const fontSize = Number(text?.fontSize || text?.height || 0) || 10
80
+ const width = value.length * fontSize * this.#DEFAULT_WIDTH_RATIO
81
+ const height = fontSize + this.#HEIGHT_PADDING
82
+ const x = Number(text?.x || 0)
83
+ const y = Number(text?.y || 0)
84
+
85
+ return TextGeometrySidecarBuilder.#stripEmpty({
86
+ elementKey: options.prefix + index,
87
+ recordId: TextGeometrySidecarBuilder.#recordId(text),
88
+ text: value,
89
+ fontFamily: text?.fontFamily || options.defaultFontFamily,
90
+ fontSize,
91
+ fontWeight: Number(text?.fontWeight || 400),
92
+ geometryKind: 'estimated-bounds-polygon',
93
+ polygon: [
94
+ { x, y },
95
+ { x: TextGeometrySidecarBuilder.#round(x + width), y },
96
+ {
97
+ x: TextGeometrySidecarBuilder.#round(x + width),
98
+ y: TextGeometrySidecarBuilder.#round(y - height)
99
+ },
100
+ { x, y: TextGeometrySidecarBuilder.#round(y - height) }
101
+ ]
102
+ })
103
+ }
104
+
105
+ /**
106
+ * Resolves a stable source record id for one text row.
107
+ * @param {object} text Text row.
108
+ * @returns {string}
109
+ */
110
+ static #recordId(text) {
111
+ const recordId =
112
+ text?.recordId ?? text?.sourceRecordId ?? text?.sourceRecordIndex
113
+
114
+ return recordId === null || recordId === undefined
115
+ ? ''
116
+ : String(recordId)
117
+ }
118
+
119
+ /**
120
+ * Rounds numbers to a concise JSON-friendly precision.
121
+ * @param {number} value Raw number.
122
+ * @returns {number}
123
+ */
124
+ static #round(value) {
125
+ return Number(Number(value).toFixed(4))
126
+ }
127
+
128
+ /**
129
+ * Removes empty fields while preserving zero and false.
130
+ * @param {Record<string, unknown>} value Candidate entry.
131
+ * @returns {Record<string, unknown>}
132
+ */
133
+ static #stripEmpty(value) {
134
+ return Object.fromEntries(
135
+ Object.entries(value || {}).filter(([, entryValue]) => {
136
+ if (Array.isArray(entryValue)) {
137
+ return entryValue.length > 0
138
+ }
139
+ return (
140
+ entryValue !== null &&
141
+ entryValue !== undefined &&
142
+ entryValue !== ''
143
+ )
144
+ })
145
+ )
146
+ }
147
+ }