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.
@@ -0,0 +1,287 @@
1
+ import { AltiumLayoutParser } from './AltiumLayoutParser.mjs'
2
+
3
+ /**
4
+ * Removes ownerless Altium free graphics that sit fully outside the authored
5
+ * schematic sheet bounds.
6
+ */
7
+ export class AltiumSchematicSheetBoundsNormalizer {
8
+ static #BOUNDS_TOLERANCE = 0.001
9
+
10
+ /**
11
+ * Applies sheet-bound filtering to parsed schematic primitives.
12
+ * @param {object} documentModel Parsed document model.
13
+ * @returns {object}
14
+ */
15
+ static normalize(documentModel) {
16
+ const schematic = documentModel?.schematic
17
+ const bounds =
18
+ AltiumSchematicSheetBoundsNormalizer.#resolveSourceBounds(
19
+ schematic?.sheet
20
+ )
21
+ if (!schematic || !bounds) {
22
+ return documentModel
23
+ }
24
+
25
+ const originalLines = schematic.lines
26
+ schematic.lines = AltiumSchematicSheetBoundsNormalizer.#filterLines(
27
+ schematic.lines,
28
+ bounds
29
+ )
30
+ if (
31
+ Array.isArray(originalLines) &&
32
+ schematic.lines.length !== originalLines.length
33
+ ) {
34
+ AltiumSchematicSheetBoundsNormalizer.#shrinkSheetToRemainingContent(
35
+ schematic
36
+ )
37
+ }
38
+
39
+ return documentModel
40
+ }
41
+
42
+ /**
43
+ * Returns the source sheet rectangle when finite dimensions are available.
44
+ * @param {object | null | undefined} sheet Parsed sheet metadata.
45
+ * @returns {{ minX: number, minY: number, maxX: number, maxY: number } | null}
46
+ */
47
+ static #resolveSourceBounds(sheet) {
48
+ const width =
49
+ AltiumSchematicSheetBoundsNormalizer.#finitePositiveNumber(
50
+ sheet?.sourceWidth
51
+ ) ||
52
+ AltiumSchematicSheetBoundsNormalizer.#finitePositiveNumber(
53
+ sheet?.width
54
+ )
55
+ const height =
56
+ AltiumSchematicSheetBoundsNormalizer.#finitePositiveNumber(
57
+ sheet?.sourceHeight
58
+ ) ||
59
+ AltiumSchematicSheetBoundsNormalizer.#finitePositiveNumber(
60
+ sheet?.height
61
+ )
62
+
63
+ if (!width || !height) {
64
+ return null
65
+ }
66
+
67
+ return {
68
+ minX: 0,
69
+ minY: 0,
70
+ maxX: width,
71
+ maxY: height
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Filters unsupported off-sheet free graphic lines while preserving wires.
77
+ * @param {object[] | undefined} lines Parsed line primitives.
78
+ * @param {{ minX: number, minY: number, maxX: number, maxY: number }} bounds Source sheet bounds.
79
+ * @returns {object[] | undefined}
80
+ */
81
+ static #filterLines(lines, bounds) {
82
+ if (!Array.isArray(lines)) {
83
+ return lines
84
+ }
85
+
86
+ return lines.filter(
87
+ (line) =>
88
+ !AltiumSchematicSheetBoundsNormalizer.#isOffSheetFreeLine(
89
+ line,
90
+ bounds
91
+ )
92
+ )
93
+ }
94
+
95
+ /**
96
+ * Recomputes inflated sheet dimensions from the remaining drawable model.
97
+ * @param {object} schematic Parsed schematic model.
98
+ * @returns {void}
99
+ */
100
+ static #shrinkSheetToRemainingContent(schematic) {
101
+ const sheet = schematic?.sheet
102
+ if (!sheet || typeof sheet !== 'object') {
103
+ return
104
+ }
105
+
106
+ const baseSheet =
107
+ AltiumSchematicSheetBoundsNormalizer.#sourceSizedSheet(sheet)
108
+ const resolvedSheet = AltiumLayoutParser.resolveSchematicSheetSize(
109
+ baseSheet,
110
+ [],
111
+ schematic.lines || [],
112
+ schematic.texts || [],
113
+ schematic.components || [],
114
+ schematic.pins || [],
115
+ [
116
+ ...(schematic.rectangles || []),
117
+ ...(schematic.roundedRectangles || [])
118
+ ],
119
+ schematic.regions || [],
120
+ schematic.ports || [],
121
+ schematic.crosses || []
122
+ )
123
+
124
+ AltiumSchematicSheetBoundsNormalizer.#applyShrunkenSheetSize(
125
+ sheet,
126
+ resolvedSheet
127
+ )
128
+ }
129
+
130
+ /**
131
+ * Builds a sheet metadata copy using source dimensions as declared bounds.
132
+ * @param {object} sheet Parsed sheet metadata.
133
+ * @returns {object}
134
+ */
135
+ static #sourceSizedSheet(sheet) {
136
+ return {
137
+ ...sheet,
138
+ width:
139
+ AltiumSchematicSheetBoundsNormalizer.#finitePositiveNumber(
140
+ sheet?.sourceWidth
141
+ ) ||
142
+ AltiumSchematicSheetBoundsNormalizer.#finitePositiveNumber(
143
+ sheet?.width
144
+ ),
145
+ height:
146
+ AltiumSchematicSheetBoundsNormalizer.#finitePositiveNumber(
147
+ sheet?.sourceHeight
148
+ ) ||
149
+ AltiumSchematicSheetBoundsNormalizer.#finitePositiveNumber(
150
+ sheet?.height
151
+ )
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Applies only smaller resolved dimensions to avoid expanding content.
157
+ * @param {object} sheet Mutable sheet metadata.
158
+ * @param {object | null | undefined} resolvedSheet Recomputed sheet metadata.
159
+ * @returns {void}
160
+ */
161
+ static #applyShrunkenSheetSize(sheet, resolvedSheet) {
162
+ const currentWidth =
163
+ AltiumSchematicSheetBoundsNormalizer.#finitePositiveNumber(
164
+ sheet?.width
165
+ )
166
+ const currentHeight =
167
+ AltiumSchematicSheetBoundsNormalizer.#finitePositiveNumber(
168
+ sheet?.height
169
+ )
170
+ const resolvedWidth =
171
+ AltiumSchematicSheetBoundsNormalizer.#finitePositiveNumber(
172
+ resolvedSheet?.width
173
+ )
174
+ const resolvedHeight =
175
+ AltiumSchematicSheetBoundsNormalizer.#finitePositiveNumber(
176
+ resolvedSheet?.height
177
+ )
178
+
179
+ if (resolvedWidth && currentWidth && resolvedWidth < currentWidth) {
180
+ sheet.width = resolvedWidth
181
+ }
182
+ if (resolvedHeight && currentHeight && resolvedHeight < currentHeight) {
183
+ sheet.height = resolvedHeight
184
+ }
185
+ if (
186
+ (sheet.width === resolvedWidth ||
187
+ sheet.height === resolvedHeight) &&
188
+ Object.prototype.hasOwnProperty.call(
189
+ resolvedSheet || {},
190
+ 'paperSize'
191
+ )
192
+ ) {
193
+ sheet.paperSize = resolvedSheet.paperSize
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Returns true when one free drawing line lies wholly outside the sheet.
199
+ * @param {object | null | undefined} line Parsed line primitive.
200
+ * @param {{ minX: number, minY: number, maxX: number, maxY: number }} bounds Source sheet bounds.
201
+ * @returns {boolean}
202
+ */
203
+ static #isOffSheetFreeLine(line, bounds) {
204
+ if (
205
+ String(line?.recordType || '') !== '6' ||
206
+ AltiumSchematicSheetBoundsNormalizer.#hasOwner(line) ||
207
+ line?.isBus === true
208
+ ) {
209
+ return false
210
+ }
211
+
212
+ const lineBounds =
213
+ AltiumSchematicSheetBoundsNormalizer.#resolveLineBounds(line)
214
+ if (!lineBounds) {
215
+ return false
216
+ }
217
+
218
+ return !AltiumSchematicSheetBoundsNormalizer.#boundsIntersect(
219
+ lineBounds,
220
+ bounds
221
+ )
222
+ }
223
+
224
+ /**
225
+ * Returns true when a primitive has an owner reference.
226
+ * @param {object | null | undefined} primitive Parsed primitive.
227
+ * @returns {boolean}
228
+ */
229
+ static #hasOwner(primitive) {
230
+ return String(primitive?.ownerIndex || '').trim() !== ''
231
+ }
232
+
233
+ /**
234
+ * Resolves finite bounds for one line primitive.
235
+ * @param {object | null | undefined} line Parsed line primitive.
236
+ * @returns {{ minX: number, minY: number, maxX: number, maxY: number } | null}
237
+ */
238
+ static #resolveLineBounds(line) {
239
+ const x1 = Number(line?.x1)
240
+ const y1 = Number(line?.y1)
241
+ const x2 = Number(line?.x2)
242
+ const y2 = Number(line?.y2)
243
+
244
+ if (
245
+ !Number.isFinite(x1) ||
246
+ !Number.isFinite(y1) ||
247
+ !Number.isFinite(x2) ||
248
+ !Number.isFinite(y2)
249
+ ) {
250
+ return null
251
+ }
252
+
253
+ return {
254
+ minX: Math.min(x1, x2),
255
+ minY: Math.min(y1, y2),
256
+ maxX: Math.max(x1, x2),
257
+ maxY: Math.max(y1, y2)
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Returns true when two bounds overlap within tolerance.
263
+ * @param {{ minX: number, minY: number, maxX: number, maxY: number }} left First bounds.
264
+ * @param {{ minX: number, minY: number, maxX: number, maxY: number }} right Second bounds.
265
+ * @returns {boolean}
266
+ */
267
+ static #boundsIntersect(left, right) {
268
+ const tolerance = AltiumSchematicSheetBoundsNormalizer.#BOUNDS_TOLERANCE
269
+
270
+ return !(
271
+ left.maxX < right.minX - tolerance ||
272
+ left.minX > right.maxX + tolerance ||
273
+ left.maxY < right.minY - tolerance ||
274
+ left.minY > right.maxY + tolerance
275
+ )
276
+ }
277
+
278
+ /**
279
+ * Converts a value to a finite positive number.
280
+ * @param {unknown} value Candidate number.
281
+ * @returns {number | null}
282
+ */
283
+ static #finitePositiveNumber(value) {
284
+ const number = Number(value)
285
+ return Number.isFinite(number) && number > 0 ? number : null
286
+ }
287
+ }
package/src/parser.mjs CHANGED
@@ -16,6 +16,11 @@ export { SourceBundleExporter } from './core/altium/SourceBundleExporter.mjs'
16
16
  export { SourceComponentBundleNormalizer } from './core/altium/SourceComponentBundleNormalizer.mjs'
17
17
  export { SourceComponentClient } from './core/altium/SourceComponentClient.mjs'
18
18
  export { AltiumParser } from './core/altium/AltiumParser.mjs'
19
+ export { AltiumSchematicArcAngleNormalizer } from './core/altium/AltiumSchematicArcAngleNormalizer.mjs'
20
+ export { AltiumSchematicFreeGraphicStrokeNormalizer } from './core/altium/AltiumSchematicFreeGraphicStrokeNormalizer.mjs'
21
+ export { AltiumSchematicHiddenDesignatorResolver } from './core/altium/AltiumSchematicHiddenDesignatorResolver.mjs'
22
+ export { AltiumSchematicPackedImageResolver } from './core/altium/AltiumSchematicPackedImageResolver.mjs'
23
+ export { AltiumSchematicSheetBoundsNormalizer } from './core/altium/AltiumSchematicSheetBoundsNormalizer.mjs'
19
24
  export { AltiumLayoutParser } from './core/altium/AltiumLayoutParser.mjs'
20
25
  export { AsciiRecordParser } from './core/altium/AsciiRecordParser.mjs'
21
26
  export { HostCapabilityDiagnosticsBuilder } from './core/altium/HostCapabilityDiagnosticsBuilder.mjs'
package/src/renderers.mjs CHANGED
@@ -3,9 +3,11 @@
3
3
  // SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
5
  export { BomTableRenderer } from './ui/BomTableRenderer.mjs'
6
+ export { AltiumPcbBottomViewMirror } from './ui/AltiumPcbBottomViewMirror.mjs'
6
7
  export { PcbArcUtils } from './ui/PcbArcUtils.mjs'
7
8
  export { PcbEdgeFacingGlyphNormalizer } from './ui/PcbEdgeFacingGlyphNormalizer.mjs'
8
9
  export { PcbFootprintPrimitiveSelector } from './ui/PcbFootprintPrimitiveSelector.mjs'
10
+ export { PcbFootprintPadAxisNormalizer } from './ui/PcbFootprintPadAxisNormalizer.mjs'
9
11
  export { PcbInteractionIndex } from './ui/PcbInteractionIndex.mjs'
10
12
  export { PcbInteractionItemRegistry } from './ui/PcbInteractionItemRegistry.mjs'
11
13
  export { PcbInteractionLayerModel } from './ui/PcbInteractionLayerModel.mjs'
package/src/scene3d.mjs CHANGED
@@ -3,6 +3,7 @@
3
3
  // SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
5
  export { PcbScene3dBuilder } from './ui/PcbScene3dBuilder.mjs'
6
+ export { AltiumScene3dAuthoredBodyAnchorAdapter } from './ui/AltiumScene3dAuthoredBodyAnchorAdapter.mjs'
6
7
  export { PcbScene3dModelRegistry } from './ui/PcbScene3dModelRegistry.mjs'
7
8
  export { PcbScene3dPackages } from './ui/PcbScene3dPackages.mjs'
8
9
  export { PcbScene3dScenePreparator } from './ui/PcbScene3dScenePreparator.mjs'