brep-io-kernel 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-kernel/brep-kernel.js +7631 -7247
- package/package.json +1 -1
- package/src/UI/HistoryWidget.js +9 -4
- package/src/UI/SelectionFilter.js +471 -33
- package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +1 -0
- package/src/UI/assembly/AssemblyConstraintsWidget.js +4 -0
- package/src/UI/featureDialogs.js +55 -0
- 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/PMIMode.js +4 -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 +51 -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/viewer.js +24 -3
- package/src/features/tube/TubeFeature.js +57 -14
package/package.json
CHANGED
package/src/UI/HistoryWidget.js
CHANGED
|
@@ -12,6 +12,7 @@ export class HistoryWidget extends HistoryCollectionWidget {
|
|
|
12
12
|
|
|
13
13
|
// Override configurable hooks from the base widget after super() so they can access `this`.
|
|
14
14
|
this._autoSyncOpenState = true;
|
|
15
|
+
this._autoFocusOnExpand = true;
|
|
15
16
|
this._determineExpanded = (entry) => this.#shouldExpandEntry(entry);
|
|
16
17
|
this._formOptionsProvider = (context) => this.#buildFormOptions(context);
|
|
17
18
|
this._decorateEntryHeader = (context) => this.#decorateEntryHeader(context);
|
|
@@ -380,10 +381,14 @@ export class HistoryWidget extends HistoryCollectionWidget {
|
|
|
380
381
|
if (!target) return;
|
|
381
382
|
if (this._expandedId && String(this._expandedId) === String(target)) return;
|
|
382
383
|
const entries = this._getEntries();
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
384
|
+
for (let i = 0; i < entries.length; i++) {
|
|
385
|
+
const entry = entries[i];
|
|
386
|
+
if (this._extractEntryId(entry, i) !== String(target)) continue;
|
|
387
|
+
if (!this.#shouldExpandEntry(entry)) return;
|
|
388
|
+
this._expandedId = String(target);
|
|
389
|
+
this.render();
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
387
392
|
}
|
|
388
393
|
|
|
389
394
|
#computeIdsSignature() {
|
|
@@ -26,6 +26,20 @@ export class SelectionFilter {
|
|
|
26
26
|
static _historyContextActions = new Map();
|
|
27
27
|
static _selectionActionSeparator = null;
|
|
28
28
|
static _contextSuppressReasons = new Set();
|
|
29
|
+
static _selectionFilterIndicator = null;
|
|
30
|
+
static _selectionFilterIndicatorToggle = null;
|
|
31
|
+
static _selectionFilterIndicatorPanel = null;
|
|
32
|
+
static _selectionFilterCheckboxes = new Map();
|
|
33
|
+
static _selectionFilterTypes = null;
|
|
34
|
+
static _selectionFilterOutsideBound = false;
|
|
35
|
+
static _selectionFilterTintBtn = null;
|
|
36
|
+
static _selectableTintState = {
|
|
37
|
+
active: false,
|
|
38
|
+
activeColor: null,
|
|
39
|
+
colorIndex: 0,
|
|
40
|
+
colors: ['#34d399', '#f97316', '#60a5fa', '#f43f5e'],
|
|
41
|
+
materials: new Map(),
|
|
42
|
+
};
|
|
29
43
|
|
|
30
44
|
constructor() {
|
|
31
45
|
throw new Error("SelectionFilter is static and cannot be instantiated.");
|
|
@@ -58,6 +72,7 @@ export class SelectionFilter {
|
|
|
58
72
|
if (types === SelectionFilter.ALL) {
|
|
59
73
|
SelectionFilter.allowedSelectionTypes = SelectionFilter.ALL;
|
|
60
74
|
SelectionFilter.triggerUI();
|
|
75
|
+
SelectionFilter._ensureSceneSelectionHandlers();
|
|
61
76
|
SelectionFilter.#logAllowedTypesChange(SelectionFilter.allowedSelectionTypes, 'SetSelectionTypes');
|
|
62
77
|
return;
|
|
63
78
|
}
|
|
@@ -66,6 +81,7 @@ export class SelectionFilter {
|
|
|
66
81
|
if (invalid.length) throw new Error(`Unknown selection type(s): ${invalid.join(", ")}`);
|
|
67
82
|
SelectionFilter.allowedSelectionTypes = new Set(list);
|
|
68
83
|
SelectionFilter.triggerUI();
|
|
84
|
+
SelectionFilter._ensureSceneSelectionHandlers();
|
|
69
85
|
SelectionFilter.#logAllowedTypesChange(SelectionFilter.allowedSelectionTypes, 'SetSelectionTypes');
|
|
70
86
|
}
|
|
71
87
|
|
|
@@ -78,10 +94,63 @@ export class SelectionFilter {
|
|
|
78
94
|
SelectionFilter.allowedSelectionTypes = SelectionFilter.previouseAllowedSelectionTypes;
|
|
79
95
|
SelectionFilter.previouseAllowedSelectionTypes = null;
|
|
80
96
|
SelectionFilter.triggerUI();
|
|
97
|
+
SelectionFilter._ensureSceneSelectionHandlers();
|
|
81
98
|
SelectionFilter.#logAllowedTypesChange(SelectionFilter.allowedSelectionTypes, 'RestoreSelectionTypes');
|
|
82
99
|
}
|
|
83
100
|
}
|
|
84
101
|
|
|
102
|
+
static ensureSelectionHandlers(obj, { deep = false } = {}) {
|
|
103
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
104
|
+
let changed = false;
|
|
105
|
+
const attach = (target) => {
|
|
106
|
+
if (!target || typeof target !== 'object') return;
|
|
107
|
+
if (typeof target.onClick === 'function') return;
|
|
108
|
+
target.onClick = () => {
|
|
109
|
+
try {
|
|
110
|
+
if (target.type === SelectionFilter.SOLID && target.parent && target.parent.type === SelectionFilter.COMPONENT) {
|
|
111
|
+
const handledByParent = SelectionFilter.toggleSelection(target.parent);
|
|
112
|
+
if (!handledByParent) SelectionFilter.toggleSelection(target);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
SelectionFilter.toggleSelection(target);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
try { console.warn('[SelectionFilter] toggleSelection failed:', error); } catch (_) { /* ignore */ }
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
try { target.onClick.__brepSelectionHandler = true; } catch (_) { /* ignore */ }
|
|
121
|
+
changed = true;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (!deep) {
|
|
125
|
+
attach(obj);
|
|
126
|
+
return changed;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const stack = [obj];
|
|
130
|
+
while (stack.length) {
|
|
131
|
+
const current = stack.pop();
|
|
132
|
+
attach(current);
|
|
133
|
+
const kids = Array.isArray(current?.children) ? current.children : [];
|
|
134
|
+
for (const child of kids) {
|
|
135
|
+
if (child) stack.push(child);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return changed;
|
|
139
|
+
}
|
|
140
|
+
|
|
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
|
+
|
|
85
154
|
|
|
86
155
|
|
|
87
156
|
static allowType(type) {
|
|
@@ -640,7 +709,10 @@ export class SelectionFilter {
|
|
|
640
709
|
}
|
|
641
710
|
|
|
642
711
|
static set uiCallback(callback) { SelectionFilter._uiCallback = callback; }
|
|
643
|
-
static triggerUI() {
|
|
712
|
+
static triggerUI() {
|
|
713
|
+
if (SelectionFilter._uiCallback) SelectionFilter._uiCallback();
|
|
714
|
+
try { SelectionFilter._updateSelectionFilterIndicator(); } catch (_) { }
|
|
715
|
+
}
|
|
644
716
|
|
|
645
717
|
// Emit a global event so UI can react without polling
|
|
646
718
|
static _emitSelectionChanged() {
|
|
@@ -810,49 +882,52 @@ 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
|
-
if (result && typeof result === 'object' && result.show === false) continue;
|
|
821
|
-
const label = (result && typeof result === 'object' && result.label) || FeatureClass.longName || FeatureClass.shortName || FeatureClass.name || 'Feature';
|
|
822
|
-
const typeKey = FeatureClass.shortName || FeatureClass.type || FeatureClass.name || label;
|
|
823
|
-
const params = SelectionFilter._extractContextParams(result);
|
|
824
|
-
addSpec({
|
|
825
|
-
id: safeId('ctx-feature', typeKey),
|
|
826
|
-
label,
|
|
827
|
-
title: `Create ${label}`,
|
|
828
|
-
onClick: () => SelectionFilter._createFeatureFromContext(viewer, typeKey, params),
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
const constraintRegistry = viewer?.partHistory?.assemblyConstraintRegistry || null;
|
|
833
|
-
const constraintClasses = typeof constraintRegistry?.listAvailable === 'function'
|
|
834
|
-
? constraintRegistry.listAvailable()
|
|
835
|
-
: (typeof constraintRegistry?.list === 'function' ? constraintRegistry.list() : []);
|
|
836
|
-
if (Array.isArray(constraintClasses)) {
|
|
837
|
-
for (const ConstraintClass of constraintClasses) {
|
|
838
|
-
if (!ConstraintClass) continue;
|
|
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
|
+
for (const FeatureClass of features) {
|
|
891
|
+
if (!FeatureClass) continue;
|
|
839
892
|
let result = null;
|
|
840
|
-
try { result =
|
|
893
|
+
try { result = FeatureClass.showContexButton?.(items); } catch { result = null; }
|
|
841
894
|
if (!result) continue;
|
|
842
895
|
if (result && typeof result === 'object' && result.show === false) continue;
|
|
843
|
-
const label = (result && typeof result === 'object' && result.label) ||
|
|
844
|
-
const typeKey =
|
|
896
|
+
const label = (result && typeof result === 'object' && result.label) || FeatureClass.longName || FeatureClass.shortName || FeatureClass.name || 'Feature';
|
|
897
|
+
const typeKey = FeatureClass.shortName || FeatureClass.type || FeatureClass.name || label;
|
|
845
898
|
const params = SelectionFilter._extractContextParams(result);
|
|
846
899
|
addSpec({
|
|
847
|
-
id: safeId('ctx-
|
|
900
|
+
id: safeId('ctx-feature', typeKey),
|
|
848
901
|
label,
|
|
849
902
|
title: `Create ${label}`,
|
|
850
|
-
onClick: () => SelectionFilter.
|
|
903
|
+
onClick: () => SelectionFilter._createFeatureFromContext(viewer, typeKey, params),
|
|
851
904
|
});
|
|
852
905
|
}
|
|
906
|
+
|
|
907
|
+
const constraintRegistry = viewer?.partHistory?.assemblyConstraintRegistry || null;
|
|
908
|
+
const constraintClasses = typeof constraintRegistry?.listAvailable === 'function'
|
|
909
|
+
? constraintRegistry.listAvailable()
|
|
910
|
+
: (typeof constraintRegistry?.list === 'function' ? constraintRegistry.list() : []);
|
|
911
|
+
if (Array.isArray(constraintClasses)) {
|
|
912
|
+
for (const ConstraintClass of constraintClasses) {
|
|
913
|
+
if (!ConstraintClass) continue;
|
|
914
|
+
let result = null;
|
|
915
|
+
try { result = ConstraintClass.showContexButton?.(items); } catch { result = null; }
|
|
916
|
+
if (!result) continue;
|
|
917
|
+
if (result && typeof result === 'object' && result.show === false) continue;
|
|
918
|
+
const label = (result && typeof result === 'object' && result.label) || ConstraintClass.longName || ConstraintClass.shortName || ConstraintClass.name || 'Constraint';
|
|
919
|
+
const typeKey = ConstraintClass.constraintType || ConstraintClass.shortName || ConstraintClass.name || label;
|
|
920
|
+
const params = SelectionFilter._extractContextParams(result);
|
|
921
|
+
addSpec({
|
|
922
|
+
id: safeId('ctx-constraint', typeKey),
|
|
923
|
+
label,
|
|
924
|
+
title: `Create ${label}`,
|
|
925
|
+
onClick: () => SelectionFilter._createConstraintFromContext(viewer, typeKey, params),
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
853
929
|
}
|
|
854
930
|
|
|
855
|
-
const pmimode = viewer?._pmiMode || null;
|
|
856
931
|
const annotationRegistry = viewer?.annotationRegistry || null;
|
|
857
932
|
if (pmimode && annotationRegistry && typeof annotationRegistry.list === 'function') {
|
|
858
933
|
const annClasses = annotationRegistry.list();
|
|
@@ -1038,6 +1113,369 @@ export class SelectionFilter {
|
|
|
1038
1113
|
return bar;
|
|
1039
1114
|
}
|
|
1040
1115
|
|
|
1116
|
+
static _getSelectionFilterTypeList() {
|
|
1117
|
+
if (!SelectionFilter._selectionFilterTypes) {
|
|
1118
|
+
SelectionFilter._selectionFilterTypes = SelectionFilter.TYPES.filter((t) => t !== SelectionFilter.ALL);
|
|
1119
|
+
}
|
|
1120
|
+
return SelectionFilter._selectionFilterTypes;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
static _getSelectionFilterLabel(type) {
|
|
1124
|
+
const labels = {
|
|
1125
|
+
SOLID: 'Solid',
|
|
1126
|
+
COMPONENT: 'Component',
|
|
1127
|
+
FACE: 'Face',
|
|
1128
|
+
PLANE: 'Plane',
|
|
1129
|
+
SKETCH: 'Sketch',
|
|
1130
|
+
EDGE: 'Edge',
|
|
1131
|
+
LOOP: 'Loop',
|
|
1132
|
+
VERTEX: 'Vertex',
|
|
1133
|
+
};
|
|
1134
|
+
return labels[type] || type;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
static _summarizeSelectionFilter(types) {
|
|
1138
|
+
const list = Array.isArray(types) ? types : [];
|
|
1139
|
+
const allTypes = SelectionFilter._getSelectionFilterTypeList();
|
|
1140
|
+
if (list.length === 0) return 'None';
|
|
1141
|
+
if (list.length === allTypes.length) return 'All';
|
|
1142
|
+
return list.map((t) => SelectionFilter._getSelectionFilterLabel(t)).join(', ');
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
static _getAllowedTypeList() {
|
|
1146
|
+
const allTypes = SelectionFilter._getSelectionFilterTypeList();
|
|
1147
|
+
if (SelectionFilter.allowedSelectionTypes === SelectionFilter.ALL) return allTypes.slice();
|
|
1148
|
+
const allowed = new Set(Array.from(SelectionFilter.allowedSelectionTypes || []));
|
|
1149
|
+
return allTypes.filter((t) => allowed.has(t));
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
static _updateSelectionFilterIndicator() {
|
|
1153
|
+
const wrap = SelectionFilter._selectionFilterIndicator;
|
|
1154
|
+
if (!wrap) return;
|
|
1155
|
+
const toggle = SelectionFilter._selectionFilterIndicatorToggle;
|
|
1156
|
+
const types = SelectionFilter._getAllowedTypeList();
|
|
1157
|
+
if (SelectionFilter._selectionFilterCheckboxes && SelectionFilter._selectionFilterCheckboxes.size) {
|
|
1158
|
+
const set = new Set(types);
|
|
1159
|
+
for (const [type, cb] of SelectionFilter._selectionFilterCheckboxes.entries()) {
|
|
1160
|
+
if (cb) cb.checked = set.has(type);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
if (toggle) {
|
|
1164
|
+
toggle.textContent = `Selection filter: ${SelectionFilter._summarizeSelectionFilter(types)}`;
|
|
1165
|
+
}
|
|
1166
|
+
SelectionFilter._updateSelectableTintButton();
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
static _getSelectableTintTargets() {
|
|
1170
|
+
const allowed = SelectionFilter.allowedSelectionTypes;
|
|
1171
|
+
const allowAll = allowed === SelectionFilter.ALL;
|
|
1172
|
+
const allowFace = allowAll || (allowed && typeof allowed.has === 'function' && allowed.has(SelectionFilter.FACE));
|
|
1173
|
+
const allowEdge = allowAll || (allowed && typeof allowed.has === 'function' && allowed.has(SelectionFilter.EDGE));
|
|
1174
|
+
return { allowFace, allowEdge };
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
static _updateSelectableTintButton() {
|
|
1178
|
+
const btn = SelectionFilter._selectionFilterTintBtn;
|
|
1179
|
+
if (!btn) return;
|
|
1180
|
+
const state = SelectionFilter._selectableTintState;
|
|
1181
|
+
const active = !!state?.active;
|
|
1182
|
+
const { allowFace, allowEdge } = SelectionFilter._getSelectableTintTargets();
|
|
1183
|
+
const hasTargets = allowFace || allowEdge;
|
|
1184
|
+
const colors = Array.isArray(state?.colors) && state.colors.length ? state.colors : ['#60a5fa'];
|
|
1185
|
+
const nextColor = colors[(state?.colorIndex ?? 0) % colors.length] || '#60a5fa';
|
|
1186
|
+
const displayColor = active ? (state?.activeColor || nextColor) : nextColor;
|
|
1187
|
+
btn.classList.toggle('is-active', active);
|
|
1188
|
+
btn.style.setProperty('--sfi-tint', displayColor);
|
|
1189
|
+
btn.textContent = active ? 'Reset selectable tint' : 'Tint selectable';
|
|
1190
|
+
btn.disabled = !active && !hasTargets;
|
|
1191
|
+
btn.title = active
|
|
1192
|
+
? 'Restore original face/edge colors'
|
|
1193
|
+
: (hasTargets ? 'Tint selectable faces and edges' : 'Enable Face or Edge selection to tint');
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
static _applySelectableTint(scene, { allowFace, allowEdge, faceColor, edgeColor }) {
|
|
1197
|
+
if (!scene || (!allowFace && !allowEdge)) return;
|
|
1198
|
+
const state = SelectionFilter._selectableTintState;
|
|
1199
|
+
const storeColor = (mat) => {
|
|
1200
|
+
if (!mat || !mat.color || typeof mat.color.getHexString !== 'function') return;
|
|
1201
|
+
if (state.materials.has(mat)) return;
|
|
1202
|
+
try { state.materials.set(mat, `#${mat.color.getHexString()}`); } catch { }
|
|
1203
|
+
};
|
|
1204
|
+
const tintMaterial = (mat, color) => {
|
|
1205
|
+
if (!mat || !mat.color || typeof mat.color.set !== 'function') return;
|
|
1206
|
+
storeColor(mat);
|
|
1207
|
+
try { mat.color.set(color); } catch { }
|
|
1208
|
+
try { mat.needsUpdate = true; } catch { }
|
|
1209
|
+
};
|
|
1210
|
+
const tintObject = (obj, color) => {
|
|
1211
|
+
if (!obj || obj.visible === false) return;
|
|
1212
|
+
if (obj.selected === true) return;
|
|
1213
|
+
const mat = obj.material;
|
|
1214
|
+
if (Array.isArray(mat)) {
|
|
1215
|
+
for (const m of mat) tintMaterial(m, color);
|
|
1216
|
+
} else {
|
|
1217
|
+
tintMaterial(mat, color);
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
const isPreview = (obj) => {
|
|
1221
|
+
if (!obj) return true;
|
|
1222
|
+
if (obj.userData?.refPreview) return true;
|
|
1223
|
+
const name = typeof obj.name === 'string' ? obj.name : '';
|
|
1224
|
+
const type = typeof obj.type === 'string' ? obj.type : '';
|
|
1225
|
+
if (name.startsWith('__refPreview__')) return true;
|
|
1226
|
+
if (type.startsWith('REF_PREVIEW')) return true;
|
|
1227
|
+
return false;
|
|
1228
|
+
};
|
|
1229
|
+
scene.traverse((obj) => {
|
|
1230
|
+
if (!obj || isPreview(obj)) return;
|
|
1231
|
+
if (allowFace && obj.type === SelectionFilter.FACE) {
|
|
1232
|
+
tintObject(obj, faceColor);
|
|
1233
|
+
} else if (allowEdge && obj.type === SelectionFilter.EDGE) {
|
|
1234
|
+
tintObject(obj, edgeColor);
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
static _restoreSelectableTint() {
|
|
1240
|
+
const state = SelectionFilter._selectableTintState;
|
|
1241
|
+
if (!state || !state.materials) return;
|
|
1242
|
+
for (const [mat, color] of state.materials.entries()) {
|
|
1243
|
+
if (!mat || !mat.color || typeof mat.color.set !== 'function') continue;
|
|
1244
|
+
if (!color) continue;
|
|
1245
|
+
try { mat.color.set(color); } catch { }
|
|
1246
|
+
try { mat.needsUpdate = true; } catch { }
|
|
1247
|
+
}
|
|
1248
|
+
state.materials.clear();
|
|
1249
|
+
state.active = false;
|
|
1250
|
+
state.activeColor = null;
|
|
1251
|
+
SelectionFilter._updateSelectableTintButton();
|
|
1252
|
+
try { SelectionFilter.viewer?.render?.(); } catch { }
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
static _toggleSelectableTint(viewer) {
|
|
1256
|
+
const state = SelectionFilter._selectableTintState;
|
|
1257
|
+
if (!state) return;
|
|
1258
|
+
if (state.active) {
|
|
1259
|
+
SelectionFilter._restoreSelectableTint();
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
const { allowFace, allowEdge } = SelectionFilter._getSelectableTintTargets();
|
|
1263
|
+
if (!allowFace && !allowEdge) return;
|
|
1264
|
+
const scene = viewer?.partHistory?.scene || viewer?.scene || SelectionFilter.viewer?.partHistory?.scene || SelectionFilter.viewer?.scene || null;
|
|
1265
|
+
if (!scene) return;
|
|
1266
|
+
const colors = Array.isArray(state.colors) && state.colors.length ? state.colors : ['#60a5fa'];
|
|
1267
|
+
const color = colors[state.colorIndex % colors.length] || '#60a5fa';
|
|
1268
|
+
state.colorIndex = (state.colorIndex + 1) % colors.length;
|
|
1269
|
+
SelectionFilter._applySelectableTint(scene, {
|
|
1270
|
+
allowFace,
|
|
1271
|
+
allowEdge,
|
|
1272
|
+
faceColor: color,
|
|
1273
|
+
edgeColor: color,
|
|
1274
|
+
});
|
|
1275
|
+
state.active = true;
|
|
1276
|
+
state.activeColor = color;
|
|
1277
|
+
SelectionFilter._updateSelectableTintButton();
|
|
1278
|
+
try { (viewer || SelectionFilter.viewer)?.render?.(); } catch { }
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
static _ensureSelectionFilterIndicator(viewer) {
|
|
1282
|
+
if (SelectionFilter._selectionFilterIndicator && SelectionFilter._selectionFilterIndicator.isConnected) {
|
|
1283
|
+
SelectionFilter._updateSelectionFilterIndicator();
|
|
1284
|
+
return SelectionFilter._selectionFilterIndicator;
|
|
1285
|
+
}
|
|
1286
|
+
if (typeof document === 'undefined') return null;
|
|
1287
|
+
const host = viewer?.container || document.body || null;
|
|
1288
|
+
if (!host) return null;
|
|
1289
|
+
try {
|
|
1290
|
+
if (!document.getElementById('selection-filter-indicator-styles')) {
|
|
1291
|
+
const style = document.createElement('style');
|
|
1292
|
+
style.id = 'selection-filter-indicator-styles';
|
|
1293
|
+
style.textContent = `
|
|
1294
|
+
.selection-filter-indicator {
|
|
1295
|
+
position: fixed;
|
|
1296
|
+
bottom: 8px;
|
|
1297
|
+
left: 50%;
|
|
1298
|
+
transform: translateX(-50%);
|
|
1299
|
+
display: flex;
|
|
1300
|
+
flex-direction: column;
|
|
1301
|
+
gap: 6px;
|
|
1302
|
+
background: rgba(20,24,30,.85);
|
|
1303
|
+
border: 1px solid #262b36;
|
|
1304
|
+
border-radius: 10px;
|
|
1305
|
+
padding: 6px;
|
|
1306
|
+
color: #ddd;
|
|
1307
|
+
z-index: 12;
|
|
1308
|
+
user-select: none;
|
|
1309
|
+
min-width: 220px;
|
|
1310
|
+
max-width: min(440px, calc(100vw - 16px));
|
|
1311
|
+
box-shadow: 0 6px 18px rgba(0,0,0,.35);
|
|
1312
|
+
}
|
|
1313
|
+
.selection-filter-indicator .sfi-toggle {
|
|
1314
|
+
background: transparent;
|
|
1315
|
+
border-radius: 8px;
|
|
1316
|
+
padding: 6px 10px;
|
|
1317
|
+
width: 100%;
|
|
1318
|
+
min-height: 32px;
|
|
1319
|
+
box-sizing: border-box;
|
|
1320
|
+
color: #ddd;
|
|
1321
|
+
border: 1px solid #364053;
|
|
1322
|
+
cursor: pointer;
|
|
1323
|
+
text-align: left;
|
|
1324
|
+
}
|
|
1325
|
+
.selection-filter-indicator .sfi-toggle:hover { filter: brightness(1.08); }
|
|
1326
|
+
.selection-filter-indicator .sfi-toggle:active { filter: brightness(1.15); }
|
|
1327
|
+
.selection-filter-indicator .sfi-panel {
|
|
1328
|
+
border: 1px solid #2b3240;
|
|
1329
|
+
border-radius: 8px;
|
|
1330
|
+
padding: 8px 10px;
|
|
1331
|
+
background: rgba(17,22,31,.95);
|
|
1332
|
+
}
|
|
1333
|
+
.selection-filter-indicator .sfi-panel[hidden] { display: none; }
|
|
1334
|
+
.selection-filter-indicator .sfi-list {
|
|
1335
|
+
display: grid;
|
|
1336
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1337
|
+
gap: 6px 10px;
|
|
1338
|
+
}
|
|
1339
|
+
.selection-filter-indicator .sfi-option {
|
|
1340
|
+
display: flex;
|
|
1341
|
+
align-items: center;
|
|
1342
|
+
gap: 6px;
|
|
1343
|
+
font-size: 12px;
|
|
1344
|
+
color: #cbd5e1;
|
|
1345
|
+
}
|
|
1346
|
+
.selection-filter-indicator input[type="checkbox"] {
|
|
1347
|
+
width: 16px;
|
|
1348
|
+
height: 16px;
|
|
1349
|
+
accent-color: #60a5fa;
|
|
1350
|
+
}
|
|
1351
|
+
.selection-filter-indicator .sfi-actions {
|
|
1352
|
+
display: flex;
|
|
1353
|
+
gap: 6px;
|
|
1354
|
+
margin-top: 8px;
|
|
1355
|
+
}
|
|
1356
|
+
.selection-filter-indicator .sfi-btn {
|
|
1357
|
+
flex: 1;
|
|
1358
|
+
background: rgba(255,255,255,.04);
|
|
1359
|
+
border: 1px solid #364053;
|
|
1360
|
+
border-radius: 8px;
|
|
1361
|
+
color: #e2e8f0;
|
|
1362
|
+
padding: 6px 10px;
|
|
1363
|
+
font-size: 12px;
|
|
1364
|
+
cursor: pointer;
|
|
1365
|
+
text-align: center;
|
|
1366
|
+
min-height: 28px;
|
|
1367
|
+
}
|
|
1368
|
+
.selection-filter-indicator .sfi-btn:hover { filter: brightness(1.08); }
|
|
1369
|
+
.selection-filter-indicator .sfi-btn:active { filter: brightness(1.15); }
|
|
1370
|
+
.selection-filter-indicator .sfi-btn.is-active {
|
|
1371
|
+
border-color: var(--sfi-tint, #60a5fa);
|
|
1372
|
+
color: var(--sfi-tint, #60a5fa);
|
|
1373
|
+
}
|
|
1374
|
+
`;
|
|
1375
|
+
document.head.appendChild(style);
|
|
1376
|
+
}
|
|
1377
|
+
} catch { }
|
|
1378
|
+
|
|
1379
|
+
const wrap = document.createElement('div');
|
|
1380
|
+
wrap.className = 'selection-filter-indicator';
|
|
1381
|
+
|
|
1382
|
+
const toggle = document.createElement('button');
|
|
1383
|
+
toggle.type = 'button';
|
|
1384
|
+
toggle.className = 'sfi-toggle';
|
|
1385
|
+
const panelId = `selection-filter-panel-${Math.random().toString(36).slice(2, 8)}`;
|
|
1386
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
1387
|
+
toggle.setAttribute('aria-controls', panelId);
|
|
1388
|
+
wrap.appendChild(toggle);
|
|
1389
|
+
|
|
1390
|
+
const panel = document.createElement('div');
|
|
1391
|
+
panel.className = 'sfi-panel';
|
|
1392
|
+
panel.id = panelId;
|
|
1393
|
+
panel.hidden = true;
|
|
1394
|
+
|
|
1395
|
+
const list = document.createElement('div');
|
|
1396
|
+
list.className = 'sfi-list';
|
|
1397
|
+
panel.appendChild(list);
|
|
1398
|
+
|
|
1399
|
+
const checkboxByType = new Map();
|
|
1400
|
+
const types = SelectionFilter._getSelectionFilterTypeList();
|
|
1401
|
+
for (const type of types) {
|
|
1402
|
+
const option = document.createElement('label');
|
|
1403
|
+
option.className = 'sfi-option';
|
|
1404
|
+
|
|
1405
|
+
const box = document.createElement('input');
|
|
1406
|
+
box.type = 'checkbox';
|
|
1407
|
+
box.dataset.type = type;
|
|
1408
|
+
box.addEventListener('click', (ev) => ev.stopPropagation());
|
|
1409
|
+
box.addEventListener('change', (ev) => {
|
|
1410
|
+
ev.stopPropagation();
|
|
1411
|
+
const next = [];
|
|
1412
|
+
for (const t of types) {
|
|
1413
|
+
const cb = checkboxByType.get(t);
|
|
1414
|
+
if (cb && cb.checked) next.push(t);
|
|
1415
|
+
}
|
|
1416
|
+
const nextValue = next.length === types.length ? SelectionFilter.ALL : next;
|
|
1417
|
+
try { SelectionFilter.SetSelectionTypes(nextValue); } catch { }
|
|
1418
|
+
if (SelectionFilter.previouseAllowedSelectionTypes !== null) {
|
|
1419
|
+
SelectionFilter.previouseAllowedSelectionTypes = SelectionFilter.allowedSelectionTypes;
|
|
1420
|
+
}
|
|
1421
|
+
SelectionFilter._updateSelectionFilterIndicator();
|
|
1422
|
+
});
|
|
1423
|
+
|
|
1424
|
+
const label = document.createElement('span');
|
|
1425
|
+
label.textContent = SelectionFilter._getSelectionFilterLabel(type);
|
|
1426
|
+
|
|
1427
|
+
option.appendChild(box);
|
|
1428
|
+
option.appendChild(label);
|
|
1429
|
+
list.appendChild(option);
|
|
1430
|
+
checkboxByType.set(type, box);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
const actions = document.createElement('div');
|
|
1434
|
+
actions.className = 'sfi-actions';
|
|
1435
|
+
const tintBtn = document.createElement('button');
|
|
1436
|
+
tintBtn.type = 'button';
|
|
1437
|
+
tintBtn.className = 'sfi-btn';
|
|
1438
|
+
tintBtn.addEventListener('click', (ev) => {
|
|
1439
|
+
ev.stopPropagation();
|
|
1440
|
+
SelectionFilter._toggleSelectableTint(viewer);
|
|
1441
|
+
});
|
|
1442
|
+
actions.appendChild(tintBtn);
|
|
1443
|
+
panel.appendChild(actions);
|
|
1444
|
+
|
|
1445
|
+
toggle.addEventListener('click', (ev) => {
|
|
1446
|
+
ev.stopPropagation();
|
|
1447
|
+
const nextOpen = panel.hidden;
|
|
1448
|
+
panel.hidden = !nextOpen;
|
|
1449
|
+
toggle.setAttribute('aria-expanded', String(nextOpen));
|
|
1450
|
+
if (nextOpen) SelectionFilter._updateSelectionFilterIndicator();
|
|
1451
|
+
});
|
|
1452
|
+
panel.addEventListener('click', (ev) => ev.stopPropagation());
|
|
1453
|
+
|
|
1454
|
+
if (!SelectionFilter._selectionFilterOutsideBound) {
|
|
1455
|
+
SelectionFilter._selectionFilterOutsideBound = true;
|
|
1456
|
+
document.addEventListener('mousedown', (ev) => {
|
|
1457
|
+
const panelEl = SelectionFilter._selectionFilterIndicatorPanel;
|
|
1458
|
+
const toggleEl = SelectionFilter._selectionFilterIndicatorToggle;
|
|
1459
|
+
const wrapEl = SelectionFilter._selectionFilterIndicator;
|
|
1460
|
+
if (wrapEl && ev && wrapEl.contains(ev.target)) return;
|
|
1461
|
+
if (!panelEl || panelEl.hidden) return;
|
|
1462
|
+
panelEl.hidden = true;
|
|
1463
|
+
if (toggleEl) toggleEl.setAttribute('aria-expanded', 'false');
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
wrap.appendChild(panel);
|
|
1468
|
+
host.appendChild(wrap);
|
|
1469
|
+
|
|
1470
|
+
SelectionFilter._selectionFilterIndicator = wrap;
|
|
1471
|
+
SelectionFilter._selectionFilterIndicatorToggle = toggle;
|
|
1472
|
+
SelectionFilter._selectionFilterIndicatorPanel = panel;
|
|
1473
|
+
SelectionFilter._selectionFilterCheckboxes = checkboxByType;
|
|
1474
|
+
SelectionFilter._selectionFilterTintBtn = tintBtn;
|
|
1475
|
+
SelectionFilter._updateSelectionFilterIndicator();
|
|
1476
|
+
return wrap;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1041
1479
|
static _createSelectionActionButton(entry) {
|
|
1042
1480
|
try {
|
|
1043
1481
|
const btn = document.createElement('button');
|
|
@@ -164,6 +164,7 @@ export class AssemblyConstraintCollectionWidget extends HistoryCollectionWidget
|
|
|
164
164
|
|
|
165
165
|
this.partHistory = partHistory || null;
|
|
166
166
|
this.viewer = viewer || null;
|
|
167
|
+
this._autoFocusOnExpand = true;
|
|
167
168
|
this._highlightCallback = typeof onHighlightRequest === 'function' ? onHighlightRequest : null;
|
|
168
169
|
this._clearHighlightCallback = typeof onClearHighlight === 'function' ? onClearHighlight : null;
|
|
169
170
|
this._beforeConstraintChangeHandler = callBeforeChange;
|
|
@@ -209,6 +209,10 @@ export class AssemblyConstraintsWidget {
|
|
|
209
209
|
this._scheduleSync();
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
collapseExpandedDialogs() {
|
|
213
|
+
try { this._constraintList?.collapseExpandedEntries?.({ clearOpenState: true }); } catch { /* ignore */ }
|
|
214
|
+
}
|
|
215
|
+
|
|
212
216
|
#handleHistoryChange() {
|
|
213
217
|
this._scheduleSync();
|
|
214
218
|
if (this._ignoreFullSolveChangeCount > 0) {
|