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,185 +1,6 @@
|
|
|
1
|
+
import { resolveSelectionObject as resolveSelectionObjectBase, scoreObjectForNormal } from '../../utils/selectionResolver.js';
|
|
2
|
+
|
|
1
3
|
export function resolveSelectionObject(scene, selection, options = {}) {
|
|
2
4
|
const scoreFn = typeof options.scoreFn === 'function' ? options.scoreFn : scoreObjectForNormal;
|
|
3
|
-
return
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
function internalResolveSelectionObject(scene, selection, options) {
|
|
7
|
-
if (!scene || selection == null) return null;
|
|
8
|
-
if (selection.isObject3D) return selection;
|
|
9
|
-
|
|
10
|
-
if (Array.isArray(selection)) {
|
|
11
|
-
for (const item of selection) {
|
|
12
|
-
const resolved = internalResolveSelectionObject(scene, item, options);
|
|
13
|
-
if (resolved) return resolved;
|
|
14
|
-
}
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (typeof selection === 'string') {
|
|
19
|
-
return resolveObjectFromString(scene, selection, options);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (typeof selection === 'object') {
|
|
23
|
-
if (selection.isObject3D) return selection;
|
|
24
|
-
const {
|
|
25
|
-
uuid,
|
|
26
|
-
name,
|
|
27
|
-
id,
|
|
28
|
-
path,
|
|
29
|
-
reference,
|
|
30
|
-
target,
|
|
31
|
-
} = selection;
|
|
32
|
-
|
|
33
|
-
if (typeof uuid === 'string') {
|
|
34
|
-
try {
|
|
35
|
-
const found = scene.getObjectByProperty?.('uuid', uuid);
|
|
36
|
-
if (found) return found;
|
|
37
|
-
} catch { /* ignore */ }
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const resolveCandidate = (candidate) => (
|
|
41
|
-
typeof candidate === 'string'
|
|
42
|
-
? resolveObjectFromString(scene, candidate, options)
|
|
43
|
-
: null
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
const nameCandidate = typeof name === 'string'
|
|
47
|
-
? name
|
|
48
|
-
: (typeof selection?.selectionName === 'string' ? selection.selectionName : null);
|
|
49
|
-
const idCandidate = typeof id === 'string' ? id : null;
|
|
50
|
-
|
|
51
|
-
const nameResolved = resolveCandidate(nameCandidate);
|
|
52
|
-
if (nameResolved) return nameResolved;
|
|
53
|
-
|
|
54
|
-
const idResolved = resolveCandidate(idCandidate);
|
|
55
|
-
if (idResolved) return idResolved;
|
|
56
|
-
|
|
57
|
-
if (Array.isArray(path)) {
|
|
58
|
-
for (let i = path.length - 1; i >= 0; i -= 1) {
|
|
59
|
-
const segment = path[i];
|
|
60
|
-
if (typeof segment !== 'string') continue;
|
|
61
|
-
const resolved = resolveObjectFromString(scene, segment, options);
|
|
62
|
-
if (resolved) return resolved;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (reference != null) {
|
|
67
|
-
const resolved = internalResolveSelectionObject(scene, reference, options);
|
|
68
|
-
if (resolved) return resolved;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (target != null) {
|
|
72
|
-
const resolved = internalResolveSelectionObject(scene, target, options);
|
|
73
|
-
if (resolved) return resolved;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function resolveObjectFromString(scene, value, options) {
|
|
81
|
-
if (!scene || typeof value !== 'string') return null;
|
|
82
|
-
const trimmed = value.trim();
|
|
83
|
-
if (!trimmed) return null;
|
|
84
|
-
|
|
85
|
-
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
86
|
-
try {
|
|
87
|
-
const parsed = JSON.parse(trimmed);
|
|
88
|
-
if (parsed != null) {
|
|
89
|
-
const resolved = internalResolveSelectionObject(scene, parsed, options);
|
|
90
|
-
if (resolved) return resolved;
|
|
91
|
-
}
|
|
92
|
-
} catch { /* ignore JSON parse errors */ }
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const direct = findObjectByName(scene, trimmed, options.scoreFn);
|
|
96
|
-
if (direct) return direct;
|
|
97
|
-
|
|
98
|
-
if (looksLikeUUID(trimmed)) {
|
|
99
|
-
try {
|
|
100
|
-
const byUuid = scene.getObjectByProperty?.('uuid', trimmed);
|
|
101
|
-
if (byUuid) return byUuid;
|
|
102
|
-
} catch { /* ignore */ }
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const candidates = new Set();
|
|
106
|
-
candidates.add(trimmed);
|
|
107
|
-
|
|
108
|
-
const splitByDelims = trimmed.split(/›|>|\/|\||→|->/);
|
|
109
|
-
if (splitByDelims.length > 1) {
|
|
110
|
-
for (const segment of splitByDelims) {
|
|
111
|
-
const s = segment.trim();
|
|
112
|
-
if (s) candidates.add(s);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (trimmed.includes(':')) {
|
|
117
|
-
for (const segment of trimmed.split(':')) {
|
|
118
|
-
const s = segment.trim();
|
|
119
|
-
if (s) candidates.add(s);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
for (const candidate of candidates) {
|
|
124
|
-
const found = findObjectByName(scene, candidate, options.scoreFn);
|
|
125
|
-
if (found) return found;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
let fallback = null;
|
|
129
|
-
try {
|
|
130
|
-
scene.traverse?.((obj) => {
|
|
131
|
-
if (fallback || !obj?.name) return;
|
|
132
|
-
if (!trimmed.includes(obj.name)) return;
|
|
133
|
-
if (!fallback) {
|
|
134
|
-
fallback = obj;
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
const currentScore = options.scoreFn(fallback);
|
|
138
|
-
const nextScore = options.scoreFn(obj);
|
|
139
|
-
if (nextScore > currentScore || obj.name.length > fallback.name.length) {
|
|
140
|
-
fallback = obj;
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
} catch { /* ignore */ }
|
|
144
|
-
|
|
145
|
-
return fallback;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function findObjectByName(scene, name, scoreFn) {
|
|
149
|
-
if (!scene || typeof name !== 'string' || !name) return null;
|
|
150
|
-
|
|
151
|
-
if (typeof scene.traverse !== 'function') {
|
|
152
|
-
return scene?.getObjectByName?.(name) || null;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
let best = null;
|
|
156
|
-
scene.traverse((obj) => {
|
|
157
|
-
if (!obj || obj.name !== name) return;
|
|
158
|
-
if (!best) {
|
|
159
|
-
best = obj;
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const currentScore = scoreFn(best);
|
|
163
|
-
const newScore = scoreFn(obj);
|
|
164
|
-
if (newScore > currentScore) best = obj;
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
if (best) return best;
|
|
168
|
-
if (typeof scene.getObjectByName === 'function') return scene.getObjectByName(name);
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function looksLikeUUID(value) {
|
|
173
|
-
if (typeof value !== 'string') return false;
|
|
174
|
-
const trimmed = value.trim();
|
|
175
|
-
if (trimmed.length !== 36) return false;
|
|
176
|
-
return /^[0-9a-fA-F-]{36}$/.test(trimmed);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function scoreObjectForNormal(object) {
|
|
180
|
-
if (!object) return -Infinity;
|
|
181
|
-
const type = object.userData?.type || object.userData?.brepType || object.type;
|
|
182
|
-
if (String(type).toUpperCase() === 'FACE') return 3;
|
|
183
|
-
if (object.geometry) return 2;
|
|
184
|
-
return 1;
|
|
5
|
+
return resolveSelectionObjectBase(scene, selection, { ...options, scoreFn });
|
|
185
6
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
-
import { objectRepresentativePoint } from '
|
|
2
|
+
import { objectRepresentativePoint } from './pmi/annUtils.js';
|
|
3
3
|
|
|
4
4
|
export function isFaceObject(object) {
|
|
5
5
|
if (!object) return false;
|
|
@@ -35,6 +35,21 @@ export function computeFaceOrigin(object) {
|
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export function computeFaceCenter(object) {
|
|
39
|
+
if (!object) return null;
|
|
40
|
+
try { object.updateMatrixWorld?.(true); } catch { /* ignore */ }
|
|
41
|
+
try {
|
|
42
|
+
const box = new THREE.Box3().setFromObject(object);
|
|
43
|
+
if (!box.isEmpty()) return box.getCenter(new THREE.Vector3());
|
|
44
|
+
} catch { /* ignore */ }
|
|
45
|
+
try {
|
|
46
|
+
const geom = object.geometry;
|
|
47
|
+
const bs = geom?.boundingSphere || (geom?.computeBoundingSphere && (geom.computeBoundingSphere(), geom.boundingSphere));
|
|
48
|
+
if (bs) return object.localToWorld(bs.center.clone());
|
|
49
|
+
} catch { /* ignore */ }
|
|
50
|
+
return computeFaceOrigin(object);
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
export function computeFaceNormal(object) {
|
|
39
54
|
if (!object) return null;
|
|
40
55
|
try {
|
|
@@ -45,9 +60,11 @@ export function computeFaceNormal(object) {
|
|
|
45
60
|
} catch { /* ignore */ }
|
|
46
61
|
|
|
47
62
|
const geom = object.geometry;
|
|
48
|
-
if (!geom?.isBufferGeometry)
|
|
63
|
+
if (!geom?.isBufferGeometry) {
|
|
64
|
+
return fallbackQuaternionNormal(object);
|
|
65
|
+
}
|
|
49
66
|
const pos = geom.getAttribute?.('position');
|
|
50
|
-
if (!pos || pos.itemSize !== 3 || pos.count < 3) return
|
|
67
|
+
if (!pos || pos.itemSize !== 3 || pos.count < 3) return fallbackQuaternionNormal(object);
|
|
51
68
|
const index = geom.getIndex?.();
|
|
52
69
|
|
|
53
70
|
const v0 = new THREE.Vector3();
|
|
@@ -94,10 +111,10 @@ export function computeFaceNormal(object) {
|
|
|
94
111
|
}
|
|
95
112
|
}
|
|
96
113
|
|
|
97
|
-
if (count === 0) return
|
|
114
|
+
if (count === 0) return fallbackQuaternionNormal(object);
|
|
98
115
|
|
|
99
116
|
accum.divideScalar(count);
|
|
100
|
-
if (accum.lengthSq() <= 1e-10) return
|
|
117
|
+
if (accum.lengthSq() <= 1e-10) return fallbackQuaternionNormal(object);
|
|
101
118
|
|
|
102
119
|
return accum.normalize();
|
|
103
120
|
}
|
|
@@ -113,3 +130,11 @@ export function estimateArrowLength(object) {
|
|
|
113
130
|
}
|
|
114
131
|
return 10;
|
|
115
132
|
}
|
|
133
|
+
|
|
134
|
+
function fallbackQuaternionNormal(object) {
|
|
135
|
+
try {
|
|
136
|
+
const q = object?.getWorldQuaternion?.(new THREE.Quaternion());
|
|
137
|
+
if (q) return new THREE.Vector3(0, 0, 1).applyQuaternion(q).normalize();
|
|
138
|
+
} catch { /* ignore */ }
|
|
139
|
+
return null;
|
|
140
|
+
}
|
package/src/UI/featureDialogs.js
CHANGED
|
@@ -1047,50 +1047,50 @@ export class SchemaForm {
|
|
|
1047
1047
|
for (const name of list) {
|
|
1048
1048
|
if (!name) continue;
|
|
1049
1049
|
const existing = cache.get(name) || null;
|
|
1050
|
-
const snapshot = store ? store[name] : null;
|
|
1051
|
-
if (snapshot) {
|
|
1052
|
-
const snapType = String(snapshot.type || '').toUpperCase();
|
|
1053
|
-
const snapEdges = Array.isArray(snapshot.edgePositions) ? snapshot.edgePositions : null;
|
|
1054
|
-
const isFaceSnap = (snapType === 'FACE' || snapType === 'PLANE') && snapEdges && snapEdges.length;
|
|
1055
|
-
const isEdgeSnap = snapType === 'EDGE' && Array.isArray(snapshot.positions) && snapshot.positions.length >= 6;
|
|
1056
|
-
const isVertexSnap = snapType === 'VERTEX' && Array.isArray(snapshot.position) && snapshot.position.length >= 3;
|
|
1057
|
-
if (isFaceSnap || isEdgeSnap || isVertexSnap) {
|
|
1058
|
-
const shouldOverride = !existing || !existing.fromSnapshot || isFaceSnap;
|
|
1059
|
-
if (shouldOverride) {
|
|
1060
|
-
const ghost = this._buildReferencePreviewFromSnapshot(name, snapshot);
|
|
1061
|
-
if (ghost) {
|
|
1062
|
-
cache.set(name, {
|
|
1063
|
-
object: ghost,
|
|
1064
|
-
type: snapshot.type || null,
|
|
1065
|
-
sourceUuid: snapshot.sourceUuid || null,
|
|
1066
|
-
sourceFeatureId: snapshot.sourceFeatureId || null,
|
|
1067
|
-
showWhenOriginalPresent: isFaceSnap || !!ghost?.userData?.previewHasEdges,
|
|
1068
|
-
fromSnapshot: true,
|
|
1069
|
-
});
|
|
1070
|
-
continue;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
if (cache.has(name)) continue;
|
|
1076
1050
|
const obj = scene.getObjectByName(name);
|
|
1077
1051
|
if (obj && !obj?.userData?.refPreview) {
|
|
1078
|
-
|
|
1052
|
+
const objType = String(obj.type || '').toUpperCase();
|
|
1053
|
+
const isEdgeObj = objType === SelectionFilter.EDGE || objType === 'EDGE';
|
|
1054
|
+
const objTimestamp = (obj.timestamp ?? obj.userData?.timestamp ?? null);
|
|
1055
|
+
const existingTimestamp = existing?.sourceTimestamp ?? null;
|
|
1056
|
+
const shouldRefresh = !existing
|
|
1057
|
+
|| existing.fromSnapshot
|
|
1058
|
+
|| (!!existing?.sourceUuid && !!obj.uuid && existing.sourceUuid !== obj.uuid)
|
|
1059
|
+
|| (!existing?.sourceUuid && !!obj.uuid)
|
|
1060
|
+
|| (objTimestamp != null && existingTimestamp !== objTimestamp)
|
|
1061
|
+
|| (isEdgeObj && !existing?.showWhenOriginalPresent);
|
|
1062
|
+
if (shouldRefresh) {
|
|
1063
|
+
this._storeReferencePreviewSnapshot(inputEl, def, obj);
|
|
1064
|
+
}
|
|
1079
1065
|
continue;
|
|
1080
1066
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1067
|
+
const snapshot = store ? store[name] : null;
|
|
1068
|
+
if (!snapshot) continue;
|
|
1069
|
+
const snapType = String(snapshot.type || '').toUpperCase();
|
|
1070
|
+
const snapEdges = Array.isArray(snapshot.edgePositions) ? snapshot.edgePositions : null;
|
|
1071
|
+
const isFaceSnap = (snapType === 'FACE' || snapType === 'PLANE') && snapEdges && snapEdges.length;
|
|
1072
|
+
const isEdgeSnap = snapType === 'EDGE' && Array.isArray(snapshot.positions) && snapshot.positions.length >= 6;
|
|
1073
|
+
const isVertexSnap = snapType === 'VERTEX' && Array.isArray(snapshot.position) && snapshot.position.length >= 3;
|
|
1074
|
+
if (!(isFaceSnap || isEdgeSnap || isVertexSnap)) continue;
|
|
1075
|
+
const snapTimestamp = snapshot.sourceTimestamp ?? null;
|
|
1076
|
+
const shouldOverride = !existing
|
|
1077
|
+
|| !existing.fromSnapshot
|
|
1078
|
+
|| isFaceSnap
|
|
1079
|
+
|| (!!snapshot.sourceUuid && !!existing?.sourceUuid && snapshot.sourceUuid !== existing.sourceUuid)
|
|
1080
|
+
|| (snapTimestamp != null && (existing?.sourceTimestamp ?? null) !== snapTimestamp)
|
|
1081
|
+
|| (isEdgeSnap && !existing?.showWhenOriginalPresent);
|
|
1082
|
+
if (!shouldOverride) continue;
|
|
1083
|
+
const ghost = this._buildReferencePreviewFromSnapshot(name, snapshot);
|
|
1084
|
+
if (ghost) {
|
|
1085
|
+
cache.set(name, {
|
|
1086
|
+
object: ghost,
|
|
1087
|
+
type: snapshot.type || null,
|
|
1088
|
+
sourceUuid: snapshot.sourceUuid || null,
|
|
1089
|
+
sourceFeatureId: snapshot.sourceFeatureId || null,
|
|
1090
|
+
sourceTimestamp: snapTimestamp,
|
|
1091
|
+
showWhenOriginalPresent: isFaceSnap || isEdgeSnap || !!ghost?.userData?.previewHasEdges,
|
|
1092
|
+
fromSnapshot: true,
|
|
1093
|
+
});
|
|
1094
1094
|
}
|
|
1095
1095
|
}
|
|
1096
1096
|
}
|
|
@@ -1106,21 +1106,24 @@ export class SchemaForm {
|
|
|
1106
1106
|
if (!ghost) return;
|
|
1107
1107
|
const sourceUuid = obj.uuid || null;
|
|
1108
1108
|
const sourceFeatureId = this._getOwningFeatureIdForObject(obj);
|
|
1109
|
+
const sourceTimestamp = (obj.timestamp ?? obj.userData?.timestamp ?? null);
|
|
1110
|
+
const objType = String(obj.type || '').toUpperCase();
|
|
1111
|
+
const isEdge = objType === SelectionFilter.EDGE || objType === 'EDGE';
|
|
1109
1112
|
cache.set(refName, {
|
|
1110
1113
|
object: ghost,
|
|
1111
1114
|
type: obj.type || null,
|
|
1112
1115
|
sourceUuid,
|
|
1113
1116
|
sourceFeatureId,
|
|
1114
|
-
|
|
1117
|
+
sourceTimestamp,
|
|
1118
|
+
showWhenOriginalPresent: isEdge || !!ghost?.userData?.previewHasEdges,
|
|
1115
1119
|
});
|
|
1116
1120
|
try {
|
|
1117
1121
|
const store = this._getReferencePreviewPersistentBucket(inputEl);
|
|
1118
1122
|
if (store) {
|
|
1119
|
-
const objType = String(obj.type || '').toUpperCase();
|
|
1120
1123
|
if (objType === SelectionFilter.EDGE || objType === 'EDGE') {
|
|
1121
1124
|
const positions = this._extractEdgeWorldPositions(obj);
|
|
1122
1125
|
if (positions && positions.length >= 6) {
|
|
1123
|
-
store[refName] = { type: 'EDGE', positions, sourceUuid, sourceFeatureId };
|
|
1126
|
+
store[refName] = { type: 'EDGE', positions, sourceUuid, sourceFeatureId, sourceTimestamp };
|
|
1124
1127
|
}
|
|
1125
1128
|
} else if (objType === SelectionFilter.VERTEX || objType === 'VERTEX') {
|
|
1126
1129
|
const pos = new THREE.Vector3();
|
|
@@ -1128,7 +1131,7 @@ export class SchemaForm {
|
|
|
1128
1131
|
if (typeof obj.getWorldPosition === 'function') obj.getWorldPosition(pos);
|
|
1129
1132
|
else pos.set(obj.position?.x || 0, obj.position?.y || 0, obj.position?.z || 0);
|
|
1130
1133
|
} catch (_) { }
|
|
1131
|
-
store[refName] = { type: 'VERTEX', position: [pos.x, pos.y, pos.z], sourceUuid, sourceFeatureId };
|
|
1134
|
+
store[refName] = { type: 'VERTEX', position: [pos.x, pos.y, pos.z], sourceUuid, sourceFeatureId, sourceTimestamp };
|
|
1132
1135
|
}
|
|
1133
1136
|
}
|
|
1134
1137
|
} catch (_) { }
|
|
@@ -1275,60 +1278,87 @@ export class SchemaForm {
|
|
|
1275
1278
|
_hoverReferenceSelectionItem(inputEl, def, name) {
|
|
1276
1279
|
try {
|
|
1277
1280
|
if (!inputEl) return;
|
|
1278
|
-
const
|
|
1279
|
-
if (activeInput !== inputEl) {
|
|
1280
|
-
const wrap = inputEl.closest?.('.ref-active') || null;
|
|
1281
|
-
if (!wrap) return;
|
|
1282
|
-
}
|
|
1281
|
+
const isActive = (SchemaForm.__activeRefInput || null) === inputEl;
|
|
1283
1282
|
const normalized = normalizeReferenceName(name);
|
|
1284
1283
|
if (!normalized) return;
|
|
1285
1284
|
const scene = this._getReferenceSelectionScene();
|
|
1286
1285
|
if (!scene) return;
|
|
1287
1286
|
try { console.log('[ReferenceSelection] Hover', { name: normalized }); } catch (_) { }
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
try { this._syncActiveReferenceSelectionPreview(inputEl, def); } catch (_) { }
|
|
1291
|
-
|
|
1292
|
-
let target = null;
|
|
1293
|
-
try { target = scene.getObjectByName(normalized); } catch (_) { target = null; }
|
|
1294
|
-
if (!target) {
|
|
1295
|
-
const cache = this._getReferencePreviewCache(inputEl);
|
|
1296
|
-
const entry = cache ? cache.get(normalized) : null;
|
|
1297
|
-
target = entry?.object || entry || null;
|
|
1287
|
+
if (isActive) {
|
|
1288
|
+
try { this._ensureReferencePreviewSnapshots(inputEl, def); } catch (_) { }
|
|
1298
1289
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1290
|
+
try { this._seedReferencePreviewCacheFromScene(inputEl, def, [normalized], scene); } catch (_) { }
|
|
1291
|
+
if (isActive) {
|
|
1292
|
+
try { this._syncActiveReferenceSelectionPreview(inputEl, def); } catch (_) { }
|
|
1301
1293
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1294
|
+
|
|
1295
|
+
const resolveHoverCandidate = (obj) => {
|
|
1296
|
+
if (!obj) return null;
|
|
1297
|
+
if (obj.material) return obj;
|
|
1298
|
+
if (!obj.traverse) return obj;
|
|
1304
1299
|
let candidate = null;
|
|
1305
1300
|
try {
|
|
1306
|
-
|
|
1301
|
+
obj.traverse((child) => {
|
|
1307
1302
|
if (!child || candidate) return;
|
|
1308
1303
|
if (child.type === 'REF_PREVIEW_EDGE') { candidate = child; return; }
|
|
1309
1304
|
if (child.material) candidate = child;
|
|
1310
1305
|
});
|
|
1311
1306
|
} catch (_) { }
|
|
1312
|
-
|
|
1307
|
+
return candidate || obj;
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
const targets = [];
|
|
1311
|
+
const pushTarget = (obj) => {
|
|
1312
|
+
if (!obj || !obj.isObject3D) return;
|
|
1313
|
+
const candidate = resolveHoverCandidate(obj);
|
|
1314
|
+
if (!candidate) return;
|
|
1315
|
+
if (!targets.includes(candidate)) targets.push(candidate);
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
let sceneObj = null;
|
|
1319
|
+
try { sceneObj = scene.getObjectByName(normalized); } catch (_) { sceneObj = null; }
|
|
1320
|
+
if (sceneObj && !sceneObj?.userData?.refPreview) pushTarget(sceneObj);
|
|
1321
|
+
|
|
1322
|
+
const cache = this._getReferencePreviewCache(inputEl);
|
|
1323
|
+
const entry = cache ? cache.get(normalized) : null;
|
|
1324
|
+
const ghost = entry?.object || entry || null;
|
|
1325
|
+
if (ghost && ghost !== sceneObj) pushTarget(ghost);
|
|
1326
|
+
|
|
1327
|
+
let namedPreview = null;
|
|
1328
|
+
try { namedPreview = scene.getObjectByName(`__refPreview__${normalized}`); } catch (_) { namedPreview = null; }
|
|
1329
|
+
if (namedPreview && namedPreview !== sceneObj && namedPreview !== ghost) pushTarget(namedPreview);
|
|
1330
|
+
|
|
1331
|
+
if (!targets.length) return;
|
|
1332
|
+
if (!isActive && ghost && ghost.isObject3D && !ghost.parent) {
|
|
1333
|
+
try {
|
|
1334
|
+
const group = this._ensureReferencePreviewGroup(inputEl);
|
|
1335
|
+
if (group && ghost.parent !== group) {
|
|
1336
|
+
group.add(ghost);
|
|
1337
|
+
inputEl.__refPreviewHoverGroup = true;
|
|
1338
|
+
}
|
|
1339
|
+
} catch (_) { }
|
|
1313
1340
|
}
|
|
1314
1341
|
try {
|
|
1315
1342
|
console.log('[ReferenceSelection] Hover target', {
|
|
1316
1343
|
name: normalized,
|
|
1317
|
-
|
|
1318
|
-
inScene: !!(target && target.parent),
|
|
1344
|
+
targetCount: targets.length,
|
|
1319
1345
|
});
|
|
1320
1346
|
} catch (_) { }
|
|
1321
1347
|
inputEl.__refChipHoverActive = true;
|
|
1322
|
-
try { SelectionFilter.
|
|
1348
|
+
try { SelectionFilter.setHoverObjects(targets, { ignoreFilter: true }); } catch (_) { }
|
|
1323
1349
|
} catch (_) { }
|
|
1324
1350
|
}
|
|
1325
1351
|
|
|
1326
1352
|
_clearReferenceSelectionHover(inputEl) {
|
|
1327
1353
|
try {
|
|
1328
|
-
if (!inputEl
|
|
1354
|
+
if (!inputEl) return;
|
|
1329
1355
|
if (!inputEl.__refChipHoverActive) return;
|
|
1330
1356
|
inputEl.__refChipHoverActive = false;
|
|
1331
1357
|
SelectionFilter.clearHover();
|
|
1358
|
+
if (SchemaForm.__activeRefInput !== inputEl && inputEl.__refPreviewHoverGroup) {
|
|
1359
|
+
inputEl.__refPreviewHoverGroup = false;
|
|
1360
|
+
try { this._removeReferencePreviewGroup(inputEl); } catch (_) { }
|
|
1361
|
+
}
|
|
1332
1362
|
} catch (_) { }
|
|
1333
1363
|
}
|
|
1334
1364
|
|
|
@@ -65,6 +65,38 @@ export class LabelOverlay {
|
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
|
+
if (!el.__wheelPassThrough) {
|
|
69
|
+
const onWheel = (e) => {
|
|
70
|
+
if (!el.classList.contains('constraint-label')) return;
|
|
71
|
+
const canvas = this.viewer?.renderer?.domElement;
|
|
72
|
+
if (!canvas) return;
|
|
73
|
+
let canceled = false;
|
|
74
|
+
try {
|
|
75
|
+
const forwarded = new WheelEvent(e.type, {
|
|
76
|
+
bubbles: true,
|
|
77
|
+
cancelable: true,
|
|
78
|
+
deltaX: e.deltaX,
|
|
79
|
+
deltaY: e.deltaY,
|
|
80
|
+
deltaZ: e.deltaZ,
|
|
81
|
+
deltaMode: e.deltaMode,
|
|
82
|
+
clientX: e.clientX,
|
|
83
|
+
clientY: e.clientY,
|
|
84
|
+
screenX: e.screenX,
|
|
85
|
+
screenY: e.screenY,
|
|
86
|
+
ctrlKey: e.ctrlKey,
|
|
87
|
+
shiftKey: e.shiftKey,
|
|
88
|
+
altKey: e.altKey,
|
|
89
|
+
metaKey: e.metaKey,
|
|
90
|
+
});
|
|
91
|
+
canceled = !canvas.dispatchEvent(forwarded);
|
|
92
|
+
} catch { }
|
|
93
|
+
if (canceled) {
|
|
94
|
+
try { e.preventDefault(); } catch { }
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
el.addEventListener('wheel', onWheel, { passive: false });
|
|
98
|
+
el.__wheelPassThrough = onWheel;
|
|
99
|
+
}
|
|
68
100
|
if (this.onDblClick) el.addEventListener('dblclick', (e) => this.onDblClick(idx, ann, e));
|
|
69
101
|
try { this._root.appendChild(el); this._labelMap.set(idx, el); } catch {}
|
|
70
102
|
} else if (text != null) {
|
package/src/UI/pmi/PMIMode.js
CHANGED
|
@@ -12,6 +12,7 @@ import { getPMIStyle, setPMIStyle, sanitizePMIStyle } from './pmiStyle.js';
|
|
|
12
12
|
import { AnnotationHistory } from './AnnotationHistory.js';
|
|
13
13
|
import { LabelOverlay } from './LabelOverlay.js';
|
|
14
14
|
import { AnnotationCollectionWidget } from './AnnotationCollectionWidget.js';
|
|
15
|
+
import { SelectionFilter } from '../SelectionFilter.js';
|
|
15
16
|
import { localStorage as LS } from '../../idbStorage.js';
|
|
16
17
|
|
|
17
18
|
const cssEscape = (value) => {
|
|
@@ -89,6 +90,9 @@ export class PMIMode {
|
|
|
89
90
|
const v = this.viewer;
|
|
90
91
|
if (!v || !v.container) return;
|
|
91
92
|
|
|
93
|
+
// Clear any lingering reference-selection state before PMI interactions.
|
|
94
|
+
this.#clearActiveReferenceSelection();
|
|
95
|
+
|
|
92
96
|
// Save and hide existing accordion sections instead of hiding the whole sidebar
|
|
93
97
|
this.#hideOriginalSidebarSections();
|
|
94
98
|
|
|
@@ -172,6 +176,22 @@ export class PMIMode {
|
|
|
172
176
|
try { this._annotationWidget?.collapseExpandedEntries?.({ clearOpenState: true }); } catch { /* ignore */ }
|
|
173
177
|
}
|
|
174
178
|
|
|
179
|
+
#clearActiveReferenceSelection() {
|
|
180
|
+
try {
|
|
181
|
+
const active = document.querySelectorAll('[active-reference-selection="true"],[active-reference-selection=true]');
|
|
182
|
+
active.forEach((el) => {
|
|
183
|
+
try { el.style.filter = 'none'; } catch { }
|
|
184
|
+
try { el.removeAttribute('active-reference-selection'); } catch { }
|
|
185
|
+
try {
|
|
186
|
+
const wrap = el.closest('.ref-single-wrap, .ref-multi-wrap');
|
|
187
|
+
if (wrap) wrap.classList.remove('ref-active');
|
|
188
|
+
} catch { }
|
|
189
|
+
});
|
|
190
|
+
} catch { }
|
|
191
|
+
try { if (window.__BREP_activeRefInput) window.__BREP_activeRefInput = null; } catch { }
|
|
192
|
+
try { SelectionFilter.restoreAllowedSelectionTypes(); } catch { }
|
|
193
|
+
}
|
|
194
|
+
|
|
175
195
|
applyViewTransformsSequential() {
|
|
176
196
|
try {
|
|
177
197
|
this.#applyViewTransforms();
|
|
@@ -599,6 +619,9 @@ export class PMIMode {
|
|
|
599
619
|
this.#markAnnotationsDirty();
|
|
600
620
|
},
|
|
601
621
|
});
|
|
622
|
+
// Avoid auto-activating reference selection on expand; PMI scene clicks should
|
|
623
|
+
// stay as normal selections unless the user explicitly activates a ref field.
|
|
624
|
+
this._annotationWidget._autoFocusOnExpand = false;
|
|
602
625
|
widgetWrap.appendChild(this._annotationWidget.uiElement);
|
|
603
626
|
|
|
604
627
|
this._pmiAnnotationsSection = sec;
|
|
@@ -331,13 +331,19 @@ function hasHoleMetadata(target) {
|
|
|
331
331
|
if (!target) return false;
|
|
332
332
|
const queue = [target];
|
|
333
333
|
const visited = new Set();
|
|
334
|
+
const hasOwnFaces = (obj) => Object.prototype.hasOwnProperty.call(obj, 'faces');
|
|
334
335
|
while (queue.length) {
|
|
335
336
|
const obj = queue.shift();
|
|
336
337
|
if (!obj || visited.has(obj)) continue;
|
|
337
338
|
visited.add(obj);
|
|
338
339
|
if (readHoleMetadata(obj)) return true;
|
|
339
|
-
if (Array.isArray(obj.faces)) {
|
|
340
|
+
if (hasOwnFaces(obj) && Array.isArray(obj.faces)) {
|
|
340
341
|
for (const face of obj.faces) queue.push(face);
|
|
342
|
+
} else if (obj.type === 'SOLID' || obj.type === 'COMPONENT') {
|
|
343
|
+
const kids = Array.isArray(obj.children) ? obj.children : [];
|
|
344
|
+
for (const child of kids) {
|
|
345
|
+
if (child && child.type === 'FACE') queue.push(child);
|
|
346
|
+
}
|
|
341
347
|
}
|
|
342
348
|
if (obj.parent) queue.push(obj.parent);
|
|
343
349
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
+
import { computeFaceCenter, computeFaceNormal } from '../faceUtils.js';
|
|
2
3
|
|
|
3
4
|
const FACE_TYPES = new Set(['FACE', 'PLANE']);
|
|
4
5
|
|
|
@@ -26,46 +27,12 @@ function _findSelectedFace(viewer) {
|
|
|
26
27
|
return found;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
function _computeFaceCenter(obj) {
|
|
30
|
-
if (!obj) return null;
|
|
31
|
-
try { obj.updateMatrixWorld?.(true); } catch {}
|
|
32
|
-
try {
|
|
33
|
-
const box = new THREE.Box3().setFromObject(obj);
|
|
34
|
-
if (!box.isEmpty()) return box.getCenter(new THREE.Vector3());
|
|
35
|
-
} catch {}
|
|
36
|
-
try {
|
|
37
|
-
const geom = obj.geometry;
|
|
38
|
-
const bs = geom?.boundingSphere || (geom?.computeBoundingSphere && (geom.computeBoundingSphere(), geom.boundingSphere));
|
|
39
|
-
if (bs) return obj.localToWorld(bs.center.clone());
|
|
40
|
-
} catch {}
|
|
41
|
-
try {
|
|
42
|
-
return obj.getWorldPosition?.(new THREE.Vector3()) || null;
|
|
43
|
-
} catch {}
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function _computeFaceNormal(obj) {
|
|
48
|
-
if (!obj) return null;
|
|
49
|
-
let n = null;
|
|
50
|
-
if (typeof obj.getAverageNormal === 'function') {
|
|
51
|
-
try { n = obj.getAverageNormal().clone(); } catch {}
|
|
52
|
-
}
|
|
53
|
-
if (!n || n.lengthSq() < 1e-10) {
|
|
54
|
-
try {
|
|
55
|
-
const q = obj.getWorldQuaternion?.(new THREE.Quaternion());
|
|
56
|
-
if (q) n = new THREE.Vector3(0, 0, 1).applyQuaternion(q);
|
|
57
|
-
} catch {}
|
|
58
|
-
}
|
|
59
|
-
if (!n || n.lengthSq() < 1e-10) return null;
|
|
60
|
-
return n.normalize();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
30
|
function _orientCameraToFace(viewer, face) {
|
|
64
31
|
const cam = viewer?.camera;
|
|
65
32
|
if (!viewer || !cam || !face) return false;
|
|
66
33
|
|
|
67
|
-
const target =
|
|
68
|
-
const normal =
|
|
34
|
+
const target = computeFaceCenter(face);
|
|
35
|
+
const normal = computeFaceNormal(face);
|
|
69
36
|
if (!target || !normal) return false;
|
|
70
37
|
|
|
71
38
|
const toCam = cam.position.clone().sub(target);
|
|
@@ -11,6 +11,7 @@ import { createFlatPatternButton } from './flatPatternButton.js';
|
|
|
11
11
|
import { createAboutButton } from './aboutButton.js';
|
|
12
12
|
import { createTestsButton } from './testsButton.js';
|
|
13
13
|
import { createScriptRunnerButton } from './scriptRunnerButton.js';
|
|
14
|
+
import { createSelectionStateButton } from './selectionStateButton.js';
|
|
14
15
|
|
|
15
16
|
export function registerDefaultToolbarButtons(viewer) {
|
|
16
17
|
if (!viewer || typeof viewer.addToolbarButton !== 'function') return;
|
|
@@ -25,6 +26,7 @@ export function registerDefaultToolbarButtons(viewer) {
|
|
|
25
26
|
createAboutButton,
|
|
26
27
|
createTestsButton,
|
|
27
28
|
createScriptRunnerButton,
|
|
29
|
+
createSelectionStateButton,
|
|
28
30
|
createUndoButton,
|
|
29
31
|
createRedoButton,
|
|
30
32
|
];
|