altium-toolkit 1.1.2 → 1.1.3
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/AltiumLayoutParser.mjs +275 -13
- package/src/core/altium/AltiumParser.mjs +240 -8
- package/src/core/altium/PcbEmbeddedFontExtractor.mjs +186 -43
- 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/SchematicImageParser.mjs +291 -6
- package/src/core/altium/SchematicMultipartDesignatorNormalizer.mjs +164 -0
- package/src/core/altium/SchematicMultipartOwnerMatcher.mjs +2 -0
- package/src/core/altium/SchematicPinParser.mjs +175 -4
- package/src/core/altium/SchematicSheetStyleResolver.mjs +38 -0
- package/src/core/altium/SchematicTextParser.mjs +125 -11
- package/src/core/altium/SchematicTextPostProcessor.mjs +146 -102
- package/src/ui/SchematicColorResolver.mjs +78 -0
- package/src/ui/SchematicContentLayout.mjs +58 -1
- package/src/ui/SchematicImageRenderer.mjs +125 -10
- package/src/ui/SchematicJunctionRenderer.mjs +1 -1
- package/src/ui/SchematicNativeFooterPartitioner.mjs +275 -0
- package/src/ui/SchematicNoteRenderer.mjs +82 -6
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +292 -3
- package/src/ui/SchematicPinSvgRenderer.mjs +197 -15
- 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 +82 -23
- package/src/ui/SchematicSvgRenderer.mjs +293 -47
|
@@ -12,10 +12,15 @@ export class SchematicPinParser {
|
|
|
12
12
|
/**
|
|
13
13
|
* Normalizes schematic pin records into drawable pin primitives.
|
|
14
14
|
* @param {{ fields: Record<string, string | string[]> }[]} records
|
|
15
|
+
* @param {{ ownerDrawnInternalPinOwners?: Set<string>, numericEndpointLabelOwners?: Set<string> }} [options]
|
|
15
16
|
* @returns {{ 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 }[]}
|
|
16
17
|
*/
|
|
17
|
-
static parseSchematicPins(records) {
|
|
18
|
+
static parseSchematicPins(records, options = {}) {
|
|
18
19
|
const groups = new Map()
|
|
20
|
+
const ownerDrawnInternalPinOwners =
|
|
21
|
+
options.ownerDrawnInternalPinOwners || new Set()
|
|
22
|
+
const numericEndpointLabelOwners =
|
|
23
|
+
options.numericEndpointLabelOwners || new Set()
|
|
19
24
|
|
|
20
25
|
for (const record of records) {
|
|
21
26
|
const ownerIndex = ParserUtils.getField(record.fields, 'OwnerIndex')
|
|
@@ -81,7 +86,11 @@ export class SchematicPinParser {
|
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
return [...groups.values()].flatMap((pins) =>
|
|
84
|
-
SchematicPinParser.#normalizeSchematicPinGroup(
|
|
89
|
+
SchematicPinParser.#normalizeSchematicPinGroup(
|
|
90
|
+
pins,
|
|
91
|
+
ownerDrawnInternalPinOwners,
|
|
92
|
+
numericEndpointLabelOwners
|
|
93
|
+
)
|
|
85
94
|
)
|
|
86
95
|
}
|
|
87
96
|
|
|
@@ -730,9 +739,15 @@ export class SchematicPinParser {
|
|
|
730
739
|
/**
|
|
731
740
|
* Deduces the visible pins for one schematic symbol owner.
|
|
732
741
|
* @param {{ x: number, y: number, length: number, conglomerate?: number, name: string, nameSegments?: { text: string, overline: boolean }[], designator: string, orientation: 'left' | 'right' | 'top' | 'bottom', electrical?: number, symbolOuter?: number, color?: string, labelColor?: string, ownerIndex: string }[]} pins
|
|
742
|
+
* @param {Set<string>} ownerDrawnInternalPinOwners
|
|
743
|
+
* @param {Set<string>} numericEndpointLabelOwners
|
|
733
744
|
* @returns {{ 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 }[]}
|
|
734
745
|
*/
|
|
735
|
-
static #normalizeSchematicPinGroup(
|
|
746
|
+
static #normalizeSchematicPinGroup(
|
|
747
|
+
pins,
|
|
748
|
+
ownerDrawnInternalPinOwners,
|
|
749
|
+
numericEndpointLabelOwners
|
|
750
|
+
) {
|
|
736
751
|
const deduped = SchematicPinParser.#dedupeSchematicPins(pins)
|
|
737
752
|
const inferredSequentialDesignators =
|
|
738
753
|
SchematicPinDesignatorInferer.inferSequentialCompactFourPinDesignators(
|
|
@@ -773,6 +788,7 @@ export class SchematicPinParser {
|
|
|
773
788
|
/^\d+$/.test(String(pin.designator || '').trim()) &&
|
|
774
789
|
(!pin.name || /^\d+$/.test(String(pin.name || '').trim()))
|
|
775
790
|
)
|
|
791
|
+
const ownerIndex = normalizedPins[0]?.ownerIndex || ''
|
|
776
792
|
let labelMode = 'name-and-number'
|
|
777
793
|
|
|
778
794
|
if (
|
|
@@ -795,12 +811,26 @@ export class SchematicPinParser {
|
|
|
795
811
|
labelMode = 'number-only'
|
|
796
812
|
}
|
|
797
813
|
|
|
798
|
-
if (
|
|
814
|
+
if (
|
|
815
|
+
numericEndpointLabelOwners.has(ownerIndex) &&
|
|
816
|
+
SchematicPinParser.#isTwoPinNumericEndpointGroup(normalizedPins)
|
|
817
|
+
) {
|
|
818
|
+
labelMode = 'number-only'
|
|
819
|
+
} else if (allPassive && normalizedPins.length <= 2) {
|
|
799
820
|
labelMode = SchematicPinParser.#isCanonicalPassiveTwoPinGroup(
|
|
800
821
|
normalizedPins
|
|
801
822
|
)
|
|
802
823
|
? 'hidden'
|
|
803
824
|
: 'number-only'
|
|
825
|
+
} else if (
|
|
826
|
+
ownerDrawnInternalPinOwners.has(ownerIndex) &&
|
|
827
|
+
SchematicPinParser.#isCompactNumberedFetTerminalGroup(
|
|
828
|
+
normalizedPins,
|
|
829
|
+
semanticNames,
|
|
830
|
+
orientationCount
|
|
831
|
+
)
|
|
832
|
+
) {
|
|
833
|
+
labelMode = 'number-only'
|
|
804
834
|
} else if (
|
|
805
835
|
SchematicPinParser.#isOwnerDrawnTerminalGlyphGroup(
|
|
806
836
|
normalizedPins,
|
|
@@ -809,6 +839,24 @@ export class SchematicPinParser {
|
|
|
809
839
|
)
|
|
810
840
|
) {
|
|
811
841
|
labelMode = 'hidden'
|
|
842
|
+
} else if (
|
|
843
|
+
ownerDrawnInternalPinOwners.has(ownerIndex) &&
|
|
844
|
+
SchematicPinParser.#isCompactInternalTerminalGroup(
|
|
845
|
+
normalizedPins,
|
|
846
|
+
names,
|
|
847
|
+
orientationCount
|
|
848
|
+
)
|
|
849
|
+
) {
|
|
850
|
+
labelMode = 'hidden'
|
|
851
|
+
} else if (
|
|
852
|
+
ownerDrawnInternalPinOwners.has(ownerIndex) &&
|
|
853
|
+
SchematicPinParser.#isCompactTwoPinInternalTerminalGroup(
|
|
854
|
+
normalizedPins,
|
|
855
|
+
names,
|
|
856
|
+
orientationCount
|
|
857
|
+
)
|
|
858
|
+
) {
|
|
859
|
+
labelMode = 'number-only'
|
|
812
860
|
} else if (!semanticNames.length && orientationCount <= 2) {
|
|
813
861
|
labelMode = 'number-only'
|
|
814
862
|
} else if (
|
|
@@ -827,6 +875,33 @@ export class SchematicPinParser {
|
|
|
827
875
|
}))
|
|
828
876
|
}
|
|
829
877
|
|
|
878
|
+
/**
|
|
879
|
+
* Returns true when a compact owner-drawn FET body uses semantic terminal
|
|
880
|
+
* names internally but still exposes external numeric contact labels.
|
|
881
|
+
* @param {{ designator: string, name: string, orientation: 'left' | 'right' | 'top' | 'bottom' }[]} pins
|
|
882
|
+
* @param {string[]} semanticNames
|
|
883
|
+
* @param {number} orientationCount
|
|
884
|
+
* @returns {boolean}
|
|
885
|
+
*/
|
|
886
|
+
static #isCompactNumberedFetTerminalGroup(
|
|
887
|
+
pins,
|
|
888
|
+
semanticNames,
|
|
889
|
+
orientationCount
|
|
890
|
+
) {
|
|
891
|
+
if (
|
|
892
|
+
pins.length !== 4 ||
|
|
893
|
+
orientationCount < 3 ||
|
|
894
|
+
semanticNames.length !== pins.length ||
|
|
895
|
+
!pins.every((pin) => /^\d+$/.test(String(pin.designator || '')))
|
|
896
|
+
) {
|
|
897
|
+
return false
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
return semanticNames.every((name) =>
|
|
901
|
+
SchematicPinParser.#isFetTerminalName(name)
|
|
902
|
+
)
|
|
903
|
+
}
|
|
904
|
+
|
|
830
905
|
/**
|
|
831
906
|
* Returns true when a compact multi-side owner has transistor-like terminal
|
|
832
907
|
* letters that are part of the drawn symbol body, not external pin labels.
|
|
@@ -858,6 +933,60 @@ export class SchematicPinParser {
|
|
|
858
933
|
)
|
|
859
934
|
}
|
|
860
935
|
|
|
936
|
+
/**
|
|
937
|
+
* Returns true when a compact owner-drawn body carries repeated internal
|
|
938
|
+
* terminal names that belong to the symbol body, not external labels.
|
|
939
|
+
* @param {{ designator: string, name: string, orientation: 'left' | 'right' | 'top' | 'bottom' }[]} pins
|
|
940
|
+
* @param {string[]} names
|
|
941
|
+
* @param {number} orientationCount
|
|
942
|
+
* @returns {boolean}
|
|
943
|
+
*/
|
|
944
|
+
static #isCompactInternalTerminalGroup(pins, names, orientationCount) {
|
|
945
|
+
if (
|
|
946
|
+
pins.length < 3 ||
|
|
947
|
+
pins.length > 4 ||
|
|
948
|
+
orientationCount < 3 ||
|
|
949
|
+
names.length >= pins.length ||
|
|
950
|
+
!SchematicPinParser.#hasOptionalNumericPinDesignators(pins)
|
|
951
|
+
) {
|
|
952
|
+
return false
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
return (
|
|
956
|
+
names.length > 0 &&
|
|
957
|
+
names.every((name) =>
|
|
958
|
+
SchematicPinParser.#isInternalTerminalName(name)
|
|
959
|
+
)
|
|
960
|
+
)
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Returns true when a compact two-pin owner-drawn symbol stores internal
|
|
965
|
+
* placeholder terminal names that should not be rendered as labels.
|
|
966
|
+
* @param {{ designator: string, name: string, orientation: 'left' | 'right' | 'top' | 'bottom' }[]} pins
|
|
967
|
+
* @param {string[]} names
|
|
968
|
+
* @param {number} orientationCount
|
|
969
|
+
* @returns {boolean}
|
|
970
|
+
*/
|
|
971
|
+
static #isCompactTwoPinInternalTerminalGroup(
|
|
972
|
+
pins,
|
|
973
|
+
names,
|
|
974
|
+
orientationCount
|
|
975
|
+
) {
|
|
976
|
+
if (
|
|
977
|
+
pins.length !== 2 ||
|
|
978
|
+
orientationCount < 2 ||
|
|
979
|
+
names.length !== pins.length ||
|
|
980
|
+
!SchematicPinParser.#hasOptionalNumericPinDesignators(pins)
|
|
981
|
+
) {
|
|
982
|
+
return false
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
return names.every((name) =>
|
|
986
|
+
SchematicPinParser.#isInternalTerminalName(name)
|
|
987
|
+
)
|
|
988
|
+
}
|
|
989
|
+
|
|
861
990
|
/**
|
|
862
991
|
* Returns true when compact owner-drawn terminal glyph pins have either no
|
|
863
992
|
* external designators or ordinary numeric pin numbers.
|
|
@@ -881,6 +1010,28 @@ export class SchematicPinParser {
|
|
|
881
1010
|
return /^[BCDEGS]$/i.test(String(name || '').trim())
|
|
882
1011
|
}
|
|
883
1012
|
|
|
1013
|
+
/**
|
|
1014
|
+
* Returns true for FET terminal names, including numbered gate/source
|
|
1015
|
+
* variants used by dual-gate symbols.
|
|
1016
|
+
* @param {string} name
|
|
1017
|
+
* @returns {boolean}
|
|
1018
|
+
*/
|
|
1019
|
+
static #isFetTerminalName(name) {
|
|
1020
|
+
return /^(?:[DS]|[GS]\d*)$/i.test(String(name || '').trim())
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Returns true for compact internal terminal labels usually drawn inside
|
|
1025
|
+
* owner-authored symbol bodies.
|
|
1026
|
+
* @param {string} name
|
|
1027
|
+
* @returns {boolean}
|
|
1028
|
+
*/
|
|
1029
|
+
static #isInternalTerminalName(name) {
|
|
1030
|
+
return /^(x|y|gnd|agnd|dgnd|pgnd|vcc|vdd|vee|vss|nc)$/i.test(
|
|
1031
|
+
String(name || '').trim()
|
|
1032
|
+
)
|
|
1033
|
+
}
|
|
1034
|
+
|
|
884
1035
|
/**
|
|
885
1036
|
* Returns true when one passive two-pin symbol uses the ordinary 1/2 pin
|
|
886
1037
|
* numbering that should stay hidden for simple resistor-like parts.
|
|
@@ -899,6 +1050,24 @@ export class SchematicPinParser {
|
|
|
899
1050
|
return designators[0] === '1' && designators[1] === '2'
|
|
900
1051
|
}
|
|
901
1052
|
|
|
1053
|
+
/**
|
|
1054
|
+
* Returns true when one owner exposes exactly two numeric endpoints.
|
|
1055
|
+
* @param {{ designator: string, name: string }[]} pins
|
|
1056
|
+
* @returns {boolean}
|
|
1057
|
+
*/
|
|
1058
|
+
static #isTwoPinNumericEndpointGroup(pins) {
|
|
1059
|
+
if (pins.length !== 2) {
|
|
1060
|
+
return false
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
return pins.every((pin) => {
|
|
1064
|
+
const designator = String(pin.designator || '').trim()
|
|
1065
|
+
const name = String(pin.name || '').trim()
|
|
1066
|
+
|
|
1067
|
+
return /^\d+$/.test(designator) && (!name || /^\d+$/.test(name))
|
|
1068
|
+
})
|
|
1069
|
+
}
|
|
1070
|
+
|
|
902
1071
|
/**
|
|
903
1072
|
* Returns true when one owner uses the dense two-sided horizontal 48/50
|
|
904
1073
|
* pin family whose semantic names belong to the owner-drawn symbol body
|
|
@@ -1049,10 +1218,12 @@ export class SchematicPinParser {
|
|
|
1049
1218
|
static #inferSchematicPinOrientation(conglomerate) {
|
|
1050
1219
|
switch (conglomerate) {
|
|
1051
1220
|
case 34:
|
|
1221
|
+
case 42:
|
|
1052
1222
|
case 50:
|
|
1053
1223
|
case 58:
|
|
1054
1224
|
return 'left'
|
|
1055
1225
|
case 32:
|
|
1226
|
+
case 40:
|
|
1056
1227
|
case 48:
|
|
1057
1228
|
case 56:
|
|
1058
1229
|
return 'right'
|
|
@@ -43,4 +43,42 @@ export class SchematicSheetStyleResolver {
|
|
|
43
43
|
|
|
44
44
|
return configuredXZones
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolves the displayed vertical sheet-zone count after the page size
|
|
49
|
+
* has been normalized.
|
|
50
|
+
* @param {{ width: number, height: number, yZones: number, paperSize?: string, sheetStyle?: number }} sheet
|
|
51
|
+
* @returns {number}
|
|
52
|
+
*/
|
|
53
|
+
static resolveYZones(sheet) {
|
|
54
|
+
const configuredYZones = Math.max(Number(sheet?.yZones || 0), 1)
|
|
55
|
+
const paperSize = String(sheet?.paperSize || '')
|
|
56
|
+
.trim()
|
|
57
|
+
.toUpperCase()
|
|
58
|
+
|
|
59
|
+
if (Number(sheet?.sheetStyle || 0) !== 1 && !paperSize) {
|
|
60
|
+
return configuredYZones
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const width = Number(sheet?.width || 0)
|
|
64
|
+
const height = Number(sheet?.height || 0)
|
|
65
|
+
if (height < width) {
|
|
66
|
+
return configuredYZones
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
paperSize === 'A2' ||
|
|
71
|
+
(width === 1654 && height === 2339) ||
|
|
72
|
+
paperSize === 'A3' ||
|
|
73
|
+
(width === 1169 && height === 1654)
|
|
74
|
+
) {
|
|
75
|
+
return 8
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (paperSize === 'A4' || (width === 827 && height === 1169)) {
|
|
79
|
+
return 4
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return configuredYZones
|
|
83
|
+
}
|
|
46
84
|
}
|
|
@@ -17,6 +17,10 @@ export class SchematicTextParser {
|
|
|
17
17
|
const metadata = {}
|
|
18
18
|
|
|
19
19
|
for (const record of records) {
|
|
20
|
+
if (ParserUtils.getField(record.fields, 'OwnerIndex')) {
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
const name = ParserUtils.getField(record.fields, 'Name').trim()
|
|
21
25
|
const value = ParserUtils.getDisplayText(record.fields)
|
|
22
26
|
|
|
@@ -30,6 +34,39 @@ export class SchematicTextParser {
|
|
|
30
34
|
return metadata
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Extracts owner-local parameter values used by component text templates.
|
|
39
|
+
* @param {{ fields: Record<string, string | string[]> }[]} records
|
|
40
|
+
* @returns {Map<string, Record<string, string>>}
|
|
41
|
+
*/
|
|
42
|
+
static extractSchematicOwnerMetadata(records) {
|
|
43
|
+
const ownerMetadata = new Map()
|
|
44
|
+
|
|
45
|
+
for (const record of records) {
|
|
46
|
+
const ownerIndex = ParserUtils.getField(record.fields, 'OwnerIndex')
|
|
47
|
+
const name = ParserUtils.getField(record.fields, 'Name').trim()
|
|
48
|
+
const value = ParserUtils.getDisplayText(record.fields)
|
|
49
|
+
|
|
50
|
+
if (
|
|
51
|
+
!ownerIndex ||
|
|
52
|
+
!name ||
|
|
53
|
+
!value ||
|
|
54
|
+
value === '*' ||
|
|
55
|
+
String(value).trim().startsWith('=')
|
|
56
|
+
) {
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!ownerMetadata.has(ownerIndex)) {
|
|
61
|
+
ownerMetadata.set(ownerIndex, {})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
ownerMetadata.get(ownerIndex)[name.toLowerCase()] = value
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return ownerMetadata
|
|
68
|
+
}
|
|
69
|
+
|
|
33
70
|
/**
|
|
34
71
|
* Builds a font table from the sheet header.
|
|
35
72
|
* @param {Record<string, string | string[]> | undefined} fields
|
|
@@ -96,18 +133,30 @@ export class SchematicTextParser {
|
|
|
96
133
|
* @param {Record<string, string>} metadata
|
|
97
134
|
* @param {{ width: number, marginWidth: number, titleBlockOn?: boolean }} sheet
|
|
98
135
|
* @param {Record<string, { size: number, family: string, bold: boolean, italic?: boolean, rotation: number }>} fonts
|
|
136
|
+
* @param {Map<string, Record<string, string>>} [ownerMetadata]
|
|
99
137
|
* @returns {{ x: number, y: number, text: string, color: string, hidden: boolean, name: string, ownerIndex?: string, recordType: string, style: number, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string, 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[] } | null}
|
|
100
138
|
*/
|
|
101
|
-
static normalizeSchematicTextRecord(
|
|
139
|
+
static normalizeSchematicTextRecord(
|
|
140
|
+
fields,
|
|
141
|
+
metadata,
|
|
142
|
+
sheet,
|
|
143
|
+
fonts,
|
|
144
|
+
ownerMetadata = new Map()
|
|
145
|
+
) {
|
|
102
146
|
const x = ParserUtils.parseNumericField(fields, 'Location.X')
|
|
103
147
|
const y = ParserUtils.parseNumericField(fields, 'Location.Y')
|
|
104
148
|
const hidden = ParserUtils.parseBoolean(fields.IsHidden)
|
|
105
149
|
const name = ParserUtils.getField(fields, 'Name')
|
|
106
150
|
const rawText = ParserUtils.getDisplayText(fields)
|
|
107
151
|
const recordType = ParserUtils.getField(fields, 'RECORD')
|
|
152
|
+
const ownerIndex = ParserUtils.getField(fields, 'OwnerIndex')
|
|
108
153
|
const text = SchematicTextParser.#resolveSchematicTemplateText(
|
|
109
154
|
rawText,
|
|
110
|
-
|
|
155
|
+
SchematicTextParser.#resolveSchematicTextMetadata(
|
|
156
|
+
ownerIndex,
|
|
157
|
+
metadata,
|
|
158
|
+
ownerMetadata
|
|
159
|
+
)
|
|
111
160
|
)
|
|
112
161
|
|
|
113
162
|
if (hidden || x === null || y === null || !text) {
|
|
@@ -149,7 +198,7 @@ export class SchematicTextParser {
|
|
|
149
198
|
),
|
|
150
199
|
hidden,
|
|
151
200
|
name,
|
|
152
|
-
ownerIndex:
|
|
201
|
+
ownerIndex: ownerIndex || undefined,
|
|
153
202
|
recordType,
|
|
154
203
|
style: ParserUtils.parseNumericField(fields, 'Style') || 0,
|
|
155
204
|
renderOrder:
|
|
@@ -476,6 +525,21 @@ export class SchematicTextParser {
|
|
|
476
525
|
return replacement ? replacement : normalized
|
|
477
526
|
}
|
|
478
527
|
|
|
528
|
+
/**
|
|
529
|
+
* Chooses the parameter map for one text record.
|
|
530
|
+
* @param {string} ownerIndex
|
|
531
|
+
* @param {Record<string, string>} metadata
|
|
532
|
+
* @param {Map<string, Record<string, string>>} ownerMetadata
|
|
533
|
+
* @returns {Record<string, string>}
|
|
534
|
+
*/
|
|
535
|
+
static #resolveSchematicTextMetadata(ownerIndex, metadata, ownerMetadata) {
|
|
536
|
+
if (!ownerIndex) {
|
|
537
|
+
return metadata
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return ownerMetadata.get(ownerIndex) || {}
|
|
541
|
+
}
|
|
542
|
+
|
|
479
543
|
/**
|
|
480
544
|
* Returns true when a text record is metadata rather than sheet content.
|
|
481
545
|
* @param {Record<string, string | string[]>} fields
|
|
@@ -510,7 +574,6 @@ export class SchematicTextParser {
|
|
|
510
574
|
if (nonDrawableNames.has(normalizedName)) return true
|
|
511
575
|
if (/uniqueid$/i.test(normalizedName)) return true
|
|
512
576
|
if (!normalizedText || normalizedText === '*') return true
|
|
513
|
-
if (/^=/.test(normalizedText)) return true
|
|
514
577
|
if (
|
|
515
578
|
sheet.titleBlockOn &&
|
|
516
579
|
SchematicTextParser.isTitleBlockFooterRecord(fields, sheet.width)
|
|
@@ -537,10 +600,47 @@ export class SchematicTextParser {
|
|
|
537
600
|
|
|
538
601
|
if (recordType === '17') return 'middle'
|
|
539
602
|
if (explicitAnchor) return explicitAnchor
|
|
603
|
+
if (
|
|
604
|
+
SchematicTextParser.#shouldCenterSchematicNoteByDefault(
|
|
605
|
+
fields,
|
|
606
|
+
recordType,
|
|
607
|
+
text
|
|
608
|
+
)
|
|
609
|
+
) {
|
|
610
|
+
return 'middle'
|
|
611
|
+
}
|
|
540
612
|
|
|
541
613
|
return 'start'
|
|
542
614
|
}
|
|
543
615
|
|
|
616
|
+
/**
|
|
617
|
+
* Returns true for Altium text frames that encode centered one-line labels
|
|
618
|
+
* without carrying an explicit justification field.
|
|
619
|
+
* @param {Record<string, string | string[]>} fields
|
|
620
|
+
* @param {string} recordType
|
|
621
|
+
* @param {string} text
|
|
622
|
+
* @returns {boolean}
|
|
623
|
+
*/
|
|
624
|
+
static #shouldCenterSchematicNoteByDefault(fields, recordType, text) {
|
|
625
|
+
if (recordType !== '209' && recordType !== '28') return false
|
|
626
|
+
if (String(text || '').includes('~1')) return false
|
|
627
|
+
if (
|
|
628
|
+
ParserUtils.parseNumericField(fields, 'Corner.X') === null ||
|
|
629
|
+
ParserUtils.parseNumericField(fields, 'Corner.Y') === null
|
|
630
|
+
) {
|
|
631
|
+
return false
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (recordType === '28') {
|
|
635
|
+
return ParserUtils.parseBoolean(fields.WordWrap)
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return (
|
|
639
|
+
ParserUtils.parseBoolean(fields.WordWrap) &&
|
|
640
|
+
ParserUtils.parseBoolean(fields.ClipToRect)
|
|
641
|
+
)
|
|
642
|
+
}
|
|
643
|
+
|
|
544
644
|
/**
|
|
545
645
|
* Decodes Altium's three-column text justification grid into one
|
|
546
646
|
* horizontal SVG text anchor.
|
|
@@ -548,23 +648,37 @@ export class SchematicTextParser {
|
|
|
548
648
|
* @returns {'start' | 'middle' | 'end' | null}
|
|
549
649
|
*/
|
|
550
650
|
static #resolveSchematicTextJustificationAnchor(fields) {
|
|
551
|
-
const justification =
|
|
552
|
-
fields,
|
|
553
|
-
'
|
|
554
|
-
)
|
|
651
|
+
const justification =
|
|
652
|
+
ParserUtils.parseNumericField(fields, 'Justification') ??
|
|
653
|
+
ParserUtils.parseNumericField(fields, 'Alignment')
|
|
555
654
|
|
|
556
655
|
if (justification === null) {
|
|
557
656
|
return null
|
|
558
657
|
}
|
|
559
658
|
|
|
659
|
+
let anchor = 'start'
|
|
560
660
|
switch (((justification % 3) + 3) % 3) {
|
|
561
661
|
case 1:
|
|
562
|
-
|
|
662
|
+
anchor = 'middle'
|
|
663
|
+
break
|
|
563
664
|
case 2:
|
|
564
|
-
|
|
665
|
+
anchor = 'end'
|
|
666
|
+
break
|
|
565
667
|
default:
|
|
566
|
-
|
|
668
|
+
anchor = 'start'
|
|
567
669
|
}
|
|
670
|
+
|
|
671
|
+
if (
|
|
672
|
+
anchor !== 'middle' &&
|
|
673
|
+
ParserUtils.getField(fields, 'RECORD') === '4' &&
|
|
674
|
+
ParserUtils.getField(fields, 'OwnerIndex') &&
|
|
675
|
+
ParserUtils.parseBoolean(fields.IsMirrored) &&
|
|
676
|
+
ParserUtils.parseNumericField(fields, 'Orientation') === 2
|
|
677
|
+
) {
|
|
678
|
+
return anchor === 'start' ? 'end' : 'start'
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return anchor
|
|
568
682
|
}
|
|
569
683
|
|
|
570
684
|
/**
|