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
|
@@ -53,6 +53,50 @@ export class PcbLayerStackSourceMetadataParser {
|
|
|
53
53
|
'COVERLAYEXPANSION',
|
|
54
54
|
'COVERLAY_EXPANSION'
|
|
55
55
|
]),
|
|
56
|
+
process: layerField(['PROCESS']),
|
|
57
|
+
pullbackDistance: layerField([
|
|
58
|
+
'PULLBACKDISTANCE',
|
|
59
|
+
'PULLBACK_DISTANCE'
|
|
60
|
+
]),
|
|
61
|
+
copperOrientation: layerField([
|
|
62
|
+
'COPPERORIENTATION',
|
|
63
|
+
'COPPER_ORIENTATION'
|
|
64
|
+
]),
|
|
65
|
+
orientation: layerField(['ORIENTATION']),
|
|
66
|
+
note: layerField(['NOTE']),
|
|
67
|
+
comment: layerField(['COMMENT']),
|
|
68
|
+
materialManufacturer: layerField([
|
|
69
|
+
'MATERIALMANUFACTURER',
|
|
70
|
+
'MATERIAL_MANUFACTURER'
|
|
71
|
+
]),
|
|
72
|
+
materialDescription: layerField([
|
|
73
|
+
'MATERIALDESCRIPTION',
|
|
74
|
+
'MATERIAL_DESCRIPTION'
|
|
75
|
+
]),
|
|
76
|
+
materialGlassTransitionTemp: layerField([
|
|
77
|
+
'MATERIALGLASSTRANSITIONTEMP',
|
|
78
|
+
'MATERIAL_GLASS_TRANSITION_TEMP'
|
|
79
|
+
]),
|
|
80
|
+
glassTransitionTemp: layerField([
|
|
81
|
+
'GLASSTRANSITIONTEMP',
|
|
82
|
+
'GLASS_TRANSITION_TEMP'
|
|
83
|
+
]),
|
|
84
|
+
dielectricStrength: layerField([
|
|
85
|
+
'DIELECTRICSTRENGTH',
|
|
86
|
+
'DIELECTRIC_STRENGTH'
|
|
87
|
+
]),
|
|
88
|
+
volumeResistivity: layerField([
|
|
89
|
+
'VOLUMERESISTIVITY',
|
|
90
|
+
'VOLUME_RESISTIVITY'
|
|
91
|
+
]),
|
|
92
|
+
resin: layerField(['RESIN']),
|
|
93
|
+
solid: layerField(['SOLID']),
|
|
94
|
+
materialFrequency: layerField([
|
|
95
|
+
'MATERIALFREQUENCY',
|
|
96
|
+
'MATERIAL_FREQUENCY'
|
|
97
|
+
]),
|
|
98
|
+
frequency: layerField(['FREQUENCY']),
|
|
99
|
+
constructions: layerField(['CONSTRUCTIONS']),
|
|
56
100
|
isStiffener: PcbLayerStackSourceMetadataParser.#optionalBoolean(
|
|
57
101
|
layerField(['ISSTIFFENER', 'IS_STIFFENER'])
|
|
58
102
|
),
|
|
@@ -235,10 +279,17 @@ export class PcbLayerStackSourceMetadataParser {
|
|
|
235
279
|
* @returns {object[]}
|
|
236
280
|
*/
|
|
237
281
|
static #substackEnablement(fields, layerIndex) {
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
282
|
+
const prefix = 'V9_STACK_LAYER' + layerIndex + '_SUBSTACK'
|
|
283
|
+
if (
|
|
284
|
+
!PcbLayerStackSourceMetadataParser.#hasCaseInsensitivePrefix(
|
|
285
|
+
fields,
|
|
286
|
+
prefix
|
|
287
|
+
)
|
|
288
|
+
) {
|
|
289
|
+
return []
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const pattern = new RegExp('^' + prefix + '(\\d+)_ENABLED$', 'iu')
|
|
242
293
|
|
|
243
294
|
return Object.keys(fields)
|
|
244
295
|
.flatMap((key) => {
|
|
@@ -261,6 +312,25 @@ export class PcbLayerStackSourceMetadataParser {
|
|
|
261
312
|
.sort((left, right) => left.substackIndex - right.substackIndex)
|
|
262
313
|
}
|
|
263
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Returns true when any field key starts with a prefix, ignoring case.
|
|
317
|
+
* @param {Record<string, string | string[]>} fields Source fields.
|
|
318
|
+
* @param {string} prefix Field-key prefix.
|
|
319
|
+
* @returns {boolean}
|
|
320
|
+
*/
|
|
321
|
+
static #hasCaseInsensitivePrefix(fields, prefix) {
|
|
322
|
+
const upperPrefix = prefix.toUpperCase()
|
|
323
|
+
for (const key of PcbLayerStackSourceMetadataParser.#fieldIndex(
|
|
324
|
+
fields
|
|
325
|
+
).keys()) {
|
|
326
|
+
if (key.startsWith(upperPrefix)) {
|
|
327
|
+
return true
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return false
|
|
332
|
+
}
|
|
333
|
+
|
|
264
334
|
/**
|
|
265
335
|
* Parses branch-section stack rows.
|
|
266
336
|
* @param {Record<string, string | string[]>} fields Board fields.
|
|
@@ -20,7 +20,7 @@ export class PcbLibModelParser {
|
|
|
20
20
|
/**
|
|
21
21
|
* Parses one extracted PcbLib into a normalized read-only model.
|
|
22
22
|
* @param {string} fileName
|
|
23
|
-
* @param {{ libraryHeader?: Record<string, string>, componentParamsToc?: Record<string, object>, sectionKeys?: Record<string, string>, footprints?: object[], embeddedFonts?: { fonts?: object[] }, streamNames?: string[], diagnostics?: Record<string, number> } | null} extraction
|
|
23
|
+
* @param {{ libraryHeader?: Record<string, string>, componentParamsToc?: Record<string, object>, sectionKeys?: Record<string, string>, footprints?: object[], embeddedFonts?: { fonts?: object[] }, nativeStreams?: object, streamNames?: string[], diagnostics?: Record<string, number> } | null} extraction
|
|
24
24
|
* @returns {{ schema: string, kind: 'pcb-library', fileType: 'PcbLib', fileName: string, summary: Record<string, number | string>, diagnostics: { severity: 'info' | 'warning', message: string }[], pcbLibrary: { libraryHeader: Record<string, string>, componentParamsToc: Record<string, object>, sectionKeys: Record<string, string>, footprints: object[], embeddedFonts: object[] }, bom: [] }}
|
|
25
25
|
*/
|
|
26
26
|
static parse(fileName, extraction) {
|
|
@@ -54,7 +54,8 @@ export class PcbLibModelParser {
|
|
|
54
54
|
fileName,
|
|
55
55
|
footprints,
|
|
56
56
|
embeddedFonts,
|
|
57
|
-
embeddedModels
|
|
57
|
+
embeddedModels,
|
|
58
|
+
safeExtraction.nativeStreams
|
|
58
59
|
)
|
|
59
60
|
const diagnostics = PcbLibModelParser.#buildDiagnostics(
|
|
60
61
|
footprints,
|
|
@@ -71,6 +72,9 @@ export class PcbLibModelParser {
|
|
|
71
72
|
embeddedFonts,
|
|
72
73
|
embeddedModels,
|
|
73
74
|
componentBodies,
|
|
75
|
+
...(safeExtraction.nativeStreams
|
|
76
|
+
? { nativeStreams: safeExtraction.nativeStreams }
|
|
77
|
+
: {}),
|
|
74
78
|
...(defaults ? { defaults } : {})
|
|
75
79
|
}
|
|
76
80
|
pcbLibrary.renderManifest =
|
|
@@ -94,9 +98,16 @@ export class PcbLibModelParser {
|
|
|
94
98
|
* @param {object[]} footprints
|
|
95
99
|
* @param {object[]} embeddedFonts
|
|
96
100
|
* @param {object[]} embeddedModels
|
|
101
|
+
* @param {object | undefined} nativeStreams Native stream inventory.
|
|
97
102
|
* @returns {Record<string, number | string>}
|
|
98
103
|
*/
|
|
99
|
-
static #buildSummary(
|
|
104
|
+
static #buildSummary(
|
|
105
|
+
fileName,
|
|
106
|
+
footprints,
|
|
107
|
+
embeddedFonts,
|
|
108
|
+
embeddedModels,
|
|
109
|
+
nativeStreams
|
|
110
|
+
) {
|
|
100
111
|
return {
|
|
101
112
|
title: stripExtension(fileName),
|
|
102
113
|
footprintCount: footprints.length,
|
|
@@ -116,7 +127,12 @@ export class PcbLibModelParser {
|
|
|
116
127
|
'rawRecords'
|
|
117
128
|
),
|
|
118
129
|
embeddedFontCount: embeddedFonts.length,
|
|
119
|
-
embeddedModelCount: embeddedModels.length
|
|
130
|
+
embeddedModelCount: embeddedModels.length,
|
|
131
|
+
...(nativeStreams?.summary?.streamCount
|
|
132
|
+
? {
|
|
133
|
+
nativeStreamCount: nativeStreams.summary.streamCount
|
|
134
|
+
}
|
|
135
|
+
: {})
|
|
120
136
|
}
|
|
121
137
|
}
|
|
122
138
|
|
|
@@ -7,6 +7,7 @@ import { PcbCustomPadShapeParser } from './PcbCustomPadShapeParser.mjs'
|
|
|
7
7
|
import { PcbEmbeddedFontExtractor } from './PcbEmbeddedFontExtractor.mjs'
|
|
8
8
|
import { PcbEmbeddedModelExtractor } from './PcbEmbeddedModelExtractor.mjs'
|
|
9
9
|
import { PcbExtendedPrimitiveInformationParser } from './PcbExtendedPrimitiveInformationParser.mjs'
|
|
10
|
+
import { NativeStreamInventoryBuilder } from './NativeStreamInventoryBuilder.mjs'
|
|
10
11
|
import { PcbRawRecordRegistry } from './PcbRawRecordRegistry.mjs'
|
|
11
12
|
import { OleCompoundDocument } from '../ole/OleCompoundDocument.mjs'
|
|
12
13
|
import { OleConstants } from '../ole/OleConstants.mjs'
|
|
@@ -164,18 +165,29 @@ export class PcbLibStreamExtractor {
|
|
|
164
165
|
PcbEmbeddedFontExtractor.extractFromStreams(streams)
|
|
165
166
|
const embeddedModels =
|
|
166
167
|
PcbEmbeddedModelExtractor.extractFromStreams(streams)
|
|
168
|
+
const usedStreamNames = PcbLibStreamExtractor.#collectUsedStreamNames(
|
|
169
|
+
footprints,
|
|
170
|
+
streams,
|
|
171
|
+
embeddedFonts,
|
|
172
|
+
embeddedModels
|
|
173
|
+
)
|
|
174
|
+
const nativeStreams = NativeStreamInventoryBuilder.buildFromStreams(
|
|
175
|
+
streams,
|
|
176
|
+
{
|
|
177
|
+
source: 'pcblib',
|
|
178
|
+
consumedStreamNames: usedStreamNames,
|
|
179
|
+
knownStreamNames:
|
|
180
|
+
PcbLibStreamExtractor.#knownStreamNames(streams)
|
|
181
|
+
}
|
|
182
|
+
)
|
|
167
183
|
|
|
168
184
|
return {
|
|
169
185
|
libraryHeader: parsedLibraryData.libraryHeader,
|
|
170
186
|
componentParamsToc,
|
|
171
187
|
sectionKeys,
|
|
172
188
|
footprints,
|
|
173
|
-
streamNames:
|
|
174
|
-
|
|
175
|
-
streams,
|
|
176
|
-
embeddedFonts,
|
|
177
|
-
embeddedModels
|
|
178
|
-
),
|
|
189
|
+
streamNames: usedStreamNames,
|
|
190
|
+
nativeStreams,
|
|
179
191
|
embeddedFonts,
|
|
180
192
|
embeddedModels,
|
|
181
193
|
diagnostics: {
|
|
@@ -999,6 +1011,37 @@ export class PcbLibStreamExtractor {
|
|
|
999
1011
|
return [...names].sort((left, right) => left.localeCompare(right))
|
|
1000
1012
|
}
|
|
1001
1013
|
|
|
1014
|
+
/**
|
|
1015
|
+
* Builds recognized PcbLib stream names from one stream map.
|
|
1016
|
+
* @param {Map<string, Uint8Array>} streams Native stream map.
|
|
1017
|
+
* @returns {string[]}
|
|
1018
|
+
*/
|
|
1019
|
+
static #knownStreamNames(streams) {
|
|
1020
|
+
return [...(streams || new Map()).keys()].filter((streamName) => {
|
|
1021
|
+
if (
|
|
1022
|
+
[
|
|
1023
|
+
'Library/Data',
|
|
1024
|
+
'Library/ComponentParamsTOC/Data',
|
|
1025
|
+
'SectionKeys'
|
|
1026
|
+
].includes(streamName)
|
|
1027
|
+
) {
|
|
1028
|
+
return true
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const leafName = String(streamName || '')
|
|
1032
|
+
.split('/')
|
|
1033
|
+
.at(-1)
|
|
1034
|
+
return [
|
|
1035
|
+
'Header',
|
|
1036
|
+
'Data',
|
|
1037
|
+
'Parameters',
|
|
1038
|
+
'WideStrings',
|
|
1039
|
+
'ExtendedPrimitiveInformation',
|
|
1040
|
+
'CustomShapes'
|
|
1041
|
+
].includes(leafName)
|
|
1042
|
+
})
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1002
1045
|
/**
|
|
1003
1046
|
* Sanitizes one footprint name for legacy OLE storage lookup.
|
|
1004
1047
|
* @param {string} name
|
|
@@ -12,6 +12,7 @@ import { PcbComponentKindPolicy } from './PcbComponentKindPolicy.mjs'
|
|
|
12
12
|
import { PcbComponentPrimitiveIndexer } from './PcbComponentPrimitiveIndexer.mjs'
|
|
13
13
|
import { PcbCustomPadShapeParser } from './PcbCustomPadShapeParser.mjs'
|
|
14
14
|
import { PcbDimensionParser } from './PcbDimensionParser.mjs'
|
|
15
|
+
import { PcbLayerStackCustomDataParser } from './PcbLayerStackCustomDataParser.mjs'
|
|
15
16
|
import { PcbLayerStackReadModelBuilder } from './PcbLayerStackReadModelBuilder.mjs'
|
|
16
17
|
import { PcbMechanicalLayerPairParser } from './PcbMechanicalLayerPairParser.mjs'
|
|
17
18
|
import { PcbDefaultsParser } from './PcbDefaultsParser.mjs'
|
|
@@ -50,13 +51,21 @@ export class PcbModelParser {
|
|
|
50
51
|
* resolved primitive netName fields keyed by numeric netIndex values.
|
|
51
52
|
* @param {string} fileName
|
|
52
53
|
* @param {{ raw: string, fields: Record<string, string | string[]>, sourceStream?: string }[]} records
|
|
53
|
-
* @param {{ streamNames: string[], binaryPrimitives: Record<string, object[]>, primitiveParameters?: object, viaStructures?: object, customPadShapes?: object, extendedPrimitiveInformation?: object, unions?: object, diagnostics: { printableRecordCount: number, printableStreamCount: number, binaryPrimitiveCount: number } } | null} pcbExtraction
|
|
54
|
+
* @param {{ streamNames: string[], nativeStreams?: object, binaryPrimitives: Record<string, object[]>, primitiveParameters?: object, viaStructures?: object, customPadShapes?: object, extendedPrimitiveInformation?: object, unions?: object, diagnostics: { printableRecordCount: number, printableStreamCount: number, binaryPrimitiveCount: number } } | null} pcbExtraction
|
|
54
55
|
* @returns {{ schema: string, kind: 'pcb', fileType: 'PcbDoc', fileName: string, summary: Record<string, number | string>, diagnostics: { severity: 'info' | 'warning', message: string }[], pcb: Record<string, unknown>, bom: { designators: string[], quantity: number, pattern: string, source: string, value: string }[] }}
|
|
55
56
|
*/
|
|
56
57
|
static parse(fileName, records, pcbExtraction = null) {
|
|
57
58
|
const boardRecords = records.filter(
|
|
58
59
|
(record) => record.sourceStream === 'Board6/Data'
|
|
59
60
|
)
|
|
61
|
+
const stackCustomData =
|
|
62
|
+
PcbLayerStackCustomDataParser.parseBoardRecordsWithDiagnostics(
|
|
63
|
+
boardRecords
|
|
64
|
+
)
|
|
65
|
+
const layerStackBoardRecords = [
|
|
66
|
+
...boardRecords,
|
|
67
|
+
...stackCustomData.records
|
|
68
|
+
]
|
|
60
69
|
const boardRecord =
|
|
61
70
|
boardRecords.find(
|
|
62
71
|
(record) =>
|
|
@@ -64,7 +73,7 @@ export class PcbModelParser {
|
|
|
64
73
|
record.sourceStream === 'Board6/Data'
|
|
65
74
|
) || records.find((record) => getField(record.fields, 'KIND0'))
|
|
66
75
|
const layerRecord =
|
|
67
|
-
|
|
76
|
+
layerStackBoardRecords.find(
|
|
68
77
|
(record) =>
|
|
69
78
|
countMatchingKeys(
|
|
70
79
|
record.fields,
|
|
@@ -100,7 +109,7 @@ export class PcbModelParser {
|
|
|
100
109
|
)
|
|
101
110
|
const layerSubstacks =
|
|
102
111
|
PcbBoardRegionSemanticsParser.parseLayerSubstacks(
|
|
103
|
-
|
|
112
|
+
layerStackBoardRecords.map((record) => record.fields)
|
|
104
113
|
)
|
|
105
114
|
const primitiveLayers = AltiumLayoutParser.parsePrimitiveLayerNames(
|
|
106
115
|
boardRecords.map((record) => record.fields)
|
|
@@ -120,6 +129,9 @@ export class PcbModelParser {
|
|
|
120
129
|
PcbModelParser.#parseDifferentialPairRecords(records),
|
|
121
130
|
classes
|
|
122
131
|
)
|
|
132
|
+
const embeddedBoards =
|
|
133
|
+
PcbModelParser.#parseEmbeddedBoardRecords(records)
|
|
134
|
+
const rooms = PcbModelParser.#parseRoomRecords(records)
|
|
123
135
|
const rules = PcbRuleParser.parse(records)
|
|
124
136
|
const defaults = PcbDefaultsParser.parse(
|
|
125
137
|
boardRecord?.fields || {},
|
|
@@ -190,6 +202,7 @@ export class PcbModelParser {
|
|
|
190
202
|
const rawRecords = Array.isArray(pcbExtraction?.rawRecords)
|
|
191
203
|
? pcbExtraction.rawRecords
|
|
192
204
|
: []
|
|
205
|
+
const nativeStreams = pcbExtraction?.nativeStreams || null
|
|
193
206
|
const viaStructures = pcbExtraction?.viaStructures || {
|
|
194
207
|
structures: [],
|
|
195
208
|
links: [],
|
|
@@ -257,7 +270,7 @@ export class PcbModelParser {
|
|
|
257
270
|
)
|
|
258
271
|
const layerStackReadModel = PcbLayerStackReadModelBuilder.build({
|
|
259
272
|
fileName,
|
|
260
|
-
boardRecords,
|
|
273
|
+
boardRecords: layerStackBoardRecords,
|
|
261
274
|
streamNames: pcbExtraction?.streamNames || [],
|
|
262
275
|
layers,
|
|
263
276
|
primitiveLayers,
|
|
@@ -406,6 +419,36 @@ export class PcbModelParser {
|
|
|
406
419
|
})
|
|
407
420
|
}
|
|
408
421
|
|
|
422
|
+
if (embeddedBoards.length || rooms.length) {
|
|
423
|
+
diagnostics.push({
|
|
424
|
+
severity: 'info',
|
|
425
|
+
message:
|
|
426
|
+
'Recovered ' +
|
|
427
|
+
embeddedBoards.length +
|
|
428
|
+
' embedded board ' +
|
|
429
|
+
PcbModelParser.#plural(
|
|
430
|
+
embeddedBoards.length,
|
|
431
|
+
'placement',
|
|
432
|
+
'placements'
|
|
433
|
+
) +
|
|
434
|
+
' and ' +
|
|
435
|
+
rooms.length +
|
|
436
|
+
' placement ' +
|
|
437
|
+
PcbModelParser.#plural(rooms.length, 'room', 'rooms') +
|
|
438
|
+
'.'
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
for (const issue of stackCustomData.diagnostics) {
|
|
443
|
+
diagnostics.push({
|
|
444
|
+
severity: issue.severity || 'warning',
|
|
445
|
+
code: issue.code,
|
|
446
|
+
message: issue.message,
|
|
447
|
+
sourceStream: issue.sourceStream,
|
|
448
|
+
fieldName: issue.fieldName
|
|
449
|
+
})
|
|
450
|
+
}
|
|
451
|
+
|
|
409
452
|
for (const issue of layerStackReadModel?.diagnostics || []) {
|
|
410
453
|
diagnostics.push({
|
|
411
454
|
severity: issue.severity || 'warning',
|
|
@@ -607,6 +650,10 @@ export class PcbModelParser {
|
|
|
607
650
|
differentialPairData.differentialPairs.length,
|
|
608
651
|
differentialPairClassCount:
|
|
609
652
|
differentialPairData.differentialPairClasses.length,
|
|
653
|
+
...(embeddedBoards.length
|
|
654
|
+
? { embeddedBoardCount: embeddedBoards.length }
|
|
655
|
+
: {}),
|
|
656
|
+
...(rooms.length ? { roomCount: rooms.length } : {}),
|
|
610
657
|
ruleCount: rules.length,
|
|
611
658
|
routeReviewGroupCount:
|
|
612
659
|
reviewMetadata.summary.routeGroupCount || 0,
|
|
@@ -649,6 +696,11 @@ export class PcbModelParser {
|
|
|
649
696
|
embeddedFontCount: extractedEmbeddedFonts.length,
|
|
650
697
|
embeddedFileCount: embeddedFiles.files.length,
|
|
651
698
|
rawRecordCount: rawRecords.length,
|
|
699
|
+
...(nativeStreams?.summary?.streamCount
|
|
700
|
+
? {
|
|
701
|
+
nativeStreamCount: nativeStreams.summary.streamCount
|
|
702
|
+
}
|
|
703
|
+
: {}),
|
|
652
704
|
boardWidthMil: Math.round(boardOutline.widthMil),
|
|
653
705
|
boardHeightMil: Math.round(boardOutline.heightMil)
|
|
654
706
|
},
|
|
@@ -669,6 +721,8 @@ export class PcbModelParser {
|
|
|
669
721
|
differentialPairs: differentialPairData.differentialPairs,
|
|
670
722
|
differentialPairClasses:
|
|
671
723
|
differentialPairData.differentialPairClasses,
|
|
724
|
+
...(embeddedBoards.length ? { embeddedBoards } : {}),
|
|
725
|
+
...(rooms.length ? { rooms } : {}),
|
|
672
726
|
rules,
|
|
673
727
|
...(defaults ? { defaults } : {}),
|
|
674
728
|
maskPaste,
|
|
@@ -698,6 +752,7 @@ export class PcbModelParser {
|
|
|
698
752
|
embeddedFonts: extractedEmbeddedFonts,
|
|
699
753
|
embeddedFiles,
|
|
700
754
|
rawRecords,
|
|
755
|
+
...(nativeStreams ? { nativeStreams } : {}),
|
|
701
756
|
componentBodies,
|
|
702
757
|
componentPrimitives,
|
|
703
758
|
componentPrimitiveGroups,
|
|
@@ -995,6 +1050,136 @@ export class PcbModelParser {
|
|
|
995
1050
|
return netNameByIndex
|
|
996
1051
|
}
|
|
997
1052
|
|
|
1053
|
+
/**
|
|
1054
|
+
* Parses panel embedded-board placement records.
|
|
1055
|
+
* @param {{ fields: Record<string, string | string[]>, sourceStream?: string }[]} records Source records.
|
|
1056
|
+
* @returns {object[]}
|
|
1057
|
+
*/
|
|
1058
|
+
static #parseEmbeddedBoardRecords(records) {
|
|
1059
|
+
return (records || [])
|
|
1060
|
+
.filter((record) => record.sourceStream === 'EmbeddedBoards6/Data')
|
|
1061
|
+
.map((record, index) =>
|
|
1062
|
+
PcbModelParser.#normalizeEmbeddedBoardRecord(
|
|
1063
|
+
record.fields,
|
|
1064
|
+
index
|
|
1065
|
+
)
|
|
1066
|
+
)
|
|
1067
|
+
.filter(
|
|
1068
|
+
(embeddedBoard) =>
|
|
1069
|
+
embeddedBoard.documentPath || embeddedBoard.uniqueId
|
|
1070
|
+
)
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Normalizes one embedded-board placement.
|
|
1075
|
+
* @param {Record<string, string | string[]>} fields Native fields.
|
|
1076
|
+
* @param {number} embeddedBoardIndex Fallback index.
|
|
1077
|
+
* @returns {object}
|
|
1078
|
+
*/
|
|
1079
|
+
static #normalizeEmbeddedBoardRecord(fields, embeddedBoardIndex) {
|
|
1080
|
+
const documentPath = PcbModelParser.#firstField(fields, [
|
|
1081
|
+
'DOCUMENTPATH',
|
|
1082
|
+
'DOCUMENTNAME',
|
|
1083
|
+
'FILENAME'
|
|
1084
|
+
])
|
|
1085
|
+
const array = PcbModelParser.#stripEmptyObject({
|
|
1086
|
+
columns: PcbModelParser.#firstNumberField(fields, [
|
|
1087
|
+
'COLCOUNT',
|
|
1088
|
+
'COLUMNCOUNT',
|
|
1089
|
+
'COLUMNS'
|
|
1090
|
+
]),
|
|
1091
|
+
rows: PcbModelParser.#firstNumberField(fields, [
|
|
1092
|
+
'ROWCOUNT',
|
|
1093
|
+
'ROWS'
|
|
1094
|
+
]),
|
|
1095
|
+
columnSpacingMil: PcbModelParser.#firstNumberField(fields, [
|
|
1096
|
+
'COLUMNSPACING',
|
|
1097
|
+
'COLSPACING',
|
|
1098
|
+
'XSPACING'
|
|
1099
|
+
]),
|
|
1100
|
+
rowSpacingMil: PcbModelParser.#firstNumberField(fields, [
|
|
1101
|
+
'ROWSPACING',
|
|
1102
|
+
'YSPACING'
|
|
1103
|
+
])
|
|
1104
|
+
})
|
|
1105
|
+
const flags = PcbModelParser.#stripEmptyObject({
|
|
1106
|
+
keepIn: PcbModelParser.#optionalBooleanAny(fields, [
|
|
1107
|
+
'KEEPIN',
|
|
1108
|
+
'KEEPINSIDE'
|
|
1109
|
+
]),
|
|
1110
|
+
keepout: PcbModelParser.#optionalBooleanAny(fields, ['KEEPOUT']),
|
|
1111
|
+
electrical: PcbModelParser.#optionalBooleanAny(fields, [
|
|
1112
|
+
'ELECTRICAL'
|
|
1113
|
+
]),
|
|
1114
|
+
preRoute: PcbModelParser.#optionalBooleanAny(fields, ['PREROUTE']),
|
|
1115
|
+
teardrops: PcbModelParser.#optionalBooleanAny(fields, [
|
|
1116
|
+
'TEARDROPS'
|
|
1117
|
+
]),
|
|
1118
|
+
polygons: PcbModelParser.#optionalBooleanAny(fields, ['POLYGONS']),
|
|
1119
|
+
transmitBoardShape: PcbModelParser.#optionalBooleanAny(fields, [
|
|
1120
|
+
'TRANSMITBOARDSHAPE'
|
|
1121
|
+
])
|
|
1122
|
+
})
|
|
1123
|
+
|
|
1124
|
+
return PcbModelParser.#stripEmptyObject({
|
|
1125
|
+
embeddedBoardIndex,
|
|
1126
|
+
documentPath,
|
|
1127
|
+
fileName: PcbModelParser.#basenameFromAltiumPath(documentPath),
|
|
1128
|
+
layer: PcbModelParser.#firstField(fields, ['LAYER', 'LAYERNAME']),
|
|
1129
|
+
rotation: PcbModelParser.#firstNumberField(fields, ['ROTATION']),
|
|
1130
|
+
mirrored: PcbModelParser.#optionalBooleanAny(fields, [
|
|
1131
|
+
'MIRRORFLAG',
|
|
1132
|
+
'MIRRORED'
|
|
1133
|
+
]),
|
|
1134
|
+
originMode: PcbModelParser.#firstNumberField(fields, [
|
|
1135
|
+
'ORIGINMODE'
|
|
1136
|
+
]),
|
|
1137
|
+
scale: PcbModelParser.#firstNumberField(fields, ['SCALE']),
|
|
1138
|
+
...(Object.keys(array).length ? { array } : {}),
|
|
1139
|
+
uniqueId: PcbModelParser.#firstField(fields, ['UNIQUEID', 'UID']),
|
|
1140
|
+
...(Object.keys(flags).length ? { flags } : {})
|
|
1141
|
+
})
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Parses PCB placement room records.
|
|
1146
|
+
* @param {{ fields: Record<string, string | string[]>, sourceStream?: string }[]} records Source records.
|
|
1147
|
+
* @returns {object[]}
|
|
1148
|
+
*/
|
|
1149
|
+
static #parseRoomRecords(records) {
|
|
1150
|
+
return (records || [])
|
|
1151
|
+
.filter((record) => record.sourceStream === 'Rooms6/Data')
|
|
1152
|
+
.map((record, index) =>
|
|
1153
|
+
PcbModelParser.#normalizeRoomRecord(record.fields, index)
|
|
1154
|
+
)
|
|
1155
|
+
.filter(
|
|
1156
|
+
(room) => room.name || room.uniqueId || room.members?.length
|
|
1157
|
+
)
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* Normalizes one placement room record.
|
|
1162
|
+
* @param {Record<string, string | string[]>} fields Native fields.
|
|
1163
|
+
* @param {number} roomIndex Fallback index.
|
|
1164
|
+
* @returns {object}
|
|
1165
|
+
*/
|
|
1166
|
+
static #normalizeRoomRecord(fields, roomIndex) {
|
|
1167
|
+
const bounds = PcbModelParser.#stripEmptyObject({
|
|
1168
|
+
x1: PcbModelParser.#firstNumberField(fields, ['X1', 'LEFT']),
|
|
1169
|
+
y1: PcbModelParser.#firstNumberField(fields, ['Y1', 'TOP']),
|
|
1170
|
+
x2: PcbModelParser.#firstNumberField(fields, ['X2', 'RIGHT']),
|
|
1171
|
+
y2: PcbModelParser.#firstNumberField(fields, ['Y2', 'BOTTOM'])
|
|
1172
|
+
})
|
|
1173
|
+
|
|
1174
|
+
return PcbModelParser.#stripEmptyObject({
|
|
1175
|
+
roomIndex,
|
|
1176
|
+
name: PcbModelParser.#firstField(fields, ['NAME', 'ROOMNAME']),
|
|
1177
|
+
uniqueId: PcbModelParser.#firstField(fields, ['UNIQUEID', 'UID']),
|
|
1178
|
+
members: PcbModelParser.#parseClassMembers(fields),
|
|
1179
|
+
...(Object.keys(bounds).length ? { bounds } : {})
|
|
1180
|
+
})
|
|
1181
|
+
}
|
|
1182
|
+
|
|
998
1183
|
/**
|
|
999
1184
|
* Parses native DifferentialPairs6/Data records in stream order.
|
|
1000
1185
|
* @param {{ fields: Record<string, string | string[]>, sourceStream?: string }[]} records
|
|
@@ -1150,6 +1335,23 @@ export class PcbModelParser {
|
|
|
1150
1335
|
return raw ? parseBoolean(raw) : null
|
|
1151
1336
|
}
|
|
1152
1337
|
|
|
1338
|
+
/**
|
|
1339
|
+
* Reads the first optional boolean from a list of field aliases.
|
|
1340
|
+
* @param {Record<string, string | string[]>} fields Native fields.
|
|
1341
|
+
* @param {string[]} keys Candidate field names.
|
|
1342
|
+
* @returns {boolean | null}
|
|
1343
|
+
*/
|
|
1344
|
+
static #optionalBooleanAny(fields, keys) {
|
|
1345
|
+
for (const key of keys || []) {
|
|
1346
|
+
const raw = getField(fields, key)
|
|
1347
|
+
if (raw) {
|
|
1348
|
+
return parseBoolean(raw)
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
return null
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1153
1355
|
/**
|
|
1154
1356
|
* Builds a case-insensitive lookup key for class and pair names.
|
|
1155
1357
|
* @param {string | undefined} value Raw lookup value.
|
|
@@ -1435,6 +1637,23 @@ export class PcbModelParser {
|
|
|
1435
1637
|
return null
|
|
1436
1638
|
}
|
|
1437
1639
|
|
|
1640
|
+
/**
|
|
1641
|
+
* Returns the first numeric field value from a list of field aliases.
|
|
1642
|
+
* @param {Record<string, string | string[]>} fields Source fields.
|
|
1643
|
+
* @param {string[]} keys Candidate keys.
|
|
1644
|
+
* @returns {number | null}
|
|
1645
|
+
*/
|
|
1646
|
+
static #firstNumberField(fields, keys) {
|
|
1647
|
+
for (const key of keys || []) {
|
|
1648
|
+
const parsed = parseNumericField(fields, key)
|
|
1649
|
+
if (Number.isFinite(parsed)) {
|
|
1650
|
+
return parsed
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
return null
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1438
1657
|
/**
|
|
1439
1658
|
* Returns the first non-empty printable field value.
|
|
1440
1659
|
* @param {Record<string, string | string[]>} fields Source fields.
|