brep-io-kernel 1.0.21 → 1.0.23
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 +17545 -16874
- package/package.json +3 -2
- package/src/BREP/Edge.js +2 -0
- package/src/BREP/Face.js +2 -0
- package/src/BREP/SolidMethods/fillet.js +17 -3
- package/src/BREP/SolidMethods/visualize.js +372 -365
- package/src/BREP/Vertex.js +2 -17
- package/src/BREP/fillets/fillet.js +193 -39
- 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 +3 -0
- package/src/UI/SceneListing.js +45 -7
- package/src/UI/SelectionFilter.js +469 -442
- package/src/UI/SelectionState.js +464 -0
- package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +40 -1
- package/src/UI/assembly/AssemblyConstraintsWidget.js +17 -3
- package/src/UI/assembly/constraintSelectionUtils.js +3 -182
- package/src/UI/{assembly/constraintFaceUtils.js → faceUtils.js} +30 -5
- package/src/UI/featureDialogs.js +99 -69
- package/src/UI/pmi/LabelOverlay.js +32 -0
- package/src/UI/pmi/PMIMode.js +23 -0
- package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +7 -1
- 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 +16 -16
- 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/fillet/FilletFeature.js +7 -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 = [];
|
|
@@ -33,6 +42,10 @@ export class SelectionFilter {
|
|
|
33
42
|
static _selectionFilterTypes = null;
|
|
34
43
|
static _selectionFilterOutsideBound = false;
|
|
35
44
|
static _selectionFilterTintBtn = null;
|
|
45
|
+
static _clickWatcherTimer = null;
|
|
46
|
+
static _missingClickLogged = new Map();
|
|
47
|
+
static _clickWatcherIntervalMs = 2000;
|
|
48
|
+
static _onClickWatcherSeq = 1;
|
|
36
49
|
static _selectableTintState = {
|
|
37
50
|
active: false,
|
|
38
51
|
activeColor: null,
|
|
@@ -45,7 +58,7 @@ export class SelectionFilter {
|
|
|
45
58
|
throw new Error("SelectionFilter is static and cannot be instantiated.");
|
|
46
59
|
}
|
|
47
60
|
|
|
48
|
-
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]; }
|
|
49
62
|
|
|
50
63
|
// Convenience: return the list of selectable types for the dropdown (excludes ALL)
|
|
51
64
|
static getAvailableTypes() {
|
|
@@ -61,6 +74,144 @@ export class SelectionFilter {
|
|
|
61
74
|
return null;
|
|
62
75
|
}
|
|
63
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
|
+
|
|
64
215
|
static setCurrentType(_type) {
|
|
65
216
|
// No-op: current type is no longer tracked.
|
|
66
217
|
void _type;
|
|
@@ -72,7 +223,6 @@ export class SelectionFilter {
|
|
|
72
223
|
if (types === SelectionFilter.ALL) {
|
|
73
224
|
SelectionFilter.allowedSelectionTypes = SelectionFilter.ALL;
|
|
74
225
|
SelectionFilter.triggerUI();
|
|
75
|
-
SelectionFilter._ensureSceneSelectionHandlers();
|
|
76
226
|
SelectionFilter.#logAllowedTypesChange(SelectionFilter.allowedSelectionTypes, 'SetSelectionTypes');
|
|
77
227
|
return;
|
|
78
228
|
}
|
|
@@ -81,7 +231,6 @@ export class SelectionFilter {
|
|
|
81
231
|
if (invalid.length) throw new Error(`Unknown selection type(s): ${invalid.join(", ")}`);
|
|
82
232
|
SelectionFilter.allowedSelectionTypes = new Set(list);
|
|
83
233
|
SelectionFilter.triggerUI();
|
|
84
|
-
SelectionFilter._ensureSceneSelectionHandlers();
|
|
85
234
|
SelectionFilter.#logAllowedTypesChange(SelectionFilter.allowedSelectionTypes, 'SetSelectionTypes');
|
|
86
235
|
}
|
|
87
236
|
|
|
@@ -94,7 +243,6 @@ export class SelectionFilter {
|
|
|
94
243
|
SelectionFilter.allowedSelectionTypes = SelectionFilter.previouseAllowedSelectionTypes;
|
|
95
244
|
SelectionFilter.previouseAllowedSelectionTypes = null;
|
|
96
245
|
SelectionFilter.triggerUI();
|
|
97
|
-
SelectionFilter._ensureSceneSelectionHandlers();
|
|
98
246
|
SelectionFilter.#logAllowedTypesChange(SelectionFilter.allowedSelectionTypes, 'RestoreSelectionTypes');
|
|
99
247
|
}
|
|
100
248
|
}
|
|
@@ -104,19 +252,25 @@ export class SelectionFilter {
|
|
|
104
252
|
let changed = false;
|
|
105
253
|
const attach = (target) => {
|
|
106
254
|
if (!target || typeof target !== 'object') return;
|
|
255
|
+
SelectionState.attach(target);
|
|
256
|
+
SelectionFilter._installOnClickWatcher(target);
|
|
107
257
|
if (typeof target.onClick === 'function') return;
|
|
108
|
-
target
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
}
|
|
114
271
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
try { console.warn('[SelectionFilter] toggleSelection failed:', error); } catch (_) { /* ignore */ }
|
|
118
|
-
}
|
|
119
|
-
};
|
|
272
|
+
};
|
|
273
|
+
});
|
|
120
274
|
try { target.onClick.__brepSelectionHandler = true; } catch (_) { /* ignore */ }
|
|
121
275
|
changed = true;
|
|
122
276
|
};
|
|
@@ -138,21 +292,6 @@ export class SelectionFilter {
|
|
|
138
292
|
return changed;
|
|
139
293
|
}
|
|
140
294
|
|
|
141
|
-
static _ensureSceneSelectionHandlers() {
|
|
142
|
-
try {
|
|
143
|
-
const scene = SelectionFilter.viewer?.partHistory?.scene
|
|
144
|
-
|| SelectionFilter.viewer?.scene
|
|
145
|
-
|| null;
|
|
146
|
-
if (!scene || !Array.isArray(scene.children)) return;
|
|
147
|
-
for (const child of scene.children) {
|
|
148
|
-
if (!child || child.type !== SelectionFilter.SOLID) continue;
|
|
149
|
-
SelectionFilter.ensureSelectionHandlers(child, { deep: true });
|
|
150
|
-
}
|
|
151
|
-
} catch { }
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
295
|
static allowType(type) {
|
|
157
296
|
// Legacy support: expand available set; does not change currentType
|
|
158
297
|
if (type === SelectionFilter.ALL) { SelectionFilter.allowedSelectionTypes = SelectionFilter.ALL; SelectionFilter.triggerUI(); return; }
|
|
@@ -196,37 +335,67 @@ export class SelectionFilter {
|
|
|
196
335
|
SelectionFilter.#logAllowedTypesChange(SelectionFilter.allowedSelectionTypes, 'Reset');
|
|
197
336
|
}
|
|
198
337
|
|
|
199
|
-
static #getBaseMaterial(obj) {
|
|
200
|
-
if (!obj) return null;
|
|
201
|
-
const ud = obj.userData || {};
|
|
202
|
-
if (ud.__baseMaterial) return ud.__baseMaterial;
|
|
203
|
-
if (obj.type === SelectionFilter.FACE) return CADmaterials.FACE?.BASE ?? CADmaterials.FACE ?? obj.material;
|
|
204
|
-
if (obj.type === SelectionFilter.PLANE) return CADmaterials.PLANE?.BASE ?? CADmaterials.FACE?.BASE ?? obj.material;
|
|
205
|
-
if (obj.type === SelectionFilter.EDGE) return CADmaterials.EDGE?.BASE ?? CADmaterials.EDGE ?? obj.material;
|
|
206
|
-
if (obj.type === SelectionFilter.SOLID || obj.type === SelectionFilter.COMPONENT) return obj.material;
|
|
207
|
-
return obj.material;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
338
|
// ---------------- Hover Highlighting ----------------
|
|
211
|
-
static getHoverColor() { return SelectionFilter.hoverColor; }
|
|
339
|
+
static getHoverColor() { return SelectionState.hoverColor || SelectionFilter.hoverColor; }
|
|
212
340
|
static setHoverColor(hex) {
|
|
213
341
|
if (!hex) return;
|
|
214
342
|
try { SelectionFilter.hoverColor = String(hex); } catch (_) { }
|
|
343
|
+
SelectionState.setHoverColor(SelectionFilter.hoverColor);
|
|
215
344
|
// Update current hovered objects live
|
|
216
345
|
for (const o of Array.from(SelectionFilter._hovered)) {
|
|
217
|
-
if (o
|
|
218
|
-
|
|
219
|
-
|
|
346
|
+
if (!o) continue;
|
|
347
|
+
try {
|
|
348
|
+
SelectionState.attach(o);
|
|
349
|
+
o.hovered = false;
|
|
350
|
+
o.hovered = true;
|
|
351
|
+
} catch { }
|
|
220
352
|
}
|
|
221
353
|
}
|
|
222
354
|
|
|
223
355
|
static setHoverObject(obj, options = {}) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
+
}
|
|
230
399
|
}
|
|
231
400
|
|
|
232
401
|
static setHoverByName(scene, name) {
|
|
@@ -237,157 +406,38 @@ export class SelectionFilter {
|
|
|
237
406
|
}
|
|
238
407
|
|
|
239
408
|
static clearHover() {
|
|
240
|
-
|
|
241
|
-
for (const o of Array.from(SelectionFilter._hovered)) {
|
|
242
|
-
SelectionFilter.#restoreHover(o);
|
|
243
|
-
}
|
|
244
|
-
SelectionFilter._hovered.clear();
|
|
409
|
+
SelectionFilter._clearHoverState({ emit: true });
|
|
245
410
|
}
|
|
246
411
|
|
|
247
|
-
static
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|| SelectionFilter.matchesAllowedType(obj.type);
|
|
254
|
-
if (!allowed) return;
|
|
255
|
-
|
|
256
|
-
// Only ever highlight one object: the exact object provided, if it has a color
|
|
257
|
-
const target = obj;
|
|
258
|
-
if (!target) return;
|
|
259
|
-
|
|
260
|
-
const applyToOne = (t) => {
|
|
261
|
-
if (!t) return;
|
|
262
|
-
if (!t.userData) t.userData = {};
|
|
263
|
-
const origMat = t.material;
|
|
264
|
-
if (!origMat) return;
|
|
265
|
-
if (t.userData.__hoverMatApplied) { SelectionFilter._hovered.add(t); return; }
|
|
266
|
-
let clone;
|
|
267
|
-
try { clone = typeof origMat.clone === 'function' ? origMat.clone() : origMat; } catch { clone = origMat; }
|
|
268
|
-
try { if (clone && clone.color && typeof clone.color.set === 'function') clone.color.set(SelectionFilter.hoverColor); } catch { }
|
|
269
|
-
try {
|
|
270
|
-
if (origMat && clone && origMat.resolution && clone.resolution && typeof clone.resolution.copy === 'function') {
|
|
271
|
-
clone.resolution.copy(origMat.resolution);
|
|
272
|
-
}
|
|
273
|
-
} catch { }
|
|
274
|
-
try {
|
|
275
|
-
if (origMat && clone && typeof origMat.dashed !== 'undefined' && typeof clone.dashed !== 'undefined') {
|
|
276
|
-
clone.dashed = origMat.dashed;
|
|
277
|
-
}
|
|
278
|
-
if (origMat && clone && typeof origMat.dashSize !== 'undefined' && typeof clone.dashSize !== 'undefined') {
|
|
279
|
-
clone.dashSize = origMat.dashSize;
|
|
280
|
-
}
|
|
281
|
-
if (origMat && clone && typeof origMat.gapSize !== 'undefined' && typeof clone.gapSize !== 'undefined') {
|
|
282
|
-
clone.gapSize = origMat.gapSize;
|
|
283
|
-
}
|
|
284
|
-
if (origMat && clone && typeof origMat.dashScale !== 'undefined' && typeof clone.dashScale !== 'undefined') {
|
|
285
|
-
clone.dashScale = origMat.dashScale;
|
|
286
|
-
}
|
|
287
|
-
} catch { }
|
|
288
|
-
try {
|
|
289
|
-
t.userData.__hoverOrigMat = origMat;
|
|
290
|
-
t.userData.__hoverMatApplied = true;
|
|
291
|
-
if (clone !== origMat) t.material = clone;
|
|
292
|
-
t.userData.__hoverMat = clone;
|
|
293
|
-
} catch { }
|
|
294
|
-
SelectionFilter._hovered.add(t);
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
if (target.type === SelectionFilter.SOLID || target.type === SelectionFilter.COMPONENT) {
|
|
298
|
-
// Highlight all immediate child faces/edges for SOLID or COMPONENT children
|
|
299
|
-
if (Array.isArray(target.children)) {
|
|
300
|
-
for (const ch of target.children) {
|
|
301
|
-
if (!ch) continue;
|
|
302
|
-
if (ch.type === SelectionFilter.SOLID || ch.type === SelectionFilter.COMPONENT) {
|
|
303
|
-
if (Array.isArray(ch.children)) {
|
|
304
|
-
for (const nested of ch.children) {
|
|
305
|
-
if (nested && (nested.type === SelectionFilter.FACE || nested.type === SelectionFilter.EDGE)) applyToOne(nested);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
} else if (ch.type === SelectionFilter.FACE || ch.type === SelectionFilter.EDGE) {
|
|
309
|
-
applyToOne(ch);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
// Track the solid as a logical hovered root to clear later
|
|
314
|
-
SelectionFilter._hovered.add(target);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (target.type === SelectionFilter.VERTEX) {
|
|
319
|
-
// Apply to the vertex object and any drawable children (e.g., Points)
|
|
320
|
-
applyToOne(target);
|
|
321
|
-
if (Array.isArray(target.children)) {
|
|
322
|
-
for (const ch of target.children) {
|
|
323
|
-
applyToOne(ch);
|
|
324
|
-
}
|
|
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);
|
|
325
418
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
applyToOne(target);
|
|
419
|
+
const ev = new CustomEvent('hover-changed', { detail: { objects: list, uuids } });
|
|
420
|
+
window.dispatchEvent(ev);
|
|
421
|
+
} catch { /* ignore */ }
|
|
330
422
|
}
|
|
331
423
|
|
|
332
|
-
static
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const ud = t.userData || {};
|
|
337
|
-
if (!ud.__hoverMatApplied) return;
|
|
338
|
-
const cloneWithColor = (mat, colorHex) => {
|
|
339
|
-
if (!mat) return mat;
|
|
340
|
-
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)) {
|
|
341
428
|
try {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
} catch {
|
|
345
|
-
return c;
|
|
346
|
-
};
|
|
347
|
-
const applySelectionMaterial = () => {
|
|
348
|
-
if (t.type === SelectionFilter.FACE) return CADmaterials.FACE?.SELECTED ?? CADmaterials.FACE ?? ud.__hoverOrigMat ?? t.material;
|
|
349
|
-
if (t.type === SelectionFilter.PLANE) return CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? ud.__hoverOrigMat ?? t.material;
|
|
350
|
-
if (t.type === SelectionFilter.EDGE) {
|
|
351
|
-
const base = ud.__hoverOrigMat ?? t.material;
|
|
352
|
-
const selColor = CADmaterials.EDGE?.SELECTED?.color || CADmaterials.EDGE?.SELECTED?.color?.getHexString?.();
|
|
353
|
-
return cloneWithColor(base, selColor || '#ff00ff');
|
|
354
|
-
}
|
|
355
|
-
if (t.type === SelectionFilter.SOLID || t.type === SelectionFilter.COMPONENT) return CADmaterials.SOLID?.SELECTED ?? ud.__hoverOrigMat ?? t.material;
|
|
356
|
-
return ud.__hoverOrigMat ?? t.material;
|
|
357
|
-
};
|
|
358
|
-
const applyDeselectedMaterial = () => {
|
|
359
|
-
if (t.type === SelectionFilter.FACE) return ud.__hoverOrigMat ?? SelectionFilter.#getBaseMaterial(t) ?? t.material;
|
|
360
|
-
if (t.type === SelectionFilter.PLANE) return ud.__hoverOrigMat ?? SelectionFilter.#getBaseMaterial(t) ?? t.material;
|
|
361
|
-
if (t.type === SelectionFilter.EDGE) return ud.__hoverOrigMat ?? SelectionFilter.#getBaseMaterial(t) ?? t.material;
|
|
362
|
-
if (t.type === SelectionFilter.SOLID || t.type === SelectionFilter.COMPONENT) return ud.__hoverOrigMat ?? t.material;
|
|
363
|
-
return ud.__hoverOrigMat ?? t.material;
|
|
364
|
-
};
|
|
365
|
-
try {
|
|
366
|
-
if (t.selected) {
|
|
367
|
-
const selMat = applySelectionMaterial();
|
|
368
|
-
if (selMat) t.material = selMat;
|
|
369
|
-
} else {
|
|
370
|
-
const baseMat = applyDeselectedMaterial();
|
|
371
|
-
if (baseMat) t.material = baseMat;
|
|
372
|
-
}
|
|
373
|
-
if (ud.__hoverMat && ud.__hoverMat !== ud.__hoverOrigMat && typeof ud.__hoverMat.dispose === 'function') ud.__hoverMat.dispose();
|
|
374
|
-
} catch { }
|
|
375
|
-
try { delete t.userData.__hoverMatApplied; } catch { }
|
|
376
|
-
try { delete t.userData.__hoverOrigMat; } catch { }
|
|
377
|
-
try { delete t.userData.__hoverMat; } catch { }
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
if (obj.type === SelectionFilter.SOLID || obj.type === SelectionFilter.COMPONENT) {
|
|
381
|
-
if (Array.isArray(obj.children)) {
|
|
382
|
-
for (const ch of obj.children) restoreOne(ch);
|
|
429
|
+
SelectionState.attach(o);
|
|
430
|
+
o.hovered = false;
|
|
431
|
+
} catch { }
|
|
383
432
|
}
|
|
433
|
+
SelectionFilter._hovered.clear();
|
|
384
434
|
}
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
435
|
+
if (SelectionFilter._hoveredSourceMap.size) {
|
|
436
|
+
SelectionFilter._hoveredSourceMap.clear();
|
|
437
|
+
}
|
|
438
|
+
if (emit && hadHover) {
|
|
439
|
+
SelectionFilter._emitHoverChanged([]);
|
|
389
440
|
}
|
|
390
|
-
restoreOne(obj);
|
|
391
441
|
}
|
|
392
442
|
|
|
393
443
|
static #isInputUsable(el) {
|
|
@@ -415,246 +465,208 @@ export class SelectionFilter {
|
|
|
415
465
|
return true;
|
|
416
466
|
}
|
|
417
467
|
|
|
418
|
-
static
|
|
419
|
-
// get the type of the object
|
|
420
|
-
const type = objectToToggleSelectionOn.type;
|
|
421
|
-
if (!type) throw new Error("Object to toggle selection on must have a type.");
|
|
422
|
-
|
|
423
|
-
// Check if a reference selection is active
|
|
468
|
+
static #handleReferenceSelection(objectToToggleSelectionOn) {
|
|
424
469
|
try {
|
|
425
470
|
let activeRefInput = document.querySelector('[active-reference-selection="true"],[active-reference-selection=true]');
|
|
426
471
|
if (!activeRefInput) {
|
|
427
472
|
try { activeRefInput = window.__BREP_activeRefInput || null; } catch (_) { /* ignore */ }
|
|
428
473
|
}
|
|
429
|
-
if (activeRefInput)
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
+
}
|
|
438
484
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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) {
|
|
445
501
|
try {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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);
|
|
452
508
|
}
|
|
453
|
-
} catch (_) {
|
|
454
|
-
|
|
455
|
-
try {
|
|
456
|
-
const wrap = activeRefInput.closest('.ref-single-wrap, .ref-multi-wrap');
|
|
457
|
-
if (wrap) {
|
|
458
|
-
wrap.classList.add('ref-limit-reached');
|
|
459
|
-
setTimeout(() => {
|
|
460
|
-
try { wrap.classList.remove('ref-limit-reached'); } catch (_) { }
|
|
461
|
-
}, 480);
|
|
462
|
-
}
|
|
463
|
-
} catch (_) { }
|
|
464
|
-
return true;
|
|
465
|
-
}
|
|
509
|
+
} catch (_) { }
|
|
510
|
+
return true;
|
|
466
511
|
}
|
|
467
|
-
|
|
468
|
-
const allowAll = allowed === SelectionFilter.ALL;
|
|
469
|
-
const priorityOrder = [
|
|
470
|
-
SelectionFilter.VERTEX,
|
|
471
|
-
SelectionFilter.EDGE,
|
|
472
|
-
SelectionFilter.FACE,
|
|
473
|
-
SelectionFilter.PLANE,
|
|
474
|
-
SelectionFilter.SOLID,
|
|
475
|
-
SelectionFilter.COMPONENT,
|
|
476
|
-
];
|
|
477
|
-
const allowedHas = (t) => !!(allowed && typeof allowed.has === 'function' && allowed.has(t));
|
|
478
|
-
const allowedPriority = allowAll ? priorityOrder : priorityOrder.filter(t => allowedHas(t));
|
|
479
|
-
|
|
480
|
-
// Helper: find first descendant matching a type
|
|
481
|
-
const findDescendantOfType = (root, desired) => {
|
|
482
|
-
if (!root || !desired) return null;
|
|
483
|
-
let found = null;
|
|
484
|
-
try {
|
|
485
|
-
root.traverse?.((ch) => {
|
|
486
|
-
if (!found && ch && ch.type === desired) found = ch;
|
|
487
|
-
});
|
|
488
|
-
} catch (_) { /* ignore */ }
|
|
489
|
-
return found;
|
|
490
|
-
};
|
|
512
|
+
}
|
|
491
513
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
+
};
|
|
511
539
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
if (!targetObj) {
|
|
518
|
-
targetObj = pickByTypeList(allowedPriority);
|
|
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;
|
|
519
545
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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;
|
|
525
557
|
}
|
|
526
|
-
|
|
558
|
+
return null;
|
|
559
|
+
};
|
|
527
560
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const parsed = JSON.parse(data.selectedValues);
|
|
543
|
-
if (Array.isArray(parsed)) values = parsed;
|
|
544
|
-
} catch (_) { /* ignore */ }
|
|
545
|
-
}
|
|
546
|
-
if (!Array.isArray(values) && typeof inputEl?.__getSelectionList === 'function') {
|
|
547
|
-
try {
|
|
548
|
-
const list = inputEl.__getSelectionList();
|
|
549
|
-
if (Array.isArray(list)) values = list.slice();
|
|
550
|
-
} catch (_) { /* ignore */ }
|
|
551
|
-
}
|
|
552
|
-
let count = 0;
|
|
553
|
-
if (Array.isArray(values)) {
|
|
554
|
-
count = values.length;
|
|
555
|
-
} else if (data.selectedCount !== undefined) {
|
|
556
|
-
const parsed = Number(data.selectedCount);
|
|
557
|
-
if (Number.isFinite(parsed) && parsed >= 0) count = parsed;
|
|
558
|
-
}
|
|
559
|
-
return count;
|
|
560
|
-
};
|
|
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;
|
|
561
575
|
|
|
562
|
-
|
|
563
|
-
activeRefInput.
|
|
564
|
-
|
|
576
|
+
try {
|
|
577
|
+
if (activeRefInput && typeof activeRefInput.__captureReferencePreview === 'function') {
|
|
578
|
+
activeRefInput.__captureReferencePreview(targetObj);
|
|
579
|
+
}
|
|
580
|
+
} catch (_) { /* ignore preview capture errors */ }
|
|
565
581
|
|
|
566
|
-
|
|
567
|
-
|
|
582
|
+
const objType = targetObj.type;
|
|
583
|
+
const objectName = targetObj.name || `${objType}(${targetObj.position?.x || 0},${targetObj.position?.y || 0},${targetObj.position?.z || 0})`;
|
|
568
584
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
585
|
+
const snapshotSelections = (inputEl) => {
|
|
586
|
+
const data = inputEl?.dataset || {};
|
|
587
|
+
let values = null;
|
|
588
|
+
if (data.selectedValues) {
|
|
573
589
|
try {
|
|
574
|
-
const
|
|
575
|
-
if (
|
|
576
|
-
} catch (_) { }
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
try { if (window.__BREP_activeRefInput === activeRefInput) window.__BREP_activeRefInput = null; } catch (_) { }
|
|
580
|
-
} else {
|
|
581
|
-
activeRefInput.setAttribute('active-reference-selection', 'true');
|
|
582
|
-
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') {
|
|
583
595
|
try {
|
|
584
|
-
const
|
|
585
|
-
if (
|
|
586
|
-
} catch (_) { }
|
|
587
|
-
|
|
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;
|
|
588
606
|
}
|
|
589
|
-
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 (_) { }
|
|
590
634
|
}
|
|
635
|
+
return true;
|
|
591
636
|
} catch (error) {
|
|
592
|
-
|
|
637
|
+
if (debugMode) {
|
|
638
|
+
console.warn("Error handling reference selection:", error);
|
|
639
|
+
}
|
|
640
|
+
return false;
|
|
593
641
|
}
|
|
642
|
+
}
|
|
594
643
|
|
|
644
|
+
static #toggleStandardSelection(objectToToggleSelectionOn) {
|
|
645
|
+
const type = objectToToggleSelectionOn.type;
|
|
595
646
|
let parentSelectedAction = false;
|
|
596
|
-
// check if the object is selectable and if it is toggle the .selected atribute on the object.
|
|
597
|
-
// Allow toggling off even if type is currently disallowed; only block new selections
|
|
598
647
|
if (SelectionFilter.IsAllowed(type) || objectToToggleSelectionOn.selected === true) {
|
|
648
|
+
SelectionState.attach(objectToToggleSelectionOn);
|
|
599
649
|
objectToToggleSelectionOn.selected = !objectToToggleSelectionOn.selected;
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
if (objectToToggleSelectionOn.type === SelectionFilter.FACE) {
|
|
603
|
-
objectToToggleSelectionOn.material = CADmaterials.FACE?.SELECTED ?? CADmaterials.FACE;
|
|
604
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.PLANE) {
|
|
605
|
-
objectToToggleSelectionOn.material = CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? objectToToggleSelectionOn.material;
|
|
606
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.EDGE) {
|
|
607
|
-
objectToToggleSelectionOn.material = CADmaterials.EDGE?.SELECTED ?? CADmaterials.EDGE;
|
|
608
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.VERTEX) {
|
|
609
|
-
// Vertex visuals are handled by its selected accessor (point + sphere)
|
|
610
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.SOLID || objectToToggleSelectionOn.type === SelectionFilter.COMPONENT) {
|
|
611
|
-
parentSelectedAction = true;
|
|
612
|
-
objectToToggleSelectionOn.children.forEach(child => {
|
|
613
|
-
// apply selected material based on object type for faces and edges
|
|
614
|
-
if (child.type === SelectionFilter.FACE) child.material = CADmaterials.FACE?.SELECTED ?? CADmaterials.FACE;
|
|
615
|
-
if (child.type === SelectionFilter.PLANE) child.material = CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? child.material;
|
|
616
|
-
if (child.type === SelectionFilter.EDGE) child.material = CADmaterials.EDGE?.SELECTED ?? CADmaterials.EDGE;
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
} else {
|
|
621
|
-
if (objectToToggleSelectionOn.type === SelectionFilter.FACE) {
|
|
622
|
-
objectToToggleSelectionOn.material = SelectionFilter.#getBaseMaterial(objectToToggleSelectionOn) ?? objectToToggleSelectionOn.material;
|
|
623
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.PLANE) {
|
|
624
|
-
objectToToggleSelectionOn.material = SelectionFilter.#getBaseMaterial(objectToToggleSelectionOn) ?? objectToToggleSelectionOn.material;
|
|
625
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.EDGE) {
|
|
626
|
-
objectToToggleSelectionOn.material = SelectionFilter.#getBaseMaterial(objectToToggleSelectionOn) ?? objectToToggleSelectionOn.material;
|
|
627
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.VERTEX) {
|
|
628
|
-
// Vertex accessor handles its own visual reset
|
|
629
|
-
} else if (objectToToggleSelectionOn.type === SelectionFilter.SOLID || objectToToggleSelectionOn.type === SelectionFilter.COMPONENT) {
|
|
630
|
-
parentSelectedAction = true;
|
|
631
|
-
objectToToggleSelectionOn.children.forEach(child => {
|
|
632
|
-
// apply selected material based on object type for faces and edges
|
|
633
|
-
if (child.type === SelectionFilter.FACE) child.material = child.selected ? CADmaterials.FACE?.SELECTED ?? CADmaterials.FACE : (SelectionFilter.#getBaseMaterial(child) ?? child.material);
|
|
634
|
-
if (child.type === SelectionFilter.PLANE) child.material = child.selected ? (CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? child.material) : (SelectionFilter.#getBaseMaterial(child) ?? child.material);
|
|
635
|
-
if (child.type === SelectionFilter.EDGE) child.material = child.selected ? CADmaterials.EDGE?.SELECTED ?? CADmaterials.EDGE : (SelectionFilter.#getBaseMaterial(child) ?? child.material);
|
|
636
|
-
});
|
|
637
|
-
}
|
|
650
|
+
if (type === SelectionFilter.SOLID || type === SelectionFilter.COMPONENT) {
|
|
651
|
+
parentSelectedAction = true;
|
|
638
652
|
}
|
|
639
653
|
SelectionFilter._emitSelectionChanged();
|
|
640
654
|
}
|
|
641
|
-
|
|
642
655
|
return parentSelectedAction;
|
|
643
656
|
}
|
|
644
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
|
+
|
|
645
665
|
static unselectAll(scene) {
|
|
646
666
|
// itterate over all children and nested children of the scene and set the .selected atribute to false.
|
|
647
667
|
scene.traverse((child) => {
|
|
668
|
+
SelectionState.attach(child);
|
|
648
669
|
child.selected = false;
|
|
649
|
-
// reset material to base
|
|
650
|
-
if (child.type === SelectionFilter.FACE) {
|
|
651
|
-
child.material = SelectionFilter.#getBaseMaterial(child) ?? child.material;
|
|
652
|
-
} else if (child.type === SelectionFilter.PLANE) {
|
|
653
|
-
child.material = SelectionFilter.#getBaseMaterial(child) ?? child.material;
|
|
654
|
-
} else if (child.type === SelectionFilter.EDGE) {
|
|
655
|
-
child.material = SelectionFilter.#getBaseMaterial(child) ?? child.material;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
670
|
});
|
|
659
671
|
SelectionFilter._emitSelectionChanged();
|
|
660
672
|
}
|
|
@@ -662,17 +674,8 @@ export class SelectionFilter {
|
|
|
662
674
|
static selectItem(scene, itemName) {
|
|
663
675
|
scene.traverse((child) => {
|
|
664
676
|
if (child && child.name === itemName) {
|
|
677
|
+
SelectionState.attach(child);
|
|
665
678
|
child.selected = true;
|
|
666
|
-
// change material to selected
|
|
667
|
-
if (child.type === SelectionFilter.FACE) {
|
|
668
|
-
child.material = CADmaterials.FACE?.SELECTED ?? CADmaterials.FACE;
|
|
669
|
-
} else if (child.type === SelectionFilter.PLANE) {
|
|
670
|
-
child.material = CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? child.material;
|
|
671
|
-
} else if (child.type === SelectionFilter.EDGE) {
|
|
672
|
-
child.material = CADmaterials.EDGE?.SELECTED ?? CADmaterials.EDGE;
|
|
673
|
-
} else if (child.type === SelectionFilter.SOLID || child.type === SelectionFilter.COMPONENT) {
|
|
674
|
-
child.material = CADmaterials.SOLID?.SELECTED ?? CADmaterials.SOLID;
|
|
675
|
-
}
|
|
676
679
|
}
|
|
677
680
|
});
|
|
678
681
|
SelectionFilter._emitSelectionChanged();
|
|
@@ -682,27 +685,8 @@ export class SelectionFilter {
|
|
|
682
685
|
// Traverse scene and deselect a single item by name, updating materials appropriately
|
|
683
686
|
scene.traverse((child) => {
|
|
684
687
|
if (child.name === itemName) {
|
|
688
|
+
SelectionState.attach(child);
|
|
685
689
|
child.selected = false;
|
|
686
|
-
if (child.type === SelectionFilter.FACE) {
|
|
687
|
-
child.material = SelectionFilter.#getBaseMaterial(child) ?? child.material;
|
|
688
|
-
} else if (child.type === SelectionFilter.PLANE) {
|
|
689
|
-
child.material = SelectionFilter.#getBaseMaterial(child) ?? child.material;
|
|
690
|
-
} else if (child.type === SelectionFilter.EDGE) {
|
|
691
|
-
child.material = SelectionFilter.#getBaseMaterial(child) ?? child.material;
|
|
692
|
-
} else if (child.type === SelectionFilter.SOLID || child.type === SelectionFilter.COMPONENT) {
|
|
693
|
-
// For solids, keep children materials consistent with their own selected flags
|
|
694
|
-
child.children.forEach(grandchild => {
|
|
695
|
-
if (grandchild.type === SelectionFilter.FACE) {
|
|
696
|
-
grandchild.material = grandchild.selected ? (CADmaterials.FACE?.SELECTED ?? CADmaterials.FACE) : (SelectionFilter.#getBaseMaterial(grandchild) ?? grandchild.material);
|
|
697
|
-
}
|
|
698
|
-
if (grandchild.type === SelectionFilter.PLANE) {
|
|
699
|
-
grandchild.material = grandchild.selected ? (CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? grandchild.material) : (SelectionFilter.#getBaseMaterial(grandchild) ?? grandchild.material);
|
|
700
|
-
}
|
|
701
|
-
if (grandchild.type === SelectionFilter.EDGE) {
|
|
702
|
-
grandchild.material = grandchild.selected ? (CADmaterials.EDGE?.SELECTED ?? CADmaterials.EDGE) : (SelectionFilter.#getBaseMaterial(grandchild) ?? grandchild.material);
|
|
703
|
-
}
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
690
|
}
|
|
707
691
|
});
|
|
708
692
|
SelectionFilter._emitSelectionChanged();
|
|
@@ -716,6 +700,21 @@ export class SelectionFilter {
|
|
|
716
700
|
|
|
717
701
|
// Emit a global event so UI can react without polling
|
|
718
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 */ }
|
|
719
718
|
try {
|
|
720
719
|
const ev = new CustomEvent('selection-changed');
|
|
721
720
|
window.dispatchEvent(ev);
|
|
@@ -874,6 +873,7 @@ export class SelectionFilter {
|
|
|
874
873
|
static _getHistoryContextActionSpecs(selection, viewer) {
|
|
875
874
|
const out = [];
|
|
876
875
|
const items = Array.isArray(selection) ? selection : [];
|
|
876
|
+
const suppressFeatureButtons = SelectionFilter._hasAssemblyComponentSelection(items, viewer);
|
|
877
877
|
const safeId = (prefix, key) => {
|
|
878
878
|
const raw = String(key || '').toLowerCase().replace(/[^a-z0-9_-]+/g, '-');
|
|
879
879
|
return `${prefix}-${raw || 'item'}`;
|
|
@@ -887,21 +887,23 @@ export class SelectionFilter {
|
|
|
887
887
|
if (!pmiActive) {
|
|
888
888
|
const featureRegistry = viewer?.partHistory?.featureRegistry || null;
|
|
889
889
|
const features = Array.isArray(featureRegistry?.features) ? featureRegistry.features : [];
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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
|
+
}
|
|
905
907
|
}
|
|
906
908
|
|
|
907
909
|
const constraintRegistry = viewer?.partHistory?.assemblyConstraintRegistry || null;
|
|
@@ -952,6 +954,28 @@ export class SelectionFilter {
|
|
|
952
954
|
return out;
|
|
953
955
|
}
|
|
954
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
|
+
|
|
955
979
|
static async _createFeatureFromContext(viewer, typeKey, params = null) {
|
|
956
980
|
if (!viewer || !typeKey) return;
|
|
957
981
|
SelectionFilter.setContextBarSuppressed('context-create', true);
|
|
@@ -1127,6 +1151,8 @@ export class SelectionFilter {
|
|
|
1127
1151
|
FACE: 'Face',
|
|
1128
1152
|
PLANE: 'Plane',
|
|
1129
1153
|
SKETCH: 'Sketch',
|
|
1154
|
+
DATUM: 'Datum',
|
|
1155
|
+
HELIX: 'Helix',
|
|
1130
1156
|
EDGE: 'Edge',
|
|
1131
1157
|
LOOP: 'Loop',
|
|
1132
1158
|
VERTEX: 'Vertex',
|
|
@@ -1494,6 +1520,7 @@ export class SelectionFilter {
|
|
|
1494
1520
|
}
|
|
1495
1521
|
|
|
1496
1522
|
static #logAllowedTypesChange(next, reason = '') {
|
|
1523
|
+
if (!debugMode) return;
|
|
1497
1524
|
try {
|
|
1498
1525
|
const desc = next === SelectionFilter.ALL
|
|
1499
1526
|
? 'ALL'
|