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.
- package/README.md +34 -5
- package/docs/api.md +171 -23
- package/docs/model-format.md +192 -20
- package/docs/schemas/altium_toolkit/embedded_assets_a1.schema.json +56 -0
- package/docs/schemas/altium_toolkit/fixture_coverage_matrix_a1.schema.json +89 -0
- package/docs/schemas/altium_toolkit/geometry_bounds_a1.schema.json +86 -0
- package/docs/schemas/altium_toolkit/library_catalog_a1.schema.json +65 -0
- package/docs/schemas/altium_toolkit/library_diff_a1.schema.json +54 -0
- package/docs/schemas/altium_toolkit/library_inspection_a1.schema.json +94 -0
- package/docs/schemas/altium_toolkit/library_qa_a1.schema.json +4 -0
- package/docs/schemas/altium_toolkit/native_stream_inventory_a1.schema.json +66 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +513 -3
- package/docs/schemas/altium_toolkit/parameter_record_inventory_a1.schema.json +84 -0
- package/docs/schemas/altium_toolkit/parser_diagnostics_a1.schema.json +63 -0
- package/docs/schemas/altium_toolkit/parser_value_verification_a1.schema.json +74 -0
- package/docs/schemas/altium_toolkit/pcb_class_report_a1.schema.json +79 -0
- package/docs/schemas/altium_toolkit/pcb_inspection_a1.schema.json +65 -0
- package/docs/schemas/altium_toolkit/pcb_net_membership_a1.schema.json +98 -0
- package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +3 -0
- package/docs/schemas/altium_toolkit/project_hierarchy_a1.schema.json +79 -0
- package/docs/schemas/altium_toolkit/unsupported_features_a1.schema.json +212 -0
- package/docs/testing.md +7 -0
- package/examples/README.md +21 -0
- package/examples/cli-utils.mjs +148 -0
- package/examples/corpus-smoke.mjs +523 -0
- package/examples/extract-bom.mjs +47 -0
- package/examples/generate-pnp.mjs +59 -0
- package/examples/inspect-board.mjs +70 -0
- package/examples/inspect-schematic.mjs +406 -0
- package/examples/library-catalog.mjs +115 -0
- package/examples/net-report.mjs +61 -0
- package/examples/validate-library.mjs +59 -0
- package/package.json +1 -1
- package/spec/library-scope.md +5 -0
- package/src/core/BinaryReader.mjs +213 -2
- package/src/core/altium/AltiumLibraryBatchExporter.mjs +206 -0
- package/src/core/altium/AltiumLibraryRecordBuilder.mjs +293 -0
- package/src/core/altium/AltiumParser.mjs +357 -16
- package/src/core/altium/AltiumPcbLibExporter.mjs +101 -0
- package/src/core/altium/AltiumSchLibExporter.mjs +57 -0
- package/src/core/altium/AltiumUnits.mjs +205 -0
- package/src/core/altium/AsciiRecordParser.mjs +52 -11
- package/src/core/altium/EmbeddedAssetReportBuilder.mjs +383 -0
- package/src/core/altium/FixtureCoverageMatrixBuilder.mjs +304 -0
- package/src/core/altium/GeometryBoundsReportBuilder.mjs +935 -0
- package/src/core/altium/LibraryCatalogArtifactBuilder.mjs +296 -0
- package/src/core/altium/LibraryDiffReportBuilder.mjs +260 -0
- package/src/core/altium/LibraryInspectionReportBuilder.mjs +156 -0
- package/src/core/altium/LibraryQaReportBuilder.mjs +374 -1
- package/src/core/altium/NativeStreamInventoryBuilder.mjs +177 -0
- package/src/core/altium/NormalizedModelSchema.mjs +3 -31
- package/src/core/altium/ParameterCollection.mjs +431 -0
- package/src/core/altium/ParameterRecordInventoryBuilder.mjs +274 -0
- package/src/core/altium/ParserCompatibilityFuzzer.mjs +106 -2
- package/src/core/altium/ParserDiagnosticNormalizer.mjs +213 -0
- package/src/core/altium/ParserErrors.mjs +90 -0
- package/src/core/altium/ParserFieldCoverageReportBuilder.mjs +656 -0
- package/src/core/altium/ParserUtils.mjs +24 -0
- package/src/core/altium/ParserValueVerificationReportBuilder.mjs +323 -0
- package/src/core/altium/PcbClassReportBuilder.mjs +366 -0
- package/src/core/altium/PcbComponentKindPolicy.mjs +9 -9
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +22 -3
- package/src/core/altium/PcbInspectionReportBuilder.mjs +313 -0
- package/src/core/altium/PcbLayerGroups.mjs +308 -0
- package/src/core/altium/PcbLayerStackCustomDataParser.mjs +183 -0
- package/src/core/altium/PcbLayerStackInterchangeParser.mjs +473 -4
- package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +83 -15
- package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +74 -4
- package/src/core/altium/PcbLibModelParser.mjs +20 -4
- package/src/core/altium/PcbLibStreamExtractor.mjs +49 -6
- package/src/core/altium/PcbModelParser.mjs +223 -4
- package/src/core/altium/PcbNetMembershipReportBuilder.mjs +270 -0
- package/src/core/altium/PcbOutlineRecovery.mjs +94 -0
- package/src/core/altium/PcbStreamExtractor.mjs +130 -6
- package/src/core/altium/PcbTrackPrimitiveParser.mjs +66 -2
- package/src/core/altium/ProjectDesignBundleBuilder.mjs +15 -0
- package/src/core/altium/ProjectHierarchyReportBuilder.mjs +660 -0
- package/src/core/altium/ProjectNetlistExporter.mjs +2 -0
- package/src/core/altium/RawDataPreservationReportBuilder.mjs +348 -0
- package/src/core/altium/SchLibModelParser.mjs +840 -0
- package/src/core/altium/SchLibStreamExtractor.mjs +586 -0
- package/src/core/altium/SchematicBusEntryParser.mjs +3 -2
- package/src/core/altium/SchematicCodeSymbolParser.mjs +663 -0
- package/src/core/altium/SchematicConnectivityQaBuilder.mjs +177 -2
- package/src/core/altium/SchematicDirectiveParser.mjs +5 -17
- package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +10 -1
- package/src/core/altium/SchematicFieldCoverageReportBuilder.mjs +549 -0
- package/src/core/altium/SchematicHarnessParser.mjs +9 -3
- package/src/core/altium/SchematicHyperlinkParser.mjs +122 -0
- package/src/core/altium/SchematicNetlistBuilder.mjs +271 -8
- package/src/core/altium/SchematicNoErcSymbolResolver.mjs +36 -0
- package/src/core/altium/SchematicOwnershipGraphParser.mjs +102 -3
- package/src/core/altium/SchematicPinParser.mjs +99 -65
- package/src/core/altium/SchematicPrimitiveParser.mjs +125 -22
- package/src/core/altium/SchematicQaReportBuilder.mjs +2 -0
- package/src/core/altium/SchematicRecordStreamParser.mjs +183 -0
- package/src/core/altium/SchematicRecordTypeRegistry.mjs +6 -1
- package/src/core/altium/SchematicSheetParser.mjs +8 -2
- package/src/core/altium/SchematicStreamExtractor.mjs +107 -21
- package/src/core/altium/SchematicTextOrientationResolver.mjs +76 -0
- package/src/core/altium/SchematicTextParser.mjs +28 -12
- package/src/core/altium/SchematicTextRunParser.mjs +81 -0
- package/src/core/altium/SchematicThumbnailParser.mjs +425 -0
- package/src/core/altium/SourceBundleExporter.mjs +156 -0
- package/src/core/altium/SourceComponentBundleNormalizer.mjs +295 -0
- package/src/core/altium/SourceComponentClient.mjs +239 -0
- package/src/core/altium/UnsupportedFeatureReportBuilder.mjs +380 -0
- package/src/core/ole/OleCompoundDocumentWriter.mjs +449 -0
- package/src/parser.mjs +43 -1
- package/src/renderers.mjs +1 -0
- package/src/styles/altium-renderers.css +6 -6
- package/src/ui/PcbArcUtils.mjs +19 -2
- package/src/ui/PcbScene3dBuilder.mjs +202 -20
- package/src/ui/PcbScene3dModelRegistry.mjs +28 -18
- package/src/ui/PcbScene3dPlacementSideResolver.mjs +48 -6
- package/src/ui/SchematicColorResolver.mjs +185 -0
- package/src/ui/SchematicDirectiveRenderer.mjs +133 -22
- package/src/ui/SchematicLineColorResolver.mjs +88 -0
- package/src/ui/SchematicNoteRenderer.mjs +5 -1
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +269 -8
- package/src/ui/SchematicOwnerPinMarkerLineThemer.mjs +155 -0
- package/src/ui/SchematicPinSvgRenderer.mjs +229 -62
- package/src/ui/SchematicShapeRenderer.mjs +86 -17
- package/src/ui/SchematicSvgRenderer.mjs +980 -58
- package/src/ui/SchematicTypography.mjs +4 -3
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds deterministic textual and byte records for generated Altium libraries.
|
|
7
|
+
*/
|
|
8
|
+
export class AltiumLibraryRecordBuilder {
|
|
9
|
+
/**
|
|
10
|
+
* Builds a schematic component record.
|
|
11
|
+
* @param {object} bundle Normalized component bundle.
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
static buildSchematicComponentRecord(bundle) {
|
|
15
|
+
return AltiumLibraryRecordBuilder.#pipeRecord({
|
|
16
|
+
RECORD: 'Component',
|
|
17
|
+
Name: bundle.symbol.name,
|
|
18
|
+
SourceId: bundle.id,
|
|
19
|
+
DisplayName: bundle.name,
|
|
20
|
+
PinCount: String(bundle.symbol.pins.length),
|
|
21
|
+
PrimitiveCount: String(bundle.symbol.primitives.length)
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Builds a PCB footprint record.
|
|
27
|
+
* @param {object} bundle Normalized component bundle.
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
static buildPcbFootprintRecord(bundle) {
|
|
31
|
+
return AltiumLibraryRecordBuilder.#pipeRecord({
|
|
32
|
+
RECORD: 'Footprint',
|
|
33
|
+
Name: bundle.footprint.name,
|
|
34
|
+
SourceId: bundle.id,
|
|
35
|
+
DisplayName: bundle.name,
|
|
36
|
+
PadCount: String(bundle.footprint.pads.length),
|
|
37
|
+
TrackCount: String(bundle.footprint.tracks.length),
|
|
38
|
+
ModelCount: String(bundle.models.length)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Builds a PcbLib Library/Data stream.
|
|
44
|
+
* @param {object[]} bundles Normalized component bundles.
|
|
45
|
+
* @returns {Uint8Array}
|
|
46
|
+
*/
|
|
47
|
+
static buildPcbLibraryData(bundles) {
|
|
48
|
+
const countBytes = AltiumLibraryRecordBuilder.createCountHeader(
|
|
49
|
+
bundles.length
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return AltiumLibraryRecordBuilder.concatBytes([
|
|
53
|
+
AltiumLibraryRecordBuilder.createProperties({
|
|
54
|
+
HEADER: 'PCB 6.0 Binary Library File',
|
|
55
|
+
WEIGHT: '0',
|
|
56
|
+
GENERATEDBY: 'ECAD Forge'
|
|
57
|
+
}),
|
|
58
|
+
countBytes,
|
|
59
|
+
...bundles.map((bundle) =>
|
|
60
|
+
AltiumLibraryRecordBuilder.createStringBlock(
|
|
61
|
+
bundle.footprint.name
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
])
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Builds a PcbLib component parameters table.
|
|
69
|
+
* @param {object[]} bundles Normalized component bundles.
|
|
70
|
+
* @returns {Uint8Array}
|
|
71
|
+
*/
|
|
72
|
+
static buildComponentParamsToc(bundles) {
|
|
73
|
+
return AltiumLibraryRecordBuilder.concatBytes(
|
|
74
|
+
bundles.map((bundle) =>
|
|
75
|
+
AltiumLibraryRecordBuilder.createLengthPrefixedAscii(
|
|
76
|
+
AltiumLibraryRecordBuilder.#pipeRecord({
|
|
77
|
+
Name: bundle.footprint.name,
|
|
78
|
+
'Pad Count': String(bundle.footprint.pads.length),
|
|
79
|
+
Height: String(bundle.metadata.height || ''),
|
|
80
|
+
Description: String(bundle.metadata.description || '')
|
|
81
|
+
}) + '\r\n\u0000'
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Builds a SectionKeys stream.
|
|
89
|
+
* @param {object[]} bundles Normalized component bundles.
|
|
90
|
+
* @returns {Uint8Array}
|
|
91
|
+
*/
|
|
92
|
+
static buildSectionKeys(bundles) {
|
|
93
|
+
const entries = bundles.map((bundle) => ({
|
|
94
|
+
fullName: bundle.footprint.name,
|
|
95
|
+
storageName: AltiumLibraryRecordBuilder.sanitizeStorageName(
|
|
96
|
+
bundle.footprint.name
|
|
97
|
+
)
|
|
98
|
+
}))
|
|
99
|
+
|
|
100
|
+
return AltiumLibraryRecordBuilder.concatBytes([
|
|
101
|
+
AltiumLibraryRecordBuilder.createCountHeader(entries.length),
|
|
102
|
+
...entries.flatMap((entry) => [
|
|
103
|
+
AltiumLibraryRecordBuilder.createStringBlock(entry.fullName),
|
|
104
|
+
AltiumLibraryRecordBuilder.createStringBlock(entry.storageName)
|
|
105
|
+
])
|
|
106
|
+
])
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Builds a footprint Data stream.
|
|
111
|
+
* @param {object} bundle Normalized component bundle.
|
|
112
|
+
* @returns {Uint8Array}
|
|
113
|
+
*/
|
|
114
|
+
static buildFootprintData(bundle) {
|
|
115
|
+
return AltiumLibraryRecordBuilder.concatBytes([
|
|
116
|
+
AltiumLibraryRecordBuilder.createStringBlock(bundle.footprint.name)
|
|
117
|
+
])
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Builds a footprint Parameters stream.
|
|
122
|
+
* @param {object} bundle Normalized component bundle.
|
|
123
|
+
* @returns {Uint8Array}
|
|
124
|
+
*/
|
|
125
|
+
static buildFootprintParameters(bundle) {
|
|
126
|
+
return AltiumLibraryRecordBuilder.createProperties({
|
|
127
|
+
PATTERN: bundle.footprint.name,
|
|
128
|
+
DESCRIPTION: String(bundle.metadata.description || ''),
|
|
129
|
+
HEIGHT: String(bundle.metadata.height || ''),
|
|
130
|
+
ITEMGUID: AltiumLibraryRecordBuilder.#guidFromText(bundle.id)
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Builds model metadata entries.
|
|
136
|
+
* @param {{ model: object, id: string, checksum: number }[]} models Model rows.
|
|
137
|
+
* @returns {Uint8Array}
|
|
138
|
+
*/
|
|
139
|
+
static buildModelsData(models) {
|
|
140
|
+
return AltiumLibraryRecordBuilder.concatBytes(
|
|
141
|
+
models.map((row) =>
|
|
142
|
+
AltiumLibraryRecordBuilder.createLengthPrefixedAscii(
|
|
143
|
+
AltiumLibraryRecordBuilder.#pipeRecord({
|
|
144
|
+
ID: row.id,
|
|
145
|
+
NAME: row.model.name,
|
|
146
|
+
CHECKSUM: String(row.checksum),
|
|
147
|
+
FORMAT: row.model.format
|
|
148
|
+
}) + '\u0000'
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Creates a little-endian count header.
|
|
156
|
+
* @param {number} count Count value.
|
|
157
|
+
* @returns {Uint8Array}
|
|
158
|
+
*/
|
|
159
|
+
static createCountHeader(count) {
|
|
160
|
+
const bytes = new Uint8Array(4)
|
|
161
|
+
new DataView(bytes.buffer).setUint32(0, Number(count || 0), true)
|
|
162
|
+
return bytes
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Creates a PcbLib property stream.
|
|
167
|
+
* @param {Record<string, string>} properties Properties.
|
|
168
|
+
* @returns {Uint8Array}
|
|
169
|
+
*/
|
|
170
|
+
static createProperties(properties) {
|
|
171
|
+
return AltiumLibraryRecordBuilder.createLengthPrefixedAscii(
|
|
172
|
+
AltiumLibraryRecordBuilder.#pipeRecord(properties) + '\u0000'
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Creates a Pascal-style string block.
|
|
178
|
+
* @param {string} text Text value.
|
|
179
|
+
* @returns {Uint8Array}
|
|
180
|
+
*/
|
|
181
|
+
static createStringBlock(text) {
|
|
182
|
+
const encoded = new TextEncoder().encode(String(text || ''))
|
|
183
|
+
const bytes = new Uint8Array(4 + 1 + encoded.byteLength)
|
|
184
|
+
const view = new DataView(bytes.buffer)
|
|
185
|
+
|
|
186
|
+
view.setUint32(0, 1 + encoded.byteLength, true)
|
|
187
|
+
bytes[4] = encoded.byteLength
|
|
188
|
+
bytes.set(encoded, 5)
|
|
189
|
+
|
|
190
|
+
return bytes
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Creates a length-prefixed ASCII/UTF-8 byte block.
|
|
195
|
+
* @param {string} text Text body.
|
|
196
|
+
* @returns {Uint8Array}
|
|
197
|
+
*/
|
|
198
|
+
static createLengthPrefixedAscii(text) {
|
|
199
|
+
const encoded = new TextEncoder().encode(String(text || ''))
|
|
200
|
+
const bytes = new Uint8Array(4 + encoded.byteLength)
|
|
201
|
+
|
|
202
|
+
new DataView(bytes.buffer).setUint32(0, encoded.byteLength, true)
|
|
203
|
+
bytes.set(encoded, 4)
|
|
204
|
+
|
|
205
|
+
return bytes
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Concatenates byte chunks.
|
|
210
|
+
* @param {Uint8Array[]} chunks Byte chunks.
|
|
211
|
+
* @returns {Uint8Array}
|
|
212
|
+
*/
|
|
213
|
+
static concatBytes(chunks) {
|
|
214
|
+
const byteLength = chunks.reduce(
|
|
215
|
+
(sum, chunk) => sum + chunk.byteLength,
|
|
216
|
+
0
|
|
217
|
+
)
|
|
218
|
+
const bytes = new Uint8Array(byteLength)
|
|
219
|
+
let offset = 0
|
|
220
|
+
|
|
221
|
+
for (const chunk of chunks) {
|
|
222
|
+
bytes.set(chunk, offset)
|
|
223
|
+
offset += chunk.byteLength
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return bytes
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Sanitizes one OLE storage name.
|
|
231
|
+
* @param {string} name Storage name.
|
|
232
|
+
* @returns {string}
|
|
233
|
+
*/
|
|
234
|
+
static sanitizeStorageName(name) {
|
|
235
|
+
return String(name || 'Component')
|
|
236
|
+
.replace(/[\\/:\u0000-\u001f]/gu, '_')
|
|
237
|
+
.slice(0, 31)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Computes a simple deterministic checksum for generated model metadata.
|
|
242
|
+
* @param {Uint8Array} bytes Model bytes.
|
|
243
|
+
* @returns {number}
|
|
244
|
+
*/
|
|
245
|
+
static checksumBytes(bytes) {
|
|
246
|
+
return [...bytes].reduce(
|
|
247
|
+
(checksum, value) => (checksum + value) >>> 0,
|
|
248
|
+
0
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Builds one pipe-delimited record.
|
|
254
|
+
* @param {Record<string, string>} fields Record fields.
|
|
255
|
+
* @returns {string}
|
|
256
|
+
*/
|
|
257
|
+
static #pipeRecord(fields) {
|
|
258
|
+
return (
|
|
259
|
+
'|' +
|
|
260
|
+
Object.entries(fields)
|
|
261
|
+
.filter(([, value]) => String(value ?? '') !== '')
|
|
262
|
+
.map(([key, value]) => key + '=' + String(value))
|
|
263
|
+
.join('|')
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Builds a deterministic GUID-like id from text.
|
|
269
|
+
* @param {string} text Source text.
|
|
270
|
+
* @returns {string}
|
|
271
|
+
*/
|
|
272
|
+
static #guidFromText(text) {
|
|
273
|
+
const hex = [...new TextEncoder().encode(String(text || ''))]
|
|
274
|
+
.map((value) => value.toString(16).padStart(2, '0'))
|
|
275
|
+
.join('')
|
|
276
|
+
.padEnd(32, '0')
|
|
277
|
+
.slice(0, 32)
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
'{' +
|
|
281
|
+
hex.slice(0, 8) +
|
|
282
|
+
'-' +
|
|
283
|
+
hex.slice(8, 12) +
|
|
284
|
+
'-' +
|
|
285
|
+
hex.slice(12, 16) +
|
|
286
|
+
'-' +
|
|
287
|
+
hex.slice(16, 20) +
|
|
288
|
+
'-' +
|
|
289
|
+
hex.slice(20) +
|
|
290
|
+
'}'
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
}
|