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
@@ -57,6 +57,39 @@ export class SchematicTextParser {
57
57
  return fonts
58
58
  }
59
59
 
60
+ /**
61
+ * Extracts deterministic render diagnostics for schematic sheet fonts.
62
+ * @param {Record<string, string | string[]> | undefined} fields
63
+ * @returns {{ schema: string, fontFallbacks: object[] }}
64
+ */
65
+ static extractSchematicFontDiagnostics(fields) {
66
+ const count = ParserUtils.parseNumericField(fields, 'FontIdCount') || 0
67
+ const fontFallbacks = []
68
+
69
+ for (let index = 1; index <= count; index += 1) {
70
+ const rawFamily = ParserUtils.getField(fields, 'FontName' + index)
71
+ if (!SchematicTextParser.#needsFontFamilyFallback(rawFamily)) {
72
+ continue
73
+ }
74
+
75
+ fontFallbacks.push({
76
+ code: 'schematic.font.family-fallback',
77
+ severity: 'warning',
78
+ fontId: String(index),
79
+ sourceFamily: rawFamily,
80
+ resolvedFamily:
81
+ SchematicTextParser.#sanitizeFontFamily(rawFamily),
82
+ message:
83
+ 'Schematic font family was missing or malformed and was replaced for deterministic SVG rendering.'
84
+ })
85
+ }
86
+
87
+ return {
88
+ schema: 'altium-toolkit.schematic.render-diagnostics.a1',
89
+ fontFallbacks
90
+ }
91
+ }
92
+
60
93
  /**
61
94
  * Normalizes one schematic text record into a drawable text node.
62
95
  * @param {Record<string, string | string[]>} fields
@@ -119,6 +152,8 @@ export class SchematicTextParser {
119
152
  ownerIndex: ParserUtils.getField(fields, 'OwnerIndex') || undefined,
120
153
  recordType,
121
154
  style: ParserUtils.parseNumericField(fields, 'Style') || 0,
155
+ renderOrder:
156
+ ParserUtils.parseNumericField(fields, 'IndexInSheet') ?? 0,
122
157
  fontSize: SchematicTextParser.#toSvgFontSize(font.size),
123
158
  fontFamily: font.family,
124
159
  fontWeight: font.bold ? 700 : 400,
@@ -152,6 +187,19 @@ export class SchematicTextParser {
152
187
  return textRecord
153
188
  }
154
189
 
190
+ /**
191
+ * Builds a normalized text-frame read model from visible text records.
192
+ * @param {object[]} texts Normalized schematic text records.
193
+ * @returns {object[]}
194
+ */
195
+ static extractSchematicTextFrames(texts) {
196
+ return (texts || [])
197
+ .filter((text) => text?.recordType === '28')
198
+ .map((text) =>
199
+ SchematicTextParser.#normalizeTextFrameContract(text)
200
+ )
201
+ }
202
+
155
203
  /**
156
204
  * Extracts footer metadata used for the synthesized title block.
157
205
  * @param {{ fields: Record<string, string | string[]> }[]} records
@@ -623,6 +671,17 @@ export class SchematicTextParser {
623
671
  return normalized
624
672
  }
625
673
 
674
+ /**
675
+ * Returns true when the font family must be replaced for SVG output.
676
+ * @param {string} family Raw font family value.
677
+ * @returns {boolean}
678
+ */
679
+ static #needsFontFamilyFallback(family) {
680
+ const normalized = String(family || '').trim()
681
+
682
+ return !normalized || /["|]/.test(normalized)
683
+ }
684
+
626
685
  /**
627
686
  * Returns the default schematic font when no sheet font entry exists.
628
687
  * @returns {{ size: number, family: string, bold: boolean, italic: boolean, rotation: number }}
@@ -697,6 +756,8 @@ export class SchematicTextParser {
697
756
  fields.Color || fields.TextColor,
698
757
  '#7b7753'
699
758
  ),
759
+ lineWidth:
760
+ ParserUtils.parseNumericField(fields, 'LineWidth') ?? undefined,
700
761
  isSolid: ParserUtils.parseBoolean(fields.IsSolid),
701
762
  showBorder: ParserUtils.parseBoolean(fields.ShowBorder),
702
763
  textMargin:
@@ -705,6 +766,68 @@ export class SchematicTextParser {
705
766
  }
706
767
  }
707
768
 
769
+ /**
770
+ * Converts one rendered text-frame record into an explicit read model.
771
+ * @param {object} text Normalized text-frame text record.
772
+ * @returns {object}
773
+ */
774
+ static #normalizeTextFrameContract(text) {
775
+ const left = Math.min(Number(text.x || 0), Number(text.cornerX || 0))
776
+ const right = Math.max(Number(text.x || 0), Number(text.cornerX || 0))
777
+ const top = Math.max(Number(text.y || 0), Number(text.cornerY || 0))
778
+ const bottom = Math.min(Number(text.y || 0), Number(text.cornerY || 0))
779
+
780
+ return SchematicTextParser.#stripUndefined({
781
+ x: text.x,
782
+ y: text.y,
783
+ cornerX: text.cornerX,
784
+ cornerY: text.cornerY,
785
+ width: right - left,
786
+ height: top - bottom,
787
+ text: text.text,
788
+ alignment: SchematicTextParser.#alignmentFromAnchor(text.anchor),
789
+ borderWidth: text.lineWidth || 1,
790
+ color: text.color,
791
+ borderColor: text.borderColor,
792
+ fill: text.fill,
793
+ isSolid: text.isSolid,
794
+ showBorder: text.showBorder,
795
+ font: {
796
+ size: text.fontSize,
797
+ family: text.fontFamily,
798
+ weight: text.fontWeight,
799
+ ...(text.fontStyle ? { style: text.fontStyle } : {})
800
+ },
801
+ textMargin: text.textMargin,
802
+ renderOrder: text.renderOrder,
803
+ ownerIndex: text.ownerIndex
804
+ })
805
+ }
806
+
807
+ /**
808
+ * Converts SVG text-anchor naming to a read-model alignment label.
809
+ * @param {string | undefined} anchor Text anchor.
810
+ * @returns {'left' | 'center' | 'right'}
811
+ */
812
+ static #alignmentFromAnchor(anchor) {
813
+ if (anchor === 'middle') return 'center'
814
+ if (anchor === 'end') return 'right'
815
+ return 'left'
816
+ }
817
+
818
+ /**
819
+ * Removes undefined values from one object.
820
+ * @param {object} value Source object.
821
+ * @returns {object}
822
+ */
823
+ static #stripUndefined(value) {
824
+ return Object.fromEntries(
825
+ Object.entries(value || {}).filter(
826
+ ([, entryValue]) => entryValue !== undefined
827
+ )
828
+ )
829
+ }
830
+
708
831
  /**
709
832
  * Decodes Altium note control codes into visible text rows.
710
833
  * @param {string} text
@@ -30,6 +30,7 @@ export class OleCompoundDocument {
30
30
  constructor(arrayBuffer) {
31
31
  this.#reader = new BinaryReader(arrayBuffer)
32
32
  this.#header = this.#parseHeader()
33
+ this.#assertSectorAlignedFile()
33
34
  this.#fatEntries = this.#parseFatEntries()
34
35
  this.#directoryEntries = this.#parseDirectoryEntries()
35
36
  this.#miniFatEntries = this.#parseMiniFatEntries()
@@ -137,6 +138,25 @@ export class OleCompoundDocument {
137
138
  }
138
139
  }
139
140
 
141
+ /**
142
+ * Ensures the byte stream still matches the OLE sector grid.
143
+ */
144
+ #assertSectorAlignedFile() {
145
+ const payloadByteLength =
146
+ this.#reader.byteLength - OleConstants.HEADER_BYTE_LENGTH
147
+
148
+ if (
149
+ payloadByteLength < 0 ||
150
+ payloadByteLength % this.#header.sectorByteLength !== 0
151
+ ) {
152
+ throw new Error(
153
+ 'OLE compound document byte length is not sector-aligned. ' +
154
+ 'The file may be truncated or line-ending normalized; ' +
155
+ 'keep Altium binary files committed and served as binary.'
156
+ )
157
+ }
158
+ }
159
+
140
160
  /**
141
161
  * Parses all FAT entries.
142
162
  * @returns {number[]}
package/src/parser.mjs CHANGED
@@ -9,28 +9,57 @@ export { OleDirectoryEntry } from './core/ole/OleDirectoryEntry.mjs'
9
9
  export { AltiumParser } from './core/altium/AltiumParser.mjs'
10
10
  export { AltiumLayoutParser } from './core/altium/AltiumLayoutParser.mjs'
11
11
  export { AsciiRecordParser } from './core/altium/AsciiRecordParser.mjs'
12
+ export { IntLibModelParser } from './core/altium/IntLibModelParser.mjs'
13
+ export { IntLibStreamExtractor } from './core/altium/IntLibStreamExtractor.mjs'
14
+ export { EmbeddedFileInventoryBuilder } from './core/altium/EmbeddedFileInventoryBuilder.mjs'
15
+ export { LibraryRenderManifestBuilder } from './core/altium/LibraryRenderManifestBuilder.mjs'
16
+ export { LibrarySearchIndex } from './core/altium/LibrarySearchIndex.mjs'
12
17
  export { ParserUtils } from './core/altium/ParserUtils.mjs'
13
18
  export { NormalizedModelSchema } from './core/altium/NormalizedModelSchema.mjs'
19
+ export { ProjectDesignBundleBuilder } from './core/altium/ProjectDesignBundleBuilder.mjs'
20
+ export { ProjectAnnotationParser } from './core/altium/ProjectAnnotationParser.mjs'
21
+ export { ProjectNetlistExporter } from './core/altium/ProjectNetlistExporter.mjs'
22
+ export { ProjectVariantViewBuilder } from './core/altium/ProjectVariantViewBuilder.mjs'
14
23
  export { CircuitJsonModelSchema } from './core/circuit-json/CircuitJsonModelSchema.mjs'
15
24
  export { CircuitJsonModelAdapter } from './core/circuit-json/CircuitJsonModelAdapter.mjs'
16
25
  export { PcbBinaryPrimitiveParser } from './core/altium/PcbBinaryPrimitiveParser.mjs'
17
26
  export { PcbBoardRegionSemanticsParser } from './core/altium/PcbBoardRegionSemanticsParser.mjs'
18
27
  export { PcbComponentPrimitiveIndexer } from './core/altium/PcbComponentPrimitiveIndexer.mjs'
28
+ export { PcbCustomPadShapeParser } from './core/altium/PcbCustomPadShapeParser.mjs'
29
+ export { PcbDimensionParser } from './core/altium/PcbDimensionParser.mjs'
19
30
  export { PcbEmbeddedFontExtractor } from './core/altium/PcbEmbeddedFontExtractor.mjs'
20
31
  export { PcbEmbeddedModelExtractor } from './core/altium/PcbEmbeddedModelExtractor.mjs'
32
+ export { PcbExtendedPrimitiveInformationParser } from './core/altium/PcbExtendedPrimitiveInformationParser.mjs'
21
33
  export { PcbFontMetricsParser } from './core/altium/PcbFontMetricsParser.mjs'
22
34
  export { PcbLayerIdCodec } from './core/altium/PcbLayerIdCodec.mjs'
23
35
  export { PcbLibModelParser } from './core/altium/PcbLibModelParser.mjs'
24
36
  export { PcbLibStreamExtractor } from './core/altium/PcbLibStreamExtractor.mjs'
37
+ export { PcbMechanicalLayerPairParser } from './core/altium/PcbMechanicalLayerPairParser.mjs'
25
38
  export { PcbModelParser } from './core/altium/PcbModelParser.mjs'
26
39
  export { PcbOutlineRasterizer } from './core/altium/PcbOutlineRasterizer.mjs'
27
40
  export { PcbOutlineRecovery } from './core/altium/PcbOutlineRecovery.mjs'
41
+ export { PcbOwnershipGraphBuilder } from './core/altium/PcbOwnershipGraphBuilder.mjs'
28
42
  export { PcbPadStackParser } from './core/altium/PcbPadStackParser.mjs'
43
+ export { PcbPickPlacePositionResolver } from './core/altium/PcbPickPlacePositionResolver.mjs'
29
44
  export { PcbRawRecordRegistry } from './core/altium/PcbRawRecordRegistry.mjs'
30
45
  export { PcbRuleParser } from './core/altium/PcbRuleParser.mjs'
46
+ export { PcbSidecarRecordParser } from './core/altium/PcbSidecarRecordParser.mjs'
47
+ export { PcbSpecialStringResolver } from './core/altium/PcbSpecialStringResolver.mjs'
48
+ export { PcbStatisticsBuilder } from './core/altium/PcbStatisticsBuilder.mjs'
31
49
  export { PcbStreamExtractor } from './core/altium/PcbStreamExtractor.mjs'
50
+ export { PcbUnionParser } from './core/altium/PcbUnionParser.mjs'
32
51
  export { PcbViaStackParser } from './core/altium/PcbViaStackParser.mjs'
52
+ export { PcbViaStructureParser } from './core/altium/PcbViaStructureParser.mjs'
33
53
  export { PrintableTextDecoder } from './core/altium/PrintableTextDecoder.mjs'
34
54
  export { PrjPcbModelParser } from './core/altium/PrjPcbModelParser.mjs'
35
55
  export { SchematicMultipartOwnerMatcher } from './core/altium/SchematicMultipartOwnerMatcher.mjs'
56
+ export { SchematicCrossSheetConnectorParser } from './core/altium/SchematicCrossSheetConnectorParser.mjs'
57
+ export { SchematicHarnessParser } from './core/altium/SchematicHarnessParser.mjs'
58
+ export { SchematicImplementationParser } from './core/altium/SchematicImplementationParser.mjs'
59
+ export { SchematicOwnershipGraphParser } from './core/altium/SchematicOwnershipGraphParser.mjs'
60
+ export { SchematicProjectParameterResolver } from './core/altium/SchematicProjectParameterResolver.mjs'
61
+ export { SchematicQaReportBuilder } from './core/altium/SchematicQaReportBuilder.mjs'
62
+ export { SchematicRecordTypeRegistry } from './core/altium/SchematicRecordTypeRegistry.mjs'
63
+ export { SchematicRepeatedChannelParser } from './core/altium/SchematicRepeatedChannelParser.mjs'
64
+ export { SchematicTemplateParser } from './core/altium/SchematicTemplateParser.mjs'
36
65
  export { SchematicStandaloneCalloutNormalizer } from './core/altium/SchematicStandaloneCalloutNormalizer.mjs'
package/src/renderers.mjs CHANGED
@@ -6,6 +6,9 @@ export { BomTableRenderer } from './ui/BomTableRenderer.mjs'
6
6
  export { PcbArcUtils } from './ui/PcbArcUtils.mjs'
7
7
  export { PcbEdgeFacingGlyphNormalizer } from './ui/PcbEdgeFacingGlyphNormalizer.mjs'
8
8
  export { PcbFootprintPrimitiveSelector } from './ui/PcbFootprintPrimitiveSelector.mjs'
9
+ export { PcbInteractionIndex } from './ui/PcbInteractionIndex.mjs'
10
+ export { PcbInteractionItemRegistry } from './ui/PcbInteractionItemRegistry.mjs'
11
+ export { PcbInteractionLayerModel } from './ui/PcbInteractionLayerModel.mjs'
9
12
  export {
10
13
  PcbSideResolvedRenderModel,
11
14
  isCopperPrimitive,
@@ -137,6 +137,7 @@
137
137
  --pcb-copper-fill: rgba(196, 118, 70, 0.24);
138
138
  --pcb-copper-solid-fill: rgba(196, 118, 70, 0.78);
139
139
  --pcb-track-color: #c7522d;
140
+ --pcb-track-opacity: 1;
140
141
  --pcb-via-ring-fill: rgba(232, 236, 233, 0.92);
141
142
  --pcb-via-hole-fill: #0f746c;
142
143
  --pcb-footprint-fill: rgba(247, 230, 117, 0.14);
@@ -203,6 +204,11 @@
203
204
  stroke-linejoin: round;
204
205
  }
205
206
 
207
+ .pcb-track,
208
+ .pcb-arc {
209
+ opacity: var(--pcb-track-opacity, 1);
210
+ }
211
+
206
212
  .pcb-copper--surface .pcb-polygon {
207
213
  fill: var(--pcb-surface-copper-fill);
208
214
  }
@@ -289,6 +295,25 @@
289
295
  fill: #000;
290
296
  }
291
297
 
298
+ .pcb-barcode__background,
299
+ .pcb-barcode__bar {
300
+ fill: var(--pcb-footprint-track-color);
301
+ }
302
+
303
+ .pcb-text--barcode-inverted .pcb-barcode__background {
304
+ fill: var(--pcb-text-knockout-fill);
305
+ }
306
+
307
+ .pcb-text--barcode-inverted .pcb-barcode__bar {
308
+ fill: var(--pcb-footprint-track-color);
309
+ }
310
+
311
+ .pcb-barcode__caption {
312
+ fill: var(--pcb-footprint-track-color);
313
+ font-family: Arial, sans-serif;
314
+ font-weight: 700;
315
+ }
316
+
292
317
  .bom-panel {
293
318
  display: grid;
294
319
  gap: 1rem;