altium-toolkit 1.0.7 → 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/renderers.mjs +3 -0
- package/src/styles/altium-renderers.css +25 -0
- package/src/ui/PcbBarcodeTextRenderer.mjs +436 -0
- package/src/ui/PcbInteractionGeometry.mjs +350 -0
- package/src/ui/PcbInteractionIndex.mjs +593 -0
- package/src/ui/PcbInteractionItemRegistry.mjs +66 -0
- package/src/ui/PcbInteractionLayerModel.mjs +99 -0
- package/src/ui/PcbScene3dBoardOutlineRefiner.mjs +74 -9
- package/src/ui/PcbScene3dBuilder.mjs +169 -7
- 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,518 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import { ParserUtils } from './ParserUtils.mjs'
|
|
6
|
+
|
|
7
|
+
const { getDisplayText, parseBoolean, parseNumericField } = ParserUtils
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parses schematic implementation/model-link records into a read-only model.
|
|
11
|
+
*/
|
|
12
|
+
export class SchematicImplementationParser {
|
|
13
|
+
static SCHEMA_ID = 'altium-toolkit.schematic.implementations.a1'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parses implementation lists, model links, map definers, and parameters.
|
|
17
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }[]} records Schematic records.
|
|
18
|
+
* @returns {object | null}
|
|
19
|
+
*/
|
|
20
|
+
static parse(records) {
|
|
21
|
+
const components = SchematicImplementationParser.#componentRows(records)
|
|
22
|
+
const listsByIndex =
|
|
23
|
+
SchematicImplementationParser.#implementationLists(records)
|
|
24
|
+
const implementations = (records || [])
|
|
25
|
+
.filter(
|
|
26
|
+
(record) =>
|
|
27
|
+
SchematicImplementationParser.#field(
|
|
28
|
+
record.fields,
|
|
29
|
+
'RECORD'
|
|
30
|
+
) === '45'
|
|
31
|
+
)
|
|
32
|
+
.map((record) =>
|
|
33
|
+
SchematicImplementationParser.#implementation(
|
|
34
|
+
record,
|
|
35
|
+
records,
|
|
36
|
+
components,
|
|
37
|
+
listsByIndex
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
|
|
42
|
+
if (!implementations.length) {
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
schema: SchematicImplementationParser.SCHEMA_ID,
|
|
48
|
+
components: SchematicImplementationParser.#componentLinks(
|
|
49
|
+
components,
|
|
50
|
+
implementations
|
|
51
|
+
),
|
|
52
|
+
implementations
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Builds normalized schematic component rows for implementation linking.
|
|
58
|
+
* @param {object[]} records Schematic records.
|
|
59
|
+
* @returns {object[]}
|
|
60
|
+
*/
|
|
61
|
+
static #componentRows(records) {
|
|
62
|
+
return (records || [])
|
|
63
|
+
.filter(
|
|
64
|
+
(record) =>
|
|
65
|
+
SchematicImplementationParser.#field(
|
|
66
|
+
record.fields,
|
|
67
|
+
'RECORD'
|
|
68
|
+
) === '1'
|
|
69
|
+
)
|
|
70
|
+
.map((record) => {
|
|
71
|
+
const indexInSheet = parseNumericField(
|
|
72
|
+
record.fields,
|
|
73
|
+
'IndexInSheet'
|
|
74
|
+
)
|
|
75
|
+
return {
|
|
76
|
+
indexInSheet,
|
|
77
|
+
componentKey:
|
|
78
|
+
'schematic-component-' +
|
|
79
|
+
String(indexInSheet ?? record.recordIndex ?? 0),
|
|
80
|
+
recordKey: SchematicImplementationParser.#recordKey(record),
|
|
81
|
+
uniqueId: SchematicImplementationParser.#field(
|
|
82
|
+
record.fields,
|
|
83
|
+
'UniqueID'
|
|
84
|
+
),
|
|
85
|
+
libReference:
|
|
86
|
+
SchematicImplementationParser.#field(
|
|
87
|
+
record.fields,
|
|
88
|
+
'LibReference'
|
|
89
|
+
) ||
|
|
90
|
+
SchematicImplementationParser.#field(
|
|
91
|
+
record.fields,
|
|
92
|
+
'DesignItemId'
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Builds implementation-list rows keyed by native sheet indexes.
|
|
100
|
+
* @param {object[]} records Schematic records.
|
|
101
|
+
* @returns {Map<string, object>}
|
|
102
|
+
*/
|
|
103
|
+
static #implementationLists(records) {
|
|
104
|
+
const lists = new Map()
|
|
105
|
+
|
|
106
|
+
for (const record of records || []) {
|
|
107
|
+
if (
|
|
108
|
+
SchematicImplementationParser.#field(
|
|
109
|
+
record.fields,
|
|
110
|
+
'RECORD'
|
|
111
|
+
) !== '44'
|
|
112
|
+
) {
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const indexInSheet = parseNumericField(
|
|
117
|
+
record.fields,
|
|
118
|
+
'IndexInSheet'
|
|
119
|
+
)
|
|
120
|
+
if (indexInSheet === null) {
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
lists.set(String(indexInSheet), {
|
|
125
|
+
key: 'schematic-implementation-list-' + indexInSheet,
|
|
126
|
+
recordKey: SchematicImplementationParser.#recordKey(record),
|
|
127
|
+
ownerIndex: SchematicImplementationParser.#field(
|
|
128
|
+
record.fields,
|
|
129
|
+
'OwnerIndex'
|
|
130
|
+
)
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return lists
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Parses one implementation record.
|
|
139
|
+
* @param {object} record Implementation record.
|
|
140
|
+
* @param {object[]} records All schematic records.
|
|
141
|
+
* @param {object[]} components Component rows.
|
|
142
|
+
* @param {Map<string, object>} listsByIndex Implementation lists.
|
|
143
|
+
* @returns {object | null}
|
|
144
|
+
*/
|
|
145
|
+
static #implementation(record, records, components, listsByIndex) {
|
|
146
|
+
const indexInSheet =
|
|
147
|
+
parseNumericField(record.fields, 'IndexInSheet') ??
|
|
148
|
+
record.recordIndex ??
|
|
149
|
+
0
|
|
150
|
+
const ownerIndex = SchematicImplementationParser.#field(
|
|
151
|
+
record.fields,
|
|
152
|
+
'OwnerIndex'
|
|
153
|
+
)
|
|
154
|
+
const ownerList = listsByIndex.get(ownerIndex) || null
|
|
155
|
+
const ownerComponent = SchematicImplementationParser.#componentForOwner(
|
|
156
|
+
ownerIndex,
|
|
157
|
+
ownerList,
|
|
158
|
+
components
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return SchematicImplementationParser.#stripEmpty({
|
|
162
|
+
key: 'schematic-implementation-' + String(indexInSheet),
|
|
163
|
+
recordKey: SchematicImplementationParser.#recordKey(record),
|
|
164
|
+
ownerComponentKey: ownerComponent?.componentKey || '',
|
|
165
|
+
ownerListKey: ownerList?.key || '',
|
|
166
|
+
modelName: SchematicImplementationParser.#field(
|
|
167
|
+
record.fields,
|
|
168
|
+
'ModelName'
|
|
169
|
+
),
|
|
170
|
+
modelType: SchematicImplementationParser.#normalizeToken(
|
|
171
|
+
SchematicImplementationParser.#field(record.fields, 'ModelType')
|
|
172
|
+
),
|
|
173
|
+
description: SchematicImplementationParser.#field(
|
|
174
|
+
record.fields,
|
|
175
|
+
'Description'
|
|
176
|
+
),
|
|
177
|
+
isCurrent: parseBoolean(
|
|
178
|
+
SchematicImplementationParser.#field(record.fields, 'IsCurrent')
|
|
179
|
+
),
|
|
180
|
+
targetLibraries: SchematicImplementationParser.#targetLibraries(
|
|
181
|
+
record.fields
|
|
182
|
+
),
|
|
183
|
+
searchPaths: SchematicImplementationParser.#searchPaths(
|
|
184
|
+
record.fields
|
|
185
|
+
),
|
|
186
|
+
mapDefiners: SchematicImplementationParser.#mapDefiners(
|
|
187
|
+
records,
|
|
188
|
+
indexInSheet
|
|
189
|
+
),
|
|
190
|
+
parameters: SchematicImplementationParser.#parameters(
|
|
191
|
+
records,
|
|
192
|
+
indexInSheet
|
|
193
|
+
)
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Resolves the owning component for an implementation record.
|
|
199
|
+
* @param {string} ownerIndex Implementation owner index.
|
|
200
|
+
* @param {object | null} ownerList Implementation-list row.
|
|
201
|
+
* @param {object[]} components Component rows.
|
|
202
|
+
* @returns {object | null}
|
|
203
|
+
*/
|
|
204
|
+
static #componentForOwner(ownerIndex, ownerList, components) {
|
|
205
|
+
const direct = (components || []).find(
|
|
206
|
+
(component) => String(component.indexInSheet) === ownerIndex
|
|
207
|
+
)
|
|
208
|
+
if (direct) {
|
|
209
|
+
return direct
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
(components || []).find(
|
|
214
|
+
(component) =>
|
|
215
|
+
ownerList &&
|
|
216
|
+
String(component.indexInSheet) === ownerList.ownerIndex
|
|
217
|
+
) || null
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Builds component-level implementation-key rows.
|
|
223
|
+
* @param {object[]} components Component rows.
|
|
224
|
+
* @param {object[]} implementations Implementation rows.
|
|
225
|
+
* @returns {object[]}
|
|
226
|
+
*/
|
|
227
|
+
static #componentLinks(components, implementations) {
|
|
228
|
+
return (components || [])
|
|
229
|
+
.map((component) =>
|
|
230
|
+
SchematicImplementationParser.#stripEmpty({
|
|
231
|
+
componentKey: component.componentKey,
|
|
232
|
+
recordKey: component.recordKey,
|
|
233
|
+
uniqueId: component.uniqueId,
|
|
234
|
+
libReference: component.libReference,
|
|
235
|
+
implementationKeys: implementations
|
|
236
|
+
.filter(
|
|
237
|
+
(implementation) =>
|
|
238
|
+
implementation.ownerComponentKey ===
|
|
239
|
+
component.componentKey
|
|
240
|
+
)
|
|
241
|
+
.map((implementation) => implementation.key)
|
|
242
|
+
})
|
|
243
|
+
)
|
|
244
|
+
.filter((component) => component.implementationKeys?.length)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Parses indexed target-library fields from one implementation.
|
|
249
|
+
* @param {Record<string, string | string[]>} fields Record fields.
|
|
250
|
+
* @returns {object[]}
|
|
251
|
+
*/
|
|
252
|
+
static #targetLibraries(fields) {
|
|
253
|
+
const count =
|
|
254
|
+
parseNumericField(fields, 'DatafileCount') ??
|
|
255
|
+
SchematicImplementationParser.#countIndexedFields(
|
|
256
|
+
fields,
|
|
257
|
+
'ModelDatafileEntity'
|
|
258
|
+
)
|
|
259
|
+
const libraries = []
|
|
260
|
+
|
|
261
|
+
for (let index = 0; index < count; index += 1) {
|
|
262
|
+
const entity = SchematicImplementationParser.#field(
|
|
263
|
+
fields,
|
|
264
|
+
'ModelDatafileEntity' + index
|
|
265
|
+
)
|
|
266
|
+
const kind = SchematicImplementationParser.#normalizeToken(
|
|
267
|
+
SchematicImplementationParser.#field(
|
|
268
|
+
fields,
|
|
269
|
+
'ModelDatafileKind' + index
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if (!entity && !kind) {
|
|
274
|
+
continue
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
libraries.push(
|
|
278
|
+
SchematicImplementationParser.#stripEmpty({
|
|
279
|
+
index,
|
|
280
|
+
entity,
|
|
281
|
+
kind,
|
|
282
|
+
fileName: SchematicImplementationParser.#libraryFileName(
|
|
283
|
+
entity,
|
|
284
|
+
kind
|
|
285
|
+
)
|
|
286
|
+
})
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return libraries
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Parses indexed search-path fields.
|
|
295
|
+
* @param {Record<string, string | string[]>} fields Record fields.
|
|
296
|
+
* @returns {string[]}
|
|
297
|
+
*/
|
|
298
|
+
static #searchPaths(fields) {
|
|
299
|
+
const count =
|
|
300
|
+
parseNumericField(fields, 'SearchPathCount') ??
|
|
301
|
+
SchematicImplementationParser.#countIndexedFields(
|
|
302
|
+
fields,
|
|
303
|
+
'SearchPath'
|
|
304
|
+
)
|
|
305
|
+
const paths = []
|
|
306
|
+
|
|
307
|
+
for (let index = 0; index < count; index += 1) {
|
|
308
|
+
const value = SchematicImplementationParser.#field(
|
|
309
|
+
fields,
|
|
310
|
+
'SearchPath' + index
|
|
311
|
+
)
|
|
312
|
+
if (value) {
|
|
313
|
+
paths.push(value)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
for (const key of ['SearchPath', 'LibraryPath', 'Path']) {
|
|
318
|
+
const value = SchematicImplementationParser.#field(fields, key)
|
|
319
|
+
if (value && !paths.includes(value)) {
|
|
320
|
+
paths.push(value)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return paths
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Parses map-definer child records for one implementation.
|
|
329
|
+
* @param {object[]} records All schematic records.
|
|
330
|
+
* @param {number} implementationIndex Implementation index.
|
|
331
|
+
* @returns {object[]}
|
|
332
|
+
*/
|
|
333
|
+
static #mapDefiners(records, implementationIndex) {
|
|
334
|
+
return (records || [])
|
|
335
|
+
.filter(
|
|
336
|
+
(record) =>
|
|
337
|
+
SchematicImplementationParser.#field(
|
|
338
|
+
record.fields,
|
|
339
|
+
'RECORD'
|
|
340
|
+
) === '47' &&
|
|
341
|
+
SchematicImplementationParser.#field(
|
|
342
|
+
record.fields,
|
|
343
|
+
'OwnerIndex'
|
|
344
|
+
) === String(implementationIndex)
|
|
345
|
+
)
|
|
346
|
+
.map((record) =>
|
|
347
|
+
SchematicImplementationParser.#stripEmpty({
|
|
348
|
+
recordKey: SchematicImplementationParser.#recordKey(record),
|
|
349
|
+
designatorInterface: SchematicImplementationParser.#field(
|
|
350
|
+
record.fields,
|
|
351
|
+
'DesIntf'
|
|
352
|
+
),
|
|
353
|
+
implementationDesignators:
|
|
354
|
+
SchematicImplementationParser.#implementationDesignators(
|
|
355
|
+
record.fields
|
|
356
|
+
)
|
|
357
|
+
})
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Parses implementation parameter child records.
|
|
363
|
+
* @param {object[]} records All schematic records.
|
|
364
|
+
* @param {number} implementationIndex Implementation index.
|
|
365
|
+
* @returns {object[]}
|
|
366
|
+
*/
|
|
367
|
+
static #parameters(records, implementationIndex) {
|
|
368
|
+
return (records || [])
|
|
369
|
+
.filter((record) => {
|
|
370
|
+
const recordType = SchematicImplementationParser.#field(
|
|
371
|
+
record.fields,
|
|
372
|
+
'RECORD'
|
|
373
|
+
)
|
|
374
|
+
return (
|
|
375
|
+
(recordType === '48' || recordType === '41') &&
|
|
376
|
+
SchematicImplementationParser.#field(
|
|
377
|
+
record.fields,
|
|
378
|
+
'OwnerIndex'
|
|
379
|
+
) === String(implementationIndex)
|
|
380
|
+
)
|
|
381
|
+
})
|
|
382
|
+
.map((record) =>
|
|
383
|
+
SchematicImplementationParser.#stripEmpty({
|
|
384
|
+
recordKey: SchematicImplementationParser.#recordKey(record),
|
|
385
|
+
name: SchematicImplementationParser.#field(
|
|
386
|
+
record.fields,
|
|
387
|
+
'Name'
|
|
388
|
+
),
|
|
389
|
+
value:
|
|
390
|
+
getDisplayText(record.fields) ||
|
|
391
|
+
SchematicImplementationParser.#field(
|
|
392
|
+
record.fields,
|
|
393
|
+
'Value'
|
|
394
|
+
)
|
|
395
|
+
})
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Parses indexed implementation designator fields from a map definer.
|
|
401
|
+
* @param {Record<string, string | string[]>} fields Record fields.
|
|
402
|
+
* @returns {string[]}
|
|
403
|
+
*/
|
|
404
|
+
static #implementationDesignators(fields) {
|
|
405
|
+
const count =
|
|
406
|
+
parseNumericField(fields, 'DesImpCount') ??
|
|
407
|
+
SchematicImplementationParser.#countIndexedFields(fields, 'DesImp')
|
|
408
|
+
const designators = []
|
|
409
|
+
|
|
410
|
+
for (let index = 0; index < count; index += 1) {
|
|
411
|
+
const value = SchematicImplementationParser.#field(
|
|
412
|
+
fields,
|
|
413
|
+
'DesImp' + index
|
|
414
|
+
)
|
|
415
|
+
if (value) {
|
|
416
|
+
designators.push(value)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return designators
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Counts fields with an indexed prefix.
|
|
425
|
+
* @param {Record<string, string | string[]>} fields Record fields.
|
|
426
|
+
* @param {string} prefix Prefix before numeric index.
|
|
427
|
+
* @returns {number}
|
|
428
|
+
*/
|
|
429
|
+
static #countIndexedFields(fields, prefix) {
|
|
430
|
+
const normalizedPrefix = prefix.toLowerCase()
|
|
431
|
+
|
|
432
|
+
return Object.keys(fields || {}).filter((key) =>
|
|
433
|
+
key.toLowerCase().startsWith(normalizedPrefix)
|
|
434
|
+
).length
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Builds an inferred file name from library entity and kind.
|
|
439
|
+
* @param {string} entity Library entity.
|
|
440
|
+
* @param {string} kind Library kind.
|
|
441
|
+
* @returns {string}
|
|
442
|
+
*/
|
|
443
|
+
static #libraryFileName(entity, kind) {
|
|
444
|
+
if (!entity) {
|
|
445
|
+
return ''
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (/\.[^.]+$/.test(entity)) {
|
|
449
|
+
return entity
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const extension =
|
|
453
|
+
{
|
|
454
|
+
pcblib: 'PcbLib',
|
|
455
|
+
schlib: 'SchLib',
|
|
456
|
+
intlib: 'IntLib',
|
|
457
|
+
sim: 'SimModel'
|
|
458
|
+
}[kind] || kind
|
|
459
|
+
|
|
460
|
+
return extension ? entity + '.' + extension : entity
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Returns a lower-case token.
|
|
465
|
+
* @param {string} value Raw token.
|
|
466
|
+
* @returns {string}
|
|
467
|
+
*/
|
|
468
|
+
static #normalizeToken(value) {
|
|
469
|
+
return String(value || '')
|
|
470
|
+
.trim()
|
|
471
|
+
.toLowerCase()
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Builds a stable schematic record key.
|
|
476
|
+
* @param {object} record Schematic record.
|
|
477
|
+
* @returns {string}
|
|
478
|
+
*/
|
|
479
|
+
static #recordKey(record) {
|
|
480
|
+
return 'schematic-record-' + String(record?.recordIndex ?? 0)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Reads one field case-insensitively.
|
|
485
|
+
* @param {Record<string, string | string[]> | undefined} fields Record fields.
|
|
486
|
+
* @param {string} key Requested key.
|
|
487
|
+
* @returns {string}
|
|
488
|
+
*/
|
|
489
|
+
static #field(fields, key) {
|
|
490
|
+
const direct = ParserUtils.getField(fields, key)
|
|
491
|
+
if (direct) {
|
|
492
|
+
return direct
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const normalizedKey = String(key || '').toLowerCase()
|
|
496
|
+
const matchedKey = Object.keys(fields || {}).find(
|
|
497
|
+
(fieldKey) => fieldKey.toLowerCase() === normalizedKey
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
return matchedKey ? ParserUtils.getField(fields, matchedKey) : ''
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Drops empty optional fields while keeping explicit booleans and arrays.
|
|
505
|
+
* @param {Record<string, unknown>} value Source object.
|
|
506
|
+
* @returns {Record<string, unknown>}
|
|
507
|
+
*/
|
|
508
|
+
static #stripEmpty(value) {
|
|
509
|
+
return Object.fromEntries(
|
|
510
|
+
Object.entries(value).filter(([, entry]) => {
|
|
511
|
+
if (Array.isArray(entry)) {
|
|
512
|
+
return entry.length > 0
|
|
513
|
+
}
|
|
514
|
+
return entry !== null && entry !== undefined && entry !== ''
|
|
515
|
+
})
|
|
516
|
+
)
|
|
517
|
+
}
|
|
518
|
+
}
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
export class SchematicNetlistBuilder {
|
|
10
10
|
/**
|
|
11
11
|
* Builds normalized nets and connectivity diagnostics.
|
|
12
|
-
* @param {{ lines: { x1: number, y1: number, x2: number, y2: number, ownerIndex?: string, isBus?: boolean }[], texts: { x: number, y: number, text: string, recordType?: string }[], pins?: { x: number, y: number, length: number, orientation: 'left' | 'right' | 'top' | 'bottom', name: string, designator: string }[], ports?: { x: number, y: number, width: number, direction?: 'left' | 'right' | 'up' | 'down', name: string }[], junctions?: { x: number, y: number, color: string }[], busEntries?: { x1: number, y1: number, x2: number, y2: number }[], sheetEntries?: { x: number, y: number, name: string }[] }} schematic
|
|
13
|
-
* @returns {{ nets: { name: string, segments: { x1: number, y1: number, x2: number, y2: number, ownerIndex?: string, isBus?: boolean }[], labels: { x: number, y: number, text: string, recordType?: string }[], powerPorts: { x: number, y: number, text: string, recordType?: string }[], pins: { x: number, y: number, length: number, orientation: 'left' | 'right' | 'top' | 'bottom', name: string, designator: string }[], ports: { x: number, y: number, width: number, direction?: 'left' | 'right' | 'up' | 'down', name: string }[], junctions: { x: number, y: number, color: string }[], busEntries: { x1: number, y1: number, x2: number, y2: number }[], sheetEntries: { x: number, y: number, name: string }[] }[], diagnostics: { severity: 'warning', message: string }[] }}
|
|
12
|
+
* @param {{ lines: { x1: number, y1: number, x2: number, y2: number, ownerIndex?: string, isBus?: boolean }[], texts: { x: number, y: number, text: string, recordType?: string }[], pins?: { x: number, y: number, length: number, orientation: 'left' | 'right' | 'top' | 'bottom', name: string, designator: string }[], ports?: { x: number, y: number, width: number, direction?: 'left' | 'right' | 'up' | 'down', name: string }[], crossSheetConnectors?: { key: string, x: number, y: number, name: string, style?: string }[], junctions?: { x: number, y: number, color: string }[], busEntries?: { x1: number, y1: number, x2: number, y2: number }[], sheetEntries?: { x: number, y: number, name: string }[] }} schematic
|
|
13
|
+
* @returns {{ nets: { name: string, segments: { x1: number, y1: number, x2: number, y2: number, ownerIndex?: string, isBus?: boolean }[], labels: { x: number, y: number, text: string, recordType?: string }[], powerPorts: { x: number, y: number, text: string, recordType?: string }[], crossSheetConnectors: { key: string, name: string, x: number, y: number, style?: string }[], pins: { x: number, y: number, length: number, orientation: 'left' | 'right' | 'top' | 'bottom', name: string, designator: string }[], ports: { x: number, y: number, width: number, direction?: 'left' | 'right' | 'up' | 'down', name: string }[], junctions: { x: number, y: number, color: string }[], busEntries: { x1: number, y1: number, x2: number, y2: number }[], sheetEntries: { x: number, y: number, name: string }[] }[], diagnostics: { severity: 'warning', message: string }[] }}
|
|
14
14
|
*/
|
|
15
15
|
static build(schematic) {
|
|
16
16
|
const diagnostics = []
|
|
@@ -38,6 +38,11 @@ export class SchematicNetlistBuilder {
|
|
|
38
38
|
text.recordType === '17' &&
|
|
39
39
|
SchematicNetlistBuilder.#groupContainsPoint(group, text)
|
|
40
40
|
)
|
|
41
|
+
const crossSheetConnectors = (
|
|
42
|
+
schematic.crossSheetConnectors || []
|
|
43
|
+
).filter((connector) =>
|
|
44
|
+
SchematicNetlistBuilder.#groupContainsPoint(group, connector)
|
|
45
|
+
)
|
|
41
46
|
const pins = (schematic.pins || []).filter((pin) =>
|
|
42
47
|
SchematicNetlistBuilder.#groupContainsPoint(
|
|
43
48
|
group,
|
|
@@ -77,6 +82,7 @@ export class SchematicNetlistBuilder {
|
|
|
77
82
|
...new Set(
|
|
78
83
|
[
|
|
79
84
|
...powerPorts.map((item) => item.text),
|
|
85
|
+
...crossSheetConnectors.map((item) => item.name),
|
|
80
86
|
...labels.map((item) => item.text)
|
|
81
87
|
].filter(Boolean)
|
|
82
88
|
)
|
|
@@ -99,6 +105,13 @@ export class SchematicNetlistBuilder {
|
|
|
99
105
|
segments: group,
|
|
100
106
|
labels,
|
|
101
107
|
powerPorts,
|
|
108
|
+
crossSheetConnectors: crossSheetConnectors.map((connector) => ({
|
|
109
|
+
key: connector.key,
|
|
110
|
+
name: connector.name,
|
|
111
|
+
x: connector.x,
|
|
112
|
+
y: connector.y,
|
|
113
|
+
style: connector.style
|
|
114
|
+
})),
|
|
102
115
|
pins,
|
|
103
116
|
ports,
|
|
104
117
|
junctions,
|