altium-toolkit 1.1.22 → 1.1.24
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 +134 -23
- package/docs/model-format.md +174 -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 +511 -1
- 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 +2 -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/src/core/BinaryReader.mjs +213 -2
- package/src/core/altium/AltiumParser.mjs +352 -14
- package/src/core/altium/AltiumUnits.mjs +205 -0
- package/src/core/altium/AsciiRecordParser.mjs +9 -0
- 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/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/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/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/SchematicOwnershipGraphParser.mjs +102 -3
- package/src/core/altium/SchematicPinParser.mjs +12 -45
- package/src/core/altium/SchematicPrimitiveParser.mjs +9 -14
- 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 +64 -25
- 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/UnsupportedFeatureReportBuilder.mjs +380 -0
- package/src/parser.mjs +35 -1
- package/src/renderers.mjs +1 -0
- package/src/ui/SchematicShapeRenderer.mjs +49 -6
- package/src/ui/SchematicSvgRenderer.mjs +37 -8
- package/src/ui/SchematicTypography.mjs +4 -3
|
@@ -13,12 +13,16 @@ import { SchematicPinParser } from './SchematicPinParser.mjs'
|
|
|
13
13
|
import { SchematicPrimitiveParser } from './SchematicPrimitiveParser.mjs'
|
|
14
14
|
import { AltiumLayoutParser } from './AltiumLayoutParser.mjs'
|
|
15
15
|
import { NormalizedModelSchema } from './NormalizedModelSchema.mjs'
|
|
16
|
+
import { ParserDiagnosticNormalizer } from './ParserDiagnosticNormalizer.mjs'
|
|
17
|
+
import { AltiumUnsupportedFeatureError } from './ParserErrors.mjs'
|
|
16
18
|
import { IntLibModelParser } from './IntLibModelParser.mjs'
|
|
17
19
|
import { IntLibStreamExtractor } from './IntLibStreamExtractor.mjs'
|
|
18
20
|
import { DraftsmanDigestParser } from './DraftsmanDigestParser.mjs'
|
|
19
21
|
import { PcbModelParser } from './PcbModelParser.mjs'
|
|
20
22
|
import { PcbLibModelParser } from './PcbLibModelParser.mjs'
|
|
21
23
|
import { PcbLibStreamExtractor } from './PcbLibStreamExtractor.mjs'
|
|
24
|
+
import { SchLibModelParser } from './SchLibModelParser.mjs'
|
|
25
|
+
import { SchLibStreamExtractor } from './SchLibStreamExtractor.mjs'
|
|
22
26
|
import { PrjPcbModelParser } from './PrjPcbModelParser.mjs'
|
|
23
27
|
import { PrjScrModelParser } from './PrjScrModelParser.mjs'
|
|
24
28
|
import { PcbStreamExtractor } from './PcbStreamExtractor.mjs'
|
|
@@ -35,8 +39,11 @@ import { SchematicComponentOwnerTextResolver } from './SchematicComponentOwnerTe
|
|
|
35
39
|
import { SchematicOwnershipGraphParser } from './SchematicOwnershipGraphParser.mjs'
|
|
36
40
|
import { SchematicStreamExtractor } from './SchematicStreamExtractor.mjs'
|
|
37
41
|
import { SchematicTemplateParser } from './SchematicTemplateParser.mjs'
|
|
42
|
+
import { SchematicThumbnailParser } from './SchematicThumbnailParser.mjs'
|
|
38
43
|
import { SchematicHarnessParser } from './SchematicHarnessParser.mjs'
|
|
39
44
|
import { SchematicImplementationParser } from './SchematicImplementationParser.mjs'
|
|
45
|
+
import { SchematicCodeSymbolParser } from './SchematicCodeSymbolParser.mjs'
|
|
46
|
+
import { SchematicHyperlinkParser } from './SchematicHyperlinkParser.mjs'
|
|
40
47
|
import { SchematicCrossSheetConnectorParser } from './SchematicCrossSheetConnectorParser.mjs'
|
|
41
48
|
import { SchematicRepeatedChannelParser } from './SchematicRepeatedChannelParser.mjs'
|
|
42
49
|
import { SchematicDisplayModeCatalogParser } from './SchematicDisplayModeCatalogParser.mjs'
|
|
@@ -51,6 +58,7 @@ const {
|
|
|
51
58
|
getField,
|
|
52
59
|
parseBoolean,
|
|
53
60
|
parseNumericField,
|
|
61
|
+
parseSchematicLineWidth,
|
|
54
62
|
toColor,
|
|
55
63
|
dedupeByDesignator,
|
|
56
64
|
stripExtension
|
|
@@ -88,11 +96,35 @@ export class AltiumParser {
|
|
|
88
96
|
)
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Parses a native Altium buffer into a non-throwing Circuit JSON envelope.
|
|
101
|
+
* @param {string} fileName
|
|
102
|
+
* @param {ArrayBuffer} arrayBuffer
|
|
103
|
+
* @returns {{ ok: true, model: object[], diagnostics: object[] } | { ok: false, model: null, diagnostics: object[] }}
|
|
104
|
+
*/
|
|
105
|
+
static tryParseArrayBuffer(fileName, arrayBuffer) {
|
|
106
|
+
return AltiumParser.#tryParse(fileName, () =>
|
|
107
|
+
AltiumParser.parseArrayBuffer(fileName, arrayBuffer)
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Parses a native Altium buffer into a non-throwing renderer model envelope.
|
|
113
|
+
* @param {string} fileName
|
|
114
|
+
* @param {ArrayBuffer} arrayBuffer
|
|
115
|
+
* @returns {{ ok: true, model: object, diagnostics: object[] } | { ok: false, model: null, diagnostics: object[] }}
|
|
116
|
+
*/
|
|
117
|
+
static tryParseArrayBufferToRendererModel(fileName, arrayBuffer) {
|
|
118
|
+
return AltiumParser.#tryParse(fileName, () =>
|
|
119
|
+
AltiumParser.parseArrayBufferToRendererModel(fileName, arrayBuffer)
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
91
123
|
/**
|
|
92
124
|
* Parses a native Altium buffer into the renderer compatibility model.
|
|
93
125
|
* @param {string} fileName
|
|
94
126
|
* @param {ArrayBuffer} arrayBuffer
|
|
95
|
-
* @returns {{ schema: string, kind: 'schematic' | 'pcb' | 'pcb-library' | 'project' | 'project-script' | 'integrated-library' | 'draftsman', fileType: 'SchDoc' | 'PcbDoc' | 'PcbLib' | 'PrjPcb' | 'PrjScr' | 'IntLib' | 'PCBDwf', fileName: string, summary: Record<string, number | string>, diagnostics: { severity: 'info' | 'warning', message: string }[], schematic?: Record<string, unknown>, pcb?: Record<string, unknown>, pcbLibrary?: Record<string, unknown>, project?: Record<string, unknown>, projectScript?: Record<string, unknown>, integratedLibrary?: Record<string, unknown>, draftsman?: Record<string, unknown>, bom: { designators: string[], quantity: number, pattern: string, source: string, value: string }[] }}
|
|
127
|
+
* @returns {{ schema: string, kind: 'schematic' | 'pcb' | 'pcb-library' | 'schematic-library' | 'project' | 'project-script' | 'integrated-library' | 'draftsman', fileType: 'SchDoc' | 'PcbDoc' | 'PcbLib' | 'SchLib' | 'PrjPcb' | 'PrjScr' | 'IntLib' | 'PCBDwf', fileName: string, summary: Record<string, number | string>, diagnostics: { severity: 'info' | 'warning', message: string }[], schematic?: Record<string, unknown>, pcb?: Record<string, unknown>, pcbLibrary?: Record<string, unknown>, schematicLibrary?: Record<string, unknown>, project?: Record<string, unknown>, projectScript?: Record<string, unknown>, integratedLibrary?: Record<string, unknown>, draftsman?: Record<string, unknown>, bom: { designators: string[], quantity: number, pattern: string, source: string, value: string }[] }}
|
|
96
128
|
*/
|
|
97
129
|
static parseArrayBufferToRendererModel(fileName, arrayBuffer) {
|
|
98
130
|
const records = AsciiRecordParser.parse(arrayBuffer)
|
|
@@ -122,6 +154,12 @@ export class AltiumParser {
|
|
|
122
154
|
PcbLibStreamExtractor.extractFromArrayBuffer(arrayBuffer)
|
|
123
155
|
)
|
|
124
156
|
}
|
|
157
|
+
if (fileType === 'SchLib') {
|
|
158
|
+
return SchLibModelParser.parse(
|
|
159
|
+
fileName,
|
|
160
|
+
SchLibStreamExtractor.extractFromArrayBuffer(arrayBuffer)
|
|
161
|
+
)
|
|
162
|
+
}
|
|
125
163
|
if (fileType === 'PrjPcb') {
|
|
126
164
|
return PrjPcbModelParser.parse(fileName, arrayBuffer)
|
|
127
165
|
}
|
|
@@ -137,20 +175,78 @@ export class AltiumParser {
|
|
|
137
175
|
if (fileType === 'PCBDwf') {
|
|
138
176
|
return DraftsmanDigestParser.parse(fileName, arrayBuffer)
|
|
139
177
|
}
|
|
140
|
-
throw new
|
|
178
|
+
throw new AltiumUnsupportedFeatureError(
|
|
179
|
+
'Unsupported file type: ' + fileName,
|
|
180
|
+
{ fileName }
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Runs one parser function and returns a normalized result envelope.
|
|
186
|
+
* @param {string} fileName
|
|
187
|
+
* @param {() => object | object[]} parser
|
|
188
|
+
* @returns {{ ok: true, model: object | object[], diagnostics: object[] } | { ok: false, model: null, diagnostics: object[] }}
|
|
189
|
+
*/
|
|
190
|
+
static #tryParse(fileName, parser) {
|
|
191
|
+
try {
|
|
192
|
+
const model = parser()
|
|
193
|
+
return {
|
|
194
|
+
ok: true,
|
|
195
|
+
model,
|
|
196
|
+
diagnostics: ParserDiagnosticNormalizer.normalizeMany(
|
|
197
|
+
model?.diagnostics || [],
|
|
198
|
+
{ source: fileName }
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
return AltiumParser.#safeParseFailure(fileName, error)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Builds one safe-parse failure envelope.
|
|
208
|
+
* @param {string} fileName
|
|
209
|
+
* @param {unknown} error
|
|
210
|
+
* @returns {{ ok: false, model: null, diagnostics: object[] }}
|
|
211
|
+
*/
|
|
212
|
+
static #safeParseFailure(fileName, error) {
|
|
213
|
+
const diagnostic = ParserDiagnosticNormalizer.normalize(error, {
|
|
214
|
+
code: 'parser.safe.parse.failed',
|
|
215
|
+
severity: 'error',
|
|
216
|
+
source: fileName
|
|
217
|
+
})
|
|
218
|
+
const errorKind = diagnostic.errorKind || 'parse'
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
ok: false,
|
|
222
|
+
model: null,
|
|
223
|
+
diagnostics: [
|
|
224
|
+
{
|
|
225
|
+
...diagnostic,
|
|
226
|
+
code: 'parser.safe.parse.failed',
|
|
227
|
+
fileName: String(fileName || ''),
|
|
228
|
+
errorKind,
|
|
229
|
+
errorName:
|
|
230
|
+
error && typeof error === 'object' && 'name' in error
|
|
231
|
+
? String(error.name)
|
|
232
|
+
: 'Error'
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
}
|
|
141
236
|
}
|
|
142
237
|
|
|
143
238
|
/**
|
|
144
239
|
* Chooses the format based on extension and content.
|
|
145
240
|
* @param {string} fileName
|
|
146
241
|
* @param {{ fields: Record<string, string | string[]> }[]} records
|
|
147
|
-
* @returns {'SchDoc' | 'PcbDoc' | 'PcbLib' | 'PrjPcb' | 'PrjScr' | 'IntLib' | 'PCBDwf'}
|
|
242
|
+
* @returns {'SchDoc' | 'PcbDoc' | 'PcbLib' | 'SchLib' | 'PrjPcb' | 'PrjScr' | 'IntLib' | 'PCBDwf'}
|
|
148
243
|
*/
|
|
149
244
|
static #sniffFileType(fileName, records) {
|
|
150
245
|
const normalized = String(fileName || '').toLowerCase()
|
|
151
246
|
if (normalized.endsWith('.schdoc')) return 'SchDoc'
|
|
152
247
|
if (normalized.endsWith('.pcbdoc')) return 'PcbDoc'
|
|
153
248
|
if (normalized.endsWith('.pcblib')) return 'PcbLib'
|
|
249
|
+
if (normalized.endsWith('.schlib')) return 'SchLib'
|
|
154
250
|
if (normalized.endsWith('.prjpcb')) return 'PrjPcb'
|
|
155
251
|
if (normalized.endsWith('.prjscr')) return 'PrjScr'
|
|
156
252
|
if (normalized.endsWith('.intlib')) return 'IntLib'
|
|
@@ -159,7 +255,14 @@ export class AltiumParser {
|
|
|
159
255
|
const hasSchematicHeader = records.some((record) =>
|
|
160
256
|
getField(record.fields, 'HEADER').includes('Schematic')
|
|
161
257
|
)
|
|
162
|
-
|
|
258
|
+
const hasSchematicLibraryHeader = records.some((record) =>
|
|
259
|
+
getField(record.fields, 'HEADER').includes('Schematic Library')
|
|
260
|
+
)
|
|
261
|
+
return hasSchematicLibraryHeader
|
|
262
|
+
? 'SchLib'
|
|
263
|
+
: hasSchematicHeader
|
|
264
|
+
? 'SchDoc'
|
|
265
|
+
: 'PcbDoc'
|
|
163
266
|
}
|
|
164
267
|
/**
|
|
165
268
|
* Normalizes a schematic document.
|
|
@@ -185,6 +288,11 @@ export class AltiumParser {
|
|
|
185
288
|
const recordTypes = SchematicRecordTypeRegistry.summarize(records)
|
|
186
289
|
const ownersWithImplicitDisplayMode =
|
|
187
290
|
AltiumParser.#collectOwnersWithImplicitDisplayMode(records)
|
|
291
|
+
const activeOwnerDisplayModes =
|
|
292
|
+
AltiumParser.#collectActiveOwnerDisplayModes(
|
|
293
|
+
recordIndexAwareRecords,
|
|
294
|
+
componentRecords
|
|
295
|
+
)
|
|
188
296
|
const activeMultipartOwnerParts =
|
|
189
297
|
SchematicMultipartOwnerMatcher.collectActiveMultipartOwnerParts(
|
|
190
298
|
recordIndexAwareRecords,
|
|
@@ -200,6 +308,7 @@ export class AltiumParser {
|
|
|
200
308
|
AltiumParser.#isDrawableSchematicRecord(
|
|
201
309
|
record.fields,
|
|
202
310
|
ownersWithImplicitDisplayMode,
|
|
311
|
+
activeOwnerDisplayModes,
|
|
203
312
|
activeMultipartOwnerParts
|
|
204
313
|
)
|
|
205
314
|
)
|
|
@@ -207,6 +316,7 @@ export class AltiumParser {
|
|
|
207
316
|
AltiumParser.#isDrawableSchematicRecord(
|
|
208
317
|
record.fields,
|
|
209
318
|
ownersWithImplicitDisplayMode,
|
|
319
|
+
activeOwnerDisplayModes,
|
|
210
320
|
activeMultipartOwnerParts
|
|
211
321
|
)
|
|
212
322
|
)
|
|
@@ -215,6 +325,7 @@ export class AltiumParser {
|
|
|
215
325
|
AltiumParser.#isDrawableSchematicRecord(
|
|
216
326
|
record.fields,
|
|
217
327
|
ownersWithImplicitDisplayMode,
|
|
328
|
+
activeOwnerDisplayModes,
|
|
218
329
|
activeMultipartOwnerParts
|
|
219
330
|
) &&
|
|
220
331
|
getField(record.fields, 'RECORD') !== '211' &&
|
|
@@ -308,6 +419,9 @@ export class AltiumParser {
|
|
|
308
419
|
const implementations = SchematicImplementationParser.parse(
|
|
309
420
|
recordIndexAwareRecords
|
|
310
421
|
)
|
|
422
|
+
const codeSymbols = SchematicCodeSymbolParser.parse(
|
|
423
|
+
recordIndexAwareRecords
|
|
424
|
+
)
|
|
311
425
|
const displayModes = SchematicDisplayModeCatalogParser.parse(
|
|
312
426
|
recordIndexAwareRecords
|
|
313
427
|
)
|
|
@@ -394,7 +508,7 @@ export class AltiumParser {
|
|
|
394
508
|
x2: parseNumericField(record.fields, 'Corner.X') || 0,
|
|
395
509
|
y2: parseNumericField(record.fields, 'Corner.Y') || 0,
|
|
396
510
|
color: toColor(record.fields.Color, '#a44a1b'),
|
|
397
|
-
width:
|
|
511
|
+
width: parseSchematicLineWidth(record.fields),
|
|
398
512
|
lineStyle: parseNumericField(record.fields, 'LineStyle') || 0,
|
|
399
513
|
recordType: getField(record.fields, 'RECORD') || undefined,
|
|
400
514
|
renderOrder:
|
|
@@ -503,6 +617,8 @@ export class AltiumParser {
|
|
|
503
617
|
recordIndexAwareRecords,
|
|
504
618
|
arrayBuffer
|
|
505
619
|
)
|
|
620
|
+
const { thumbnails, diagnostics: thumbnailDiagnostics } =
|
|
621
|
+
SchematicThumbnailParser.parse(arrayBuffer)
|
|
506
622
|
const template = SchematicTemplateParser.parse(
|
|
507
623
|
recordIndexAwareRecords,
|
|
508
624
|
sheetRecord,
|
|
@@ -616,12 +732,22 @@ export class AltiumParser {
|
|
|
616
732
|
ports,
|
|
617
733
|
crosses
|
|
618
734
|
)
|
|
735
|
+
const componentDesignatorsByOwnerIndex =
|
|
736
|
+
AltiumParser.#componentDesignatorsByOwnerIndex(
|
|
737
|
+
recordIndexAwareRecords,
|
|
738
|
+
componentRecords,
|
|
739
|
+
components
|
|
740
|
+
)
|
|
619
741
|
|
|
620
742
|
resolvedSheet.xZones =
|
|
621
743
|
SchematicSheetStyleResolver.resolveXZones(resolvedSheet)
|
|
622
744
|
resolvedSheet.yZones =
|
|
623
745
|
SchematicSheetStyleResolver.resolveYZones(resolvedSheet)
|
|
624
746
|
delete resolvedSheet.sheetStyle
|
|
747
|
+
const hyperlinks = SchematicHyperlinkParser.parse(
|
|
748
|
+
recordIndexAwareRecords,
|
|
749
|
+
resolvedSheet
|
|
750
|
+
)
|
|
625
751
|
|
|
626
752
|
const title =
|
|
627
753
|
AltiumParser.#findNamedText(textRecords, 'Title') ||
|
|
@@ -675,6 +801,7 @@ export class AltiumParser {
|
|
|
675
801
|
}))
|
|
676
802
|
)
|
|
677
803
|
diagnostics.push(...imageDiagnostics)
|
|
804
|
+
diagnostics.push(...thumbnailDiagnostics)
|
|
678
805
|
const { nets, diagnostics: netDiagnostics } =
|
|
679
806
|
SchematicNetlistBuilder.build({
|
|
680
807
|
lines: normalizedLines,
|
|
@@ -684,7 +811,8 @@ export class AltiumParser {
|
|
|
684
811
|
crossSheetConnectors: crossSheetConnectors?.connectors || [],
|
|
685
812
|
junctions,
|
|
686
813
|
busEntries,
|
|
687
|
-
sheetEntries
|
|
814
|
+
sheetEntries,
|
|
815
|
+
componentDesignatorsByOwnerIndex
|
|
688
816
|
})
|
|
689
817
|
AltiumParser.#suppressRedundantSinglePinPowerNetNames(
|
|
690
818
|
pins,
|
|
@@ -700,12 +828,17 @@ export class AltiumParser {
|
|
|
700
828
|
})
|
|
701
829
|
const connectivityQa = SchematicConnectivityQaBuilder.build({
|
|
702
830
|
nets,
|
|
831
|
+
lines: normalizedLines,
|
|
703
832
|
texts: anchoredTexts,
|
|
704
833
|
pins,
|
|
705
834
|
ports,
|
|
706
835
|
junctions
|
|
707
836
|
})
|
|
708
837
|
const embeddedFiles = schematicExtraction?.embeddedFiles || null
|
|
838
|
+
const nativeStreams = schematicExtraction?.nativeStreams || null
|
|
839
|
+
const opaqueRecords = Array.isArray(schematicExtraction?.opaqueRecords)
|
|
840
|
+
? schematicExtraction.opaqueRecords
|
|
841
|
+
: []
|
|
709
842
|
|
|
710
843
|
if (embeddedFiles?.diagnostics?.length) {
|
|
711
844
|
diagnostics.push(
|
|
@@ -726,8 +859,17 @@ export class AltiumParser {
|
|
|
726
859
|
componentCount: components.length,
|
|
727
860
|
lineCount: lines.length,
|
|
728
861
|
textCount: anchoredTexts.length,
|
|
862
|
+
...(hyperlinks.length
|
|
863
|
+
? { hyperlinkCount: hyperlinks.length }
|
|
864
|
+
: {}),
|
|
729
865
|
recordTypeCount: recordTypes.length,
|
|
730
|
-
bomRowCount: bom.length
|
|
866
|
+
bomRowCount: bom.length,
|
|
867
|
+
...(nativeStreams?.summary?.streamCount
|
|
868
|
+
? {
|
|
869
|
+
nativeStreamCount: nativeStreams.summary.streamCount
|
|
870
|
+
}
|
|
871
|
+
: {}),
|
|
872
|
+
opaqueRecordCount: opaqueRecords.length
|
|
731
873
|
},
|
|
732
874
|
diagnostics,
|
|
733
875
|
schematic: {
|
|
@@ -750,6 +892,7 @@ export class AltiumParser {
|
|
|
750
892
|
directiveSemantics,
|
|
751
893
|
texts: anchoredTexts,
|
|
752
894
|
textFrames,
|
|
895
|
+
...(hyperlinks.length ? { hyperlinks } : {}),
|
|
753
896
|
components,
|
|
754
897
|
pins,
|
|
755
898
|
ports,
|
|
@@ -762,11 +905,13 @@ export class AltiumParser {
|
|
|
762
905
|
junctions,
|
|
763
906
|
busEntries,
|
|
764
907
|
images,
|
|
908
|
+
...(thumbnails.length ? { thumbnails } : {}),
|
|
765
909
|
nets,
|
|
766
910
|
ownership,
|
|
767
911
|
...(template ? { template } : {}),
|
|
768
912
|
...(harnesses ? { harnesses } : {}),
|
|
769
913
|
...(implementations ? { implementations } : {}),
|
|
914
|
+
...(codeSymbols ? { codeSymbols } : {}),
|
|
770
915
|
...(displayModes ? { displayModes } : {}),
|
|
771
916
|
...(bindings ? { bindings } : {}),
|
|
772
917
|
...(crossSheetConnectors ? { crossSheetConnectors } : {}),
|
|
@@ -776,6 +921,8 @@ export class AltiumParser {
|
|
|
776
921
|
embeddedFiles.diagnostics?.length)
|
|
777
922
|
? { embeddedFiles }
|
|
778
923
|
: {}),
|
|
924
|
+
...(nativeStreams ? { nativeStreams } : {}),
|
|
925
|
+
...(opaqueRecords.length ? { opaqueRecords } : {}),
|
|
779
926
|
qa,
|
|
780
927
|
connectivityQa
|
|
781
928
|
},
|
|
@@ -783,6 +930,43 @@ export class AltiumParser {
|
|
|
783
930
|
})
|
|
784
931
|
}
|
|
785
932
|
|
|
933
|
+
/**
|
|
934
|
+
* Maps native component owner indexes to resolved component designators.
|
|
935
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }[]} records Indexed schematic records.
|
|
936
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }[]} componentRecords Component records.
|
|
937
|
+
* @param {{ designator?: string }[]} components Normalized components.
|
|
938
|
+
* @returns {Map<string, string>}
|
|
939
|
+
*/
|
|
940
|
+
static #componentDesignatorsByOwnerIndex(
|
|
941
|
+
records,
|
|
942
|
+
componentRecords,
|
|
943
|
+
components
|
|
944
|
+
) {
|
|
945
|
+
const designatorsByOwnerIndex = new Map()
|
|
946
|
+
|
|
947
|
+
for (let index = 0; index < componentRecords.length; index += 1) {
|
|
948
|
+
const designator = String(
|
|
949
|
+
components[index]?.designator || ''
|
|
950
|
+
).trim()
|
|
951
|
+
|
|
952
|
+
if (!designator) {
|
|
953
|
+
continue
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
for (const ownerIndex of SchematicComponentOwnerTextResolver.resolveOwnerIndexes(
|
|
957
|
+
componentRecords[index],
|
|
958
|
+
records
|
|
959
|
+
)) {
|
|
960
|
+
const key = String(ownerIndex || '').trim()
|
|
961
|
+
if (key && !designatorsByOwnerIndex.has(key)) {
|
|
962
|
+
designatorsByOwnerIndex.set(key, designator)
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
return designatorsByOwnerIndex
|
|
968
|
+
}
|
|
969
|
+
|
|
786
970
|
/**
|
|
787
971
|
* Hides one-pin owner pin names when a connected power port already labels
|
|
788
972
|
* the same net, preserving the numeric pin endpoint label.
|
|
@@ -826,7 +1010,7 @@ export class AltiumParser {
|
|
|
826
1010
|
AltiumParser.#normalizePowerNetLabel(pin.name)
|
|
827
1011
|
)
|
|
828
1012
|
) {
|
|
829
|
-
pin
|
|
1013
|
+
AltiumParser.#setPinLabelMode(pin, pins, 'number-only')
|
|
830
1014
|
}
|
|
831
1015
|
}
|
|
832
1016
|
}
|
|
@@ -850,11 +1034,51 @@ export class AltiumParser {
|
|
|
850
1034
|
)
|
|
851
1035
|
|
|
852
1036
|
if (matchesDirectPowerPort) {
|
|
853
|
-
pin
|
|
1037
|
+
AltiumParser.#setPinLabelMode(pin, pins, 'number-only')
|
|
854
1038
|
}
|
|
855
1039
|
}
|
|
856
1040
|
}
|
|
857
1041
|
|
|
1042
|
+
/**
|
|
1043
|
+
* Updates a net pin and its matching top-level pin when both are present.
|
|
1044
|
+
* @param {object} pin Net or top-level pin.
|
|
1045
|
+
* @param {object[]} pins Top-level pins.
|
|
1046
|
+
* @param {'hidden' | 'number-only' | 'name-only' | 'name-and-number'} labelMode New label mode.
|
|
1047
|
+
* @returns {void}
|
|
1048
|
+
*/
|
|
1049
|
+
static #setPinLabelMode(pin, pins, labelMode) {
|
|
1050
|
+
pin.labelMode = labelMode
|
|
1051
|
+
|
|
1052
|
+
const sourcePin = (pins || []).find((candidate) =>
|
|
1053
|
+
AltiumParser.#pinsReferToSameSource(candidate, pin)
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
if (sourcePin) {
|
|
1057
|
+
sourcePin.labelMode = labelMode
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Returns true when two pin rows describe the same native source pin.
|
|
1063
|
+
* @param {object} left First pin.
|
|
1064
|
+
* @param {object} right Second pin.
|
|
1065
|
+
* @returns {boolean}
|
|
1066
|
+
*/
|
|
1067
|
+
static #pinsReferToSameSource(left, right) {
|
|
1068
|
+
if (left === right) {
|
|
1069
|
+
return true
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
return (
|
|
1073
|
+
String(left?.ownerIndex || '') ===
|
|
1074
|
+
String(right?.ownerIndex || '') &&
|
|
1075
|
+
String(left?.designator || '') ===
|
|
1076
|
+
String(right?.designator || '') &&
|
|
1077
|
+
String(left?.name || '') === String(right?.name || '') &&
|
|
1078
|
+
AltiumParser.#pointsAreNear(left, right, 0.01)
|
|
1079
|
+
)
|
|
1080
|
+
}
|
|
1081
|
+
|
|
858
1082
|
/**
|
|
859
1083
|
* Returns true when a pin is the only pin on its owner and currently shows
|
|
860
1084
|
* a name label.
|
|
@@ -962,6 +1186,103 @@ export class AltiumParser {
|
|
|
962
1186
|
return owners
|
|
963
1187
|
}
|
|
964
1188
|
|
|
1189
|
+
/**
|
|
1190
|
+
* Maps component owner indexes to their active display mode.
|
|
1191
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }[]} records
|
|
1192
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }[]} componentRecords
|
|
1193
|
+
* @returns {Map<string, number>}
|
|
1194
|
+
*/
|
|
1195
|
+
static #collectActiveOwnerDisplayModes(records, componentRecords) {
|
|
1196
|
+
const activeDisplayModes = new Map()
|
|
1197
|
+
|
|
1198
|
+
for (const componentRecord of componentRecords) {
|
|
1199
|
+
const displayMode =
|
|
1200
|
+
parseNumericField(componentRecord.fields, 'DisplayMode') || 1
|
|
1201
|
+
const displayModeCount =
|
|
1202
|
+
parseNumericField(componentRecord.fields, 'DisplayModeCount') ||
|
|
1203
|
+
0
|
|
1204
|
+
const ownerIndexes = AltiumParser.#componentOwnerIndexCandidates(
|
|
1205
|
+
records,
|
|
1206
|
+
componentRecord
|
|
1207
|
+
)
|
|
1208
|
+
|
|
1209
|
+
if (displayModeCount <= 1 && !ownerIndexes.size) {
|
|
1210
|
+
continue
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
for (const ownerIndex of ownerIndexes) {
|
|
1214
|
+
activeDisplayModes.set(ownerIndex, displayMode)
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
return activeDisplayModes
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
* Resolves owner indexes that may point at one component placement.
|
|
1223
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }[]} records
|
|
1224
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }} componentRecord
|
|
1225
|
+
* @returns {Set<string>}
|
|
1226
|
+
*/
|
|
1227
|
+
static #componentOwnerIndexCandidates(records, componentRecord) {
|
|
1228
|
+
const ownerIndexes = new Set()
|
|
1229
|
+
const indexInSheet = parseNumericField(
|
|
1230
|
+
componentRecord.fields,
|
|
1231
|
+
'IndexInSheet'
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1234
|
+
if (indexInSheet !== null) {
|
|
1235
|
+
ownerIndexes.add(String(indexInSheet))
|
|
1236
|
+
}
|
|
1237
|
+
if (Number.isInteger(componentRecord.recordIndex)) {
|
|
1238
|
+
ownerIndexes.add(String(componentRecord.recordIndex))
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
for (const ownerIndex of AltiumParser.#serializedOwnerDisplayModeIndexes(
|
|
1242
|
+
records,
|
|
1243
|
+
componentRecord.recordIndex
|
|
1244
|
+
)) {
|
|
1245
|
+
ownerIndexes.add(ownerIndex)
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
return ownerIndexes
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* Collects display-mode owner indexes serialized after one component record.
|
|
1253
|
+
* @param {{ fields: Record<string, string | string[]> }[]} records
|
|
1254
|
+
* @param {number | undefined} componentRecordIndex
|
|
1255
|
+
* @returns {Set<string>}
|
|
1256
|
+
*/
|
|
1257
|
+
static #serializedOwnerDisplayModeIndexes(records, componentRecordIndex) {
|
|
1258
|
+
const ownerIndexes = new Set()
|
|
1259
|
+
if (!Number.isInteger(componentRecordIndex)) {
|
|
1260
|
+
return ownerIndexes
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
for (
|
|
1264
|
+
let index = componentRecordIndex + 1;
|
|
1265
|
+
index < records.length;
|
|
1266
|
+
index += 1
|
|
1267
|
+
) {
|
|
1268
|
+
const record = records[index]
|
|
1269
|
+
if (getField(record.fields, 'RECORD') === '1') {
|
|
1270
|
+
break
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
if (!getField(record.fields, 'OwnerPartDisplayMode')) {
|
|
1274
|
+
continue
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
const ownerIndex = getField(record.fields, 'OwnerIndex')
|
|
1278
|
+
if (ownerIndex) {
|
|
1279
|
+
ownerIndexes.add(ownerIndex)
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
return ownerIndexes
|
|
1284
|
+
}
|
|
1285
|
+
|
|
965
1286
|
/**
|
|
966
1287
|
* Collects symbol owners that draw an inaccessible body around compact
|
|
967
1288
|
* internal pins.
|
|
@@ -1024,19 +1345,33 @@ export class AltiumParser {
|
|
|
1024
1345
|
* display mode for its owner.
|
|
1025
1346
|
* @param {Record<string, string | string[]>} fields
|
|
1026
1347
|
* @param {Set<string>} ownersWithImplicitDisplayMode
|
|
1348
|
+
* @param {Map<string, number>} activeOwnerDisplayModes
|
|
1027
1349
|
* @returns {boolean}
|
|
1028
1350
|
*/
|
|
1029
1351
|
static #isActiveSchematicDisplayModeRecord(
|
|
1030
1352
|
fields,
|
|
1031
|
-
ownersWithImplicitDisplayMode
|
|
1353
|
+
ownersWithImplicitDisplayMode,
|
|
1354
|
+
activeOwnerDisplayModes
|
|
1032
1355
|
) {
|
|
1033
1356
|
const ownerIndex = getField(fields, 'OwnerIndex')
|
|
1034
|
-
const ownerPartDisplayMode =
|
|
1035
|
-
|
|
1357
|
+
const ownerPartDisplayMode = parseNumericField(
|
|
1358
|
+
fields,
|
|
1359
|
+
'OwnerPartDisplayMode'
|
|
1360
|
+
)
|
|
1361
|
+
if (!ownerIndex || ownerPartDisplayMode === null) {
|
|
1036
1362
|
return true
|
|
1037
1363
|
}
|
|
1038
1364
|
|
|
1039
|
-
|
|
1365
|
+
if (ownersWithImplicitDisplayMode.has(ownerIndex)) {
|
|
1366
|
+
return false
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
const activeDisplayMode = activeOwnerDisplayModes.get(ownerIndex)
|
|
1370
|
+
if (activeDisplayMode !== undefined) {
|
|
1371
|
+
return ownerPartDisplayMode === activeDisplayMode
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
return true
|
|
1040
1375
|
}
|
|
1041
1376
|
|
|
1042
1377
|
/**
|
|
@@ -1044,18 +1379,21 @@ export class AltiumParser {
|
|
|
1044
1379
|
* display mode and the active multipart owner part for its owner.
|
|
1045
1380
|
* @param {Record<string, string | string[]>} fields
|
|
1046
1381
|
* @param {Set<string>} ownersWithImplicitDisplayMode
|
|
1382
|
+
* @param {Map<string, number>} activeOwnerDisplayModes
|
|
1047
1383
|
* @param {Map<string, string>} activeMultipartOwnerParts
|
|
1048
1384
|
* @returns {boolean}
|
|
1049
1385
|
*/
|
|
1050
1386
|
static #isDrawableSchematicRecord(
|
|
1051
1387
|
fields,
|
|
1052
1388
|
ownersWithImplicitDisplayMode,
|
|
1389
|
+
activeOwnerDisplayModes,
|
|
1053
1390
|
activeMultipartOwnerParts
|
|
1054
1391
|
) {
|
|
1055
1392
|
return (
|
|
1056
1393
|
AltiumParser.#isActiveSchematicDisplayModeRecord(
|
|
1057
1394
|
fields,
|
|
1058
|
-
ownersWithImplicitDisplayMode
|
|
1395
|
+
ownersWithImplicitDisplayMode,
|
|
1396
|
+
activeOwnerDisplayModes
|
|
1059
1397
|
) &&
|
|
1060
1398
|
SchematicMultipartOwnerMatcher.isActiveOwnerPartRecord(
|
|
1061
1399
|
fields,
|