altium-toolkit 1.1.1 → 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/NaturalStringComparator.mjs +175 -0
- package/src/core/altium/ParserUtils.mjs +83 -14
- package/src/core/altium/PcbComponentPrimitiveIndexer.mjs +104 -33
- package/src/core/altium/PcbEmbeddedFontExtractor.mjs +186 -43
- package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +26 -4
- package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +28 -5
- package/src/core/altium/PcbOwnershipGraphBuilder.mjs +1 -3
- package/src/core/altium/PcbReviewMetadataBuilder.mjs +59 -28
- package/src/core/altium/PcbReviewRouteHighlightProfileBuilder.mjs +66 -29
- package/src/core/altium/PcbRouteAnalysisBuilder.mjs +50 -16
- 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/SchematicDisplayModeCatalogParser.mjs +36 -11
- package/src/core/altium/SchematicImageParser.mjs +291 -6
- package/src/core/altium/SchematicImplementationParser.mjs +75 -36
- 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/SchematicQaReportBuilder.mjs +115 -38
- 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/PcbInteractionIndex.mjs +16 -1
- 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'
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { ParserUtils } from './ParserUtils.mjs'
|
|
6
6
|
|
|
7
|
-
const {
|
|
8
|
-
ParserUtils
|
|
7
|
+
const { parseBoolean, parseNumericField, toColor } = ParserUtils
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Builds deterministic read-only QA summaries for schematic documents.
|
|
@@ -25,12 +24,17 @@ export class SchematicQaReportBuilder {
|
|
|
25
24
|
const lineWidths = SchematicQaReportBuilder.#lineWidths(records)
|
|
26
25
|
const unresolvedParameters =
|
|
27
26
|
SchematicQaReportBuilder.#unresolvedParameters(records)
|
|
27
|
+
const titleBlockResidue =
|
|
28
|
+
SchematicQaReportBuilder.#titleBlockResidue(records)
|
|
28
29
|
const findings = [
|
|
29
30
|
...SchematicQaReportBuilder.#fontFindings(fonts),
|
|
30
31
|
...SchematicQaReportBuilder.#unresolvedFindings(
|
|
31
32
|
unresolvedParameters
|
|
32
33
|
),
|
|
33
|
-
...SchematicQaReportBuilder.#titleBlockFindings(
|
|
34
|
+
...SchematicQaReportBuilder.#titleBlockFindings(
|
|
35
|
+
records,
|
|
36
|
+
titleBlockResidue
|
|
37
|
+
)
|
|
34
38
|
]
|
|
35
39
|
|
|
36
40
|
return {
|
|
@@ -47,8 +51,7 @@ export class SchematicQaReportBuilder {
|
|
|
47
51
|
colors,
|
|
48
52
|
lineWidths,
|
|
49
53
|
unresolvedParameters,
|
|
50
|
-
titleBlockResidue
|
|
51
|
-
SchematicQaReportBuilder.#titleBlockResidue(records),
|
|
54
|
+
titleBlockResidue,
|
|
52
55
|
geometryFallbacks: SchematicQaReportBuilder.#geometryFallbacks(
|
|
53
56
|
input?.texts
|
|
54
57
|
),
|
|
@@ -138,26 +141,15 @@ export class SchematicQaReportBuilder {
|
|
|
138
141
|
* @returns {string[]}
|
|
139
142
|
*/
|
|
140
143
|
static #unresolvedParameters(records) {
|
|
141
|
-
const metadata = new Set(
|
|
142
|
-
(records || [])
|
|
143
|
-
.filter((record) => getField(record.fields, 'RECORD') === '41')
|
|
144
|
-
.map((record) => getField(record.fields, 'Name'))
|
|
145
|
-
.filter(Boolean)
|
|
146
|
-
)
|
|
147
144
|
const unresolved = []
|
|
148
145
|
|
|
149
146
|
for (const record of records || []) {
|
|
150
|
-
const text =
|
|
147
|
+
const text = SchematicQaReportBuilder.#displayText(record.fields)
|
|
151
148
|
const match = text.match(/^=([A-Za-z_][\w.]*)$/)
|
|
152
149
|
if (!match) {
|
|
153
150
|
continue
|
|
154
151
|
}
|
|
155
|
-
|
|
156
|
-
!metadata.has(match[1]) ||
|
|
157
|
-
text === getDisplayText(record.fields)
|
|
158
|
-
) {
|
|
159
|
-
unresolved.push(match[1])
|
|
160
|
-
}
|
|
152
|
+
unresolved.push(match[1])
|
|
161
153
|
}
|
|
162
154
|
|
|
163
155
|
return [...new Set(unresolved)].sort()
|
|
@@ -200,23 +192,25 @@ export class SchematicQaReportBuilder {
|
|
|
200
192
|
/**
|
|
201
193
|
* Builds title-block findings.
|
|
202
194
|
* @param {object[]} records Schematic records.
|
|
195
|
+
* @param {object[]} titleBlockResidue Hidden title-block residue rows.
|
|
203
196
|
* @returns {object[]}
|
|
204
197
|
*/
|
|
205
|
-
static #titleBlockFindings(records) {
|
|
198
|
+
static #titleBlockFindings(records, titleBlockResidue) {
|
|
206
199
|
const sheet = (records || []).find(
|
|
207
|
-
(record) =>
|
|
200
|
+
(record) =>
|
|
201
|
+
SchematicQaReportBuilder.#field(record.fields, 'RECORD') ===
|
|
202
|
+
'31'
|
|
208
203
|
)
|
|
209
204
|
if (!sheet || parseBoolean(sheet.fields.TitleBlockOn)) {
|
|
210
205
|
return []
|
|
211
206
|
}
|
|
212
207
|
|
|
213
|
-
|
|
214
|
-
return residue.length
|
|
208
|
+
return titleBlockResidue.length
|
|
215
209
|
? [
|
|
216
210
|
{
|
|
217
211
|
code: 'schematic.title-block.hidden-residue',
|
|
218
212
|
severity: 'info',
|
|
219
|
-
count:
|
|
213
|
+
count: titleBlockResidue.length,
|
|
220
214
|
message:
|
|
221
215
|
'Hidden title-block parameter records remain while the title block is disabled.'
|
|
222
216
|
}
|
|
@@ -240,22 +234,32 @@ export class SchematicQaReportBuilder {
|
|
|
240
234
|
'drawnby'
|
|
241
235
|
])
|
|
242
236
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
237
|
+
const residue = []
|
|
238
|
+
|
|
239
|
+
for (const record of records || []) {
|
|
240
|
+
const fields = record.fields || {}
|
|
241
|
+
if (
|
|
242
|
+
SchematicQaReportBuilder.#field(fields, 'RECORD') !== '41' ||
|
|
243
|
+
!parseBoolean(
|
|
244
|
+
SchematicQaReportBuilder.#rawField(fields, 'IsHidden')
|
|
245
|
+
)
|
|
246
|
+
) {
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const name = SchematicQaReportBuilder.#field(fields, 'Name')
|
|
251
|
+
if (!titleBlockNames.has(name.replace(/\s+/g, '').toLowerCase())) {
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
residue.push({
|
|
255
256
|
recordKey: SchematicQaReportBuilder.#recordKey(record),
|
|
256
|
-
name
|
|
257
|
-
value:
|
|
258
|
-
})
|
|
257
|
+
name,
|
|
258
|
+
value: SchematicQaReportBuilder.#displayText(fields)
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return residue
|
|
259
263
|
}
|
|
260
264
|
|
|
261
265
|
/**
|
|
@@ -281,4 +285,77 @@ export class SchematicQaReportBuilder {
|
|
|
281
285
|
static #recordKey(record) {
|
|
282
286
|
return 'schematic-record-' + String(record?.recordIndex ?? 0)
|
|
283
287
|
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Reads one common field without invoking generic field-cache bookkeeping.
|
|
291
|
+
* @param {Record<string, string | string[]> | undefined} fields Record fields.
|
|
292
|
+
* @param {string} key Requested key.
|
|
293
|
+
* @returns {string}
|
|
294
|
+
*/
|
|
295
|
+
static #field(fields, key) {
|
|
296
|
+
return SchematicQaReportBuilder.#pickFieldValue(
|
|
297
|
+
SchematicQaReportBuilder.#rawField(fields, key),
|
|
298
|
+
false
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Returns the best schematic display text without shared field-cache overhead.
|
|
304
|
+
* @param {Record<string, string | string[]> | undefined} fields Record fields.
|
|
305
|
+
* @returns {string}
|
|
306
|
+
*/
|
|
307
|
+
static #displayText(fields) {
|
|
308
|
+
return (
|
|
309
|
+
SchematicQaReportBuilder.#pickFieldValue(
|
|
310
|
+
SchematicQaReportBuilder.#rawField(fields, 'UTF8:Text'),
|
|
311
|
+
true
|
|
312
|
+
) ||
|
|
313
|
+
SchematicQaReportBuilder.#pickFieldValue(
|
|
314
|
+
SchematicQaReportBuilder.#rawField(fields, 'Text'),
|
|
315
|
+
true
|
|
316
|
+
)
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Reads one raw field from parsed records or simple fixture objects.
|
|
322
|
+
* @param {Record<string, string | string[]> | undefined} fields Record fields.
|
|
323
|
+
* @param {string} key Requested key.
|
|
324
|
+
* @returns {string | string[] | undefined}
|
|
325
|
+
*/
|
|
326
|
+
static #rawField(fields, key) {
|
|
327
|
+
if (!fields || typeof fields !== 'object') {
|
|
328
|
+
return undefined
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const direct = fields[key]
|
|
332
|
+
if (direct !== undefined) {
|
|
333
|
+
return direct
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const upperKey = key.toUpperCase()
|
|
337
|
+
return upperKey === key ? undefined : fields[upperKey]
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Returns the last meaningful value from one field payload.
|
|
342
|
+
* @param {string | string[] | undefined} raw Raw field payload.
|
|
343
|
+
* @param {boolean} skipAsterisk Whether placeholder asterisks are ignored.
|
|
344
|
+
* @returns {string}
|
|
345
|
+
*/
|
|
346
|
+
static #pickFieldValue(raw, skipAsterisk) {
|
|
347
|
+
if (!Array.isArray(raw)) {
|
|
348
|
+
const value = String(raw || '').trim()
|
|
349
|
+
return value && (!skipAsterisk || value !== '*') ? value : ''
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
for (let index = raw.length - 1; index >= 0; index -= 1) {
|
|
353
|
+
const value = String(raw[index] || '').trim()
|
|
354
|
+
if (value && (!skipAsterisk || value !== '*')) {
|
|
355
|
+
return value
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return ''
|
|
360
|
+
}
|
|
284
361
|
}
|
|
@@ -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
|
}
|