pcb-scene3d-viewer 1.1.20 → 1.1.21
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/PcbScene3dCameraRig.mjs +11 -5
- package/src/PcbScene3dComponentVisibility.mjs +161 -0
- package/src/PcbScene3dController.mjs +29 -4
- package/src/PcbScene3dExternalModelPlacementRepair.mjs +1 -1
- package/src/PcbScene3dInteractionHints.mjs +14 -6
- package/src/PcbScene3dRenderScheduler.mjs +55 -0
- package/src/PcbScene3dRuntime.mjs +56 -4
- package/src/PcbScene3dSelectionInspectorRenderer.mjs +64 -3
- package/src/PcbScene3dSelectionVisibilityBinder.mjs +119 -0
- package/src/PcbScene3dText.mjs +4 -2
- package/src/scene3d.mjs +3 -0
- package/src/styles/scene3d.css +48 -0
package/package.json
CHANGED
|
@@ -267,12 +267,18 @@ export class PcbScene3dCameraRig {
|
|
|
267
267
|
*/
|
|
268
268
|
static #applyOrthographicProjection(camera, state) {
|
|
269
269
|
const zoom = Math.max(Number(camera.zoom || 1), 0.0001)
|
|
270
|
-
const height = Math.max(Number(state.orthographicHeight || 0)
|
|
270
|
+
const height = Math.max(Number(state.orthographicHeight || 0), 1)
|
|
271
271
|
const width = height * Math.max(Number(camera.aspect || 1), 0.0001)
|
|
272
272
|
const left = -width / 2
|
|
273
273
|
const right = width / 2
|
|
274
274
|
const top = height / 2
|
|
275
275
|
const bottom = -height / 2
|
|
276
|
+
// Keep camera bounds unzoomed for OrbitControls pan math; apply zoom
|
|
277
|
+
// only to the projection matrix so dragging stays cursor-locked.
|
|
278
|
+
const projectedLeft = left / zoom
|
|
279
|
+
const projectedRight = right / zoom
|
|
280
|
+
const projectedTop = top / zoom
|
|
281
|
+
const projectedBottom = bottom / zoom
|
|
276
282
|
|
|
277
283
|
camera.left = left
|
|
278
284
|
camera.right = right
|
|
@@ -281,10 +287,10 @@ export class PcbScene3dCameraRig {
|
|
|
281
287
|
camera.isPerspectiveCamera = false
|
|
282
288
|
camera.isOrthographicCamera = true
|
|
283
289
|
camera.projectionMatrix?.makeOrthographic?.(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
290
|
+
projectedLeft,
|
|
291
|
+
projectedRight,
|
|
292
|
+
projectedTop,
|
|
293
|
+
projectedBottom,
|
|
288
294
|
camera.near,
|
|
289
295
|
camera.far
|
|
290
296
|
)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies per-component hidden state on top of existing 3D visibility toggles.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dComponentVisibility {
|
|
5
|
+
/**
|
|
6
|
+
* Stores one component hidden state.
|
|
7
|
+
* @param {Set<string>} hiddenDesignators Hidden designator set.
|
|
8
|
+
* @param {string} designator Component designator.
|
|
9
|
+
* @param {boolean} hidden Whether the component should be hidden.
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
static setHidden(hiddenDesignators, designator, hidden) {
|
|
13
|
+
if (!(hiddenDesignators instanceof Set)) {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const normalizedDesignator =
|
|
18
|
+
PcbScene3dComponentVisibility.#normalizeDesignator(designator)
|
|
19
|
+
if (!normalizedDesignator) {
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const wasHidden = hiddenDesignators.has(normalizedDesignator)
|
|
24
|
+
if (hidden) {
|
|
25
|
+
hiddenDesignators.add(normalizedDesignator)
|
|
26
|
+
} else {
|
|
27
|
+
hiddenDesignators.delete(normalizedDesignator)
|
|
28
|
+
}
|
|
29
|
+
return wasHidden !== hiddenDesignators.has(normalizedDesignator)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Checks whether one component is hidden.
|
|
34
|
+
* @param {Set<string>} hiddenDesignators Hidden designator set.
|
|
35
|
+
* @param {string} designator Component designator.
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
static isHidden(hiddenDesignators, designator) {
|
|
39
|
+
return (
|
|
40
|
+
hiddenDesignators instanceof Set &&
|
|
41
|
+
hiddenDesignators.has(
|
|
42
|
+
PcbScene3dComponentVisibility.#normalizeDesignator(designator)
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 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.
|
|
50
|
+
* @returns {void}
|
|
51
|
+
*/
|
|
52
|
+
static apply(state) {
|
|
53
|
+
const selectionRoots = state?.selectionRoots
|
|
54
|
+
if (!(selectionRoots instanceof Map)) {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
selectionRoots.forEach((roots, designator) => {
|
|
59
|
+
roots?.forEach?.((rootObject) => {
|
|
60
|
+
if (!rootObject) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
rootObject.visible =
|
|
65
|
+
!PcbScene3dComponentVisibility.isHidden(
|
|
66
|
+
state?.hiddenDesignators,
|
|
67
|
+
designator
|
|
68
|
+
) &&
|
|
69
|
+
PcbScene3dComponentVisibility.#resolveRootVisible(
|
|
70
|
+
state,
|
|
71
|
+
designator,
|
|
72
|
+
rootObject
|
|
73
|
+
)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolves root-level visibility before selected-component hiding.
|
|
80
|
+
* @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.
|
|
81
|
+
* @param {string} designator Component designator.
|
|
82
|
+
* @param {any} rootObject Render root.
|
|
83
|
+
* @returns {boolean}
|
|
84
|
+
*/
|
|
85
|
+
static #resolveRootVisible(state, designator, rootObject) {
|
|
86
|
+
if (
|
|
87
|
+
PcbScene3dComponentVisibility.#isFallbackRoot(
|
|
88
|
+
state?.fallbackBodyRoots,
|
|
89
|
+
designator,
|
|
90
|
+
rootObject
|
|
91
|
+
)
|
|
92
|
+
) {
|
|
93
|
+
return PcbScene3dComponentVisibility.#resolveFallbackVisible(
|
|
94
|
+
state,
|
|
95
|
+
designator
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (state?.modelSearchExternalModelRoots?.has?.(rootObject)) {
|
|
100
|
+
return (
|
|
101
|
+
Boolean(state?.toggles?.['external-models']) &&
|
|
102
|
+
state?.toggles?.['model-search-models'] !== false
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return true
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolves fallback root visibility from current detail toggles.
|
|
111
|
+
* @param {{ loadedExternalModelDesignators?: Set<string>, toggles?: { 'external-models'?: boolean, 'fallback-bodies'?: boolean }, hasLoadedBoardAssemblyModel?: boolean }} state Visibility state.
|
|
112
|
+
* @param {string} designator Component designator.
|
|
113
|
+
* @returns {boolean}
|
|
114
|
+
*/
|
|
115
|
+
static #resolveFallbackVisible(state, designator) {
|
|
116
|
+
const boardAssemblyActive =
|
|
117
|
+
Boolean(state?.hasLoadedBoardAssemblyModel) &&
|
|
118
|
+
Boolean(state?.toggles?.['external-models'])
|
|
119
|
+
const showFallbackBodies =
|
|
120
|
+
Boolean(state?.toggles?.['fallback-bodies']) && !boardAssemblyActive
|
|
121
|
+
const hideForLoadedExternal =
|
|
122
|
+
Boolean(state?.toggles?.['external-models']) &&
|
|
123
|
+
state?.loadedExternalModelDesignators?.has?.(
|
|
124
|
+
PcbScene3dComponentVisibility.#normalizeDesignator(designator)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return showFallbackBodies && !hideForLoadedExternal
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Checks whether a root belongs to fallback-body tracking.
|
|
132
|
+
* @param {Map<string, Set<any>> | undefined} fallbackBodyRoots Fallback roots.
|
|
133
|
+
* @param {string} designator Component designator.
|
|
134
|
+
* @param {any} rootObject Render root.
|
|
135
|
+
* @returns {boolean}
|
|
136
|
+
*/
|
|
137
|
+
static #isFallbackRoot(fallbackBodyRoots, designator, rootObject) {
|
|
138
|
+
if (!(fallbackBodyRoots instanceof Map)) {
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return Boolean(
|
|
143
|
+
fallbackBodyRoots
|
|
144
|
+
.get(
|
|
145
|
+
PcbScene3dComponentVisibility.#normalizeDesignator(
|
|
146
|
+
designator
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
?.has?.(rootObject)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Normalizes component designator keys.
|
|
155
|
+
* @param {string} designator Raw designator.
|
|
156
|
+
* @returns {string}
|
|
157
|
+
*/
|
|
158
|
+
static #normalizeDesignator(designator) {
|
|
159
|
+
return String(designator || '').trim()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -4,6 +4,7 @@ import { PcbScene3dCircuitJsonAdapter } from './PcbScene3dCircuitJsonAdapter.mjs
|
|
|
4
4
|
import { PcbScene3dInteractionHints } from './PcbScene3dInteractionHints.mjs'
|
|
5
5
|
import { PcbScene3dRuntime } from './PcbScene3dRuntime.mjs'
|
|
6
6
|
import { PcbScene3dSelectionInspectorRenderer } from './PcbScene3dSelectionInspectorRenderer.mjs'
|
|
7
|
+
import { PcbScene3dSelectionVisibilityBinder } from './PcbScene3dSelectionVisibilityBinder.mjs'
|
|
7
8
|
import { PcbScene3dText } from './PcbScene3dText.mjs'
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -31,6 +32,8 @@ export class PcbScene3dController {
|
|
|
31
32
|
/** @type {PcbScene3dAdjustmentControlBinder | null} */
|
|
32
33
|
#adjustmentControls
|
|
33
34
|
|
|
35
|
+
/** @type {PcbScene3dSelectionVisibilityBinder | null} */
|
|
36
|
+
#visibilityControls
|
|
34
37
|
/** @type {Array<{ node: EventTarget, type: string, listener: (event: any) => void }>} */
|
|
35
38
|
#listeners
|
|
36
39
|
|
|
@@ -43,7 +46,7 @@ export class PcbScene3dController {
|
|
|
43
46
|
/** @type {string} */
|
|
44
47
|
#selectedComponentKey
|
|
45
48
|
|
|
46
|
-
/** @type {
|
|
49
|
+
/** @type {any | null} */
|
|
47
50
|
#runtime
|
|
48
51
|
|
|
49
52
|
/** @type {any | null} */
|
|
@@ -151,19 +154,36 @@ export class PcbScene3dController {
|
|
|
151
154
|
suppressed ? '' : this.#selectedComponentKey
|
|
152
155
|
)
|
|
153
156
|
},
|
|
154
|
-
refreshSelection: (designator) => {
|
|
157
|
+
refreshSelection: (designator, sourceType) => {
|
|
155
158
|
this.#setSelection({
|
|
156
159
|
designator,
|
|
157
|
-
sourceType:
|
|
160
|
+
sourceType:
|
|
161
|
+
sourceType ||
|
|
162
|
+
this.#resolveSelectionSourceType(designator)
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
this.#visibilityControls = new PcbScene3dSelectionVisibilityBinder({
|
|
167
|
+
resolveDesignator: () => this.#selectedComponentKey,
|
|
168
|
+
resolveHidden: (designator) =>
|
|
169
|
+
Boolean(this.#runtime?.isComponentHidden?.(designator)),
|
|
170
|
+
setHidden: (designator, hidden) => {
|
|
171
|
+
this.#runtime?.setComponentHidden?.(designator, hidden)
|
|
172
|
+
},
|
|
173
|
+
refreshSelection: (designator, source) => {
|
|
174
|
+
this.#setSelection({
|
|
175
|
+
designator,
|
|
176
|
+
sourceType:
|
|
177
|
+
source || this.#resolveSelectionSourceType(designator)
|
|
158
178
|
})
|
|
159
179
|
}
|
|
160
180
|
})
|
|
161
|
-
|
|
162
181
|
this.#bindPresets()
|
|
163
182
|
this.#setActivePresetButton('isometric')
|
|
164
183
|
this.#bindToggles()
|
|
165
184
|
this.#bindExportAction()
|
|
166
185
|
this.#adjustmentControls.bindSelectionNode(this.#selectionNode)
|
|
186
|
+
this.#visibilityControls.bindSelectionNode(this.#selectionNode)
|
|
167
187
|
this.#setSelection(null)
|
|
168
188
|
this.#setLoadingVisible(true)
|
|
169
189
|
const circuitJsonModel = PcbScene3dController.#resolveCircuitJsonModel(
|
|
@@ -287,6 +307,8 @@ export class PcbScene3dController {
|
|
|
287
307
|
this.#scenePrepClient = null
|
|
288
308
|
this.#adjustmentControls?.dispose()
|
|
289
309
|
this.#adjustmentControls = null
|
|
310
|
+
this.#visibilityControls?.dispose()
|
|
311
|
+
this.#visibilityControls = null
|
|
290
312
|
this.#runtime?.dispose?.()
|
|
291
313
|
this.#runtime = null
|
|
292
314
|
this.#sceneDescription = null
|
|
@@ -739,6 +761,9 @@ export class PcbScene3dController {
|
|
|
739
761
|
this.#selectionNode.innerHTML =
|
|
740
762
|
PcbScene3dSelectionInspectorRenderer.renderSelected({
|
|
741
763
|
designator,
|
|
764
|
+
hidden: Boolean(
|
|
765
|
+
this.#runtime?.isComponentHidden?.(designator)
|
|
766
|
+
),
|
|
742
767
|
selection,
|
|
743
768
|
selectionEntry,
|
|
744
769
|
adjustment,
|
|
@@ -13,7 +13,7 @@ export class PcbScene3dExternalModelPlacementRepair {
|
|
|
13
13
|
'#333333',
|
|
14
14
|
'#3f3f3f'
|
|
15
15
|
])
|
|
16
|
-
static #MIN_CENTER_ERROR_MIL =
|
|
16
|
+
static #MIN_CENTER_ERROR_MIL = 1
|
|
17
17
|
static #MIN_TERMINAL_SIZE_MIL = 1
|
|
18
18
|
static #SOURCE_ORIGIN_SHIFT_EPSILON_MIL = 0.01
|
|
19
19
|
static #SOURCE_Z_DEPTH_MIN_RATIO = 1.5
|
|
@@ -6,24 +6,32 @@ export class PcbScene3dInteractionHints {
|
|
|
6
6
|
* Applies explicit OrbitControls input mappings for desktop and touch.
|
|
7
7
|
* @param {{ mouseButtons?: any, touches?: any }} controls
|
|
8
8
|
* @param {{ MOUSE?: any, TOUCH?: any }} THREE
|
|
9
|
+
* @param {string} [preset] Active camera preset.
|
|
9
10
|
* @returns {void}
|
|
10
11
|
*/
|
|
11
|
-
static configureControls(controls, THREE) {
|
|
12
|
+
static configureControls(controls, THREE, preset = 'isometric') {
|
|
12
13
|
if (!controls) {
|
|
13
14
|
return
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
const inspectionPreset =
|
|
18
|
+
String(preset || '').toLowerCase() === 'top' ||
|
|
19
|
+
String(preset || '').toLowerCase() === 'bottom'
|
|
20
|
+
|
|
16
21
|
if (THREE?.MOUSE) {
|
|
17
22
|
controls.mouseButtons = {
|
|
18
|
-
LEFT: THREE.MOUSE.ROTATE,
|
|
23
|
+
LEFT: inspectionPreset ? THREE.MOUSE.PAN : THREE.MOUSE.ROTATE,
|
|
19
24
|
MIDDLE: THREE.MOUSE.DOLLY,
|
|
20
|
-
RIGHT: THREE.MOUSE.PAN
|
|
25
|
+
RIGHT: inspectionPreset ? THREE.MOUSE.ROTATE : THREE.MOUSE.PAN
|
|
21
26
|
}
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
if (THREE?.TOUCH) {
|
|
25
30
|
controls.touches = {
|
|
26
|
-
ONE:
|
|
31
|
+
ONE:
|
|
32
|
+
inspectionPreset && THREE.TOUCH.PAN
|
|
33
|
+
? THREE.TOUCH.PAN
|
|
34
|
+
: THREE.TOUCH.ROTATE,
|
|
27
35
|
TWO: THREE.TOUCH.DOLLY_PAN
|
|
28
36
|
}
|
|
29
37
|
}
|
|
@@ -44,13 +52,13 @@ export class PcbScene3dInteractionHints {
|
|
|
44
52
|
const value = translate('scene3d.touchHint')
|
|
45
53
|
if (value && value !== 'scene3d.touchHint') return value
|
|
46
54
|
}
|
|
47
|
-
return '
|
|
55
|
+
return 'One-finger drag pans in Top/Bottom and orbits in Isometric. Pinch to zoom.'
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
if (typeof translate === 'function') {
|
|
51
59
|
const value = translate('scene3d.pointerHint')
|
|
52
60
|
if (value && value !== 'scene3d.pointerHint') return value
|
|
53
61
|
}
|
|
54
|
-
return 'Drag
|
|
62
|
+
return 'Drag pans in Top/Bottom and orbits in Isometric; right-drag uses the alternate action. Use the wheel to zoom.'
|
|
55
63
|
}
|
|
56
64
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coalesces high-frequency scene renders into animation frames.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dRenderScheduler {
|
|
5
|
+
/** @type {() => void} */
|
|
6
|
+
#render
|
|
7
|
+
|
|
8
|
+
/** @type {number | null} */
|
|
9
|
+
#pendingFrame
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {() => void} render Render callback.
|
|
13
|
+
*/
|
|
14
|
+
constructor(render) {
|
|
15
|
+
this.#render = render
|
|
16
|
+
this.#pendingFrame = null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Queues a render for the next animation frame.
|
|
21
|
+
* @returns {void}
|
|
22
|
+
*/
|
|
23
|
+
schedule() {
|
|
24
|
+
if (this.#pendingFrame !== null) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const requestFrame = globalThis.window?.requestAnimationFrame
|
|
29
|
+
if (typeof requestFrame !== 'function') {
|
|
30
|
+
this.#render()
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.#pendingFrame = requestFrame(() => {
|
|
35
|
+
this.#pendingFrame = null
|
|
36
|
+
this.#render()
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Cancels one pending animation-frame render.
|
|
42
|
+
* @returns {void}
|
|
43
|
+
*/
|
|
44
|
+
cancel() {
|
|
45
|
+
if (this.#pendingFrame === null) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const cancelFrame = globalThis.window?.cancelAnimationFrame
|
|
50
|
+
if (typeof cancelFrame === 'function') {
|
|
51
|
+
cancelFrame(this.#pendingFrame)
|
|
52
|
+
}
|
|
53
|
+
this.#pendingFrame = null
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -2,6 +2,7 @@ import { PcbScene3dBoardSolderMaskFactory } from './PcbScene3dBoardSolderMaskFac
|
|
|
2
2
|
import { PcbScene3dBodyColor } from './PcbScene3dBodyColor.mjs'
|
|
3
3
|
import { PcbScene3dCameraRig } from './PcbScene3dCameraRig.mjs'
|
|
4
4
|
import { PcbScene3dCircuitJsonAdapter } from './PcbScene3dCircuitJsonAdapter.mjs'
|
|
5
|
+
import { PcbScene3dComponentVisibility } from './PcbScene3dComponentVisibility.mjs'
|
|
5
6
|
import { PcbScene3dComponentAdjustment } from './PcbScene3dComponentAdjustment.mjs'
|
|
6
7
|
import { PcbScene3dComponentAdjustmentRegistry } from './PcbScene3dComponentAdjustmentRegistry.mjs'
|
|
7
8
|
import { PcbScene3dCopperDetailGroupBuilder } from './PcbScene3dCopperDetailGroupBuilder.mjs'
|
|
@@ -15,6 +16,7 @@ import { PcbScene3dMountRig } from './PcbScene3dMountRig.mjs'
|
|
|
15
16
|
import { PcbScene3dModelSearchPlacement } from './PcbScene3dModelSearchPlacement.mjs'
|
|
16
17
|
import { PcbScene3dPresetState } from './PcbScene3dPresetState.mjs'
|
|
17
18
|
import { PcbScene3dRenderGroupVisibility } from './PcbScene3dRenderGroupVisibility.mjs'
|
|
19
|
+
import { PcbScene3dRenderScheduler } from './PcbScene3dRenderScheduler.mjs'
|
|
18
20
|
import { PcbScene3dRuntimeBoardMeshes } from './PcbScene3dRuntimeBoardMeshes.mjs'
|
|
19
21
|
import { PcbScene3dSilkscreenChunkedFactory } from './PcbScene3dSilkscreenChunkedFactory.mjs'
|
|
20
22
|
import { PcbScene3dTrueTypeTextFactory } from './PcbScene3dTrueTypeTextFactory.mjs'
|
|
@@ -52,6 +54,7 @@ export class PcbScene3dRuntime {
|
|
|
52
54
|
#pointer
|
|
53
55
|
#pointerDownPosition
|
|
54
56
|
#selectionRoots
|
|
57
|
+
#hiddenComponentDesignators
|
|
55
58
|
#componentAdjustmentRegistry
|
|
56
59
|
#fallbackBodyRoots
|
|
57
60
|
#loadedExternalModelDesignators
|
|
@@ -64,6 +67,7 @@ export class PcbScene3dRuntime {
|
|
|
64
67
|
#readyPromise
|
|
65
68
|
#resolveReadyPromise
|
|
66
69
|
#hasSettledReady
|
|
70
|
+
#renderScheduler
|
|
67
71
|
/**
|
|
68
72
|
* @param {HTMLElement} viewportNode
|
|
69
73
|
* @param {any} sceneDescription Scene description or CircuitJSON model.
|
|
@@ -96,6 +100,7 @@ export class PcbScene3dRuntime {
|
|
|
96
100
|
this.#pointer = null
|
|
97
101
|
this.#pointerDownPosition = null
|
|
98
102
|
this.#selectionRoots = new Map()
|
|
103
|
+
this.#hiddenComponentDesignators = new Set()
|
|
99
104
|
this.#componentAdjustmentRegistry =
|
|
100
105
|
new PcbScene3dComponentAdjustmentRegistry(() => this.#three)
|
|
101
106
|
this.#fallbackBodyRoots = new Map()
|
|
@@ -108,6 +113,11 @@ export class PcbScene3dRuntime {
|
|
|
108
113
|
this.#presetState = new PcbScene3dPresetState()
|
|
109
114
|
this.#isDisposed = false
|
|
110
115
|
this.#hasSettledReady = false
|
|
116
|
+
this.#renderScheduler = new PcbScene3dRenderScheduler(() => {
|
|
117
|
+
if (!this.#isDisposed) {
|
|
118
|
+
this.#render()
|
|
119
|
+
}
|
|
120
|
+
})
|
|
111
121
|
this.#resolveReadyPromise = null
|
|
112
122
|
this.#readyPromise = new Promise((resolve) => {
|
|
113
123
|
this.#resolveReadyPromise = resolve
|
|
@@ -124,10 +134,14 @@ export class PcbScene3dRuntime {
|
|
|
124
134
|
setPreset(preset) {
|
|
125
135
|
const normalizedPreset = this.#presetState.set(preset)
|
|
126
136
|
this.#applyViewScale(normalizedPreset)
|
|
137
|
+
PcbScene3dInteractionHints.configureControls(
|
|
138
|
+
this.#controls,
|
|
139
|
+
this.#three,
|
|
140
|
+
normalizedPreset
|
|
141
|
+
)
|
|
127
142
|
if (!this.#camera) {
|
|
128
143
|
return
|
|
129
144
|
}
|
|
130
|
-
|
|
131
145
|
PcbScene3dCameraRig.applyPreset(
|
|
132
146
|
this.#camera,
|
|
133
147
|
this.#controls,
|
|
@@ -141,7 +155,6 @@ export class PcbScene3dRuntime {
|
|
|
141
155
|
if (!(toggleName in this.#toggles)) {
|
|
142
156
|
return
|
|
143
157
|
}
|
|
144
|
-
|
|
145
158
|
this.#toggles[toggleName] = enabled
|
|
146
159
|
this.#applyToggleVisibility()
|
|
147
160
|
this.#render()
|
|
@@ -150,6 +163,27 @@ export class PcbScene3dRuntime {
|
|
|
150
163
|
setSelectedDesignator(designator) {
|
|
151
164
|
this.#setSelectedDesignator(designator)
|
|
152
165
|
}
|
|
166
|
+
/** @param {string} designator @param {boolean} hidden */
|
|
167
|
+
setComponentHidden(designator, hidden) {
|
|
168
|
+
if (
|
|
169
|
+
!PcbScene3dComponentVisibility.setHidden(
|
|
170
|
+
this.#hiddenComponentDesignators,
|
|
171
|
+
designator,
|
|
172
|
+
hidden
|
|
173
|
+
)
|
|
174
|
+
) {
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
this.#applyToggleVisibility()
|
|
178
|
+
this.#render()
|
|
179
|
+
}
|
|
180
|
+
/** @param {string} designator @returns {boolean} */
|
|
181
|
+
isComponentHidden(designator) {
|
|
182
|
+
return PcbScene3dComponentVisibility.isHidden(
|
|
183
|
+
this.#hiddenComponentDesignators,
|
|
184
|
+
designator
|
|
185
|
+
)
|
|
186
|
+
}
|
|
153
187
|
/**
|
|
154
188
|
* Applies a live model-local adjustment to one component.
|
|
155
189
|
* @param {string} designator Component designator.
|
|
@@ -172,6 +206,7 @@ export class PcbScene3dRuntime {
|
|
|
172
206
|
/** @returns {void} */
|
|
173
207
|
dispose() {
|
|
174
208
|
this.#isDisposed = true
|
|
209
|
+
this.#renderScheduler.cancel()
|
|
175
210
|
this.#listeners.forEach(({ node, type, listener }) => {
|
|
176
211
|
node.removeEventListener?.(type, listener)
|
|
177
212
|
})
|
|
@@ -196,6 +231,7 @@ export class PcbScene3dRuntime {
|
|
|
196
231
|
this.#pointer = null
|
|
197
232
|
this.#pointerDownPosition = null
|
|
198
233
|
this.#selectionRoots.clear()
|
|
234
|
+
this.#hiddenComponentDesignators.clear()
|
|
199
235
|
this.#componentAdjustmentRegistry.clear()
|
|
200
236
|
this.#fallbackBodyRoots.clear()
|
|
201
237
|
this.#loadedExternalModelDesignators.clear()
|
|
@@ -636,7 +672,11 @@ export class PcbScene3dRuntime {
|
|
|
636
672
|
this.#controls.minDistance = 140
|
|
637
673
|
this.#controls.maxDistance = this.#initialRadius * 8
|
|
638
674
|
this.#controls.target.set(0, 0, 0)
|
|
639
|
-
PcbScene3dInteractionHints.configureControls(
|
|
675
|
+
PcbScene3dInteractionHints.configureControls(
|
|
676
|
+
this.#controls,
|
|
677
|
+
THREE,
|
|
678
|
+
this.#presetState.get()
|
|
679
|
+
)
|
|
640
680
|
|
|
641
681
|
PcbScene3dCameraRig.applyPreset(
|
|
642
682
|
this.#camera,
|
|
@@ -645,7 +685,9 @@ export class PcbScene3dRuntime {
|
|
|
645
685
|
this.#sceneDescription
|
|
646
686
|
)
|
|
647
687
|
|
|
648
|
-
this.#bindListener(this.#controls, 'change', () =>
|
|
688
|
+
this.#bindListener(this.#controls, 'change', () => {
|
|
689
|
+
this.#renderScheduler.schedule()
|
|
690
|
+
})
|
|
649
691
|
this.#bindListener(domElement, 'contextmenu', (event) => {
|
|
650
692
|
event.preventDefault?.()
|
|
651
693
|
})
|
|
@@ -726,6 +768,16 @@ export class PcbScene3dRuntime {
|
|
|
726
768
|
modelSearchExternalModelRoots: this.#modelSearchExternalModelRoots,
|
|
727
769
|
hasLoadedBoardAssemblyModel: this.#hasLoadedBoardAssemblyModel
|
|
728
770
|
})
|
|
771
|
+
PcbScene3dComponentVisibility.apply({
|
|
772
|
+
selectionRoots: this.#selectionRoots,
|
|
773
|
+
hiddenDesignators: this.#hiddenComponentDesignators,
|
|
774
|
+
fallbackBodyRoots: this.#fallbackBodyRoots,
|
|
775
|
+
loadedExternalModelDesignators:
|
|
776
|
+
this.#loadedExternalModelDesignators,
|
|
777
|
+
modelSearchExternalModelRoots: this.#modelSearchExternalModelRoots,
|
|
778
|
+
toggles: this.#toggles,
|
|
779
|
+
hasLoadedBoardAssemblyModel: this.#hasLoadedBoardAssemblyModel
|
|
780
|
+
})
|
|
729
781
|
}
|
|
730
782
|
|
|
731
783
|
/** @returns {void} */
|
|
@@ -46,12 +46,13 @@ export class PcbScene3dSelectionInspectorRenderer {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Renders selected component details and optional editable transform controls.
|
|
49
|
-
* @param {{ designator: string, selection?: { sourceType?: string } | null, selectionEntry: { component: any | null, externalPlacement: any | null }, adjustment: { scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }, includeControls?: boolean, translate: (key: string) => string }} options Render options.
|
|
49
|
+
* @param {{ designator: string, hidden?: boolean, selection?: { sourceType?: string } | null, selectionEntry: { component: any | null, externalPlacement: any | null }, adjustment: { scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }, includeControls?: boolean, translate: (key: string) => string }} options Render options.
|
|
50
50
|
* @returns {string}
|
|
51
51
|
*/
|
|
52
52
|
static renderSelected(options) {
|
|
53
53
|
const component = options.selectionEntry.component
|
|
54
54
|
const externalPlacement = options.selectionEntry.externalPlacement
|
|
55
|
+
const hidden = options.hidden === true
|
|
55
56
|
const fields = [
|
|
56
57
|
[options.translate('scene3d.designator'), options.designator],
|
|
57
58
|
[
|
|
@@ -127,11 +128,18 @@ export class PcbScene3dSelectionInspectorRenderer {
|
|
|
127
128
|
].filter(([, value]) => String(value || '').trim())
|
|
128
129
|
|
|
129
130
|
return (
|
|
130
|
-
'<h4 class="scene-3d__selection-title">' +
|
|
131
|
+
'<div class="scene-3d__selection-header"><h4 class="scene-3d__selection-title">' +
|
|
131
132
|
PcbScene3dSelectionInspectorRenderer.#escapeHtml(
|
|
132
133
|
options.translate('scene3d.componentInspector')
|
|
133
134
|
) +
|
|
134
|
-
'</h4
|
|
135
|
+
'</h4>' +
|
|
136
|
+
PcbScene3dSelectionInspectorRenderer.#renderVisibilityToggle(
|
|
137
|
+
options.designator,
|
|
138
|
+
options.selection?.sourceType || '',
|
|
139
|
+
hidden,
|
|
140
|
+
options.translate
|
|
141
|
+
) +
|
|
142
|
+
'</div><dl class="scene-3d__selection-list">' +
|
|
135
143
|
fields
|
|
136
144
|
.map(
|
|
137
145
|
([label, value]) =>
|
|
@@ -461,6 +469,59 @@ export class PcbScene3dSelectionInspectorRenderer {
|
|
|
461
469
|
)
|
|
462
470
|
}
|
|
463
471
|
|
|
472
|
+
/**
|
|
473
|
+
* Renders the selected-component visibility toggle.
|
|
474
|
+
* @param {string} designator Selected component designator.
|
|
475
|
+
* @param {string} sourceType Selection source type.
|
|
476
|
+
* @param {boolean} hidden Whether the component is hidden.
|
|
477
|
+
* @param {(key: string) => string} translate Translation lookup.
|
|
478
|
+
* @returns {string}
|
|
479
|
+
*/
|
|
480
|
+
static #renderVisibilityToggle(designator, sourceType, hidden, translate) {
|
|
481
|
+
const label = hidden
|
|
482
|
+
? translate('scene3d.showSelectedComponent')
|
|
483
|
+
: translate('scene3d.hideSelectedComponent')
|
|
484
|
+
const iconClass = hidden
|
|
485
|
+
? 'scene-3d__selection-eye-icon scene-3d__selection-eye-off-icon'
|
|
486
|
+
: 'scene-3d__selection-eye-icon'
|
|
487
|
+
|
|
488
|
+
return (
|
|
489
|
+
'<button class="scene-3d__selection-visibility" type="button" data-scene-3d-component-visibility="' +
|
|
490
|
+
PcbScene3dSelectionInspectorRenderer.#escapeHtml(designator) +
|
|
491
|
+
'" data-scene-3d-component-source="' +
|
|
492
|
+
PcbScene3dSelectionInspectorRenderer.#escapeHtml(sourceType) +
|
|
493
|
+
'" aria-label="' +
|
|
494
|
+
PcbScene3dSelectionInspectorRenderer.#escapeHtml(label) +
|
|
495
|
+
'" title="' +
|
|
496
|
+
PcbScene3dSelectionInspectorRenderer.#escapeHtml(label) +
|
|
497
|
+
'" aria-pressed="' +
|
|
498
|
+
(hidden ? 'true' : 'false') +
|
|
499
|
+
'"><span class="' +
|
|
500
|
+
iconClass +
|
|
501
|
+
'" aria-hidden="true">' +
|
|
502
|
+
PcbScene3dSelectionInspectorRenderer.#renderVisibilityIcon(
|
|
503
|
+
!hidden
|
|
504
|
+
) +
|
|
505
|
+
'</span></button>'
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Renders an eye icon for component visibility state.
|
|
511
|
+
* @param {boolean} visible Whether the component is visible.
|
|
512
|
+
* @returns {string}
|
|
513
|
+
*/
|
|
514
|
+
static #renderVisibilityIcon(visible) {
|
|
515
|
+
const slash = visible ? '' : '<path d="M4 4l16 16" />'
|
|
516
|
+
return (
|
|
517
|
+
'<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">' +
|
|
518
|
+
'<path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6z" />' +
|
|
519
|
+
'<circle cx="12" cy="12" r="2.5" />' +
|
|
520
|
+
slash +
|
|
521
|
+
'</svg>'
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
|
|
464
525
|
/**
|
|
465
526
|
* Formats a model file label.
|
|
466
527
|
* @param {any} externalPlacement External placement.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binds the selected-component visibility toggle in the inspector.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dSelectionVisibilityBinder {
|
|
5
|
+
/** @type {HTMLElement | null} */
|
|
6
|
+
#selectionNode
|
|
7
|
+
|
|
8
|
+
/** @type {{ node: EventTarget, type: string, listener: (event: any) => void } | null} */
|
|
9
|
+
#listenerRecord
|
|
10
|
+
|
|
11
|
+
/** @type {() => string} */
|
|
12
|
+
#resolveDesignator
|
|
13
|
+
|
|
14
|
+
/** @type {(designator: string) => boolean} */
|
|
15
|
+
#resolveHidden
|
|
16
|
+
|
|
17
|
+
/** @type {(designator: string, hidden: boolean) => void} */
|
|
18
|
+
#setHidden
|
|
19
|
+
|
|
20
|
+
/** @type {(designator: string, sourceType: string) => void} */
|
|
21
|
+
#refreshSelection
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {{ resolveDesignator: () => string, resolveHidden: (designator: string) => boolean, setHidden: (designator: string, hidden: boolean) => void, refreshSelection: (designator: string, sourceType: string) => void }} options Binder callbacks.
|
|
25
|
+
*/
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.#selectionNode = null
|
|
28
|
+
this.#listenerRecord = null
|
|
29
|
+
this.#resolveDesignator = options.resolveDesignator
|
|
30
|
+
this.#resolveHidden = options.resolveHidden
|
|
31
|
+
this.#setHidden = options.setHidden
|
|
32
|
+
this.#refreshSelection = options.refreshSelection
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Binds delegated visibility toggles in the selection inspector.
|
|
37
|
+
* @param {HTMLElement | null} selectionNode Inspector node.
|
|
38
|
+
* @returns {void}
|
|
39
|
+
*/
|
|
40
|
+
bindSelectionNode(selectionNode) {
|
|
41
|
+
this.dispose()
|
|
42
|
+
this.#selectionNode =
|
|
43
|
+
selectionNode && typeof selectionNode === 'object'
|
|
44
|
+
? selectionNode
|
|
45
|
+
: null
|
|
46
|
+
if (!this.#selectionNode) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const listener = (event) => this.#handleClick(event)
|
|
51
|
+
this.#selectionNode.addEventListener?.('click', listener)
|
|
52
|
+
this.#listenerRecord = {
|
|
53
|
+
node: this.#selectionNode,
|
|
54
|
+
type: 'click',
|
|
55
|
+
listener
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Removes the active delegated click listener.
|
|
61
|
+
* @returns {void}
|
|
62
|
+
*/
|
|
63
|
+
dispose() {
|
|
64
|
+
if (this.#listenerRecord) {
|
|
65
|
+
const { node, type, listener } = this.#listenerRecord
|
|
66
|
+
node.removeEventListener?.(type, listener)
|
|
67
|
+
}
|
|
68
|
+
this.#listenerRecord = null
|
|
69
|
+
this.#selectionNode = null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Handles one delegated visibility-button click.
|
|
74
|
+
* @param {any} event DOM event.
|
|
75
|
+
* @returns {void}
|
|
76
|
+
*/
|
|
77
|
+
#handleClick(event) {
|
|
78
|
+
const button =
|
|
79
|
+
PcbScene3dSelectionVisibilityBinder.#closestVisibilityButton(
|
|
80
|
+
event?.target
|
|
81
|
+
)
|
|
82
|
+
if (!button) {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
event?.preventDefault?.()
|
|
87
|
+
const designator = String(
|
|
88
|
+
button?.getAttribute?.('data-scene-3d-component-visibility') ||
|
|
89
|
+
this.#resolveDesignator() ||
|
|
90
|
+
''
|
|
91
|
+
).trim()
|
|
92
|
+
if (!designator) {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.#setHidden(designator, !this.#resolveHidden(designator))
|
|
97
|
+
this.#refreshSelection(
|
|
98
|
+
designator,
|
|
99
|
+
String(
|
|
100
|
+
button?.getAttribute?.('data-scene-3d-component-source') || ''
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Finds a visibility button from an event target.
|
|
107
|
+
* @param {any} target Event target.
|
|
108
|
+
* @returns {any | null}
|
|
109
|
+
*/
|
|
110
|
+
static #closestVisibilityButton(target) {
|
|
111
|
+
const closest = target?.closest?.(
|
|
112
|
+
'[data-scene-3d-component-visibility]'
|
|
113
|
+
)
|
|
114
|
+
return closest ||
|
|
115
|
+
target?.getAttribute?.('data-scene-3d-component-visibility')
|
|
116
|
+
? closest || target
|
|
117
|
+
: null
|
|
118
|
+
}
|
|
119
|
+
}
|
package/src/PcbScene3dText.mjs
CHANGED
|
@@ -22,6 +22,7 @@ const FALLBACK_MESSAGES = {
|
|
|
22
22
|
'scene3d.fallbackBody': 'Fallback body',
|
|
23
23
|
'scene3d.fallbackBodies': 'Fallback bodies',
|
|
24
24
|
'scene3d.footprint': 'Footprint',
|
|
25
|
+
'scene3d.hideSelectedComponent': 'Hide selected component',
|
|
25
26
|
'scene3d.inspectPrompt': 'Click a component to inspect it.',
|
|
26
27
|
'scene3d.interactiveViewAria': 'Interactive 3D PCB view',
|
|
27
28
|
'scene3d.isometric': 'Isometric',
|
|
@@ -39,11 +40,12 @@ const FALLBACK_MESSAGES = {
|
|
|
39
40
|
'scene3d.picked': 'Picked',
|
|
40
41
|
'scene3d.placements': 'Placements',
|
|
41
42
|
'scene3d.pointerHint':
|
|
42
|
-
'Drag
|
|
43
|
+
'Drag pans in Top/Bottom and orbits in Isometric; right-drag uses the alternate action. Use the wheel to zoom.',
|
|
43
44
|
'scene3d.offset': 'Offset',
|
|
44
45
|
'scene3d.resetTransform': 'Reset',
|
|
45
46
|
'scene3d.rotation': 'Rotation',
|
|
46
47
|
'scene3d.scale': 'Scale',
|
|
48
|
+
'scene3d.showSelectedComponent': 'Show selected component',
|
|
47
49
|
'scene3d.skipped': 'Skipped',
|
|
48
50
|
'scene3d.source': 'Source',
|
|
49
51
|
'scene3d.startFailed': '3D preview could not start:',
|
|
@@ -53,7 +55,7 @@ const FALLBACK_MESSAGES = {
|
|
|
53
55
|
'scene3d.toolbarAria': '3D camera presets',
|
|
54
56
|
'scene3d.top': 'Top',
|
|
55
57
|
'scene3d.touchHint':
|
|
56
|
-
'
|
|
58
|
+
'One-finger drag pans in Top/Bottom and orbits in Isometric. Pinch to zoom.',
|
|
57
59
|
'scene3d.unresolved': 'unresolved'
|
|
58
60
|
}
|
|
59
61
|
|
package/src/scene3d.mjs
CHANGED
|
@@ -17,6 +17,7 @@ export { PcbScene3dBoardSolderMaskFactory } from './PcbScene3dBoardSolderMaskFac
|
|
|
17
17
|
export { PcbScene3dCameraRig } from './PcbScene3dCameraRig.mjs'
|
|
18
18
|
export { PcbScene3dController } from './PcbScene3dController.mjs'
|
|
19
19
|
export { PcbScene3dCircuitJsonAdapter } from './PcbScene3dCircuitJsonAdapter.mjs'
|
|
20
|
+
export { PcbScene3dComponentVisibility } from './PcbScene3dComponentVisibility.mjs'
|
|
20
21
|
export { PcbScene3dCopperDetailFilter } from './PcbScene3dCopperDetailFilter.mjs'
|
|
21
22
|
export { PcbScene3dCopperFactory } from './PcbScene3dCopperFactory.mjs'
|
|
22
23
|
export { PcbScene3dCopperTextFactory } from './PcbScene3dCopperTextFactory.mjs'
|
|
@@ -34,9 +35,11 @@ export { PcbScene3dOutlineBuilder } from './PcbScene3dOutlineBuilder.mjs'
|
|
|
34
35
|
export { PcbScene3dPadFactory } from './PcbScene3dPadFactory.mjs'
|
|
35
36
|
export { PcbScene3dPresetState } from './PcbScene3dPresetState.mjs'
|
|
36
37
|
export { PcbScene3dRenderGroupVisibility } from './PcbScene3dRenderGroupVisibility.mjs'
|
|
38
|
+
export { PcbScene3dRenderScheduler } from './PcbScene3dRenderScheduler.mjs'
|
|
37
39
|
export { PcbScene3dRuntime } from './PcbScene3dRuntime.mjs'
|
|
38
40
|
export { PcbScene3dRuntimeBoardMeshes } from './PcbScene3dRuntimeBoardMeshes.mjs'
|
|
39
41
|
export { PcbScene3dSelectionStyler } from './PcbScene3dSelectionStyler.mjs'
|
|
42
|
+
export { PcbScene3dSelectionVisibilityBinder } from './PcbScene3dSelectionVisibilityBinder.mjs'
|
|
40
43
|
export { PcbScene3dShapePathFactory } from './PcbScene3dShapePathFactory.mjs'
|
|
41
44
|
export { PcbScene3dShellRenderer } from './PcbScene3dShellRenderer.mjs'
|
|
42
45
|
export { PcbScene3dSilkscreenFactory } from './PcbScene3dSilkscreenFactory.mjs'
|
package/src/styles/scene3d.css
CHANGED
|
@@ -173,11 +173,59 @@ body.is-viewer-mode.is-viewer-3d .scene-3d {
|
|
|
173
173
|
border: 1px solid rgba(95, 112, 124, 0.12);
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
.scene-3d__selection-header {
|
|
177
|
+
display: grid;
|
|
178
|
+
grid-template-columns: minmax(0, 1fr) 2.1rem;
|
|
179
|
+
align-items: center;
|
|
180
|
+
gap: 0.5rem;
|
|
181
|
+
}
|
|
182
|
+
|
|
176
183
|
.scene-3d__selection-title {
|
|
177
184
|
margin: 0;
|
|
178
185
|
font-size: 0.9rem;
|
|
179
186
|
}
|
|
180
187
|
|
|
188
|
+
.scene-3d__selection-visibility {
|
|
189
|
+
display: inline-flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
justify-content: center;
|
|
192
|
+
width: 2.1rem;
|
|
193
|
+
height: 2.1rem;
|
|
194
|
+
border: 1px solid rgba(95, 112, 124, 0.16);
|
|
195
|
+
border-radius: 8px;
|
|
196
|
+
background: rgba(255, 255, 255, 0.78);
|
|
197
|
+
color: #425d70;
|
|
198
|
+
cursor: pointer;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.scene-3d__selection-visibility:hover,
|
|
202
|
+
.scene-3d__selection-visibility[aria-pressed='true'] {
|
|
203
|
+
border-color: rgba(184, 90, 37, 0.34);
|
|
204
|
+
color: var(--accent);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.scene-3d__selection-eye-icon {
|
|
208
|
+
width: 1.1rem;
|
|
209
|
+
height: 1.1rem;
|
|
210
|
+
display: inline-flex;
|
|
211
|
+
align-items: center;
|
|
212
|
+
justify-content: center;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.scene-3d__selection-eye-icon .icon {
|
|
216
|
+
width: 1.05rem;
|
|
217
|
+
height: 1.05rem;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.scene-3d__selection-eye-icon .icon path,
|
|
221
|
+
.scene-3d__selection-eye-icon .icon circle {
|
|
222
|
+
fill: none;
|
|
223
|
+
stroke: currentColor;
|
|
224
|
+
stroke-width: 1.8;
|
|
225
|
+
stroke-linecap: round;
|
|
226
|
+
stroke-linejoin: round;
|
|
227
|
+
}
|
|
228
|
+
|
|
181
229
|
.scene-3d__selection-empty {
|
|
182
230
|
margin: 0;
|
|
183
231
|
color: var(--muted);
|