altium-toolkit 1.1.32 → 1.1.35
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/docs/model-format.md +2 -3
- package/package.json +1 -1
- package/src/core/altium/AltiumGeneratedLibraryRecordBuilder.mjs +551 -0
- package/src/core/altium/AltiumLibraryRecordBuilder.mjs +286 -2
- package/src/core/altium/AltiumPcbLibExporter.mjs +9 -2
- package/src/core/altium/AltiumSchLibExporter.mjs +1 -1
- package/src/core/altium/PcbComponentBodyPlacementNormalizer.mjs +120 -15
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +52 -4
- package/src/core/altium/PcbShapeBasedBodyGeometryParser.mjs +85 -11
- package/src/core/altium/SourceComponentBundleNormalizer.mjs +31 -0
- package/src/core/circuit-json/CircuitJsonModelSchema.mjs +1 -1
- package/src/ui/AltiumScene3dAuthoredBodyAnchorAdapter.mjs +30 -2
- package/src/ui/AltiumScene3dExternalPlacementAdapter.mjs +27 -2
- package/src/ui/AltiumScene3dPlacementRotationPolicy.mjs +44 -0
- package/src/ui/AltiumScene3dRepeatedModelOwnerRepair.mjs +171 -6
- package/src/ui/AltiumScene3dShapeStackOwnerAdapter.mjs +782 -0
- package/src/ui/AltiumScene3dTwoRowFootprintDetector.mjs +90 -0
- package/src/ui/PcbScene3dBuilder.mjs +595 -28
- package/src/ui/PcbScene3dCopperRegionDetailBuilder.mjs +280 -0
- package/src/ui/PcbScene3dModelRegistry.mjs +16 -0
- package/src/ui/PcbScene3dPackageDimensionResolver.mjs +107 -0
- package/src/ui/PcbScene3dPackages.mjs +15 -3
- package/src/ui/PcbScene3dPadYawResolver.mjs +200 -0
- package/src/ui/PcbScene3dPlacementSideResolver.mjs +56 -0
- package/src/ui/PcbScene3dStaticBodyPlacementBuilder.mjs +638 -16
- package/src/ui/PcbScene3dStaticBodyRecovery.mjs +891 -0
- package/src/ui/PcbScene3dStaticBodySelectionKeyBuilder.mjs +358 -0
- package/src/ui/PcbScene3dStaticBodySymmetryRecovery.mjs +876 -0
package/docs/model-format.md
CHANGED
|
@@ -80,9 +80,8 @@ The legacy renderer compatibility contract is published as a JSON Schema at
|
|
|
80
80
|
[`docs/schemas/altium_toolkit/normalized_model_a1.schema.json`](schemas/altium_toolkit/normalized_model_a1.schema.json).
|
|
81
81
|
Compatibility fields expose the same id through the top-level `schema` field,
|
|
82
82
|
and consumers can compare it with `NormalizedModelSchema.CURRENT_SCHEMA_ID`.
|
|
83
|
-
The serialized parser return value follows the
|
|
84
|
-
|
|
85
|
-
array convention.
|
|
83
|
+
The serialized parser return value follows the Circuit JSON element-array
|
|
84
|
+
convention.
|
|
86
85
|
|
|
87
86
|
## Parser Utility Reports
|
|
88
87
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds generated schematic and PCB primitive records for compact libraries.
|
|
7
|
+
*/
|
|
8
|
+
export class AltiumGeneratedLibraryRecordBuilder {
|
|
9
|
+
/**
|
|
10
|
+
* Builds a full schematic component record stream.
|
|
11
|
+
* @param {object} bundle Normalized component bundle.
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
static buildSchematicComponentData(bundle) {
|
|
15
|
+
return [
|
|
16
|
+
AltiumGeneratedLibraryRecordBuilder.#pipeRecord({
|
|
17
|
+
RECORD: 'Component',
|
|
18
|
+
Name: bundle.symbol.name,
|
|
19
|
+
SourceId: bundle.id,
|
|
20
|
+
DisplayName: bundle.name,
|
|
21
|
+
PinCount: String(bundle.symbol.pins.length),
|
|
22
|
+
PrimitiveCount: String(bundle.symbol.primitives.length)
|
|
23
|
+
}),
|
|
24
|
+
...bundle.symbol.pins.map((pin) =>
|
|
25
|
+
AltiumGeneratedLibraryRecordBuilder.#schematicPinRecord(pin)
|
|
26
|
+
),
|
|
27
|
+
...bundle.symbol.primitives.map((primitive) =>
|
|
28
|
+
AltiumGeneratedLibraryRecordBuilder.#schematicPrimitiveRecord(
|
|
29
|
+
primitive
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
].join('')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Counts generated footprint primitives.
|
|
37
|
+
* @param {object} footprint Normalized footprint.
|
|
38
|
+
* @returns {number}
|
|
39
|
+
*/
|
|
40
|
+
static footprintPrimitiveCount(footprint) {
|
|
41
|
+
return (
|
|
42
|
+
AltiumGeneratedLibraryRecordBuilder.#array(footprint.pads).length +
|
|
43
|
+
AltiumGeneratedLibraryRecordBuilder.#array(footprint.tracks)
|
|
44
|
+
.length +
|
|
45
|
+
AltiumGeneratedLibraryRecordBuilder.#array(footprint.arcs).length +
|
|
46
|
+
AltiumGeneratedLibraryRecordBuilder.#array(footprint.fills).length +
|
|
47
|
+
AltiumGeneratedLibraryRecordBuilder.#array(footprint.texts).length
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Builds binary PcbLib footprint data after the leading name block.
|
|
53
|
+
* @param {object} footprint Normalized footprint.
|
|
54
|
+
* @returns {Uint8Array[]}
|
|
55
|
+
*/
|
|
56
|
+
static buildFootprintPrimitiveRecords(footprint) {
|
|
57
|
+
return [
|
|
58
|
+
...AltiumGeneratedLibraryRecordBuilder.#array(footprint.pads).map(
|
|
59
|
+
(pad) => AltiumGeneratedLibraryRecordBuilder.#padRecord(pad)
|
|
60
|
+
),
|
|
61
|
+
...AltiumGeneratedLibraryRecordBuilder.#array(footprint.tracks).map(
|
|
62
|
+
(track) =>
|
|
63
|
+
AltiumGeneratedLibraryRecordBuilder.#trackRecord(track)
|
|
64
|
+
),
|
|
65
|
+
...AltiumGeneratedLibraryRecordBuilder.#array(footprint.arcs).map(
|
|
66
|
+
(arc) => AltiumGeneratedLibraryRecordBuilder.#arcRecord(arc)
|
|
67
|
+
),
|
|
68
|
+
...AltiumGeneratedLibraryRecordBuilder.#array(footprint.fills).map(
|
|
69
|
+
(fill) => AltiumGeneratedLibraryRecordBuilder.#fillRecord(fill)
|
|
70
|
+
),
|
|
71
|
+
...AltiumGeneratedLibraryRecordBuilder.#array(footprint.texts).map(
|
|
72
|
+
(text) => AltiumGeneratedLibraryRecordBuilder.#textRecord(text)
|
|
73
|
+
)
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Builds one schematic pin record.
|
|
79
|
+
* @param {object} pin Normalized pin.
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
static #schematicPinRecord(pin) {
|
|
83
|
+
return AltiumGeneratedLibraryRecordBuilder.#pipeRecord({
|
|
84
|
+
RECORD: 'Pin',
|
|
85
|
+
Designator:
|
|
86
|
+
pin.number || pin.pinNumber || pin.designator || pin.name,
|
|
87
|
+
Name: pin.name || pin.pinName || pin.designator,
|
|
88
|
+
ElectricalType:
|
|
89
|
+
AltiumGeneratedLibraryRecordBuilder.#schematicElectricalType(
|
|
90
|
+
pin
|
|
91
|
+
),
|
|
92
|
+
'Location.X': AltiumGeneratedLibraryRecordBuilder.#numberText(
|
|
93
|
+
pin.x,
|
|
94
|
+
0
|
|
95
|
+
),
|
|
96
|
+
'Location.Y': AltiumGeneratedLibraryRecordBuilder.#numberText(
|
|
97
|
+
pin.y,
|
|
98
|
+
0
|
|
99
|
+
),
|
|
100
|
+
PinLength: AltiumGeneratedLibraryRecordBuilder.#numberText(
|
|
101
|
+
pin.length,
|
|
102
|
+
10
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Resolves one schematic pin electrical type label.
|
|
109
|
+
* @param {object} pin Normalized pin.
|
|
110
|
+
* @returns {string}
|
|
111
|
+
*/
|
|
112
|
+
static #schematicElectricalType(pin) {
|
|
113
|
+
if (typeof pin.electrical === 'string') return pin.electrical
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
{
|
|
117
|
+
0: 'input',
|
|
118
|
+
1: 'io',
|
|
119
|
+
2: 'output',
|
|
120
|
+
3: 'open-collector',
|
|
121
|
+
4: 'passive',
|
|
122
|
+
5: 'hi-z',
|
|
123
|
+
6: 'open-emitter',
|
|
124
|
+
7: 'power'
|
|
125
|
+
}[Number(pin.electrical)] || 'passive'
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Builds one schematic primitive record.
|
|
131
|
+
* @param {object} primitive Normalized primitive.
|
|
132
|
+
* @returns {string}
|
|
133
|
+
*/
|
|
134
|
+
static #schematicPrimitiveRecord(primitive) {
|
|
135
|
+
const type =
|
|
136
|
+
AltiumGeneratedLibraryRecordBuilder.#primitiveType(primitive)
|
|
137
|
+
|
|
138
|
+
return AltiumGeneratedLibraryRecordBuilder.#pipeRecord({
|
|
139
|
+
RECORD: type,
|
|
140
|
+
'Location.X': AltiumGeneratedLibraryRecordBuilder.#numberText(
|
|
141
|
+
primitive.x ?? primitive.x1,
|
|
142
|
+
0
|
|
143
|
+
),
|
|
144
|
+
'Location.Y': AltiumGeneratedLibraryRecordBuilder.#numberText(
|
|
145
|
+
primitive.y ?? primitive.y1,
|
|
146
|
+
0
|
|
147
|
+
),
|
|
148
|
+
'Corner.X': AltiumGeneratedLibraryRecordBuilder.#numberText(
|
|
149
|
+
primitive.cornerX ??
|
|
150
|
+
primitive.x2 ??
|
|
151
|
+
Number(primitive.x || 0) + Number(primitive.width || 0),
|
|
152
|
+
0
|
|
153
|
+
),
|
|
154
|
+
'Corner.Y': AltiumGeneratedLibraryRecordBuilder.#numberText(
|
|
155
|
+
primitive.cornerY ??
|
|
156
|
+
primitive.y2 ??
|
|
157
|
+
Number(primitive.y || 0) + Number(primitive.height || 0),
|
|
158
|
+
0
|
|
159
|
+
)
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Resolves a schematic primitive record type.
|
|
165
|
+
* @param {object} primitive Normalized primitive.
|
|
166
|
+
* @returns {string}
|
|
167
|
+
*/
|
|
168
|
+
static #primitiveType(primitive) {
|
|
169
|
+
const type = String(primitive.type || primitive.recordType || '')
|
|
170
|
+
.trim()
|
|
171
|
+
.toLowerCase()
|
|
172
|
+
if (type === 'line' || ('x1' in primitive && 'x2' in primitive)) {
|
|
173
|
+
return 'Line'
|
|
174
|
+
}
|
|
175
|
+
if (type === 'arc') return 'Arc'
|
|
176
|
+
if (type === 'ellipse') return 'Ellipse'
|
|
177
|
+
if (type === 'polygon') return 'Polygon'
|
|
178
|
+
|
|
179
|
+
return 'Rectangle'
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Builds one pad primitive record.
|
|
184
|
+
* @param {object} pad Normalized pad.
|
|
185
|
+
* @returns {Uint8Array}
|
|
186
|
+
*/
|
|
187
|
+
static #padRecord(pad) {
|
|
188
|
+
const main = new Uint8Array(118)
|
|
189
|
+
const view = new DataView(main.buffer)
|
|
190
|
+
const width = AltiumGeneratedLibraryRecordBuilder.#number(
|
|
191
|
+
pad.width ?? pad.sizeTopX ?? pad.sizeMidX ?? pad.sizeBottomX,
|
|
192
|
+
1
|
|
193
|
+
)
|
|
194
|
+
const height = AltiumGeneratedLibraryRecordBuilder.#number(
|
|
195
|
+
pad.height ?? pad.sizeTopY ?? pad.sizeMidY ?? pad.sizeBottomY,
|
|
196
|
+
1
|
|
197
|
+
)
|
|
198
|
+
const layerId = AltiumGeneratedLibraryRecordBuilder.#layerId(pad, 1)
|
|
199
|
+
const shape = AltiumGeneratedLibraryRecordBuilder.#padShape(pad)
|
|
200
|
+
|
|
201
|
+
view.setUint8(0, layerId)
|
|
202
|
+
view.setUint16(1, 8, true)
|
|
203
|
+
AltiumGeneratedLibraryRecordBuilder.#writeOwnerIndexes(view, pad, {
|
|
204
|
+
net: 3,
|
|
205
|
+
polygon: 5,
|
|
206
|
+
component: 7
|
|
207
|
+
})
|
|
208
|
+
view.setUint32(9, 0xffffffff, true)
|
|
209
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 13, pad.x, 0)
|
|
210
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 17, pad.y, 0)
|
|
211
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 21, width, 1)
|
|
212
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 25, height, 1)
|
|
213
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 29, width, 1)
|
|
214
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 33, height, 1)
|
|
215
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 37, width, 1)
|
|
216
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 41, height, 1)
|
|
217
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(
|
|
218
|
+
view,
|
|
219
|
+
45,
|
|
220
|
+
pad.holeDiameter,
|
|
221
|
+
0
|
|
222
|
+
)
|
|
223
|
+
view.setUint8(49, shape)
|
|
224
|
+
view.setUint8(50, shape)
|
|
225
|
+
view.setUint8(51, shape)
|
|
226
|
+
view.setFloat64(
|
|
227
|
+
52,
|
|
228
|
+
AltiumGeneratedLibraryRecordBuilder.#number(pad.rotation, 0),
|
|
229
|
+
true
|
|
230
|
+
)
|
|
231
|
+
view.setUint8(60, pad.isPlated === false ? 0 : 1)
|
|
232
|
+
view.setUint8(62, 0)
|
|
233
|
+
view.setUint8(101, 1)
|
|
234
|
+
view.setUint8(102, 1)
|
|
235
|
+
|
|
236
|
+
return AltiumGeneratedLibraryRecordBuilder.#concatBytes([
|
|
237
|
+
new Uint8Array([2]),
|
|
238
|
+
...[0, 1, 2, 3].map(() =>
|
|
239
|
+
AltiumGeneratedLibraryRecordBuilder.#lengthPrefixed(
|
|
240
|
+
new Uint8Array()
|
|
241
|
+
)
|
|
242
|
+
),
|
|
243
|
+
AltiumGeneratedLibraryRecordBuilder.#lengthPrefixed(main),
|
|
244
|
+
AltiumGeneratedLibraryRecordBuilder.#lengthPrefixed(
|
|
245
|
+
new Uint8Array()
|
|
246
|
+
)
|
|
247
|
+
])
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Builds one track primitive record.
|
|
252
|
+
* @param {object} track Normalized track.
|
|
253
|
+
* @returns {Uint8Array}
|
|
254
|
+
*/
|
|
255
|
+
static #trackRecord(track) {
|
|
256
|
+
const payload = new Uint8Array(49)
|
|
257
|
+
const view = new DataView(payload.buffer)
|
|
258
|
+
|
|
259
|
+
view.setUint8(0, AltiumGeneratedLibraryRecordBuilder.#layerId(track, 1))
|
|
260
|
+
AltiumGeneratedLibraryRecordBuilder.#writeOwnerIndexes(view, track, {
|
|
261
|
+
net: 3,
|
|
262
|
+
polygon: 5,
|
|
263
|
+
component: 7
|
|
264
|
+
})
|
|
265
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 13, track.x1, 0)
|
|
266
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 17, track.y1, 0)
|
|
267
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 21, track.x2, 0)
|
|
268
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 25, track.y2, 0)
|
|
269
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 29, track.width, 1)
|
|
270
|
+
|
|
271
|
+
return AltiumGeneratedLibraryRecordBuilder.#objectRecord(4, payload)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Builds one arc primitive record.
|
|
276
|
+
* @param {object} arc Normalized arc.
|
|
277
|
+
* @returns {Uint8Array}
|
|
278
|
+
*/
|
|
279
|
+
static #arcRecord(arc) {
|
|
280
|
+
const payload = new Uint8Array(45)
|
|
281
|
+
const view = new DataView(payload.buffer)
|
|
282
|
+
|
|
283
|
+
view.setUint8(0, AltiumGeneratedLibraryRecordBuilder.#layerId(arc, 1))
|
|
284
|
+
AltiumGeneratedLibraryRecordBuilder.#writeOwnerIndexes(view, arc, {
|
|
285
|
+
net: 3,
|
|
286
|
+
polygon: 5,
|
|
287
|
+
component: 7
|
|
288
|
+
})
|
|
289
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 13, arc.x, 0)
|
|
290
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 17, arc.y, 0)
|
|
291
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 21, arc.radius, 1)
|
|
292
|
+
view.setFloat64(
|
|
293
|
+
25,
|
|
294
|
+
AltiumGeneratedLibraryRecordBuilder.#number(arc.startAngle, 0),
|
|
295
|
+
true
|
|
296
|
+
)
|
|
297
|
+
view.setFloat64(
|
|
298
|
+
33,
|
|
299
|
+
AltiumGeneratedLibraryRecordBuilder.#number(arc.endAngle, 360),
|
|
300
|
+
true
|
|
301
|
+
)
|
|
302
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 41, arc.width, 1)
|
|
303
|
+
|
|
304
|
+
return AltiumGeneratedLibraryRecordBuilder.#objectRecord(1, payload)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Builds one fill primitive record.
|
|
309
|
+
* @param {object} fill Normalized fill.
|
|
310
|
+
* @returns {Uint8Array}
|
|
311
|
+
*/
|
|
312
|
+
static #fillRecord(fill) {
|
|
313
|
+
const bytes = new Uint8Array(55)
|
|
314
|
+
const view = new DataView(bytes.buffer)
|
|
315
|
+
const layerId = AltiumGeneratedLibraryRecordBuilder.#layerId(fill, 1)
|
|
316
|
+
|
|
317
|
+
bytes[0] = 6
|
|
318
|
+
view.setUint32(1, 50, true)
|
|
319
|
+
view.setUint8(5, layerId)
|
|
320
|
+
AltiumGeneratedLibraryRecordBuilder.#writeOwnerIndexes(view, fill, {
|
|
321
|
+
net: 8,
|
|
322
|
+
polygon: 10,
|
|
323
|
+
component: 12
|
|
324
|
+
})
|
|
325
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 18, fill.x1, 0)
|
|
326
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 22, fill.y1, 0)
|
|
327
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 26, fill.x2, 0)
|
|
328
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 30, fill.y2, 0)
|
|
329
|
+
view.setUint16(46, layerId, true)
|
|
330
|
+
|
|
331
|
+
return bytes
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Builds one PCB text primitive record.
|
|
336
|
+
* @param {object} text Normalized text.
|
|
337
|
+
* @returns {Uint8Array}
|
|
338
|
+
*/
|
|
339
|
+
static #textRecord(text) {
|
|
340
|
+
const payload = new Uint8Array(64)
|
|
341
|
+
const view = new DataView(payload.buffer)
|
|
342
|
+
const textBytes = new TextEncoder().encode(String(text.text || ''))
|
|
343
|
+
|
|
344
|
+
view.setUint8(0, AltiumGeneratedLibraryRecordBuilder.#layerId(text, 21))
|
|
345
|
+
view.setInt16(
|
|
346
|
+
7,
|
|
347
|
+
AltiumGeneratedLibraryRecordBuilder.#nullableIndex(
|
|
348
|
+
text.ownerIndex ?? text.componentIndex
|
|
349
|
+
),
|
|
350
|
+
true
|
|
351
|
+
)
|
|
352
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 13, text.x, 0)
|
|
353
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 17, text.y, 0)
|
|
354
|
+
AltiumGeneratedLibraryRecordBuilder.#writeMil(view, 21, text.height, 10)
|
|
355
|
+
view.setUint32(25, 0, true)
|
|
356
|
+
view.setUint32(41, 0, true)
|
|
357
|
+
|
|
358
|
+
return AltiumGeneratedLibraryRecordBuilder.#concatBytes([
|
|
359
|
+
AltiumGeneratedLibraryRecordBuilder.#objectRecord(5, payload),
|
|
360
|
+
AltiumGeneratedLibraryRecordBuilder.#lengthPrefixed(textBytes)
|
|
361
|
+
])
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Creates one object-id and payload-length prefixed primitive record.
|
|
366
|
+
* @param {number} objectId Primitive object id.
|
|
367
|
+
* @param {Uint8Array} payload Payload bytes.
|
|
368
|
+
* @returns {Uint8Array}
|
|
369
|
+
*/
|
|
370
|
+
static #objectRecord(objectId, payload) {
|
|
371
|
+
const bytes = new Uint8Array(5 + payload.byteLength)
|
|
372
|
+
const view = new DataView(bytes.buffer)
|
|
373
|
+
|
|
374
|
+
bytes[0] = Number(objectId)
|
|
375
|
+
view.setUint32(1, payload.byteLength, true)
|
|
376
|
+
bytes.set(payload, 5)
|
|
377
|
+
|
|
378
|
+
return bytes
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Creates one four-byte length-prefixed byte block.
|
|
383
|
+
* @param {Uint8Array} payload Payload bytes.
|
|
384
|
+
* @returns {Uint8Array}
|
|
385
|
+
*/
|
|
386
|
+
static #lengthPrefixed(payload) {
|
|
387
|
+
const bytes = new Uint8Array(4 + payload.byteLength)
|
|
388
|
+
const view = new DataView(bytes.buffer)
|
|
389
|
+
|
|
390
|
+
view.setUint32(0, payload.byteLength, true)
|
|
391
|
+
bytes.set(payload, 4)
|
|
392
|
+
|
|
393
|
+
return bytes
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Writes owner indexes at the supplied offsets.
|
|
398
|
+
* @param {DataView} view Target view.
|
|
399
|
+
* @param {object} primitive Source primitive.
|
|
400
|
+
* @param {{ component: number, net: number, polygon: number }} offsets Field offsets.
|
|
401
|
+
* @returns {void}
|
|
402
|
+
*/
|
|
403
|
+
static #writeOwnerIndexes(view, primitive, offsets) {
|
|
404
|
+
view.setUint16(
|
|
405
|
+
offsets.component,
|
|
406
|
+
AltiumGeneratedLibraryRecordBuilder.#nullableIndex(
|
|
407
|
+
primitive.componentIndex ?? primitive.ownerIndex
|
|
408
|
+
),
|
|
409
|
+
true
|
|
410
|
+
)
|
|
411
|
+
view.setUint16(
|
|
412
|
+
offsets.net,
|
|
413
|
+
AltiumGeneratedLibraryRecordBuilder.#nullableIndex(
|
|
414
|
+
primitive.netIndex
|
|
415
|
+
),
|
|
416
|
+
true
|
|
417
|
+
)
|
|
418
|
+
view.setUint16(
|
|
419
|
+
offsets.polygon,
|
|
420
|
+
AltiumGeneratedLibraryRecordBuilder.#nullableIndex(
|
|
421
|
+
primitive.polygonIndex
|
|
422
|
+
),
|
|
423
|
+
true
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Converts one optional index into Altium's nullable index encoding.
|
|
429
|
+
* @param {unknown} value Source value.
|
|
430
|
+
* @returns {number}
|
|
431
|
+
*/
|
|
432
|
+
static #nullableIndex(value) {
|
|
433
|
+
const number = Number(value)
|
|
434
|
+
return Number.isInteger(number) && number >= 0 ? number : 0xffff
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Writes one mil value as Altium fixed-point integer.
|
|
439
|
+
* @param {DataView} view Target view.
|
|
440
|
+
* @param {number} offset Byte offset.
|
|
441
|
+
* @param {unknown} value Source value.
|
|
442
|
+
* @param {number} fallback Fallback value.
|
|
443
|
+
* @returns {void}
|
|
444
|
+
*/
|
|
445
|
+
static #writeMil(view, offset, value, fallback) {
|
|
446
|
+
view.setInt32(
|
|
447
|
+
offset,
|
|
448
|
+
Math.round(
|
|
449
|
+
AltiumGeneratedLibraryRecordBuilder.#number(value, fallback) *
|
|
450
|
+
10000
|
|
451
|
+
),
|
|
452
|
+
true
|
|
453
|
+
)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Resolves one layer id.
|
|
458
|
+
* @param {object} primitive Source primitive.
|
|
459
|
+
* @param {number} fallback Fallback layer id.
|
|
460
|
+
* @returns {number}
|
|
461
|
+
*/
|
|
462
|
+
static #layerId(primitive, fallback) {
|
|
463
|
+
return AltiumGeneratedLibraryRecordBuilder.#number(
|
|
464
|
+
primitive.layerId ?? primitive.layerCode ?? primitive.legacyLayerId,
|
|
465
|
+
fallback
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Resolves one pad shape id.
|
|
471
|
+
* @param {object} pad Source pad.
|
|
472
|
+
* @returns {number}
|
|
473
|
+
*/
|
|
474
|
+
static #padShape(pad) {
|
|
475
|
+
const shape = String(
|
|
476
|
+
pad.shapeTopName || pad.shape || pad.padShape || ''
|
|
477
|
+
).toLowerCase()
|
|
478
|
+
if (shape.includes('round')) return 1
|
|
479
|
+
if (shape.includes('oct')) return 3
|
|
480
|
+
|
|
481
|
+
return Number(pad.shapeTop || pad.shapeMid || pad.shapeBottom || 2)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Returns a finite number or fallback.
|
|
486
|
+
* @param {unknown} value Source value.
|
|
487
|
+
* @param {number} fallback Fallback value.
|
|
488
|
+
* @returns {number}
|
|
489
|
+
*/
|
|
490
|
+
static #number(value, fallback) {
|
|
491
|
+
const number = Number(value)
|
|
492
|
+
return Number.isFinite(number) ? number : fallback
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Returns one numeric field as a compact string.
|
|
497
|
+
* @param {unknown} value Source value.
|
|
498
|
+
* @param {number} fallback Fallback value.
|
|
499
|
+
* @returns {string}
|
|
500
|
+
*/
|
|
501
|
+
static #numberText(value, fallback) {
|
|
502
|
+
return String(
|
|
503
|
+
AltiumGeneratedLibraryRecordBuilder.#number(value, fallback)
|
|
504
|
+
)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Builds one pipe-delimited record.
|
|
509
|
+
* @param {Record<string, string>} fields Record fields.
|
|
510
|
+
* @returns {string}
|
|
511
|
+
*/
|
|
512
|
+
static #pipeRecord(fields) {
|
|
513
|
+
return (
|
|
514
|
+
'|' +
|
|
515
|
+
Object.entries(fields)
|
|
516
|
+
.filter(([, value]) => String(value ?? '') !== '')
|
|
517
|
+
.map(([key, value]) => key + '=' + String(value))
|
|
518
|
+
.join('|')
|
|
519
|
+
)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Concatenates byte chunks.
|
|
524
|
+
* @param {Uint8Array[]} chunks Byte chunks.
|
|
525
|
+
* @returns {Uint8Array}
|
|
526
|
+
*/
|
|
527
|
+
static #concatBytes(chunks) {
|
|
528
|
+
const byteLength = chunks.reduce(
|
|
529
|
+
(sum, chunk) => sum + chunk.byteLength,
|
|
530
|
+
0
|
|
531
|
+
)
|
|
532
|
+
const bytes = new Uint8Array(byteLength)
|
|
533
|
+
let offset = 0
|
|
534
|
+
|
|
535
|
+
for (const chunk of chunks) {
|
|
536
|
+
bytes.set(chunk, offset)
|
|
537
|
+
offset += chunk.byteLength
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return bytes
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Normalizes an unknown value to an array.
|
|
545
|
+
* @param {unknown} value Source value.
|
|
546
|
+
* @returns {Array}
|
|
547
|
+
*/
|
|
548
|
+
static #array(value) {
|
|
549
|
+
return Array.isArray(value) ? value : []
|
|
550
|
+
}
|
|
551
|
+
}
|