altium-toolkit 1.0.8 → 1.0.10
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/ci_artifact_bundle_a1.schema.json +76 -0
- package/docs/schemas/altium_toolkit/draftsman_digest_a1.schema.json +35 -0
- package/docs/schemas/altium_toolkit/netlist_a1.schema.json +53 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +1826 -110
- package/docs/schemas/altium_toolkit/parser_compatibility_fuzz_a1.schema.json +25 -0
- package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +86 -0
- package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +63 -0
- package/docs/schemas/altium_toolkit/project_document_graph_a1.schema.json +33 -0
- package/docs/schemas/altium_toolkit/schematic_svg_semantics_a1.schema.json +50 -0
- package/docs/schemas/altium_toolkit/svg_model_cross_link_a1.schema.json +39 -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 +196 -45
- package/src/core/altium/CiArtifactBundleBuilder.mjs +202 -0
- package/src/core/altium/DraftsmanDigestParser.mjs +689 -0
- 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/ParserCompatibilityFuzzer.mjs +192 -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 +495 -32
- package/src/core/altium/PcbOwnershipGraphBuilder.mjs +245 -0
- package/src/core/altium/PcbPadPrimitiveParser.mjs +78 -65
- package/src/core/altium/PcbPadStackParser.mjs +229 -2
- package/src/core/altium/PcbPickPlacePositionResolver.mjs +224 -0
- package/src/core/altium/PcbPrimitiveParameterParser.mjs +3 -2
- package/src/core/altium/PcbRawRecordRegistry.mjs +121 -130
- package/src/core/altium/PcbRegionPrimitiveParser.mjs +76 -3
- package/src/core/altium/PcbRouteAnalysisBuilder.mjs +730 -0
- 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 +541 -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 +281 -7
- package/src/core/altium/ProjectAnnotationParser.mjs +205 -0
- package/src/core/altium/ProjectDesignBundleBuilder.mjs +492 -0
- package/src/core/altium/ProjectDocumentGraphBuilder.mjs +280 -0
- package/src/core/altium/ProjectNetlistExporter.mjs +503 -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/altium/SvgModelCrossLinkValidator.mjs +402 -0
- package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +136 -96
- package/src/core/circuit-json/CircuitJsonModelAdapterPcbElements.mjs +244 -0
- package/src/core/circuit-json/CircuitJsonModelSchema.mjs +1 -1
- package/src/core/ole/OleCompoundDocument.mjs +20 -0
- package/src/parser.mjs +35 -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 +1252 -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,312 @@
|
|
|
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 { getField, parseNumericField } = ParserUtils
|
|
8
|
+
const COMPONENT_OWNER_AXIS_THRESHOLD = 160
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolves native owner-linked text records for schematic components.
|
|
12
|
+
*/
|
|
13
|
+
export class SchematicComponentOwnerTextResolver {
|
|
14
|
+
/**
|
|
15
|
+
* Resolves raw text records belonging to one schematic component.
|
|
16
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }} componentRecord Component placement record.
|
|
17
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }[]} records Indexed schematic records.
|
|
18
|
+
* @param {Map<string, { fields: Record<string, string | string[]> }[]>} relatedTexts Text records grouped by native OwnerIndex.
|
|
19
|
+
* @returns {{ fields: Record<string, string | string[]> }[]}
|
|
20
|
+
*/
|
|
21
|
+
static resolveOwnerTexts(componentRecord, records, relatedTexts) {
|
|
22
|
+
const ownerIndexes =
|
|
23
|
+
SchematicComponentOwnerTextResolver.#resolveOwnerIndexes(
|
|
24
|
+
componentRecord,
|
|
25
|
+
records
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
for (const ownerIndex of ownerIndexes) {
|
|
29
|
+
const ownerTexts = relatedTexts.get(ownerIndex) || []
|
|
30
|
+
if (
|
|
31
|
+
SchematicComponentOwnerTextResolver.#hasComponentLabelText(
|
|
32
|
+
ownerTexts
|
|
33
|
+
)
|
|
34
|
+
) {
|
|
35
|
+
return ownerTexts
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return []
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns true when a schematic primitive participates in owner display
|
|
44
|
+
* mode selection.
|
|
45
|
+
* @param {Record<string, string | string[]>} fields
|
|
46
|
+
* @returns {boolean}
|
|
47
|
+
*/
|
|
48
|
+
static isDisplayModeSelectablePrimitive(fields) {
|
|
49
|
+
const recordType = getField(fields, 'RECORD')
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
recordType === '2' ||
|
|
53
|
+
recordType === '6' ||
|
|
54
|
+
recordType === '11' ||
|
|
55
|
+
recordType === '12' ||
|
|
56
|
+
recordType === '13' ||
|
|
57
|
+
recordType === '27' ||
|
|
58
|
+
(SchematicComponentOwnerTextResolver.#hasCoordinatePair(
|
|
59
|
+
fields,
|
|
60
|
+
'Location'
|
|
61
|
+
) &&
|
|
62
|
+
SchematicComponentOwnerTextResolver.#hasCoordinatePair(
|
|
63
|
+
fields,
|
|
64
|
+
'Corner'
|
|
65
|
+
))
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolves candidate owner indexes for one schematic component record.
|
|
71
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }} componentRecord Component placement record.
|
|
72
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }[]} records Indexed schematic records.
|
|
73
|
+
* @returns {string[]}
|
|
74
|
+
*/
|
|
75
|
+
static #resolveOwnerIndexes(componentRecord, records) {
|
|
76
|
+
return SchematicComponentOwnerTextResolver.#dedupeStrings([
|
|
77
|
+
SchematicComponentOwnerTextResolver.#inferFollowingOwnerIndex(
|
|
78
|
+
componentRecord,
|
|
79
|
+
records
|
|
80
|
+
),
|
|
81
|
+
...SchematicComponentOwnerTextResolver.#legacyOwnerIndexes(
|
|
82
|
+
componentRecord
|
|
83
|
+
)
|
|
84
|
+
])
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Infers the owner id used by the display primitives following a component.
|
|
89
|
+
* @param {{ recordIndex?: number }} componentRecord Component placement record.
|
|
90
|
+
* @param {{ fields: Record<string, string | string[]> }[]} records Indexed schematic records.
|
|
91
|
+
* @returns {string}
|
|
92
|
+
*/
|
|
93
|
+
static #inferFollowingOwnerIndex(componentRecord, records) {
|
|
94
|
+
if (!Number.isInteger(componentRecord?.recordIndex)) {
|
|
95
|
+
return ''
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let labelOwnerIndex = ''
|
|
99
|
+
|
|
100
|
+
for (
|
|
101
|
+
let index = componentRecord.recordIndex + 1;
|
|
102
|
+
index < records.length;
|
|
103
|
+
index += 1
|
|
104
|
+
) {
|
|
105
|
+
const fields = records[index]?.fields
|
|
106
|
+
const recordType = getField(fields, 'RECORD')
|
|
107
|
+
if (recordType === '1' || recordType === '45') {
|
|
108
|
+
break
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const ownerIndex = getField(fields, 'OwnerIndex')
|
|
112
|
+
if (!ownerIndex) {
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (
|
|
117
|
+
SchematicComponentOwnerTextResolver.#isDisplayOwnerRecord(
|
|
118
|
+
fields
|
|
119
|
+
) &&
|
|
120
|
+
SchematicComponentOwnerTextResolver.#isNearComponent(
|
|
121
|
+
fields,
|
|
122
|
+
componentRecord
|
|
123
|
+
)
|
|
124
|
+
) {
|
|
125
|
+
return ownerIndex
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (
|
|
129
|
+
!labelOwnerIndex &&
|
|
130
|
+
SchematicComponentOwnerTextResolver.#isComponentLabelRecord(
|
|
131
|
+
fields
|
|
132
|
+
) &&
|
|
133
|
+
SchematicComponentOwnerTextResolver.#isNearComponent(
|
|
134
|
+
fields,
|
|
135
|
+
componentRecord
|
|
136
|
+
)
|
|
137
|
+
) {
|
|
138
|
+
labelOwnerIndex = ownerIndex
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return labelOwnerIndex
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Resolves legacy owner keys used by older printable schematic records.
|
|
147
|
+
* @param {{ fields: Record<string, string | string[]> }} componentRecord Component placement record.
|
|
148
|
+
* @returns {string[]}
|
|
149
|
+
*/
|
|
150
|
+
static #legacyOwnerIndexes(componentRecord) {
|
|
151
|
+
const indexInSheet = parseNumericField(
|
|
152
|
+
componentRecord?.fields,
|
|
153
|
+
'IndexInSheet'
|
|
154
|
+
)
|
|
155
|
+
const ownerIndex = getField(componentRecord?.fields, 'OwnerIndex')
|
|
156
|
+
const keys = []
|
|
157
|
+
|
|
158
|
+
if (indexInSheet !== null) {
|
|
159
|
+
keys.push(String(indexInSheet + 1), String(indexInSheet))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
keys.push(ownerIndex)
|
|
163
|
+
|
|
164
|
+
return keys
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Returns true when one record can identify a component display owner.
|
|
169
|
+
* @param {Record<string, string | string[]>} fields Raw record fields.
|
|
170
|
+
* @returns {boolean}
|
|
171
|
+
*/
|
|
172
|
+
static #isDisplayOwnerRecord(fields) {
|
|
173
|
+
const ownerPartId = getField(fields, 'OwnerPartId')
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
ownerPartId !== '-1' &&
|
|
177
|
+
SchematicComponentOwnerTextResolver.isDisplayModeSelectablePrimitive(
|
|
178
|
+
fields
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Returns true when a related text group contains a component label.
|
|
185
|
+
* @param {{ fields: Record<string, string | string[]> }[]} records Text records.
|
|
186
|
+
* @returns {boolean}
|
|
187
|
+
*/
|
|
188
|
+
static #hasComponentLabelText(records) {
|
|
189
|
+
return records.some((record) =>
|
|
190
|
+
SchematicComponentOwnerTextResolver.#isComponentLabelRecord(
|
|
191
|
+
record.fields
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Returns true when one text record is a component designator or comment.
|
|
198
|
+
* @param {Record<string, string | string[]>} fields Raw record fields.
|
|
199
|
+
* @returns {boolean}
|
|
200
|
+
*/
|
|
201
|
+
static #isComponentLabelRecord(fields) {
|
|
202
|
+
const name = getField(fields, 'Name').trim().toLowerCase()
|
|
203
|
+
|
|
204
|
+
return name === 'designator' || name === 'comment' || name === 'value'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Returns true when one owner record is plausibly part of a component's
|
|
209
|
+
* display group.
|
|
210
|
+
* @param {Record<string, string | string[]>} fields Raw record fields.
|
|
211
|
+
* @param {{ fields: Record<string, string | string[]> }} componentRecord Component record.
|
|
212
|
+
* @returns {boolean}
|
|
213
|
+
*/
|
|
214
|
+
static #isNearComponent(fields, componentRecord) {
|
|
215
|
+
const componentX = parseNumericField(
|
|
216
|
+
componentRecord?.fields,
|
|
217
|
+
'Location.X'
|
|
218
|
+
)
|
|
219
|
+
const componentY = parseNumericField(
|
|
220
|
+
componentRecord?.fields,
|
|
221
|
+
'Location.Y'
|
|
222
|
+
)
|
|
223
|
+
const bounds = SchematicComponentOwnerTextResolver.#recordBounds(fields)
|
|
224
|
+
|
|
225
|
+
if (componentX === null || componentY === null || !bounds) {
|
|
226
|
+
return true
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
SchematicComponentOwnerTextResolver.#axisDistance(
|
|
231
|
+
componentX,
|
|
232
|
+
bounds.minX,
|
|
233
|
+
bounds.maxX
|
|
234
|
+
) <= COMPONENT_OWNER_AXIS_THRESHOLD &&
|
|
235
|
+
SchematicComponentOwnerTextResolver.#axisDistance(
|
|
236
|
+
componentY,
|
|
237
|
+
bounds.minY,
|
|
238
|
+
bounds.maxY
|
|
239
|
+
) <= COMPONENT_OWNER_AXIS_THRESHOLD
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Resolves a loose coordinate envelope for one schematic record.
|
|
245
|
+
* @param {Record<string, string | string[]>} fields Raw record fields.
|
|
246
|
+
* @returns {{ minX: number, maxX: number, minY: number, maxY: number } | null}
|
|
247
|
+
*/
|
|
248
|
+
static #recordBounds(fields) {
|
|
249
|
+
const points = [
|
|
250
|
+
{
|
|
251
|
+
x: parseNumericField(fields, 'Location.X'),
|
|
252
|
+
y: parseNumericField(fields, 'Location.Y')
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
x: parseNumericField(fields, 'Corner.X'),
|
|
256
|
+
y: parseNumericField(fields, 'Corner.Y')
|
|
257
|
+
}
|
|
258
|
+
].filter((point) => point.x !== null && point.y !== null)
|
|
259
|
+
|
|
260
|
+
if (!points.length) {
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
minX: Math.min(...points.map((point) => point.x)),
|
|
266
|
+
maxX: Math.max(...points.map((point) => point.x)),
|
|
267
|
+
minY: Math.min(...points.map((point) => point.y)),
|
|
268
|
+
maxY: Math.max(...points.map((point) => point.y))
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Measures how far a coordinate is from an inclusive axis interval.
|
|
274
|
+
* @param {number} value Coordinate value.
|
|
275
|
+
* @param {number} min Axis interval minimum.
|
|
276
|
+
* @param {number} max Axis interval maximum.
|
|
277
|
+
* @returns {number}
|
|
278
|
+
*/
|
|
279
|
+
static #axisDistance(value, min, max) {
|
|
280
|
+
if (value < min) return min - value
|
|
281
|
+
if (value > max) return value - max
|
|
282
|
+
return 0
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Removes duplicate and empty string values while preserving order.
|
|
287
|
+
* @param {string[]} values Candidate values.
|
|
288
|
+
* @returns {string[]}
|
|
289
|
+
*/
|
|
290
|
+
static #dedupeStrings(values) {
|
|
291
|
+
return [
|
|
292
|
+
...new Set(
|
|
293
|
+
values
|
|
294
|
+
.map((value) => String(value || '').trim())
|
|
295
|
+
.filter(Boolean)
|
|
296
|
+
)
|
|
297
|
+
]
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Returns true when both X and Y exist for a point prefix.
|
|
302
|
+
* @param {Record<string, string | string[]>} fields
|
|
303
|
+
* @param {string} prefix
|
|
304
|
+
* @returns {boolean}
|
|
305
|
+
*/
|
|
306
|
+
static #hasCoordinatePair(fields, prefix) {
|
|
307
|
+
return (
|
|
308
|
+
parseNumericField(fields, prefix + '.X') !== null &&
|
|
309
|
+
parseNumericField(fields, prefix + '.Y') !== null
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -15,19 +15,30 @@ export class SchematicComponentTextResolver {
|
|
|
15
15
|
* @param {{ fields: Record<string, string | string[]> }[]} ownerTexts
|
|
16
16
|
* @param {{ x: number, y: number, text: string, name: string }[]} texts
|
|
17
17
|
* @param {{ x: number, y: number, libReference: string }} component
|
|
18
|
-
* @returns {string}
|
|
18
|
+
* @returns {string | null}
|
|
19
19
|
*/
|
|
20
20
|
static resolveDesignator(ownerTexts, texts, component) {
|
|
21
|
-
const ownerDesignator =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const ownerDesignator =
|
|
22
|
+
SchematicComponentTextResolver.#findRelatedTextRecord(
|
|
23
|
+
ownerTexts,
|
|
24
|
+
'Designator'
|
|
25
|
+
)
|
|
25
26
|
if (
|
|
27
|
+
ownerDesignator.found &&
|
|
26
28
|
SchematicComponentTextResolver.#isResolvedComponentText(
|
|
27
|
-
ownerDesignator
|
|
29
|
+
ownerDesignator.text
|
|
30
|
+
)
|
|
31
|
+
) {
|
|
32
|
+
return ownerDesignator.text
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
ownerDesignator.found &&
|
|
37
|
+
SchematicComponentTextResolver.#isExplicitEmptyText(
|
|
38
|
+
ownerDesignator.text
|
|
28
39
|
)
|
|
29
40
|
) {
|
|
30
|
-
return
|
|
41
|
+
return ''
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
return SchematicComponentTextResolver.#findNearbyComponentDesignator(
|
|
@@ -45,15 +56,24 @@ export class SchematicComponentTextResolver {
|
|
|
45
56
|
*/
|
|
46
57
|
static resolveValue(ownerTexts, texts, component) {
|
|
47
58
|
const ownerValue =
|
|
48
|
-
SchematicComponentTextResolver.#
|
|
59
|
+
SchematicComponentTextResolver.#findFirstRelatedTextRecord(
|
|
49
60
|
ownerTexts,
|
|
50
|
-
'Comment'
|
|
51
|
-
)
|
|
52
|
-
|
|
61
|
+
['Comment', 'VALUE']
|
|
62
|
+
)
|
|
63
|
+
if (
|
|
64
|
+
ownerValue.found &&
|
|
65
|
+
SchematicComponentTextResolver.#isResolvedComponentText(
|
|
66
|
+
ownerValue.text
|
|
67
|
+
)
|
|
68
|
+
) {
|
|
69
|
+
return ownerValue.text
|
|
70
|
+
}
|
|
71
|
+
|
|
53
72
|
if (
|
|
54
|
-
|
|
73
|
+
ownerValue.found &&
|
|
74
|
+
SchematicComponentTextResolver.#isExplicitEmptyText(ownerValue.text)
|
|
55
75
|
) {
|
|
56
|
-
return
|
|
76
|
+
return ''
|
|
57
77
|
}
|
|
58
78
|
|
|
59
79
|
return (
|
|
@@ -65,7 +85,9 @@ export class SchematicComponentTextResolver {
|
|
|
65
85
|
SchematicComponentTextResolver.#inferComponentValueHint(
|
|
66
86
|
component.libReference
|
|
67
87
|
)
|
|
68
|
-
) ||
|
|
88
|
+
) ||
|
|
89
|
+
ownerValue.text ||
|
|
90
|
+
''
|
|
69
91
|
)
|
|
70
92
|
}
|
|
71
93
|
|
|
@@ -73,22 +95,44 @@ export class SchematicComponentTextResolver {
|
|
|
73
95
|
* Finds a related text value by name.
|
|
74
96
|
* @param {{ fields: Record<string, string | string[]> }[]} records
|
|
75
97
|
* @param {string} logicalName
|
|
76
|
-
* @returns {string}
|
|
98
|
+
* @returns {{ found: boolean, text: string }}
|
|
77
99
|
*/
|
|
78
|
-
static #
|
|
100
|
+
static #findRelatedTextRecord(records, logicalName) {
|
|
79
101
|
const match = records.find(
|
|
80
102
|
(record) =>
|
|
81
103
|
getField(record.fields, 'Name').toLowerCase() ===
|
|
82
104
|
logicalName.toLowerCase()
|
|
83
105
|
)
|
|
84
|
-
return match
|
|
106
|
+
return match
|
|
107
|
+
? { found: true, text: getDisplayText(match.fields) }
|
|
108
|
+
: { found: false, text: '' }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Finds the first related text value by logical name.
|
|
113
|
+
* @param {{ fields: Record<string, string | string[]> }[]} records
|
|
114
|
+
* @param {string[]} logicalNames Logical text names.
|
|
115
|
+
* @returns {{ found: boolean, text: string }}
|
|
116
|
+
*/
|
|
117
|
+
static #findFirstRelatedTextRecord(records, logicalNames) {
|
|
118
|
+
for (const logicalName of logicalNames) {
|
|
119
|
+
const match = SchematicComponentTextResolver.#findRelatedTextRecord(
|
|
120
|
+
records,
|
|
121
|
+
logicalName
|
|
122
|
+
)
|
|
123
|
+
if (match.found) {
|
|
124
|
+
return match
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { found: false, text: '' }
|
|
85
129
|
}
|
|
86
130
|
|
|
87
131
|
/**
|
|
88
132
|
* Finds the closest nearby designator text for one component.
|
|
89
133
|
* @param {{ x: number, y: number, text: string, name: string }[]} texts
|
|
90
134
|
* @param {{ x: number, y: number, libReference: string }} component
|
|
91
|
-
* @returns {string}
|
|
135
|
+
* @returns {string | null}
|
|
92
136
|
*/
|
|
93
137
|
static #findNearbyComponentDesignator(texts, component) {
|
|
94
138
|
const expectedPrefix =
|
|
@@ -128,7 +172,7 @@ export class SchematicComponentTextResolver {
|
|
|
128
172
|
}))
|
|
129
173
|
.sort((left, right) => left.score - right.score)
|
|
130
174
|
|
|
131
|
-
return rankedCandidates[0]?.text ||
|
|
175
|
+
return rankedCandidates[0]?.text || null
|
|
132
176
|
}
|
|
133
177
|
|
|
134
178
|
/**
|
|
@@ -268,6 +312,15 @@ export class SchematicComponentTextResolver {
|
|
|
268
312
|
)
|
|
269
313
|
}
|
|
270
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Returns true when owner-linked text intentionally contains no value.
|
|
317
|
+
* @param {string} value Text value.
|
|
318
|
+
* @returns {boolean}
|
|
319
|
+
*/
|
|
320
|
+
static #isExplicitEmptyText(value) {
|
|
321
|
+
return String(value ?? '') === ''
|
|
322
|
+
}
|
|
323
|
+
|
|
271
324
|
/**
|
|
272
325
|
* Infers the visible designator prefix from a library reference.
|
|
273
326
|
* @param {string} libReference
|