altium-toolkit 1.4.7 → 1.4.8
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,42 @@
|
|
|
1
|
+
# altium-toolkit 1.4.8
|
|
2
|
+
|
|
3
|
+
Version 1.4.8 corrects embedded STEP placement for bottom-side components and
|
|
4
|
+
for package owners recovered after initial scene construction.
|
|
5
|
+
|
|
6
|
+
## Signed STEP source orientation
|
|
7
|
+
|
|
8
|
+
- Embedded STEP registry entries expose signed source bounds derived from the
|
|
9
|
+
model payload and its declared length units.
|
|
10
|
+
- Bottom-side models whose geometry is predominantly below the authored origin
|
|
11
|
+
preserve their authored half-turn instead of being normalized upside down.
|
|
12
|
+
- Direct scene construction and asynchronous scene preparation share the same
|
|
13
|
+
convergence registry and builder path.
|
|
14
|
+
|
|
15
|
+
## Late-owner model seating
|
|
16
|
+
|
|
17
|
+
- Final placement owners are reconciled with their original component-body
|
|
18
|
+
metadata after historical ownership adapters complete.
|
|
19
|
+
- A finite zero authored standoff clears only an unchanged positive source
|
|
20
|
+
`dzMil`, allowing the shared viewer's model-bounds seating to place the model
|
|
21
|
+
on the PCB surface.
|
|
22
|
+
- Positive authored standoffs, unresolved bodies, downstream-adjusted offsets,
|
|
23
|
+
and unrelated model transformations remain unchanged.
|
|
24
|
+
- The rule is derived from ownership and placement metadata without component,
|
|
25
|
+
model, vendor, package, project, or fixture-specific matching.
|
|
26
|
+
|
|
27
|
+
## Compatibility
|
|
28
|
+
|
|
29
|
+
- Historical native source remains frozen; the corrections live in the public
|
|
30
|
+
convergence layer.
|
|
31
|
+
- Existing parser, renderer, extension, and viewer scene contracts remain
|
|
32
|
+
unchanged.
|
|
33
|
+
|
|
34
|
+
## Verification
|
|
35
|
+
|
|
36
|
+
- Repository-owned fake regressions cover signed-source bottom half-turns,
|
|
37
|
+
zero-standoff late owners, positive authored standoffs, unresolved bodies,
|
|
38
|
+
and downstream-adjusted offsets.
|
|
39
|
+
- Exact local-board verification checks the affected model seating together
|
|
40
|
+
with the previously reported bottom connector orientation.
|
|
41
|
+
- Release gates include the complete package suite, formatting check, npm dry
|
|
42
|
+
run, ECAD Forge integration tests, structured-data check, and static build.
|
package/package.json
CHANGED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
import { PcbScene3dBuilder as HistoricalPcbScene3dBuilder } from '../ui/PcbScene3dBuilder.mjs'
|
|
5
|
+
|
|
6
|
+
const NEGATIVE_SOURCE_Z_DOMINANCE_RATIO = 0.8
|
|
7
|
+
const POSITION_EPSILON_MIL = 1e-6
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Adds signed-source model orientation correction around the preserved native
|
|
11
|
+
* scene builder.
|
|
12
|
+
*/
|
|
13
|
+
export class PcbScene3dBuilder {
|
|
14
|
+
/**
|
|
15
|
+
* Builds a scene and preserves structurally negative-Z source half-turns.
|
|
16
|
+
* @param {object} documentModel Parsed Altium document model.
|
|
17
|
+
* @param {object} [options] Native scene-builder options.
|
|
18
|
+
* @returns {object}
|
|
19
|
+
*/
|
|
20
|
+
static build(documentModel, options = {}) {
|
|
21
|
+
const scene = HistoricalPcbScene3dBuilder.build(documentModel, options)
|
|
22
|
+
if (!Array.isArray(scene?.externalPlacements)) {
|
|
23
|
+
return scene
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...scene,
|
|
28
|
+
externalPlacements: scene.externalPlacements.map((placement) =>
|
|
29
|
+
PcbScene3dBuilder.#normalizePlacement(
|
|
30
|
+
placement,
|
|
31
|
+
documentModel?.pcb?.componentBodies,
|
|
32
|
+
documentModel?.pcb?.components
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Preserves one bottom placement half-turn when its source geometry is
|
|
40
|
+
* predominantly below the authored origin.
|
|
41
|
+
* @param {object} placement Built external placement.
|
|
42
|
+
* @param {object[] | undefined} componentBodies Source component bodies.
|
|
43
|
+
* @param {object[] | undefined} components Source PCB components.
|
|
44
|
+
* @returns {object}
|
|
45
|
+
*/
|
|
46
|
+
static #normalizePlacement(placement, componentBodies, components) {
|
|
47
|
+
const componentBody = PcbScene3dBuilder.#resolveComponentBody(
|
|
48
|
+
placement,
|
|
49
|
+
componentBodies
|
|
50
|
+
)
|
|
51
|
+
const normalizedPlacement =
|
|
52
|
+
PcbScene3dBuilder.#normalizeRecoveredOwnerVerticalOffset(
|
|
53
|
+
placement,
|
|
54
|
+
componentBody,
|
|
55
|
+
components
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
String(normalizedPlacement?.mountSide || '').toLowerCase() !==
|
|
60
|
+
'bottom' ||
|
|
61
|
+
!PcbScene3dBuilder.#isDominantlyNegativeSourceZ(
|
|
62
|
+
normalizedPlacement?.externalModel?.sourceBoundsMil
|
|
63
|
+
)
|
|
64
|
+
) {
|
|
65
|
+
return normalizedPlacement
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
PcbScene3dBuilder.#normalizeAngle(
|
|
70
|
+
componentBody?.modelRotationDeg?.x ??
|
|
71
|
+
normalizedPlacement?.externalModel?.transform?.rotationDeg
|
|
72
|
+
?.x
|
|
73
|
+
) !== 180
|
|
74
|
+
) {
|
|
75
|
+
return normalizedPlacement
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
...normalizedPlacement,
|
|
80
|
+
modelTransform: {
|
|
81
|
+
...(normalizedPlacement.modelTransform || {}),
|
|
82
|
+
rotationDeg: {
|
|
83
|
+
...(normalizedPlacement.modelTransform?.rotationDeg || {}),
|
|
84
|
+
x: -180
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Applies owned-package seating after a later adapter recovered the owner.
|
|
92
|
+
* @param {object} placement Built external placement.
|
|
93
|
+
* @param {object | null} componentBody Resolved source component body.
|
|
94
|
+
* @param {object[] | undefined} components Source PCB components.
|
|
95
|
+
* @returns {object}
|
|
96
|
+
*/
|
|
97
|
+
static #normalizeRecoveredOwnerVerticalOffset(
|
|
98
|
+
placement,
|
|
99
|
+
componentBody,
|
|
100
|
+
components
|
|
101
|
+
) {
|
|
102
|
+
if (
|
|
103
|
+
!PcbScene3dBuilder.#hasResolvedOwner(placement, components) ||
|
|
104
|
+
!PcbScene3dBuilder.#hasZeroAuthoredStandoff(componentBody) ||
|
|
105
|
+
!PcbScene3dBuilder.#retainsPositiveSourceOffset(
|
|
106
|
+
placement,
|
|
107
|
+
componentBody
|
|
108
|
+
)
|
|
109
|
+
) {
|
|
110
|
+
return placement
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
...placement,
|
|
115
|
+
modelTransform: {
|
|
116
|
+
...(placement.modelTransform || {}),
|
|
117
|
+
dzMil: 0
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Checks whether the final placement designator resolves a source owner.
|
|
124
|
+
* @param {object} placement Built external placement.
|
|
125
|
+
* @param {object[] | undefined} components Source PCB components.
|
|
126
|
+
* @returns {boolean}
|
|
127
|
+
*/
|
|
128
|
+
static #hasResolvedOwner(placement, components) {
|
|
129
|
+
const designator = String(placement?.designator || '')
|
|
130
|
+
return (
|
|
131
|
+
designator.length > 0 &&
|
|
132
|
+
(Array.isArray(components) ? components : []).some(
|
|
133
|
+
(component) =>
|
|
134
|
+
String(component?.designator || '') === designator
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Checks whether the source explicitly authors zero model standoff.
|
|
141
|
+
* @param {object | null} componentBody Resolved source component body.
|
|
142
|
+
* @returns {boolean}
|
|
143
|
+
*/
|
|
144
|
+
static #hasZeroAuthoredStandoff(componentBody) {
|
|
145
|
+
const sourceStandoff = componentBody?.standoffHeightMil
|
|
146
|
+
if (
|
|
147
|
+
sourceStandoff === null ||
|
|
148
|
+
sourceStandoff === undefined ||
|
|
149
|
+
sourceStandoff === ''
|
|
150
|
+
) {
|
|
151
|
+
return false
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const standoff = Number(sourceStandoff)
|
|
155
|
+
return (
|
|
156
|
+
Number.isFinite(standoff) &&
|
|
157
|
+
Math.abs(standoff) <= POSITION_EPSILON_MIL
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Checks whether the final placement still carries the positive source Z.
|
|
163
|
+
* @param {object} placement Built external placement.
|
|
164
|
+
* @param {object | null} componentBody Resolved source component body.
|
|
165
|
+
* @returns {boolean}
|
|
166
|
+
*/
|
|
167
|
+
static #retainsPositiveSourceOffset(placement, componentBody) {
|
|
168
|
+
const sourceOffset = Number(componentBody?.dzMil)
|
|
169
|
+
const placementOffset = Number(placement?.modelTransform?.dzMil)
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
Number.isFinite(sourceOffset) &&
|
|
173
|
+
sourceOffset > 0 &&
|
|
174
|
+
Number.isFinite(placementOffset) &&
|
|
175
|
+
Math.abs(placementOffset - sourceOffset) <= POSITION_EPSILON_MIL
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Resolves the source body associated with one built placement.
|
|
181
|
+
* @param {object} placement Built external placement.
|
|
182
|
+
* @param {object[] | undefined} componentBodies Source component bodies.
|
|
183
|
+
* @returns {object | null}
|
|
184
|
+
*/
|
|
185
|
+
static #resolveComponentBody(placement, componentBodies) {
|
|
186
|
+
const bodyPosition = placement?.bodyPositionMil || {}
|
|
187
|
+
const modelName = String(placement?.externalModel?.name || '')
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
(Array.isArray(componentBodies) ? componentBodies : []).find(
|
|
191
|
+
(componentBody) =>
|
|
192
|
+
String(componentBody?.name || '') === modelName &&
|
|
193
|
+
PcbScene3dBuilder.#positionsMatch(
|
|
194
|
+
componentBody?.positionMil,
|
|
195
|
+
bodyPosition
|
|
196
|
+
)
|
|
197
|
+
) || null
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Checks whether two model positions represent the same authored anchor.
|
|
203
|
+
* @param {{ x?: number, y?: number } | undefined} left Left position.
|
|
204
|
+
* @param {{ x?: number, y?: number } | undefined} right Right position.
|
|
205
|
+
* @returns {boolean}
|
|
206
|
+
*/
|
|
207
|
+
static #positionsMatch(left, right) {
|
|
208
|
+
return (
|
|
209
|
+
Math.abs(Number(left?.x) - Number(right?.x)) <=
|
|
210
|
+
POSITION_EPSILON_MIL &&
|
|
211
|
+
Math.abs(Number(left?.y) - Number(right?.y)) <= POSITION_EPSILON_MIL
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Checks whether at least four fifths of the source Z span lies below the
|
|
217
|
+
* authored origin.
|
|
218
|
+
* @param {{ minZ?: number, maxZ?: number } | null | undefined} sourceBoundsMil Signed source-model bounds.
|
|
219
|
+
* @returns {boolean}
|
|
220
|
+
*/
|
|
221
|
+
static #isDominantlyNegativeSourceZ(sourceBoundsMil) {
|
|
222
|
+
const minZ = Number(sourceBoundsMil?.minZ)
|
|
223
|
+
const maxZ = Number(sourceBoundsMil?.maxZ)
|
|
224
|
+
if (!Number.isFinite(minZ) || !Number.isFinite(maxZ) || maxZ <= minZ) {
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const negativeSpan = Math.max(0, Math.min(0, maxZ) - minZ)
|
|
229
|
+
return negativeSpan / (maxZ - minZ) >= NEGATIVE_SOURCE_Z_DOMINANCE_RATIO
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Normalizes one angle into the positive 0-359 degree range.
|
|
234
|
+
* @param {unknown} angle Source angle.
|
|
235
|
+
* @returns {number}
|
|
236
|
+
*/
|
|
237
|
+
static #normalizeAngle(angle) {
|
|
238
|
+
const numericAngle = Number(angle || 0)
|
|
239
|
+
const normalized = numericAngle % 360
|
|
240
|
+
|
|
241
|
+
return normalized < 0 ? normalized + 360 : normalized
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
import { PcbScene3dModelRegistry as HistoricalPcbScene3dModelRegistry } from '../ui/PcbScene3dModelRegistry.mjs'
|
|
5
|
+
|
|
6
|
+
const MILS_PER_METER = 39370.07874015748
|
|
7
|
+
const MILS_PER_INCH = 1000
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Adds signed STEP source bounds to the preserved native model registry.
|
|
11
|
+
*/
|
|
12
|
+
export class PcbScene3dModelRegistry {
|
|
13
|
+
#registry
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates one converged registry around either a preserved native registry
|
|
17
|
+
* or the native constructor's normalized model rows.
|
|
18
|
+
* @param {HistoricalPcbScene3dModelRegistry | object[]} registryOrModelFiles Native registry or normalized session models.
|
|
19
|
+
* @param {object[]} embeddedModels Normalized embedded models.
|
|
20
|
+
*/
|
|
21
|
+
constructor(registryOrModelFiles, embeddedModels) {
|
|
22
|
+
this.#registry =
|
|
23
|
+
registryOrModelFiles instanceof HistoricalPcbScene3dModelRegistry
|
|
24
|
+
? registryOrModelFiles
|
|
25
|
+
: new HistoricalPcbScene3dModelRegistry(
|
|
26
|
+
registryOrModelFiles,
|
|
27
|
+
embeddedModels
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates one model registry from session and embedded model assets.
|
|
33
|
+
* @param {{ name?: string, relativePath?: string, source?: string }[]} sessionFiles Session model files.
|
|
34
|
+
* @param {{ id?: string, checksum?: number | null, name?: string, format?: string, payloadText?: string, sourceStream?: string, transform?: object }[]} [embeddedModels] Embedded model payloads.
|
|
35
|
+
* @returns {PcbScene3dModelRegistry}
|
|
36
|
+
*/
|
|
37
|
+
static create(sessionFiles, embeddedModels = []) {
|
|
38
|
+
return new PcbScene3dModelRegistry(
|
|
39
|
+
HistoricalPcbScene3dModelRegistry.create(
|
|
40
|
+
sessionFiles,
|
|
41
|
+
embeddedModels
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the best available model for one component.
|
|
48
|
+
* @param {{ pattern?: string, source?: string, modelPath?: string }} component Component metadata.
|
|
49
|
+
* @returns {object | null}
|
|
50
|
+
*/
|
|
51
|
+
resolveComponentModel(component) {
|
|
52
|
+
return this.#registry.resolveComponentModel(component)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolves a component-body model and exposes its signed STEP bounds.
|
|
57
|
+
* @param {{ modelId?: string, checksum?: number | null, name?: string }} componentBody Component-body metadata.
|
|
58
|
+
* @returns {object | null}
|
|
59
|
+
*/
|
|
60
|
+
resolveComponentBodyModel(componentBody) {
|
|
61
|
+
const model = this.#registry.resolveComponentBodyModel(componentBody)
|
|
62
|
+
const sourceBoundsMil =
|
|
63
|
+
PcbScene3dModelRegistry.#resolveSourceBoundsMil(model)
|
|
64
|
+
|
|
65
|
+
return sourceBoundsMil ? { ...model, sourceBoundsMil } : model
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolves a project-level full-board assembly model.
|
|
70
|
+
* @param {{ fileName?: string }} documentModel Document metadata.
|
|
71
|
+
* @returns {object | null}
|
|
72
|
+
*/
|
|
73
|
+
resolveBoardAssemblyModel(documentModel) {
|
|
74
|
+
return this.#registry.resolveBoardAssemblyModel(documentModel)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolves signed bounds for one inline STEP model.
|
|
79
|
+
* @param {{ format?: string, payloadText?: string } | null} model Resolved model.
|
|
80
|
+
* @returns {{ minX: number, maxX: number, minY: number, maxY: number, minZ: number, maxZ: number } | null}
|
|
81
|
+
*/
|
|
82
|
+
static #resolveSourceBoundsMil(model) {
|
|
83
|
+
const format = String(model?.format || '').toLowerCase()
|
|
84
|
+
if ((format !== 'step' && format !== 'stp') || !model?.payloadText) {
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const text = String(model.payloadText)
|
|
89
|
+
const points = []
|
|
90
|
+
const pointPattern =
|
|
91
|
+
/CARTESIAN_POINT\s*\(\s*(?:'[^']*'|[^,]*),\s*\(([^)]*)\)\s*\)/giu
|
|
92
|
+
let match = pointPattern.exec(text)
|
|
93
|
+
|
|
94
|
+
while (match) {
|
|
95
|
+
const coordinates = String(match[1] || '')
|
|
96
|
+
.split(',')
|
|
97
|
+
.slice(0, 3)
|
|
98
|
+
.map((value) => Number(value.trim()))
|
|
99
|
+
if (
|
|
100
|
+
coordinates.length === 3 &&
|
|
101
|
+
coordinates.every((value) => Number.isFinite(value))
|
|
102
|
+
) {
|
|
103
|
+
points.push(coordinates)
|
|
104
|
+
}
|
|
105
|
+
match = pointPattern.exec(text)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (points.length < 2) {
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const [firstPoint] = points
|
|
113
|
+
const bounds = {
|
|
114
|
+
minX: firstPoint[0],
|
|
115
|
+
maxX: firstPoint[0],
|
|
116
|
+
minY: firstPoint[1],
|
|
117
|
+
maxY: firstPoint[1],
|
|
118
|
+
minZ: firstPoint[2],
|
|
119
|
+
maxZ: firstPoint[2]
|
|
120
|
+
}
|
|
121
|
+
points.slice(1).forEach(([x, y, z]) => {
|
|
122
|
+
bounds.minX = Math.min(bounds.minX, x)
|
|
123
|
+
bounds.maxX = Math.max(bounds.maxX, x)
|
|
124
|
+
bounds.minY = Math.min(bounds.minY, y)
|
|
125
|
+
bounds.maxY = Math.max(bounds.maxY, y)
|
|
126
|
+
bounds.minZ = Math.min(bounds.minZ, z)
|
|
127
|
+
bounds.maxZ = Math.max(bounds.maxZ, z)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const scale = PcbScene3dModelRegistry.#resolveStepMilScale(text)
|
|
131
|
+
return Object.fromEntries(
|
|
132
|
+
Object.entries(bounds).map(([key, value]) => [key, value * scale])
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Resolves the STEP length-unit scale to mils.
|
|
138
|
+
* @param {string} payloadText STEP text payload.
|
|
139
|
+
* @returns {number}
|
|
140
|
+
*/
|
|
141
|
+
static #resolveStepMilScale(payloadText) {
|
|
142
|
+
const text = String(payloadText || '').toUpperCase()
|
|
143
|
+
if (/\bINCH\b|\.INCH\./u.test(text)) {
|
|
144
|
+
return MILS_PER_INCH
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const siUnitMatch = text.match(
|
|
148
|
+
/SI_UNIT\s*\(\s*(\.[A-Z]+\.|\$)\s*,\s*\.METRE\.\s*\)/u
|
|
149
|
+
)
|
|
150
|
+
return (
|
|
151
|
+
PcbScene3dModelRegistry.#resolveSiPrefixScale(siUnitMatch?.[1]) *
|
|
152
|
+
MILS_PER_METER
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resolves one STEP SI length prefix to a meter multiplier.
|
|
158
|
+
* @param {string | undefined} prefix STEP SI prefix.
|
|
159
|
+
* @returns {number}
|
|
160
|
+
*/
|
|
161
|
+
static #resolveSiPrefixScale(prefix) {
|
|
162
|
+
const scales = {
|
|
163
|
+
'.EXA.': 1e18,
|
|
164
|
+
'.PETA.': 1e15,
|
|
165
|
+
'.TERA.': 1e12,
|
|
166
|
+
'.GIGA.': 1e9,
|
|
167
|
+
'.MEGA.': 1e6,
|
|
168
|
+
'.KILO.': 1e3,
|
|
169
|
+
'.HECTO.': 1e2,
|
|
170
|
+
'.DECA.': 1e1,
|
|
171
|
+
$: 1,
|
|
172
|
+
'.DECI.': 1e-1,
|
|
173
|
+
'.CENTI.': 1e-2,
|
|
174
|
+
'.MILLI.': 1e-3,
|
|
175
|
+
'.MICRO.': 1e-6,
|
|
176
|
+
'.NANO.': 1e-9,
|
|
177
|
+
'.PICO.': 1e-12,
|
|
178
|
+
'.FEMTO.': 1e-15,
|
|
179
|
+
'.ATTO.': 1e-18
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return scales[prefix || '.MILLI.'] ?? 1e-3
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
import { PcbScene3dBuilder } from './PcbScene3dBuilder.mjs'
|
|
5
|
+
import { PcbScene3dModelRegistry } from './PcbScene3dModelRegistry.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Builds converged renderer-ready scene descriptions for async preprocessing.
|
|
9
|
+
*/
|
|
10
|
+
export class PcbScene3dScenePreparator {
|
|
11
|
+
/**
|
|
12
|
+
* Builds one scene description with the converged registry and builder.
|
|
13
|
+
* @param {object} documentModel Parsed Altium document model.
|
|
14
|
+
* @param {{ sessionAssets?: object[], modelRegistry?: object | null, buildScene?: (documentModel: object, options: { modelRegistry: object }) => object }} [options] Scene preparation options.
|
|
15
|
+
* @returns {Promise<object>}
|
|
16
|
+
*/
|
|
17
|
+
static async prepare(documentModel, options = {}) {
|
|
18
|
+
const modelRegistry =
|
|
19
|
+
options.modelRegistry ||
|
|
20
|
+
PcbScene3dModelRegistry.create(
|
|
21
|
+
options.sessionAssets || [],
|
|
22
|
+
Array.isArray(documentModel?.pcb?.embeddedModels)
|
|
23
|
+
? documentModel.pcb.embeddedModels
|
|
24
|
+
: []
|
|
25
|
+
)
|
|
26
|
+
const buildScene =
|
|
27
|
+
options.buildScene ||
|
|
28
|
+
((nextDocumentModel, buildOptions) =>
|
|
29
|
+
PcbScene3dBuilder.build(nextDocumentModel, buildOptions))
|
|
30
|
+
|
|
31
|
+
return buildScene(documentModel, { modelRegistry })
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/extensions.mjs
CHANGED
|
@@ -4,9 +4,17 @@
|
|
|
4
4
|
export * from 'circuitjson-toolkit/extensions'
|
|
5
5
|
|
|
6
6
|
export { AltiumExtensionResolver } from './convergence/AltiumExtensionResolver.mjs'
|
|
7
|
+
export { PcbScene3dBuilder } from './convergence/PcbScene3dBuilder.mjs'
|
|
8
|
+
export { PcbScene3dModelRegistry } from './convergence/PcbScene3dModelRegistry.mjs'
|
|
9
|
+
export { PcbScene3dScenePreparator } from './convergence/PcbScene3dScenePreparator.mjs'
|
|
7
10
|
export { PcbSvgRenderer } from './convergence/PcbSvgRenderer.mjs'
|
|
8
11
|
export { SchematicSvgRenderer } from './convergence/SchematicSvgRenderer.mjs'
|
|
9
12
|
export * from './legacy-parser.mjs'
|
|
10
13
|
export * from './legacy-netlist-query.mjs'
|
|
11
14
|
export * from './legacy-renderers.mjs'
|
|
12
|
-
export
|
|
15
|
+
export {
|
|
16
|
+
AltiumScene3dAuthoredBodyAnchorAdapter,
|
|
17
|
+
PcbScene3dPackages,
|
|
18
|
+
PcbScene3dSummaryRenderer,
|
|
19
|
+
PcbScene3dTextBoxLayoutResolver
|
|
20
|
+
} from './legacy-scene3d.mjs'
|