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,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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves which external-model placements can be centered after loading.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dExternalModelCenteringPolicy {
|
|
5
|
+
/**
|
|
6
|
+
* Checks whether a loaded model should be centered on its owner footprint.
|
|
7
|
+
* @param {object | null | undefined} placement External placement.
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
static shouldCenterOnOwner(placement) {
|
|
11
|
+
const source = String(placement?.projection?.source || '').toLowerCase()
|
|
12
|
+
if (source === 'pad-fallback') {
|
|
13
|
+
return true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
source === 'model-anchor-fallback' &&
|
|
18
|
+
Boolean(placement?.modelTransform?.ownerAnchorOffsetMil)
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { PcbScene3dTransparentMeshSplitter } from './PcbScene3dTransparentMeshSplitter.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Applies source-authored display opacity to loaded external model materials.
|
|
5
|
+
*/
|
|
6
|
+
export class PcbScene3dExternalModelOpacity {
|
|
7
|
+
/**
|
|
8
|
+
* Applies source-authored placement opacity to every material below a
|
|
9
|
+
* loaded external model.
|
|
10
|
+
* @param {any} THREE Three.js namespace.
|
|
11
|
+
* @param {{ bodyOpacity?: number | string } | null | undefined} placement Placement metadata.
|
|
12
|
+
* @param {any} placementGroup Rendered placement root.
|
|
13
|
+
* @returns {void}
|
|
14
|
+
*/
|
|
15
|
+
static apply(THREE, placement, placementGroup) {
|
|
16
|
+
const opacity = Number(placement?.bodyOpacity)
|
|
17
|
+
if (!Number.isFinite(opacity) || opacity < 0 || opacity >= 1) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const clampedOpacity = Math.max(0, Math.min(1, opacity))
|
|
22
|
+
PcbScene3dExternalModelOpacity.#visitMaterials(
|
|
23
|
+
placementGroup,
|
|
24
|
+
(material) => {
|
|
25
|
+
material.transparent = true
|
|
26
|
+
material.opacity = clampedOpacity
|
|
27
|
+
material.depthWrite = false
|
|
28
|
+
material.needsUpdate = true
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
PcbScene3dTransparentMeshSplitter.split(THREE, placementGroup)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Visits material instances below one object tree.
|
|
36
|
+
* @param {any} rootObject Root object.
|
|
37
|
+
* @param {(material: any) => void} visitor Material visitor.
|
|
38
|
+
* @returns {void}
|
|
39
|
+
*/
|
|
40
|
+
static #visitMaterials(rootObject, visitor) {
|
|
41
|
+
rootObject?.traverse?.((object) => {
|
|
42
|
+
const materials = Array.isArray(object?.material)
|
|
43
|
+
? object.material
|
|
44
|
+
: object?.material
|
|
45
|
+
? [object.material]
|
|
46
|
+
: []
|
|
47
|
+
|
|
48
|
+
materials.filter(Boolean).forEach((material) => visitor(material))
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PcbScene3dExternalModelCenteringPolicy } from './PcbScene3dExternalModelCenteringPolicy.mjs'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Repairs loaded external-model placements using normalized scene metadata.
|
|
3
5
|
*/
|
|
@@ -352,8 +354,9 @@ export class PcbScene3dExternalModelPlacementRepair {
|
|
|
352
354
|
if (
|
|
353
355
|
!component ||
|
|
354
356
|
!placementGroup?.position ||
|
|
355
|
-
|
|
356
|
-
|
|
357
|
+
!PcbScene3dExternalModelCenteringPolicy.shouldCenterOnOwner(
|
|
358
|
+
placement
|
|
359
|
+
) ||
|
|
357
360
|
!THREE?.Box3
|
|
358
361
|
) {
|
|
359
362
|
return
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves source-origin adjustment policy for external model placements.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dExternalModelSourceOriginPolicy {
|
|
5
|
+
/**
|
|
6
|
+
* Checks whether an explicit owner anchor should keep source-origin repair
|
|
7
|
+
* disabled. Translucent cover-sized bodies use corner-origin STEP data, so
|
|
8
|
+
* they still need the embedded source-origin correction.
|
|
9
|
+
* @param {{ bodyOpacity?: number | string, modelTransform?: { ownerAnchorOffsetMil?: object }, projection?: { boundsMil?: { width?: number, depth?: number } } }} placement Placement metadata.
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
static shouldSkipOwnerAnchoredAdjustment(placement) {
|
|
13
|
+
return (
|
|
14
|
+
Boolean(placement?.modelTransform?.ownerAnchorOffsetMil) &&
|
|
15
|
+
!PcbScene3dExternalModelSourceOriginPolicy.#isTransparentCoverSizedPlacement(
|
|
16
|
+
placement
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Checks whether a placement looks like an authored translucent cover body.
|
|
23
|
+
* @param {{ bodyOpacity?: number | string, projection?: { boundsMil?: { width?: number, depth?: number } } }} placement Placement metadata.
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
static #isTransparentCoverSizedPlacement(placement) {
|
|
27
|
+
const opacity = Number(placement?.bodyOpacity)
|
|
28
|
+
const bounds = placement?.projection?.boundsMil || {}
|
|
29
|
+
const width = Number(bounds.width)
|
|
30
|
+
const depth = Number(bounds.depth)
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
Number.isFinite(opacity) &&
|
|
34
|
+
opacity >= 0 &&
|
|
35
|
+
opacity < 1 &&
|
|
36
|
+
Number.isFinite(width) &&
|
|
37
|
+
Number.isFinite(depth) &&
|
|
38
|
+
Math.min(Math.abs(width), Math.abs(depth)) >= 200
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -2,8 +2,10 @@ import { PcbScene3dBoardAssemblyPresentation } from './PcbScene3dBoardAssemblyPr
|
|
|
2
2
|
import { PcbScene3dBoardAssemblyPlacement } from './PcbScene3dBoardAssemblyPlacement.mjs'
|
|
3
3
|
import { PcbScene3dBoardAssemblyTransform } from './PcbScene3dBoardAssemblyTransform.mjs'
|
|
4
4
|
import { PcbScene3dBufferAttributeFactory } from './PcbScene3dBufferAttributeFactory.mjs'
|
|
5
|
+
import { PcbScene3dExternalModelOpacity } from './PcbScene3dExternalModelOpacity.mjs'
|
|
5
6
|
import { PcbScene3dExternalModelPlacementRepair } from './PcbScene3dExternalModelPlacementRepair.mjs'
|
|
6
7
|
import { PcbScene3dExternalModelLoadOrder } from './PcbScene3dExternalModelLoadOrder.mjs'
|
|
8
|
+
import { PcbScene3dExternalModelSourceOriginPolicy } from './PcbScene3dExternalModelSourceOriginPolicy.mjs'
|
|
7
9
|
import { PcbScene3dModelBounds } from './PcbScene3dModelBounds.mjs'
|
|
8
10
|
import { PcbScene3dMountRig } from './PcbScene3dMountRig.mjs'
|
|
9
11
|
import { PcbScene3dStepLoader } from './PcbScene3dStepLoader.mjs'
|
|
@@ -54,7 +56,6 @@ export class PcbScene3dExternalModels {
|
|
|
54
56
|
if (!loadedGroup || options?.isDisposed?.()) {
|
|
55
57
|
continue
|
|
56
58
|
}
|
|
57
|
-
|
|
58
59
|
externalModelsGroup.add(loadedGroup)
|
|
59
60
|
PcbScene3dExternalModelPlacementRepair.apply(
|
|
60
61
|
options.three,
|
|
@@ -65,6 +66,11 @@ export class PcbScene3dExternalModels {
|
|
|
65
66
|
PcbScene3dExternalModelPlacementRepair.isolatePlacementMaterials(
|
|
66
67
|
loadedGroup
|
|
67
68
|
)
|
|
69
|
+
PcbScene3dExternalModelOpacity.apply(
|
|
70
|
+
options.three,
|
|
71
|
+
placement,
|
|
72
|
+
loadedGroup
|
|
73
|
+
)
|
|
68
74
|
options?.onPlacementGroup?.(placement, loadedGroup)
|
|
69
75
|
processedPlacements += 1
|
|
70
76
|
if (processedPlacements % 12 === 0) {
|
|
@@ -293,6 +299,12 @@ export class PcbScene3dExternalModels {
|
|
|
293
299
|
designator,
|
|
294
300
|
sourceType: 'external-model'
|
|
295
301
|
}
|
|
302
|
+
const variantGroupKey = String(
|
|
303
|
+
placement?.coLocatedVariantGroupKey || ''
|
|
304
|
+
).trim()
|
|
305
|
+
if (variantGroupKey) {
|
|
306
|
+
wrapperGroup.userData.scene3dVariantGroupKey = variantGroupKey
|
|
307
|
+
}
|
|
296
308
|
adjustmentGroup.userData.scene3dAdjustmentTarget = true
|
|
297
309
|
adjustmentGroup.userData.scene3dAdjustmentDesignator = designator
|
|
298
310
|
adjustmentGroup.userData.scene3dAdjustmentBaseline =
|
|
@@ -497,6 +509,9 @@ export class PcbScene3dExternalModels {
|
|
|
497
509
|
if (
|
|
498
510
|
String(placement?.externalModel?.origin || '').toLowerCase() !==
|
|
499
511
|
'embedded' ||
|
|
512
|
+
PcbScene3dExternalModelSourceOriginPolicy.shouldSkipOwnerAnchoredAdjustment(
|
|
513
|
+
placement
|
|
514
|
+
) ||
|
|
500
515
|
PcbScene3dExternalModels.#normalizeAngle(modelRotation?.x) !==
|
|
501
516
|
270 ||
|
|
502
517
|
!Number.isFinite(centerX) ||
|
|
@@ -917,15 +932,12 @@ export class PcbScene3dExternalModels {
|
|
|
917
932
|
const triangleCount = Math.floor(Number(indices?.length || 0) / 3)
|
|
918
933
|
let triangleIndex = 0
|
|
919
934
|
let faceColorIndex = 0
|
|
920
|
-
|
|
921
935
|
while (triangleIndex < triangleCount) {
|
|
922
936
|
const firstIndex = triangleIndex
|
|
923
937
|
let lastIndex = triangleCount
|
|
924
938
|
let materialIndex = 0
|
|
925
|
-
|
|
926
939
|
if (faceColorIndex < faceColors.length) {
|
|
927
940
|
const currentFaceColor = faceColors[faceColorIndex]
|
|
928
|
-
|
|
929
941
|
if (triangleIndex < currentFaceColor.first) {
|
|
930
942
|
lastIndex = currentFaceColor.first
|
|
931
943
|
} else {
|
|
@@ -937,7 +949,6 @@ export class PcbScene3dExternalModels {
|
|
|
937
949
|
faceColorIndex += 1
|
|
938
950
|
}
|
|
939
951
|
}
|
|
940
|
-
|
|
941
952
|
geometry.addGroup(
|
|
942
953
|
firstIndex * 3,
|
|
943
954
|
Math.max(lastIndex - firstIndex, 0) * 3,
|
|
@@ -957,7 +968,6 @@ export class PcbScene3dExternalModels {
|
|
|
957
968
|
const first = Number(faceColor?.first)
|
|
958
969
|
const last = Number(faceColor?.last)
|
|
959
970
|
const triangleCount = Math.floor(Number(indices?.length || 0) / 3)
|
|
960
|
-
|
|
961
971
|
return (
|
|
962
972
|
Number.isInteger(first) &&
|
|
963
973
|
Number.isInteger(last) &&
|
|
@@ -977,7 +987,6 @@ export class PcbScene3dExternalModels {
|
|
|
977
987
|
if (!Array.isArray(color) || color.length < 3) {
|
|
978
988
|
return 0xc8c8c8
|
|
979
989
|
}
|
|
980
|
-
|
|
981
990
|
return new THREE.Color(
|
|
982
991
|
Number(color[0] || 0),
|
|
983
992
|
Number(color[1] || 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
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { PcbScene3dGeometryZCompressor } from './PcbScene3dGeometryZCompressor.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds side-specific copper relief groups that sit below solder mask.
|
|
5
|
+
*/
|
|
6
|
+
export class PcbScene3dMaskCoveredCopperSideGroupBuilder {
|
|
7
|
+
/**
|
|
8
|
+
* Builds one side group from prepared mask-covered copper meshes.
|
|
9
|
+
* @param {any} THREE Three.js namespace.
|
|
10
|
+
* @param {{ trackMesh?: any | null, arcMesh?: any | null, fillMesh?: any | null, z?: number, mirrorY?: boolean }} options
|
|
11
|
+
* @returns {any}
|
|
12
|
+
*/
|
|
13
|
+
static build(
|
|
14
|
+
THREE,
|
|
15
|
+
{
|
|
16
|
+
trackMesh = null,
|
|
17
|
+
arcMesh = null,
|
|
18
|
+
fillMesh = null,
|
|
19
|
+
z = 0,
|
|
20
|
+
mirrorY = false
|
|
21
|
+
} = {}
|
|
22
|
+
) {
|
|
23
|
+
const group = new THREE.Group()
|
|
24
|
+
|
|
25
|
+
PcbScene3dMaskCoveredCopperSideGroupBuilder.#addCompressedMesh(
|
|
26
|
+
group,
|
|
27
|
+
trackMesh,
|
|
28
|
+
'mask-covered-copper-tracks',
|
|
29
|
+
z
|
|
30
|
+
)
|
|
31
|
+
PcbScene3dMaskCoveredCopperSideGroupBuilder.#addCompressedMesh(
|
|
32
|
+
group,
|
|
33
|
+
arcMesh,
|
|
34
|
+
'mask-covered-copper-arcs',
|
|
35
|
+
z
|
|
36
|
+
)
|
|
37
|
+
PcbScene3dMaskCoveredCopperSideGroupBuilder.#addCompressedMesh(
|
|
38
|
+
group,
|
|
39
|
+
fillMesh,
|
|
40
|
+
'mask-covered-copper-fills',
|
|
41
|
+
z
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if (mirrorY && group.children.length) {
|
|
45
|
+
group.rotation.x = Math.PI
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return group
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Compresses one mesh into mask relief space before adding it to a group.
|
|
53
|
+
* @param {any} group Parent group.
|
|
54
|
+
* @param {any | null} mesh Mesh to add.
|
|
55
|
+
* @param {string} name Scene object name.
|
|
56
|
+
* @param {number} z Source center Z.
|
|
57
|
+
* @returns {void}
|
|
58
|
+
*/
|
|
59
|
+
static #addCompressedMesh(group, mesh, name, z) {
|
|
60
|
+
if (!mesh) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
PcbScene3dGeometryZCompressor.compressMaskCoveredCopperMesh(mesh, z)
|
|
65
|
+
mesh.name = name
|
|
66
|
+
group.add(mesh)
|
|
67
|
+
}
|
|
68
|
+
}
|