pcb-scene3d-viewer 1.1.21 → 1.1.22
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/PcbAssemblyBoardSubstrateBuilder.mjs +7 -5
- package/src/PcbAssemblyGeometryBuilder.mjs +7 -134
- package/src/PcbAssemblyMeshUtils.mjs +117 -0
- package/src/PcbAssemblyPadMeshBuilder.mjs +394 -0
- package/src/PcbAssemblyStepWriter.mjs +24 -2
- package/src/PcbModelArchiveExporter.mjs +504 -5
- package/src/PcbScene3dCompanionBasePlacementAdjuster.mjs +242 -0
- package/src/PcbScene3dComponentVisibility.mjs +101 -5
- package/src/PcbScene3dExternalCompanionFallback.mjs +202 -0
- package/src/PcbScene3dExternalModels.mjs +6 -0
- package/src/PcbScene3dFallbackBodyFactory.mjs +105 -0
- package/src/PcbScene3dFallbackVisibility.mjs +58 -1
- package/src/PcbScene3dRenderGroupVisibility.mjs +8 -1
- package/src/PcbScene3dRuntime.mjs +54 -63
- package/src/PcbScene3dStaticBodyFactory.mjs +241 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { PcbScene3dExternalCompanionFallback } from './PcbScene3dExternalCompanionFallback.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lifts authored model placements that sit on companion fallback bases.
|
|
5
|
+
*/
|
|
6
|
+
export class PcbScene3dCompanionBasePlacementAdjuster {
|
|
7
|
+
static #ENVELOPE_MARGIN_RATIO = 0.25
|
|
8
|
+
static #ENVELOPE_MIN_MARGIN_MIL = 20
|
|
9
|
+
static #POSITION_EPSILON = 1e-6
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Returns a scene copy with placements seated on companion fallback bases.
|
|
13
|
+
* @param {object} sceneDescription Scene description.
|
|
14
|
+
* @returns {object}
|
|
15
|
+
*/
|
|
16
|
+
static adjust(sceneDescription) {
|
|
17
|
+
const bases =
|
|
18
|
+
PcbScene3dCompanionBasePlacementAdjuster.#companionBases(
|
|
19
|
+
sceneDescription
|
|
20
|
+
)
|
|
21
|
+
if (!bases.length) {
|
|
22
|
+
return sceneDescription
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
...sceneDescription,
|
|
27
|
+
externalPlacements:
|
|
28
|
+
PcbScene3dCompanionBasePlacementAdjuster.#adjustPlacements(
|
|
29
|
+
sceneDescription?.externalPlacements,
|
|
30
|
+
bases
|
|
31
|
+
),
|
|
32
|
+
staticBodyPlacements:
|
|
33
|
+
PcbScene3dCompanionBasePlacementAdjuster.#adjustPlacements(
|
|
34
|
+
sceneDescription?.staticBodyPlacements,
|
|
35
|
+
bases
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolves companion fallback bases from components in one scene.
|
|
42
|
+
* @param {object} sceneDescription Scene description.
|
|
43
|
+
* @returns {object[]}
|
|
44
|
+
*/
|
|
45
|
+
static #companionBases(sceneDescription) {
|
|
46
|
+
return (
|
|
47
|
+
Array.isArray(sceneDescription?.components)
|
|
48
|
+
? sceneDescription.components
|
|
49
|
+
: []
|
|
50
|
+
)
|
|
51
|
+
.filter((component) =>
|
|
52
|
+
PcbScene3dExternalCompanionFallback.shouldKeepFallback(
|
|
53
|
+
sceneDescription,
|
|
54
|
+
component
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
.map((component) =>
|
|
58
|
+
PcbScene3dCompanionBasePlacementAdjuster.#buildBase(component)
|
|
59
|
+
)
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Builds one companion base descriptor from a component.
|
|
65
|
+
* @param {object} component Scene component.
|
|
66
|
+
* @returns {object | null}
|
|
67
|
+
*/
|
|
68
|
+
static #buildBase(component) {
|
|
69
|
+
const size = component?.body?.sizeMil || {}
|
|
70
|
+
const heightMil = Number(size.height || 0)
|
|
71
|
+
const widthMil = Number(size.width || 0)
|
|
72
|
+
const depthMil = Number(size.depth || 0)
|
|
73
|
+
if (!(heightMil > 0) || !(widthMil > 0) || !(depthMil > 0)) {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
designator: String(component?.designator || '').trim(),
|
|
79
|
+
mountSide: String(component?.mountSide || 'top').toLowerCase(),
|
|
80
|
+
positionMil: {
|
|
81
|
+
x: Number(component?.positionMil?.x || 0),
|
|
82
|
+
y: Number(component?.positionMil?.y || 0),
|
|
83
|
+
z: Number(component?.positionMil?.z || 0)
|
|
84
|
+
},
|
|
85
|
+
rotationDeg: Number(component?.rotationDeg || 0),
|
|
86
|
+
widthMil,
|
|
87
|
+
depthMil,
|
|
88
|
+
heightMil,
|
|
89
|
+
signedLiftMil:
|
|
90
|
+
(PcbScene3dCompanionBasePlacementAdjuster.#isBottomSide(
|
|
91
|
+
component?.mountSide
|
|
92
|
+
)
|
|
93
|
+
? -1
|
|
94
|
+
: 1) * heightMil
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Adjusts one placement list against all companion bases.
|
|
100
|
+
* @param {object[] | undefined} placements Original placements.
|
|
101
|
+
* @param {object[]} bases Companion bases.
|
|
102
|
+
* @returns {object[]}
|
|
103
|
+
*/
|
|
104
|
+
static #adjustPlacements(placements, bases) {
|
|
105
|
+
return (Array.isArray(placements) ? placements : []).map((placement) =>
|
|
106
|
+
PcbScene3dCompanionBasePlacementAdjuster.#adjustPlacement(
|
|
107
|
+
placement,
|
|
108
|
+
bases
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Adjusts one placement when it overlaps a companion base.
|
|
115
|
+
* @param {object} placement Original placement.
|
|
116
|
+
* @param {object[]} bases Companion bases.
|
|
117
|
+
* @returns {object}
|
|
118
|
+
*/
|
|
119
|
+
static #adjustPlacement(placement, bases) {
|
|
120
|
+
const base = bases.find((candidate) =>
|
|
121
|
+
PcbScene3dCompanionBasePlacementAdjuster.#isPlacementOnBase(
|
|
122
|
+
placement,
|
|
123
|
+
candidate
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
if (!base) {
|
|
127
|
+
return placement
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
...placement,
|
|
132
|
+
positionMil: {
|
|
133
|
+
...(placement?.positionMil || {}),
|
|
134
|
+
z: PcbScene3dCompanionBasePlacementAdjuster.#roundMil(
|
|
135
|
+
Number(placement?.positionMil?.z || 0) + base.signedLiftMil
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Checks whether a placement should be lifted onto one companion base.
|
|
143
|
+
* @param {object} placement Candidate placement.
|
|
144
|
+
* @param {object} base Companion base descriptor.
|
|
145
|
+
* @returns {boolean}
|
|
146
|
+
*/
|
|
147
|
+
static #isPlacementOnBase(placement, base) {
|
|
148
|
+
if (
|
|
149
|
+
!PcbScene3dCompanionBasePlacementAdjuster.#isSameSide(
|
|
150
|
+
placement,
|
|
151
|
+
base
|
|
152
|
+
)
|
|
153
|
+
) {
|
|
154
|
+
return false
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const local =
|
|
158
|
+
PcbScene3dCompanionBasePlacementAdjuster.#toBaseLocalPosition(
|
|
159
|
+
placement,
|
|
160
|
+
base
|
|
161
|
+
)
|
|
162
|
+
const envelopeMarginMil =
|
|
163
|
+
PcbScene3dCompanionBasePlacementAdjuster.#envelopeMarginMil(base)
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
Math.abs(local.x) <= base.widthMil / 2 + envelopeMarginMil &&
|
|
167
|
+
Math.abs(local.y) <= base.depthMil / 2 + envelopeMarginMil
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Checks whether a placement and base are on the same board side.
|
|
173
|
+
* @param {object} placement Candidate placement.
|
|
174
|
+
* @param {object} base Companion base descriptor.
|
|
175
|
+
* @returns {boolean}
|
|
176
|
+
*/
|
|
177
|
+
static #isSameSide(placement, base) {
|
|
178
|
+
return (
|
|
179
|
+
String(placement?.mountSide || 'top').toLowerCase() ===
|
|
180
|
+
String(base?.mountSide || 'top').toLowerCase()
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Converts a placement anchor into a companion-base local frame.
|
|
186
|
+
* @param {object} placement Candidate placement.
|
|
187
|
+
* @param {object} base Companion base descriptor.
|
|
188
|
+
* @returns {{ x: number, y: number }}
|
|
189
|
+
*/
|
|
190
|
+
static #toBaseLocalPosition(placement, base) {
|
|
191
|
+
const dx =
|
|
192
|
+
Number(placement?.positionMil?.x || 0) -
|
|
193
|
+
Number(base?.positionMil?.x || 0)
|
|
194
|
+
const dy =
|
|
195
|
+
Number(placement?.positionMil?.y || 0) -
|
|
196
|
+
Number(base?.positionMil?.y || 0)
|
|
197
|
+
const radians = -((Number(base?.rotationDeg || 0) * Math.PI) / 180)
|
|
198
|
+
const cos = Math.cos(radians)
|
|
199
|
+
const sin = Math.sin(radians)
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
x: dx * cos - dy * sin,
|
|
203
|
+
y: dx * sin + dy * cos
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Resolves loose matching margin for authored parts near a base edge.
|
|
209
|
+
* @param {object} base Companion base descriptor.
|
|
210
|
+
* @returns {number}
|
|
211
|
+
*/
|
|
212
|
+
static #envelopeMarginMil(base) {
|
|
213
|
+
return Math.max(
|
|
214
|
+
PcbScene3dCompanionBasePlacementAdjuster.#ENVELOPE_MIN_MARGIN_MIL,
|
|
215
|
+
Math.min(Number(base?.widthMil || 0), Number(base?.depthMil || 0)) *
|
|
216
|
+
PcbScene3dCompanionBasePlacementAdjuster.#ENVELOPE_MARGIN_RATIO
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Checks whether a mount side is bottom.
|
|
222
|
+
* @param {string | undefined} mountSide Mount side.
|
|
223
|
+
* @returns {boolean}
|
|
224
|
+
*/
|
|
225
|
+
static #isBottomSide(mountSide) {
|
|
226
|
+
return String(mountSide || 'top').toLowerCase() === 'bottom'
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Rounds one mil coordinate.
|
|
231
|
+
* @param {number} value Candidate value.
|
|
232
|
+
* @returns {number}
|
|
233
|
+
*/
|
|
234
|
+
static #roundMil(value) {
|
|
235
|
+
const rounded =
|
|
236
|
+
Math.round(
|
|
237
|
+
Number(value || 0) /
|
|
238
|
+
PcbScene3dCompanionBasePlacementAdjuster.#POSITION_EPSILON
|
|
239
|
+
) * PcbScene3dCompanionBasePlacementAdjuster.#POSITION_EPSILON
|
|
240
|
+
return Object.is(rounded, -0) ? 0 : rounded
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PcbScene3dFallbackVisibility } from './PcbScene3dFallbackVisibility.mjs'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Applies per-component hidden state on top of existing 3D visibility toggles.
|
|
3
5
|
*/
|
|
@@ -46,7 +48,7 @@ export class PcbScene3dComponentVisibility {
|
|
|
46
48
|
|
|
47
49
|
/**
|
|
48
50
|
* Applies component visibility to all selectable render roots.
|
|
49
|
-
* @param {{ selectionRoots?: Map<string, Set<any>>, hiddenDesignators?: Set<string>, fallbackBodyRoots?: Map<string, Set<any>>, loadedExternalModelDesignators?: Set<string>, modelSearchExternalModelRoots?: Set<any>, toggles?: { 'external-models'?: boolean, 'fallback-bodies'?: boolean, 'model-search-models'?: boolean }, hasLoadedBoardAssemblyModel?: boolean }} state Visibility state.
|
|
51
|
+
* @param {{ selectionRoots?: Map<string, Set<any>>, selectedDesignator?: string, hiddenDesignators?: Set<string>, fallbackBodyRoots?: Map<string, Set<any>>, loadedExternalModelDesignators?: Set<string>, modelSearchExternalModelRoots?: Set<any>, toggles?: { 'external-models'?: boolean, 'fallback-bodies'?: boolean, 'model-search-models'?: boolean }, hasLoadedBoardAssemblyModel?: boolean }} state Visibility state.
|
|
50
52
|
* @returns {void}
|
|
51
53
|
*/
|
|
52
54
|
static apply(state) {
|
|
@@ -55,6 +57,11 @@ export class PcbScene3dComponentVisibility {
|
|
|
55
57
|
return
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
const selectedVariantGroupKeys =
|
|
61
|
+
PcbScene3dComponentVisibility.#selectedVariantGroupKeys(
|
|
62
|
+
selectionRoots,
|
|
63
|
+
state?.selectedDesignator
|
|
64
|
+
)
|
|
58
65
|
selectionRoots.forEach((roots, designator) => {
|
|
59
66
|
roots?.forEach?.((rootObject) => {
|
|
60
67
|
if (!rootObject) {
|
|
@@ -66,6 +73,12 @@ export class PcbScene3dComponentVisibility {
|
|
|
66
73
|
state?.hiddenDesignators,
|
|
67
74
|
designator
|
|
68
75
|
) &&
|
|
76
|
+
!PcbScene3dComponentVisibility.#isUnselectedVariantSibling(
|
|
77
|
+
rootObject,
|
|
78
|
+
designator,
|
|
79
|
+
state?.selectedDesignator,
|
|
80
|
+
selectedVariantGroupKeys
|
|
81
|
+
) &&
|
|
69
82
|
PcbScene3dComponentVisibility.#resolveRootVisible(
|
|
70
83
|
state,
|
|
71
84
|
designator,
|
|
@@ -75,6 +88,75 @@ export class PcbScene3dComponentVisibility {
|
|
|
75
88
|
})
|
|
76
89
|
}
|
|
77
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Resolves variant groups represented by the selected component roots.
|
|
93
|
+
* @param {Map<string, Set<any>>} selectionRoots Registered selectable roots.
|
|
94
|
+
* @param {string | undefined} selectedDesignator Current selection.
|
|
95
|
+
* @returns {Set<string>}
|
|
96
|
+
*/
|
|
97
|
+
static #selectedVariantGroupKeys(selectionRoots, selectedDesignator) {
|
|
98
|
+
const selectedKey =
|
|
99
|
+
PcbScene3dComponentVisibility.#normalizeDesignator(
|
|
100
|
+
selectedDesignator
|
|
101
|
+
)
|
|
102
|
+
const groups = new Set()
|
|
103
|
+
if (!selectedKey) {
|
|
104
|
+
return groups
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
selectionRoots.get(selectedKey)?.forEach?.((rootObject) => {
|
|
108
|
+
const groupKey =
|
|
109
|
+
PcbScene3dComponentVisibility.#variantGroupKey(rootObject)
|
|
110
|
+
if (groupKey) {
|
|
111
|
+
groups.add(groupKey)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
return groups
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Checks whether one selectable root is an alternate of the selection.
|
|
119
|
+
* @param {any} rootObject Selectable root.
|
|
120
|
+
* @param {string} designator Root designator.
|
|
121
|
+
* @param {string | undefined} selectedDesignator Current selection.
|
|
122
|
+
* @param {Set<string>} selectedVariantGroupKeys Selected variant groups.
|
|
123
|
+
* @returns {boolean}
|
|
124
|
+
*/
|
|
125
|
+
static #isUnselectedVariantSibling(
|
|
126
|
+
rootObject,
|
|
127
|
+
designator,
|
|
128
|
+
selectedDesignator,
|
|
129
|
+
selectedVariantGroupKeys
|
|
130
|
+
) {
|
|
131
|
+
const selectedKey =
|
|
132
|
+
PcbScene3dComponentVisibility.#normalizeDesignator(
|
|
133
|
+
selectedDesignator
|
|
134
|
+
)
|
|
135
|
+
if (!selectedKey || !selectedVariantGroupKeys.size) {
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (
|
|
140
|
+
PcbScene3dComponentVisibility.#normalizeDesignator(designator) ===
|
|
141
|
+
selectedKey
|
|
142
|
+
) {
|
|
143
|
+
return false
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const groupKey =
|
|
147
|
+
PcbScene3dComponentVisibility.#variantGroupKey(rootObject)
|
|
148
|
+
return Boolean(groupKey && selectedVariantGroupKeys.has(groupKey))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Reads the authored co-located variant group key from a render root.
|
|
153
|
+
* @param {any} rootObject Render root.
|
|
154
|
+
* @returns {string}
|
|
155
|
+
*/
|
|
156
|
+
static #variantGroupKey(rootObject) {
|
|
157
|
+
return String(rootObject?.userData?.scene3dVariantGroupKey || '').trim()
|
|
158
|
+
}
|
|
159
|
+
|
|
78
160
|
/**
|
|
79
161
|
* Resolves root-level visibility before selected-component hiding.
|
|
80
162
|
* @param {{ fallbackBodyRoots?: Map<string, Set<any>>, loadedExternalModelDesignators?: Set<string>, modelSearchExternalModelRoots?: Set<any>, toggles?: { 'external-models'?: boolean, 'fallback-bodies'?: boolean, 'model-search-models'?: boolean }, hasLoadedBoardAssemblyModel?: boolean }} state Visibility state.
|
|
@@ -92,7 +174,8 @@ export class PcbScene3dComponentVisibility {
|
|
|
92
174
|
) {
|
|
93
175
|
return PcbScene3dComponentVisibility.#resolveFallbackVisible(
|
|
94
176
|
state,
|
|
95
|
-
designator
|
|
177
|
+
designator,
|
|
178
|
+
rootObject
|
|
96
179
|
)
|
|
97
180
|
}
|
|
98
181
|
|
|
@@ -110,14 +193,27 @@ export class PcbScene3dComponentVisibility {
|
|
|
110
193
|
* Resolves fallback root visibility from current detail toggles.
|
|
111
194
|
* @param {{ loadedExternalModelDesignators?: Set<string>, toggles?: { 'external-models'?: boolean, 'fallback-bodies'?: boolean }, hasLoadedBoardAssemblyModel?: boolean }} state Visibility state.
|
|
112
195
|
* @param {string} designator Component designator.
|
|
196
|
+
* @param {any} rootObject Render root.
|
|
113
197
|
* @returns {boolean}
|
|
114
198
|
*/
|
|
115
|
-
static #resolveFallbackVisible(state, designator) {
|
|
199
|
+
static #resolveFallbackVisible(state, designator, rootObject) {
|
|
116
200
|
const boardAssemblyActive =
|
|
117
201
|
Boolean(state?.hasLoadedBoardAssemblyModel) &&
|
|
118
202
|
Boolean(state?.toggles?.['external-models'])
|
|
119
|
-
|
|
120
|
-
|
|
203
|
+
if (boardAssemblyActive) {
|
|
204
|
+
return false
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (
|
|
208
|
+
PcbScene3dFallbackVisibility.shouldKeepExternalCompanion(
|
|
209
|
+
rootObject,
|
|
210
|
+
state?.toggles
|
|
211
|
+
)
|
|
212
|
+
) {
|
|
213
|
+
return true
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const showFallbackBodies = Boolean(state?.toggles?.['fallback-bodies'])
|
|
121
217
|
const hideForLoadedExternal =
|
|
122
218
|
Boolean(state?.toggles?.['external-models']) &&
|
|
123
219
|
state?.loadedExternalModelDesignators?.has?.(
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects fallback bodies that should remain visible as companions for partial
|
|
3
|
+
* external model placements.
|
|
4
|
+
*/
|
|
5
|
+
export class PcbScene3dExternalCompanionFallback {
|
|
6
|
+
/**
|
|
7
|
+
* Checks whether a component fallback body should remain visible as the
|
|
8
|
+
* package companion for a partial embedded external model.
|
|
9
|
+
* @param {{ externalPlacements?: object[], staticBodyPlacements?: object[] }} sceneDescription Scene data.
|
|
10
|
+
* @param {{ designator?: string, mountSide?: string, positionMil?: { x?: number, y?: number }, body?: { sizeMil?: { width?: number, depth?: number } } }} component Component scene entry.
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
static shouldKeepFallback(sceneDescription, component) {
|
|
14
|
+
const designator =
|
|
15
|
+
PcbScene3dExternalCompanionFallback.#normalizedDesignator(
|
|
16
|
+
component?.designator
|
|
17
|
+
)
|
|
18
|
+
if (!designator) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (
|
|
23
|
+
PcbScene3dExternalCompanionFallback.#hasAuthoredCompanionBase(
|
|
24
|
+
sceneDescription,
|
|
25
|
+
component
|
|
26
|
+
)
|
|
27
|
+
) {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return PcbScene3dExternalCompanionFallback.#externalPlacements(
|
|
32
|
+
sceneDescription
|
|
33
|
+
).some(
|
|
34
|
+
(placement) =>
|
|
35
|
+
PcbScene3dExternalCompanionFallback.#normalizedDesignator(
|
|
36
|
+
placement?.designator
|
|
37
|
+
) === designator &&
|
|
38
|
+
PcbScene3dExternalCompanionFallback.#isPartialEmbeddedPlacement(
|
|
39
|
+
placement
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Checks whether an authored static body already provides the package base
|
|
46
|
+
* for one partial embedded placement.
|
|
47
|
+
* @param {{ staticBodyPlacements?: object[] }} sceneDescription Scene data.
|
|
48
|
+
* @param {{ mountSide?: string, positionMil?: { x?: number, y?: number }, body?: { sizeMil?: { width?: number, depth?: number } } }} component Component scene entry.
|
|
49
|
+
* @returns {boolean}
|
|
50
|
+
*/
|
|
51
|
+
static #hasAuthoredCompanionBase(sceneDescription, component) {
|
|
52
|
+
return PcbScene3dExternalCompanionFallback.#staticBodyPlacements(
|
|
53
|
+
sceneDescription
|
|
54
|
+
).some(
|
|
55
|
+
(placement) =>
|
|
56
|
+
PcbScene3dExternalCompanionFallback.#isSameSide(
|
|
57
|
+
placement,
|
|
58
|
+
component
|
|
59
|
+
) &&
|
|
60
|
+
PcbScene3dExternalCompanionFallback.#isRenderableBase(
|
|
61
|
+
placement
|
|
62
|
+
) &&
|
|
63
|
+
PcbScene3dExternalCompanionFallback.#isNearComponent(
|
|
64
|
+
placement,
|
|
65
|
+
component
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolves normalized external placements from one scene description.
|
|
72
|
+
* @param {{ externalPlacements?: object[] }} sceneDescription Scene data.
|
|
73
|
+
* @returns {object[]}
|
|
74
|
+
*/
|
|
75
|
+
static #externalPlacements(sceneDescription) {
|
|
76
|
+
return Array.isArray(sceneDescription?.externalPlacements)
|
|
77
|
+
? sceneDescription.externalPlacements
|
|
78
|
+
: []
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resolves normalized static body placements from one scene description.
|
|
83
|
+
* @param {{ staticBodyPlacements?: object[] }} sceneDescription Scene data.
|
|
84
|
+
* @returns {object[]}
|
|
85
|
+
*/
|
|
86
|
+
static #staticBodyPlacements(sceneDescription) {
|
|
87
|
+
return Array.isArray(sceneDescription?.staticBodyPlacements)
|
|
88
|
+
? sceneDescription.staticBodyPlacements
|
|
89
|
+
: []
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Checks whether a placement and component share a board side.
|
|
94
|
+
* @param {object} placement Static body placement.
|
|
95
|
+
* @param {object} component Scene component.
|
|
96
|
+
* @returns {boolean}
|
|
97
|
+
*/
|
|
98
|
+
static #isSameSide(placement, component) {
|
|
99
|
+
return (
|
|
100
|
+
String(placement?.mountSide || 'top').toLowerCase() ===
|
|
101
|
+
String(component?.mountSide || 'top').toLowerCase()
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Checks whether one static body can act as a package base.
|
|
107
|
+
* @param {object} placement Static body placement.
|
|
108
|
+
* @returns {boolean}
|
|
109
|
+
*/
|
|
110
|
+
static #isRenderableBase(placement) {
|
|
111
|
+
const geometry = placement?.geometry || {}
|
|
112
|
+
return (
|
|
113
|
+
String(geometry?.kind || '').toLowerCase() === 'extruded-polygon' &&
|
|
114
|
+
PcbScene3dExternalCompanionFallback.#geometrySpan(geometry).width >
|
|
115
|
+
0 &&
|
|
116
|
+
PcbScene3dExternalCompanionFallback.#geometrySpan(geometry).depth >
|
|
117
|
+
0
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Checks whether one authored base is close enough to own a component base.
|
|
123
|
+
* @param {object} placement Static body placement.
|
|
124
|
+
* @param {object} component Scene component.
|
|
125
|
+
* @returns {boolean}
|
|
126
|
+
*/
|
|
127
|
+
static #isNearComponent(placement, component) {
|
|
128
|
+
const span = PcbScene3dExternalCompanionFallback.#geometrySpan(
|
|
129
|
+
placement?.geometry
|
|
130
|
+
)
|
|
131
|
+
const componentSize = component?.body?.sizeMil || {}
|
|
132
|
+
const threshold = Math.max(
|
|
133
|
+
30,
|
|
134
|
+
Number(span.width || 0) / 2,
|
|
135
|
+
Number(span.depth || 0) / 2,
|
|
136
|
+
Number(componentSize.width || 0) / 2,
|
|
137
|
+
Number(componentSize.depth || 0) / 2
|
|
138
|
+
)
|
|
139
|
+
const dx =
|
|
140
|
+
Number(placement?.positionMil?.x || 0) -
|
|
141
|
+
Number(component?.positionMil?.x || 0)
|
|
142
|
+
const dy =
|
|
143
|
+
Number(placement?.positionMil?.y || 0) -
|
|
144
|
+
Number(component?.positionMil?.y || 0)
|
|
145
|
+
|
|
146
|
+
return Math.hypot(dx, dy) <= threshold
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Resolves width and depth for one polygon geometry.
|
|
151
|
+
* @param {{ verticesMil?: { x?: number, y?: number }[] } | undefined} geometry Static geometry.
|
|
152
|
+
* @returns {{ width: number, depth: number }}
|
|
153
|
+
*/
|
|
154
|
+
static #geometrySpan(geometry) {
|
|
155
|
+
const vertices = Array.isArray(geometry?.verticesMil)
|
|
156
|
+
? geometry.verticesMil
|
|
157
|
+
: []
|
|
158
|
+
if (vertices.length < 3) {
|
|
159
|
+
return { width: 0, depth: 0 }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const xs = vertices.map((vertex) => Number(vertex?.x || 0))
|
|
163
|
+
const ys = vertices.map((vertex) => Number(vertex?.y || 0))
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
width: Math.max(...xs) - Math.min(...xs),
|
|
167
|
+
depth: Math.max(...ys) - Math.min(...ys)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Checks for embedded external placements that represent a sub-body, not a
|
|
173
|
+
* complete replacement for the procedural component body.
|
|
174
|
+
* @param {object} placement External model placement.
|
|
175
|
+
* @returns {boolean}
|
|
176
|
+
*/
|
|
177
|
+
static #isPartialEmbeddedPlacement(placement) {
|
|
178
|
+
const projection = placement?.projection || {}
|
|
179
|
+
const bounds = projection?.boundsMil || {}
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
String(placement?.externalModel?.origin || '').toLowerCase() ===
|
|
183
|
+
'embedded' &&
|
|
184
|
+
String(projection?.source || '').toLowerCase() ===
|
|
185
|
+
'model-anchor-fallback' &&
|
|
186
|
+
Math.max(
|
|
187
|
+
Number(bounds.width || 0),
|
|
188
|
+
Number(bounds.depth || 0),
|
|
189
|
+
Number(bounds.height || 0)
|
|
190
|
+
) <= 0
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Normalizes one designator for component/external placement matching.
|
|
196
|
+
* @param {unknown} designator Candidate designator.
|
|
197
|
+
* @returns {string}
|
|
198
|
+
*/
|
|
199
|
+
static #normalizedDesignator(designator) {
|
|
200
|
+
return String(designator || '').trim()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -293,6 +293,12 @@ export class PcbScene3dExternalModels {
|
|
|
293
293
|
designator,
|
|
294
294
|
sourceType: 'external-model'
|
|
295
295
|
}
|
|
296
|
+
const variantGroupKey = String(
|
|
297
|
+
placement?.coLocatedVariantGroupKey || ''
|
|
298
|
+
).trim()
|
|
299
|
+
if (variantGroupKey) {
|
|
300
|
+
wrapperGroup.userData.scene3dVariantGroupKey = variantGroupKey
|
|
301
|
+
}
|
|
296
302
|
adjustmentGroup.userData.scene3dAdjustmentTarget = true
|
|
297
303
|
adjustmentGroup.userData.scene3dAdjustmentDesignator = designator
|
|
298
304
|
adjustmentGroup.userData.scene3dAdjustmentBaseline =
|