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
|
@@ -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' &&
|
|
@@ -238,8 +349,11 @@ export class AltiumParser {
|
|
|
238
349
|
const rectangleRecords = drawableRecords.filter(
|
|
239
350
|
(record) =>
|
|
240
351
|
SchematicPrimitiveParser.isRectangleRecord(record.fields) &&
|
|
241
|
-
AltiumParser.#hasCoordinatePair(record.fields, 'Location') &&
|
|
242
|
-
|
|
352
|
+
((AltiumParser.#hasCoordinatePair(record.fields, 'Location') &&
|
|
353
|
+
AltiumParser.#hasCoordinatePair(record.fields, 'Corner')) ||
|
|
354
|
+
SchematicPrimitiveParser.isPointListedRectangleRecord(
|
|
355
|
+
record.fields
|
|
356
|
+
))
|
|
243
357
|
)
|
|
244
358
|
const roundedRectangleRecords = drawableRecords.filter(
|
|
245
359
|
(record) =>
|
|
@@ -305,6 +419,9 @@ export class AltiumParser {
|
|
|
305
419
|
const implementations = SchematicImplementationParser.parse(
|
|
306
420
|
recordIndexAwareRecords
|
|
307
421
|
)
|
|
422
|
+
const codeSymbols = SchematicCodeSymbolParser.parse(
|
|
423
|
+
recordIndexAwareRecords
|
|
424
|
+
)
|
|
308
425
|
const displayModes = SchematicDisplayModeCatalogParser.parse(
|
|
309
426
|
recordIndexAwareRecords
|
|
310
427
|
)
|
|
@@ -391,7 +508,7 @@ export class AltiumParser {
|
|
|
391
508
|
x2: parseNumericField(record.fields, 'Corner.X') || 0,
|
|
392
509
|
y2: parseNumericField(record.fields, 'Corner.Y') || 0,
|
|
393
510
|
color: toColor(record.fields.Color, '#a44a1b'),
|
|
394
|
-
width:
|
|
511
|
+
width: parseSchematicLineWidth(record.fields),
|
|
395
512
|
lineStyle: parseNumericField(record.fields, 'LineStyle') || 0,
|
|
396
513
|
recordType: getField(record.fields, 'RECORD') || undefined,
|
|
397
514
|
renderOrder:
|
|
@@ -500,6 +617,8 @@ export class AltiumParser {
|
|
|
500
617
|
recordIndexAwareRecords,
|
|
501
618
|
arrayBuffer
|
|
502
619
|
)
|
|
620
|
+
const { thumbnails, diagnostics: thumbnailDiagnostics } =
|
|
621
|
+
SchematicThumbnailParser.parse(arrayBuffer)
|
|
503
622
|
const template = SchematicTemplateParser.parse(
|
|
504
623
|
recordIndexAwareRecords,
|
|
505
624
|
sheetRecord,
|
|
@@ -613,12 +732,22 @@ export class AltiumParser {
|
|
|
613
732
|
ports,
|
|
614
733
|
crosses
|
|
615
734
|
)
|
|
735
|
+
const componentDesignatorsByOwnerIndex =
|
|
736
|
+
AltiumParser.#componentDesignatorsByOwnerIndex(
|
|
737
|
+
recordIndexAwareRecords,
|
|
738
|
+
componentRecords,
|
|
739
|
+
components
|
|
740
|
+
)
|
|
616
741
|
|
|
617
742
|
resolvedSheet.xZones =
|
|
618
743
|
SchematicSheetStyleResolver.resolveXZones(resolvedSheet)
|
|
619
744
|
resolvedSheet.yZones =
|
|
620
745
|
SchematicSheetStyleResolver.resolveYZones(resolvedSheet)
|
|
621
746
|
delete resolvedSheet.sheetStyle
|
|
747
|
+
const hyperlinks = SchematicHyperlinkParser.parse(
|
|
748
|
+
recordIndexAwareRecords,
|
|
749
|
+
resolvedSheet
|
|
750
|
+
)
|
|
622
751
|
|
|
623
752
|
const title =
|
|
624
753
|
AltiumParser.#findNamedText(textRecords, 'Title') ||
|
|
@@ -672,6 +801,7 @@ export class AltiumParser {
|
|
|
672
801
|
}))
|
|
673
802
|
)
|
|
674
803
|
diagnostics.push(...imageDiagnostics)
|
|
804
|
+
diagnostics.push(...thumbnailDiagnostics)
|
|
675
805
|
const { nets, diagnostics: netDiagnostics } =
|
|
676
806
|
SchematicNetlistBuilder.build({
|
|
677
807
|
lines: normalizedLines,
|
|
@@ -681,7 +811,8 @@ export class AltiumParser {
|
|
|
681
811
|
crossSheetConnectors: crossSheetConnectors?.connectors || [],
|
|
682
812
|
junctions,
|
|
683
813
|
busEntries,
|
|
684
|
-
sheetEntries
|
|
814
|
+
sheetEntries,
|
|
815
|
+
componentDesignatorsByOwnerIndex
|
|
685
816
|
})
|
|
686
817
|
AltiumParser.#suppressRedundantSinglePinPowerNetNames(
|
|
687
818
|
pins,
|
|
@@ -697,12 +828,17 @@ export class AltiumParser {
|
|
|
697
828
|
})
|
|
698
829
|
const connectivityQa = SchematicConnectivityQaBuilder.build({
|
|
699
830
|
nets,
|
|
831
|
+
lines: normalizedLines,
|
|
700
832
|
texts: anchoredTexts,
|
|
701
833
|
pins,
|
|
702
834
|
ports,
|
|
703
835
|
junctions
|
|
704
836
|
})
|
|
705
837
|
const embeddedFiles = schematicExtraction?.embeddedFiles || null
|
|
838
|
+
const nativeStreams = schematicExtraction?.nativeStreams || null
|
|
839
|
+
const opaqueRecords = Array.isArray(schematicExtraction?.opaqueRecords)
|
|
840
|
+
? schematicExtraction.opaqueRecords
|
|
841
|
+
: []
|
|
706
842
|
|
|
707
843
|
if (embeddedFiles?.diagnostics?.length) {
|
|
708
844
|
diagnostics.push(
|
|
@@ -723,8 +859,17 @@ export class AltiumParser {
|
|
|
723
859
|
componentCount: components.length,
|
|
724
860
|
lineCount: lines.length,
|
|
725
861
|
textCount: anchoredTexts.length,
|
|
862
|
+
...(hyperlinks.length
|
|
863
|
+
? { hyperlinkCount: hyperlinks.length }
|
|
864
|
+
: {}),
|
|
726
865
|
recordTypeCount: recordTypes.length,
|
|
727
|
-
bomRowCount: bom.length
|
|
866
|
+
bomRowCount: bom.length,
|
|
867
|
+
...(nativeStreams?.summary?.streamCount
|
|
868
|
+
? {
|
|
869
|
+
nativeStreamCount: nativeStreams.summary.streamCount
|
|
870
|
+
}
|
|
871
|
+
: {}),
|
|
872
|
+
opaqueRecordCount: opaqueRecords.length
|
|
728
873
|
},
|
|
729
874
|
diagnostics,
|
|
730
875
|
schematic: {
|
|
@@ -747,6 +892,7 @@ export class AltiumParser {
|
|
|
747
892
|
directiveSemantics,
|
|
748
893
|
texts: anchoredTexts,
|
|
749
894
|
textFrames,
|
|
895
|
+
...(hyperlinks.length ? { hyperlinks } : {}),
|
|
750
896
|
components,
|
|
751
897
|
pins,
|
|
752
898
|
ports,
|
|
@@ -759,11 +905,13 @@ export class AltiumParser {
|
|
|
759
905
|
junctions,
|
|
760
906
|
busEntries,
|
|
761
907
|
images,
|
|
908
|
+
...(thumbnails.length ? { thumbnails } : {}),
|
|
762
909
|
nets,
|
|
763
910
|
ownership,
|
|
764
911
|
...(template ? { template } : {}),
|
|
765
912
|
...(harnesses ? { harnesses } : {}),
|
|
766
913
|
...(implementations ? { implementations } : {}),
|
|
914
|
+
...(codeSymbols ? { codeSymbols } : {}),
|
|
767
915
|
...(displayModes ? { displayModes } : {}),
|
|
768
916
|
...(bindings ? { bindings } : {}),
|
|
769
917
|
...(crossSheetConnectors ? { crossSheetConnectors } : {}),
|
|
@@ -773,6 +921,8 @@ export class AltiumParser {
|
|
|
773
921
|
embeddedFiles.diagnostics?.length)
|
|
774
922
|
? { embeddedFiles }
|
|
775
923
|
: {}),
|
|
924
|
+
...(nativeStreams ? { nativeStreams } : {}),
|
|
925
|
+
...(opaqueRecords.length ? { opaqueRecords } : {}),
|
|
776
926
|
qa,
|
|
777
927
|
connectivityQa
|
|
778
928
|
},
|
|
@@ -780,6 +930,43 @@ export class AltiumParser {
|
|
|
780
930
|
})
|
|
781
931
|
}
|
|
782
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
|
+
|
|
783
970
|
/**
|
|
784
971
|
* Hides one-pin owner pin names when a connected power port already labels
|
|
785
972
|
* the same net, preserving the numeric pin endpoint label.
|
|
@@ -823,7 +1010,7 @@ export class AltiumParser {
|
|
|
823
1010
|
AltiumParser.#normalizePowerNetLabel(pin.name)
|
|
824
1011
|
)
|
|
825
1012
|
) {
|
|
826
|
-
pin
|
|
1013
|
+
AltiumParser.#setPinLabelMode(pin, pins, 'number-only')
|
|
827
1014
|
}
|
|
828
1015
|
}
|
|
829
1016
|
}
|
|
@@ -847,11 +1034,51 @@ export class AltiumParser {
|
|
|
847
1034
|
)
|
|
848
1035
|
|
|
849
1036
|
if (matchesDirectPowerPort) {
|
|
850
|
-
pin
|
|
1037
|
+
AltiumParser.#setPinLabelMode(pin, pins, 'number-only')
|
|
851
1038
|
}
|
|
852
1039
|
}
|
|
853
1040
|
}
|
|
854
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
|
+
|
|
855
1082
|
/**
|
|
856
1083
|
* Returns true when a pin is the only pin on its owner and currently shows
|
|
857
1084
|
* a name label.
|
|
@@ -959,6 +1186,103 @@ export class AltiumParser {
|
|
|
959
1186
|
return owners
|
|
960
1187
|
}
|
|
961
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
|
+
|
|
962
1286
|
/**
|
|
963
1287
|
* Collects symbol owners that draw an inaccessible body around compact
|
|
964
1288
|
* internal pins.
|
|
@@ -1021,19 +1345,33 @@ export class AltiumParser {
|
|
|
1021
1345
|
* display mode for its owner.
|
|
1022
1346
|
* @param {Record<string, string | string[]>} fields
|
|
1023
1347
|
* @param {Set<string>} ownersWithImplicitDisplayMode
|
|
1348
|
+
* @param {Map<string, number>} activeOwnerDisplayModes
|
|
1024
1349
|
* @returns {boolean}
|
|
1025
1350
|
*/
|
|
1026
1351
|
static #isActiveSchematicDisplayModeRecord(
|
|
1027
1352
|
fields,
|
|
1028
|
-
ownersWithImplicitDisplayMode
|
|
1353
|
+
ownersWithImplicitDisplayMode,
|
|
1354
|
+
activeOwnerDisplayModes
|
|
1029
1355
|
) {
|
|
1030
1356
|
const ownerIndex = getField(fields, 'OwnerIndex')
|
|
1031
|
-
const ownerPartDisplayMode =
|
|
1032
|
-
|
|
1357
|
+
const ownerPartDisplayMode = parseNumericField(
|
|
1358
|
+
fields,
|
|
1359
|
+
'OwnerPartDisplayMode'
|
|
1360
|
+
)
|
|
1361
|
+
if (!ownerIndex || ownerPartDisplayMode === null) {
|
|
1033
1362
|
return true
|
|
1034
1363
|
}
|
|
1035
1364
|
|
|
1036
|
-
|
|
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
|
|
1037
1375
|
}
|
|
1038
1376
|
|
|
1039
1377
|
/**
|
|
@@ -1041,18 +1379,21 @@ export class AltiumParser {
|
|
|
1041
1379
|
* display mode and the active multipart owner part for its owner.
|
|
1042
1380
|
* @param {Record<string, string | string[]>} fields
|
|
1043
1381
|
* @param {Set<string>} ownersWithImplicitDisplayMode
|
|
1382
|
+
* @param {Map<string, number>} activeOwnerDisplayModes
|
|
1044
1383
|
* @param {Map<string, string>} activeMultipartOwnerParts
|
|
1045
1384
|
* @returns {boolean}
|
|
1046
1385
|
*/
|
|
1047
1386
|
static #isDrawableSchematicRecord(
|
|
1048
1387
|
fields,
|
|
1049
1388
|
ownersWithImplicitDisplayMode,
|
|
1389
|
+
activeOwnerDisplayModes,
|
|
1050
1390
|
activeMultipartOwnerParts
|
|
1051
1391
|
) {
|
|
1052
1392
|
return (
|
|
1053
1393
|
AltiumParser.#isActiveSchematicDisplayModeRecord(
|
|
1054
1394
|
fields,
|
|
1055
|
-
ownersWithImplicitDisplayMode
|
|
1395
|
+
ownersWithImplicitDisplayMode,
|
|
1396
|
+
activeOwnerDisplayModes
|
|
1056
1397
|
) &&
|
|
1057
1398
|
SchematicMultipartOwnerMatcher.isActiveOwnerPartRecord(
|
|
1058
1399
|
fields,
|