altium-toolkit 1.1.26 → 1.1.31
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 +34 -1
- package/src/core/altium/AltiumSchematicArcAngleNormalizer.mjs +82 -0
- package/src/core/altium/AltiumSchematicFreeGraphicStrokeNormalizer.mjs +105 -0
- package/src/core/altium/AltiumSchematicHiddenDesignatorResolver.mjs +191 -0
- package/src/core/altium/AltiumSchematicPackedImageResolver.mjs +433 -0
- package/src/core/altium/AltiumSchematicSheetBoundsNormalizer.mjs +287 -0
- package/src/parser.mjs +5 -0
- package/src/renderers.mjs +2 -0
- package/src/scene3d.mjs +1 -0
- package/src/ui/AltiumPcbBottomViewMirror.mjs +394 -0
- package/src/ui/AltiumScene3dAuthoredBodyAnchorAdapter.mjs +252 -0
- package/src/ui/AltiumScene3dBottomPadRotationAdapter.mjs +127 -0
- package/src/ui/AltiumScene3dComponentBodyAdapter.mjs +153 -0
- package/src/ui/AltiumScene3dExternalPlacementAdapter.mjs +919 -0
- package/src/ui/AltiumScene3dIdentityTokens.mjs +105 -0
- package/src/ui/AltiumScene3dPlacementRotationPolicy.mjs +357 -0
- package/src/ui/AltiumScene3dRepeatedModelOwnerRepair.mjs +576 -0
- package/src/ui/PcbFootprintPadAxisNormalizer.mjs +609 -0
- package/src/ui/PcbScene3dBoardOutlineRefiner.mjs +54 -11
- package/src/ui/PcbScene3dBuilder.mjs +76 -33
- package/src/ui/PcbScene3dPadLocalSpanResolver.mjs +109 -0
package/package.json
CHANGED
|
@@ -52,6 +52,11 @@ import { SchematicBindingProvenanceParser } from './SchematicBindingProvenancePa
|
|
|
52
52
|
import { SchematicConnectivityQaBuilder } from './SchematicConnectivityQaBuilder.mjs'
|
|
53
53
|
import { SchematicQaReportBuilder } from './SchematicQaReportBuilder.mjs'
|
|
54
54
|
import { SchematicWireNormalizer } from './SchematicWireNormalizer.mjs'
|
|
55
|
+
import { AltiumSchematicArcAngleNormalizer } from './AltiumSchematicArcAngleNormalizer.mjs'
|
|
56
|
+
import { AltiumSchematicFreeGraphicStrokeNormalizer } from './AltiumSchematicFreeGraphicStrokeNormalizer.mjs'
|
|
57
|
+
import { AltiumSchematicHiddenDesignatorResolver } from './AltiumSchematicHiddenDesignatorResolver.mjs'
|
|
58
|
+
import { AltiumSchematicPackedImageResolver } from './AltiumSchematicPackedImageResolver.mjs'
|
|
59
|
+
import { AltiumSchematicSheetBoundsNormalizer } from './AltiumSchematicSheetBoundsNormalizer.mjs'
|
|
55
60
|
import { CircuitJsonModelAdapter } from '../circuit-json/CircuitJsonModelAdapter.mjs'
|
|
56
61
|
const {
|
|
57
62
|
countMatchingKeys,
|
|
@@ -856,7 +861,7 @@ export class AltiumParser {
|
|
|
856
861
|
)
|
|
857
862
|
}
|
|
858
863
|
|
|
859
|
-
|
|
864
|
+
const documentModel = NormalizedModelSchema.attach({
|
|
860
865
|
kind: 'schematic',
|
|
861
866
|
fileType: 'SchDoc',
|
|
862
867
|
fileName,
|
|
@@ -935,6 +940,34 @@ export class AltiumParser {
|
|
|
935
940
|
},
|
|
936
941
|
bom
|
|
937
942
|
})
|
|
943
|
+
|
|
944
|
+
return AltiumParser.#prepareSchematicDocument(
|
|
945
|
+
documentModel,
|
|
946
|
+
arrayBuffer
|
|
947
|
+
)
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Applies universal schematic post-processing after the parser has built
|
|
952
|
+
* the normalized renderer model.
|
|
953
|
+
* @param {object} documentModel Parsed schematic document model.
|
|
954
|
+
* @param {ArrayBuffer} arrayBuffer Source file buffer.
|
|
955
|
+
* @returns {object}
|
|
956
|
+
*/
|
|
957
|
+
static #prepareSchematicDocument(documentModel, arrayBuffer) {
|
|
958
|
+
return AltiumSchematicPackedImageResolver.hydrate(
|
|
959
|
+
AltiumSchematicFreeGraphicStrokeNormalizer.normalize(
|
|
960
|
+
AltiumSchematicSheetBoundsNormalizer.normalize(
|
|
961
|
+
AltiumSchematicArcAngleNormalizer.normalize(
|
|
962
|
+
AltiumSchematicHiddenDesignatorResolver.annotate(
|
|
963
|
+
documentModel,
|
|
964
|
+
arrayBuffer
|
|
965
|
+
)
|
|
966
|
+
)
|
|
967
|
+
)
|
|
968
|
+
),
|
|
969
|
+
arrayBuffer
|
|
970
|
+
)
|
|
938
971
|
}
|
|
939
972
|
|
|
940
973
|
/**
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes Altium schematic arc angles before SVG rendering.
|
|
3
|
+
*/
|
|
4
|
+
export class AltiumSchematicArcAngleNormalizer {
|
|
5
|
+
static #FULL_CIRCLE_TOLERANCE = 0.001
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Rewrites non-full arcs to the shortest authored sweep.
|
|
9
|
+
* @param {object} documentModel Parsed document model.
|
|
10
|
+
* @returns {object}
|
|
11
|
+
*/
|
|
12
|
+
static normalize(documentModel) {
|
|
13
|
+
const arcs = documentModel?.schematic?.arcs
|
|
14
|
+
if (!Array.isArray(arcs)) {
|
|
15
|
+
return documentModel
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (const arc of arcs) {
|
|
19
|
+
AltiumSchematicArcAngleNormalizer.#normalizeArc(arc)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return documentModel
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Normalizes one arc's end angle while preserving full-circle arcs.
|
|
27
|
+
* @param {object} arc Arc primitive.
|
|
28
|
+
* @returns {void}
|
|
29
|
+
*/
|
|
30
|
+
static #normalizeArc(arc) {
|
|
31
|
+
const startAngle = Number(arc?.startAngle)
|
|
32
|
+
const endAngle = Number(arc?.endAngle)
|
|
33
|
+
if (!Number.isFinite(startAngle) || !Number.isFinite(endAngle)) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const delta =
|
|
38
|
+
AltiumSchematicArcAngleNormalizer.#normalizeSingleTurnDelta(
|
|
39
|
+
endAngle - startAngle
|
|
40
|
+
)
|
|
41
|
+
if (AltiumSchematicArcAngleNormalizer.#isFullCircleDelta(delta)) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (delta > 180) {
|
|
46
|
+
arc.endAngle = endAngle - 360
|
|
47
|
+
} else if (delta < -180) {
|
|
48
|
+
arc.endAngle = endAngle + 360
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Keeps a delta inside one signed turn.
|
|
54
|
+
* @param {number} delta Source angle delta.
|
|
55
|
+
* @returns {number}
|
|
56
|
+
*/
|
|
57
|
+
static #normalizeSingleTurnDelta(delta) {
|
|
58
|
+
let normalized = delta
|
|
59
|
+
|
|
60
|
+
while (normalized <= -360) {
|
|
61
|
+
normalized += 360
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
while (normalized > 360) {
|
|
65
|
+
normalized -= 360
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return normalized
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns true when one normalized delta describes a full circle.
|
|
73
|
+
* @param {number} delta Normalized angle delta.
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
static #isFullCircleDelta(delta) {
|
|
77
|
+
return (
|
|
78
|
+
Math.abs(Math.abs(delta) - 360) <=
|
|
79
|
+
AltiumSchematicArcAngleNormalizer.#FULL_CIRCLE_TOLERANCE
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes ownerless Altium drawing primitive strokes to match app rendering.
|
|
3
|
+
*/
|
|
4
|
+
export class AltiumSchematicFreeGraphicStrokeNormalizer {
|
|
5
|
+
static #SOURCE_THIN_STROKE = 1
|
|
6
|
+
|
|
7
|
+
static #APP_THIN_STROKE = 0.85
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Applies app-side stroke normalization to free schematic graphics.
|
|
11
|
+
* @param {object} documentModel Parsed document model.
|
|
12
|
+
* @returns {object}
|
|
13
|
+
*/
|
|
14
|
+
static normalize(documentModel) {
|
|
15
|
+
const schematic = documentModel?.schematic
|
|
16
|
+
if (!schematic || typeof schematic !== 'object') {
|
|
17
|
+
return documentModel
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
AltiumSchematicFreeGraphicStrokeNormalizer.#normalizeLines(
|
|
21
|
+
schematic.lines
|
|
22
|
+
)
|
|
23
|
+
for (const family of [
|
|
24
|
+
schematic.arcs,
|
|
25
|
+
schematic.beziers,
|
|
26
|
+
schematic.pies,
|
|
27
|
+
schematic.polygons,
|
|
28
|
+
schematic.rectangles,
|
|
29
|
+
schematic.roundedRectangles,
|
|
30
|
+
schematic.ellipses
|
|
31
|
+
]) {
|
|
32
|
+
AltiumSchematicFreeGraphicStrokeNormalizer.#normalizeFreeGraphics(
|
|
33
|
+
family
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return documentModel
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes free drawing line strokes while leaving wires and buses alone.
|
|
42
|
+
* @param {object[] | undefined} lines Parsed line primitives.
|
|
43
|
+
* @returns {void}
|
|
44
|
+
*/
|
|
45
|
+
static #normalizeLines(lines) {
|
|
46
|
+
if (!Array.isArray(lines)) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
if (
|
|
52
|
+
line?.recordType === '6' &&
|
|
53
|
+
!line.ownerIndex &&
|
|
54
|
+
line.isBus !== true
|
|
55
|
+
) {
|
|
56
|
+
AltiumSchematicFreeGraphicStrokeNormalizer.#normalizeStroke(
|
|
57
|
+
line,
|
|
58
|
+
'width'
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Normalizes ownerless drawing primitive strokes.
|
|
66
|
+
* @param {object[] | undefined} primitives Primitive family.
|
|
67
|
+
* @returns {void}
|
|
68
|
+
*/
|
|
69
|
+
static #normalizeFreeGraphics(primitives) {
|
|
70
|
+
if (!Array.isArray(primitives)) {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const primitive of primitives) {
|
|
75
|
+
if (primitive?.ownerIndex) {
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
AltiumSchematicFreeGraphicStrokeNormalizer.#normalizeStroke(
|
|
80
|
+
primitive,
|
|
81
|
+
'width'
|
|
82
|
+
)
|
|
83
|
+
AltiumSchematicFreeGraphicStrokeNormalizer.#normalizeStroke(
|
|
84
|
+
primitive,
|
|
85
|
+
'lineWidth'
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Rewrites one source thin-stroke value to the app-normalized width.
|
|
92
|
+
* @param {object} primitive Primitive object.
|
|
93
|
+
* @param {string} key Stroke key.
|
|
94
|
+
* @returns {void}
|
|
95
|
+
*/
|
|
96
|
+
static #normalizeStroke(primitive, key) {
|
|
97
|
+
if (
|
|
98
|
+
Number(primitive?.[key]) ===
|
|
99
|
+
AltiumSchematicFreeGraphicStrokeNormalizer.#SOURCE_THIN_STROKE
|
|
100
|
+
) {
|
|
101
|
+
primitive[key] =
|
|
102
|
+
AltiumSchematicFreeGraphicStrokeNormalizer.#APP_THIN_STROKE
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { AsciiRecordParser } from './AsciiRecordParser.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Marks Altium schematic components whose native designator labels are hidden.
|
|
5
|
+
*/
|
|
6
|
+
export class AltiumSchematicHiddenDesignatorResolver {
|
|
7
|
+
/**
|
|
8
|
+
* Annotates parsed components with schematic designator visibility metadata.
|
|
9
|
+
* @param {object} documentModel Parsed document model.
|
|
10
|
+
* @param {ArrayBuffer} arrayBuffer Source document bytes.
|
|
11
|
+
* @returns {object}
|
|
12
|
+
*/
|
|
13
|
+
static annotate(documentModel, arrayBuffer) {
|
|
14
|
+
if (!documentModel?.schematic?.components?.length || !arrayBuffer) {
|
|
15
|
+
return documentModel
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const hiddenDesignatorIds =
|
|
19
|
+
AltiumSchematicHiddenDesignatorResolver.#hiddenDesignatorUniqueIds(
|
|
20
|
+
arrayBuffer
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if (!hiddenDesignatorIds.size) {
|
|
24
|
+
return documentModel
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (const component of documentModel.schematic.components) {
|
|
28
|
+
if (hiddenDesignatorIds.has(String(component.uniqueId || ''))) {
|
|
29
|
+
component.schematicDesignatorVisible = false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return documentModel
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Finds component IDs whose owner designator record is explicitly hidden.
|
|
38
|
+
* @param {ArrayBuffer} arrayBuffer Source document bytes.
|
|
39
|
+
* @returns {Set<string>}
|
|
40
|
+
*/
|
|
41
|
+
static #hiddenDesignatorUniqueIds(arrayBuffer) {
|
|
42
|
+
let records = []
|
|
43
|
+
try {
|
|
44
|
+
records = AsciiRecordParser.parse(arrayBuffer)
|
|
45
|
+
} catch {
|
|
46
|
+
return new Set()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const hiddenUniqueIds = new Set()
|
|
50
|
+
|
|
51
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
52
|
+
const record = records[index]
|
|
53
|
+
if (
|
|
54
|
+
!AltiumSchematicHiddenDesignatorResolver.#isComponentRecord(
|
|
55
|
+
record
|
|
56
|
+
)
|
|
57
|
+
) {
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const uniqueId =
|
|
62
|
+
AltiumSchematicHiddenDesignatorResolver.#field(
|
|
63
|
+
record.fields,
|
|
64
|
+
'UniqueID'
|
|
65
|
+
) ||
|
|
66
|
+
AltiumSchematicHiddenDesignatorResolver.#field(
|
|
67
|
+
record.fields,
|
|
68
|
+
'UniqueId'
|
|
69
|
+
)
|
|
70
|
+
if (!uniqueId) {
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const visibility =
|
|
75
|
+
AltiumSchematicHiddenDesignatorResolver.#ownerDesignatorVisibility(
|
|
76
|
+
records,
|
|
77
|
+
index + 1
|
|
78
|
+
)
|
|
79
|
+
if (
|
|
80
|
+
visibility.hasHiddenDesignator &&
|
|
81
|
+
!visibility.hasVisibleDesignator
|
|
82
|
+
) {
|
|
83
|
+
hiddenUniqueIds.add(uniqueId)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return hiddenUniqueIds
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Resolves designator visibility in the records following one component.
|
|
92
|
+
* @param {{ fields: Record<string, string | string[]> }[]} records Parsed records.
|
|
93
|
+
* @param {number} startIndex First record after the component.
|
|
94
|
+
* @returns {{ hasHiddenDesignator: boolean, hasVisibleDesignator: boolean }}
|
|
95
|
+
*/
|
|
96
|
+
static #ownerDesignatorVisibility(records, startIndex) {
|
|
97
|
+
let hasHiddenDesignator = false
|
|
98
|
+
let hasVisibleDesignator = false
|
|
99
|
+
|
|
100
|
+
for (let index = startIndex; index < records.length; index += 1) {
|
|
101
|
+
const record = records[index]
|
|
102
|
+
if (
|
|
103
|
+
AltiumSchematicHiddenDesignatorResolver.#isComponentRecord(
|
|
104
|
+
record
|
|
105
|
+
)
|
|
106
|
+
) {
|
|
107
|
+
break
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
!AltiumSchematicHiddenDesignatorResolver.#isDesignatorRecord(
|
|
112
|
+
record
|
|
113
|
+
)
|
|
114
|
+
) {
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (
|
|
119
|
+
AltiumSchematicHiddenDesignatorResolver.#isHiddenRecord(record)
|
|
120
|
+
) {
|
|
121
|
+
hasHiddenDesignator = true
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
hasVisibleDesignator = true
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { hasHiddenDesignator, hasVisibleDesignator }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Returns true when one raw record is a component placement.
|
|
133
|
+
* @param {{ fields?: Record<string, string | string[]> }} record Raw record.
|
|
134
|
+
* @returns {boolean}
|
|
135
|
+
*/
|
|
136
|
+
static #isComponentRecord(record) {
|
|
137
|
+
return (
|
|
138
|
+
AltiumSchematicHiddenDesignatorResolver.#field(
|
|
139
|
+
record?.fields,
|
|
140
|
+
'RECORD'
|
|
141
|
+
) === '1'
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Returns true when one raw record is a component designator label.
|
|
147
|
+
* @param {{ fields?: Record<string, string | string[]> }} record Raw record.
|
|
148
|
+
* @returns {boolean}
|
|
149
|
+
*/
|
|
150
|
+
static #isDesignatorRecord(record) {
|
|
151
|
+
return (
|
|
152
|
+
AltiumSchematicHiddenDesignatorResolver.#field(
|
|
153
|
+
record?.fields,
|
|
154
|
+
'Name'
|
|
155
|
+
)
|
|
156
|
+
.trim()
|
|
157
|
+
.toLowerCase() === 'designator'
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Returns true when one raw record is explicitly hidden.
|
|
163
|
+
* @param {{ fields?: Record<string, string | string[]> }} record Raw record.
|
|
164
|
+
* @returns {boolean}
|
|
165
|
+
*/
|
|
166
|
+
static #isHiddenRecord(record) {
|
|
167
|
+
return (
|
|
168
|
+
AltiumSchematicHiddenDesignatorResolver.#field(
|
|
169
|
+
record?.fields,
|
|
170
|
+
'IsHidden'
|
|
171
|
+
)
|
|
172
|
+
.trim()
|
|
173
|
+
.toUpperCase() === 'T'
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Reads one string field from parser records.
|
|
179
|
+
* @param {Record<string, string | string[]> | undefined} fields Field map.
|
|
180
|
+
* @param {string} key Field key.
|
|
181
|
+
* @returns {string}
|
|
182
|
+
*/
|
|
183
|
+
static #field(fields, key) {
|
|
184
|
+
const value = fields?.[key]
|
|
185
|
+
if (Array.isArray(value)) {
|
|
186
|
+
return String(value[0] || '')
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return String(value || '')
|
|
190
|
+
}
|
|
191
|
+
}
|