pcb-scene3d-viewer 1.1.21 → 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/README.md +3 -2
- package/docs/api.md +27 -1
- package/docs/circuitjson.md +63 -18
- package/docs/model-format.md +13 -2
- package/package.json +1 -1
- package/spec/library-scope.md +2 -2
- package/src/PcbAssemblyBoardSubstrateBuilder.mjs +25 -5
- package/src/PcbAssemblyComponentMeshBuilder.mjs +762 -0
- package/src/PcbAssemblyFillGeometryResolver.mjs +579 -0
- package/src/PcbAssemblyFillRingNormalizer.mjs +209 -0
- package/src/PcbAssemblyGeometryBuilder.mjs +41 -301
- package/src/PcbAssemblyGltfModelMeshParser.mjs +912 -0
- package/src/PcbAssemblyGltfValidator.mjs +460 -0
- package/src/PcbAssemblyGltfWriter.mjs +801 -0
- package/src/PcbAssemblyMeshUtils.mjs +117 -0
- package/src/PcbAssemblyModelMeshLoader.mjs +18 -0
- package/src/PcbAssemblyPadMeshBuilder.mjs +394 -0
- package/src/PcbAssemblyStepWriter.mjs +24 -2
- package/src/PcbAssemblyTextModelMeshParser.mjs +602 -0
- package/src/PcbModelArchiveExporter.mjs +521 -7
- package/src/PcbScene3dCircuitJsonAdapter.mjs +409 -7
- package/src/PcbScene3dCircuitJsonModelTransform.mjs +232 -0
- package/src/PcbScene3dCompanionBasePlacementAdjuster.mjs +242 -0
- package/src/PcbScene3dComponentVisibility.mjs +100 -5
- package/src/PcbScene3dController.mjs +25 -55
- package/src/PcbScene3dCopperDetailFilter.mjs +86 -9
- package/src/PcbScene3dCopperDetailGroupBuilder.mjs +186 -15
- package/src/PcbScene3dCopperDrillCutoutBuilder.mjs +258 -0
- package/src/PcbScene3dCopperFactory.mjs +99 -85
- package/src/PcbScene3dCopperFillMeshBuilder.mjs +393 -0
- package/src/PcbScene3dCopperLayerFilter.mjs +32 -3
- package/src/PcbScene3dCutoutGeometryFilter.mjs +17 -14
- package/src/PcbScene3dExternalCompanionFallback.mjs +202 -0
- package/src/PcbScene3dExternalModelCenteringPolicy.mjs +21 -0
- package/src/PcbScene3dExternalModelOpacity.mjs +51 -0
- package/src/PcbScene3dExternalModelPlacementRepair.mjs +5 -2
- package/src/PcbScene3dExternalModelSourceOriginPolicy.mjs +41 -0
- package/src/PcbScene3dExternalModels.mjs +16 -7
- package/src/PcbScene3dFallbackBodyFactory.mjs +105 -0
- package/src/PcbScene3dFallbackVisibility.mjs +58 -1
- package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +68 -0
- package/src/PcbScene3dPadFactory.mjs +35 -2
- package/src/PcbScene3dRenderGroupVisibility.mjs +8 -1
- package/src/PcbScene3dRuntime.mjs +94 -100
- package/src/PcbScene3dRuntimeHelpers.mjs +39 -0
- package/src/PcbScene3dSelectionIndexBuilder.mjs +87 -0
- package/src/PcbScene3dSelectionInspectorRenderer.mjs +50 -11
- package/src/PcbScene3dSelectionMarkerFactory.mjs +333 -0
- package/src/PcbScene3dSelectionMarkerOverlay.mjs +70 -0
- package/src/PcbScene3dSelectionStyler.mjs +52 -2
- package/src/PcbScene3dStaticBodyFactory.mjs +263 -0
- package/src/PcbScene3dText.mjs +1 -0
- package/src/PcbScene3dTransparentMeshSplitter.mjs +623 -0
- package/src/PcbScene3dViaFactory.mjs +27 -7
- package/src/scene3d.mjs +3 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { CircuitJsonUnits } from 'circuitjson-toolkit'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes CAD component model placement hints from CircuitJSON.
|
|
5
|
+
*/
|
|
6
|
+
export class PcbScene3dCircuitJsonModelTransform {
|
|
7
|
+
/**
|
|
8
|
+
* Builds optional model-local transforms.
|
|
9
|
+
* @param {object} cadComponent CAD component element.
|
|
10
|
+
* @returns {object | null}
|
|
11
|
+
*/
|
|
12
|
+
static build(cadComponent) {
|
|
13
|
+
const offset = cadComponent?.model_offset || cadComponent?.offset
|
|
14
|
+
const rotation =
|
|
15
|
+
cadComponent?.model_rotation || cadComponent?.model_rotation_deg
|
|
16
|
+
const rawScale = cadComponent?.model_scale
|
|
17
|
+
const unitScale = PcbScene3dCircuitJsonModelTransform.#positiveFinite(
|
|
18
|
+
cadComponent?.model_unit_to_mm_scale_factor,
|
|
19
|
+
1
|
|
20
|
+
)
|
|
21
|
+
const originPositionMil =
|
|
22
|
+
PcbScene3dCircuitJsonModelTransform.#pointMmToMil(
|
|
23
|
+
cadComponent?.model_origin_position
|
|
24
|
+
)
|
|
25
|
+
const originAlignment =
|
|
26
|
+
PcbScene3dCircuitJsonModelTransform.#stringValue(
|
|
27
|
+
cadComponent?.model_origin_alignment
|
|
28
|
+
)
|
|
29
|
+
const objectFit = PcbScene3dCircuitJsonModelTransform.#stringValue(
|
|
30
|
+
cadComponent?.model_object_fit
|
|
31
|
+
)
|
|
32
|
+
const boardNormalDirection =
|
|
33
|
+
PcbScene3dCircuitJsonModelTransform.#stringValue(
|
|
34
|
+
cadComponent?.model_board_normal_direction
|
|
35
|
+
)
|
|
36
|
+
const targetSizeMil = PcbScene3dCircuitJsonModelTransform.#sizeMmToMil(
|
|
37
|
+
cadComponent?.model_size || cadComponent?.size
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if (
|
|
41
|
+
!offset &&
|
|
42
|
+
!rotation &&
|
|
43
|
+
rawScale === undefined &&
|
|
44
|
+
unitScale === 1 &&
|
|
45
|
+
!originPositionMil &&
|
|
46
|
+
!originAlignment &&
|
|
47
|
+
!objectFit &&
|
|
48
|
+
!boardNormalDirection &&
|
|
49
|
+
!targetSizeMil
|
|
50
|
+
) {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
offsetMil: PcbScene3dCircuitJsonModelTransform.#offsetMil(offset),
|
|
56
|
+
rotationDeg:
|
|
57
|
+
PcbScene3dCircuitJsonModelTransform.#rotationDeg(rotation),
|
|
58
|
+
scale: PcbScene3dCircuitJsonModelTransform.#modelScale(
|
|
59
|
+
rawScale,
|
|
60
|
+
unitScale
|
|
61
|
+
),
|
|
62
|
+
...PcbScene3dCircuitJsonModelTransform.#optionalFields({
|
|
63
|
+
originPositionMil,
|
|
64
|
+
originAlignment,
|
|
65
|
+
objectFit,
|
|
66
|
+
boardNormalDirection,
|
|
67
|
+
targetSizeMil
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Builds display metadata for a CAD component placement.
|
|
74
|
+
* @param {object} cadComponent CAD component element.
|
|
75
|
+
* @returns {{ bodyOpacity?: number }}
|
|
76
|
+
*/
|
|
77
|
+
static displayMetadata(cadComponent) {
|
|
78
|
+
const opacity =
|
|
79
|
+
PcbScene3dCircuitJsonModelTransform.#displayOpacity(cadComponent)
|
|
80
|
+
return opacity === null ? {} : { bodyOpacity: opacity }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Builds optional transform fields from populated metadata.
|
|
85
|
+
* @param {object} fields Candidate optional fields.
|
|
86
|
+
* @returns {object}
|
|
87
|
+
*/
|
|
88
|
+
static #optionalFields(fields) {
|
|
89
|
+
return Object.fromEntries(
|
|
90
|
+
Object.entries(fields).filter((entry) => entry[1] !== null)
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Resolves model-local offset fields in mils.
|
|
96
|
+
* @param {object | undefined} offset Offset source in millimeters.
|
|
97
|
+
* @returns {{ x: number, y: number, z: number }}
|
|
98
|
+
*/
|
|
99
|
+
static #offsetMil(offset) {
|
|
100
|
+
return {
|
|
101
|
+
x: CircuitJsonUnits.mmToMil(offset?.x, 0),
|
|
102
|
+
y: CircuitJsonUnits.mmToMil(offset?.y, 0),
|
|
103
|
+
z: CircuitJsonUnits.mmToMil(offset?.z, 0)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Resolves model-local rotation fields.
|
|
109
|
+
* @param {object | number | undefined} rotation Rotation metadata.
|
|
110
|
+
* @returns {{ x: number, y: number, z: number }}
|
|
111
|
+
*/
|
|
112
|
+
static #rotationDeg(rotation) {
|
|
113
|
+
if (typeof rotation === 'number') {
|
|
114
|
+
return { x: 0, y: 0, z: Number(rotation || 0) }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
x: Number(rotation?.x || 0),
|
|
119
|
+
y: Number(rotation?.y || 0),
|
|
120
|
+
z: Number(rotation?.z || 0)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Normalizes model scale fields and unit scale.
|
|
126
|
+
* @param {unknown} scale Scale metadata.
|
|
127
|
+
* @param {number} unitScale Unit-to-millimeter scale factor.
|
|
128
|
+
* @returns {{ x: number, y: number, z: number }}
|
|
129
|
+
*/
|
|
130
|
+
static #modelScale(scale, unitScale) {
|
|
131
|
+
if (typeof scale === 'number') {
|
|
132
|
+
const value = Number(scale) || 1
|
|
133
|
+
return {
|
|
134
|
+
x: value * unitScale,
|
|
135
|
+
y: value * unitScale,
|
|
136
|
+
z: value * unitScale
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
x: (Number(scale?.x ?? 1) || 1) * unitScale,
|
|
142
|
+
y: (Number(scale?.y ?? 1) || 1) * unitScale,
|
|
143
|
+
z: (Number(scale?.z ?? 1) || 1) * unitScale
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Converts an optional point from millimeters to mils.
|
|
149
|
+
* @param {object | undefined} point Point metadata.
|
|
150
|
+
* @returns {{ x: number, y: number, z: number } | null}
|
|
151
|
+
*/
|
|
152
|
+
static #pointMmToMil(point) {
|
|
153
|
+
if (
|
|
154
|
+
!Number.isFinite(Number(point?.x)) &&
|
|
155
|
+
!Number.isFinite(Number(point?.y)) &&
|
|
156
|
+
!Number.isFinite(Number(point?.z))
|
|
157
|
+
) {
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
x: CircuitJsonUnits.mmToMil(point?.x, 0),
|
|
163
|
+
y: CircuitJsonUnits.mmToMil(point?.y, 0),
|
|
164
|
+
z: CircuitJsonUnits.mmToMil(point?.z, 0)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Converts optional model target size from millimeters to mils.
|
|
170
|
+
* @param {object | undefined} size Size metadata.
|
|
171
|
+
* @returns {{ x: number, y: number, z: number } | null}
|
|
172
|
+
*/
|
|
173
|
+
static #sizeMmToMil(size) {
|
|
174
|
+
const x = size?.x ?? size?.width
|
|
175
|
+
const y = size?.y ?? size?.height
|
|
176
|
+
const z = size?.z ?? size?.depth
|
|
177
|
+
if (
|
|
178
|
+
!Number.isFinite(Number(x)) &&
|
|
179
|
+
!Number.isFinite(Number(y)) &&
|
|
180
|
+
!Number.isFinite(Number(z))
|
|
181
|
+
) {
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
x: CircuitJsonUnits.mmToMil(x, 0),
|
|
187
|
+
y: CircuitJsonUnits.mmToMil(y, 0),
|
|
188
|
+
z: CircuitJsonUnits.mmToMil(z, 0)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Resolves optional display opacity.
|
|
194
|
+
* @param {object} cadComponent CAD component element.
|
|
195
|
+
* @returns {number | null}
|
|
196
|
+
*/
|
|
197
|
+
static #displayOpacity(cadComponent) {
|
|
198
|
+
for (const value of [
|
|
199
|
+
cadComponent?.bodyOpacity,
|
|
200
|
+
cadComponent?.body_opacity,
|
|
201
|
+
cadComponent?.model_opacity
|
|
202
|
+
]) {
|
|
203
|
+
const opacity = Number(value)
|
|
204
|
+
if (Number.isFinite(opacity) && opacity > 0 && opacity < 1) {
|
|
205
|
+
return opacity
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return cadComponent?.show_as_translucent_model === true ? 0.5 : null
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Returns a non-empty string or null.
|
|
214
|
+
* @param {unknown} value Candidate string value.
|
|
215
|
+
* @returns {string | null}
|
|
216
|
+
*/
|
|
217
|
+
static #stringValue(value) {
|
|
218
|
+
const text = String(value || '').trim()
|
|
219
|
+
return text ? text : null
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Returns a positive finite number or fallback.
|
|
224
|
+
* @param {unknown} value Candidate value.
|
|
225
|
+
* @param {number} fallback Fallback value.
|
|
226
|
+
* @returns {number}
|
|
227
|
+
*/
|
|
228
|
+
static #positiveFinite(value, fallback) {
|
|
229
|
+
const number = Number(value)
|
|
230
|
+
return Number.isFinite(number) && number > 0 ? number : fallback
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -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,74 @@ 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(designator)
|
|
133
|
+
const activeKey =
|
|
134
|
+
PcbScene3dComponentVisibility.#normalizeDesignator(
|
|
135
|
+
selectedDesignator
|
|
136
|
+
)
|
|
137
|
+
if (!activeKey || !selectedVariantGroupKeys.size) {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (selectedKey === activeKey) {
|
|
142
|
+
return false
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const groupKey =
|
|
146
|
+
PcbScene3dComponentVisibility.#variantGroupKey(rootObject)
|
|
147
|
+
return Boolean(groupKey && selectedVariantGroupKeys.has(groupKey))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Reads the authored co-located variant group key from a render root.
|
|
152
|
+
* @param {any} rootObject Render root.
|
|
153
|
+
* @returns {string}
|
|
154
|
+
*/
|
|
155
|
+
static #variantGroupKey(rootObject) {
|
|
156
|
+
return String(rootObject?.userData?.scene3dVariantGroupKey || '').trim()
|
|
157
|
+
}
|
|
158
|
+
|
|
78
159
|
/**
|
|
79
160
|
* Resolves root-level visibility before selected-component hiding.
|
|
80
161
|
* @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 +173,8 @@ export class PcbScene3dComponentVisibility {
|
|
|
92
173
|
) {
|
|
93
174
|
return PcbScene3dComponentVisibility.#resolveFallbackVisible(
|
|
94
175
|
state,
|
|
95
|
-
designator
|
|
176
|
+
designator,
|
|
177
|
+
rootObject
|
|
96
178
|
)
|
|
97
179
|
}
|
|
98
180
|
|
|
@@ -110,14 +192,27 @@ export class PcbScene3dComponentVisibility {
|
|
|
110
192
|
* Resolves fallback root visibility from current detail toggles.
|
|
111
193
|
* @param {{ loadedExternalModelDesignators?: Set<string>, toggles?: { 'external-models'?: boolean, 'fallback-bodies'?: boolean }, hasLoadedBoardAssemblyModel?: boolean }} state Visibility state.
|
|
112
194
|
* @param {string} designator Component designator.
|
|
195
|
+
* @param {any} rootObject Render root.
|
|
113
196
|
* @returns {boolean}
|
|
114
197
|
*/
|
|
115
|
-
static #resolveFallbackVisible(state, designator) {
|
|
198
|
+
static #resolveFallbackVisible(state, designator, rootObject) {
|
|
116
199
|
const boardAssemblyActive =
|
|
117
200
|
Boolean(state?.hasLoadedBoardAssemblyModel) &&
|
|
118
201
|
Boolean(state?.toggles?.['external-models'])
|
|
119
|
-
|
|
120
|
-
|
|
202
|
+
if (boardAssemblyActive) {
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (
|
|
207
|
+
PcbScene3dFallbackVisibility.shouldKeepExternalCompanion(
|
|
208
|
+
rootObject,
|
|
209
|
+
state?.toggles
|
|
210
|
+
)
|
|
211
|
+
) {
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const showFallbackBodies = Boolean(state?.toggles?.['fallback-bodies'])
|
|
121
216
|
const hideForLoadedExternal =
|
|
122
217
|
Boolean(state?.toggles?.['external-models']) &&
|
|
123
218
|
state?.loadedExternalModelDesignators?.has?.(
|