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
|
@@ -22,6 +22,9 @@ export class SchematicPinSvgRenderer {
|
|
|
22
22
|
* @param {Set<string>} rotatedVerticalNumberOwners
|
|
23
23
|
* @param {Set<string>} explicitOwnerPinNameLabels
|
|
24
24
|
* @param {Map<string, number>} explicitOwnerPinLabelOffsets
|
|
25
|
+
* @param {Map<string, 'left' | 'right'>} compactExternalNumberLabelSides
|
|
26
|
+
* @param {Map<string, { left: number, right: number }>} internalNumberLabelBoxes
|
|
27
|
+
* @param {Set<string>} overlappingExternalNumberLabelKeys
|
|
25
28
|
* @returns {string}
|
|
26
29
|
*/
|
|
27
30
|
static buildMarkup(
|
|
@@ -30,7 +33,10 @@ export class SchematicPinSvgRenderer {
|
|
|
30
33
|
sheet,
|
|
31
34
|
rotatedVerticalNumberOwners,
|
|
32
35
|
explicitOwnerPinNameLabels,
|
|
33
|
-
explicitOwnerPinLabelOffsets
|
|
36
|
+
explicitOwnerPinLabelOffsets,
|
|
37
|
+
compactExternalNumberLabelSides = new Map(),
|
|
38
|
+
internalNumberLabelBoxes = new Map(),
|
|
39
|
+
overlappingExternalNumberLabelKeys = new Set()
|
|
34
40
|
) {
|
|
35
41
|
const geometry =
|
|
36
42
|
SchematicPinSvgRenderer.#projectSchematicPinGeometry(pin)
|
|
@@ -49,6 +55,11 @@ export class SchematicPinSvgRenderer {
|
|
|
49
55
|
const labelMode = pin.labelMode || 'name-and-number'
|
|
50
56
|
const outerMarkerStyle =
|
|
51
57
|
SchematicPinSvgRenderer.#resolveSchematicOuterPinMarkerStyle(pin)
|
|
58
|
+
const markerStyle =
|
|
59
|
+
outerMarkerStyle ||
|
|
60
|
+
SchematicPinSvgRenderer.#resolveSchematicElectricalPinMarkerStyle(
|
|
61
|
+
pin
|
|
62
|
+
)
|
|
52
63
|
const rotateTopNumber =
|
|
53
64
|
pin.orientation === 'top' &&
|
|
54
65
|
rotatedVerticalNumberOwners.has(String(pin.ownerIndex || ''))
|
|
@@ -60,14 +71,47 @@ export class SchematicPinSvgRenderer {
|
|
|
60
71
|
const hasExplicitOwnerPinName =
|
|
61
72
|
Boolean(pin.name) &&
|
|
62
73
|
explicitOwnerPinNameLabels.has(ownerPinLabelKey)
|
|
74
|
+
const compactExternalNumberLabelSide =
|
|
75
|
+
compactExternalNumberLabelSides.get(String(pin.ownerIndex || '')) ||
|
|
76
|
+
null
|
|
77
|
+
const internalNumberLabelBox =
|
|
78
|
+
internalNumberLabelBoxes.get(String(pin.ownerIndex || '')) || null
|
|
79
|
+
const shouldRenderExternalNumber =
|
|
80
|
+
!overlappingExternalNumberLabelKeys.has(
|
|
81
|
+
SchematicOwnerPinLabelLayout.buildOwnerPinLabelKey(
|
|
82
|
+
pin.ownerIndex,
|
|
83
|
+
pin.designator
|
|
84
|
+
)
|
|
85
|
+
)
|
|
63
86
|
|
|
64
87
|
if (pin.orientation === 'left') {
|
|
65
88
|
if (labelMode !== 'hidden' && labelMode !== 'name-only') {
|
|
89
|
+
const internalNumberPlacement =
|
|
90
|
+
SchematicPinSvgRenderer.#resolveInternalHorizontalNumberPlacement(
|
|
91
|
+
pin,
|
|
92
|
+
internalNumberLabelBox
|
|
93
|
+
)
|
|
94
|
+
if (internalNumberPlacement) {
|
|
95
|
+
texts.push(
|
|
96
|
+
SchematicPinSvgRenderer.#buildPinNameTextMarkup(
|
|
97
|
+
'schematic-pin-name',
|
|
98
|
+
internalNumberPlacement.x,
|
|
99
|
+
projectedY + 3,
|
|
100
|
+
pin,
|
|
101
|
+
labelColor,
|
|
102
|
+
internalNumberPlacement.anchor,
|
|
103
|
+
textOptions
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
66
108
|
const defaultNumberX =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
109
|
+
SchematicPinSvgRenderer.#resolveExternalHorizontalNumberX(
|
|
110
|
+
pin,
|
|
111
|
+
geometry,
|
|
112
|
+
markerStyle,
|
|
113
|
+
compactExternalNumberLabelSide,
|
|
114
|
+
internalNumberLabelBox
|
|
71
115
|
)
|
|
72
116
|
const numberX = hasExplicitOwnerPinName
|
|
73
117
|
? SchematicOwnerPinLabelLayout.resolveExplicitOwnerPinNumberX(
|
|
@@ -76,25 +120,31 @@ export class SchematicPinSvgRenderer {
|
|
|
76
120
|
explicitOwnerPinLabelOffsets
|
|
77
121
|
)
|
|
78
122
|
: defaultNumberX
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
123
|
+
if (shouldRenderExternalNumber) {
|
|
124
|
+
texts.push(
|
|
125
|
+
createSvgText(
|
|
126
|
+
'schematic-pin-number',
|
|
127
|
+
numberX,
|
|
128
|
+
projectedY - 1,
|
|
129
|
+
pin.designator,
|
|
130
|
+
labelColor,
|
|
131
|
+
SchematicPinSvgRenderer.#resolveExternalHorizontalNumberAnchor(
|
|
132
|
+
pin,
|
|
133
|
+
markerStyle
|
|
134
|
+
),
|
|
135
|
+
textOptions
|
|
136
|
+
)
|
|
88
137
|
)
|
|
89
|
-
|
|
138
|
+
}
|
|
90
139
|
}
|
|
91
140
|
|
|
92
141
|
if (
|
|
93
142
|
labelMode !== 'hidden' &&
|
|
94
143
|
labelMode !== 'number-only' &&
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
144
|
+
SchematicPinSvgRenderer.#shouldRenderHorizontalPinName(
|
|
145
|
+
pin,
|
|
146
|
+
hasExplicitOwnerPinName
|
|
147
|
+
)
|
|
98
148
|
) {
|
|
99
149
|
texts.push(
|
|
100
150
|
SchematicPinSvgRenderer.#buildPinNameTextMarkup(
|
|
@@ -116,11 +166,32 @@ export class SchematicPinSvgRenderer {
|
|
|
116
166
|
|
|
117
167
|
if (pin.orientation === 'right') {
|
|
118
168
|
if (labelMode !== 'hidden' && labelMode !== 'name-only') {
|
|
169
|
+
const internalNumberPlacement =
|
|
170
|
+
SchematicPinSvgRenderer.#resolveInternalHorizontalNumberPlacement(
|
|
171
|
+
pin,
|
|
172
|
+
internalNumberLabelBox
|
|
173
|
+
)
|
|
174
|
+
if (internalNumberPlacement) {
|
|
175
|
+
texts.push(
|
|
176
|
+
SchematicPinSvgRenderer.#buildPinNameTextMarkup(
|
|
177
|
+
'schematic-pin-name',
|
|
178
|
+
internalNumberPlacement.x,
|
|
179
|
+
projectedY + 3,
|
|
180
|
+
pin,
|
|
181
|
+
labelColor,
|
|
182
|
+
internalNumberPlacement.anchor,
|
|
183
|
+
textOptions
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
119
188
|
const defaultNumberX =
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
189
|
+
SchematicPinSvgRenderer.#resolveExternalHorizontalNumberX(
|
|
190
|
+
pin,
|
|
191
|
+
geometry,
|
|
192
|
+
markerStyle,
|
|
193
|
+
compactExternalNumberLabelSide,
|
|
194
|
+
internalNumberLabelBox
|
|
124
195
|
)
|
|
125
196
|
const numberX = hasExplicitOwnerPinName
|
|
126
197
|
? SchematicOwnerPinLabelLayout.resolveExplicitOwnerPinNumberX(
|
|
@@ -129,25 +200,31 @@ export class SchematicPinSvgRenderer {
|
|
|
129
200
|
explicitOwnerPinLabelOffsets
|
|
130
201
|
)
|
|
131
202
|
: defaultNumberX
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
203
|
+
if (shouldRenderExternalNumber) {
|
|
204
|
+
texts.push(
|
|
205
|
+
createSvgText(
|
|
206
|
+
'schematic-pin-number',
|
|
207
|
+
numberX,
|
|
208
|
+
projectedY - 1,
|
|
209
|
+
pin.designator,
|
|
210
|
+
labelColor,
|
|
211
|
+
SchematicPinSvgRenderer.#resolveExternalHorizontalNumberAnchor(
|
|
212
|
+
pin,
|
|
213
|
+
markerStyle
|
|
214
|
+
),
|
|
215
|
+
textOptions
|
|
216
|
+
)
|
|
141
217
|
)
|
|
142
|
-
|
|
218
|
+
}
|
|
143
219
|
}
|
|
144
220
|
|
|
145
221
|
if (
|
|
146
222
|
labelMode !== 'hidden' &&
|
|
147
223
|
labelMode !== 'number-only' &&
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
224
|
+
SchematicPinSvgRenderer.#shouldRenderHorizontalPinName(
|
|
225
|
+
pin,
|
|
226
|
+
hasExplicitOwnerPinName
|
|
227
|
+
)
|
|
151
228
|
) {
|
|
152
229
|
texts.push(
|
|
153
230
|
SchematicPinSvgRenderer.#buildPinNameTextMarkup(
|
|
@@ -175,7 +252,12 @@ export class SchematicPinSvgRenderer {
|
|
|
175
252
|
texts.push(
|
|
176
253
|
createSvgText(
|
|
177
254
|
'schematic-pin-number',
|
|
178
|
-
|
|
255
|
+
compactExternalNumberLabelSide
|
|
256
|
+
? SchematicPinSvgRenderer.#resolveCompactExternalVerticalNumberX(
|
|
257
|
+
geometry,
|
|
258
|
+
compactExternalNumberLabelSide
|
|
259
|
+
)
|
|
260
|
+
: geometry.bodyX - 2,
|
|
179
261
|
pin.orientation === 'top'
|
|
180
262
|
? projectedInnerY - 6
|
|
181
263
|
: projectedInnerY + 7,
|
|
@@ -230,12 +312,7 @@ export class SchematicPinSvgRenderer {
|
|
|
230
312
|
'" y2="' +
|
|
231
313
|
formatNumber(projectedOuterY) +
|
|
232
314
|
'" stroke="' +
|
|
233
|
-
escapeHtml(
|
|
234
|
-
SchematicColorResolver.resolveColor(
|
|
235
|
-
pin.color,
|
|
236
|
-
'--schematic-accent-ink-color'
|
|
237
|
-
)
|
|
238
|
-
) +
|
|
315
|
+
escapeHtml(SchematicPinSvgRenderer.#resolvePinStrokeColor(pin)) +
|
|
239
316
|
'" />' +
|
|
240
317
|
markerMarkup +
|
|
241
318
|
texts.join('') +
|
|
@@ -262,6 +339,19 @@ export class SchematicPinSvgRenderer {
|
|
|
262
339
|
)
|
|
263
340
|
}
|
|
264
341
|
|
|
342
|
+
const electricalMarkerStyle =
|
|
343
|
+
SchematicPinSvgRenderer.#resolveSchematicElectricalPinMarkerStyle(
|
|
344
|
+
pin
|
|
345
|
+
)
|
|
346
|
+
if (electricalMarkerStyle) {
|
|
347
|
+
return SchematicPinSvgRenderer.#buildOuterPinMarkerMarkup(
|
|
348
|
+
pin,
|
|
349
|
+
geometry,
|
|
350
|
+
sheetHeight,
|
|
351
|
+
electricalMarkerStyle
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
|
|
265
355
|
if (Number(pin.electrical || 0) !== 1) {
|
|
266
356
|
return ''
|
|
267
357
|
}
|
|
@@ -324,7 +414,7 @@ export class SchematicPinSvgRenderer {
|
|
|
324
414
|
* @param {{ orientation: 'left' | 'right' | 'top' | 'bottom', labelColor?: string, color: string }} pin
|
|
325
415
|
* @param {{ bodyX: number, bodyY: number }} geometry
|
|
326
416
|
* @param {number} sheetHeight
|
|
327
|
-
* @param {'single-in' | 'single-out' | 'double'} markerStyle
|
|
417
|
+
* @param {'single-in' | 'single-out' | 'double' | 'cross'} markerStyle
|
|
328
418
|
* @returns {string}
|
|
329
419
|
*/
|
|
330
420
|
static #buildOuterPinMarkerMarkup(pin, geometry, sheetHeight, markerStyle) {
|
|
@@ -339,6 +429,15 @@ export class SchematicPinSvgRenderer {
|
|
|
339
429
|
'--schematic-text-color'
|
|
340
430
|
)
|
|
341
431
|
|
|
432
|
+
if (markerStyle === 'cross') {
|
|
433
|
+
return SchematicPinSvgRenderer.#buildOuterPinCrossMarkerMarkup(
|
|
434
|
+
geometry.bodyX,
|
|
435
|
+
projectedY,
|
|
436
|
+
pin.orientation,
|
|
437
|
+
strokeColor
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
|
|
342
441
|
const polygons = SchematicPinSvgRenderer.#buildOuterPinMarkerPolygons(
|
|
343
442
|
geometry.bodyX,
|
|
344
443
|
projectedY,
|
|
@@ -374,22 +473,51 @@ export class SchematicPinSvgRenderer {
|
|
|
374
473
|
|
|
375
474
|
/**
|
|
376
475
|
* Returns the horizontal pin-number clearance needed by the pin geometry.
|
|
377
|
-
* @param {'single-in' | 'single-out' | 'double' | null} markerStyle
|
|
378
|
-
* @param {{ length?: number }} pin
|
|
476
|
+
* @param {'single-in' | 'single-out' | 'double' | 'cross' | null} markerStyle
|
|
477
|
+
* @param {{ length?: number, electrical?: number }} pin
|
|
379
478
|
* @returns {number}
|
|
380
479
|
*/
|
|
381
480
|
static #resolveHorizontalPinNumberClearance(markerStyle, pin) {
|
|
382
481
|
switch (markerStyle) {
|
|
383
482
|
case 'double':
|
|
384
|
-
return
|
|
483
|
+
return 21
|
|
484
|
+
case 'cross':
|
|
485
|
+
return 9
|
|
385
486
|
case 'single-in':
|
|
386
487
|
case 'single-out':
|
|
387
488
|
return 8
|
|
388
489
|
default:
|
|
490
|
+
if (Number(pin?.electrical || 0) === 1) {
|
|
491
|
+
return 16
|
|
492
|
+
}
|
|
493
|
+
|
|
389
494
|
return SchematicPinSvgRenderer.#resolveLongPinInset(pin, 2)
|
|
390
495
|
}
|
|
391
496
|
}
|
|
392
497
|
|
|
498
|
+
/**
|
|
499
|
+
* Returns whether a horizontal pin name should be rendered inside the body.
|
|
500
|
+
* Numeric name/designator matches are duplicate pin numbers; nonnumeric
|
|
501
|
+
* matches such as PAD are valid body labels.
|
|
502
|
+
* @param {{ name?: string, designator?: string }} pin Pin primitive.
|
|
503
|
+
* @param {boolean} hasExplicitOwnerPinName Whether an explicit owner text already draws the label.
|
|
504
|
+
* @returns {boolean}
|
|
505
|
+
*/
|
|
506
|
+
static #shouldRenderHorizontalPinName(pin, hasExplicitOwnerPinName) {
|
|
507
|
+
const name = String(pin?.name || '').trim()
|
|
508
|
+
const designator = String(pin?.designator || '').trim()
|
|
509
|
+
|
|
510
|
+
if (!name || hasExplicitOwnerPinName) {
|
|
511
|
+
return false
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (name !== designator) {
|
|
515
|
+
return true
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return !/^\d+$/u.test(name)
|
|
519
|
+
}
|
|
520
|
+
|
|
393
521
|
/**
|
|
394
522
|
* Returns the horizontal pin-name inset used inside the symbol body.
|
|
395
523
|
* @param {{ length?: number }} pin
|
|
@@ -401,7 +529,7 @@ export class SchematicPinSvgRenderer {
|
|
|
401
529
|
return 10
|
|
402
530
|
}
|
|
403
531
|
|
|
404
|
-
return SchematicPinSvgRenderer.#resolveLongPinInset(pin,
|
|
532
|
+
return SchematicPinSvgRenderer.#resolveLongPinInset(pin, 7)
|
|
405
533
|
}
|
|
406
534
|
|
|
407
535
|
/**
|
|
@@ -420,6 +548,192 @@ export class SchematicPinSvgRenderer {
|
|
|
420
548
|
return fallback === 2 ? 10 : 8
|
|
421
549
|
}
|
|
422
550
|
|
|
551
|
+
/**
|
|
552
|
+
* Places synthetic horizontal pin numbers outside the symbol body. Owner
|
|
553
|
+
* bodies with internal numeric labels need a stub-side offset so the
|
|
554
|
+
* outside contact number does not collapse onto the inside duplicate.
|
|
555
|
+
* @param {{ length?: number, orientation: 'left' | 'right' | 'top' | 'bottom', electrical?: number }} pin
|
|
556
|
+
* @param {{ bodyX: number }} geometry
|
|
557
|
+
* @param {'single-in' | 'single-out' | 'double' | 'cross' | null} markerStyle
|
|
558
|
+
* @param {'left' | 'right' | null} compactExternalNumberLabelSide
|
|
559
|
+
* @param {{ left: number, right: number } | null} internalNumberLabelBox
|
|
560
|
+
* @returns {number}
|
|
561
|
+
*/
|
|
562
|
+
static #resolveExternalHorizontalNumberX(
|
|
563
|
+
pin,
|
|
564
|
+
geometry,
|
|
565
|
+
markerStyle,
|
|
566
|
+
compactExternalNumberLabelSide,
|
|
567
|
+
internalNumberLabelBox
|
|
568
|
+
) {
|
|
569
|
+
const clearance =
|
|
570
|
+
SchematicPinSvgRenderer.#resolveHorizontalPinNumberClearance(
|
|
571
|
+
markerStyle,
|
|
572
|
+
pin
|
|
573
|
+
)
|
|
574
|
+
const offset =
|
|
575
|
+
compactExternalNumberLabelSide || internalNumberLabelBox
|
|
576
|
+
? Math.max(
|
|
577
|
+
clearance,
|
|
578
|
+
SchematicPinSvgRenderer.#resolveCompactExternalHorizontalNumberOffset(
|
|
579
|
+
pin
|
|
580
|
+
)
|
|
581
|
+
)
|
|
582
|
+
: clearance
|
|
583
|
+
|
|
584
|
+
if (pin.orientation === 'left') {
|
|
585
|
+
return geometry.bodyX - offset
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (pin.orientation === 'right') {
|
|
589
|
+
return geometry.bodyX + offset
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return geometry.bodyX
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Resolves the text edge used for external horizontal pin numbers.
|
|
597
|
+
* Double/electrical marker labels are authored from the route side back
|
|
598
|
+
* toward the marker, while ordinary pins extend away from the body.
|
|
599
|
+
* @param {{ orientation: 'left' | 'right' | 'top' | 'bottom', electrical?: number }} pin Pin primitive.
|
|
600
|
+
* @param {'single-in' | 'single-out' | 'double' | 'cross' | null} markerStyle Marker style.
|
|
601
|
+
* @returns {'start' | 'end'}
|
|
602
|
+
*/
|
|
603
|
+
static #resolveExternalHorizontalNumberAnchor(pin, markerStyle) {
|
|
604
|
+
const routeFacing =
|
|
605
|
+
markerStyle === 'double' ||
|
|
606
|
+
(!markerStyle && Number(pin?.electrical || 0) === 1)
|
|
607
|
+
|
|
608
|
+
if (routeFacing) {
|
|
609
|
+
return pin.orientation === 'left' ? 'start' : 'end'
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return pin.orientation === 'left' ? 'end' : 'start'
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Places numeric-only labels just inside a rectangular owner body.
|
|
617
|
+
* @param {{ name?: string, orientation: 'left' | 'right' | 'top' | 'bottom' }} pin
|
|
618
|
+
* @param {{ left: number, right: number } | null} box
|
|
619
|
+
* @returns {{ x: number, anchor: 'middle' } | null}
|
|
620
|
+
*/
|
|
621
|
+
static #resolveInternalHorizontalNumberPlacement(pin, box) {
|
|
622
|
+
if (
|
|
623
|
+
!box ||
|
|
624
|
+
(pin.orientation !== 'left' && pin.orientation !== 'right') ||
|
|
625
|
+
!/^\d+$/u.test(String(pin.name || '').trim())
|
|
626
|
+
) {
|
|
627
|
+
return null
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const left = Number(box.left)
|
|
631
|
+
const right = Number(box.right)
|
|
632
|
+
|
|
633
|
+
if (
|
|
634
|
+
!Number.isFinite(left) ||
|
|
635
|
+
!Number.isFinite(right) ||
|
|
636
|
+
right <= left
|
|
637
|
+
) {
|
|
638
|
+
return null
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const inset = Math.min(10, Math.max(6, (right - left) / 6))
|
|
642
|
+
|
|
643
|
+
return {
|
|
644
|
+
x: pin.orientation === 'left' ? left + inset : right - inset,
|
|
645
|
+
anchor: 'middle'
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Places compact owner-drawn side pin numbers near the external pin stub.
|
|
651
|
+
* @param {{ length?: number, orientation: 'left' | 'right' | 'top' | 'bottom' }} pin
|
|
652
|
+
* @param {{ bodyX: number }} geometry
|
|
653
|
+
* @returns {number}
|
|
654
|
+
*/
|
|
655
|
+
static #resolveCompactExternalHorizontalNumberX(pin, geometry) {
|
|
656
|
+
const offset =
|
|
657
|
+
SchematicPinSvgRenderer.#resolveCompactExternalHorizontalNumberOffset(
|
|
658
|
+
pin
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
if (pin.orientation === 'left') {
|
|
662
|
+
return geometry.bodyX - offset
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (pin.orientation === 'right') {
|
|
666
|
+
return geometry.bodyX + offset
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return geometry.bodyX
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Resolves a stub-side offset for compact external horizontal numbers.
|
|
674
|
+
* @param {{ length?: number }} pin
|
|
675
|
+
* @returns {number}
|
|
676
|
+
*/
|
|
677
|
+
static #resolveCompactExternalHorizontalNumberOffset(pin) {
|
|
678
|
+
const length = Math.abs(Number(pin.length || 0))
|
|
679
|
+
|
|
680
|
+
return Math.max(8, Math.min(length - 6, 12))
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Places compact owner-drawn vertical pin numbers on the same side as the
|
|
685
|
+
* horizontal terminal contacts.
|
|
686
|
+
* @param {{ bodyX: number }} geometry
|
|
687
|
+
* @param {'left' | 'right'} side
|
|
688
|
+
* @returns {number}
|
|
689
|
+
*/
|
|
690
|
+
static #resolveCompactExternalVerticalNumberX(geometry, side) {
|
|
691
|
+
return geometry.bodyX + (side === 'left' ? -10 : 10)
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Builds an authored outer pin cross marker.
|
|
696
|
+
* @param {number} bodyX
|
|
697
|
+
* @param {number} projectedY
|
|
698
|
+
* @param {'left' | 'right' | 'top' | 'bottom'} orientation
|
|
699
|
+
* @param {string} strokeColor
|
|
700
|
+
* @returns {string}
|
|
701
|
+
*/
|
|
702
|
+
static #buildOuterPinCrossMarkerMarkup(
|
|
703
|
+
bodyX,
|
|
704
|
+
projectedY,
|
|
705
|
+
orientation,
|
|
706
|
+
strokeColor
|
|
707
|
+
) {
|
|
708
|
+
const direction = orientation === 'left' ? 1 : -1
|
|
709
|
+
const centerX = bodyX - direction * 8
|
|
710
|
+
const halfSize = 3
|
|
711
|
+
|
|
712
|
+
return (
|
|
713
|
+
'<g class="schematic-pin-marker"><line x1="' +
|
|
714
|
+
formatNumber(centerX - halfSize) +
|
|
715
|
+
'" y1="' +
|
|
716
|
+
formatNumber(projectedY - halfSize) +
|
|
717
|
+
'" x2="' +
|
|
718
|
+
formatNumber(centerX + halfSize) +
|
|
719
|
+
'" y2="' +
|
|
720
|
+
formatNumber(projectedY + halfSize) +
|
|
721
|
+
'" stroke="' +
|
|
722
|
+
escapeHtml(strokeColor) +
|
|
723
|
+
'" stroke-width="0.75" vector-effect="non-scaling-stroke" /><line x1="' +
|
|
724
|
+
formatNumber(centerX - halfSize) +
|
|
725
|
+
'" y1="' +
|
|
726
|
+
formatNumber(projectedY + halfSize) +
|
|
727
|
+
'" x2="' +
|
|
728
|
+
formatNumber(centerX + halfSize) +
|
|
729
|
+
'" y2="' +
|
|
730
|
+
formatNumber(projectedY - halfSize) +
|
|
731
|
+
'" stroke="' +
|
|
732
|
+
escapeHtml(strokeColor) +
|
|
733
|
+
'" stroke-width="0.75" vector-effect="non-scaling-stroke" /></g>'
|
|
734
|
+
)
|
|
735
|
+
}
|
|
736
|
+
|
|
423
737
|
/**
|
|
424
738
|
* Builds one or two authored outer-marker polygons for one horizontal pin.
|
|
425
739
|
* @param {number} bodyX
|
|
@@ -469,7 +783,7 @@ export class SchematicPinSvgRenderer {
|
|
|
469
783
|
/**
|
|
470
784
|
* Resolves one authored outer pin marker style from the stored symbol flag.
|
|
471
785
|
* @param {{ symbolOuter?: number, orientation: 'left' | 'right' | 'top' | 'bottom' }} pin
|
|
472
|
-
* @returns {'single-in' | 'single-out' | 'double' | null}
|
|
786
|
+
* @returns {'single-in' | 'single-out' | 'double' | 'cross' | null}
|
|
473
787
|
*/
|
|
474
788
|
static #resolveSchematicOuterPinMarkerStyle(pin) {
|
|
475
789
|
if (pin.orientation !== 'left' && pin.orientation !== 'right') {
|
|
@@ -483,6 +797,8 @@ export class SchematicPinSvgRenderer {
|
|
|
483
797
|
return 'single-out'
|
|
484
798
|
case 2:
|
|
485
799
|
return 'single-in'
|
|
800
|
+
case 6:
|
|
801
|
+
return 'cross'
|
|
486
802
|
case 34:
|
|
487
803
|
return 'double'
|
|
488
804
|
default:
|
|
@@ -490,6 +806,39 @@ export class SchematicPinSvgRenderer {
|
|
|
490
806
|
}
|
|
491
807
|
}
|
|
492
808
|
|
|
809
|
+
/**
|
|
810
|
+
* Resolves marker styles derived from the pin electrical type. The
|
|
811
|
+
* bidirectional type keeps its legacy two-triangle geometry path.
|
|
812
|
+
* @param {{ electrical?: number, orientation: 'left' | 'right' | 'top' | 'bottom' }} pin
|
|
813
|
+
* @returns {'single-in' | 'single-out' | null}
|
|
814
|
+
*/
|
|
815
|
+
static #resolveSchematicElectricalPinMarkerStyle(pin) {
|
|
816
|
+
if (pin.orientation !== 'left' && pin.orientation !== 'right') {
|
|
817
|
+
return null
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
switch (Number(pin.electrical)) {
|
|
821
|
+
case 0:
|
|
822
|
+
return 'single-in'
|
|
823
|
+
case 2:
|
|
824
|
+
return 'single-out'
|
|
825
|
+
default:
|
|
826
|
+
return null
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Resolves the visible stroke for a schematic pin stub.
|
|
832
|
+
* @param {{ color?: string }} pin
|
|
833
|
+
* @returns {string}
|
|
834
|
+
*/
|
|
835
|
+
static #resolvePinStrokeColor(pin) {
|
|
836
|
+
return SchematicColorResolver.resolveNonTextColor(
|
|
837
|
+
pin?.color,
|
|
838
|
+
'--schematic-accent-ink-color'
|
|
839
|
+
)
|
|
840
|
+
}
|
|
841
|
+
|
|
493
842
|
/**
|
|
494
843
|
* Builds one pin-name text element, including overline tspans when needed.
|
|
495
844
|
* @param {string} className
|