altium-toolkit 1.0.8 → 1.0.9
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 +18 -6
- package/docs/api.md +78 -16
- package/docs/model-format.md +229 -8
- package/docs/schemas/altium_toolkit/netlist_a1.schema.json +47 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +1661 -104
- package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +59 -0
- package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +57 -0
- package/docs/schemas/altium_toolkit/schematic_svg_semantics_a1.schema.json +50 -0
- package/docs/testing.md +9 -3
- package/package.json +1 -1
- package/spec/library-scope.md +7 -1
- package/src/core/altium/AltiumLayoutParser.mjs +104 -8
- package/src/core/altium/AltiumParser.mjs +191 -45
- package/src/core/altium/EmbeddedFileInventoryBuilder.mjs +255 -0
- package/src/core/altium/IntLibModelParser.mjs +240 -0
- package/src/core/altium/IntLibStreamExtractor.mjs +366 -0
- package/src/core/altium/LibraryRenderManifestBuilder.mjs +417 -0
- package/src/core/altium/LibrarySearchIndex.mjs +215 -0
- package/src/core/altium/NormalizedModelSchema.mjs +36 -0
- package/src/core/altium/PcbCustomPadShapeParser.mjs +244 -0
- package/src/core/altium/PcbDefaultsParser.mjs +171 -0
- package/src/core/altium/PcbDimensionParser.mjs +229 -0
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +232 -6
- package/src/core/altium/PcbExtendedPrimitiveInformationParser.mjs +256 -0
- package/src/core/altium/PcbLibModelParser.mjs +235 -14
- package/src/core/altium/PcbLibStreamExtractor.mjs +62 -4
- package/src/core/altium/PcbMaskPasteResolver.mjs +354 -0
- package/src/core/altium/PcbMechanicalLayerPairParser.mjs +204 -0
- package/src/core/altium/PcbModelParser.mjs +466 -28
- package/src/core/altium/PcbOwnershipGraphBuilder.mjs +245 -0
- package/src/core/altium/PcbPadPrimitiveParser.mjs +78 -65
- package/src/core/altium/PcbPadStackParser.mjs +58 -0
- package/src/core/altium/PcbPickPlacePositionResolver.mjs +217 -0
- package/src/core/altium/PcbPrimitiveParameterParser.mjs +3 -2
- package/src/core/altium/PcbRawRecordRegistry.mjs +121 -130
- package/src/core/altium/PcbRegionPrimitiveParser.mjs +5 -1
- package/src/core/altium/PcbRuleParser.mjs +354 -33
- package/src/core/altium/PcbSidecarRecordParser.mjs +177 -0
- package/src/core/altium/PcbSpecialStringResolver.mjs +220 -0
- package/src/core/altium/PcbStatisticsBuilder.mjs +532 -0
- package/src/core/altium/PcbStreamExtractor.mjs +111 -4
- package/src/core/altium/PcbTextPrimitiveParser.mjs +60 -0
- package/src/core/altium/PcbUnionParser.mjs +307 -0
- package/src/core/altium/PcbViaStackParser.mjs +98 -10
- package/src/core/altium/PcbViaStructureParser.mjs +335 -0
- package/src/core/altium/PrintableTextDecoder.mjs +53 -3
- package/src/core/altium/PrjPcbModelParser.mjs +257 -5
- package/src/core/altium/ProjectAnnotationParser.mjs +205 -0
- package/src/core/altium/ProjectDesignBundleBuilder.mjs +477 -0
- package/src/core/altium/ProjectNetlistExporter.mjs +499 -0
- package/src/core/altium/ProjectOutJobDigestBuilder.mjs +109 -0
- package/src/core/altium/ProjectVariantViewBuilder.mjs +334 -0
- package/src/core/altium/SchematicBindingProvenanceParser.mjs +223 -0
- package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +312 -0
- package/src/core/altium/SchematicComponentTextResolver.mjs +72 -19
- package/src/core/altium/SchematicConnectivityQaBuilder.mjs +271 -0
- package/src/core/altium/SchematicCrossSheetConnectorParser.mjs +140 -0
- package/src/core/altium/SchematicDirectiveParser.mjs +312 -0
- package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +231 -0
- package/src/core/altium/SchematicHarnessParser.mjs +302 -0
- package/src/core/altium/SchematicImageParser.mjs +474 -3
- package/src/core/altium/SchematicImplementationParser.mjs +518 -0
- package/src/core/altium/SchematicNetlistBuilder.mjs +15 -2
- package/src/core/altium/SchematicOwnershipGraphParser.mjs +195 -0
- package/src/core/altium/SchematicPinParser.mjs +84 -1
- package/src/core/altium/SchematicPrimitiveParser.mjs +301 -0
- package/src/core/altium/SchematicProjectParameterResolver.mjs +361 -0
- package/src/core/altium/SchematicQaReportBuilder.mjs +284 -0
- package/src/core/altium/SchematicRecordTypeRegistry.mjs +137 -0
- package/src/core/altium/SchematicRepeatedChannelParser.mjs +229 -0
- package/src/core/altium/SchematicStreamExtractor.mjs +10 -1
- package/src/core/altium/SchematicTemplateParser.mjs +256 -0
- package/src/core/altium/SchematicTextParser.mjs +123 -0
- package/src/core/ole/OleCompoundDocument.mjs +20 -0
- package/src/parser.mjs +29 -0
- package/src/styles/altium-renderers.css +19 -0
- package/src/ui/PcbBarcodeTextRenderer.mjs +436 -0
- package/src/ui/PcbInteractionIndex.mjs +9 -4
- package/src/ui/PcbScene3dBuilder.mjs +137 -3
- package/src/ui/PcbScene3dModelRegistry.mjs +74 -0
- package/src/ui/PcbSvgRenderer.mjs +1187 -34
- package/src/ui/PcbTextPrimitiveRenderer.mjs +193 -7
- package/src/ui/SchematicNoteRenderer.mjs +9 -2
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +206 -0
- package/src/ui/SchematicShapeRenderer.mjs +362 -0
- package/src/ui/SchematicSvgRenderer.mjs +1442 -92
- package/src/ui/SchematicTypography.mjs +48 -5
- package/src/ui/TextGeometrySidecarBuilder.mjs +147 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds a read-only PCB primitive ownership graph from normalized indexes.
|
|
7
|
+
*/
|
|
8
|
+
export class PcbOwnershipGraphBuilder {
|
|
9
|
+
static SCHEMA_ID = 'altium-toolkit.pcb.ownership.a1'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builds primitive owner groups keyed by component, net, and polygon index.
|
|
13
|
+
* @param {{ components?: object[], nets?: object[], fills?: object[], tracks?: object[], arcs?: object[], vias?: object[], pads?: object[], regions?: object[], shapeBasedRegions?: object[], texts?: object[] }} pcb Normalized PCB model.
|
|
14
|
+
* @returns {{ schema: string, primitiveOwners: object[], componentsByIndex: Record<string, object>, netsByIndex: Record<string, object>, polygonsByIndex: Record<string, object> }}
|
|
15
|
+
*/
|
|
16
|
+
static build(pcb) {
|
|
17
|
+
const componentNames = PcbOwnershipGraphBuilder.#componentNames(
|
|
18
|
+
pcb?.components || []
|
|
19
|
+
)
|
|
20
|
+
const netNames = PcbOwnershipGraphBuilder.#netNames(pcb?.nets || [])
|
|
21
|
+
const componentsByIndex =
|
|
22
|
+
PcbOwnershipGraphBuilder.#initialComponentGroups(
|
|
23
|
+
pcb?.components || []
|
|
24
|
+
)
|
|
25
|
+
const netsByIndex = {}
|
|
26
|
+
const polygonsByIndex = {}
|
|
27
|
+
const primitiveOwners = []
|
|
28
|
+
|
|
29
|
+
for (const item of PcbOwnershipGraphBuilder.#primitiveItems(pcb)) {
|
|
30
|
+
const owner = PcbOwnershipGraphBuilder.#primitiveOwner(
|
|
31
|
+
item,
|
|
32
|
+
componentNames,
|
|
33
|
+
netNames
|
|
34
|
+
)
|
|
35
|
+
if (!owner) {
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
primitiveOwners.push(owner)
|
|
40
|
+
PcbOwnershipGraphBuilder.#addGroupKey(
|
|
41
|
+
componentsByIndex,
|
|
42
|
+
owner.componentIndex,
|
|
43
|
+
{
|
|
44
|
+
componentIndex: owner.componentIndex,
|
|
45
|
+
designator: owner.component || '',
|
|
46
|
+
primitiveKeys: []
|
|
47
|
+
},
|
|
48
|
+
owner.primitiveKey
|
|
49
|
+
)
|
|
50
|
+
PcbOwnershipGraphBuilder.#addGroupKey(
|
|
51
|
+
netsByIndex,
|
|
52
|
+
owner.netIndex,
|
|
53
|
+
{
|
|
54
|
+
netIndex: owner.netIndex,
|
|
55
|
+
name: owner.net || '',
|
|
56
|
+
primitiveKeys: []
|
|
57
|
+
},
|
|
58
|
+
owner.primitiveKey
|
|
59
|
+
)
|
|
60
|
+
PcbOwnershipGraphBuilder.#addGroupKey(
|
|
61
|
+
polygonsByIndex,
|
|
62
|
+
owner.polygonIndex,
|
|
63
|
+
{
|
|
64
|
+
polygonIndex: owner.polygonIndex,
|
|
65
|
+
primitiveKeys: []
|
|
66
|
+
},
|
|
67
|
+
owner.primitiveKey
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
schema: PcbOwnershipGraphBuilder.SCHEMA_ID,
|
|
73
|
+
primitiveOwners,
|
|
74
|
+
componentsByIndex,
|
|
75
|
+
netsByIndex,
|
|
76
|
+
polygonsByIndex
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Builds primitive iterable entries in stable renderer collection order.
|
|
82
|
+
* @param {object} pcb Normalized PCB model.
|
|
83
|
+
* @returns {{ primitiveKind: string, primitiveKey: string, primitive: object }[]}
|
|
84
|
+
*/
|
|
85
|
+
static #primitiveItems(pcb) {
|
|
86
|
+
return [
|
|
87
|
+
['fill', pcb?.fills || []],
|
|
88
|
+
['track', pcb?.tracks || []],
|
|
89
|
+
['arc', pcb?.arcs || []],
|
|
90
|
+
['via', pcb?.vias || []],
|
|
91
|
+
['pad', pcb?.pads || []],
|
|
92
|
+
['region', pcb?.regions || []],
|
|
93
|
+
['shape-based-region', pcb?.shapeBasedRegions || []],
|
|
94
|
+
['text', pcb?.texts || []],
|
|
95
|
+
['polygon', pcb?.polygons || []]
|
|
96
|
+
].flatMap(([primitiveKind, primitives]) =>
|
|
97
|
+
primitives.map((primitive, index) => ({
|
|
98
|
+
primitiveKind,
|
|
99
|
+
primitiveKey: primitiveKind + '-' + index,
|
|
100
|
+
primitive
|
|
101
|
+
}))
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Builds a compact primitive owner row.
|
|
107
|
+
* @param {{ primitiveKind: string, primitiveKey: string, primitive: object }} item Primitive item.
|
|
108
|
+
* @param {Map<number, string>} componentNames Component names by native index.
|
|
109
|
+
* @param {Map<number, string>} netNames Net names by native index.
|
|
110
|
+
* @returns {object | null}
|
|
111
|
+
*/
|
|
112
|
+
static #primitiveOwner(item, componentNames, netNames) {
|
|
113
|
+
const componentIndex =
|
|
114
|
+
PcbOwnershipGraphBuilder.#optionalInteger(
|
|
115
|
+
item.primitive.componentIndex
|
|
116
|
+
) ??
|
|
117
|
+
(item.primitiveKind === 'text'
|
|
118
|
+
? PcbOwnershipGraphBuilder.#optionalInteger(
|
|
119
|
+
item.primitive.ownerIndex
|
|
120
|
+
)
|
|
121
|
+
: null)
|
|
122
|
+
const netIndex = PcbOwnershipGraphBuilder.#optionalInteger(
|
|
123
|
+
item.primitive.netIndex
|
|
124
|
+
)
|
|
125
|
+
const polygonIndex = PcbOwnershipGraphBuilder.#optionalInteger(
|
|
126
|
+
item.primitive.polygonIndex
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
componentIndex === null &&
|
|
131
|
+
netIndex === null &&
|
|
132
|
+
polygonIndex === null
|
|
133
|
+
) {
|
|
134
|
+
return null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
primitiveKey: item.primitiveKey,
|
|
139
|
+
primitiveKind: item.primitiveKind,
|
|
140
|
+
componentIndex,
|
|
141
|
+
component:
|
|
142
|
+
componentIndex === null
|
|
143
|
+
? ''
|
|
144
|
+
: componentNames.get(componentIndex) || '',
|
|
145
|
+
netIndex,
|
|
146
|
+
net: netIndex === null ? '' : netNames.get(netIndex) || '',
|
|
147
|
+
polygonIndex
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Builds component designator lookup by native component index.
|
|
153
|
+
* @param {object[]} components Component rows.
|
|
154
|
+
* @returns {Map<number, string>}
|
|
155
|
+
*/
|
|
156
|
+
static #componentNames(components) {
|
|
157
|
+
const names = new Map()
|
|
158
|
+
|
|
159
|
+
for (const component of components || []) {
|
|
160
|
+
const componentIndex = PcbOwnershipGraphBuilder.#optionalInteger(
|
|
161
|
+
component?.componentIndex
|
|
162
|
+
)
|
|
163
|
+
if (componentIndex !== null) {
|
|
164
|
+
names.set(componentIndex, String(component.designator || ''))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return names
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Builds net name lookup by native net index.
|
|
173
|
+
* @param {object[]} nets Net rows.
|
|
174
|
+
* @returns {Map<number, string>}
|
|
175
|
+
*/
|
|
176
|
+
static #netNames(nets) {
|
|
177
|
+
const names = new Map()
|
|
178
|
+
|
|
179
|
+
for (const net of nets || []) {
|
|
180
|
+
const netIndex = PcbOwnershipGraphBuilder.#optionalInteger(
|
|
181
|
+
net?.netIndex
|
|
182
|
+
)
|
|
183
|
+
if (netIndex !== null) {
|
|
184
|
+
names.set(netIndex, String(net.name || ''))
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return names
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Creates empty component groups so consumers can inspect ownerless rows.
|
|
193
|
+
* @param {object[]} components Component rows.
|
|
194
|
+
* @returns {Record<string, object>}
|
|
195
|
+
*/
|
|
196
|
+
static #initialComponentGroups(components) {
|
|
197
|
+
const groups = {}
|
|
198
|
+
|
|
199
|
+
for (const component of components || []) {
|
|
200
|
+
const componentIndex = PcbOwnershipGraphBuilder.#optionalInteger(
|
|
201
|
+
component?.componentIndex
|
|
202
|
+
)
|
|
203
|
+
if (componentIndex !== null) {
|
|
204
|
+
groups[String(componentIndex)] = {
|
|
205
|
+
componentIndex,
|
|
206
|
+
designator: String(component.designator || ''),
|
|
207
|
+
primitiveKeys: []
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return groups
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Adds one primitive key to a numeric owner group.
|
|
217
|
+
* @param {Record<string, object>} groups Group map.
|
|
218
|
+
* @param {number | null} index Owner index.
|
|
219
|
+
* @param {object} fallbackGroup Group to create when missing.
|
|
220
|
+
* @param {string} primitiveKey Primitive key to append.
|
|
221
|
+
*/
|
|
222
|
+
static #addGroupKey(groups, index, fallbackGroup, primitiveKey) {
|
|
223
|
+
if (index === null) {
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const key = String(index)
|
|
228
|
+
if (!groups[key]) {
|
|
229
|
+
groups[key] = fallbackGroup
|
|
230
|
+
}
|
|
231
|
+
if (!groups[key].primitiveKeys.includes(primitiveKey)) {
|
|
232
|
+
groups[key].primitiveKeys.push(primitiveKey)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Parses an optional integer value.
|
|
238
|
+
* @param {unknown} value Candidate value.
|
|
239
|
+
* @returns {number | null}
|
|
240
|
+
*/
|
|
241
|
+
static #optionalInteger(value) {
|
|
242
|
+
const parsed = Number(value)
|
|
243
|
+
return Number.isInteger(parsed) ? parsed : null
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -35,19 +35,18 @@ export class PcbPadPrimitiveParser {
|
|
|
35
35
|
return []
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
const records = PcbPadPrimitiveParser.#readPadRecordSequence(
|
|
39
|
+
normalizedData,
|
|
40
|
+
0,
|
|
41
|
+
count
|
|
42
|
+
)
|
|
39
43
|
const pads = []
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
offset
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
if (!record) {
|
|
48
|
-
return []
|
|
49
|
-
}
|
|
45
|
+
if (!records) {
|
|
46
|
+
return []
|
|
47
|
+
}
|
|
50
48
|
|
|
49
|
+
for (const record of records) {
|
|
51
50
|
const pad = PcbPadPrimitiveParser.#parsePadSubrecords(
|
|
52
51
|
record.subrecords
|
|
53
52
|
)
|
|
@@ -57,25 +56,69 @@ export class PcbPadPrimitiveParser {
|
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
pads.push(pad)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return pads
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Reads all expected pad records without recursive suffix validation.
|
|
66
|
+
* @param {Uint8Array} bytes
|
|
67
|
+
* @param {number} offset
|
|
68
|
+
* @param {number} count
|
|
69
|
+
* @returns {{ subrecords: DataView[], nextOffset: number }[] | null}
|
|
70
|
+
*/
|
|
71
|
+
static #readPadRecordSequence(bytes, offset, count) {
|
|
72
|
+
const firstRecord = PcbPadPrimitiveParser.#readPadRecordAt(
|
|
73
|
+
bytes,
|
|
74
|
+
offset
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if (!firstRecord) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const records = [firstRecord]
|
|
82
|
+
const alternativeScanOffsets = [null]
|
|
83
|
+
let depth = 1
|
|
84
|
+
let scanOffset = firstRecord.nextOffset
|
|
85
|
+
|
|
86
|
+
while (depth < count) {
|
|
87
|
+
const candidate = PcbPadPrimitiveParser.#findNextPadRecordCandidate(
|
|
88
|
+
bytes,
|
|
89
|
+
scanOffset
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if (!candidate) {
|
|
93
|
+
let foundAlternative = false
|
|
94
|
+
|
|
95
|
+
while (depth > 1 && !foundAlternative) {
|
|
96
|
+
depth -= 1
|
|
97
|
+
records.length = depth
|
|
98
|
+
|
|
99
|
+
const alternativeOffset = alternativeScanOffsets[depth]
|
|
100
|
+
alternativeScanOffsets.length = depth
|
|
101
|
+
|
|
102
|
+
if (alternativeOffset !== null) {
|
|
103
|
+
scanOffset = alternativeOffset
|
|
104
|
+
foundAlternative = true
|
|
105
|
+
}
|
|
72
106
|
}
|
|
73
107
|
|
|
74
|
-
|
|
108
|
+
if (!foundAlternative) {
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
continue
|
|
75
113
|
}
|
|
114
|
+
|
|
115
|
+
records[depth] = candidate.record
|
|
116
|
+
alternativeScanOffsets[depth] = candidate.alternativeOffset
|
|
117
|
+
depth += 1
|
|
118
|
+
scanOffset = candidate.record.nextOffset
|
|
76
119
|
}
|
|
77
120
|
|
|
78
|
-
return
|
|
121
|
+
return records
|
|
79
122
|
}
|
|
80
123
|
|
|
81
124
|
/**
|
|
@@ -149,31 +192,28 @@ export class PcbPadPrimitiveParser {
|
|
|
149
192
|
}
|
|
150
193
|
|
|
151
194
|
/**
|
|
152
|
-
* Finds the next pad record
|
|
195
|
+
* Finds the next readable pad record after optional unknown subrecords.
|
|
153
196
|
* @param {Uint8Array} bytes
|
|
154
197
|
* @param {number} offset
|
|
155
|
-
* @
|
|
156
|
-
* @returns {number | null}
|
|
198
|
+
* @returns {{ record: { subrecords: DataView[], nextOffset: number }, alternativeOffset: number | null } | null}
|
|
157
199
|
*/
|
|
158
|
-
static #
|
|
200
|
+
static #findNextPadRecordCandidate(bytes, offset) {
|
|
159
201
|
let cursor = offset
|
|
160
202
|
|
|
161
203
|
while (cursor < bytes.byteLength) {
|
|
162
|
-
|
|
163
|
-
PcbPadPrimitiveParser.#canReadPadRecordSequence(
|
|
164
|
-
bytes,
|
|
165
|
-
cursor,
|
|
166
|
-
remainingCount
|
|
167
|
-
)
|
|
168
|
-
) {
|
|
169
|
-
return cursor
|
|
170
|
-
}
|
|
171
|
-
|
|
204
|
+
const record = PcbPadPrimitiveParser.#readPadRecordAt(bytes, cursor)
|
|
172
205
|
const unknownSubrecord = PcbPadPrimitiveParser.#readSubrecordAt(
|
|
173
206
|
bytes,
|
|
174
207
|
cursor
|
|
175
208
|
)
|
|
176
209
|
|
|
210
|
+
if (record) {
|
|
211
|
+
return {
|
|
212
|
+
record,
|
|
213
|
+
alternativeOffset: unknownSubrecord?.nextOffset ?? null
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
177
217
|
if (!unknownSubrecord) {
|
|
178
218
|
return null
|
|
179
219
|
}
|
|
@@ -184,33 +224,6 @@ export class PcbPadPrimitiveParser {
|
|
|
184
224
|
return null
|
|
185
225
|
}
|
|
186
226
|
|
|
187
|
-
/**
|
|
188
|
-
* Checks whether the remaining pad records can be read from an offset.
|
|
189
|
-
* @param {Uint8Array} bytes
|
|
190
|
-
* @param {number} offset
|
|
191
|
-
* @param {number} remainingCount
|
|
192
|
-
* @returns {boolean}
|
|
193
|
-
*/
|
|
194
|
-
static #canReadPadRecordSequence(bytes, offset, remainingCount) {
|
|
195
|
-
const record = PcbPadPrimitiveParser.#readPadRecordAt(bytes, offset)
|
|
196
|
-
|
|
197
|
-
if (!record) {
|
|
198
|
-
return false
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (remainingCount <= 1) {
|
|
202
|
-
return true
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return (
|
|
206
|
-
PcbPadPrimitiveParser.#findNextPadRecordOffset(
|
|
207
|
-
bytes,
|
|
208
|
-
record.nextOffset,
|
|
209
|
-
remainingCount - 1
|
|
210
|
-
) !== null
|
|
211
|
-
)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
227
|
/**
|
|
215
228
|
* Decodes one pad payload from its subrecords.
|
|
216
229
|
* @param {DataView[]} subrecords
|
|
@@ -48,6 +48,12 @@ export class PcbPadStackParser {
|
|
|
48
48
|
|
|
49
49
|
static #SOLDER_MASK_CACHE_VALID_OFFSET = 104
|
|
50
50
|
|
|
51
|
+
static #POSITIVE_TOLERANCE_OFFSET = 162
|
|
52
|
+
|
|
53
|
+
static #NEGATIVE_TOLERANCE_OFFSET = 166
|
|
54
|
+
|
|
55
|
+
static #HOLE_TOLERANCE_UNSET = 0x7fffffff
|
|
56
|
+
|
|
51
57
|
static #EXTENSION_MIN_BYTE_LENGTH = 596
|
|
52
58
|
|
|
53
59
|
static #INNER_LAYER_COUNT = 29
|
|
@@ -178,6 +184,7 @@ export class PcbPadStackParser {
|
|
|
178
184
|
|
|
179
185
|
PcbPadStackParser.#assignPadCacheFields(result, mainRecord)
|
|
180
186
|
PcbPadStackParser.#assignMaskCacheFields(result, mainRecord)
|
|
187
|
+
PcbPadStackParser.#assignHoleToleranceFields(result, mainRecord)
|
|
181
188
|
|
|
182
189
|
return result
|
|
183
190
|
}
|
|
@@ -292,6 +299,35 @@ export class PcbPadStackParser {
|
|
|
292
299
|
}
|
|
293
300
|
}
|
|
294
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Adds optional hole tolerance fields to an output object.
|
|
304
|
+
* @param {Record<string, unknown>} result
|
|
305
|
+
* @param {DataView} mainRecord
|
|
306
|
+
*/
|
|
307
|
+
static #assignHoleToleranceFields(result, mainRecord) {
|
|
308
|
+
const positiveTolerance = PcbPadStackParser.#readHoleTolerance(
|
|
309
|
+
mainRecord,
|
|
310
|
+
PcbPadStackParser.#POSITIVE_TOLERANCE_OFFSET
|
|
311
|
+
)
|
|
312
|
+
const negativeTolerance = PcbPadStackParser.#readHoleTolerance(
|
|
313
|
+
mainRecord,
|
|
314
|
+
PcbPadStackParser.#NEGATIVE_TOLERANCE_OFFSET
|
|
315
|
+
)
|
|
316
|
+
const holeTolerance = {}
|
|
317
|
+
|
|
318
|
+
if (positiveTolerance !== null) {
|
|
319
|
+
result.positiveTolerance = positiveTolerance
|
|
320
|
+
holeTolerance.positive = positiveTolerance
|
|
321
|
+
}
|
|
322
|
+
if (negativeTolerance !== null) {
|
|
323
|
+
result.negativeTolerance = negativeTolerance
|
|
324
|
+
holeTolerance.negative = negativeTolerance
|
|
325
|
+
}
|
|
326
|
+
if (Object.keys(holeTolerance).length) {
|
|
327
|
+
result.holeTolerance = holeTolerance
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
295
331
|
/**
|
|
296
332
|
* Returns whether one decoded pad cache contains meaningful data.
|
|
297
333
|
* @param {{ planeConnectionStyle: number, thermalReliefConductorWidth: number, thermalReliefConductorCount: number, thermalReliefAirGap: number, powerPlaneReliefExpansion: number, powerPlaneClearance: number, validity: Record<string, number> }} padCache
|
|
@@ -311,6 +347,28 @@ export class PcbPadStackParser {
|
|
|
311
347
|
return values.some((value) => value !== 0)
|
|
312
348
|
}
|
|
313
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Reads one optional hole tolerance from a pad main record.
|
|
352
|
+
* @param {DataView} mainRecord
|
|
353
|
+
* @param {number} offset
|
|
354
|
+
* @returns {number | null}
|
|
355
|
+
*/
|
|
356
|
+
static #readHoleTolerance(mainRecord, offset) {
|
|
357
|
+
if (!mainRecord || offset + 4 > mainRecord.byteLength) {
|
|
358
|
+
return null
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const rawValue = mainRecord.getInt32(offset, true)
|
|
362
|
+
if (
|
|
363
|
+
rawValue === 0 ||
|
|
364
|
+
rawValue === PcbPadStackParser.#HOLE_TOLERANCE_UNSET
|
|
365
|
+
) {
|
|
366
|
+
return null
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return rawValue / 10000
|
|
370
|
+
}
|
|
371
|
+
|
|
314
372
|
/**
|
|
315
373
|
* Adds derived mask-expansion and layer-opening semantics.
|
|
316
374
|
* @param {Record<string, boolean | number>} flags
|