altium-toolkit 0.1.18 → 0.1.21
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/package.json +1 -1
- package/src/core/altium/AltiumParser.mjs +15 -3
- package/src/core/altium/PcbFontMetricsParser.mjs +33 -6
- package/src/core/altium/PcbModelParser.mjs +82 -0
- package/src/core/altium/PcbTextPrimitiveParser.mjs +48 -3
- package/src/core/altium/SchematicPinDesignatorInferer.mjs +401 -0
- package/src/core/altium/SchematicPinParser.mjs +136 -56
- package/src/core/altium/SchematicStreamExtractor.mjs +93 -0
- package/src/core/altium/SchematicTextPostProcessor.mjs +40 -12
- package/src/ui/PcbScene3dBuilder.mjs +593 -53
- package/src/ui/PcbScene3dDrillCutoutBuilder.mjs +453 -0
- package/src/ui/SchematicImageRenderer.mjs +134 -32
- package/src/ui/SchematicJunctionRenderer.mjs +22 -4
- package/src/ui/SchematicNoteRenderer.mjs +73 -9
- package/src/ui/SchematicPinSvgRenderer.mjs +25 -3
- package/src/ui/SchematicPowerPortRenderer.mjs +183 -36
- package/src/ui/SchematicSvgRenderer.mjs +2 -2
|
@@ -13,7 +13,7 @@ const { escapeHtml, formatNumber, projectSchematicY } = SchematicSvgUtils
|
|
|
13
13
|
export class SchematicJunctionRenderer {
|
|
14
14
|
/**
|
|
15
15
|
* Builds junction-dot markup from connected wire linework.
|
|
16
|
-
* @param {{ x1: number, y1: number, x2: number, y2: number, color: string, ownerIndex?: string, isBus?: boolean }[]} lines
|
|
16
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number, color: string, ownerIndex?: string, isBus?: boolean, recordType?: string }[]} lines
|
|
17
17
|
* @param {{ x: number, y: number }[]} crosses
|
|
18
18
|
* @param {{ x: number, y: number, width: number, direction?: 'left' | 'right' | 'up' | 'down' }[]} [ports]
|
|
19
19
|
* @param {{ x: number, y: number, style?: number, powerPortDirection?: 'up' | 'down' | 'left' | 'right' }[]} [powerPorts]
|
|
@@ -53,15 +53,15 @@ export class SchematicJunctionRenderer {
|
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Resolves all wire-junction points that should display a connection dot.
|
|
56
|
-
* @param {{ x1: number, y1: number, x2: number, y2: number, color: string, ownerIndex?: string, isBus?: boolean }[]} lines
|
|
56
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number, color: string, ownerIndex?: string, isBus?: boolean, recordType?: string }[]} lines
|
|
57
57
|
* @param {{ x: number, y: number }[]} crosses
|
|
58
58
|
* @param {{ x: number, y: number, width: number, direction?: 'left' | 'right' | 'up' | 'down' }[]} ports
|
|
59
59
|
* @param {{ x: number, y: number, style?: number, powerPortDirection?: 'up' | 'down' | 'left' | 'right' }[]} powerPorts
|
|
60
60
|
* @returns {{ x: number, y: number, color: string }[]}
|
|
61
61
|
*/
|
|
62
62
|
static #resolveJunctions(lines, crosses, ports, powerPorts) {
|
|
63
|
-
const wireLines = lines.filter(
|
|
64
|
-
(line)
|
|
63
|
+
const wireLines = lines.filter((line) =>
|
|
64
|
+
SchematicJunctionRenderer.#isElectricalWireLine(line)
|
|
65
65
|
)
|
|
66
66
|
const verticalPorts = ports.filter((port) =>
|
|
67
67
|
SchematicJunctionRenderer.#isVerticalPort(port)
|
|
@@ -370,6 +370,24 @@ export class SchematicJunctionRenderer {
|
|
|
370
370
|
)
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Returns true when one line can participate in synthetic electrical
|
|
375
|
+
* junction dots.
|
|
376
|
+
* @param {{ ownerIndex?: string, isBus?: boolean, recordType?: string }} line
|
|
377
|
+
* @returns {boolean}
|
|
378
|
+
*/
|
|
379
|
+
static #isElectricalWireLine(line) {
|
|
380
|
+
if (line?.ownerIndex || line?.isBus === true) {
|
|
381
|
+
return false
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (!Object.prototype.hasOwnProperty.call(line || {}, 'recordType')) {
|
|
385
|
+
return true
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return !['6', '7', '26'].includes(String(line.recordType || ''))
|
|
389
|
+
}
|
|
390
|
+
|
|
373
391
|
/**
|
|
374
392
|
* Builds a stable map key for one point.
|
|
375
393
|
* @param {{ x: number, y: number }} point
|
|
@@ -51,11 +51,23 @@ export class SchematicNoteRenderer {
|
|
|
51
51
|
'--schematic-note-border-color'
|
|
52
52
|
)
|
|
53
53
|
const noteStroke = text.showBorder ? borderColor : 'none'
|
|
54
|
+
const noteSourceLines = text.noteLines || []
|
|
55
|
+
const verticalTextMargin =
|
|
56
|
+
SchematicNoteRenderer.#resolveVerticalTextMargin(
|
|
57
|
+
textMargin,
|
|
58
|
+
height,
|
|
59
|
+
requestedTextSize
|
|
60
|
+
)
|
|
54
61
|
const layout = SchematicNoteRenderer.#resolveTextLayout(
|
|
55
|
-
|
|
62
|
+
noteSourceLines,
|
|
56
63
|
Math.max(width - textMargin * 2, requestedTextSize),
|
|
57
|
-
Math.max(height -
|
|
58
|
-
requestedTextSize
|
|
64
|
+
Math.max(height - verticalTextMargin * 2, requestedTextSize),
|
|
65
|
+
requestedTextSize,
|
|
66
|
+
SchematicNoteRenderer.#isCompactSingleLineNote(
|
|
67
|
+
noteSourceLines,
|
|
68
|
+
height,
|
|
69
|
+
requestedTextSize
|
|
70
|
+
)
|
|
59
71
|
)
|
|
60
72
|
const noteLines = layout.noteLines
|
|
61
73
|
const textSize = layout.textSize
|
|
@@ -69,6 +81,7 @@ export class SchematicNoteRenderer {
|
|
|
69
81
|
right,
|
|
70
82
|
top,
|
|
71
83
|
textMargin,
|
|
84
|
+
verticalTextMargin,
|
|
72
85
|
lineHeight,
|
|
73
86
|
textSize,
|
|
74
87
|
text
|
|
@@ -102,14 +115,33 @@ export class SchematicNoteRenderer {
|
|
|
102
115
|
* @param {number} maxWidth
|
|
103
116
|
* @param {number} maxHeight
|
|
104
117
|
* @param {number} requestedTextSize
|
|
118
|
+
* @param {boolean} keepSingleLineSize
|
|
105
119
|
* @returns {{ noteLines: string[], textSize: number, lineHeight: number }}
|
|
106
120
|
*/
|
|
107
121
|
static #resolveTextLayout(
|
|
108
122
|
noteLines,
|
|
109
123
|
maxWidth,
|
|
110
124
|
maxHeight,
|
|
111
|
-
requestedTextSize
|
|
125
|
+
requestedTextSize,
|
|
126
|
+
keepSingleLineSize = false
|
|
112
127
|
) {
|
|
128
|
+
if (keepSingleLineSize) {
|
|
129
|
+
const visibleLines = noteLines.filter((line) =>
|
|
130
|
+
String(line || '').trim()
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
noteLines: visibleLines,
|
|
135
|
+
textSize: requestedTextSize,
|
|
136
|
+
lineHeight: SchematicNoteRenderer.#resolveLineHeight(
|
|
137
|
+
requestedTextSize,
|
|
138
|
+
maxHeight,
|
|
139
|
+
0,
|
|
140
|
+
visibleLines.length
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
113
145
|
let textSize = requestedTextSize
|
|
114
146
|
let wrappedLines = []
|
|
115
147
|
|
|
@@ -262,6 +294,36 @@ export class SchematicNoteRenderer {
|
|
|
262
294
|
return wrappedLines
|
|
263
295
|
}
|
|
264
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Checks if a note is a tight one-line callout where Altium preserves the
|
|
299
|
+
* text size even when the note rectangle has little vertical padding.
|
|
300
|
+
* @param {string[]} noteLines
|
|
301
|
+
* @param {number} height
|
|
302
|
+
* @param {number} requestedTextSize
|
|
303
|
+
* @returns {boolean}
|
|
304
|
+
*/
|
|
305
|
+
static #isCompactSingleLineNote(noteLines, height, requestedTextSize) {
|
|
306
|
+
const visibleLineCount = noteLines.filter((line) =>
|
|
307
|
+
String(line || '').trim()
|
|
308
|
+
).length
|
|
309
|
+
|
|
310
|
+
return visibleLineCount === 1 && height <= requestedTextSize * 1.5
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Reduces vertical padding for short note rectangles so readable text is
|
|
315
|
+
* centered instead of scaled down to satisfy the default margin.
|
|
316
|
+
* @param {number} textMargin
|
|
317
|
+
* @param {number} height
|
|
318
|
+
* @param {number} requestedTextSize
|
|
319
|
+
* @returns {number}
|
|
320
|
+
*/
|
|
321
|
+
static #resolveVerticalTextMargin(textMargin, height, requestedTextSize) {
|
|
322
|
+
const centeredMargin = Math.max((height - requestedTextSize) / 2, 0)
|
|
323
|
+
|
|
324
|
+
return Math.min(textMargin, centeredMargin)
|
|
325
|
+
}
|
|
326
|
+
|
|
265
327
|
/**
|
|
266
328
|
* Splits one oversized token into smaller width-safe fragments.
|
|
267
329
|
* @param {string} token
|
|
@@ -360,7 +422,8 @@ export class SchematicNoteRenderer {
|
|
|
360
422
|
* @param {number} left
|
|
361
423
|
* @param {number} right
|
|
362
424
|
* @param {number} top
|
|
363
|
-
* @param {number}
|
|
425
|
+
* @param {number} horizontalTextMargin
|
|
426
|
+
* @param {number} verticalTextMargin
|
|
364
427
|
* @param {number} lineHeight
|
|
365
428
|
* @param {number} textSize
|
|
366
429
|
* @param {{ color: string, fontFamily?: string, fontWeight?: number }} text
|
|
@@ -372,13 +435,14 @@ export class SchematicNoteRenderer {
|
|
|
372
435
|
left,
|
|
373
436
|
right,
|
|
374
437
|
top,
|
|
375
|
-
|
|
438
|
+
horizontalTextMargin,
|
|
439
|
+
verticalTextMargin,
|
|
376
440
|
lineHeight,
|
|
377
441
|
textSize,
|
|
378
442
|
text
|
|
379
443
|
) {
|
|
380
|
-
const x = left +
|
|
381
|
-
const y = top +
|
|
444
|
+
const x = left + horizontalTextMargin
|
|
445
|
+
const y = top + verticalTextMargin + textSize + index * lineHeight
|
|
382
446
|
|
|
383
447
|
if (/^_+$/.test(String(line || '').trim())) {
|
|
384
448
|
return (
|
|
@@ -387,7 +451,7 @@ export class SchematicNoteRenderer {
|
|
|
387
451
|
'" y1="' +
|
|
388
452
|
formatNumber(y - textSize * 0.35) +
|
|
389
453
|
'" x2="' +
|
|
390
|
-
formatNumber(right -
|
|
454
|
+
formatNumber(right - horizontalTextMargin) +
|
|
391
455
|
'" y2="' +
|
|
392
456
|
formatNumber(y - textSize * 0.35) +
|
|
393
457
|
'" stroke="' +
|
|
@@ -49,7 +49,6 @@ export class SchematicPinSvgRenderer {
|
|
|
49
49
|
const labelMode = pin.labelMode || 'name-and-number'
|
|
50
50
|
const outerMarkerStyle =
|
|
51
51
|
SchematicPinSvgRenderer.#resolveSchematicOuterPinMarkerStyle(pin)
|
|
52
|
-
const usesOuterMarker = outerMarkerStyle !== null
|
|
53
52
|
const rotateTopNumber =
|
|
54
53
|
pin.orientation === 'top' &&
|
|
55
54
|
rotatedVerticalNumberOwners.has(String(pin.ownerIndex || ''))
|
|
@@ -65,7 +64,10 @@ export class SchematicPinSvgRenderer {
|
|
|
65
64
|
if (pin.orientation === 'left') {
|
|
66
65
|
if (labelMode !== 'hidden' && labelMode !== 'name-only') {
|
|
67
66
|
const defaultNumberX =
|
|
68
|
-
geometry.bodyX -
|
|
67
|
+
geometry.bodyX -
|
|
68
|
+
SchematicPinSvgRenderer.#resolveHorizontalPinNumberClearance(
|
|
69
|
+
outerMarkerStyle
|
|
70
|
+
)
|
|
69
71
|
const numberX = hasExplicitOwnerPinName
|
|
70
72
|
? SchematicOwnerPinLabelLayout.resolveExplicitOwnerPinNumberX(
|
|
71
73
|
pin,
|
|
@@ -110,7 +112,10 @@ export class SchematicPinSvgRenderer {
|
|
|
110
112
|
if (pin.orientation === 'right') {
|
|
111
113
|
if (labelMode !== 'hidden' && labelMode !== 'name-only') {
|
|
112
114
|
const defaultNumberX =
|
|
113
|
-
geometry.bodyX +
|
|
115
|
+
geometry.bodyX +
|
|
116
|
+
SchematicPinSvgRenderer.#resolveHorizontalPinNumberClearance(
|
|
117
|
+
outerMarkerStyle
|
|
118
|
+
)
|
|
114
119
|
const numberX = hasExplicitOwnerPinName
|
|
115
120
|
? SchematicOwnerPinLabelLayout.resolveExplicitOwnerPinNumberX(
|
|
116
121
|
pin,
|
|
@@ -357,6 +362,23 @@ export class SchematicPinSvgRenderer {
|
|
|
357
362
|
)
|
|
358
363
|
}
|
|
359
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Returns the horizontal pin-number clearance needed by an authored marker.
|
|
367
|
+
* @param {'single-in' | 'single-out' | 'double' | null} markerStyle
|
|
368
|
+
* @returns {number}
|
|
369
|
+
*/
|
|
370
|
+
static #resolveHorizontalPinNumberClearance(markerStyle) {
|
|
371
|
+
switch (markerStyle) {
|
|
372
|
+
case 'double':
|
|
373
|
+
return 17
|
|
374
|
+
case 'single-in':
|
|
375
|
+
case 'single-out':
|
|
376
|
+
return 8
|
|
377
|
+
default:
|
|
378
|
+
return 2
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
360
382
|
/**
|
|
361
383
|
* Builds one or two authored outer-marker polygons for one horizontal pin.
|
|
362
384
|
* @param {number} bodyX
|
|
@@ -56,7 +56,9 @@ export class SchematicPowerPortRenderer {
|
|
|
56
56
|
y,
|
|
57
57
|
fontSize,
|
|
58
58
|
labelOptions,
|
|
59
|
-
resolvedColor
|
|
59
|
+
resolvedColor,
|
|
60
|
+
lines,
|
|
61
|
+
sheetHeight
|
|
60
62
|
) +
|
|
61
63
|
'</g>'
|
|
62
64
|
)
|
|
@@ -77,7 +79,9 @@ export class SchematicPowerPortRenderer {
|
|
|
77
79
|
y,
|
|
78
80
|
fontSize,
|
|
79
81
|
labelOptions,
|
|
80
|
-
resolvedColor
|
|
82
|
+
resolvedColor,
|
|
83
|
+
lines,
|
|
84
|
+
sheetHeight
|
|
81
85
|
) +
|
|
82
86
|
'</g>'
|
|
83
87
|
)
|
|
@@ -514,6 +518,8 @@ export class SchematicPowerPortRenderer {
|
|
|
514
518
|
* @param {number} fontSize
|
|
515
519
|
* @param {{ fontSize?: number, fontFamily?: string, fontWeight?: number }} labelOptions
|
|
516
520
|
* @param {string} color
|
|
521
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number }[]} lines
|
|
522
|
+
* @param {number} sheetHeight
|
|
517
523
|
* @returns {string}
|
|
518
524
|
*/
|
|
519
525
|
static #buildDirectionalLabel(
|
|
@@ -523,52 +529,193 @@ export class SchematicPowerPortRenderer {
|
|
|
523
529
|
y,
|
|
524
530
|
fontSize,
|
|
525
531
|
labelOptions,
|
|
526
|
-
color
|
|
532
|
+
color,
|
|
533
|
+
lines = [],
|
|
534
|
+
sheetHeight = 0
|
|
535
|
+
) {
|
|
536
|
+
const placement = SchematicPowerPortRenderer.#resolveLabelPlacement(
|
|
537
|
+
text,
|
|
538
|
+
direction,
|
|
539
|
+
x,
|
|
540
|
+
y,
|
|
541
|
+
fontSize,
|
|
542
|
+
lines,
|
|
543
|
+
sheetHeight
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
return createSvgText(
|
|
547
|
+
'schematic-power-port-label',
|
|
548
|
+
placement.x,
|
|
549
|
+
placement.y,
|
|
550
|
+
text.text,
|
|
551
|
+
color,
|
|
552
|
+
placement.anchor,
|
|
553
|
+
labelOptions
|
|
554
|
+
)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Resolves label placement and avoids nearby horizontal net-line overlap
|
|
559
|
+
* for upward rail labels in dense schematic areas.
|
|
560
|
+
* @param {{ text: string, style?: number }} text
|
|
561
|
+
* @param {'up' | 'down' | 'left' | 'right'} direction
|
|
562
|
+
* @param {number} x
|
|
563
|
+
* @param {number} y
|
|
564
|
+
* @param {number} fontSize
|
|
565
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number }[]} lines
|
|
566
|
+
* @param {number} sheetHeight
|
|
567
|
+
* @returns {{ x: number, y: number, anchor: 'start' | 'middle' | 'end' }}
|
|
568
|
+
*/
|
|
569
|
+
static #resolveLabelPlacement(
|
|
570
|
+
text,
|
|
571
|
+
direction,
|
|
572
|
+
x,
|
|
573
|
+
y,
|
|
574
|
+
fontSize,
|
|
575
|
+
lines,
|
|
576
|
+
sheetHeight
|
|
527
577
|
) {
|
|
528
578
|
if (direction === 'up') {
|
|
529
|
-
return
|
|
530
|
-
'schematic-power-port-label',
|
|
579
|
+
return {
|
|
531
580
|
x,
|
|
532
|
-
y
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
581
|
+
y: SchematicPowerPortRenderer.#resolveUpwardRailLabelY(
|
|
582
|
+
text,
|
|
583
|
+
x,
|
|
584
|
+
y,
|
|
585
|
+
y - 16,
|
|
586
|
+
fontSize,
|
|
587
|
+
lines,
|
|
588
|
+
sheetHeight
|
|
589
|
+
),
|
|
590
|
+
anchor: 'middle'
|
|
591
|
+
}
|
|
538
592
|
}
|
|
539
593
|
|
|
540
594
|
if (direction === 'right') {
|
|
541
|
-
return
|
|
542
|
-
'schematic-power-port-label',
|
|
543
|
-
x + 18,
|
|
544
|
-
y + fontSize * 0.36,
|
|
545
|
-
text.text,
|
|
546
|
-
color,
|
|
547
|
-
'start',
|
|
548
|
-
labelOptions
|
|
549
|
-
)
|
|
595
|
+
return { x: x + 18, y: y + fontSize * 0.36, anchor: 'start' }
|
|
550
596
|
}
|
|
551
597
|
|
|
552
598
|
if (direction === 'left') {
|
|
553
|
-
return
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
599
|
+
return { x: x - 18, y: y + fontSize * 0.36, anchor: 'end' }
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return { x, y: y + 25, anchor: 'middle' }
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Shifts an upward rail label below close parallel net lines when the
|
|
607
|
+
* default placement would draw text through an existing wire.
|
|
608
|
+
* @param {{ text: string, style?: number }} text
|
|
609
|
+
* @param {number} x
|
|
610
|
+
* @param {number} connectionY
|
|
611
|
+
* @param {number} defaultY
|
|
612
|
+
* @param {number} fontSize
|
|
613
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number }[]} lines
|
|
614
|
+
* @param {number} sheetHeight
|
|
615
|
+
* @returns {number}
|
|
616
|
+
*/
|
|
617
|
+
static #resolveUpwardRailLabelY(
|
|
618
|
+
text,
|
|
619
|
+
x,
|
|
620
|
+
connectionY,
|
|
621
|
+
defaultY,
|
|
622
|
+
fontSize,
|
|
623
|
+
lines,
|
|
624
|
+
sheetHeight
|
|
625
|
+
) {
|
|
626
|
+
if (Number(text.style || 0) === 4) {
|
|
627
|
+
return defaultY
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
let labelY = defaultY
|
|
631
|
+
|
|
632
|
+
for (const line of lines) {
|
|
633
|
+
const horizontalLine =
|
|
634
|
+
SchematicPowerPortRenderer.#projectHorizontalLine(
|
|
635
|
+
line,
|
|
636
|
+
sheetHeight
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
if (!horizontalLine) {
|
|
640
|
+
continue
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (
|
|
644
|
+
!SchematicPowerPortRenderer.#horizontalLineIntersectsLabel(
|
|
645
|
+
horizontalLine,
|
|
646
|
+
text.text,
|
|
647
|
+
x,
|
|
648
|
+
labelY,
|
|
649
|
+
fontSize
|
|
650
|
+
)
|
|
651
|
+
) {
|
|
652
|
+
continue
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
labelY = Math.min(
|
|
656
|
+
connectionY - 4,
|
|
657
|
+
Math.max(labelY, horizontalLine.y + fontSize + 4)
|
|
561
658
|
)
|
|
562
659
|
}
|
|
563
660
|
|
|
564
|
-
return
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
661
|
+
return labelY
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Projects one source horizontal line into SVG coordinates.
|
|
666
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number }} line
|
|
667
|
+
* @param {number} sheetHeight
|
|
668
|
+
* @returns {{ y: number, minX: number, maxX: number } | null}
|
|
669
|
+
*/
|
|
670
|
+
static #projectHorizontalLine(line, sheetHeight) {
|
|
671
|
+
const y1 = projectSchematicY(sheetHeight, line.y1)
|
|
672
|
+
const y2 = projectSchematicY(sheetHeight, line.y2)
|
|
673
|
+
|
|
674
|
+
if (Math.abs(y1 - y2) > 0.01) {
|
|
675
|
+
return null
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return {
|
|
679
|
+
y: y1,
|
|
680
|
+
minX: Math.min(line.x1, line.x2),
|
|
681
|
+
maxX: Math.max(line.x1, line.x2)
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Checks whether a projected horizontal line crosses an estimated text box.
|
|
687
|
+
* @param {{ y: number, minX: number, maxX: number }} line
|
|
688
|
+
* @param {string} label
|
|
689
|
+
* @param {number} x
|
|
690
|
+
* @param {number} labelY
|
|
691
|
+
* @param {number} fontSize
|
|
692
|
+
* @returns {boolean}
|
|
693
|
+
*/
|
|
694
|
+
static #horizontalLineIntersectsLabel(line, label, x, labelY, fontSize) {
|
|
695
|
+
const textWidth = SchematicPowerPortRenderer.#estimateLabelWidth(
|
|
696
|
+
label,
|
|
697
|
+
fontSize
|
|
698
|
+
)
|
|
699
|
+
const textMinX = x - textWidth / 2
|
|
700
|
+
const textMaxX = x + textWidth / 2
|
|
701
|
+
const textTopY = labelY - fontSize
|
|
702
|
+
const textBottomY = labelY + fontSize * 0.25
|
|
703
|
+
|
|
704
|
+
return (
|
|
705
|
+
line.maxX >= textMinX &&
|
|
706
|
+
line.minX <= textMaxX &&
|
|
707
|
+
line.y >= textTopY &&
|
|
708
|
+
line.y <= textBottomY
|
|
572
709
|
)
|
|
573
710
|
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Estimates one power-port label width for clearance checks.
|
|
714
|
+
* @param {string} label
|
|
715
|
+
* @param {number} fontSize
|
|
716
|
+
* @returns {number}
|
|
717
|
+
*/
|
|
718
|
+
static #estimateLabelWidth(label, fontSize) {
|
|
719
|
+
return String(label || '').length * fontSize * 0.56
|
|
720
|
+
}
|
|
574
721
|
}
|
|
@@ -28,7 +28,7 @@ const { createSvgText, escapeHtml, formatNumber, projectSchematicY } =
|
|
|
28
28
|
export class SchematicSvgRenderer {
|
|
29
29
|
/**
|
|
30
30
|
* Renders a normalized schematic model into SVG markup.
|
|
31
|
-
* @param {{ fileName?: string, summary: { title?: string }, schematic?: { sheet: { width: number, height: number, sourceWidth?: number, sourceHeight?: number, paperSize?: string, borderOn?: boolean, titleBlockOn?: boolean, marginWidth?: number, xZones?: number, yZones?: number, titleBlock?: { title?: string, revision?: string, documentNumber?: string, sheetNumber?: string, sheetTotal?: string, date?: string, drawnBy?: string } }, lines: { x1: number, y1: number, x2: number, y2: number, color: string, width: number, lineStyle?: number, isBus?: boolean, ownerIndex?: string, renderOrder?: number }[], polygons?: { points: { x: number, y: number }[], color: string, fill: string, isSolid: boolean, transparent: boolean, lineWidth: number, ownerIndex?: string, renderOrder?: number }[], rectangles?: { x: number, y: number, width: number, height: number, color: string, fill: string, isSolid: boolean, transparent: boolean, lineWidth: number, ownerIndex?: string, renderOrder?: number }[], regions?: { x: number, y: number, width: number, height: number, color: string, fill: string, renderOrder?: number }[], ellipses?: { x: number, y: number, radiusX: number, radiusY: number, color: string, fill: string, isSolid: boolean, transparent: boolean, lineWidth: number, ownerIndex?: string, renderOrder?: number }[], arcs?: { x: number, y: number, radius: number, startAngle: number, endAngle: number, color: string, width: number, ownerIndex?: string, renderOrder?: number }[], directives?: { x: number, y: number, color: string, name: string, orientation?: number }[], texts: { x: number, y: number, text: string, color: string, recordType?: string, style?: number, fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number, sourceOrientation?: number, isMirrored?: boolean, anchor?: 'start' | 'middle' | 'end', powerPortDirection?: 'up' | 'down' | 'left' | 'right', cornerX?: number, cornerY?: number, fill?: string, borderColor?: string, isSolid?: boolean, showBorder?: boolean, textMargin?: number, noteLines?: string[] }[], components: { x: number, y: number, designator: string }[], pins?: { x: number, y: number, length: number, name: string, nameSegments?: { text: string, overline: boolean }[], designator: string, orientation: 'left' | 'right' | 'top' | 'bottom', electrical?: number, symbolOuter?: number, color: string, labelColor?: string, labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number', ownerIndex?: string }[], ports?: { x: number, y: number, width: number, height: number, name: string, fill: string, color: string, direction?: 'left' | 'right' | 'up' | 'down', shape?: 'single' | 'double' | 'plain' }[], crosses?: { x: number, y: number, size: number, color: string }[] } }} documentModel
|
|
31
|
+
* @param {{ fileName?: string, summary: { title?: string }, schematic?: { sheet: { width: number, height: number, sourceWidth?: number, sourceHeight?: number, paperSize?: string, borderOn?: boolean, titleBlockOn?: boolean, marginWidth?: number, xZones?: number, yZones?: number, titleBlock?: { title?: string, revision?: string, documentNumber?: string, sheetNumber?: string, sheetTotal?: string, date?: string, drawnBy?: string } }, lines: { x1: number, y1: number, x2: number, y2: number, color: string, width: number, lineStyle?: number, isBus?: boolean, ownerIndex?: string, renderOrder?: number, recordType?: string }[], polygons?: { points: { x: number, y: number }[], color: string, fill: string, isSolid: boolean, transparent: boolean, lineWidth: number, ownerIndex?: string, renderOrder?: number }[], rectangles?: { x: number, y: number, width: number, height: number, color: string, fill: string, isSolid: boolean, transparent: boolean, lineWidth: number, ownerIndex?: string, renderOrder?: number }[], regions?: { x: number, y: number, width: number, height: number, color: string, fill: string, renderOrder?: number }[], ellipses?: { x: number, y: number, radiusX: number, radiusY: number, color: string, fill: string, isSolid: boolean, transparent: boolean, lineWidth: number, ownerIndex?: string, renderOrder?: number }[], arcs?: { x: number, y: number, radius: number, startAngle: number, endAngle: number, color: string, width: number, ownerIndex?: string, renderOrder?: number }[], directives?: { x: number, y: number, color: string, name: string, orientation?: number }[], texts: { x: number, y: number, text: string, color: string, recordType?: string, style?: number, fontSize?: number, fontFamily?: string, fontWeight?: number, rotation?: number, sourceOrientation?: number, isMirrored?: boolean, anchor?: 'start' | 'middle' | 'end', powerPortDirection?: 'up' | 'down' | 'left' | 'right', cornerX?: number, cornerY?: number, fill?: string, borderColor?: string, isSolid?: boolean, showBorder?: boolean, textMargin?: number, noteLines?: string[] }[], components: { x: number, y: number, designator: string }[], pins?: { x: number, y: number, length: number, name: string, nameSegments?: { text: string, overline: boolean }[], designator: string, orientation: 'left' | 'right' | 'top' | 'bottom', electrical?: number, symbolOuter?: number, color: string, labelColor?: string, labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number', ownerIndex?: string }[], ports?: { x: number, y: number, width: number, height: number, name: string, fill: string, color: string, direction?: 'left' | 'right' | 'up' | 'down', shape?: 'single' | 'double' | 'plain' }[], crosses?: { x: number, y: number, size: number, color: string }[] } }} documentModel
|
|
32
32
|
* @returns {string}
|
|
33
33
|
*/
|
|
34
34
|
static render(documentModel) {
|
|
@@ -461,7 +461,7 @@ export class SchematicSvgRenderer {
|
|
|
461
461
|
/**
|
|
462
462
|
* Builds one schematic line segment, preserving dashed line styles when
|
|
463
463
|
* the source primitive requests them.
|
|
464
|
-
* @param {{ x1: number, y1: number, x2: number, y2: number, color: string, width: number, lineStyle?: number, isBus?: boolean }} line
|
|
464
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number, color: string, width: number, lineStyle?: number, isBus?: boolean, recordType?: string }} line
|
|
465
465
|
* @param {number} sheetHeight
|
|
466
466
|
* @returns {string}
|
|
467
467
|
*/
|