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,406 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
3
|
+
//
|
|
4
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
modelIdentity,
|
|
8
|
+
printJson,
|
|
9
|
+
runReadOnlyScript,
|
|
10
|
+
wantsJson
|
|
11
|
+
} from './cli-utils.mjs'
|
|
12
|
+
|
|
13
|
+
const VIEWS = new Set(['summary', 'flat', 'hierarchy', 'parts', 'nets', 'all'])
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolves the requested inspection view.
|
|
17
|
+
* @param {string[]} args CLI arguments.
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
function viewFromArgs(args) {
|
|
21
|
+
const inline = args.find((arg) => arg.startsWith('--view='))
|
|
22
|
+
if (inline) {
|
|
23
|
+
return inline.slice('--view='.length)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const index = args.indexOf('--view')
|
|
27
|
+
if (index >= 0) {
|
|
28
|
+
return args[index + 1] || ''
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return 'summary'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns true when a requested view is supported.
|
|
36
|
+
* @param {string} view Requested view.
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
function isSupportedView(view) {
|
|
40
|
+
return VIEWS.has(view)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Builds one schematic inspection report.
|
|
45
|
+
* @param {object} model Parsed model.
|
|
46
|
+
* @param {string} view Requested view.
|
|
47
|
+
* @returns {object}
|
|
48
|
+
*/
|
|
49
|
+
function buildReport(model, view) {
|
|
50
|
+
const base = {
|
|
51
|
+
...modelIdentity(model),
|
|
52
|
+
view,
|
|
53
|
+
summary: buildSummary(model)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (view === 'flat') {
|
|
57
|
+
return {
|
|
58
|
+
...base,
|
|
59
|
+
records: flatRecords(model)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (view === 'hierarchy') {
|
|
64
|
+
return {
|
|
65
|
+
...base,
|
|
66
|
+
hierarchy: schematic(model).ownership?.hierarchy || []
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (view === 'parts') {
|
|
71
|
+
return {
|
|
72
|
+
...base,
|
|
73
|
+
parts: partRows(model)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (view === 'nets') {
|
|
78
|
+
return {
|
|
79
|
+
...base,
|
|
80
|
+
nets: schematic(model).nets || []
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (view === 'all') {
|
|
85
|
+
return {
|
|
86
|
+
...base,
|
|
87
|
+
flat: flatRecords(model),
|
|
88
|
+
hierarchy: schematic(model).ownership?.hierarchy || [],
|
|
89
|
+
parts: partRows(model),
|
|
90
|
+
nets: schematic(model).nets || []
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
...base,
|
|
96
|
+
recordTypes: schematic(model).recordTypes || [],
|
|
97
|
+
fieldCoverage: schematic(model).qa?.fieldCoverage || {
|
|
98
|
+
recordTypes: []
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Builds a compact schematic summary.
|
|
105
|
+
* @param {object} model Parsed model.
|
|
106
|
+
* @returns {object}
|
|
107
|
+
*/
|
|
108
|
+
function buildSummary(model) {
|
|
109
|
+
const root = schematic(model)
|
|
110
|
+
const fieldCoverage = root.qa?.fieldCoverage?.summary || {}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
recordCount: (root.ownership?.records || []).length,
|
|
114
|
+
topLevelRecordCount: (root.ownership?.hierarchy || []).length,
|
|
115
|
+
componentCount: (root.components || []).length,
|
|
116
|
+
netCount: (root.nets || []).length,
|
|
117
|
+
diagnosticCount: (model.diagnostics || []).length,
|
|
118
|
+
fieldGapRecordTypeCount: fieldCoverage.recordTypeCount || 0,
|
|
119
|
+
unrecognizedFieldCount: fieldCoverage.unrecognizedFieldCount || 0
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Returns the schematic root or an empty object.
|
|
125
|
+
* @param {object} model Parsed model.
|
|
126
|
+
* @returns {object}
|
|
127
|
+
*/
|
|
128
|
+
function schematic(model) {
|
|
129
|
+
return model.schematic || {}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Returns flat ownership records from the schematic sidecar.
|
|
134
|
+
* @param {object} model Parsed model.
|
|
135
|
+
* @returns {object[]}
|
|
136
|
+
*/
|
|
137
|
+
function flatRecords(model) {
|
|
138
|
+
return schematic(model).ownership?.records || []
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Builds compact part rows with recovered pin counts.
|
|
143
|
+
* @param {object} model Parsed model.
|
|
144
|
+
* @returns {object[]}
|
|
145
|
+
*/
|
|
146
|
+
function partRows(model) {
|
|
147
|
+
const root = schematic(model)
|
|
148
|
+
const ownerIndexByDesignator = buildOwnerIndexByDesignator(root)
|
|
149
|
+
|
|
150
|
+
return (root.components || []).map((component) => ({
|
|
151
|
+
designator: component.designator || '',
|
|
152
|
+
libReference: component.libReference || '',
|
|
153
|
+
value: component.value || '',
|
|
154
|
+
uniqueId: component.uniqueId || '',
|
|
155
|
+
x: component.x,
|
|
156
|
+
y: component.y,
|
|
157
|
+
pinCount: pinCountForComponent(
|
|
158
|
+
root,
|
|
159
|
+
component.designator || '',
|
|
160
|
+
ownerIndexByDesignator
|
|
161
|
+
)
|
|
162
|
+
}))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Builds component owner-index lookups by visible designator.
|
|
167
|
+
* @param {object} root Schematic root.
|
|
168
|
+
* @returns {Map<string, string>}
|
|
169
|
+
*/
|
|
170
|
+
function buildOwnerIndexByDesignator(root) {
|
|
171
|
+
const records = root.ownership?.records || []
|
|
172
|
+
const ownerIndexByDesignator = new Map()
|
|
173
|
+
|
|
174
|
+
for (const record of records) {
|
|
175
|
+
if (record.recordType !== '1' || record.indexInSheet === undefined) {
|
|
176
|
+
continue
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const designator = findDesignatorForOwner(records, record.indexInSheet)
|
|
180
|
+
if (designator) {
|
|
181
|
+
ownerIndexByDesignator.set(designator, String(record.indexInSheet))
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return ownerIndexByDesignator
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Finds a visible designator text record for one component owner index.
|
|
190
|
+
* @param {object[]} records Ownership sidecar records.
|
|
191
|
+
* @param {number | string} ownerIndex Component owner index.
|
|
192
|
+
* @returns {string}
|
|
193
|
+
*/
|
|
194
|
+
function findDesignatorForOwner(records, ownerIndex) {
|
|
195
|
+
const owner = String(ownerIndex)
|
|
196
|
+
const record = records.find(
|
|
197
|
+
(item) =>
|
|
198
|
+
String(item.ownerIndex || '') === owner &&
|
|
199
|
+
String(item.name || '').toLowerCase() === 'designator' &&
|
|
200
|
+
String(item.text || '').trim()
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return String(record?.text || '').trim()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Counts pins belonging to one recovered component.
|
|
208
|
+
* @param {object} root Schematic root.
|
|
209
|
+
* @param {string} designator Component designator.
|
|
210
|
+
* @param {Map<string, string>} ownerIndexByDesignator Owner index lookup.
|
|
211
|
+
* @returns {number}
|
|
212
|
+
*/
|
|
213
|
+
function pinCountForComponent(root, designator, ownerIndexByDesignator) {
|
|
214
|
+
const ownerIndex = ownerIndexByDesignator.get(designator) || ''
|
|
215
|
+
|
|
216
|
+
return (root.pins || []).filter((pin) => {
|
|
217
|
+
if (pin.componentDesignator === designator) {
|
|
218
|
+
return true
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return ownerIndex && String(pin.ownerIndex || '') === ownerIndex
|
|
222
|
+
}).length
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Prints a text report for the requested view.
|
|
227
|
+
* @param {object} report Inspection report.
|
|
228
|
+
* @returns {void}
|
|
229
|
+
*/
|
|
230
|
+
function printTextReport(report) {
|
|
231
|
+
if (report.view === 'flat') {
|
|
232
|
+
printFlatText(report)
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (report.view === 'hierarchy') {
|
|
237
|
+
printHierarchyText(report)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (report.view === 'parts') {
|
|
242
|
+
printPartsText(report)
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (report.view === 'nets') {
|
|
247
|
+
printNetsText(report)
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (report.view === 'all') {
|
|
252
|
+
printAllText(report)
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
printSummaryText(report)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Prints the summary view.
|
|
261
|
+
* @param {object} report Inspection report.
|
|
262
|
+
* @returns {void}
|
|
263
|
+
*/
|
|
264
|
+
function printSummaryText(report) {
|
|
265
|
+
console.log(report.title)
|
|
266
|
+
console.log('Type: ' + report.fileType)
|
|
267
|
+
console.log('Kind: ' + report.kind)
|
|
268
|
+
console.log('Records: ' + report.summary.recordCount)
|
|
269
|
+
console.log('Components: ' + report.summary.componentCount)
|
|
270
|
+
console.log('Nets: ' + report.summary.netCount)
|
|
271
|
+
console.log('Diagnostics: ' + report.summary.diagnosticCount)
|
|
272
|
+
console.log('Unrecognized fields: ' + report.summary.unrecognizedFieldCount)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Prints all compact schematic views.
|
|
277
|
+
* @param {object} report Inspection report.
|
|
278
|
+
* @returns {void}
|
|
279
|
+
*/
|
|
280
|
+
function printAllText(report) {
|
|
281
|
+
printSummaryText(report)
|
|
282
|
+
console.log('')
|
|
283
|
+
printFlatText({ ...report, records: report.flat || [] })
|
|
284
|
+
console.log('')
|
|
285
|
+
printHierarchyText(report)
|
|
286
|
+
console.log('')
|
|
287
|
+
printPartsText(report)
|
|
288
|
+
console.log('')
|
|
289
|
+
printNetsText(report)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Prints the flat record view.
|
|
294
|
+
* @param {object} report Inspection report.
|
|
295
|
+
* @returns {void}
|
|
296
|
+
*/
|
|
297
|
+
function printFlatText(report) {
|
|
298
|
+
console.log(report.title + ' flat records')
|
|
299
|
+
for (const record of report.records || []) {
|
|
300
|
+
console.log(recordLine(record))
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Prints the hierarchy view.
|
|
306
|
+
* @param {object} report Inspection report.
|
|
307
|
+
* @returns {void}
|
|
308
|
+
*/
|
|
309
|
+
function printHierarchyText(report) {
|
|
310
|
+
console.log(report.title + ' hierarchy')
|
|
311
|
+
for (const node of report.hierarchy || []) {
|
|
312
|
+
printHierarchyNode(node, 0)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Prints one hierarchy node and its descendants.
|
|
318
|
+
* @param {object} node Hierarchy node.
|
|
319
|
+
* @param {number} depth Nesting depth.
|
|
320
|
+
* @returns {void}
|
|
321
|
+
*/
|
|
322
|
+
function printHierarchyNode(node, depth) {
|
|
323
|
+
console.log(' '.repeat(depth) + recordLine(node))
|
|
324
|
+
for (const child of node.children || []) {
|
|
325
|
+
printHierarchyNode(child, depth + 1)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Prints compact part rows.
|
|
331
|
+
* @param {object} report Inspection report.
|
|
332
|
+
* @returns {void}
|
|
333
|
+
*/
|
|
334
|
+
function printPartsText(report) {
|
|
335
|
+
console.log(report.title + ' parts')
|
|
336
|
+
for (const part of report.parts || []) {
|
|
337
|
+
console.log(
|
|
338
|
+
[
|
|
339
|
+
part.designator || '(unnamed)',
|
|
340
|
+
part.libReference || '(no-library)',
|
|
341
|
+
'pins=' + part.pinCount
|
|
342
|
+
].join(' ')
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Prints compact net rows.
|
|
349
|
+
* @param {object} report Inspection report.
|
|
350
|
+
* @returns {void}
|
|
351
|
+
*/
|
|
352
|
+
function printNetsText(report) {
|
|
353
|
+
console.log(report.title + ' nets')
|
|
354
|
+
for (const net of report.nets || []) {
|
|
355
|
+
console.log(
|
|
356
|
+
[
|
|
357
|
+
net.name || '(unnamed)',
|
|
358
|
+
'segments=' + (net.segments || []).length,
|
|
359
|
+
'pins=' + (net.pins || []).length,
|
|
360
|
+
'labels=' + (net.labels || []).length
|
|
361
|
+
].join(' ')
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Builds one readable record line.
|
|
368
|
+
* @param {object} record Ownership sidecar record.
|
|
369
|
+
* @returns {string}
|
|
370
|
+
*/
|
|
371
|
+
function recordLine(record) {
|
|
372
|
+
const label = [record.name, record.text].filter(Boolean).join('=')
|
|
373
|
+
|
|
374
|
+
return ['#' + record.recordIndex, 'RECORD=' + record.recordType, label]
|
|
375
|
+
.filter(Boolean)
|
|
376
|
+
.join(' ')
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
await runReadOnlyScript({
|
|
380
|
+
scriptName: 'inspect-schematic',
|
|
381
|
+
summary: 'Inspect a parsed schematic and print a read-only view.',
|
|
382
|
+
helpLines: [
|
|
383
|
+
'Use --view summary, flat, hierarchy, parts, nets, or all.',
|
|
384
|
+
'Use --json for the full structured view.'
|
|
385
|
+
],
|
|
386
|
+
run(model, args) {
|
|
387
|
+
const view = viewFromArgs(args)
|
|
388
|
+
if (!isSupportedView(view)) {
|
|
389
|
+
console.error(
|
|
390
|
+
'Unsupported view "' +
|
|
391
|
+
view +
|
|
392
|
+
'". Use summary, flat, hierarchy, parts, nets, or all.'
|
|
393
|
+
)
|
|
394
|
+
process.exitCode = 1
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const report = buildReport(model, view)
|
|
399
|
+
if (wantsJson(args)) {
|
|
400
|
+
printJson(report)
|
|
401
|
+
return
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
printTextReport(report)
|
|
405
|
+
}
|
|
406
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
3
|
+
//
|
|
4
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
modelIdentity,
|
|
8
|
+
printCsv,
|
|
9
|
+
printJson,
|
|
10
|
+
runReadOnlyScript,
|
|
11
|
+
wantsJson
|
|
12
|
+
} from './cli-utils.mjs'
|
|
13
|
+
import { LibraryCatalogArtifactBuilder } from '../src/index.mjs'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Builds library catalog rows from parsed library models.
|
|
17
|
+
* @param {object} model Parsed model.
|
|
18
|
+
* @returns {object[]}
|
|
19
|
+
*/
|
|
20
|
+
function catalogRows(model) {
|
|
21
|
+
const artifact = catalogArtifact(model)
|
|
22
|
+
if (artifact) {
|
|
23
|
+
return artifact.entries.map((entry) => ({
|
|
24
|
+
kind: entry.kind,
|
|
25
|
+
name: entry.name,
|
|
26
|
+
primitiveCount: entry.primitiveCount || '',
|
|
27
|
+
rawRecordCount: entry.rawRecordCount || '',
|
|
28
|
+
issueCount: entry.issueCodes.length,
|
|
29
|
+
outputSvgKey: entry.outputSvgKey || ''
|
|
30
|
+
}))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const footprintRows = (model.pcbLibrary?.footprints || []).map(
|
|
34
|
+
(footprint) => ({
|
|
35
|
+
kind: 'pcb-footprint',
|
|
36
|
+
name: footprint.name || footprint.sourceStorage || '',
|
|
37
|
+
primitiveCount:
|
|
38
|
+
(footprint.pads || []).length +
|
|
39
|
+
(footprint.tracks || []).length +
|
|
40
|
+
(footprint.arcs || []).length +
|
|
41
|
+
(footprint.vias || []).length +
|
|
42
|
+
(footprint.fills || []).length +
|
|
43
|
+
(footprint.regions || []).length,
|
|
44
|
+
rawRecordCount: (footprint.rawRecords || []).length
|
|
45
|
+
})
|
|
46
|
+
)
|
|
47
|
+
const sourceRows = (model.integratedLibrary?.sources || []).map(
|
|
48
|
+
(source) => ({
|
|
49
|
+
kind: source.fileType || 'source',
|
|
50
|
+
name: source.name || source.fileName || '',
|
|
51
|
+
primitiveCount: '',
|
|
52
|
+
rawRecordCount: '',
|
|
53
|
+
issueCount: '',
|
|
54
|
+
outputSvgKey: ''
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return [...footprintRows, ...sourceRows]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Builds a static catalog artifact for parsed library roots.
|
|
63
|
+
* @param {object} model Parsed model.
|
|
64
|
+
* @returns {object | null}
|
|
65
|
+
*/
|
|
66
|
+
function catalogArtifact(model) {
|
|
67
|
+
if (model.schematicLibrary) {
|
|
68
|
+
return LibraryCatalogArtifactBuilder.build({
|
|
69
|
+
schematicLibraries: [model]
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
if (model.pcbLibrary) {
|
|
73
|
+
return LibraryCatalogArtifactBuilder.build({
|
|
74
|
+
pcbLibraries: [model]
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await runReadOnlyScript({
|
|
82
|
+
scriptName: 'library-catalog',
|
|
83
|
+
summary: 'Catalog parsed library contents without modifying source files.',
|
|
84
|
+
helpLines: [
|
|
85
|
+
'Default output is CSV. Use --json for structured rows.',
|
|
86
|
+
'Use --html with SchLib/PcbLib inputs for a static catalog artifact.'
|
|
87
|
+
],
|
|
88
|
+
run(model, args) {
|
|
89
|
+
const artifact = catalogArtifact(model)
|
|
90
|
+
if (args.includes('--html') && artifact) {
|
|
91
|
+
console.log(artifact.html)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
const rows = catalogRows(model)
|
|
95
|
+
if (wantsJson(args)) {
|
|
96
|
+
printJson({
|
|
97
|
+
...modelIdentity(model),
|
|
98
|
+
...(artifact ? { catalog: artifact } : {}),
|
|
99
|
+
rows
|
|
100
|
+
})
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
printCsv(
|
|
104
|
+
[
|
|
105
|
+
'kind',
|
|
106
|
+
'name',
|
|
107
|
+
'primitiveCount',
|
|
108
|
+
'rawRecordCount',
|
|
109
|
+
'issueCount',
|
|
110
|
+
'outputSvgKey'
|
|
111
|
+
],
|
|
112
|
+
rows
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
3
|
+
//
|
|
4
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
modelIdentity,
|
|
8
|
+
printCsv,
|
|
9
|
+
printJson,
|
|
10
|
+
runReadOnlyScript,
|
|
11
|
+
wantsJson
|
|
12
|
+
} from './cli-utils.mjs'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Builds schematic and PCB net report rows.
|
|
16
|
+
* @param {object} model Parsed model.
|
|
17
|
+
* @returns {object[]}
|
|
18
|
+
*/
|
|
19
|
+
function netRows(model) {
|
|
20
|
+
const schematicRows = (model.schematic?.nets || []).map((net) => ({
|
|
21
|
+
source: 'schematic',
|
|
22
|
+
name: net.name || '',
|
|
23
|
+
segmentCount: (net.segments || []).length,
|
|
24
|
+
pinCount: (net.pins || []).length,
|
|
25
|
+
primitiveCount: ''
|
|
26
|
+
}))
|
|
27
|
+
const pcbRows = (model.pcb?.nets || []).map((net) => ({
|
|
28
|
+
source: 'pcb',
|
|
29
|
+
name: net.name || '',
|
|
30
|
+
segmentCount: '',
|
|
31
|
+
pinCount: '',
|
|
32
|
+
primitiveCount:
|
|
33
|
+
model.pcb?.routeAnalysis?.nets?.[net.name]?.primitiveCount || ''
|
|
34
|
+
}))
|
|
35
|
+
|
|
36
|
+
return [...schematicRows, ...pcbRows].sort((left, right) =>
|
|
37
|
+
(left.source + ':' + left.name).localeCompare(
|
|
38
|
+
right.source + ':' + right.name
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await runReadOnlyScript({
|
|
44
|
+
scriptName: 'net-report',
|
|
45
|
+
summary: 'Report recovered schematic and PCB net names.',
|
|
46
|
+
helpLines: ['Default output is CSV. Use --json for structured rows.'],
|
|
47
|
+
run(model, args) {
|
|
48
|
+
const rows = netRows(model)
|
|
49
|
+
if (wantsJson(args)) {
|
|
50
|
+
printJson({
|
|
51
|
+
...modelIdentity(model),
|
|
52
|
+
rows
|
|
53
|
+
})
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
printCsv(
|
|
57
|
+
['source', 'name', 'segmentCount', 'pinCount', 'primitiveCount'],
|
|
58
|
+
rows
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
3
|
+
//
|
|
4
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
import { LibraryQaReportBuilder } from '../src/index.mjs'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
modelIdentity,
|
|
10
|
+
printJson,
|
|
11
|
+
runReadOnlyScript,
|
|
12
|
+
wantsJson
|
|
13
|
+
} from './cli-utils.mjs'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Builds a read-only library QA report for one parsed model.
|
|
17
|
+
* @param {object} model Parsed model.
|
|
18
|
+
* @returns {object}
|
|
19
|
+
*/
|
|
20
|
+
function validationReport(model) {
|
|
21
|
+
const report = LibraryQaReportBuilder.build({
|
|
22
|
+
pcbLibraries: model.pcbLibrary ? [model] : [],
|
|
23
|
+
schematicLibraries: model.schematicLibrary ? [model] : []
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...modelIdentity(model),
|
|
28
|
+
report
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Prints a concise validation summary.
|
|
34
|
+
* @param {object} report Validation report.
|
|
35
|
+
* @returns {void}
|
|
36
|
+
*/
|
|
37
|
+
function printTextReport(report) {
|
|
38
|
+
console.log(report.title)
|
|
39
|
+
console.log('Type: ' + report.fileType)
|
|
40
|
+
console.log('Issues: ' + report.report.summary.issueCount)
|
|
41
|
+
console.log(
|
|
42
|
+
'Duplicate footprints: ' + report.report.summary.duplicateFootprintCount
|
|
43
|
+
)
|
|
44
|
+
console.log('Missing models: ' + report.report.summary.missingModelCount)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await runReadOnlyScript({
|
|
48
|
+
scriptName: 'validate-library',
|
|
49
|
+
summary: 'Run read-only library QA checks against a parsed library file.',
|
|
50
|
+
helpLines: ['Use --json for the full structured QA report.'],
|
|
51
|
+
run(model, args) {
|
|
52
|
+
const report = validationReport(model)
|
|
53
|
+
if (wantsJson(args)) {
|
|
54
|
+
printJson(report)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
printTextReport(report)
|
|
58
|
+
}
|
|
59
|
+
})
|
package/package.json
CHANGED
package/spec/library-scope.md
CHANGED
|
@@ -31,6 +31,9 @@ rendering primitives.
|
|
|
31
31
|
- Optional renderer CSS
|
|
32
32
|
- Versioned normalized model schema identifiers and machine-readable schema
|
|
33
33
|
contracts
|
|
34
|
+
- Source component client, source bundle exporter, generated `.SchLib` and
|
|
35
|
+
`.PcbLib` byte exporters, and batch export orchestration with progress,
|
|
36
|
+
checkpoint, append-skip, merged output, and retry-friendly client hooks
|
|
34
37
|
|
|
35
38
|
## Out Of Scope
|
|
36
39
|
|
|
@@ -40,6 +43,8 @@ rendering primitives.
|
|
|
40
43
|
- Three.js runtime, OrbitControls, canvas mounting, and picking
|
|
41
44
|
- STEP mesh loading and browser script injection
|
|
42
45
|
- Model ZIP export UI and download orchestration
|
|
46
|
+
- Choosing a hosted component-source provider for applications; hosts inject
|
|
47
|
+
fetch and endpoint configuration explicitly
|
|
43
48
|
- Server, deployment, and app metadata endpoints
|
|
44
49
|
- Native document authoring, round-trip writing, GUI automation, and compiled
|
|
45
50
|
multi-sheet project netlist generation
|