altium-toolkit 1.1.2 → 1.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/api.md +37 -0
- package/docs/model-format.md +18 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +2 -2
- package/docs/testing.md +5 -0
- package/package.json +1 -1
- package/spec/library-scope.md +5 -0
- package/src/core/altium/AltiumLayoutParser.mjs +275 -13
- package/src/core/altium/AltiumLibraryBatchExporter.mjs +206 -0
- package/src/core/altium/AltiumLibraryRecordBuilder.mjs +293 -0
- package/src/core/altium/AltiumParser.mjs +245 -10
- package/src/core/altium/AltiumPcbLibExporter.mjs +101 -0
- package/src/core/altium/AltiumSchLibExporter.mjs +57 -0
- package/src/core/altium/AsciiRecordParser.mjs +43 -11
- package/src/core/altium/PcbComponentKindPolicy.mjs +9 -9
- package/src/core/altium/PcbEmbeddedFontExtractor.mjs +186 -43
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +22 -3
- package/src/core/altium/PcbOutlineRecovery.mjs +94 -0
- package/src/core/altium/PrintableTextDecoder.mjs +133 -13
- package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +13 -0
- package/src/core/altium/SchematicComponentTextResolver.mjs +40 -1
- package/src/core/altium/SchematicDirectiveParser.mjs +5 -17
- package/src/core/altium/SchematicImageParser.mjs +291 -6
- package/src/core/altium/SchematicMultipartDesignatorNormalizer.mjs +164 -0
- package/src/core/altium/SchematicMultipartOwnerMatcher.mjs +2 -0
- package/src/core/altium/SchematicNoErcSymbolResolver.mjs +36 -0
- package/src/core/altium/SchematicPinParser.mjs +262 -24
- package/src/core/altium/SchematicPrimitiveParser.mjs +116 -8
- package/src/core/altium/SchematicSheetStyleResolver.mjs +38 -0
- package/src/core/altium/SchematicStreamExtractor.mjs +62 -15
- package/src/core/altium/SchematicTextParser.mjs +125 -11
- package/src/core/altium/SchematicTextPostProcessor.mjs +146 -102
- package/src/core/altium/SourceBundleExporter.mjs +156 -0
- package/src/core/altium/SourceComponentBundleNormalizer.mjs +295 -0
- package/src/core/altium/SourceComponentClient.mjs +239 -0
- package/src/core/ole/OleCompoundDocumentWriter.mjs +449 -0
- package/src/parser.mjs +8 -0
- package/src/styles/altium-renderers.css +6 -6
- package/src/ui/PcbArcUtils.mjs +19 -2
- package/src/ui/PcbScene3dBuilder.mjs +202 -20
- package/src/ui/PcbScene3dModelRegistry.mjs +28 -18
- package/src/ui/PcbScene3dPlacementSideResolver.mjs +48 -6
- package/src/ui/SchematicColorResolver.mjs +263 -0
- package/src/ui/SchematicContentLayout.mjs +58 -1
- package/src/ui/SchematicDirectiveRenderer.mjs +133 -22
- package/src/ui/SchematicImageRenderer.mjs +125 -10
- package/src/ui/SchematicJunctionRenderer.mjs +1 -1
- package/src/ui/SchematicLineColorResolver.mjs +88 -0
- package/src/ui/SchematicNativeFooterPartitioner.mjs +275 -0
- package/src/ui/SchematicNoteRenderer.mjs +87 -7
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +560 -10
- package/src/ui/SchematicOwnerPinMarkerLineThemer.mjs +155 -0
- package/src/ui/SchematicPinSvgRenderer.mjs +397 -48
- package/src/ui/SchematicPowerDiagramImageProcessor.mjs +970 -0
- package/src/ui/SchematicPowerDiagramLineMasks.mjs +631 -0
- package/src/ui/SchematicPowerPortRenderer.mjs +1 -1
- package/src/ui/SchematicShapeRenderer.mjs +109 -24
- package/src/ui/SchematicSvgRenderer.mjs +1210 -71
|
@@ -24,6 +24,8 @@ export class PcbOutlineRecovery {
|
|
|
24
24
|
|
|
25
25
|
static #MAX_DIRECT_RENDER_ARC_SWEEP_DEGREES = 120
|
|
26
26
|
|
|
27
|
+
static #MAX_MECHANICAL_FRAME_TO_AUTHORED_AREA_RATIO = 16
|
|
28
|
+
|
|
27
29
|
/**
|
|
28
30
|
* Selects a recoverable board outline from mechanical track layers.
|
|
29
31
|
* @param {{ fallbackOutline: { minX: number, minY: number, widthMil: number, heightMil: number, segments: Array<Record<string, number | string>> }, components: { x: number, y: number }[], tracks: { x1: number, y1: number, x2: number, y2: number, width: number, layerId?: number }[] }} options
|
|
@@ -76,6 +78,20 @@ export class PcbOutlineRecovery {
|
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
if (
|
|
82
|
+
PcbOutlineRecovery.#shouldKeepAuthoredOutline(
|
|
83
|
+
fallbackOutline,
|
|
84
|
+
boundaryLayer.bounds,
|
|
85
|
+
componentBounds
|
|
86
|
+
)
|
|
87
|
+
) {
|
|
88
|
+
return {
|
|
89
|
+
source: 'fallback',
|
|
90
|
+
layerId: null,
|
|
91
|
+
outline: fallbackOutline
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
79
95
|
const recoveredOutline =
|
|
80
96
|
PcbOutlineRecovery.#traceTrackOutline(
|
|
81
97
|
boundaryLayer.tracks,
|
|
@@ -166,6 +182,84 @@ export class PcbOutlineRecovery {
|
|
|
166
182
|
return candidates[0] || null
|
|
167
183
|
}
|
|
168
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Returns true when the mechanical candidate is likely a drawing frame
|
|
187
|
+
* around an otherwise plausible authored board route.
|
|
188
|
+
* @param {{ minX: number, minY: number, widthMil: number, heightMil: number, segments?: Array<Record<string, number | string>> }} authoredOutline Authored board-route outline.
|
|
189
|
+
* @param {{ widthMil: number, heightMil: number }} mechanicalBounds Mechanical track-layer bounds.
|
|
190
|
+
* @param {{ centerX: number, centerY: number }} componentBounds Component placement envelope.
|
|
191
|
+
* @returns {boolean}
|
|
192
|
+
*/
|
|
193
|
+
static #shouldKeepAuthoredOutline(
|
|
194
|
+
authoredOutline,
|
|
195
|
+
mechanicalBounds,
|
|
196
|
+
componentBounds
|
|
197
|
+
) {
|
|
198
|
+
if (
|
|
199
|
+
!PcbOutlineRecovery.#isClosedOutlinePath(
|
|
200
|
+
authoredOutline?.segments || []
|
|
201
|
+
)
|
|
202
|
+
) {
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const authoredArea =
|
|
207
|
+
Number(authoredOutline?.widthMil || 0) *
|
|
208
|
+
Number(authoredOutline?.heightMil || 0)
|
|
209
|
+
const mechanicalArea =
|
|
210
|
+
Number(mechanicalBounds?.widthMil || 0) *
|
|
211
|
+
Number(mechanicalBounds?.heightMil || 0)
|
|
212
|
+
|
|
213
|
+
if (!authoredArea || !mechanicalArea) {
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (
|
|
218
|
+
mechanicalArea / authoredArea <=
|
|
219
|
+
PcbOutlineRecovery.#MAX_MECHANICAL_FRAME_TO_AUTHORED_AREA_RATIO
|
|
220
|
+
) {
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return PcbOutlineRecovery.#outlineEnvelopeContainsPoint(
|
|
225
|
+
authoredOutline,
|
|
226
|
+
componentBounds?.centerX,
|
|
227
|
+
componentBounds?.centerY
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Returns true when one point falls within an outline's bounding envelope.
|
|
233
|
+
* @param {{ minX?: number, minY?: number, widthMil?: number, heightMil?: number }} outline Outline bounds.
|
|
234
|
+
* @param {number} x Point X coordinate.
|
|
235
|
+
* @param {number} y Point Y coordinate.
|
|
236
|
+
* @returns {boolean}
|
|
237
|
+
*/
|
|
238
|
+
static #outlineEnvelopeContainsPoint(outline, x, y) {
|
|
239
|
+
const minX = Number(outline?.minX)
|
|
240
|
+
const minY = Number(outline?.minY)
|
|
241
|
+
const widthMil = Number(outline?.widthMil)
|
|
242
|
+
const heightMil = Number(outline?.heightMil)
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
!Number.isFinite(minX) ||
|
|
246
|
+
!Number.isFinite(minY) ||
|
|
247
|
+
!Number.isFinite(widthMil) ||
|
|
248
|
+
!Number.isFinite(heightMil) ||
|
|
249
|
+
!Number.isFinite(x) ||
|
|
250
|
+
!Number.isFinite(y)
|
|
251
|
+
) {
|
|
252
|
+
return false
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
x >= minX &&
|
|
257
|
+
x <= minX + widthMil &&
|
|
258
|
+
y >= minY &&
|
|
259
|
+
y <= minY + heightMil
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
169
263
|
/**
|
|
170
264
|
* Builds one track-bounds envelope.
|
|
171
265
|
* @param {{ x1: number, y1: number, x2: number, y2: number }[]} tracks
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* Extracts long printable runs from binary Altium documents.
|
|
7
7
|
*/
|
|
8
8
|
export class PrintableTextDecoder {
|
|
9
|
+
static #decoderCache = new Map()
|
|
10
|
+
static #decoderConstructor = null
|
|
11
|
+
|
|
9
12
|
static #WINDOWS_1252_PRINTABLE_CONTROL_BYTES = new Set([
|
|
10
13
|
0x80, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c,
|
|
11
14
|
0x8e, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
|
|
@@ -115,7 +118,7 @@ export class PrintableTextDecoder {
|
|
|
115
118
|
if (preferredEncoding === 'utf-8') {
|
|
116
119
|
return (
|
|
117
120
|
PrintableTextDecoder.#tryDecode(bytes, 'utf-8') ||
|
|
118
|
-
|
|
121
|
+
PrintableTextDecoder.#decode(bytes, 'utf-8')
|
|
119
122
|
)
|
|
120
123
|
}
|
|
121
124
|
if (
|
|
@@ -124,7 +127,7 @@ export class PrintableTextDecoder {
|
|
|
124
127
|
) {
|
|
125
128
|
return (
|
|
126
129
|
PrintableTextDecoder.#tryDecodeWindows1252(bytes) ||
|
|
127
|
-
|
|
130
|
+
PrintableTextDecoder.#decode(bytes, 'utf-8')
|
|
128
131
|
)
|
|
129
132
|
}
|
|
130
133
|
|
|
@@ -144,7 +147,7 @@ export class PrintableTextDecoder {
|
|
|
144
147
|
return (
|
|
145
148
|
PrintableTextDecoder.#tryDecode(bytes, 'gb18030') ||
|
|
146
149
|
PrintableTextDecoder.#tryDecodeWindows1252(bytes) ||
|
|
147
|
-
|
|
150
|
+
PrintableTextDecoder.#decode(bytes, 'utf-8')
|
|
148
151
|
)
|
|
149
152
|
}
|
|
150
153
|
|
|
@@ -157,18 +160,25 @@ export class PrintableTextDecoder {
|
|
|
157
160
|
* @param {number} minLength
|
|
158
161
|
*/
|
|
159
162
|
static #pushRunBytes(runs, bytes, start, end, minLength) {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const normalized = PrintableTextDecoder.#normalizeRun(
|
|
165
|
-
PrintableTextDecoder.decodeBytes(slice)
|
|
163
|
+
const bounds = PrintableTextDecoder.#trimAsciiByteRange(
|
|
164
|
+
bytes,
|
|
165
|
+
start,
|
|
166
|
+
end
|
|
166
167
|
)
|
|
168
|
+
const length = bounds.end - bounds.start
|
|
169
|
+
if (length < minLength) return
|
|
167
170
|
|
|
168
|
-
if (
|
|
169
|
-
|
|
171
|
+
if (
|
|
172
|
+
!PrintableTextDecoder.#containsRecordDelimiterBytes(
|
|
173
|
+
bytes,
|
|
174
|
+
bounds.start,
|
|
175
|
+
bounds.end
|
|
176
|
+
)
|
|
177
|
+
) {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
170
180
|
|
|
171
|
-
runs.push(slice)
|
|
181
|
+
runs.push(bytes.slice(start, end))
|
|
172
182
|
}
|
|
173
183
|
|
|
174
184
|
/**
|
|
@@ -218,6 +228,72 @@ export class PrintableTextDecoder {
|
|
|
218
228
|
return false
|
|
219
229
|
}
|
|
220
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Trims ASCII whitespace from one byte range.
|
|
233
|
+
* @param {Uint8Array} bytes
|
|
234
|
+
* @param {number} start
|
|
235
|
+
* @param {number} end
|
|
236
|
+
* @returns {{ start: number, end: number }}
|
|
237
|
+
*/
|
|
238
|
+
static #trimAsciiByteRange(bytes, start, end) {
|
|
239
|
+
let trimmedStart = start
|
|
240
|
+
let trimmedEnd = end
|
|
241
|
+
|
|
242
|
+
while (
|
|
243
|
+
trimmedStart < trimmedEnd &&
|
|
244
|
+
PrintableTextDecoder.#isAsciiWhitespaceByte(bytes[trimmedStart])
|
|
245
|
+
) {
|
|
246
|
+
trimmedStart += 1
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
while (
|
|
250
|
+
trimmedEnd > trimmedStart &&
|
|
251
|
+
PrintableTextDecoder.#isAsciiWhitespaceByte(bytes[trimmedEnd - 1])
|
|
252
|
+
) {
|
|
253
|
+
trimmedEnd -= 1
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
start: trimmedStart,
|
|
258
|
+
end: trimmedEnd
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Returns true when one byte range contains Altium record delimiters.
|
|
264
|
+
* @param {Uint8Array} bytes
|
|
265
|
+
* @param {number} start
|
|
266
|
+
* @param {number} end
|
|
267
|
+
* @returns {boolean}
|
|
268
|
+
*/
|
|
269
|
+
static #containsRecordDelimiterBytes(bytes, start, end) {
|
|
270
|
+
let hasPipe = false
|
|
271
|
+
let hasEquals = false
|
|
272
|
+
|
|
273
|
+
for (let index = start; index < end; index += 1) {
|
|
274
|
+
if (bytes[index] === 0x7c) {
|
|
275
|
+
hasPipe = true
|
|
276
|
+
} else if (bytes[index] === 0x3d) {
|
|
277
|
+
hasEquals = true
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (hasPipe && hasEquals) {
|
|
281
|
+
return true
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return false
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Returns true when a byte is ASCII whitespace normalized around runs.
|
|
290
|
+
* @param {number} byte
|
|
291
|
+
* @returns {boolean}
|
|
292
|
+
*/
|
|
293
|
+
static #isAsciiWhitespaceByte(byte) {
|
|
294
|
+
return byte === 9 || byte === 10 || byte === 13 || byte === 32
|
|
295
|
+
}
|
|
296
|
+
|
|
221
297
|
/**
|
|
222
298
|
* Tries one strict decode and returns null when bytes are invalid for it.
|
|
223
299
|
* @param {Uint8Array} bytes
|
|
@@ -226,12 +302,56 @@ export class PrintableTextDecoder {
|
|
|
226
302
|
*/
|
|
227
303
|
static #tryDecode(bytes, encoding) {
|
|
228
304
|
try {
|
|
229
|
-
return
|
|
305
|
+
return PrintableTextDecoder.#decode(bytes, encoding, {
|
|
306
|
+
fatal: true
|
|
307
|
+
})
|
|
230
308
|
} catch {
|
|
231
309
|
return null
|
|
232
310
|
}
|
|
233
311
|
}
|
|
234
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Decodes one byte slice with a cached runtime decoder.
|
|
315
|
+
* @param {Uint8Array} bytes
|
|
316
|
+
* @param {string} encoding
|
|
317
|
+
* @param {{ fatal?: boolean }} [options]
|
|
318
|
+
* @returns {string}
|
|
319
|
+
*/
|
|
320
|
+
static #decode(bytes, encoding, options = {}) {
|
|
321
|
+
return PrintableTextDecoder.#getTextDecoder(encoding, options).decode(
|
|
322
|
+
bytes
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Resolves a cached TextDecoder for one encoding and fatal mode.
|
|
328
|
+
* @param {string} encoding
|
|
329
|
+
* @param {{ fatal?: boolean }} options
|
|
330
|
+
* @returns {TextDecoder}
|
|
331
|
+
*/
|
|
332
|
+
static #getTextDecoder(encoding, options) {
|
|
333
|
+
const Decoder = globalThis.TextDecoder
|
|
334
|
+
if (PrintableTextDecoder.#decoderConstructor !== Decoder) {
|
|
335
|
+
PrintableTextDecoder.#decoderCache = new Map()
|
|
336
|
+
PrintableTextDecoder.#decoderConstructor = Decoder
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const normalizedEncoding = String(encoding || 'utf-8').toLowerCase()
|
|
340
|
+
const fatal = Boolean(options?.fatal)
|
|
341
|
+
const cacheKey = `${normalizedEncoding}:${fatal ? 'fatal' : 'replace'}`
|
|
342
|
+
const cached = PrintableTextDecoder.#decoderCache.get(cacheKey)
|
|
343
|
+
if (cached) {
|
|
344
|
+
return cached
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const decoder = new Decoder(
|
|
348
|
+
normalizedEncoding,
|
|
349
|
+
fatal ? { fatal: true } : {}
|
|
350
|
+
)
|
|
351
|
+
PrintableTextDecoder.#decoderCache.set(cacheKey, decoder)
|
|
352
|
+
return decoder
|
|
353
|
+
}
|
|
354
|
+
|
|
235
355
|
/**
|
|
236
356
|
* Tries a Windows-1252 decode and normalizes runtimes that expose C1 bytes
|
|
237
357
|
* as control characters instead of punctuation.
|
|
@@ -66,6 +66,19 @@ export class SchematicComponentOwnerTextResolver {
|
|
|
66
66
|
)
|
|
67
67
|
}
|
|
68
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.#resolveOwnerIndexes(
|
|
77
|
+
componentRecord,
|
|
78
|
+
records
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
69
82
|
/**
|
|
70
83
|
* Resolves candidate owner indexes for one schematic component record.
|
|
71
84
|
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }} componentRecord Component placement record.
|
|
@@ -56,7 +56,7 @@ export class SchematicComponentTextResolver {
|
|
|
56
56
|
*/
|
|
57
57
|
static resolveValue(ownerTexts, texts, component) {
|
|
58
58
|
const ownerValue =
|
|
59
|
-
SchematicComponentTextResolver.#
|
|
59
|
+
SchematicComponentTextResolver.#findFirstUsableRelatedTextRecord(
|
|
60
60
|
ownerTexts,
|
|
61
61
|
['Comment', 'VALUE']
|
|
62
62
|
)
|
|
@@ -128,6 +128,45 @@ export class SchematicComponentTextResolver {
|
|
|
128
128
|
return { found: false, text: '' }
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Finds the first owner text that is resolved or explicitly empty, while
|
|
133
|
+
* allowing unresolved templates to fall through to later owner parameters.
|
|
134
|
+
* @param {{ fields: Record<string, string | string[]> }[]} records
|
|
135
|
+
* @param {string[]} logicalNames Logical text names.
|
|
136
|
+
* @returns {{ found: boolean, text: string }}
|
|
137
|
+
*/
|
|
138
|
+
static #findFirstUsableRelatedTextRecord(records, logicalNames) {
|
|
139
|
+
let unresolvedMatch = { found: false, text: '' }
|
|
140
|
+
|
|
141
|
+
for (const logicalName of logicalNames) {
|
|
142
|
+
const match = SchematicComponentTextResolver.#findRelatedTextRecord(
|
|
143
|
+
records,
|
|
144
|
+
logicalName
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if (!match.found) {
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
SchematicComponentTextResolver.#isExplicitEmptyText(
|
|
153
|
+
match.text
|
|
154
|
+
) ||
|
|
155
|
+
SchematicComponentTextResolver.#isResolvedComponentText(
|
|
156
|
+
match.text
|
|
157
|
+
)
|
|
158
|
+
) {
|
|
159
|
+
return match
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!unresolvedMatch.found) {
|
|
163
|
+
unresolvedMatch = match
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return unresolvedMatch
|
|
168
|
+
}
|
|
169
|
+
|
|
131
170
|
/**
|
|
132
171
|
* Finds the closest nearby designator text for one component.
|
|
133
172
|
* @param {{ x: number, y: number, text: string, name: string }[]} texts
|
|
@@ -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 { SchematicNoErcSymbolResolver } from './SchematicNoErcSymbolResolver.mjs'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Helpers for normalized schematic directive primitives.
|
|
@@ -144,6 +145,7 @@ export class SchematicDirectiveParser {
|
|
|
144
145
|
const x = ParserUtils.parseNumericField(record.fields, 'Location.X')
|
|
145
146
|
const y = ParserUtils.parseNumericField(record.fields, 'Location.Y')
|
|
146
147
|
if (x === null || y === null) return null
|
|
148
|
+
const rawSymbol = ParserUtils.getField(record.fields, 'Symbol')
|
|
147
149
|
const symbol = ParserUtils.parseNumericField(record.fields, 'Symbol')
|
|
148
150
|
|
|
149
151
|
return {
|
|
@@ -160,7 +162,9 @@ export class SchematicDirectiveParser {
|
|
|
160
162
|
ParserUtils.parseNumericField(record.fields, 'Orientation') ||
|
|
161
163
|
0,
|
|
162
164
|
symbol,
|
|
163
|
-
symbolName:
|
|
165
|
+
symbolName: SchematicNoErcSymbolResolver.resolveSymbolName(
|
|
166
|
+
rawSymbol || symbol
|
|
167
|
+
)
|
|
164
168
|
}
|
|
165
169
|
}
|
|
166
170
|
|
|
@@ -332,22 +336,6 @@ export class SchematicDirectiveParser {
|
|
|
332
336
|
return 'record-' + String(record.recordIndex ?? 0)
|
|
333
337
|
}
|
|
334
338
|
|
|
335
|
-
/**
|
|
336
|
-
* Converts common No ERC symbol ids into public labels.
|
|
337
|
-
* @param {number | null} symbol Symbol id.
|
|
338
|
-
* @returns {string}
|
|
339
|
-
*/
|
|
340
|
-
static #noErcSymbolName(symbol) {
|
|
341
|
-
return (
|
|
342
|
-
{
|
|
343
|
-
0: 'generic',
|
|
344
|
-
1: 'box',
|
|
345
|
-
2: 'cross',
|
|
346
|
-
3: 'triangle'
|
|
347
|
-
}[Number(symbol)] || 'unknown'
|
|
348
|
-
)
|
|
349
|
-
}
|
|
350
|
-
|
|
351
339
|
/**
|
|
352
340
|
* Parses one boolean-ish directive parameter value.
|
|
353
341
|
* @param {unknown} value Raw parameter value.
|