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,271 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds deterministic read-only schematic connectivity QA findings.
|
|
7
|
+
*/
|
|
8
|
+
export class SchematicConnectivityQaBuilder {
|
|
9
|
+
static SCHEMA_ID = 'altium-toolkit.schematic.connectivity-qa.a1'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builds connectivity QA from the normalized single-sheet net model.
|
|
13
|
+
* @param {{ nets?: object[], texts?: object[], pins?: object[], ports?: object[], junctions?: object[] }} schematic Normalized schematic fragments.
|
|
14
|
+
* @returns {object}
|
|
15
|
+
*/
|
|
16
|
+
static build(schematic) {
|
|
17
|
+
const nets = Array.isArray(schematic?.nets) ? schematic.nets : []
|
|
18
|
+
const labels = (schematic?.texts || []).filter(
|
|
19
|
+
(text) => text.recordType === '25'
|
|
20
|
+
)
|
|
21
|
+
const ports = Array.isArray(schematic?.ports) ? schematic.ports : []
|
|
22
|
+
const pins = Array.isArray(schematic?.pins) ? schematic.pins : []
|
|
23
|
+
const junctions = Array.isArray(schematic?.junctions)
|
|
24
|
+
? schematic.junctions
|
|
25
|
+
: []
|
|
26
|
+
const findings = [
|
|
27
|
+
...SchematicConnectivityQaBuilder.#implicitNetFindings(nets),
|
|
28
|
+
...SchematicConnectivityQaBuilder.#danglingLabelFindings(
|
|
29
|
+
labels,
|
|
30
|
+
nets
|
|
31
|
+
),
|
|
32
|
+
...SchematicConnectivityQaBuilder.#orphanPortFindings(ports, nets),
|
|
33
|
+
...SchematicConnectivityQaBuilder.#unconnectedPinFindings(
|
|
34
|
+
pins,
|
|
35
|
+
nets
|
|
36
|
+
),
|
|
37
|
+
...SchematicConnectivityQaBuilder.#ambiguousJunctionFindings(
|
|
38
|
+
junctions,
|
|
39
|
+
nets
|
|
40
|
+
)
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
schema: SchematicConnectivityQaBuilder.SCHEMA_ID,
|
|
45
|
+
summary: SchematicConnectivityQaBuilder.#summary(nets, findings),
|
|
46
|
+
findings
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Reports nets that received a synthesized name.
|
|
52
|
+
* @param {object[]} nets Normalized nets.
|
|
53
|
+
* @returns {object[]}
|
|
54
|
+
*/
|
|
55
|
+
static #implicitNetFindings(nets) {
|
|
56
|
+
return nets
|
|
57
|
+
.filter((net) => /^UnknownNet\d+$/u.test(String(net.name || '')))
|
|
58
|
+
.map((net) => ({
|
|
59
|
+
code: 'schematic.connectivity.implicit-net-name',
|
|
60
|
+
severity: 'info',
|
|
61
|
+
netName: net.name,
|
|
62
|
+
segmentCount: (net.segments || []).length
|
|
63
|
+
}))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reports wire labels not assigned to any net.
|
|
68
|
+
* @param {object[]} labels Wire labels.
|
|
69
|
+
* @param {object[]} nets Normalized nets.
|
|
70
|
+
* @returns {object[]}
|
|
71
|
+
*/
|
|
72
|
+
static #danglingLabelFindings(labels, nets) {
|
|
73
|
+
return labels
|
|
74
|
+
.filter(
|
|
75
|
+
(label) =>
|
|
76
|
+
!nets.some((net) =>
|
|
77
|
+
(net.labels || []).some((netLabel) =>
|
|
78
|
+
SchematicConnectivityQaBuilder.#sameTextPoint(
|
|
79
|
+
label,
|
|
80
|
+
netLabel
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
.map((label) => ({
|
|
86
|
+
code: 'schematic.connectivity.dangling-label',
|
|
87
|
+
severity: 'warning',
|
|
88
|
+
text: label.text,
|
|
89
|
+
x: label.x,
|
|
90
|
+
y: label.y
|
|
91
|
+
}))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Reports ports not assigned to any net.
|
|
96
|
+
* @param {object[]} ports Ports.
|
|
97
|
+
* @param {object[]} nets Normalized nets.
|
|
98
|
+
* @returns {object[]}
|
|
99
|
+
*/
|
|
100
|
+
static #orphanPortFindings(ports, nets) {
|
|
101
|
+
return ports
|
|
102
|
+
.filter(
|
|
103
|
+
(port) =>
|
|
104
|
+
!nets.some((net) =>
|
|
105
|
+
(net.ports || []).some((netPort) =>
|
|
106
|
+
SchematicConnectivityQaBuilder.#sameNamedPoint(
|
|
107
|
+
port,
|
|
108
|
+
netPort
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
.map((port) => ({
|
|
114
|
+
code: 'schematic.connectivity.orphan-port',
|
|
115
|
+
severity: 'warning',
|
|
116
|
+
name: port.name,
|
|
117
|
+
x: port.x,
|
|
118
|
+
y: port.y
|
|
119
|
+
}))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Reports pins not assigned to any net.
|
|
124
|
+
* @param {object[]} pins Pins.
|
|
125
|
+
* @param {object[]} nets Normalized nets.
|
|
126
|
+
* @returns {object[]}
|
|
127
|
+
*/
|
|
128
|
+
static #unconnectedPinFindings(pins, nets) {
|
|
129
|
+
return pins
|
|
130
|
+
.filter(
|
|
131
|
+
(pin) =>
|
|
132
|
+
!nets.some((net) =>
|
|
133
|
+
(net.pins || []).some((netPin) =>
|
|
134
|
+
SchematicConnectivityQaBuilder.#samePin(pin, netPin)
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
.map((pin) => ({
|
|
139
|
+
code: 'schematic.connectivity.unconnected-pin',
|
|
140
|
+
severity: 'warning',
|
|
141
|
+
ownerIndex: pin.ownerIndex,
|
|
142
|
+
designator: pin.designator,
|
|
143
|
+
name: pin.name,
|
|
144
|
+
x: pin.x,
|
|
145
|
+
y: pin.y
|
|
146
|
+
}))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Reports authored junctions that do not participate in any net.
|
|
151
|
+
* @param {object[]} junctions Authored junctions.
|
|
152
|
+
* @param {object[]} nets Normalized nets.
|
|
153
|
+
* @returns {object[]}
|
|
154
|
+
*/
|
|
155
|
+
static #ambiguousJunctionFindings(junctions, nets) {
|
|
156
|
+
return junctions
|
|
157
|
+
.filter(
|
|
158
|
+
(junction) =>
|
|
159
|
+
!nets.some((net) =>
|
|
160
|
+
(net.junctions || []).some((netJunction) =>
|
|
161
|
+
SchematicConnectivityQaBuilder.#samePoint(
|
|
162
|
+
junction,
|
|
163
|
+
netJunction
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
.map((junction) => ({
|
|
169
|
+
code: 'schematic.connectivity.ambiguous-junction',
|
|
170
|
+
severity: 'warning',
|
|
171
|
+
x: junction.x,
|
|
172
|
+
y: junction.y
|
|
173
|
+
}))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Builds finding counters.
|
|
178
|
+
* @param {object[]} nets Normalized nets.
|
|
179
|
+
* @param {object[]} findings QA findings.
|
|
180
|
+
* @returns {object}
|
|
181
|
+
*/
|
|
182
|
+
static #summary(nets, findings) {
|
|
183
|
+
return {
|
|
184
|
+
netCount: nets.length,
|
|
185
|
+
findingCount: findings.length,
|
|
186
|
+
danglingLabelCount: SchematicConnectivityQaBuilder.#countCode(
|
|
187
|
+
findings,
|
|
188
|
+
'schematic.connectivity.dangling-label'
|
|
189
|
+
),
|
|
190
|
+
orphanPortCount: SchematicConnectivityQaBuilder.#countCode(
|
|
191
|
+
findings,
|
|
192
|
+
'schematic.connectivity.orphan-port'
|
|
193
|
+
),
|
|
194
|
+
unconnectedPinCount: SchematicConnectivityQaBuilder.#countCode(
|
|
195
|
+
findings,
|
|
196
|
+
'schematic.connectivity.unconnected-pin'
|
|
197
|
+
),
|
|
198
|
+
implicitNetCount: SchematicConnectivityQaBuilder.#countCode(
|
|
199
|
+
findings,
|
|
200
|
+
'schematic.connectivity.implicit-net-name'
|
|
201
|
+
),
|
|
202
|
+
ambiguousJunctionCount: SchematicConnectivityQaBuilder.#countCode(
|
|
203
|
+
findings,
|
|
204
|
+
'schematic.connectivity.ambiguous-junction'
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Counts findings with one code.
|
|
211
|
+
* @param {object[]} findings QA findings.
|
|
212
|
+
* @param {string} code Diagnostic code.
|
|
213
|
+
* @returns {number}
|
|
214
|
+
*/
|
|
215
|
+
static #countCode(findings, code) {
|
|
216
|
+
return findings.filter((finding) => finding.code === code).length
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Compares text rows by text and point.
|
|
221
|
+
* @param {object} left First row.
|
|
222
|
+
* @param {object} right Second row.
|
|
223
|
+
* @returns {boolean}
|
|
224
|
+
*/
|
|
225
|
+
static #sameTextPoint(left, right) {
|
|
226
|
+
return (
|
|
227
|
+
String(left.text || '') === String(right.text || '') &&
|
|
228
|
+
SchematicConnectivityQaBuilder.#samePoint(left, right)
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Compares named rows by name and point.
|
|
234
|
+
* @param {object} left First row.
|
|
235
|
+
* @param {object} right Second row.
|
|
236
|
+
* @returns {boolean}
|
|
237
|
+
*/
|
|
238
|
+
static #sameNamedPoint(left, right) {
|
|
239
|
+
return (
|
|
240
|
+
String(left.name || '') === String(right.name || '') &&
|
|
241
|
+
SchematicConnectivityQaBuilder.#samePoint(left, right)
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Compares pin rows by identity and point.
|
|
247
|
+
* @param {object} left First pin.
|
|
248
|
+
* @param {object} right Second pin.
|
|
249
|
+
* @returns {boolean}
|
|
250
|
+
*/
|
|
251
|
+
static #samePin(left, right) {
|
|
252
|
+
return (
|
|
253
|
+
String(left.ownerIndex || '') === String(right.ownerIndex || '') &&
|
|
254
|
+
String(left.designator || '') === String(right.designator || '') &&
|
|
255
|
+
SchematicConnectivityQaBuilder.#samePoint(left, right)
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Compares points with a small parser-tolerance.
|
|
261
|
+
* @param {object} left First point.
|
|
262
|
+
* @param {object} right Second point.
|
|
263
|
+
* @returns {boolean}
|
|
264
|
+
*/
|
|
265
|
+
static #samePoint(left, right) {
|
|
266
|
+
return (
|
|
267
|
+
Math.abs(Number(left.x) - Number(right.x)) <= 0.01 &&
|
|
268
|
+
Math.abs(Number(left.y) - Number(right.y)) <= 0.01
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
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, getField, parseBoolean, parseNumericField, toColor } =
|
|
8
|
+
ParserUtils
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parses cross-sheet connector records into a first-class schematic sidecar.
|
|
12
|
+
*/
|
|
13
|
+
export class SchematicCrossSheetConnectorParser {
|
|
14
|
+
static SCHEMA_ID = 'altium-toolkit.schematic.cross-sheet-connectors.a1'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parses cross-sheet connector records.
|
|
18
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }[]} records Schematic records.
|
|
19
|
+
* @returns {object | null}
|
|
20
|
+
*/
|
|
21
|
+
static parse(records) {
|
|
22
|
+
const connectors = (records || [])
|
|
23
|
+
.filter((record) =>
|
|
24
|
+
SchematicCrossSheetConnectorParser.#isCrossSheetConnector(
|
|
25
|
+
record
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
.map((record) =>
|
|
29
|
+
SchematicCrossSheetConnectorParser.#connector(record)
|
|
30
|
+
)
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
|
|
33
|
+
if (!connectors.length) {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
schema: SchematicCrossSheetConnectorParser.SCHEMA_ID,
|
|
39
|
+
connectors
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns true when a record is tagged as a cross-sheet connector.
|
|
45
|
+
* @param {object} record Schematic record.
|
|
46
|
+
* @returns {boolean}
|
|
47
|
+
*/
|
|
48
|
+
static #isCrossSheetConnector(record) {
|
|
49
|
+
if (getField(record?.fields, 'RECORD') !== '17') {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
parseBoolean(record.fields.IsCrossSheetConnector) ||
|
|
55
|
+
parseBoolean(record.fields.CrossSheetConnector) ||
|
|
56
|
+
parseBoolean(record.fields.ISCROSSSHEETCONNECTOR) ||
|
|
57
|
+
parseBoolean(record.fields.IsOffSheetConnector)
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parses one cross-sheet connector record.
|
|
63
|
+
* @param {object} record Schematic record.
|
|
64
|
+
* @returns {object | null}
|
|
65
|
+
*/
|
|
66
|
+
static #connector(record) {
|
|
67
|
+
const x = parseNumericField(record.fields, 'Location.X')
|
|
68
|
+
const y = parseNumericField(record.fields, 'Location.Y')
|
|
69
|
+
|
|
70
|
+
if (x === null || y === null) {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const indexInSheet =
|
|
75
|
+
parseNumericField(record.fields, 'IndexInSheet') ??
|
|
76
|
+
record.recordIndex ??
|
|
77
|
+
0
|
|
78
|
+
const name =
|
|
79
|
+
getDisplayText(record.fields) || getField(record.fields, 'Name')
|
|
80
|
+
|
|
81
|
+
return SchematicCrossSheetConnectorParser.#stripEmpty({
|
|
82
|
+
key: 'cross-sheet-connector-' + String(indexInSheet),
|
|
83
|
+
recordKey: SchematicCrossSheetConnectorParser.#recordKey(record),
|
|
84
|
+
recordId: 'record-' + String(indexInSheet),
|
|
85
|
+
name,
|
|
86
|
+
x,
|
|
87
|
+
y,
|
|
88
|
+
style: SchematicCrossSheetConnectorParser.#style(
|
|
89
|
+
parseNumericField(record.fields, 'Style') ??
|
|
90
|
+
parseNumericField(record.fields, 'Orientation') ??
|
|
91
|
+
0
|
|
92
|
+
),
|
|
93
|
+
color: toColor(record.fields.Color, '#000000')
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolves a connector style code.
|
|
99
|
+
* @param {number} value Style code.
|
|
100
|
+
* @returns {'left' | 'right' | 'top' | 'bottom'}
|
|
101
|
+
*/
|
|
102
|
+
static #style(value) {
|
|
103
|
+
switch (Number(value)) {
|
|
104
|
+
case 1:
|
|
105
|
+
return 'right'
|
|
106
|
+
case 2:
|
|
107
|
+
return 'top'
|
|
108
|
+
case 3:
|
|
109
|
+
return 'bottom'
|
|
110
|
+
case 0:
|
|
111
|
+
default:
|
|
112
|
+
return 'left'
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Builds a stable schematic record key.
|
|
118
|
+
* @param {object} record Schematic record.
|
|
119
|
+
* @returns {string}
|
|
120
|
+
*/
|
|
121
|
+
static #recordKey(record) {
|
|
122
|
+
return 'schematic-record-' + String(record?.recordIndex ?? 0)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Drops empty optional fields.
|
|
127
|
+
* @param {Record<string, unknown>} value Source object.
|
|
128
|
+
* @returns {Record<string, unknown>}
|
|
129
|
+
*/
|
|
130
|
+
static #stripEmpty(value) {
|
|
131
|
+
return Object.fromEntries(
|
|
132
|
+
Object.entries(value).filter(([, entry]) => {
|
|
133
|
+
if (Array.isArray(entry)) {
|
|
134
|
+
return entry.length > 0
|
|
135
|
+
}
|
|
136
|
+
return entry !== null && entry !== undefined && entry !== ''
|
|
137
|
+
})
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
}
|