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,105 @@
|
|
|
1
|
+
import { PcbScene3dBodyColor } from './PcbScene3dBodyColor.mjs'
|
|
2
|
+
import { PcbScene3dComponentAdjustment } from './PcbScene3dComponentAdjustment.mjs'
|
|
3
|
+
import { PcbScene3dMountRig } from './PcbScene3dMountRig.mjs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds procedural fallback body render roots for PCB components.
|
|
7
|
+
*/
|
|
8
|
+
export class PcbScene3dFallbackBodyFactory {
|
|
9
|
+
/**
|
|
10
|
+
* Builds one procedural fallback body root.
|
|
11
|
+
* @param {any} THREE Three.js namespace.
|
|
12
|
+
* @param {{ designator?: string, positionMil: { x: number, y: number, z: number }, rotationDeg: number, mountSide: string, body: { family: string, sizeMil: { width: number, depth: number, height: number } } }} component Component scene entry.
|
|
13
|
+
* @param {{ companionBase?: boolean }} [options] Rendering options.
|
|
14
|
+
* @returns {{ rootGroup: any, adjustmentGroup: any }}
|
|
15
|
+
*/
|
|
16
|
+
static build(THREE, component, options = {}) {
|
|
17
|
+
const family = component.body.family
|
|
18
|
+
const size = component.body.sizeMil
|
|
19
|
+
const material = new THREE.MeshStandardMaterial({
|
|
20
|
+
color: PcbScene3dFallbackBodyFactory.#resolveColor(family, options),
|
|
21
|
+
roughness: 0.72,
|
|
22
|
+
metalness: family === 'chip' ? 0.12 : 0.08
|
|
23
|
+
})
|
|
24
|
+
const mesh = PcbScene3dFallbackBodyFactory.#buildMesh(
|
|
25
|
+
THREE,
|
|
26
|
+
family,
|
|
27
|
+
size,
|
|
28
|
+
material
|
|
29
|
+
)
|
|
30
|
+
const mountRig = PcbScene3dMountRig.create(THREE, component)
|
|
31
|
+
const adjustmentGroup =
|
|
32
|
+
PcbScene3dFallbackBodyFactory.#buildAdjustmentGroup(
|
|
33
|
+
THREE,
|
|
34
|
+
component
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
mountRig.rootGroup.userData.scene3dSelection = {
|
|
38
|
+
designator: String(component?.designator || 'component'),
|
|
39
|
+
sourceType: 'component'
|
|
40
|
+
}
|
|
41
|
+
adjustmentGroup.add(mesh)
|
|
42
|
+
mountRig.faceGroup.add(adjustmentGroup)
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
rootGroup: mountRig.rootGroup,
|
|
46
|
+
adjustmentGroup
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolves the fallback body color.
|
|
52
|
+
* @param {string} family Package family.
|
|
53
|
+
* @param {{ companionBase?: boolean }} options Rendering options.
|
|
54
|
+
* @returns {number}
|
|
55
|
+
*/
|
|
56
|
+
static #resolveColor(family, options) {
|
|
57
|
+
return options?.companionBase
|
|
58
|
+
? 0x808080
|
|
59
|
+
: PcbScene3dBodyColor.resolve(family)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Builds the body mesh for one package family.
|
|
64
|
+
* @param {any} THREE Three.js namespace.
|
|
65
|
+
* @param {string} family Package family.
|
|
66
|
+
* @param {{ width: number, depth: number, height: number }} size Body size.
|
|
67
|
+
* @param {any} material Mesh material.
|
|
68
|
+
* @returns {any}
|
|
69
|
+
*/
|
|
70
|
+
static #buildMesh(THREE, family, size, material) {
|
|
71
|
+
if (family === 'radial-capacitor' || family === 'test-point') {
|
|
72
|
+
return new THREE.Mesh(
|
|
73
|
+
new THREE.CylinderGeometry(
|
|
74
|
+
size.width / 2,
|
|
75
|
+
size.width / 2,
|
|
76
|
+
size.height,
|
|
77
|
+
28
|
|
78
|
+
),
|
|
79
|
+
material
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return new THREE.Mesh(
|
|
84
|
+
new THREE.BoxGeometry(size.width, size.depth, size.height),
|
|
85
|
+
material
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Builds the transform node used by live component adjustments.
|
|
91
|
+
* @param {any} THREE Three.js namespace.
|
|
92
|
+
* @param {{ designator?: string }} component Component scene entry.
|
|
93
|
+
* @returns {any}
|
|
94
|
+
*/
|
|
95
|
+
static #buildAdjustmentGroup(THREE, component) {
|
|
96
|
+
const adjustmentGroup = new THREE.Group()
|
|
97
|
+
adjustmentGroup.userData.scene3dAdjustmentTarget = true
|
|
98
|
+
adjustmentGroup.userData.scene3dAdjustmentDesignator = String(
|
|
99
|
+
component?.designator || 'component'
|
|
100
|
+
)
|
|
101
|
+
adjustmentGroup.userData.scene3dAdjustmentBaseline =
|
|
102
|
+
PcbScene3dComponentAdjustment.neutral()
|
|
103
|
+
return adjustmentGroup
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -74,9 +74,66 @@ export class PcbScene3dFallbackVisibility {
|
|
|
74
74
|
return
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
const stitchedCompanion =
|
|
78
|
+
PcbScene3dFallbackVisibility.shouldKeepExternalCompanion(
|
|
79
|
+
rootObject,
|
|
80
|
+
toggles
|
|
81
|
+
)
|
|
77
82
|
rootObject.visible =
|
|
78
|
-
|
|
83
|
+
stitchedCompanion ||
|
|
84
|
+
(showFallbackBodies && !hideForLoadedExternal)
|
|
79
85
|
})
|
|
80
86
|
})
|
|
81
87
|
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Checks whether any fallback root should keep the fallback group visible
|
|
91
|
+
* as part of a stitched external-model package.
|
|
92
|
+
* @param {Map<string, Set<any>>} fallbackRoots Fallback root registry.
|
|
93
|
+
* @param {{ 'external-models'?: boolean }} toggles Detail toggles.
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*/
|
|
96
|
+
static hasVisibleExternalCompanion(fallbackRoots, toggles) {
|
|
97
|
+
if (
|
|
98
|
+
!(fallbackRoots instanceof Map) ||
|
|
99
|
+
!Boolean(toggles?.['external-models'])
|
|
100
|
+
) {
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let hasCompanion = false
|
|
105
|
+
fallbackRoots.forEach((roots) => {
|
|
106
|
+
roots?.forEach?.((rootObject) => {
|
|
107
|
+
hasCompanion =
|
|
108
|
+
hasCompanion ||
|
|
109
|
+
PcbScene3dFallbackVisibility.#isExternalCompanion(
|
|
110
|
+
rootObject
|
|
111
|
+
)
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
return hasCompanion
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Checks whether one fallback body should stay visible as a stitched
|
|
119
|
+
* external-model companion under the active toggles.
|
|
120
|
+
* @param {any} rootObject Fallback root object.
|
|
121
|
+
* @param {{ 'external-models'?: boolean }} toggles Detail toggles.
|
|
122
|
+
* @returns {boolean}
|
|
123
|
+
*/
|
|
124
|
+
static shouldKeepExternalCompanion(rootObject, toggles) {
|
|
125
|
+
return (
|
|
126
|
+
Boolean(toggles?.['external-models']) &&
|
|
127
|
+
PcbScene3dFallbackVisibility.#isExternalCompanion(rootObject)
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Checks whether a fallback body is part of a stitched external model.
|
|
133
|
+
* @param {any} rootObject Fallback root object.
|
|
134
|
+
* @returns {boolean}
|
|
135
|
+
*/
|
|
136
|
+
static #isExternalCompanion(rootObject) {
|
|
137
|
+
return Boolean(rootObject?.userData?.scene3dFallbackExternalCompanion)
|
|
138
|
+
}
|
|
82
139
|
}
|
|
@@ -37,9 +37,16 @@ export class PcbScene3dRenderGroupVisibility {
|
|
|
37
37
|
state?.groups?.get('copper'),
|
|
38
38
|
boardAssemblyActive
|
|
39
39
|
)
|
|
40
|
+
const showFallbackBodies =
|
|
41
|
+
!boardAssemblyActive &&
|
|
42
|
+
(Boolean(state?.toggles?.['fallback-bodies']) ||
|
|
43
|
+
PcbScene3dFallbackVisibility.hasVisibleExternalCompanion(
|
|
44
|
+
state?.fallbackBodyRoots,
|
|
45
|
+
state?.toggles
|
|
46
|
+
))
|
|
40
47
|
PcbScene3dRenderGroupVisibility.#setVisible(
|
|
41
48
|
state?.groups?.get('fallback-bodies'),
|
|
42
|
-
|
|
49
|
+
showFallbackBodies
|
|
43
50
|
)
|
|
44
51
|
|
|
45
52
|
PcbScene3dFallbackVisibility.applyVisibility(
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { PcbScene3dBoardSolderMaskFactory } from './PcbScene3dBoardSolderMaskFactory.mjs'
|
|
2
|
-
import { PcbScene3dBodyColor } from './PcbScene3dBodyColor.mjs'
|
|
3
2
|
import { PcbScene3dCameraRig } from './PcbScene3dCameraRig.mjs'
|
|
4
3
|
import { PcbScene3dCircuitJsonAdapter } from './PcbScene3dCircuitJsonAdapter.mjs'
|
|
5
4
|
import { PcbScene3dComponentVisibility } from './PcbScene3dComponentVisibility.mjs'
|
|
6
5
|
import { PcbScene3dComponentAdjustment } from './PcbScene3dComponentAdjustment.mjs'
|
|
7
6
|
import { PcbScene3dComponentAdjustmentRegistry } from './PcbScene3dComponentAdjustmentRegistry.mjs'
|
|
7
|
+
import { PcbScene3dCompanionBasePlacementAdjuster } from './PcbScene3dCompanionBasePlacementAdjuster.mjs'
|
|
8
8
|
import { PcbScene3dCopperDetailGroupBuilder } from './PcbScene3dCopperDetailGroupBuilder.mjs'
|
|
9
9
|
import { PcbScene3dCopperFactory } from './PcbScene3dCopperFactory.mjs'
|
|
10
10
|
import { PcbScene3dDetailCoordinateNormalizer } from './PcbScene3dDetailCoordinateNormalizer.mjs'
|
|
11
11
|
import { PcbScene3dDrillVoidFactory } from './PcbScene3dDrillVoidFactory.mjs'
|
|
12
|
+
import { PcbScene3dExternalCompanionFallback } from './PcbScene3dExternalCompanionFallback.mjs'
|
|
12
13
|
import { PcbScene3dExternalModels } from './PcbScene3dExternalModels.mjs'
|
|
14
|
+
import { PcbScene3dFallbackBodyFactory } from './PcbScene3dFallbackBodyFactory.mjs'
|
|
13
15
|
import { PcbScene3dFallbackVisibility } from './PcbScene3dFallbackVisibility.mjs'
|
|
14
16
|
import { PcbScene3dInteractionHints } from './PcbScene3dInteractionHints.mjs'
|
|
15
|
-
import { PcbScene3dMountRig } from './PcbScene3dMountRig.mjs'
|
|
16
17
|
import { PcbScene3dModelSearchPlacement } from './PcbScene3dModelSearchPlacement.mjs'
|
|
17
18
|
import { PcbScene3dPresetState } from './PcbScene3dPresetState.mjs'
|
|
18
19
|
import { PcbScene3dRenderGroupVisibility } from './PcbScene3dRenderGroupVisibility.mjs'
|
|
@@ -22,6 +23,7 @@ import { PcbScene3dSilkscreenChunkedFactory } from './PcbScene3dSilkscreenChunke
|
|
|
22
23
|
import { PcbScene3dTrueTypeTextFactory } from './PcbScene3dTrueTypeTextFactory.mjs'
|
|
23
24
|
import { PcbScene3dSelectionResolver } from './PcbScene3dSelectionResolver.mjs'
|
|
24
25
|
import { PcbScene3dSelectionStyler } from './PcbScene3dSelectionStyler.mjs'
|
|
26
|
+
import { PcbScene3dStaticBodyFactory } from './PcbScene3dStaticBodyFactory.mjs'
|
|
25
27
|
import { PcbScene3dViewportResize } from './PcbScene3dViewportResize.mjs'
|
|
26
28
|
import { PcbScene3dViewScale } from './PcbScene3dViewScale.mjs'
|
|
27
29
|
const SILKSCREEN_COPPER_CLEARANCE_MIL = 0.12
|
|
@@ -37,6 +39,7 @@ const Z_MIL = {
|
|
|
37
39
|
export class PcbScene3dRuntime {
|
|
38
40
|
#viewportNode
|
|
39
41
|
#sceneDescription
|
|
42
|
+
#placementSceneDescription
|
|
40
43
|
#hooks
|
|
41
44
|
#toggles
|
|
42
45
|
#groups
|
|
@@ -78,6 +81,8 @@ export class PcbScene3dRuntime {
|
|
|
78
81
|
PcbScene3dRuntime.#normalizeSceneDescription(sceneDescription)
|
|
79
82
|
this.#viewportNode = viewportNode
|
|
80
83
|
this.#sceneDescription = renderModel
|
|
84
|
+
this.#placementSceneDescription =
|
|
85
|
+
PcbScene3dCompanionBasePlacementAdjuster.adjust(renderModel)
|
|
81
86
|
this.#hooks = hooks
|
|
82
87
|
this.#toggles = {
|
|
83
88
|
'external-models': true,
|
|
@@ -385,19 +390,60 @@ export class PcbScene3dRuntime {
|
|
|
385
390
|
this.#groups.set('copper', copperGroup)
|
|
386
391
|
this.#rootGroup.add(copperGroup)
|
|
387
392
|
const fallbackBodiesGroup = new THREE.Group()
|
|
393
|
+
const staticBodiesGroup = new THREE.Group()
|
|
388
394
|
const externalModelsGroup = new THREE.Group()
|
|
389
395
|
this.#sceneDescription.components.forEach((component) => {
|
|
390
|
-
|
|
391
|
-
|
|
396
|
+
if (component?.renderFallbackBody === false) {
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const isCompanionBase =
|
|
401
|
+
PcbScene3dExternalCompanionFallback.shouldKeepFallback(
|
|
402
|
+
this.#sceneDescription,
|
|
403
|
+
component
|
|
404
|
+
)
|
|
405
|
+
const fallbackBody = PcbScene3dFallbackBodyFactory.build(
|
|
406
|
+
THREE,
|
|
407
|
+
component,
|
|
408
|
+
{ companionBase: isCompanionBase }
|
|
409
|
+
)
|
|
410
|
+
if (isCompanionBase) {
|
|
411
|
+
fallbackBody.rootGroup.userData.scene3dFallbackExternalCompanion = true
|
|
412
|
+
}
|
|
413
|
+
fallbackBodiesGroup.add(fallbackBody.rootGroup)
|
|
392
414
|
PcbScene3dFallbackVisibility.registerFallbackRoot(
|
|
393
415
|
this.#fallbackBodyRoots,
|
|
394
416
|
component?.designator,
|
|
395
|
-
fallbackBody
|
|
417
|
+
fallbackBody.rootGroup
|
|
418
|
+
)
|
|
419
|
+
this.#registerSelectionRoot(
|
|
420
|
+
component?.designator,
|
|
421
|
+
fallbackBody.rootGroup
|
|
422
|
+
)
|
|
423
|
+
this.#componentAdjustmentRegistry.register(
|
|
424
|
+
component?.designator,
|
|
425
|
+
fallbackBody.adjustmentGroup
|
|
426
|
+
)
|
|
427
|
+
})
|
|
428
|
+
PcbScene3dStaticBodyFactory.buildMany(
|
|
429
|
+
THREE,
|
|
430
|
+
this.#placementSceneDescription.staticBodyPlacements
|
|
431
|
+
).forEach((staticBody) => {
|
|
432
|
+
staticBodiesGroup.add(staticBody.rootGroup)
|
|
433
|
+
this.#registerSelectionRoot(
|
|
434
|
+
staticBody.placement?.designator,
|
|
435
|
+
staticBody.rootGroup
|
|
436
|
+
)
|
|
437
|
+
this.#componentAdjustmentRegistry.register(
|
|
438
|
+
staticBody.placement?.designator,
|
|
439
|
+
staticBody.adjustmentGroup
|
|
396
440
|
)
|
|
397
441
|
})
|
|
398
442
|
this.#groups.set('fallback-bodies', fallbackBodiesGroup)
|
|
443
|
+
this.#groups.set('static-bodies', staticBodiesGroup)
|
|
399
444
|
this.#groups.set('external-models', externalModelsGroup)
|
|
400
445
|
this.#rootGroup.add(fallbackBodiesGroup)
|
|
446
|
+
this.#rootGroup.add(staticBodiesGroup)
|
|
401
447
|
this.#rootGroup.add(externalModelsGroup)
|
|
402
448
|
const boardSpan = Math.max(board.widthMil, board.heightMil, 1)
|
|
403
449
|
this.#scene.fog = new THREE.Fog(
|
|
@@ -536,63 +582,6 @@ export class PcbScene3dRuntime {
|
|
|
536
582
|
}
|
|
537
583
|
}
|
|
538
584
|
|
|
539
|
-
/**
|
|
540
|
-
* Builds one procedural fallback body mesh.
|
|
541
|
-
* @param {{ positionMil: { x: number, y: number, z: number }, rotationDeg: number, mountSide: string, body: { family: string, sizeMil: { width: number, depth: number, height: number } } }} component
|
|
542
|
-
* @returns {any}
|
|
543
|
-
*/
|
|
544
|
-
#buildFallbackBody(component) {
|
|
545
|
-
const THREE = this.#three
|
|
546
|
-
const family = component.body.family
|
|
547
|
-
const size = component.body.sizeMil
|
|
548
|
-
const material = new THREE.MeshStandardMaterial({
|
|
549
|
-
color: PcbScene3dBodyColor.resolve(family),
|
|
550
|
-
roughness: 0.72,
|
|
551
|
-
metalness: family === 'chip' ? 0.12 : 0.08
|
|
552
|
-
})
|
|
553
|
-
|
|
554
|
-
let mesh
|
|
555
|
-
if (family === 'radial-capacitor' || family === 'test-point') {
|
|
556
|
-
mesh = new THREE.Mesh(
|
|
557
|
-
new THREE.CylinderGeometry(
|
|
558
|
-
size.width / 2,
|
|
559
|
-
size.width / 2,
|
|
560
|
-
size.height,
|
|
561
|
-
28
|
|
562
|
-
),
|
|
563
|
-
material
|
|
564
|
-
)
|
|
565
|
-
} else {
|
|
566
|
-
mesh = new THREE.Mesh(
|
|
567
|
-
new THREE.BoxGeometry(size.width, size.depth, size.height),
|
|
568
|
-
material
|
|
569
|
-
)
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
const mountRig = PcbScene3dMountRig.create(THREE, component)
|
|
573
|
-
const rootGroup = mountRig.rootGroup
|
|
574
|
-
const adjustmentGroup = new THREE.Group()
|
|
575
|
-
|
|
576
|
-
rootGroup.userData.scene3dSelection = {
|
|
577
|
-
designator: String(component?.designator || 'component'),
|
|
578
|
-
sourceType: 'component'
|
|
579
|
-
}
|
|
580
|
-
adjustmentGroup.userData.scene3dAdjustmentTarget = true
|
|
581
|
-
adjustmentGroup.userData.scene3dAdjustmentDesignator = String(
|
|
582
|
-
component?.designator || 'component'
|
|
583
|
-
)
|
|
584
|
-
adjustmentGroup.userData.scene3dAdjustmentBaseline =
|
|
585
|
-
PcbScene3dComponentAdjustment.neutral()
|
|
586
|
-
this.#registerSelectionRoot(component?.designator, rootGroup)
|
|
587
|
-
this.#componentAdjustmentRegistry.register(
|
|
588
|
-
component?.designator,
|
|
589
|
-
adjustmentGroup
|
|
590
|
-
)
|
|
591
|
-
adjustmentGroup.add(mesh)
|
|
592
|
-
mountRig.faceGroup.add(adjustmentGroup)
|
|
593
|
-
return rootGroup
|
|
594
|
-
}
|
|
595
|
-
|
|
596
585
|
/**
|
|
597
586
|
* Attempts to load any resolved external 3D models.
|
|
598
587
|
* @returns {Promise<void>}
|
|
@@ -605,7 +594,7 @@ export class PcbScene3dRuntime {
|
|
|
605
594
|
|
|
606
595
|
const diagnostics = await PcbScene3dExternalModels.loadIntoScene({
|
|
607
596
|
three: this.#three,
|
|
608
|
-
sceneDescription: this.#
|
|
597
|
+
sceneDescription: this.#placementSceneDescription,
|
|
609
598
|
externalModelsGroup,
|
|
610
599
|
modelViewScale: PcbScene3dRuntime.resolveViewScale(
|
|
611
600
|
this.#presetState.get(),
|
|
@@ -770,6 +759,7 @@ export class PcbScene3dRuntime {
|
|
|
770
759
|
})
|
|
771
760
|
PcbScene3dComponentVisibility.apply({
|
|
772
761
|
selectionRoots: this.#selectionRoots,
|
|
762
|
+
selectedDesignator: this.#selectedDesignator,
|
|
773
763
|
hiddenDesignators: this.#hiddenComponentDesignators,
|
|
774
764
|
fallbackBodyRoots: this.#fallbackBodyRoots,
|
|
775
765
|
loadedExternalModelDesignators:
|
|
@@ -891,6 +881,7 @@ export class PcbScene3dRuntime {
|
|
|
891
881
|
|
|
892
882
|
const selectableRoots = [
|
|
893
883
|
this.#groups.get('external-models'),
|
|
884
|
+
this.#groups.get('static-bodies'),
|
|
894
885
|
this.#groups.get('fallback-bodies')
|
|
895
886
|
].filter((group) => group && group.visible !== false)
|
|
896
887
|
const intersections = this.#raycaster.intersectObjects(
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { PcbScene3dComponentAdjustment } from './PcbScene3dComponentAdjustment.mjs'
|
|
2
|
+
import { PcbScene3dMountRig } from './PcbScene3dMountRig.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds authored static component-body render roots.
|
|
6
|
+
*/
|
|
7
|
+
export class PcbScene3dStaticBodyFactory {
|
|
8
|
+
static #DEFAULT_COLOR = 0x808080
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Builds all renderable static body roots.
|
|
12
|
+
* @param {any} THREE Three.js namespace.
|
|
13
|
+
* @param {object[]} placements Static body placements.
|
|
14
|
+
* @returns {{ rootGroup: any, adjustmentGroup: any, placement: object }[]}
|
|
15
|
+
*/
|
|
16
|
+
static buildMany(THREE, placements) {
|
|
17
|
+
return (Array.isArray(placements) ? placements : [])
|
|
18
|
+
.map((placement) =>
|
|
19
|
+
PcbScene3dStaticBodyFactory.build(THREE, placement)
|
|
20
|
+
)
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Builds one authored static body root.
|
|
26
|
+
* @param {any} THREE Three.js namespace.
|
|
27
|
+
* @param {{ designator?: string, mountSide?: string, rotationDeg?: number, positionMil?: { x?: number, y?: number, z?: number }, geometry?: object, bodyColor?: object, bodyOpacity?: number }} placement Static body placement.
|
|
28
|
+
* @returns {{ rootGroup: any, adjustmentGroup: any, placement: object } | null}
|
|
29
|
+
*/
|
|
30
|
+
static build(THREE, placement) {
|
|
31
|
+
const geometry = PcbScene3dStaticBodyFactory.#buildGeometry(
|
|
32
|
+
THREE,
|
|
33
|
+
placement?.geometry
|
|
34
|
+
)
|
|
35
|
+
if (!geometry) {
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const material = new THREE.MeshStandardMaterial({
|
|
40
|
+
color: PcbScene3dStaticBodyFactory.#resolveColor(placement),
|
|
41
|
+
roughness: 0.7,
|
|
42
|
+
metalness: 0.08,
|
|
43
|
+
transparent:
|
|
44
|
+
PcbScene3dStaticBodyFactory.#resolveOpacity(placement) < 1,
|
|
45
|
+
opacity: PcbScene3dStaticBodyFactory.#resolveOpacity(placement)
|
|
46
|
+
})
|
|
47
|
+
const mesh = new THREE.Mesh(geometry, material)
|
|
48
|
+
const mountRig = PcbScene3dMountRig.create(THREE, placement)
|
|
49
|
+
const adjustmentGroup =
|
|
50
|
+
PcbScene3dStaticBodyFactory.#buildAdjustmentGroup(THREE, placement)
|
|
51
|
+
|
|
52
|
+
mountRig.rootGroup.userData.scene3dSelection = {
|
|
53
|
+
designator: String(placement?.designator || 'static-body'),
|
|
54
|
+
sourceType: 'static-body'
|
|
55
|
+
}
|
|
56
|
+
PcbScene3dStaticBodyFactory.#applyVariantMetadata(
|
|
57
|
+
mountRig.rootGroup,
|
|
58
|
+
placement
|
|
59
|
+
)
|
|
60
|
+
adjustmentGroup.add(mesh)
|
|
61
|
+
mountRig.faceGroup.add(adjustmentGroup)
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
rootGroup: mountRig.rootGroup,
|
|
65
|
+
adjustmentGroup,
|
|
66
|
+
placement
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Builds the geometry for one supported static body.
|
|
72
|
+
* @param {any} THREE Three.js namespace.
|
|
73
|
+
* @param {{ kind?: string, verticesMil?: { x?: number, y?: number }[], heightMil?: number, radiusMil?: number }} geometry Static body geometry.
|
|
74
|
+
* @returns {any | null}
|
|
75
|
+
*/
|
|
76
|
+
static #buildGeometry(THREE, geometry) {
|
|
77
|
+
const kind = String(geometry?.kind || '').toLowerCase()
|
|
78
|
+
if (kind === 'extruded-polygon') {
|
|
79
|
+
return PcbScene3dStaticBodyFactory.#buildExtrudedPolygon(
|
|
80
|
+
THREE,
|
|
81
|
+
geometry
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
if (kind === 'cylinder') {
|
|
85
|
+
return PcbScene3dStaticBodyFactory.#buildCylinder(THREE, geometry)
|
|
86
|
+
}
|
|
87
|
+
if (kind === 'cone') {
|
|
88
|
+
return PcbScene3dStaticBodyFactory.#buildCone(THREE, geometry)
|
|
89
|
+
}
|
|
90
|
+
if (kind === 'sphere') {
|
|
91
|
+
return PcbScene3dStaticBodyFactory.#buildSphere(THREE, geometry)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Builds an extruded polygon geometry centered on its authored Z midpoint.
|
|
99
|
+
* @param {any} THREE Three.js namespace.
|
|
100
|
+
* @param {{ verticesMil?: { x?: number, y?: number }[], heightMil?: number }} geometry Static body geometry.
|
|
101
|
+
* @returns {any | null}
|
|
102
|
+
*/
|
|
103
|
+
static #buildExtrudedPolygon(THREE, geometry) {
|
|
104
|
+
const vertices = Array.isArray(geometry?.verticesMil)
|
|
105
|
+
? geometry.verticesMil
|
|
106
|
+
: []
|
|
107
|
+
const heightMil = Number(geometry?.heightMil || 0)
|
|
108
|
+
if (vertices.length < 3 || !(heightMil > 0)) {
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const shape = new THREE.Shape()
|
|
113
|
+
shape.moveTo(Number(vertices[0].x || 0), Number(vertices[0].y || 0))
|
|
114
|
+
vertices.slice(1).forEach((vertex) => {
|
|
115
|
+
shape.lineTo(Number(vertex.x || 0), Number(vertex.y || 0))
|
|
116
|
+
})
|
|
117
|
+
shape.closePath()
|
|
118
|
+
|
|
119
|
+
const extrudedGeometry = new THREE.ExtrudeGeometry(shape, {
|
|
120
|
+
depth: heightMil,
|
|
121
|
+
bevelEnabled: false
|
|
122
|
+
})
|
|
123
|
+
extrudedGeometry.translate?.(0, 0, -heightMil / 2)
|
|
124
|
+
return extrudedGeometry
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Builds a cylinder body geometry.
|
|
129
|
+
* @param {any} THREE Three.js namespace.
|
|
130
|
+
* @param {{ radiusMil?: number, heightMil?: number }} geometry Static body geometry.
|
|
131
|
+
* @returns {any | null}
|
|
132
|
+
*/
|
|
133
|
+
static #buildCylinder(THREE, geometry) {
|
|
134
|
+
const radiusMil = Number(geometry?.radiusMil || 0)
|
|
135
|
+
const heightMil = Number(geometry?.heightMil || 0)
|
|
136
|
+
if (!(radiusMil > 0) || !(heightMil > 0)) {
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return new THREE.CylinderGeometry(radiusMil, radiusMil, heightMil, 32)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Builds a cone body geometry.
|
|
145
|
+
* @param {any} THREE Three.js namespace.
|
|
146
|
+
* @param {{ radiusMil?: number, heightMil?: number }} geometry Static body geometry.
|
|
147
|
+
* @returns {any | null}
|
|
148
|
+
*/
|
|
149
|
+
static #buildCone(THREE, geometry) {
|
|
150
|
+
const radiusMil = Number(geometry?.radiusMil || 0)
|
|
151
|
+
const heightMil = Number(geometry?.heightMil || 0)
|
|
152
|
+
if (!(radiusMil > 0) || !(heightMil > 0)) {
|
|
153
|
+
return null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (typeof THREE.ConeGeometry === 'function') {
|
|
157
|
+
return new THREE.ConeGeometry(radiusMil, heightMil, 32)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return new THREE.CylinderGeometry(0, radiusMil, heightMil, 32)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Builds a sphere body geometry.
|
|
165
|
+
* @param {any} THREE Three.js namespace.
|
|
166
|
+
* @param {{ radiusMil?: number }} geometry Static body geometry.
|
|
167
|
+
* @returns {any | null}
|
|
168
|
+
*/
|
|
169
|
+
static #buildSphere(THREE, geometry) {
|
|
170
|
+
const radiusMil = Number(geometry?.radiusMil || 0)
|
|
171
|
+
if (!(radiusMil > 0) || typeof THREE.SphereGeometry !== 'function') {
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return new THREE.SphereGeometry(radiusMil, 32, 16)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Builds the transform node used by live component adjustments.
|
|
180
|
+
* @param {any} THREE Three.js namespace.
|
|
181
|
+
* @param {{ designator?: string }} placement Static body placement.
|
|
182
|
+
* @returns {any}
|
|
183
|
+
*/
|
|
184
|
+
static #buildAdjustmentGroup(THREE, placement) {
|
|
185
|
+
const adjustmentGroup = new THREE.Group()
|
|
186
|
+
adjustmentGroup.userData.scene3dAdjustmentTarget = true
|
|
187
|
+
adjustmentGroup.userData.scene3dAdjustmentDesignator = String(
|
|
188
|
+
placement?.designator || 'static-body'
|
|
189
|
+
)
|
|
190
|
+
adjustmentGroup.userData.scene3dAdjustmentBaseline =
|
|
191
|
+
PcbScene3dComponentAdjustment.neutral()
|
|
192
|
+
return adjustmentGroup
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Copies authored variant grouping metadata onto the selectable root.
|
|
197
|
+
* @param {any} rootGroup Static body root group.
|
|
198
|
+
* @param {{ coLocatedVariantGroupKey?: string }} placement Static body placement.
|
|
199
|
+
* @returns {void}
|
|
200
|
+
*/
|
|
201
|
+
static #applyVariantMetadata(rootGroup, placement) {
|
|
202
|
+
const groupKey = String(
|
|
203
|
+
placement?.coLocatedVariantGroupKey || ''
|
|
204
|
+
).trim()
|
|
205
|
+
if (groupKey) {
|
|
206
|
+
rootGroup.userData.scene3dVariantGroupKey = groupKey
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Resolves static body display color.
|
|
212
|
+
* @param {{ bodyColor?: object, geometry?: object }} placement Static body placement.
|
|
213
|
+
* @returns {number}
|
|
214
|
+
*/
|
|
215
|
+
static #resolveColor(placement) {
|
|
216
|
+
const rawColor = Number(
|
|
217
|
+
placement?.bodyColor?.raw ??
|
|
218
|
+
placement?.geometry?.bodyColor?.raw ??
|
|
219
|
+
placement?.geometry?.color
|
|
220
|
+
)
|
|
221
|
+
return Number.isInteger(rawColor)
|
|
222
|
+
? rawColor
|
|
223
|
+
: PcbScene3dStaticBodyFactory.#DEFAULT_COLOR
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Resolves static body opacity.
|
|
228
|
+
* @param {{ bodyOpacity?: number, geometry?: object }} placement Static body placement.
|
|
229
|
+
* @returns {number}
|
|
230
|
+
*/
|
|
231
|
+
static #resolveOpacity(placement) {
|
|
232
|
+
const opacity = Number(
|
|
233
|
+
placement?.bodyOpacity ?? placement?.geometry?.bodyOpacity
|
|
234
|
+
)
|
|
235
|
+
if (!Number.isFinite(opacity)) {
|
|
236
|
+
return 1
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return Math.min(1, Math.max(0, opacity))
|
|
240
|
+
}
|
|
241
|
+
}
|