pcb-scene3d-viewer 1.1.19 → 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 +3 -2
- package/src/PcbAssemblyBoardSubstrateBuilder.mjs +467 -0
- package/src/PcbAssemblyExportCoordinateFrame.mjs +19 -0
- package/src/PcbAssemblyGeometryBuildProgress.mjs +201 -0
- package/src/PcbAssemblyGeometryBuilder.mjs +994 -0
- package/src/PcbAssemblyMeshUtils.mjs +541 -0
- package/src/PcbAssemblyModelMeshLoader.mjs +447 -0
- package/src/PcbAssemblyPolygonTriangulator.mjs +301 -0
- package/src/PcbAssemblyStepWriter.mjs +952 -0
- package/src/PcbAssemblyWrlWriter.mjs +144 -0
- 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 +12 -0
- package/src/styles/scene3d.css +48 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { PcbAssemblyExportCoordinateFrame } from './PcbAssemblyExportCoordinateFrame.mjs'
|
|
2
|
+
import { PcbAssemblyMeshUtils } from './PcbAssemblyMeshUtils.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Writes PCB assembly meshes as VRML 2.0 WRL.
|
|
6
|
+
*/
|
|
7
|
+
export class PcbAssemblyWrlWriter {
|
|
8
|
+
/**
|
|
9
|
+
* Writes meshes to WRL text.
|
|
10
|
+
* @param {{ name?: string, meshes?: object[] }} assembly Assembly data.
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
static write(assembly = {}) {
|
|
14
|
+
const meshes = Array.isArray(assembly?.meshes) ? assembly.meshes : []
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
'#VRML V2.0 utf8\n' +
|
|
18
|
+
'WorldInfo { title "' +
|
|
19
|
+
PcbAssemblyWrlWriter.#escapeString(assembly?.name || 'assembly') +
|
|
20
|
+
'" }\n' +
|
|
21
|
+
'Group {\n children [\n' +
|
|
22
|
+
meshes.map((mesh) => PcbAssemblyWrlWriter.#shape(mesh)).join('') +
|
|
23
|
+
' ]\n}\n'
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Writes one mesh shape.
|
|
29
|
+
* @param {{ name?: string, vertices?: number[][], faces?: number[][], color?: number[] }} mesh Mesh data.
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
static #shape(mesh) {
|
|
33
|
+
const vertices = Array.isArray(mesh?.vertices) ? mesh.vertices : []
|
|
34
|
+
const faces = Array.isArray(mesh?.faces) ? mesh.faces : []
|
|
35
|
+
if (!vertices.length || !faces.length) {
|
|
36
|
+
return ''
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
' DEF ' +
|
|
41
|
+
PcbAssemblyWrlWriter.#safeVrmlName(mesh?.name || 'mesh') +
|
|
42
|
+
' Shape {\n' +
|
|
43
|
+
' appearance Appearance { material Material { diffuseColor ' +
|
|
44
|
+
PcbAssemblyWrlWriter.#color(mesh?.color) +
|
|
45
|
+
' } }\n' +
|
|
46
|
+
' geometry IndexedFaceSet {\n' +
|
|
47
|
+
' solid TRUE\n' +
|
|
48
|
+
' coord Coordinate { point [\n' +
|
|
49
|
+
vertices
|
|
50
|
+
.map(
|
|
51
|
+
(vertex) =>
|
|
52
|
+
' ' + PcbAssemblyWrlWriter.#point(vertex)
|
|
53
|
+
)
|
|
54
|
+
.join('') +
|
|
55
|
+
' ] }\n' +
|
|
56
|
+
' coordIndex [\n' +
|
|
57
|
+
faces
|
|
58
|
+
.map(
|
|
59
|
+
(face) =>
|
|
60
|
+
' ' +
|
|
61
|
+
PcbAssemblyWrlWriter.#faceIndices(face) +
|
|
62
|
+
' -1,\n'
|
|
63
|
+
)
|
|
64
|
+
.join('') +
|
|
65
|
+
' ]\n' +
|
|
66
|
+
' }\n' +
|
|
67
|
+
' }\n'
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Formats one exported coordinate point.
|
|
73
|
+
* @param {number[]} vertex Internal mesh vertex in mils.
|
|
74
|
+
* @returns {string}
|
|
75
|
+
*/
|
|
76
|
+
static #point(vertex) {
|
|
77
|
+
return (
|
|
78
|
+
PcbAssemblyExportCoordinateFrame.vertexMilToMm(vertex)
|
|
79
|
+
.map((value) => PcbAssemblyWrlWriter.#formatNumber(value))
|
|
80
|
+
.join(' ') + ',\n'
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Formats one face index list.
|
|
86
|
+
* @param {number[]} face Face indices.
|
|
87
|
+
* @returns {string}
|
|
88
|
+
*/
|
|
89
|
+
static #faceIndices(face) {
|
|
90
|
+
return (Array.isArray(face) ? face : [])
|
|
91
|
+
.map((index) => String(Number(index || 0)))
|
|
92
|
+
.join(' ')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Formats one RGB color.
|
|
97
|
+
* @param {number[] | undefined} color RGB color.
|
|
98
|
+
* @returns {string}
|
|
99
|
+
*/
|
|
100
|
+
static #color(color) {
|
|
101
|
+
const normalized = Array.isArray(color) ? color : [0.55, 0.56, 0.58]
|
|
102
|
+
return [0, 1, 2]
|
|
103
|
+
.map((index) =>
|
|
104
|
+
PcbAssemblyWrlWriter.#formatNumber(
|
|
105
|
+
Math.max(Math.min(Number(normalized[index] || 0), 1), 0)
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
.join(' ')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Formats a compact numeric literal.
|
|
113
|
+
* @param {number} value Numeric value.
|
|
114
|
+
* @returns {string}
|
|
115
|
+
*/
|
|
116
|
+
static #formatNumber(value) {
|
|
117
|
+
const number = Number(value || 0)
|
|
118
|
+
return Math.abs(number) < 0.0000001
|
|
119
|
+
? '0'
|
|
120
|
+
: Number(number.toFixed(6)).toString()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Escapes a WRL string.
|
|
125
|
+
* @param {string} value Source value.
|
|
126
|
+
* @returns {string}
|
|
127
|
+
*/
|
|
128
|
+
static #escapeString(value) {
|
|
129
|
+
return String(value || '').replaceAll('"', '\\"')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Builds a valid VRML DEF token.
|
|
134
|
+
* @param {string} value Source value.
|
|
135
|
+
* @returns {string}
|
|
136
|
+
*/
|
|
137
|
+
static #safeVrmlName(value) {
|
|
138
|
+
const safe = PcbAssemblyMeshUtils.safeName(value).replace(
|
|
139
|
+
/[^A-Za-z0-9_]+/gu,
|
|
140
|
+
'_'
|
|
141
|
+
)
|
|
142
|
+
return /^[A-Za-z_]/u.test(safe) ? safe : '_' + safe
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -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
|
+
}
|