altium-toolkit 1.1.22 → 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 +134 -23
- package/docs/model-format.md +174 -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 +511 -1
- 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 +2 -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/src/core/BinaryReader.mjs +213 -2
- package/src/core/altium/AltiumParser.mjs +352 -14
- package/src/core/altium/AltiumUnits.mjs +205 -0
- package/src/core/altium/AsciiRecordParser.mjs +9 -0
- 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/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/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/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/SchematicOwnershipGraphParser.mjs +102 -3
- package/src/core/altium/SchematicPinParser.mjs +12 -45
- package/src/core/altium/SchematicPrimitiveParser.mjs +9 -14
- 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 +64 -25
- 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/UnsupportedFeatureReportBuilder.mjs +380 -0
- package/src/parser.mjs +35 -1
- package/src/renderers.mjs +1 -0
- package/src/ui/SchematicShapeRenderer.mjs +49 -6
- package/src/ui/SchematicSvgRenderer.mjs +37 -8
- package/src/ui/SchematicTypography.mjs +4 -3
|
@@ -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 }[], 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 }[] }}
|
|
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, ownerIndex?: string, componentDesignator?: 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 }[], componentDesignatorsByOwnerIndex?: Map<string, string> | Record<string, string> }} schematic
|
|
13
|
+
* @returns {{ nets: { name: string, autoName?: string, autoNameSource?: string, aliasCandidates?: 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, ownerIndex?: string, componentDesignator?: 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 = []
|
|
@@ -43,12 +43,19 @@ export class SchematicNetlistBuilder {
|
|
|
43
43
|
).filter((connector) =>
|
|
44
44
|
SchematicNetlistBuilder.#groupContainsPoint(group, connector)
|
|
45
45
|
)
|
|
46
|
-
const pins = (schematic.pins || [])
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
const pins = (schematic.pins || [])
|
|
47
|
+
.filter((pin) =>
|
|
48
|
+
SchematicNetlistBuilder.#groupContainsPoint(
|
|
49
|
+
group,
|
|
50
|
+
SchematicNetlistBuilder.#resolvePinConnectionPoint(pin)
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
.map((pin) =>
|
|
54
|
+
SchematicNetlistBuilder.#annotatePinComponentDesignator(
|
|
55
|
+
pin,
|
|
56
|
+
schematic.componentDesignatorsByOwnerIndex
|
|
57
|
+
)
|
|
50
58
|
)
|
|
51
|
-
)
|
|
52
59
|
const ports = (schematic.ports || []).filter((port) =>
|
|
53
60
|
SchematicNetlistBuilder.#groupContainsPoint(
|
|
54
61
|
group,
|
|
@@ -89,6 +96,11 @@ export class SchematicNetlistBuilder {
|
|
|
89
96
|
]
|
|
90
97
|
const name =
|
|
91
98
|
explicitNames[0] || 'UnknownNet' + String(unknownNetIndex++)
|
|
99
|
+
const implicitNaming =
|
|
100
|
+
SchematicNetlistBuilder.#deriveImplicitNetNaming({
|
|
101
|
+
explicitNames,
|
|
102
|
+
pins
|
|
103
|
+
})
|
|
92
104
|
|
|
93
105
|
if (explicitNames.length > 1) {
|
|
94
106
|
diagnostics.push({
|
|
@@ -102,6 +114,7 @@ export class SchematicNetlistBuilder {
|
|
|
102
114
|
|
|
103
115
|
return {
|
|
104
116
|
name,
|
|
117
|
+
...implicitNaming,
|
|
105
118
|
segments: group,
|
|
106
119
|
labels,
|
|
107
120
|
powerPorts,
|
|
@@ -120,7 +133,257 @@ export class SchematicNetlistBuilder {
|
|
|
120
133
|
}
|
|
121
134
|
})
|
|
122
135
|
|
|
123
|
-
return {
|
|
136
|
+
return {
|
|
137
|
+
nets: SchematicNetlistBuilder.#mergeMatchingPowerPortNets(nets),
|
|
138
|
+
diagnostics
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Adds a component designator to a net pin when owner metadata resolves it.
|
|
144
|
+
* @param {object} pin Net pin.
|
|
145
|
+
* @param {Map<string, string> | Record<string, string> | undefined} componentDesignatorsByOwnerIndex Component designators by native owner id.
|
|
146
|
+
* @returns {object}
|
|
147
|
+
*/
|
|
148
|
+
static #annotatePinComponentDesignator(
|
|
149
|
+
pin,
|
|
150
|
+
componentDesignatorsByOwnerIndex
|
|
151
|
+
) {
|
|
152
|
+
const componentDesignator =
|
|
153
|
+
pin?.componentDesignator ||
|
|
154
|
+
SchematicNetlistBuilder.#lookupComponentDesignator(
|
|
155
|
+
componentDesignatorsByOwnerIndex,
|
|
156
|
+
pin?.ownerIndex
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if (!componentDesignator) {
|
|
160
|
+
return pin
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
...pin,
|
|
165
|
+
componentDesignator
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Derives non-breaking names for unnamed nets from attached pins.
|
|
171
|
+
* @param {{ explicitNames: string[], pins: object[] }} options Naming inputs.
|
|
172
|
+
* @returns {{ autoName?: string, autoNameSource?: string, aliasCandidates?: string[] }}
|
|
173
|
+
*/
|
|
174
|
+
static #deriveImplicitNetNaming(options) {
|
|
175
|
+
if ((options.explicitNames || []).length) {
|
|
176
|
+
return {}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const candidates = SchematicNetlistBuilder.#pinAliasCandidates(
|
|
180
|
+
options.pins || []
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if (!candidates.length) {
|
|
184
|
+
return {}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
autoName: candidates[0].name,
|
|
189
|
+
autoNameSource: candidates[0].source,
|
|
190
|
+
aliasCandidates: candidates.map((candidate) => candidate.name)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Builds deterministic alias candidates from connected pins.
|
|
196
|
+
* @param {object[]} pins Connected pins.
|
|
197
|
+
* @returns {{ name: string, source: string }[]}
|
|
198
|
+
*/
|
|
199
|
+
static #pinAliasCandidates(pins) {
|
|
200
|
+
const candidates = []
|
|
201
|
+
|
|
202
|
+
for (const pin of pins || []) {
|
|
203
|
+
const componentDesignator = String(
|
|
204
|
+
pin?.componentDesignator || ''
|
|
205
|
+
).trim()
|
|
206
|
+
const ownerIndex = String(pin?.ownerIndex || '').trim()
|
|
207
|
+
const pinDesignator = String(pin?.designator || '').trim()
|
|
208
|
+
const pinName = String(pin?.name || '').trim()
|
|
209
|
+
|
|
210
|
+
if (componentDesignator) {
|
|
211
|
+
SchematicNetlistBuilder.#pushPinAliasCandidate(
|
|
212
|
+
candidates,
|
|
213
|
+
componentDesignator,
|
|
214
|
+
pinDesignator,
|
|
215
|
+
'component-pin'
|
|
216
|
+
)
|
|
217
|
+
SchematicNetlistBuilder.#pushPinAliasCandidate(
|
|
218
|
+
candidates,
|
|
219
|
+
componentDesignator,
|
|
220
|
+
pinName,
|
|
221
|
+
'component-pin-name'
|
|
222
|
+
)
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (ownerIndex) {
|
|
227
|
+
SchematicNetlistBuilder.#pushPinAliasCandidate(
|
|
228
|
+
candidates,
|
|
229
|
+
'owner-' + ownerIndex,
|
|
230
|
+
pinDesignator,
|
|
231
|
+
'owner-pin'
|
|
232
|
+
)
|
|
233
|
+
SchematicNetlistBuilder.#pushPinAliasCandidate(
|
|
234
|
+
candidates,
|
|
235
|
+
'owner-' + ownerIndex,
|
|
236
|
+
pinName,
|
|
237
|
+
'owner-pin-name'
|
|
238
|
+
)
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (pinName) {
|
|
243
|
+
candidates.push({
|
|
244
|
+
name: 'pin.' + pinName,
|
|
245
|
+
source: 'pin-name'
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return SchematicNetlistBuilder.#dedupeAliasCandidates(candidates)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Adds one scoped pin alias candidate.
|
|
255
|
+
* @param {{ name: string, source: string }[]} candidates Alias candidates.
|
|
256
|
+
* @param {string} scope Alias scope.
|
|
257
|
+
* @param {string} pinLabel Pin designator or name.
|
|
258
|
+
* @param {string} source Alias source label.
|
|
259
|
+
* @returns {void}
|
|
260
|
+
*/
|
|
261
|
+
static #pushPinAliasCandidate(candidates, scope, pinLabel, source) {
|
|
262
|
+
const trimmedScope = String(scope || '').trim()
|
|
263
|
+
const trimmedPinLabel = String(pinLabel || '').trim()
|
|
264
|
+
|
|
265
|
+
if (!trimmedScope || !trimmedPinLabel) {
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
candidates.push({
|
|
270
|
+
name: trimmedScope + '.' + trimmedPinLabel,
|
|
271
|
+
source
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Deduplicates alias candidates by visible alias name.
|
|
277
|
+
* @param {{ name: string, source: string }[]} candidates Alias candidates.
|
|
278
|
+
* @returns {{ name: string, source: string }[]}
|
|
279
|
+
*/
|
|
280
|
+
static #dedupeAliasCandidates(candidates) {
|
|
281
|
+
const seen = new Set()
|
|
282
|
+
const uniqueCandidates = []
|
|
283
|
+
|
|
284
|
+
for (const candidate of candidates || []) {
|
|
285
|
+
if (!candidate.name || seen.has(candidate.name)) {
|
|
286
|
+
continue
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
seen.add(candidate.name)
|
|
290
|
+
uniqueCandidates.push(candidate)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return uniqueCandidates
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Looks up a component designator by native owner id.
|
|
298
|
+
* @param {Map<string, string> | Record<string, string> | undefined} componentDesignatorsByOwnerIndex Component designators by native owner id.
|
|
299
|
+
* @param {string | undefined} ownerIndex Native owner id.
|
|
300
|
+
* @returns {string}
|
|
301
|
+
*/
|
|
302
|
+
static #lookupComponentDesignator(
|
|
303
|
+
componentDesignatorsByOwnerIndex,
|
|
304
|
+
ownerIndex
|
|
305
|
+
) {
|
|
306
|
+
const key = String(ownerIndex || '').trim()
|
|
307
|
+
if (!key || !componentDesignatorsByOwnerIndex) {
|
|
308
|
+
return ''
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (componentDesignatorsByOwnerIndex instanceof Map) {
|
|
312
|
+
return String(componentDesignatorsByOwnerIndex.get(key) || '')
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return String(componentDesignatorsByOwnerIndex[key] || '')
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Merges physically separate net groups that share one power-port label.
|
|
320
|
+
* @param {{ name: string, powerPorts: { text?: string }[] }[]} nets Source nets.
|
|
321
|
+
* @returns {object[]}
|
|
322
|
+
*/
|
|
323
|
+
static #mergeMatchingPowerPortNets(nets) {
|
|
324
|
+
const mergedNets = []
|
|
325
|
+
const netsByPowerName = new Map()
|
|
326
|
+
|
|
327
|
+
for (const net of nets) {
|
|
328
|
+
const powerName =
|
|
329
|
+
SchematicNetlistBuilder.#singlePowerPortNetName(net)
|
|
330
|
+
|
|
331
|
+
if (!powerName) {
|
|
332
|
+
mergedNets.push(net)
|
|
333
|
+
continue
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const existingNet = netsByPowerName.get(powerName)
|
|
337
|
+
if (!existingNet) {
|
|
338
|
+
netsByPowerName.set(powerName, net)
|
|
339
|
+
mergedNets.push(net)
|
|
340
|
+
continue
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
SchematicNetlistBuilder.#appendNet(existingNet, net)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return mergedNets
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Returns one normalized power-port name when every port agrees.
|
|
351
|
+
* @param {{ powerPorts?: { text?: string }[] }} net Source net.
|
|
352
|
+
* @returns {string}
|
|
353
|
+
*/
|
|
354
|
+
static #singlePowerPortNetName(net) {
|
|
355
|
+
const names = [
|
|
356
|
+
...new Set(
|
|
357
|
+
(net.powerPorts || [])
|
|
358
|
+
.map((powerPort) => String(powerPort.text || '').trim())
|
|
359
|
+
.filter(Boolean)
|
|
360
|
+
.map((name) => name.toUpperCase())
|
|
361
|
+
)
|
|
362
|
+
]
|
|
363
|
+
|
|
364
|
+
return names.length === 1 ? names[0] : ''
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Appends one net's observable members to an existing logical net.
|
|
369
|
+
* @param {object} target Existing logical net.
|
|
370
|
+
* @param {object} source Source net to append.
|
|
371
|
+
* @returns {void}
|
|
372
|
+
*/
|
|
373
|
+
static #appendNet(target, source) {
|
|
374
|
+
for (const key of [
|
|
375
|
+
'segments',
|
|
376
|
+
'labels',
|
|
377
|
+
'powerPorts',
|
|
378
|
+
'crossSheetConnectors',
|
|
379
|
+
'pins',
|
|
380
|
+
'ports',
|
|
381
|
+
'junctions',
|
|
382
|
+
'busEntries',
|
|
383
|
+
'sheetEntries'
|
|
384
|
+
]) {
|
|
385
|
+
target[key].push(...(source[key] || []))
|
|
386
|
+
}
|
|
124
387
|
}
|
|
125
388
|
|
|
126
389
|
/**
|
|
@@ -15,7 +15,7 @@ export class SchematicOwnershipGraphParser {
|
|
|
15
15
|
/**
|
|
16
16
|
* Parses record parent/child links from raw schematic records.
|
|
17
17
|
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }[]} records Schematic records.
|
|
18
|
-
* @returns {{ schema: string, records: object[], recordsByIndexInSheet: Record<string, object>, childrenByParentKey: Record<string, object[]>, parentsByChildKey: Record<string, object> }}
|
|
18
|
+
* @returns {{ schema: string, records: object[], hierarchy: object[], recordsByRecordIndex: Record<string, object>, recordsByIndexInSheet: Record<string, object>, childrenByParentKey: Record<string, object[]>, parentsByChildKey: Record<string, object> }}
|
|
19
19
|
*/
|
|
20
20
|
static parse(records) {
|
|
21
21
|
const entries = (records || []).map((record, fallbackIndex) =>
|
|
@@ -54,6 +54,12 @@ export class SchematicOwnershipGraphParser {
|
|
|
54
54
|
records: entries.map((entry) =>
|
|
55
55
|
SchematicOwnershipGraphParser.#publicRecord(entry)
|
|
56
56
|
),
|
|
57
|
+
hierarchy: SchematicOwnershipGraphParser.#hierarchy(
|
|
58
|
+
entries,
|
|
59
|
+
parentsByChildKey
|
|
60
|
+
),
|
|
61
|
+
recordsByRecordIndex:
|
|
62
|
+
SchematicOwnershipGraphParser.#recordsByRecordIndex(entries),
|
|
57
63
|
recordsByIndexInSheet:
|
|
58
64
|
SchematicOwnershipGraphParser.#recordsByIndexInSheet(entries),
|
|
59
65
|
childrenByParentKey,
|
|
@@ -61,6 +67,63 @@ export class SchematicOwnershipGraphParser {
|
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Builds a nested public hierarchy from resolved parent links.
|
|
72
|
+
* @param {object[]} entries Record sidecar rows.
|
|
73
|
+
* @param {Record<string, { parentKey: string }>} parentsByChildKey Resolved parent links.
|
|
74
|
+
* @returns {object[]}
|
|
75
|
+
*/
|
|
76
|
+
static #hierarchy(entries, parentsByChildKey) {
|
|
77
|
+
const entriesByKey = new Map(entries.map((entry) => [entry.key, entry]))
|
|
78
|
+
const childrenByParentKey = new Map()
|
|
79
|
+
const childKeys = new Set()
|
|
80
|
+
|
|
81
|
+
for (const [childKey, parentLink] of Object.entries(
|
|
82
|
+
parentsByChildKey
|
|
83
|
+
)) {
|
|
84
|
+
const child = entriesByKey.get(childKey)
|
|
85
|
+
const parent = entriesByKey.get(parentLink.parentKey)
|
|
86
|
+
|
|
87
|
+
if (!child || !parent) {
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
childKeys.add(childKey)
|
|
92
|
+
if (!childrenByParentKey.has(parent.key)) {
|
|
93
|
+
childrenByParentKey.set(parent.key, [])
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
childrenByParentKey.get(parent.key).push(child)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return entries
|
|
100
|
+
.filter((entry) => !childKeys.has(entry.key))
|
|
101
|
+
.map((entry) =>
|
|
102
|
+
SchematicOwnershipGraphParser.#hierarchyNode(
|
|
103
|
+
entry,
|
|
104
|
+
childrenByParentKey
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Builds one nested hierarchy node.
|
|
111
|
+
* @param {object} entry Record sidecar row.
|
|
112
|
+
* @param {Map<string, object[]>} childrenByParentKey Child rows by parent key.
|
|
113
|
+
* @returns {object}
|
|
114
|
+
*/
|
|
115
|
+
static #hierarchyNode(entry, childrenByParentKey) {
|
|
116
|
+
return {
|
|
117
|
+
...SchematicOwnershipGraphParser.#publicRecord(entry),
|
|
118
|
+
children: (childrenByParentKey.get(entry.key) || []).map((child) =>
|
|
119
|
+
SchematicOwnershipGraphParser.#hierarchyNode(
|
|
120
|
+
child,
|
|
121
|
+
childrenByParentKey
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
64
127
|
/**
|
|
65
128
|
* Normalizes one raw schematic record into a sidecar row.
|
|
66
129
|
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }} record Source record.
|
|
@@ -83,7 +146,8 @@ export class SchematicOwnershipGraphParser {
|
|
|
83
146
|
getField(record?.fields, 'UniqueID') ||
|
|
84
147
|
getField(record?.fields, 'UniqueId'),
|
|
85
148
|
name: getField(record?.fields, 'Name'),
|
|
86
|
-
text: getField(record?.fields, 'Text')
|
|
149
|
+
text: getField(record?.fields, 'Text'),
|
|
150
|
+
fields: SchematicOwnershipGraphParser.#publicFields(record?.fields)
|
|
87
151
|
}
|
|
88
152
|
}
|
|
89
153
|
|
|
@@ -139,7 +203,8 @@ export class SchematicOwnershipGraphParser {
|
|
|
139
203
|
ownerIndex: entry.ownerIndex,
|
|
140
204
|
uniqueId: entry.uniqueId,
|
|
141
205
|
name: entry.name,
|
|
142
|
-
text: entry.text
|
|
206
|
+
text: entry.text,
|
|
207
|
+
fields: entry.fields
|
|
143
208
|
})
|
|
144
209
|
}
|
|
145
210
|
|
|
@@ -159,6 +224,22 @@ export class SchematicOwnershipGraphParser {
|
|
|
159
224
|
})
|
|
160
225
|
}
|
|
161
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Indexes records by parser record index.
|
|
229
|
+
* @param {object[]} entries Record sidecar rows.
|
|
230
|
+
* @returns {Record<string, object>}
|
|
231
|
+
*/
|
|
232
|
+
static #recordsByRecordIndex(entries) {
|
|
233
|
+
const index = {}
|
|
234
|
+
|
|
235
|
+
for (const entry of entries) {
|
|
236
|
+
index[String(entry.recordIndex)] =
|
|
237
|
+
SchematicOwnershipGraphParser.#publicRecord(entry)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return index
|
|
241
|
+
}
|
|
242
|
+
|
|
162
243
|
/**
|
|
163
244
|
* Indexes records by native IndexInSheet values.
|
|
164
245
|
* @param {object[]} entries Record sidecar rows.
|
|
@@ -177,6 +258,24 @@ export class SchematicOwnershipGraphParser {
|
|
|
177
258
|
return index
|
|
178
259
|
}
|
|
179
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Copies source fields into a stable plain object for read-only consumers.
|
|
263
|
+
* @param {Record<string, string | string[]> | undefined} fields Source fields.
|
|
264
|
+
* @returns {Record<string, string | string[]>}
|
|
265
|
+
*/
|
|
266
|
+
static #publicFields(fields) {
|
|
267
|
+
if (!fields || typeof fields !== 'object') {
|
|
268
|
+
return {}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return Object.fromEntries(
|
|
272
|
+
Object.entries(fields).map(([key, value]) => [
|
|
273
|
+
key,
|
|
274
|
+
Array.isArray(value) ? [...value] : value
|
|
275
|
+
])
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
180
279
|
/**
|
|
181
280
|
* Drops empty object fields while preserving numeric zero.
|
|
182
281
|
* @param {Record<string, unknown>} value Candidate object.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { ParserUtils } from './ParserUtils.mjs'
|
|
6
6
|
import { SchematicNoErcSymbolResolver } from './SchematicNoErcSymbolResolver.mjs'
|
|
7
7
|
import { SchematicPinDesignatorInferer } from './SchematicPinDesignatorInferer.mjs'
|
|
8
|
+
import { SchematicTextRunParser } from './SchematicTextRunParser.mjs'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Helpers for normalized schematic pins, ports, and crosses.
|
|
@@ -480,7 +481,7 @@ export class SchematicPinParser {
|
|
|
480
481
|
options.recordType
|
|
481
482
|
)
|
|
482
483
|
),
|
|
483
|
-
width: ParserUtils.
|
|
484
|
+
width: ParserUtils.parseSchematicLineWidth(fields),
|
|
484
485
|
lineStyle,
|
|
485
486
|
isBus: options.isBus === true ? true : undefined,
|
|
486
487
|
recordType: options.recordType || undefined,
|
|
@@ -524,7 +525,7 @@ export class SchematicPinParser {
|
|
|
524
525
|
fields.Color,
|
|
525
526
|
SchematicPinParser.#resolveDefaultPolylineColor(fields, '7')
|
|
526
527
|
),
|
|
527
|
-
width: ParserUtils.
|
|
528
|
+
width: ParserUtils.parseSchematicLineWidth(fields),
|
|
528
529
|
lineStyle
|
|
529
530
|
})
|
|
530
531
|
}
|
|
@@ -541,7 +542,7 @@ export class SchematicPinParser {
|
|
|
541
542
|
fields.Color,
|
|
542
543
|
SchematicPinParser.#resolveDefaultPolylineColor(fields, '7')
|
|
543
544
|
),
|
|
544
|
-
width: ParserUtils.
|
|
545
|
+
width: ParserUtils.parseSchematicLineWidth(fields),
|
|
545
546
|
lineStyle
|
|
546
547
|
})
|
|
547
548
|
|
|
@@ -644,8 +645,12 @@ export class SchematicPinParser {
|
|
|
644
645
|
case 2:
|
|
645
646
|
return 'filled-arrow'
|
|
646
647
|
case 3:
|
|
647
|
-
return '
|
|
648
|
+
return 'tail'
|
|
648
649
|
case 4:
|
|
650
|
+
return 'filled-tail'
|
|
651
|
+
case 5:
|
|
652
|
+
return 'circle'
|
|
653
|
+
case 6:
|
|
649
654
|
return 'square'
|
|
650
655
|
default:
|
|
651
656
|
return 'marker-' + shape
|
|
@@ -1209,49 +1214,11 @@ export class SchematicPinParser {
|
|
|
1209
1214
|
* @returns {{ name: string, nameSegments?: { text: string, overline: boolean }[] }}
|
|
1210
1215
|
*/
|
|
1211
1216
|
static #parseSchematicPinName(name) {
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
for (const character of String(name || '').trim()) {
|
|
1215
|
-
if (character === '\\') {
|
|
1216
|
-
const previousCharacter = characters.at(-1)
|
|
1217
|
-
if (previousCharacter) {
|
|
1218
|
-
previousCharacter.overline = true
|
|
1219
|
-
}
|
|
1220
|
-
continue
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
characters.push({
|
|
1224
|
-
text: character,
|
|
1225
|
-
overline: false
|
|
1226
|
-
})
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
const normalizedName = characters
|
|
1230
|
-
.map((character) => character.text)
|
|
1231
|
-
.join('')
|
|
1232
|
-
const nameSegments = []
|
|
1233
|
-
|
|
1234
|
-
for (const character of characters) {
|
|
1235
|
-
const previousSegment = nameSegments.at(-1)
|
|
1236
|
-
if (
|
|
1237
|
-
previousSegment &&
|
|
1238
|
-
previousSegment.overline === character.overline
|
|
1239
|
-
) {
|
|
1240
|
-
previousSegment.text += character.text
|
|
1241
|
-
continue
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
nameSegments.push({
|
|
1245
|
-
text: character.text,
|
|
1246
|
-
overline: character.overline
|
|
1247
|
-
})
|
|
1248
|
-
}
|
|
1217
|
+
const parsed = SchematicTextRunParser.parseOptionalOverlineRuns(name)
|
|
1249
1218
|
|
|
1250
1219
|
return {
|
|
1251
|
-
name:
|
|
1252
|
-
nameSegments:
|
|
1253
|
-
? nameSegments
|
|
1254
|
-
: undefined
|
|
1220
|
+
name: parsed.text,
|
|
1221
|
+
nameSegments: parsed.segments
|
|
1255
1222
|
}
|
|
1256
1223
|
}
|
|
1257
1224
|
|
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
parseBoolean,
|
|
10
10
|
parseNumericField,
|
|
11
11
|
parseNumericFieldWithFraction,
|
|
12
|
+
parseSchematicLineWidth,
|
|
12
13
|
toColor
|
|
13
14
|
} = ParserUtils
|
|
14
15
|
|
|
@@ -134,8 +135,7 @@ export class SchematicPrimitiveParser {
|
|
|
134
135
|
fill: toColor(record.fields.AreaColor, color),
|
|
135
136
|
isSolid: parseBoolean(record.fields.IsSolid),
|
|
136
137
|
transparent: parseBoolean(record.fields.Transparent),
|
|
137
|
-
lineWidth:
|
|
138
|
-
parseNumericField(record.fields, 'LineWidth') || 1,
|
|
138
|
+
lineWidth: parseSchematicLineWidth(record.fields),
|
|
139
139
|
renderOrder: SchematicPrimitiveParser.#resolveRenderOrder(
|
|
140
140
|
record.fields,
|
|
141
141
|
index
|
|
@@ -169,7 +169,7 @@ export class SchematicPrimitiveParser {
|
|
|
169
169
|
points,
|
|
170
170
|
segments,
|
|
171
171
|
color: toColor(record.fields.Color, '#000000'),
|
|
172
|
-
width:
|
|
172
|
+
width: parseSchematicLineWidth(record.fields),
|
|
173
173
|
lineStyle:
|
|
174
174
|
parseNumericField(record.fields, 'LineStyle') || 0,
|
|
175
175
|
renderOrder: SchematicPrimitiveParser.#resolveRenderOrder(
|
|
@@ -233,8 +233,7 @@ export class SchematicPrimitiveParser {
|
|
|
233
233
|
fill: toColor(record.fields.AreaColor, '#ffffff'),
|
|
234
234
|
isSolid: parseBoolean(record.fields.IsSolid),
|
|
235
235
|
transparent: parseBoolean(record.fields.Transparent),
|
|
236
|
-
lineWidth:
|
|
237
|
-
parseNumericField(record.fields, 'LineWidth') || 1,
|
|
236
|
+
lineWidth: parseSchematicLineWidth(record.fields),
|
|
238
237
|
renderOrder: SchematicPrimitiveParser.#resolveRenderOrder(
|
|
239
238
|
record.fields,
|
|
240
239
|
index
|
|
@@ -280,8 +279,7 @@ export class SchematicPrimitiveParser {
|
|
|
280
279
|
fill: toColor(record.fields.AreaColor, '#ffffff'),
|
|
281
280
|
isSolid: parseBoolean(record.fields.IsSolid),
|
|
282
281
|
transparent: parseBoolean(record.fields.Transparent),
|
|
283
|
-
lineWidth:
|
|
284
|
-
parseNumericField(record.fields, 'LineWidth') || 1,
|
|
282
|
+
lineWidth: parseSchematicLineWidth(record.fields),
|
|
285
283
|
lineStyle:
|
|
286
284
|
parseNumericField(record.fields, 'LineStyle') || 0,
|
|
287
285
|
renderOrder: SchematicPrimitiveParser.#resolveRenderOrder(
|
|
@@ -339,8 +337,7 @@ export class SchematicPrimitiveParser {
|
|
|
339
337
|
1
|
|
340
338
|
),
|
|
341
339
|
color: toColor(record.fields.Color, '#000000'),
|
|
342
|
-
lineWidth:
|
|
343
|
-
parseNumericField(record.fields, 'LineWidth') || 1,
|
|
340
|
+
lineWidth: parseSchematicLineWidth(record.fields),
|
|
344
341
|
renderOrder: SchematicPrimitiveParser.#resolveRenderOrder(
|
|
345
342
|
record.fields,
|
|
346
343
|
index
|
|
@@ -428,8 +425,7 @@ export class SchematicPrimitiveParser {
|
|
|
428
425
|
transparent: usesFrameFallback
|
|
429
426
|
? false
|
|
430
427
|
: parseBoolean(record.fields.Transparent),
|
|
431
|
-
lineWidth:
|
|
432
|
-
parseNumericField(record.fields, 'LineWidth') || 1,
|
|
428
|
+
lineWidth: parseSchematicLineWidth(record.fields),
|
|
433
429
|
lineStyle:
|
|
434
430
|
usesFrameFallback ||
|
|
435
431
|
SchematicPrimitiveParser.#shouldDefaultBlanketDash(
|
|
@@ -604,7 +600,7 @@ export class SchematicPrimitiveParser {
|
|
|
604
600
|
startAngle: startAngle === null ? 0 : startAngle,
|
|
605
601
|
endAngle: endAngle === null ? 360 : endAngle,
|
|
606
602
|
color: toColor(record.fields.Color, '#a44a1b'),
|
|
607
|
-
width:
|
|
603
|
+
width: parseSchematicLineWidth(record.fields),
|
|
608
604
|
renderOrder: SchematicPrimitiveParser.#resolveRenderOrder(
|
|
609
605
|
record.fields,
|
|
610
606
|
index
|
|
@@ -661,8 +657,7 @@ export class SchematicPrimitiveParser {
|
|
|
661
657
|
fill: toColor(record.fields.AreaColor, '#ffffff'),
|
|
662
658
|
isSolid: parseBoolean(record.fields.IsSolid),
|
|
663
659
|
transparent: parseBoolean(record.fields.Transparent),
|
|
664
|
-
lineWidth:
|
|
665
|
-
parseNumericField(record.fields, 'LineWidth') || 1,
|
|
660
|
+
lineWidth: parseSchematicLineWidth(record.fields),
|
|
666
661
|
renderOrder: SchematicPrimitiveParser.#resolveRenderOrder(
|
|
667
662
|
record.fields,
|
|
668
663
|
index
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
4
|
|
|
5
5
|
import { ParserUtils } from './ParserUtils.mjs'
|
|
6
|
+
import { SchematicFieldCoverageReportBuilder } from './SchematicFieldCoverageReportBuilder.mjs'
|
|
6
7
|
|
|
7
8
|
const { parseBoolean, parseNumericField, toColor } = ParserUtils
|
|
8
9
|
|
|
@@ -55,6 +56,7 @@ export class SchematicQaReportBuilder {
|
|
|
55
56
|
geometryFallbacks: SchematicQaReportBuilder.#geometryFallbacks(
|
|
56
57
|
input?.texts
|
|
57
58
|
),
|
|
59
|
+
fieldCoverage: SchematicFieldCoverageReportBuilder.build(records),
|
|
58
60
|
findings
|
|
59
61
|
}
|
|
60
62
|
}
|