brep-io-kernel 1.0.20 → 1.0.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/README.md +4 -1
- package/dist-kernel/brep-kernel.js +10858 -9938
- package/package.json +3 -2
- package/src/BREP/Edge.js +2 -0
- package/src/BREP/Face.js +2 -0
- package/src/BREP/SolidMethods/visualize.js +372 -365
- package/src/BREP/Vertex.js +2 -17
- package/src/PartHistory.js +4 -25
- package/src/SketchSolver2D.js +3 -0
- package/src/UI/AccordionWidget.js +1 -1
- package/src/UI/EnvMonacoEditor.js +0 -3
- package/src/UI/HistoryWidget.js +12 -4
- package/src/UI/SceneListing.js +45 -7
- package/src/UI/SelectionFilter.js +903 -438
- package/src/UI/SelectionState.js +464 -0
- package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +41 -1
- package/src/UI/assembly/AssemblyConstraintsWidget.js +21 -3
- package/src/UI/assembly/constraintSelectionUtils.js +3 -182
- package/src/UI/{assembly/constraintFaceUtils.js → faceUtils.js} +30 -5
- package/src/UI/featureDialogs.js +154 -69
- package/src/UI/history/HistoryCollectionWidget.js +65 -0
- package/src/UI/pmi/AnnotationCollectionWidget.js +1 -0
- package/src/UI/pmi/BaseAnnotation.js +37 -0
- package/src/UI/pmi/LabelOverlay.js +32 -0
- package/src/UI/pmi/PMIMode.js +27 -0
- package/src/UI/pmi/dimensions/AngleDimensionAnnotation.js +5 -0
- package/src/UI/pmi/dimensions/ExplodeBodyAnnotation.js +5 -0
- package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +57 -0
- package/src/UI/pmi/dimensions/LeaderAnnotation.js +5 -0
- package/src/UI/pmi/dimensions/LinearDimensionAnnotation.js +22 -16
- package/src/UI/pmi/dimensions/NoteAnnotation.js +9 -0
- package/src/UI/pmi/dimensions/RadialDimensionAnnotation.js +81 -16
- package/src/UI/toolbarButtons/orientToFaceButton.js +3 -36
- package/src/UI/toolbarButtons/registerDefaultButtons.js +2 -0
- package/src/UI/toolbarButtons/selectionStateButton.js +206 -0
- package/src/UI/viewer.js +34 -13
- package/src/assemblyConstraints/AssemblyConstraintHistory.js +18 -42
- package/src/assemblyConstraints/constraints/AngleConstraint.js +1 -0
- package/src/assemblyConstraints/constraints/DistanceConstraint.js +1 -0
- package/src/features/selectionUtils.js +21 -5
- package/src/features/sketch/SketchFeature.js +2 -2
- package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +3 -2
- package/src/utils/selectionResolver.js +258 -0
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SelectionState } from "./SelectionState.js";
|
|
2
2
|
import {BREP} from '../BREP/BREP.js';
|
|
3
|
+
|
|
4
|
+
const debugMode = false;
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
3
9
|
export class SelectionFilter {
|
|
4
10
|
static SOLID = "SOLID";
|
|
5
11
|
static COMPONENT = "COMPONENT";
|
|
6
12
|
static FACE = "FACE";
|
|
7
13
|
static PLANE = "PLANE";
|
|
8
14
|
static SKETCH = "SKETCH";
|
|
15
|
+
static DATUM = "DATUM";
|
|
16
|
+
static HELIX = "HELIX";
|
|
9
17
|
static EDGE = "EDGE";
|
|
10
18
|
static LOOP = "LOOP";
|
|
11
19
|
static VERTEX = "VERTEX";
|
|
@@ -16,6 +24,7 @@ export class SelectionFilter {
|
|
|
16
24
|
static viewer = null;
|
|
17
25
|
static previouseAllowedSelectionTypes = null;
|
|
18
26
|
static _hovered = new Set(); // objects currently hover-highlighted
|
|
27
|
+
static _hoveredSourceMap = new Map(); // key -> source object for hover
|
|
19
28
|
static hoverColor = '#fbff00'; // default hover tint
|
|
20
29
|
static _selectionActions = new Map();
|
|
21
30
|
static _selectionActionOrder = [];
|
|
@@ -26,12 +35,30 @@ export class SelectionFilter {
|
|
|
26
35
|
static _historyContextActions = new Map();
|
|
27
36
|
static _selectionActionSeparator = null;
|
|
28
37
|
static _contextSuppressReasons = new Set();
|
|
38
|
+
static _selectionFilterIndicator = null;
|
|
39
|
+
static _selectionFilterIndicatorToggle = null;
|
|
40
|
+
static _selectionFilterIndicatorPanel = null;
|
|
41
|
+
static _selectionFilterCheckboxes = new Map();
|
|
42
|
+
static _selectionFilterTypes = null;
|
|
43
|
+
static _selectionFilterOutsideBound = false;
|
|
44
|
+
static _selectionFilterTintBtn = null;
|
|
45
|
+
static _clickWatcherTimer = null;
|
|
46
|
+
static _missingClickLogged = new Map();
|
|
47
|
+
static _clickWatcherIntervalMs = 2000;
|
|
48
|
+
static _onClickWatcherSeq = 1;
|
|
49
|
+
static _selectableTintState = {
|
|
50
|
+
active: false,
|
|
51
|
+
activeColor: null,
|
|
52
|
+
colorIndex: 0,
|
|
53
|
+
colors: ['#34d399', '#f97316', '#60a5fa', '#f43f5e'],
|
|
54
|
+
materials: new Map(),
|
|
55
|
+
};
|
|
29
56
|
|
|
30
57
|
constructor() {
|
|
31
58
|
throw new Error("SelectionFilter is static and cannot be instantiated.");
|
|
32
59
|
}
|
|
33
60
|
|
|
34
|
-
static get TYPES() { return [this.SOLID, this.COMPONENT, this.FACE, this.PLANE, this.SKETCH, this.EDGE, this.LOOP, this.VERTEX, this.ALL]; }
|
|
61
|
+
static get TYPES() { return [this.SOLID, this.COMPONENT, this.FACE, this.PLANE, this.SKETCH, this.DATUM, this.HELIX, this.EDGE, this.LOOP, this.VERTEX, this.ALL]; }
|
|
35
62
|
|
|
36
63
|
// Convenience: return the list of selectable types for the dropdown (excludes ALL)
|
|
37
64
|
static getAvailableTypes() {
|
|
@@ -47,6 +74,144 @@ export class SelectionFilter {
|
|
|
47
74
|
return null;
|
|
48
75
|
}
|
|
49
76
|
|
|
77
|
+
static _withSilentOnClick(target, fn) {
|
|
78
|
+
if (!target || typeof fn !== 'function') return;
|
|
79
|
+
try { target.__brepOnClickSilent = true; } catch { }
|
|
80
|
+
try { fn(); } catch { } finally {
|
|
81
|
+
try { target.__brepOnClickSilent = false; } catch { }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static _installOnClickWatcher(target) {
|
|
86
|
+
if (!target || typeof target !== 'object') return;
|
|
87
|
+
const existingDesc = Object.getOwnPropertyDescriptor(target, 'onClick');
|
|
88
|
+
if (existingDesc?.get && existingDesc?.get.__brepOnClickWatcher) return;
|
|
89
|
+
let current = typeof target.onClick !== 'undefined' ? target.onClick : undefined;
|
|
90
|
+
const getter = function () { return current; };
|
|
91
|
+
getter.__brepOnClickWatcher = true;
|
|
92
|
+
const setter = function (v) {
|
|
93
|
+
const prev = current;
|
|
94
|
+
current = v;
|
|
95
|
+
try {
|
|
96
|
+
target.__brepOnClickLastSetAt = Date.now();
|
|
97
|
+
target.__brepOnClickLastSetStack = new Error('[SelectionFilter] onClick set').stack;
|
|
98
|
+
} catch { }
|
|
99
|
+
const silent = !!target.__brepOnClickSilent;
|
|
100
|
+
const prevFn = typeof prev === 'function';
|
|
101
|
+
const nextFn = typeof v === 'function';
|
|
102
|
+
if (!silent && prev !== v) {
|
|
103
|
+
if (!nextFn || (prevFn && !nextFn)) {
|
|
104
|
+
if (debugMode) {
|
|
105
|
+
try {
|
|
106
|
+
console.log('[SelectionFilter] onClick removed/overwritten', {
|
|
107
|
+
name: target?.name,
|
|
108
|
+
type: target?.type,
|
|
109
|
+
uuid: target?.uuid,
|
|
110
|
+
prev,
|
|
111
|
+
next: v,
|
|
112
|
+
target,
|
|
113
|
+
});
|
|
114
|
+
console.trace('[SelectionFilter] onClick change stack');
|
|
115
|
+
} catch { }
|
|
116
|
+
}
|
|
117
|
+
} else if (!v?.__brepSelectionHandler) {
|
|
118
|
+
if (debugMode) {
|
|
119
|
+
try {
|
|
120
|
+
console.log('[SelectionFilter] onClick replaced', {
|
|
121
|
+
name: target?.name,
|
|
122
|
+
type: target?.type,
|
|
123
|
+
uuid: target?.uuid,
|
|
124
|
+
prev,
|
|
125
|
+
next: v,
|
|
126
|
+
target,
|
|
127
|
+
});
|
|
128
|
+
console.trace('[SelectionFilter] onClick set stack');
|
|
129
|
+
} catch { }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
setter.__brepOnClickWatcher = true;
|
|
135
|
+
try {
|
|
136
|
+
Object.defineProperty(target, 'onClick', {
|
|
137
|
+
get: getter,
|
|
138
|
+
set: setter,
|
|
139
|
+
configurable: true,
|
|
140
|
+
enumerable: true,
|
|
141
|
+
});
|
|
142
|
+
} catch {
|
|
143
|
+
try { target.onClick = current; } catch { }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
static startClickWatcher(viewer = null, { intervalMs = 2000 } = {}) {
|
|
148
|
+
const v = viewer || SelectionFilter.viewer;
|
|
149
|
+
SelectionFilter._clickWatcherIntervalMs = Math.max(250, Number(intervalMs) || 2000);
|
|
150
|
+
if (SelectionFilter._clickWatcherTimer) return;
|
|
151
|
+
const scan = () => {
|
|
152
|
+
try {
|
|
153
|
+
const scene = v?.partHistory?.scene || v?.scene || SelectionFilter.viewer?.partHistory?.scene || SelectionFilter.viewer?.scene || null;
|
|
154
|
+
if (!scene) return;
|
|
155
|
+
const selectionTypes = new Set(SelectionFilter.TYPES.filter(t => t && t !== SelectionFilter.ALL));
|
|
156
|
+
const missingNow = new Set();
|
|
157
|
+
const stack = Array.isArray(scene.children) ? [...scene.children] : [];
|
|
158
|
+
while (stack.length) {
|
|
159
|
+
const current = stack.pop();
|
|
160
|
+
if (!current) continue;
|
|
161
|
+
const kids = Array.isArray(current?.children) ? current.children : [];
|
|
162
|
+
for (const child of kids) stack.push(child);
|
|
163
|
+
|
|
164
|
+
const type = String(current.type || '').toUpperCase();
|
|
165
|
+
if (!selectionTypes.has(type)) continue;
|
|
166
|
+
|
|
167
|
+
SelectionFilter._installOnClickWatcher(current);
|
|
168
|
+
let hasClick = typeof current.onClick === 'function';
|
|
169
|
+
if (!hasClick) {
|
|
170
|
+
try {
|
|
171
|
+
SelectionFilter.ensureSelectionHandlers(current, { deep: false });
|
|
172
|
+
} catch { }
|
|
173
|
+
hasClick = typeof current.onClick === 'function';
|
|
174
|
+
}
|
|
175
|
+
if (!hasClick) {
|
|
176
|
+
missingNow.add(current.uuid);
|
|
177
|
+
const last = SelectionFilter._missingClickLogged.get(current.uuid);
|
|
178
|
+
if (!last) {
|
|
179
|
+
SelectionFilter._missingClickLogged.set(current.uuid, Date.now());
|
|
180
|
+
if (debugMode) {
|
|
181
|
+
try {
|
|
182
|
+
console.log('[SelectionFilter] Missing onClick', {
|
|
183
|
+
name: current?.name,
|
|
184
|
+
type: current?.type,
|
|
185
|
+
uuid: current?.uuid,
|
|
186
|
+
parentName: current?.parent?.name,
|
|
187
|
+
parentType: current?.parent?.type,
|
|
188
|
+
lastSetAt: current?.__brepOnClickLastSetAt || null,
|
|
189
|
+
lastSetStack: current?.__brepOnClickLastSetStack || null,
|
|
190
|
+
object: current,
|
|
191
|
+
});
|
|
192
|
+
} catch { }
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Clear recovered entries
|
|
198
|
+
for (const key of SelectionFilter._missingClickLogged.keys()) {
|
|
199
|
+
if (!missingNow.has(key)) SelectionFilter._missingClickLogged.delete(key);
|
|
200
|
+
}
|
|
201
|
+
} catch { }
|
|
202
|
+
};
|
|
203
|
+
try { scan(); } catch { }
|
|
204
|
+
SelectionFilter._clickWatcherTimer = setInterval(scan, SelectionFilter._clickWatcherIntervalMs);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
static stopClickWatcher() {
|
|
208
|
+
if (SelectionFilter._clickWatcherTimer) {
|
|
209
|
+
clearInterval(SelectionFilter._clickWatcherTimer);
|
|
210
|
+
SelectionFilter._clickWatcherTimer = null;
|
|
211
|
+
}
|
|
212
|
+
SelectionFilter._missingClickLogged.clear();
|
|
213
|
+
}
|
|
214
|
+
|
|
50
215
|
static setCurrentType(_type) {
|
|
51
216
|
// No-op: current type is no longer tracked.
|
|
52
217
|
void _type;
|
|
@@ -82,7 +247,50 @@ export class SelectionFilter {
|
|
|
82
247
|
}
|
|
83
248
|
}
|
|
84
249
|
|
|
250
|
+
static ensureSelectionHandlers(obj, { deep = false } = {}) {
|
|
251
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
252
|
+
let changed = false;
|
|
253
|
+
const attach = (target) => {
|
|
254
|
+
if (!target || typeof target !== 'object') return;
|
|
255
|
+
SelectionState.attach(target);
|
|
256
|
+
SelectionFilter._installOnClickWatcher(target);
|
|
257
|
+
if (typeof target.onClick === 'function') return;
|
|
258
|
+
SelectionFilter._withSilentOnClick(target, () => {
|
|
259
|
+
target.onClick = () => {
|
|
260
|
+
try {
|
|
261
|
+
if (target.type === SelectionFilter.SOLID && target.parent && target.parent.type === SelectionFilter.COMPONENT) {
|
|
262
|
+
const handledByParent = SelectionFilter.toggleSelection(target.parent);
|
|
263
|
+
if (!handledByParent) SelectionFilter.toggleSelection(target);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
SelectionFilter.toggleSelection(target);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (debugMode) {
|
|
269
|
+
try { console.warn('[SelectionFilter] toggleSelection failed:', error); } catch (_) { /* ignore */ }
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
try { target.onClick.__brepSelectionHandler = true; } catch (_) { /* ignore */ }
|
|
275
|
+
changed = true;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
if (!deep) {
|
|
279
|
+
attach(obj);
|
|
280
|
+
return changed;
|
|
281
|
+
}
|
|
85
282
|
|
|
283
|
+
const stack = [obj];
|
|
284
|
+
while (stack.length) {
|
|
285
|
+
const current = stack.pop();
|
|
286
|
+
attach(current);
|
|
287
|
+
const kids = Array.isArray(current?.children) ? current.children : [];
|
|
288
|
+
for (const child of kids) {
|
|
289
|
+
if (child) stack.push(child);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return changed;
|
|
293
|
+
}
|
|
86
294
|
|
|
87
295
|
static allowType(type) {
|
|
88
296
|
// Legacy support: expand available set; does not change currentType
|
|
@@ -127,37 +335,67 @@ export class SelectionFilter {
|
|
|
127
335
|
SelectionFilter.#logAllowedTypesChange(SelectionFilter.allowedSelectionTypes, 'Reset');
|
|
128
336
|
}
|
|
129
337
|
|
|
130
|
-
static #getBaseMaterial(obj) {
|
|
131
|
-
if (!obj) return null;
|
|
132
|
-
const ud = obj.userData || {};
|
|
133
|
-
if (ud.__baseMaterial) return ud.__baseMaterial;
|
|
134
|
-
if (obj.type === SelectionFilter.FACE) return CADmaterials.FACE?.BASE ?? CADmaterials.FACE ?? obj.material;
|
|
135
|
-
if (obj.type === SelectionFilter.PLANE) return CADmaterials.PLANE?.BASE ?? CADmaterials.FACE?.BASE ?? obj.material;
|
|
136
|
-
if (obj.type === SelectionFilter.EDGE) return CADmaterials.EDGE?.BASE ?? CADmaterials.EDGE ?? obj.material;
|
|
137
|
-
if (obj.type === SelectionFilter.SOLID || obj.type === SelectionFilter.COMPONENT) return obj.material;
|
|
138
|
-
return obj.material;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
338
|
// ---------------- Hover Highlighting ----------------
|
|
142
|
-
static getHoverColor() { return SelectionFilter.hoverColor; }
|
|
339
|
+
static getHoverColor() { return SelectionState.hoverColor || SelectionFilter.hoverColor; }
|
|
143
340
|
static setHoverColor(hex) {
|
|
144
341
|
if (!hex) return;
|
|
145
342
|
try { SelectionFilter.hoverColor = String(hex); } catch (_) { }
|
|
343
|
+
SelectionState.setHoverColor(SelectionFilter.hoverColor);
|
|
146
344
|
// Update current hovered objects live
|
|
147
345
|
for (const o of Array.from(SelectionFilter._hovered)) {
|
|
148
|
-
if (o
|
|
149
|
-
|
|
150
|
-
|
|
346
|
+
if (!o) continue;
|
|
347
|
+
try {
|
|
348
|
+
SelectionState.attach(o);
|
|
349
|
+
o.hovered = false;
|
|
350
|
+
o.hovered = true;
|
|
351
|
+
} catch { }
|
|
151
352
|
}
|
|
152
353
|
}
|
|
153
354
|
|
|
154
355
|
static setHoverObject(obj, options = {}) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
356
|
+
SelectionFilter.setHoverObjects(obj ? [obj] : [], options);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
static setHoverObjects(objs, options = {}) {
|
|
360
|
+
const { ignoreFilter = false, append = false } = options;
|
|
361
|
+
const prevKeys = new Set(SelectionFilter._hoveredSourceMap.keys());
|
|
362
|
+
if (!append) {
|
|
363
|
+
SelectionFilter._clearHoverState({ emit: false });
|
|
364
|
+
}
|
|
365
|
+
if (!objs) return;
|
|
366
|
+
const list = Array.isArray(objs) ? objs : [objs];
|
|
367
|
+
const seen = new Set();
|
|
368
|
+
const keyFor = (obj) => obj?.uuid || obj?.id || obj?.name || obj;
|
|
369
|
+
for (const obj of list) {
|
|
370
|
+
if (!obj) continue;
|
|
371
|
+
const key = keyFor(obj);
|
|
372
|
+
if (seen.has(key)) continue;
|
|
373
|
+
seen.add(key);
|
|
374
|
+
const allowed = ignoreFilter || SelectionFilter.IsAllowed(obj.type);
|
|
375
|
+
if (!allowed) continue;
|
|
376
|
+
if (key && !SelectionFilter._hoveredSourceMap.has(key)) {
|
|
377
|
+
SelectionFilter._hoveredSourceMap.set(key, obj);
|
|
378
|
+
}
|
|
379
|
+
const targets = SelectionState.getHoverTargets(obj);
|
|
380
|
+
for (const t of targets) {
|
|
381
|
+
if (!t) continue;
|
|
382
|
+
try {
|
|
383
|
+
SelectionState.attach(t);
|
|
384
|
+
t.hovered = true;
|
|
385
|
+
SelectionFilter._hovered.add(t);
|
|
386
|
+
} catch { }
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const nextKeys = new Set(SelectionFilter._hoveredSourceMap.keys());
|
|
390
|
+
let changed = prevKeys.size !== nextKeys.size;
|
|
391
|
+
if (!changed) {
|
|
392
|
+
for (const k of nextKeys) {
|
|
393
|
+
if (!prevKeys.has(k)) { changed = true; break; }
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (changed) {
|
|
397
|
+
SelectionFilter._emitHoverChanged(Array.from(SelectionFilter._hoveredSourceMap.values()));
|
|
398
|
+
}
|
|
161
399
|
}
|
|
162
400
|
|
|
163
401
|
static setHoverByName(scene, name) {
|
|
@@ -168,157 +406,38 @@ export class SelectionFilter {
|
|
|
168
406
|
}
|
|
169
407
|
|
|
170
408
|
static clearHover() {
|
|
171
|
-
|
|
172
|
-
for (const o of Array.from(SelectionFilter._hovered)) {
|
|
173
|
-
SelectionFilter.#restoreHover(o);
|
|
174
|
-
}
|
|
175
|
-
SelectionFilter._hovered.clear();
|
|
409
|
+
SelectionFilter._clearHoverState({ emit: true });
|
|
176
410
|
}
|
|
177
411
|
|
|
178
|
-
static
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|| SelectionFilter.matchesAllowedType(obj.type);
|
|
185
|
-
if (!allowed) return;
|
|
186
|
-
|
|
187
|
-
// Only ever highlight one object: the exact object provided, if it has a color
|
|
188
|
-
const target = obj;
|
|
189
|
-
if (!target) return;
|
|
190
|
-
|
|
191
|
-
const applyToOne = (t) => {
|
|
192
|
-
if (!t) return;
|
|
193
|
-
if (!t.userData) t.userData = {};
|
|
194
|
-
const origMat = t.material;
|
|
195
|
-
if (!origMat) return;
|
|
196
|
-
if (t.userData.__hoverMatApplied) { SelectionFilter._hovered.add(t); return; }
|
|
197
|
-
let clone;
|
|
198
|
-
try { clone = typeof origMat.clone === 'function' ? origMat.clone() : origMat; } catch { clone = origMat; }
|
|
199
|
-
try { if (clone && clone.color && typeof clone.color.set === 'function') clone.color.set(SelectionFilter.hoverColor); } catch { }
|
|
200
|
-
try {
|
|
201
|
-
if (origMat && clone && origMat.resolution && clone.resolution && typeof clone.resolution.copy === 'function') {
|
|
202
|
-
clone.resolution.copy(origMat.resolution);
|
|
203
|
-
}
|
|
204
|
-
} catch { }
|
|
205
|
-
try {
|
|
206
|
-
if (origMat && clone && typeof origMat.dashed !== 'undefined' && typeof clone.dashed !== 'undefined') {
|
|
207
|
-
clone.dashed = origMat.dashed;
|
|
208
|
-
}
|
|
209
|
-
if (origMat && clone && typeof origMat.dashSize !== 'undefined' && typeof clone.dashSize !== 'undefined') {
|
|
210
|
-
clone.dashSize = origMat.dashSize;
|
|
211
|
-
}
|
|
212
|
-
if (origMat && clone && typeof origMat.gapSize !== 'undefined' && typeof clone.gapSize !== 'undefined') {
|
|
213
|
-
clone.gapSize = origMat.gapSize;
|
|
214
|
-
}
|
|
215
|
-
if (origMat && clone && typeof origMat.dashScale !== 'undefined' && typeof clone.dashScale !== 'undefined') {
|
|
216
|
-
clone.dashScale = origMat.dashScale;
|
|
217
|
-
}
|
|
218
|
-
} catch { }
|
|
219
|
-
try {
|
|
220
|
-
t.userData.__hoverOrigMat = origMat;
|
|
221
|
-
t.userData.__hoverMatApplied = true;
|
|
222
|
-
if (clone !== origMat) t.material = clone;
|
|
223
|
-
t.userData.__hoverMat = clone;
|
|
224
|
-
} catch { }
|
|
225
|
-
SelectionFilter._hovered.add(t);
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
if (target.type === SelectionFilter.SOLID || target.type === SelectionFilter.COMPONENT) {
|
|
229
|
-
// Highlight all immediate child faces/edges for SOLID or COMPONENT children
|
|
230
|
-
if (Array.isArray(target.children)) {
|
|
231
|
-
for (const ch of target.children) {
|
|
232
|
-
if (!ch) continue;
|
|
233
|
-
if (ch.type === SelectionFilter.SOLID || ch.type === SelectionFilter.COMPONENT) {
|
|
234
|
-
if (Array.isArray(ch.children)) {
|
|
235
|
-
for (const nested of ch.children) {
|
|
236
|
-
if (nested && (nested.type === SelectionFilter.FACE || nested.type === SelectionFilter.EDGE)) applyToOne(nested);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
} else if (ch.type === SelectionFilter.FACE || ch.type === SelectionFilter.EDGE) {
|
|
240
|
-
applyToOne(ch);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
// Track the solid as a logical hovered root to clear later
|
|
245
|
-
SelectionFilter._hovered.add(target);
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (target.type === SelectionFilter.VERTEX) {
|
|
250
|
-
// Apply to the vertex object and any drawable children (e.g., Points)
|
|
251
|
-
applyToOne(target);
|
|
252
|
-
if (Array.isArray(target.children)) {
|
|
253
|
-
for (const ch of target.children) {
|
|
254
|
-
applyToOne(ch);
|
|
255
|
-
}
|
|
412
|
+
static _emitHoverChanged(objs = []) {
|
|
413
|
+
try {
|
|
414
|
+
const list = Array.isArray(objs) ? objs : [];
|
|
415
|
+
const uuids = [];
|
|
416
|
+
for (const obj of list) {
|
|
417
|
+
if (obj && obj.uuid) uuids.push(obj.uuid);
|
|
256
418
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
applyToOne(target);
|
|
419
|
+
const ev = new CustomEvent('hover-changed', { detail: { objects: list, uuids } });
|
|
420
|
+
window.dispatchEvent(ev);
|
|
421
|
+
} catch { /* ignore */ }
|
|
261
422
|
}
|
|
262
423
|
|
|
263
|
-
static
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const ud = t.userData || {};
|
|
268
|
-
if (!ud.__hoverMatApplied) return;
|
|
269
|
-
const cloneWithColor = (mat, colorHex) => {
|
|
270
|
-
if (!mat) return mat;
|
|
271
|
-
let c = mat;
|
|
424
|
+
static _clearHoverState({ emit = true } = {}) {
|
|
425
|
+
const hadHover = (SelectionFilter._hovered && SelectionFilter._hovered.size) || SelectionFilter._hoveredSourceMap.size;
|
|
426
|
+
if (SelectionFilter._hovered && SelectionFilter._hovered.size) {
|
|
427
|
+
for (const o of Array.from(SelectionFilter._hovered)) {
|
|
272
428
|
try {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
} catch {
|
|
276
|
-
return c;
|
|
277
|
-
};
|
|
278
|
-
const applySelectionMaterial = () => {
|
|
279
|
-
if (t.type === SelectionFilter.FACE) return CADmaterials.FACE?.SELECTED ?? CADmaterials.FACE ?? ud.__hoverOrigMat ?? t.material;
|
|
280
|
-
if (t.type === SelectionFilter.PLANE) return CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? ud.__hoverOrigMat ?? t.material;
|
|
281
|
-
if (t.type === SelectionFilter.EDGE) {
|
|
282
|
-
const base = ud.__hoverOrigMat ?? t.material;
|
|
283
|
-
const selColor = CADmaterials.EDGE?.SELECTED?.color || CADmaterials.EDGE?.SELECTED?.color?.getHexString?.();
|
|
284
|
-
return cloneWithColor(base, selColor || '#ff00ff');
|
|
285
|
-
}
|
|
286
|
-
if (t.type === SelectionFilter.SOLID || t.type === SelectionFilter.COMPONENT) return CADmaterials.SOLID?.SELECTED ?? ud.__hoverOrigMat ?? t.material;
|
|
287
|
-
return ud.__hoverOrigMat ?? t.material;
|
|
288
|
-
};
|
|
289
|
-
const applyDeselectedMaterial = () => {
|
|
290
|
-
if (t.type === SelectionFilter.FACE) return ud.__hoverOrigMat ?? SelectionFilter.#getBaseMaterial(t) ?? t.material;
|
|
291
|
-
if (t.type === SelectionFilter.PLANE) return ud.__hoverOrigMat ?? SelectionFilter.#getBaseMaterial(t) ?? t.material;
|
|
292
|
-
if (t.type === SelectionFilter.EDGE) return ud.__hoverOrigMat ?? SelectionFilter.#getBaseMaterial(t) ?? t.material;
|
|
293
|
-
if (t.type === SelectionFilter.SOLID || t.type === SelectionFilter.COMPONENT) return ud.__hoverOrigMat ?? t.material;
|
|
294
|
-
return ud.__hoverOrigMat ?? t.material;
|
|
295
|
-
};
|
|
296
|
-
try {
|
|
297
|
-
if (t.selected) {
|
|
298
|
-
const selMat = applySelectionMaterial();
|
|
299
|
-
if (selMat) t.material = selMat;
|
|
300
|
-
} else {
|
|
301
|
-
const baseMat = applyDeselectedMaterial();
|
|
302
|
-
if (baseMat) t.material = baseMat;
|
|
303
|
-
}
|
|
304
|
-
if (ud.__hoverMat && ud.__hoverMat !== ud.__hoverOrigMat && typeof ud.__hoverMat.dispose === 'function') ud.__hoverMat.dispose();
|
|
305
|
-
} catch { }
|
|
306
|
-
try { delete t.userData.__hoverMatApplied; } catch { }
|
|
307
|
-
try { delete t.userData.__hoverOrigMat; } catch { }
|
|
308
|
-
try { delete t.userData.__hoverMat; } catch { }
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
if (obj.type === SelectionFilter.SOLID || obj.type === SelectionFilter.COMPONENT) {
|
|
312
|
-
if (Array.isArray(obj.children)) {
|
|
313
|
-
for (const ch of obj.children) restoreOne(ch);
|
|
429
|
+
SelectionState.attach(o);
|
|
430
|
+
o.hovered = false;
|
|
431
|
+
} catch { }
|
|
314
432
|
}
|
|
433
|
+
SelectionFilter._hovered.clear();
|
|
315
434
|
}
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
435
|
+
if (SelectionFilter._hoveredSourceMap.size) {
|
|
436
|
+
SelectionFilter._hoveredSourceMap.clear();
|
|
437
|
+
}
|
|
438
|
+
if (emit && hadHover) {
|
|
439
|
+
SelectionFilter._emitHoverChanged([]);
|
|
320
440
|
}
|
|
321
|
-
restoreOne(obj);
|
|
322
441
|
}
|
|
323
442
|
|
|
324
443
|
static #isInputUsable(el) {
|
|
@@ -346,246 +465,208 @@ export class SelectionFilter {
|
|
|
346
465
|
return true;
|
|
347
466
|
}
|
|
348
467
|
|
|
349
|
-
static
|
|
350
|
-
// get the type of the object
|
|
351
|
-
const type = objectToToggleSelectionOn.type;
|
|
352
|
-
if (!type) throw new Error("Object to toggle selection on must have a type.");
|
|
353
|
-
|
|
354
|
-
// Check if a reference selection is active
|
|
468
|
+
static #handleReferenceSelection(objectToToggleSelectionOn) {
|
|
355
469
|
try {
|
|
356
470
|
let activeRefInput = document.querySelector('[active-reference-selection="true"],[active-reference-selection=true]');
|
|
357
471
|
if (!activeRefInput) {
|
|
358
472
|
try { activeRefInput = window.__BREP_activeRefInput || null; } catch (_) { /* ignore */ }
|
|
359
473
|
}
|
|
360
|
-
if (activeRefInput)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
474
|
+
if (!activeRefInput) return false;
|
|
475
|
+
|
|
476
|
+
const usable = SelectionFilter.#isInputUsable(activeRefInput);
|
|
477
|
+
if (!usable) {
|
|
478
|
+
try { activeRefInput.removeAttribute('active-reference-selection'); } catch (_) { }
|
|
479
|
+
try { activeRefInput.style.filter = 'none'; } catch (_) { }
|
|
480
|
+
try { if (window.__BREP_activeRefInput === activeRefInput) window.__BREP_activeRefInput = null; } catch (_) { }
|
|
481
|
+
SelectionFilter.restoreAllowedSelectionTypes();
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
369
484
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
485
|
+
const dataset = activeRefInput.dataset || {};
|
|
486
|
+
const isMultiRef = dataset.multiple === 'true';
|
|
487
|
+
const maxSelections = Number(dataset.maxSelections);
|
|
488
|
+
const hasMax = Number.isFinite(maxSelections) && maxSelections > 0;
|
|
489
|
+
if (isMultiRef) {
|
|
490
|
+
let currentCount = 0;
|
|
491
|
+
try {
|
|
492
|
+
if (typeof activeRefInput.__getSelectionList === 'function') {
|
|
493
|
+
const list = activeRefInput.__getSelectionList();
|
|
494
|
+
if (Array.isArray(list)) currentCount = list.length;
|
|
495
|
+
} else if (dataset.selectedCount !== undefined) {
|
|
496
|
+
const parsed = Number(dataset.selectedCount);
|
|
497
|
+
if (Number.isFinite(parsed)) currentCount = parsed;
|
|
498
|
+
}
|
|
499
|
+
} catch (_) { /* ignore */ }
|
|
500
|
+
if (hasMax && currentCount >= maxSelections) {
|
|
376
501
|
try {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
502
|
+
const wrap = activeRefInput.closest('.ref-single-wrap, .ref-multi-wrap');
|
|
503
|
+
if (wrap) {
|
|
504
|
+
wrap.classList.add('ref-limit-reached');
|
|
505
|
+
setTimeout(() => {
|
|
506
|
+
try { wrap.classList.remove('ref-limit-reached'); } catch (_) { }
|
|
507
|
+
}, 480);
|
|
383
508
|
}
|
|
384
|
-
} catch (_) {
|
|
385
|
-
|
|
386
|
-
try {
|
|
387
|
-
const wrap = activeRefInput.closest('.ref-single-wrap, .ref-multi-wrap');
|
|
388
|
-
if (wrap) {
|
|
389
|
-
wrap.classList.add('ref-limit-reached');
|
|
390
|
-
setTimeout(() => {
|
|
391
|
-
try { wrap.classList.remove('ref-limit-reached'); } catch (_) { }
|
|
392
|
-
}, 480);
|
|
393
|
-
}
|
|
394
|
-
} catch (_) { }
|
|
395
|
-
return true;
|
|
396
|
-
}
|
|
509
|
+
} catch (_) { }
|
|
510
|
+
return true;
|
|
397
511
|
}
|
|
398
|
-
|
|
399
|
-
const allowAll = allowed === SelectionFilter.ALL;
|
|
400
|
-
const priorityOrder = [
|
|
401
|
-
SelectionFilter.VERTEX,
|
|
402
|
-
SelectionFilter.EDGE,
|
|
403
|
-
SelectionFilter.FACE,
|
|
404
|
-
SelectionFilter.PLANE,
|
|
405
|
-
SelectionFilter.SOLID,
|
|
406
|
-
SelectionFilter.COMPONENT,
|
|
407
|
-
];
|
|
408
|
-
const allowedHas = (t) => !!(allowed && typeof allowed.has === 'function' && allowed.has(t));
|
|
409
|
-
const allowedPriority = allowAll ? priorityOrder : priorityOrder.filter(t => allowedHas(t));
|
|
410
|
-
|
|
411
|
-
// Helper: find first descendant matching a type
|
|
412
|
-
const findDescendantOfType = (root, desired) => {
|
|
413
|
-
if (!root || !desired) return null;
|
|
414
|
-
let found = null;
|
|
415
|
-
try {
|
|
416
|
-
root.traverse?.((ch) => {
|
|
417
|
-
if (!found && ch && ch.type === desired) found = ch;
|
|
418
|
-
});
|
|
419
|
-
} catch (_) { /* ignore */ }
|
|
420
|
-
return found;
|
|
421
|
-
};
|
|
512
|
+
}
|
|
422
513
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
514
|
+
const allowed = SelectionFilter.allowedSelectionTypes;
|
|
515
|
+
const allowAll = allowed === SelectionFilter.ALL;
|
|
516
|
+
const priorityOrder = [
|
|
517
|
+
SelectionFilter.VERTEX,
|
|
518
|
+
SelectionFilter.EDGE,
|
|
519
|
+
SelectionFilter.FACE,
|
|
520
|
+
SelectionFilter.PLANE,
|
|
521
|
+
SelectionFilter.SKETCH,
|
|
522
|
+
SelectionFilter.LOOP,
|
|
523
|
+
SelectionFilter.SOLID,
|
|
524
|
+
SelectionFilter.COMPONENT,
|
|
525
|
+
];
|
|
526
|
+
const allowedHas = (t) => !!(allowed && typeof allowed.has === 'function' && allowed.has(t));
|
|
527
|
+
const allowedPriority = allowAll ? priorityOrder : priorityOrder.filter(t => allowedHas(t));
|
|
528
|
+
|
|
529
|
+
const findDescendantOfType = (root, desired) => {
|
|
530
|
+
if (!root || !desired) return null;
|
|
531
|
+
let found = null;
|
|
532
|
+
try {
|
|
533
|
+
root.traverse?.((ch) => {
|
|
534
|
+
if (!found && ch && ch.type === desired) found = ch;
|
|
535
|
+
});
|
|
536
|
+
} catch (_) { /* ignore */ }
|
|
537
|
+
return found;
|
|
538
|
+
};
|
|
442
539
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (!targetObj) {
|
|
449
|
-
targetObj = pickByTypeList(allowedPriority);
|
|
450
|
-
}
|
|
451
|
-
if (!targetObj && !allowAll && allowed && typeof allowed[Symbol.iterator] === 'function') {
|
|
452
|
-
targetObj = pickByTypeList(Array.from(allowed));
|
|
540
|
+
const findAncestorOfType = (obj, desired) => {
|
|
541
|
+
let cur = obj;
|
|
542
|
+
while (cur && cur.parent) {
|
|
543
|
+
if (cur.type === desired) return cur;
|
|
544
|
+
cur = cur.parent;
|
|
453
545
|
}
|
|
454
|
-
|
|
455
|
-
|
|
546
|
+
return null;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const pickByTypeList = (typeList) => {
|
|
550
|
+
if (!Array.isArray(typeList)) return null;
|
|
551
|
+
for (const desired of typeList) {
|
|
552
|
+
if (!desired) continue;
|
|
553
|
+
if (objectToToggleSelectionOn?.type === desired) return objectToToggleSelectionOn;
|
|
554
|
+
let picked = findDescendantOfType(objectToToggleSelectionOn, desired);
|
|
555
|
+
if (!picked) picked = findAncestorOfType(objectToToggleSelectionOn, desired);
|
|
556
|
+
if (picked) return picked;
|
|
456
557
|
}
|
|
457
|
-
|
|
558
|
+
return null;
|
|
559
|
+
};
|
|
458
560
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const parsed = JSON.parse(data.selectedValues);
|
|
474
|
-
if (Array.isArray(parsed)) values = parsed;
|
|
475
|
-
} catch (_) { /* ignore */ }
|
|
476
|
-
}
|
|
477
|
-
if (!Array.isArray(values) && typeof inputEl?.__getSelectionList === 'function') {
|
|
478
|
-
try {
|
|
479
|
-
const list = inputEl.__getSelectionList();
|
|
480
|
-
if (Array.isArray(list)) values = list.slice();
|
|
481
|
-
} catch (_) { /* ignore */ }
|
|
482
|
-
}
|
|
483
|
-
let count = 0;
|
|
484
|
-
if (Array.isArray(values)) {
|
|
485
|
-
count = values.length;
|
|
486
|
-
} else if (data.selectedCount !== undefined) {
|
|
487
|
-
const parsed = Number(data.selectedCount);
|
|
488
|
-
if (Number.isFinite(parsed) && parsed >= 0) count = parsed;
|
|
489
|
-
}
|
|
490
|
-
return count;
|
|
491
|
-
};
|
|
561
|
+
let targetObj = null;
|
|
562
|
+
if (allowAll || allowedHas(objectToToggleSelectionOn?.type)) {
|
|
563
|
+
targetObj = objectToToggleSelectionOn;
|
|
564
|
+
}
|
|
565
|
+
if (!targetObj) {
|
|
566
|
+
targetObj = pickByTypeList(allowedPriority);
|
|
567
|
+
}
|
|
568
|
+
if (!targetObj && !allowAll && allowed && typeof allowed[Symbol.iterator] === 'function') {
|
|
569
|
+
targetObj = pickByTypeList(Array.from(allowed));
|
|
570
|
+
}
|
|
571
|
+
if (!targetObj && allowAll) {
|
|
572
|
+
targetObj = objectToToggleSelectionOn;
|
|
573
|
+
}
|
|
574
|
+
if (!targetObj) return false;
|
|
492
575
|
|
|
493
|
-
|
|
494
|
-
activeRefInput.
|
|
495
|
-
|
|
576
|
+
try {
|
|
577
|
+
if (activeRefInput && typeof activeRefInput.__captureReferencePreview === 'function') {
|
|
578
|
+
activeRefInput.__captureReferencePreview(targetObj);
|
|
579
|
+
}
|
|
580
|
+
} catch (_) { /* ignore preview capture errors */ }
|
|
496
581
|
|
|
497
|
-
|
|
498
|
-
|
|
582
|
+
const objType = targetObj.type;
|
|
583
|
+
const objectName = targetObj.name || `${objType}(${targetObj.position?.x || 0},${targetObj.position?.y || 0},${targetObj.position?.z || 0})`;
|
|
499
584
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
585
|
+
const snapshotSelections = (inputEl) => {
|
|
586
|
+
const data = inputEl?.dataset || {};
|
|
587
|
+
let values = null;
|
|
588
|
+
if (data.selectedValues) {
|
|
504
589
|
try {
|
|
505
|
-
const
|
|
506
|
-
if (
|
|
507
|
-
} catch (_) { }
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
try { if (window.__BREP_activeRefInput === activeRefInput) window.__BREP_activeRefInput = null; } catch (_) { }
|
|
511
|
-
} else {
|
|
512
|
-
activeRefInput.setAttribute('active-reference-selection', 'true');
|
|
513
|
-
activeRefInput.style.filter = 'invert(1)';
|
|
590
|
+
const parsed = JSON.parse(data.selectedValues);
|
|
591
|
+
if (Array.isArray(parsed)) values = parsed;
|
|
592
|
+
} catch (_) { /* ignore */ }
|
|
593
|
+
}
|
|
594
|
+
if (!Array.isArray(values) && typeof inputEl?.__getSelectionList === 'function') {
|
|
514
595
|
try {
|
|
515
|
-
const
|
|
516
|
-
if (
|
|
517
|
-
} catch (_) { }
|
|
518
|
-
|
|
596
|
+
const list = inputEl.__getSelectionList();
|
|
597
|
+
if (Array.isArray(list)) values = list.slice();
|
|
598
|
+
} catch (_) { /* ignore */ }
|
|
599
|
+
}
|
|
600
|
+
let count = 0;
|
|
601
|
+
if (Array.isArray(values)) {
|
|
602
|
+
count = values.length;
|
|
603
|
+
} else if (data.selectedCount !== undefined) {
|
|
604
|
+
const parsed = Number(data.selectedCount);
|
|
605
|
+
if (Number.isFinite(parsed) && parsed >= 0) count = parsed;
|
|
519
606
|
}
|
|
520
|
-
return
|
|
607
|
+
return count;
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
activeRefInput.value = objectName;
|
|
611
|
+
activeRefInput.dispatchEvent(new Event('change'));
|
|
612
|
+
const afterSelectionCount = snapshotSelections(activeRefInput);
|
|
613
|
+
|
|
614
|
+
const didReachLimit = isMultiRef && hasMax && afterSelectionCount >= maxSelections;
|
|
615
|
+
const keepActive = isMultiRef && !didReachLimit;
|
|
616
|
+
|
|
617
|
+
if (!keepActive) {
|
|
618
|
+
activeRefInput.removeAttribute('active-reference-selection');
|
|
619
|
+
activeRefInput.style.filter = 'none';
|
|
620
|
+
try {
|
|
621
|
+
const wrap = activeRefInput.closest('.ref-single-wrap, .ref-multi-wrap');
|
|
622
|
+
if (wrap) wrap.classList.remove('ref-active');
|
|
623
|
+
} catch (_) { }
|
|
624
|
+
SelectionFilter.restoreAllowedSelectionTypes();
|
|
625
|
+
try { if (window.__BREP_activeRefInput === activeRefInput) window.__BREP_activeRefInput = null; } catch (_) { }
|
|
626
|
+
} else {
|
|
627
|
+
activeRefInput.setAttribute('active-reference-selection', 'true');
|
|
628
|
+
activeRefInput.style.filter = 'invert(1)';
|
|
629
|
+
try {
|
|
630
|
+
const wrap = activeRefInput.closest('.ref-single-wrap, .ref-multi-wrap');
|
|
631
|
+
if (wrap) wrap.classList.add('ref-active');
|
|
632
|
+
} catch (_) { }
|
|
633
|
+
try { window.__BREP_activeRefInput = activeRefInput; } catch (_) { }
|
|
521
634
|
}
|
|
635
|
+
return true;
|
|
522
636
|
} catch (error) {
|
|
523
|
-
|
|
637
|
+
if (debugMode) {
|
|
638
|
+
console.warn("Error handling reference selection:", error);
|
|
639
|
+
}
|
|
640
|
+
return false;
|
|
524
641
|
}
|
|
642
|
+
}
|
|
525
643
|
|
|
644
|
+
static #toggleStandardSelection(objectToToggleSelectionOn) {
|
|
645
|
+
const type = objectToToggleSelectionOn.type;
|
|
526
646
|
let parentSelectedAction = false;
|
|
527
|
-
// check if the object is selectable and if it is toggle the .selected atribute on the object.
|
|
528
|
-
// Allow toggling off even if type is currently disallowed; only block new selections
|
|
529
647
|
if (SelectionFilter.IsAllowed(type) || objectToToggleSelectionOn.selected === true) {
|
|
648
|
+
SelectionState.attach(objectToToggleSelectionOn);
|
|
530
649
|
objectToToggleSelectionOn.selected = !objectToToggleSelectionOn.selected;
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if (objectToToggleSelectionOn.type === SelectionFilter.FACE) {
|
|
534
|
-
objectToToggleSelectionOn.material = CADmaterials.FACE?.SELECTED ?? CADmaterials.FACE;
|
|
535
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.PLANE) {
|
|
536
|
-
objectToToggleSelectionOn.material = CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? objectToToggleSelectionOn.material;
|
|
537
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.EDGE) {
|
|
538
|
-
objectToToggleSelectionOn.material = CADmaterials.EDGE?.SELECTED ?? CADmaterials.EDGE;
|
|
539
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.VERTEX) {
|
|
540
|
-
// Vertex visuals are handled by its selected accessor (point + sphere)
|
|
541
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.SOLID || objectToToggleSelectionOn.type === SelectionFilter.COMPONENT) {
|
|
542
|
-
parentSelectedAction = true;
|
|
543
|
-
objectToToggleSelectionOn.children.forEach(child => {
|
|
544
|
-
// apply selected material based on object type for faces and edges
|
|
545
|
-
if (child.type === SelectionFilter.FACE) child.material = CADmaterials.FACE?.SELECTED ?? CADmaterials.FACE;
|
|
546
|
-
if (child.type === SelectionFilter.PLANE) child.material = CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? child.material;
|
|
547
|
-
if (child.type === SelectionFilter.EDGE) child.material = CADmaterials.EDGE?.SELECTED ?? CADmaterials.EDGE;
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
} else {
|
|
552
|
-
if (objectToToggleSelectionOn.type === SelectionFilter.FACE) {
|
|
553
|
-
objectToToggleSelectionOn.material = SelectionFilter.#getBaseMaterial(objectToToggleSelectionOn) ?? objectToToggleSelectionOn.material;
|
|
554
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.PLANE) {
|
|
555
|
-
objectToToggleSelectionOn.material = SelectionFilter.#getBaseMaterial(objectToToggleSelectionOn) ?? objectToToggleSelectionOn.material;
|
|
556
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.EDGE) {
|
|
557
|
-
objectToToggleSelectionOn.material = SelectionFilter.#getBaseMaterial(objectToToggleSelectionOn) ?? objectToToggleSelectionOn.material;
|
|
558
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.VERTEX) {
|
|
559
|
-
// Vertex accessor handles its own visual reset
|
|
560
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.SOLID || objectToToggleSelectionOn.type === SelectionFilter.COMPONENT) {
|
|
561
|
-
parentSelectedAction = true;
|
|
562
|
-
objectToToggleSelectionOn.children.forEach(child => {
|
|
563
|
-
// apply selected material based on object type for faces and edges
|
|
564
|
-
if (child.type === SelectionFilter.FACE) child.material = child.selected ? CADmaterials.FACE?.SELECTED ?? CADmaterials.FACE : (SelectionFilter.#getBaseMaterial(child) ?? child.material);
|
|
565
|
-
if (child.type === SelectionFilter.PLANE) child.material = child.selected ? (CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? child.material) : (SelectionFilter.#getBaseMaterial(child) ?? child.material);
|
|
566
|
-
if (child.type === SelectionFilter.EDGE) child.material = child.selected ? CADmaterials.EDGE?.SELECTED ?? CADmaterials.EDGE : (SelectionFilter.#getBaseMaterial(child) ?? child.material);
|
|
567
|
-
});
|
|
568
|
-
}
|
|
650
|
+
if (type === SelectionFilter.SOLID || type === SelectionFilter.COMPONENT) {
|
|
651
|
+
parentSelectedAction = true;
|
|
569
652
|
}
|
|
570
653
|
SelectionFilter._emitSelectionChanged();
|
|
571
654
|
}
|
|
572
|
-
|
|
573
655
|
return parentSelectedAction;
|
|
574
656
|
}
|
|
575
657
|
|
|
658
|
+
static toggleSelection(objectToToggleSelectionOn) {
|
|
659
|
+
const type = objectToToggleSelectionOn.type;
|
|
660
|
+
if (!type) throw new Error("Object to toggle selection on must have a type.");
|
|
661
|
+
if (SelectionFilter.#handleReferenceSelection(objectToToggleSelectionOn)) return true;
|
|
662
|
+
return SelectionFilter.#toggleStandardSelection(objectToToggleSelectionOn);
|
|
663
|
+
}
|
|
664
|
+
|
|
576
665
|
static unselectAll(scene) {
|
|
577
666
|
// itterate over all children and nested children of the scene and set the .selected atribute to false.
|
|
578
667
|
scene.traverse((child) => {
|
|
668
|
+
SelectionState.attach(child);
|
|
579
669
|
child.selected = false;
|
|
580
|
-
// reset material to base
|
|
581
|
-
if (child.type === SelectionFilter.FACE) {
|
|
582
|
-
child.material = SelectionFilter.#getBaseMaterial(child) ?? child.material;
|
|
583
|
-
} else if (child.type === SelectionFilter.PLANE) {
|
|
584
|
-
child.material = SelectionFilter.#getBaseMaterial(child) ?? child.material;
|
|
585
|
-
} else if (child.type === SelectionFilter.EDGE) {
|
|
586
|
-
child.material = SelectionFilter.#getBaseMaterial(child) ?? child.material;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
670
|
});
|
|
590
671
|
SelectionFilter._emitSelectionChanged();
|
|
591
672
|
}
|
|
@@ -593,17 +674,8 @@ export class SelectionFilter {
|
|
|
593
674
|
static selectItem(scene, itemName) {
|
|
594
675
|
scene.traverse((child) => {
|
|
595
676
|
if (child && child.name === itemName) {
|
|
677
|
+
SelectionState.attach(child);
|
|
596
678
|
child.selected = true;
|
|
597
|
-
// change material to selected
|
|
598
|
-
if (child.type === SelectionFilter.FACE) {
|
|
599
|
-
child.material = CADmaterials.FACE?.SELECTED ?? CADmaterials.FACE;
|
|
600
|
-
} else if (child.type === SelectionFilter.PLANE) {
|
|
601
|
-
child.material = CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? child.material;
|
|
602
|
-
} else if (child.type === SelectionFilter.EDGE) {
|
|
603
|
-
child.material = CADmaterials.EDGE?.SELECTED ?? CADmaterials.EDGE;
|
|
604
|
-
} else if (child.type === SelectionFilter.SOLID || child.type === SelectionFilter.COMPONENT) {
|
|
605
|
-
child.material = CADmaterials.SOLID?.SELECTED ?? CADmaterials.SOLID;
|
|
606
|
-
}
|
|
607
679
|
}
|
|
608
680
|
});
|
|
609
681
|
SelectionFilter._emitSelectionChanged();
|
|
@@ -613,37 +685,36 @@ export class SelectionFilter {
|
|
|
613
685
|
// Traverse scene and deselect a single item by name, updating materials appropriately
|
|
614
686
|
scene.traverse((child) => {
|
|
615
687
|
if (child.name === itemName) {
|
|
688
|
+
SelectionState.attach(child);
|
|
616
689
|
child.selected = false;
|
|
617
|
-
if (child.type === SelectionFilter.FACE) {
|
|
618
|
-
child.material = SelectionFilter.#getBaseMaterial(child) ?? child.material;
|
|
619
|
-
} else if (child.type === SelectionFilter.PLANE) {
|
|
620
|
-
child.material = SelectionFilter.#getBaseMaterial(child) ?? child.material;
|
|
621
|
-
} else if (child.type === SelectionFilter.EDGE) {
|
|
622
|
-
child.material = SelectionFilter.#getBaseMaterial(child) ?? child.material;
|
|
623
|
-
} else if (child.type === SelectionFilter.SOLID || child.type === SelectionFilter.COMPONENT) {
|
|
624
|
-
// For solids, keep children materials consistent with their own selected flags
|
|
625
|
-
child.children.forEach(grandchild => {
|
|
626
|
-
if (grandchild.type === SelectionFilter.FACE) {
|
|
627
|
-
grandchild.material = grandchild.selected ? (CADmaterials.FACE?.SELECTED ?? CADmaterials.FACE) : (SelectionFilter.#getBaseMaterial(grandchild) ?? grandchild.material);
|
|
628
|
-
}
|
|
629
|
-
if (grandchild.type === SelectionFilter.PLANE) {
|
|
630
|
-
grandchild.material = grandchild.selected ? (CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? grandchild.material) : (SelectionFilter.#getBaseMaterial(grandchild) ?? grandchild.material);
|
|
631
|
-
}
|
|
632
|
-
if (grandchild.type === SelectionFilter.EDGE) {
|
|
633
|
-
grandchild.material = grandchild.selected ? (CADmaterials.EDGE?.SELECTED ?? CADmaterials.EDGE) : (SelectionFilter.#getBaseMaterial(grandchild) ?? grandchild.material);
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
690
|
}
|
|
638
691
|
});
|
|
639
692
|
SelectionFilter._emitSelectionChanged();
|
|
640
693
|
}
|
|
641
694
|
|
|
642
695
|
static set uiCallback(callback) { SelectionFilter._uiCallback = callback; }
|
|
643
|
-
static triggerUI() {
|
|
696
|
+
static triggerUI() {
|
|
697
|
+
if (SelectionFilter._uiCallback) SelectionFilter._uiCallback();
|
|
698
|
+
try { SelectionFilter._updateSelectionFilterIndicator(); } catch (_) { }
|
|
699
|
+
}
|
|
644
700
|
|
|
645
701
|
// Emit a global event so UI can react without polling
|
|
646
702
|
static _emitSelectionChanged() {
|
|
703
|
+
try {
|
|
704
|
+
const selection = SelectionFilter.getSelectedObjects();
|
|
705
|
+
const names = selection.map((obj) => (
|
|
706
|
+
obj?.name
|
|
707
|
+
|| obj?.userData?.faceName
|
|
708
|
+
|| obj?.userData?.edgeName
|
|
709
|
+
|| obj?.userData?.vertexName
|
|
710
|
+
|| obj?.userData?.solidName
|
|
711
|
+
|| obj?.userData?.name
|
|
712
|
+
|| obj?.type
|
|
713
|
+
|| 'Object'
|
|
714
|
+
));
|
|
715
|
+
const desc = names.length ? names.join(', ') : '(none)';
|
|
716
|
+
console.log(`[SelectionFilter] selection changed -> ${desc}`);
|
|
717
|
+
} catch { /* noop */ }
|
|
647
718
|
try {
|
|
648
719
|
const ev = new CustomEvent('selection-changed');
|
|
649
720
|
window.dispatchEvent(ev);
|
|
@@ -802,6 +873,7 @@ export class SelectionFilter {
|
|
|
802
873
|
static _getHistoryContextActionSpecs(selection, viewer) {
|
|
803
874
|
const out = [];
|
|
804
875
|
const items = Array.isArray(selection) ? selection : [];
|
|
876
|
+
const suppressFeatureButtons = SelectionFilter._hasAssemblyComponentSelection(items, viewer);
|
|
805
877
|
const safeId = (prefix, key) => {
|
|
806
878
|
const raw = String(key || '').toLowerCase().replace(/[^a-z0-9_-]+/g, '-');
|
|
807
879
|
return `${prefix}-${raw || 'item'}`;
|
|
@@ -810,49 +882,54 @@ export class SelectionFilter {
|
|
|
810
882
|
if (spec && spec.id) out.push(spec);
|
|
811
883
|
};
|
|
812
884
|
|
|
813
|
-
const
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
885
|
+
const pmimode = viewer?._pmiMode || null;
|
|
886
|
+
const pmiActive = !!pmimode;
|
|
887
|
+
if (!pmiActive) {
|
|
888
|
+
const featureRegistry = viewer?.partHistory?.featureRegistry || null;
|
|
889
|
+
const features = Array.isArray(featureRegistry?.features) ? featureRegistry.features : [];
|
|
890
|
+
if (!suppressFeatureButtons) {
|
|
891
|
+
for (const FeatureClass of features) {
|
|
892
|
+
if (!FeatureClass) continue;
|
|
893
|
+
let result = null;
|
|
894
|
+
try { result = FeatureClass.showContexButton?.(items); } catch { result = null; }
|
|
895
|
+
if (!result) continue;
|
|
896
|
+
if (result && typeof result === 'object' && result.show === false) continue;
|
|
897
|
+
const label = (result && typeof result === 'object' && result.label) || FeatureClass.longName || FeatureClass.shortName || FeatureClass.name || 'Feature';
|
|
898
|
+
const typeKey = FeatureClass.shortName || FeatureClass.type || FeatureClass.name || label;
|
|
899
|
+
const params = SelectionFilter._extractContextParams(result);
|
|
900
|
+
addSpec({
|
|
901
|
+
id: safeId('ctx-feature', typeKey),
|
|
902
|
+
label,
|
|
903
|
+
title: `Create ${label}`,
|
|
904
|
+
onClick: () => SelectionFilter._createFeatureFromContext(viewer, typeKey, params),
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
}
|
|
831
908
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
909
|
+
const constraintRegistry = viewer?.partHistory?.assemblyConstraintRegistry || null;
|
|
910
|
+
const constraintClasses = typeof constraintRegistry?.listAvailable === 'function'
|
|
911
|
+
? constraintRegistry.listAvailable()
|
|
912
|
+
: (typeof constraintRegistry?.list === 'function' ? constraintRegistry.list() : []);
|
|
913
|
+
if (Array.isArray(constraintClasses)) {
|
|
914
|
+
for (const ConstraintClass of constraintClasses) {
|
|
915
|
+
if (!ConstraintClass) continue;
|
|
916
|
+
let result = null;
|
|
917
|
+
try { result = ConstraintClass.showContexButton?.(items); } catch { result = null; }
|
|
918
|
+
if (!result) continue;
|
|
919
|
+
if (result && typeof result === 'object' && result.show === false) continue;
|
|
920
|
+
const label = (result && typeof result === 'object' && result.label) || ConstraintClass.longName || ConstraintClass.shortName || ConstraintClass.name || 'Constraint';
|
|
921
|
+
const typeKey = ConstraintClass.constraintType || ConstraintClass.shortName || ConstraintClass.name || label;
|
|
922
|
+
const params = SelectionFilter._extractContextParams(result);
|
|
923
|
+
addSpec({
|
|
924
|
+
id: safeId('ctx-constraint', typeKey),
|
|
925
|
+
label,
|
|
926
|
+
title: `Create ${label}`,
|
|
927
|
+
onClick: () => SelectionFilter._createConstraintFromContext(viewer, typeKey, params),
|
|
928
|
+
});
|
|
929
|
+
}
|
|
852
930
|
}
|
|
853
931
|
}
|
|
854
932
|
|
|
855
|
-
const pmimode = viewer?._pmiMode || null;
|
|
856
933
|
const annotationRegistry = viewer?.annotationRegistry || null;
|
|
857
934
|
if (pmimode && annotationRegistry && typeof annotationRegistry.list === 'function') {
|
|
858
935
|
const annClasses = annotationRegistry.list();
|
|
@@ -877,6 +954,28 @@ export class SelectionFilter {
|
|
|
877
954
|
return out;
|
|
878
955
|
}
|
|
879
956
|
|
|
957
|
+
static _hasAssemblyComponentSelection(items, viewer) {
|
|
958
|
+
if (!Array.isArray(items) || !items.length) return false;
|
|
959
|
+
const findComponent = (obj) => {
|
|
960
|
+
if (!obj) return null;
|
|
961
|
+
if (viewer && typeof viewer._findOwningComponent === 'function') {
|
|
962
|
+
try { return viewer._findOwningComponent(obj); } catch { /* ignore */ }
|
|
963
|
+
}
|
|
964
|
+
let cur = obj;
|
|
965
|
+
while (cur) {
|
|
966
|
+
if (cur.isAssemblyComponent || cur.type === SelectionFilter.COMPONENT || cur.type === 'COMPONENT') return cur;
|
|
967
|
+
cur = cur.parent || null;
|
|
968
|
+
}
|
|
969
|
+
return null;
|
|
970
|
+
};
|
|
971
|
+
for (const item of items) {
|
|
972
|
+
const obj = item?.object || item?.target || item;
|
|
973
|
+
if (!obj) continue;
|
|
974
|
+
if (findComponent(obj)) return true;
|
|
975
|
+
}
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
|
|
880
979
|
static async _createFeatureFromContext(viewer, typeKey, params = null) {
|
|
881
980
|
if (!viewer || !typeKey) return;
|
|
882
981
|
SelectionFilter.setContextBarSuppressed('context-create', true);
|
|
@@ -1038,6 +1137,371 @@ export class SelectionFilter {
|
|
|
1038
1137
|
return bar;
|
|
1039
1138
|
}
|
|
1040
1139
|
|
|
1140
|
+
static _getSelectionFilterTypeList() {
|
|
1141
|
+
if (!SelectionFilter._selectionFilterTypes) {
|
|
1142
|
+
SelectionFilter._selectionFilterTypes = SelectionFilter.TYPES.filter((t) => t !== SelectionFilter.ALL);
|
|
1143
|
+
}
|
|
1144
|
+
return SelectionFilter._selectionFilterTypes;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
static _getSelectionFilterLabel(type) {
|
|
1148
|
+
const labels = {
|
|
1149
|
+
SOLID: 'Solid',
|
|
1150
|
+
COMPONENT: 'Component',
|
|
1151
|
+
FACE: 'Face',
|
|
1152
|
+
PLANE: 'Plane',
|
|
1153
|
+
SKETCH: 'Sketch',
|
|
1154
|
+
DATUM: 'Datum',
|
|
1155
|
+
HELIX: 'Helix',
|
|
1156
|
+
EDGE: 'Edge',
|
|
1157
|
+
LOOP: 'Loop',
|
|
1158
|
+
VERTEX: 'Vertex',
|
|
1159
|
+
};
|
|
1160
|
+
return labels[type] || type;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
static _summarizeSelectionFilter(types) {
|
|
1164
|
+
const list = Array.isArray(types) ? types : [];
|
|
1165
|
+
const allTypes = SelectionFilter._getSelectionFilterTypeList();
|
|
1166
|
+
if (list.length === 0) return 'None';
|
|
1167
|
+
if (list.length === allTypes.length) return 'All';
|
|
1168
|
+
return list.map((t) => SelectionFilter._getSelectionFilterLabel(t)).join(', ');
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
static _getAllowedTypeList() {
|
|
1172
|
+
const allTypes = SelectionFilter._getSelectionFilterTypeList();
|
|
1173
|
+
if (SelectionFilter.allowedSelectionTypes === SelectionFilter.ALL) return allTypes.slice();
|
|
1174
|
+
const allowed = new Set(Array.from(SelectionFilter.allowedSelectionTypes || []));
|
|
1175
|
+
return allTypes.filter((t) => allowed.has(t));
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
static _updateSelectionFilterIndicator() {
|
|
1179
|
+
const wrap = SelectionFilter._selectionFilterIndicator;
|
|
1180
|
+
if (!wrap) return;
|
|
1181
|
+
const toggle = SelectionFilter._selectionFilterIndicatorToggle;
|
|
1182
|
+
const types = SelectionFilter._getAllowedTypeList();
|
|
1183
|
+
if (SelectionFilter._selectionFilterCheckboxes && SelectionFilter._selectionFilterCheckboxes.size) {
|
|
1184
|
+
const set = new Set(types);
|
|
1185
|
+
for (const [type, cb] of SelectionFilter._selectionFilterCheckboxes.entries()) {
|
|
1186
|
+
if (cb) cb.checked = set.has(type);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
if (toggle) {
|
|
1190
|
+
toggle.textContent = `Selection filter: ${SelectionFilter._summarizeSelectionFilter(types)}`;
|
|
1191
|
+
}
|
|
1192
|
+
SelectionFilter._updateSelectableTintButton();
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
static _getSelectableTintTargets() {
|
|
1196
|
+
const allowed = SelectionFilter.allowedSelectionTypes;
|
|
1197
|
+
const allowAll = allowed === SelectionFilter.ALL;
|
|
1198
|
+
const allowFace = allowAll || (allowed && typeof allowed.has === 'function' && allowed.has(SelectionFilter.FACE));
|
|
1199
|
+
const allowEdge = allowAll || (allowed && typeof allowed.has === 'function' && allowed.has(SelectionFilter.EDGE));
|
|
1200
|
+
return { allowFace, allowEdge };
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
static _updateSelectableTintButton() {
|
|
1204
|
+
const btn = SelectionFilter._selectionFilterTintBtn;
|
|
1205
|
+
if (!btn) return;
|
|
1206
|
+
const state = SelectionFilter._selectableTintState;
|
|
1207
|
+
const active = !!state?.active;
|
|
1208
|
+
const { allowFace, allowEdge } = SelectionFilter._getSelectableTintTargets();
|
|
1209
|
+
const hasTargets = allowFace || allowEdge;
|
|
1210
|
+
const colors = Array.isArray(state?.colors) && state.colors.length ? state.colors : ['#60a5fa'];
|
|
1211
|
+
const nextColor = colors[(state?.colorIndex ?? 0) % colors.length] || '#60a5fa';
|
|
1212
|
+
const displayColor = active ? (state?.activeColor || nextColor) : nextColor;
|
|
1213
|
+
btn.classList.toggle('is-active', active);
|
|
1214
|
+
btn.style.setProperty('--sfi-tint', displayColor);
|
|
1215
|
+
btn.textContent = active ? 'Reset selectable tint' : 'Tint selectable';
|
|
1216
|
+
btn.disabled = !active && !hasTargets;
|
|
1217
|
+
btn.title = active
|
|
1218
|
+
? 'Restore original face/edge colors'
|
|
1219
|
+
: (hasTargets ? 'Tint selectable faces and edges' : 'Enable Face or Edge selection to tint');
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
static _applySelectableTint(scene, { allowFace, allowEdge, faceColor, edgeColor }) {
|
|
1223
|
+
if (!scene || (!allowFace && !allowEdge)) return;
|
|
1224
|
+
const state = SelectionFilter._selectableTintState;
|
|
1225
|
+
const storeColor = (mat) => {
|
|
1226
|
+
if (!mat || !mat.color || typeof mat.color.getHexString !== 'function') return;
|
|
1227
|
+
if (state.materials.has(mat)) return;
|
|
1228
|
+
try { state.materials.set(mat, `#${mat.color.getHexString()}`); } catch { }
|
|
1229
|
+
};
|
|
1230
|
+
const tintMaterial = (mat, color) => {
|
|
1231
|
+
if (!mat || !mat.color || typeof mat.color.set !== 'function') return;
|
|
1232
|
+
storeColor(mat);
|
|
1233
|
+
try { mat.color.set(color); } catch { }
|
|
1234
|
+
try { mat.needsUpdate = true; } catch { }
|
|
1235
|
+
};
|
|
1236
|
+
const tintObject = (obj, color) => {
|
|
1237
|
+
if (!obj || obj.visible === false) return;
|
|
1238
|
+
if (obj.selected === true) return;
|
|
1239
|
+
const mat = obj.material;
|
|
1240
|
+
if (Array.isArray(mat)) {
|
|
1241
|
+
for (const m of mat) tintMaterial(m, color);
|
|
1242
|
+
} else {
|
|
1243
|
+
tintMaterial(mat, color);
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
1246
|
+
const isPreview = (obj) => {
|
|
1247
|
+
if (!obj) return true;
|
|
1248
|
+
if (obj.userData?.refPreview) return true;
|
|
1249
|
+
const name = typeof obj.name === 'string' ? obj.name : '';
|
|
1250
|
+
const type = typeof obj.type === 'string' ? obj.type : '';
|
|
1251
|
+
if (name.startsWith('__refPreview__')) return true;
|
|
1252
|
+
if (type.startsWith('REF_PREVIEW')) return true;
|
|
1253
|
+
return false;
|
|
1254
|
+
};
|
|
1255
|
+
scene.traverse((obj) => {
|
|
1256
|
+
if (!obj || isPreview(obj)) return;
|
|
1257
|
+
if (allowFace && obj.type === SelectionFilter.FACE) {
|
|
1258
|
+
tintObject(obj, faceColor);
|
|
1259
|
+
} else if (allowEdge && obj.type === SelectionFilter.EDGE) {
|
|
1260
|
+
tintObject(obj, edgeColor);
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
static _restoreSelectableTint() {
|
|
1266
|
+
const state = SelectionFilter._selectableTintState;
|
|
1267
|
+
if (!state || !state.materials) return;
|
|
1268
|
+
for (const [mat, color] of state.materials.entries()) {
|
|
1269
|
+
if (!mat || !mat.color || typeof mat.color.set !== 'function') continue;
|
|
1270
|
+
if (!color) continue;
|
|
1271
|
+
try { mat.color.set(color); } catch { }
|
|
1272
|
+
try { mat.needsUpdate = true; } catch { }
|
|
1273
|
+
}
|
|
1274
|
+
state.materials.clear();
|
|
1275
|
+
state.active = false;
|
|
1276
|
+
state.activeColor = null;
|
|
1277
|
+
SelectionFilter._updateSelectableTintButton();
|
|
1278
|
+
try { SelectionFilter.viewer?.render?.(); } catch { }
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
static _toggleSelectableTint(viewer) {
|
|
1282
|
+
const state = SelectionFilter._selectableTintState;
|
|
1283
|
+
if (!state) return;
|
|
1284
|
+
if (state.active) {
|
|
1285
|
+
SelectionFilter._restoreSelectableTint();
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
const { allowFace, allowEdge } = SelectionFilter._getSelectableTintTargets();
|
|
1289
|
+
if (!allowFace && !allowEdge) return;
|
|
1290
|
+
const scene = viewer?.partHistory?.scene || viewer?.scene || SelectionFilter.viewer?.partHistory?.scene || SelectionFilter.viewer?.scene || null;
|
|
1291
|
+
if (!scene) return;
|
|
1292
|
+
const colors = Array.isArray(state.colors) && state.colors.length ? state.colors : ['#60a5fa'];
|
|
1293
|
+
const color = colors[state.colorIndex % colors.length] || '#60a5fa';
|
|
1294
|
+
state.colorIndex = (state.colorIndex + 1) % colors.length;
|
|
1295
|
+
SelectionFilter._applySelectableTint(scene, {
|
|
1296
|
+
allowFace,
|
|
1297
|
+
allowEdge,
|
|
1298
|
+
faceColor: color,
|
|
1299
|
+
edgeColor: color,
|
|
1300
|
+
});
|
|
1301
|
+
state.active = true;
|
|
1302
|
+
state.activeColor = color;
|
|
1303
|
+
SelectionFilter._updateSelectableTintButton();
|
|
1304
|
+
try { (viewer || SelectionFilter.viewer)?.render?.(); } catch { }
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
static _ensureSelectionFilterIndicator(viewer) {
|
|
1308
|
+
if (SelectionFilter._selectionFilterIndicator && SelectionFilter._selectionFilterIndicator.isConnected) {
|
|
1309
|
+
SelectionFilter._updateSelectionFilterIndicator();
|
|
1310
|
+
return SelectionFilter._selectionFilterIndicator;
|
|
1311
|
+
}
|
|
1312
|
+
if (typeof document === 'undefined') return null;
|
|
1313
|
+
const host = viewer?.container || document.body || null;
|
|
1314
|
+
if (!host) return null;
|
|
1315
|
+
try {
|
|
1316
|
+
if (!document.getElementById('selection-filter-indicator-styles')) {
|
|
1317
|
+
const style = document.createElement('style');
|
|
1318
|
+
style.id = 'selection-filter-indicator-styles';
|
|
1319
|
+
style.textContent = `
|
|
1320
|
+
.selection-filter-indicator {
|
|
1321
|
+
position: fixed;
|
|
1322
|
+
bottom: 8px;
|
|
1323
|
+
left: 50%;
|
|
1324
|
+
transform: translateX(-50%);
|
|
1325
|
+
display: flex;
|
|
1326
|
+
flex-direction: column;
|
|
1327
|
+
gap: 6px;
|
|
1328
|
+
background: rgba(20,24,30,.85);
|
|
1329
|
+
border: 1px solid #262b36;
|
|
1330
|
+
border-radius: 10px;
|
|
1331
|
+
padding: 6px;
|
|
1332
|
+
color: #ddd;
|
|
1333
|
+
z-index: 12;
|
|
1334
|
+
user-select: none;
|
|
1335
|
+
min-width: 220px;
|
|
1336
|
+
max-width: min(440px, calc(100vw - 16px));
|
|
1337
|
+
box-shadow: 0 6px 18px rgba(0,0,0,.35);
|
|
1338
|
+
}
|
|
1339
|
+
.selection-filter-indicator .sfi-toggle {
|
|
1340
|
+
background: transparent;
|
|
1341
|
+
border-radius: 8px;
|
|
1342
|
+
padding: 6px 10px;
|
|
1343
|
+
width: 100%;
|
|
1344
|
+
min-height: 32px;
|
|
1345
|
+
box-sizing: border-box;
|
|
1346
|
+
color: #ddd;
|
|
1347
|
+
border: 1px solid #364053;
|
|
1348
|
+
cursor: pointer;
|
|
1349
|
+
text-align: left;
|
|
1350
|
+
}
|
|
1351
|
+
.selection-filter-indicator .sfi-toggle:hover { filter: brightness(1.08); }
|
|
1352
|
+
.selection-filter-indicator .sfi-toggle:active { filter: brightness(1.15); }
|
|
1353
|
+
.selection-filter-indicator .sfi-panel {
|
|
1354
|
+
border: 1px solid #2b3240;
|
|
1355
|
+
border-radius: 8px;
|
|
1356
|
+
padding: 8px 10px;
|
|
1357
|
+
background: rgba(17,22,31,.95);
|
|
1358
|
+
}
|
|
1359
|
+
.selection-filter-indicator .sfi-panel[hidden] { display: none; }
|
|
1360
|
+
.selection-filter-indicator .sfi-list {
|
|
1361
|
+
display: grid;
|
|
1362
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1363
|
+
gap: 6px 10px;
|
|
1364
|
+
}
|
|
1365
|
+
.selection-filter-indicator .sfi-option {
|
|
1366
|
+
display: flex;
|
|
1367
|
+
align-items: center;
|
|
1368
|
+
gap: 6px;
|
|
1369
|
+
font-size: 12px;
|
|
1370
|
+
color: #cbd5e1;
|
|
1371
|
+
}
|
|
1372
|
+
.selection-filter-indicator input[type="checkbox"] {
|
|
1373
|
+
width: 16px;
|
|
1374
|
+
height: 16px;
|
|
1375
|
+
accent-color: #60a5fa;
|
|
1376
|
+
}
|
|
1377
|
+
.selection-filter-indicator .sfi-actions {
|
|
1378
|
+
display: flex;
|
|
1379
|
+
gap: 6px;
|
|
1380
|
+
margin-top: 8px;
|
|
1381
|
+
}
|
|
1382
|
+
.selection-filter-indicator .sfi-btn {
|
|
1383
|
+
flex: 1;
|
|
1384
|
+
background: rgba(255,255,255,.04);
|
|
1385
|
+
border: 1px solid #364053;
|
|
1386
|
+
border-radius: 8px;
|
|
1387
|
+
color: #e2e8f0;
|
|
1388
|
+
padding: 6px 10px;
|
|
1389
|
+
font-size: 12px;
|
|
1390
|
+
cursor: pointer;
|
|
1391
|
+
text-align: center;
|
|
1392
|
+
min-height: 28px;
|
|
1393
|
+
}
|
|
1394
|
+
.selection-filter-indicator .sfi-btn:hover { filter: brightness(1.08); }
|
|
1395
|
+
.selection-filter-indicator .sfi-btn:active { filter: brightness(1.15); }
|
|
1396
|
+
.selection-filter-indicator .sfi-btn.is-active {
|
|
1397
|
+
border-color: var(--sfi-tint, #60a5fa);
|
|
1398
|
+
color: var(--sfi-tint, #60a5fa);
|
|
1399
|
+
}
|
|
1400
|
+
`;
|
|
1401
|
+
document.head.appendChild(style);
|
|
1402
|
+
}
|
|
1403
|
+
} catch { }
|
|
1404
|
+
|
|
1405
|
+
const wrap = document.createElement('div');
|
|
1406
|
+
wrap.className = 'selection-filter-indicator';
|
|
1407
|
+
|
|
1408
|
+
const toggle = document.createElement('button');
|
|
1409
|
+
toggle.type = 'button';
|
|
1410
|
+
toggle.className = 'sfi-toggle';
|
|
1411
|
+
const panelId = `selection-filter-panel-${Math.random().toString(36).slice(2, 8)}`;
|
|
1412
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
1413
|
+
toggle.setAttribute('aria-controls', panelId);
|
|
1414
|
+
wrap.appendChild(toggle);
|
|
1415
|
+
|
|
1416
|
+
const panel = document.createElement('div');
|
|
1417
|
+
panel.className = 'sfi-panel';
|
|
1418
|
+
panel.id = panelId;
|
|
1419
|
+
panel.hidden = true;
|
|
1420
|
+
|
|
1421
|
+
const list = document.createElement('div');
|
|
1422
|
+
list.className = 'sfi-list';
|
|
1423
|
+
panel.appendChild(list);
|
|
1424
|
+
|
|
1425
|
+
const checkboxByType = new Map();
|
|
1426
|
+
const types = SelectionFilter._getSelectionFilterTypeList();
|
|
1427
|
+
for (const type of types) {
|
|
1428
|
+
const option = document.createElement('label');
|
|
1429
|
+
option.className = 'sfi-option';
|
|
1430
|
+
|
|
1431
|
+
const box = document.createElement('input');
|
|
1432
|
+
box.type = 'checkbox';
|
|
1433
|
+
box.dataset.type = type;
|
|
1434
|
+
box.addEventListener('click', (ev) => ev.stopPropagation());
|
|
1435
|
+
box.addEventListener('change', (ev) => {
|
|
1436
|
+
ev.stopPropagation();
|
|
1437
|
+
const next = [];
|
|
1438
|
+
for (const t of types) {
|
|
1439
|
+
const cb = checkboxByType.get(t);
|
|
1440
|
+
if (cb && cb.checked) next.push(t);
|
|
1441
|
+
}
|
|
1442
|
+
const nextValue = next.length === types.length ? SelectionFilter.ALL : next;
|
|
1443
|
+
try { SelectionFilter.SetSelectionTypes(nextValue); } catch { }
|
|
1444
|
+
if (SelectionFilter.previouseAllowedSelectionTypes !== null) {
|
|
1445
|
+
SelectionFilter.previouseAllowedSelectionTypes = SelectionFilter.allowedSelectionTypes;
|
|
1446
|
+
}
|
|
1447
|
+
SelectionFilter._updateSelectionFilterIndicator();
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1450
|
+
const label = document.createElement('span');
|
|
1451
|
+
label.textContent = SelectionFilter._getSelectionFilterLabel(type);
|
|
1452
|
+
|
|
1453
|
+
option.appendChild(box);
|
|
1454
|
+
option.appendChild(label);
|
|
1455
|
+
list.appendChild(option);
|
|
1456
|
+
checkboxByType.set(type, box);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
const actions = document.createElement('div');
|
|
1460
|
+
actions.className = 'sfi-actions';
|
|
1461
|
+
const tintBtn = document.createElement('button');
|
|
1462
|
+
tintBtn.type = 'button';
|
|
1463
|
+
tintBtn.className = 'sfi-btn';
|
|
1464
|
+
tintBtn.addEventListener('click', (ev) => {
|
|
1465
|
+
ev.stopPropagation();
|
|
1466
|
+
SelectionFilter._toggleSelectableTint(viewer);
|
|
1467
|
+
});
|
|
1468
|
+
actions.appendChild(tintBtn);
|
|
1469
|
+
panel.appendChild(actions);
|
|
1470
|
+
|
|
1471
|
+
toggle.addEventListener('click', (ev) => {
|
|
1472
|
+
ev.stopPropagation();
|
|
1473
|
+
const nextOpen = panel.hidden;
|
|
1474
|
+
panel.hidden = !nextOpen;
|
|
1475
|
+
toggle.setAttribute('aria-expanded', String(nextOpen));
|
|
1476
|
+
if (nextOpen) SelectionFilter._updateSelectionFilterIndicator();
|
|
1477
|
+
});
|
|
1478
|
+
panel.addEventListener('click', (ev) => ev.stopPropagation());
|
|
1479
|
+
|
|
1480
|
+
if (!SelectionFilter._selectionFilterOutsideBound) {
|
|
1481
|
+
SelectionFilter._selectionFilterOutsideBound = true;
|
|
1482
|
+
document.addEventListener('mousedown', (ev) => {
|
|
1483
|
+
const panelEl = SelectionFilter._selectionFilterIndicatorPanel;
|
|
1484
|
+
const toggleEl = SelectionFilter._selectionFilterIndicatorToggle;
|
|
1485
|
+
const wrapEl = SelectionFilter._selectionFilterIndicator;
|
|
1486
|
+
if (wrapEl && ev && wrapEl.contains(ev.target)) return;
|
|
1487
|
+
if (!panelEl || panelEl.hidden) return;
|
|
1488
|
+
panelEl.hidden = true;
|
|
1489
|
+
if (toggleEl) toggleEl.setAttribute('aria-expanded', 'false');
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
wrap.appendChild(panel);
|
|
1494
|
+
host.appendChild(wrap);
|
|
1495
|
+
|
|
1496
|
+
SelectionFilter._selectionFilterIndicator = wrap;
|
|
1497
|
+
SelectionFilter._selectionFilterIndicatorToggle = toggle;
|
|
1498
|
+
SelectionFilter._selectionFilterIndicatorPanel = panel;
|
|
1499
|
+
SelectionFilter._selectionFilterCheckboxes = checkboxByType;
|
|
1500
|
+
SelectionFilter._selectionFilterTintBtn = tintBtn;
|
|
1501
|
+
SelectionFilter._updateSelectionFilterIndicator();
|
|
1502
|
+
return wrap;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1041
1505
|
static _createSelectionActionButton(entry) {
|
|
1042
1506
|
try {
|
|
1043
1507
|
const btn = document.createElement('button');
|
|
@@ -1056,6 +1520,7 @@ export class SelectionFilter {
|
|
|
1056
1520
|
}
|
|
1057
1521
|
|
|
1058
1522
|
static #logAllowedTypesChange(next, reason = '') {
|
|
1523
|
+
if (!debugMode) return;
|
|
1059
1524
|
try {
|
|
1060
1525
|
const desc = next === SelectionFilter.ALL
|
|
1061
1526
|
? 'ALL'
|