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/src/UI/featureDialogs.js
CHANGED
|
@@ -455,6 +455,52 @@ export class SchemaForm {
|
|
|
455
455
|
return false;
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
// Focus the first available field in this form (or activate a reference selection when needed).
|
|
459
|
+
focusFirstField() {
|
|
460
|
+
const canFocus = (el) => {
|
|
461
|
+
if (!el || typeof el.focus !== 'function') return false;
|
|
462
|
+
if (el.disabled) return false;
|
|
463
|
+
const ariaDisabled = el.getAttribute ? el.getAttribute('aria-disabled') : null;
|
|
464
|
+
if (ariaDisabled === 'true') return false;
|
|
465
|
+
return true;
|
|
466
|
+
};
|
|
467
|
+
const tryFocus = (el) => {
|
|
468
|
+
if (!canFocus(el)) return false;
|
|
469
|
+
try { el.focus(); } catch (_) { return false; }
|
|
470
|
+
return true;
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
for (const key in this.schema) {
|
|
474
|
+
if (!Object.prototype.hasOwnProperty.call(this.schema, key)) continue;
|
|
475
|
+
if (this._excludedKeys.has(key)) continue;
|
|
476
|
+
const def = this.schema[key];
|
|
477
|
+
const row = this._fieldsWrap?.querySelector?.(`[data-key="${key}"]`) || null;
|
|
478
|
+
|
|
479
|
+
// Reference selections should auto-activate instead of just focusing the display button.
|
|
480
|
+
if (def && def.type === 'reference_selection') {
|
|
481
|
+
if (this.activateField(key)) return true;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Prefer direct inputs first.
|
|
485
|
+
if (row) {
|
|
486
|
+
const input = row.querySelector('input:not([type="hidden"]), select, textarea');
|
|
487
|
+
if (tryFocus(input)) return true;
|
|
488
|
+
const btn = row.querySelector('button, [tabindex]:not([tabindex="-1"])');
|
|
489
|
+
if (tryFocus(btn)) return true;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const inputEl = this._inputs.get(key);
|
|
493
|
+
if (inputEl && inputEl.getAttribute && inputEl.getAttribute('type') !== 'hidden') {
|
|
494
|
+
if (tryFocus(inputEl)) return true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const root = this._shadow || this.uiElement;
|
|
499
|
+
const any = root?.querySelector?.('input:not([type="hidden"]), select, textarea, button, [tabindex]:not([tabindex="-1"])');
|
|
500
|
+
if (tryFocus(any)) return true;
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
|
|
458
504
|
readFieldValue(key) {
|
|
459
505
|
const widget = this._widgets.get(key);
|
|
460
506
|
if (widget && typeof widget.readValue === 'function') {
|
|
@@ -1287,6 +1333,15 @@ export class SchemaForm {
|
|
|
1287
1333
|
}
|
|
1288
1334
|
|
|
1289
1335
|
_activateReferenceSelection(inputEl, def) {
|
|
1336
|
+
// If switching between reference fields, fully stop the previous session so
|
|
1337
|
+
// selection filters restore correctly (prevents sticky FACE-only state).
|
|
1338
|
+
try {
|
|
1339
|
+
const prevActive = SchemaForm.__activeRefInput || null;
|
|
1340
|
+
if (prevActive && prevActive !== inputEl) {
|
|
1341
|
+
this._stopActiveReferenceSelection();
|
|
1342
|
+
}
|
|
1343
|
+
} catch (_) { }
|
|
1344
|
+
|
|
1290
1345
|
// Clear any lingering scene selection so the new reference starts fresh
|
|
1291
1346
|
try {
|
|
1292
1347
|
const scene = this._getReferenceSelectionScene();
|
|
@@ -95,6 +95,8 @@ export class HistoryCollectionWidget {
|
|
|
95
95
|
this._itemEls = new Map();
|
|
96
96
|
this._forms = new Map();
|
|
97
97
|
this._uiFieldSignatures = new Map();
|
|
98
|
+
this._autoFocusOnExpand = false;
|
|
99
|
+
this._pendingFocusEntryId = null;
|
|
98
100
|
this._boundHistoryListener = null;
|
|
99
101
|
this._listenerUnsub = null;
|
|
100
102
|
this._suppressHistoryListener = false;
|
|
@@ -157,6 +159,7 @@ export class HistoryCollectionWidget {
|
|
|
157
159
|
|
|
158
160
|
if (!entries.length) {
|
|
159
161
|
this._setContextSuppression(false);
|
|
162
|
+
this._pendingFocusEntryId = null;
|
|
160
163
|
const empty = document.createElement('div');
|
|
161
164
|
empty.className = 'hc-empty';
|
|
162
165
|
empty.textContent = 'No entries yet.';
|
|
@@ -198,6 +201,8 @@ export class HistoryCollectionWidget {
|
|
|
198
201
|
const itemEl = this._renderEntry(entry, id, i, targetId === id, entries.length);
|
|
199
202
|
this._listEl.appendChild(itemEl);
|
|
200
203
|
}
|
|
204
|
+
|
|
205
|
+
this._applyPendingFocus();
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
_destroyForm(id) {
|
|
@@ -223,6 +228,36 @@ export class HistoryCollectionWidget {
|
|
|
223
228
|
return this._forms.get(String(id)) || null;
|
|
224
229
|
}
|
|
225
230
|
|
|
231
|
+
// Close any expanded entry dialog and optionally clear stored open state.
|
|
232
|
+
collapseExpandedEntries({ clearOpenState = true, notify = true } = {}) {
|
|
233
|
+
const prevId = this._expandedId != null ? String(this._expandedId) : null;
|
|
234
|
+
let prevEntry = null;
|
|
235
|
+
if (prevId) {
|
|
236
|
+
const info = this._findEntryInfoById(prevId);
|
|
237
|
+
prevEntry = info?.entry || null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (clearOpenState && this._autoSyncOpenState) {
|
|
241
|
+
if (prevEntry) {
|
|
242
|
+
this._applyOpenState(prevEntry, false);
|
|
243
|
+
} else {
|
|
244
|
+
const entries = this._getEntries();
|
|
245
|
+
for (const entry of entries) {
|
|
246
|
+
this._applyOpenState(entry, false);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!prevId) {
|
|
252
|
+
this._setContextSuppression(false);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this._expandedId = null;
|
|
257
|
+
this.render();
|
|
258
|
+
if (notify) this._notifyEntryToggle(prevEntry, false);
|
|
259
|
+
}
|
|
260
|
+
|
|
226
261
|
_getEntries() {
|
|
227
262
|
if (!this.history) return [];
|
|
228
263
|
if (Array.isArray(this.history.entries)) return this.history.entries;
|
|
@@ -414,6 +449,7 @@ export class HistoryCollectionWidget {
|
|
|
414
449
|
this._applyOpenState(targetEntry, false);
|
|
415
450
|
}
|
|
416
451
|
this._expandedId = null;
|
|
452
|
+
this._pendingFocusEntryId = null;
|
|
417
453
|
this.render();
|
|
418
454
|
this._notifyEntryToggle(targetEntry, false);
|
|
419
455
|
return;
|
|
@@ -425,6 +461,9 @@ export class HistoryCollectionWidget {
|
|
|
425
461
|
if (targetEntry) this._applyOpenState(targetEntry, true);
|
|
426
462
|
}
|
|
427
463
|
this._expandedId = targetEntry ? targetId : null;
|
|
464
|
+
if (this._autoFocusOnExpand && targetEntry) {
|
|
465
|
+
this._pendingFocusEntryId = targetId;
|
|
466
|
+
}
|
|
428
467
|
this.render();
|
|
429
468
|
if (previousInfo?.entry) this._notifyEntryToggle(previousInfo.entry, false);
|
|
430
469
|
if (targetEntry) this._notifyEntryToggle(targetEntry, true);
|
|
@@ -613,6 +652,30 @@ export class HistoryCollectionWidget {
|
|
|
613
652
|
}
|
|
614
653
|
}
|
|
615
654
|
|
|
655
|
+
_applyPendingFocus() {
|
|
656
|
+
if (!this._autoFocusOnExpand) return;
|
|
657
|
+
const targetId = this._pendingFocusEntryId;
|
|
658
|
+
if (!targetId) return;
|
|
659
|
+
if (!this._expandedId || String(this._expandedId) !== String(targetId)) {
|
|
660
|
+
this._pendingFocusEntryId = null;
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const form = this.getFormForEntry(targetId);
|
|
664
|
+
if (!form) {
|
|
665
|
+
this._pendingFocusEntryId = null;
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const focus = () => {
|
|
669
|
+
try {
|
|
670
|
+
if (typeof form.focusFirstField === 'function') form.focusFirstField();
|
|
671
|
+
else if (typeof form.activateFirstReferenceSelection === 'function') form.activateFirstReferenceSelection();
|
|
672
|
+
} catch (_) { /* ignore */ }
|
|
673
|
+
};
|
|
674
|
+
if (typeof requestAnimationFrame === 'function') requestAnimationFrame(() => focus());
|
|
675
|
+
else setTimeout(focus, 0);
|
|
676
|
+
this._pendingFocusEntryId = null;
|
|
677
|
+
}
|
|
678
|
+
|
|
616
679
|
async _moveEntry(id, delta) {
|
|
617
680
|
if (!id) return;
|
|
618
681
|
const entries = this._getEntries();
|
|
@@ -671,6 +734,7 @@ export class HistoryCollectionWidget {
|
|
|
671
734
|
}
|
|
672
735
|
if (this._autoSyncOpenState) this._applyOpenState(entry, true);
|
|
673
736
|
this._expandedId = normalizedId;
|
|
737
|
+
if (this._autoFocusOnExpand) this._pendingFocusEntryId = normalizedId;
|
|
674
738
|
createdEntryId = normalizedId;
|
|
675
739
|
}
|
|
676
740
|
} catch (_) { /* ignore */ }
|
|
@@ -693,6 +757,7 @@ export class HistoryCollectionWidget {
|
|
|
693
757
|
}
|
|
694
758
|
if (this._autoSyncOpenState) this._applyOpenState(entry, true);
|
|
695
759
|
this._expandedId = normalizedId;
|
|
760
|
+
if (this._autoFocusOnExpand) this._pendingFocusEntryId = normalizedId;
|
|
696
761
|
createdEntryId = normalizedId;
|
|
697
762
|
}
|
|
698
763
|
this.render();
|
|
@@ -15,6 +15,43 @@ export class BaseAnnotation extends ListEntityBase {
|
|
|
15
15
|
this.resultArtifacts = [];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
static _normalizeSelectionItems(selectedItems) {
|
|
19
|
+
return Array.isArray(selectedItems) ? selectedItems : [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static _normalizeSelectionType(type) {
|
|
23
|
+
return String(type || '').toUpperCase();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static _isSelectionType(item, allowed) {
|
|
27
|
+
if (!allowed || !allowed.size) return true;
|
|
28
|
+
return allowed.has(BaseAnnotation._normalizeSelectionType(item?.type));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static _selectionRefName(item) {
|
|
32
|
+
return item?.name
|
|
33
|
+
|| item?.userData?.faceName
|
|
34
|
+
|| item?.userData?.edgeName
|
|
35
|
+
|| item?.userData?.vertexName
|
|
36
|
+
|| item?.userData?.solidName
|
|
37
|
+
|| item?.userData?.name
|
|
38
|
+
|| null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static _collectSelectionRefs(selectedItems, types = null) {
|
|
42
|
+
const items = BaseAnnotation._normalizeSelectionItems(selectedItems);
|
|
43
|
+
const allowed = Array.isArray(types)
|
|
44
|
+
? new Set(types.map((t) => BaseAnnotation._normalizeSelectionType(t)))
|
|
45
|
+
: null;
|
|
46
|
+
const refs = [];
|
|
47
|
+
for (const item of items) {
|
|
48
|
+
if (!BaseAnnotation._isSelectionType(item, allowed)) continue;
|
|
49
|
+
const ref = BaseAnnotation._selectionRefName(item);
|
|
50
|
+
if (ref) refs.push(ref);
|
|
51
|
+
}
|
|
52
|
+
return refs;
|
|
53
|
+
}
|
|
54
|
+
|
|
18
55
|
async run(renderingContext) {
|
|
19
56
|
// Base implementation - subclasses should override
|
|
20
57
|
// renderingContext contains: { pmimode, group, idx, ctx }
|
package/src/UI/pmi/PMIMode.js
CHANGED
|
@@ -168,6 +168,10 @@ export class PMIMode {
|
|
|
168
168
|
try { if (v.controls) v.controls.enabled = true; } catch { }
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
collapseExpandedDialogs() {
|
|
172
|
+
try { this._annotationWidget?.collapseExpandedEntries?.({ clearOpenState: true }); } catch { /* ignore */ }
|
|
173
|
+
}
|
|
174
|
+
|
|
171
175
|
applyViewTransformsSequential() {
|
|
172
176
|
try {
|
|
173
177
|
this.#applyViewTransforms();
|
|
@@ -71,6 +71,11 @@ export class AngleDimensionAnnotation extends BaseAnnotation {
|
|
|
71
71
|
static longName = 'Angle Dimension';
|
|
72
72
|
static title = 'Angle';
|
|
73
73
|
static inputParamsSchema = inputParamsSchema;
|
|
74
|
+
static showContexButton(selectedItems) {
|
|
75
|
+
const refs = BaseAnnotation._collectSelectionRefs(selectedItems, ['FACE', 'EDGE']);
|
|
76
|
+
if (refs.length < 2) return false;
|
|
77
|
+
return { params: { targets: refs.slice(0, 2) } };
|
|
78
|
+
}
|
|
74
79
|
|
|
75
80
|
constructor(opts = {}) {
|
|
76
81
|
super(opts);
|
|
@@ -41,6 +41,11 @@ export class ExplodeBodyAnnotation extends BaseAnnotation {
|
|
|
41
41
|
static title = 'Explode Body';
|
|
42
42
|
static inputParamsSchema = inputParamsSchema;
|
|
43
43
|
static aliases = ['viewTransform'];
|
|
44
|
+
static showContexButton(selectedItems) {
|
|
45
|
+
const refs = BaseAnnotation._collectSelectionRefs(selectedItems, ['SOLID']);
|
|
46
|
+
if (!refs.length) return false;
|
|
47
|
+
return { params: { targets: refs } };
|
|
48
|
+
}
|
|
44
49
|
|
|
45
50
|
constructor(opts = {}) {
|
|
46
51
|
super(opts);
|
|
@@ -71,6 +71,17 @@ export class HoleCalloutAnnotation extends BaseAnnotation {
|
|
|
71
71
|
static longName = 'Hole Callout';
|
|
72
72
|
static title = 'Hole Callout';
|
|
73
73
|
static inputParamsSchema = inputParamsSchema;
|
|
74
|
+
static showContexButton(selectedItems) {
|
|
75
|
+
const items = BaseAnnotation._normalizeSelectionItems(selectedItems);
|
|
76
|
+
const allowed = new Set(['VERTEX', 'EDGE', 'FACE']);
|
|
77
|
+
for (const item of items) {
|
|
78
|
+
if (!BaseAnnotation._isSelectionType(item, allowed)) continue;
|
|
79
|
+
if (!hasHoleMetadata(item)) continue;
|
|
80
|
+
const ref = BaseAnnotation._selectionRefName(item);
|
|
81
|
+
if (ref) return { params: { target: ref } };
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
74
85
|
|
|
75
86
|
constructor(opts = {}) {
|
|
76
87
|
super(opts);
|
|
@@ -293,6 +304,46 @@ function findHoleDescriptor(partHistory, targetObj, fallbackPoint, targetName =
|
|
|
293
304
|
return descriptors[0];
|
|
294
305
|
}
|
|
295
306
|
|
|
307
|
+
function readHoleMetadata(obj) {
|
|
308
|
+
if (!obj) return null;
|
|
309
|
+
const ud = obj.userData || null;
|
|
310
|
+
if (ud?.hole) return ud.hole;
|
|
311
|
+
if (ud?.metadata?.hole) return ud.metadata.hole;
|
|
312
|
+
if (typeof obj.getMetadata === 'function') {
|
|
313
|
+
try {
|
|
314
|
+
const meta = obj.getMetadata();
|
|
315
|
+
if (meta?.hole) return meta.hole;
|
|
316
|
+
if (meta?.metadata?.hole) return meta.metadata.hole;
|
|
317
|
+
} catch { /* ignore */ }
|
|
318
|
+
}
|
|
319
|
+
const faceName = obj?.name || ud?.faceName || null;
|
|
320
|
+
const parentSolid = obj?.parentSolid || ud?.parentSolid || null;
|
|
321
|
+
if (faceName && parentSolid && typeof parentSolid.getFaceMetadata === 'function') {
|
|
322
|
+
try {
|
|
323
|
+
const meta = parentSolid.getFaceMetadata(faceName);
|
|
324
|
+
if (meta?.hole) return meta.hole;
|
|
325
|
+
} catch { /* ignore */ }
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function hasHoleMetadata(target) {
|
|
331
|
+
if (!target) return false;
|
|
332
|
+
const queue = [target];
|
|
333
|
+
const visited = new Set();
|
|
334
|
+
while (queue.length) {
|
|
335
|
+
const obj = queue.shift();
|
|
336
|
+
if (!obj || visited.has(obj)) continue;
|
|
337
|
+
visited.add(obj);
|
|
338
|
+
if (readHoleMetadata(obj)) return true;
|
|
339
|
+
if (Array.isArray(obj.faces)) {
|
|
340
|
+
for (const face of obj.faces) queue.push(face);
|
|
341
|
+
}
|
|
342
|
+
if (obj.parent) queue.push(obj.parent);
|
|
343
|
+
}
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
|
|
296
347
|
|
|
297
348
|
function formatHoleCallout(desc, quantity = 1, options = {}) {
|
|
298
349
|
if (!desc) return '';
|
|
@@ -65,6 +65,11 @@ export class LeaderAnnotation extends BaseAnnotation {
|
|
|
65
65
|
static longName = 'Leader';
|
|
66
66
|
static title = 'Leader';
|
|
67
67
|
static inputParamsSchema = inputParamsSchema;
|
|
68
|
+
static showContexButton(selectedItems) {
|
|
69
|
+
const refs = BaseAnnotation._collectSelectionRefs(selectedItems, ['VERTEX']);
|
|
70
|
+
if (!refs.length) return false;
|
|
71
|
+
return { params: { target: refs } };
|
|
72
|
+
}
|
|
68
73
|
|
|
69
74
|
constructor(opts = {}) {
|
|
70
75
|
super(opts);
|
|
@@ -9,22 +9,7 @@ const inputParamsSchema = {
|
|
|
9
9
|
label: 'ID',
|
|
10
10
|
hint: 'unique identifier for the linear dimension',
|
|
11
11
|
},
|
|
12
|
-
|
|
13
|
-
type: 'number',
|
|
14
|
-
default_value: 3,
|
|
15
|
-
defaultResolver: ({ pmimode }) => {
|
|
16
|
-
const dec = Number.isFinite(pmimode?._opts?.dimDecimals)
|
|
17
|
-
? (pmimode._opts.dimDecimals | 0)
|
|
18
|
-
: undefined;
|
|
19
|
-
if (!Number.isFinite(dec)) return undefined;
|
|
20
|
-
return Math.max(0, Math.min(8, dec));
|
|
21
|
-
},
|
|
22
|
-
label: 'Decimals',
|
|
23
|
-
hint: 'Number of decimal places to display',
|
|
24
|
-
min: 0,
|
|
25
|
-
max: 8,
|
|
26
|
-
step: 1,
|
|
27
|
-
},
|
|
12
|
+
|
|
28
13
|
targets: {
|
|
29
14
|
type: 'reference_selection',
|
|
30
15
|
selectionFilter: ['VERTEX', 'EDGE'],
|
|
@@ -67,6 +52,22 @@ const inputParamsSchema = {
|
|
|
67
52
|
label: 'Reference',
|
|
68
53
|
hint: 'Mark as reference dimension (parentheses)',
|
|
69
54
|
},
|
|
55
|
+
decimals: {
|
|
56
|
+
type: 'number',
|
|
57
|
+
default_value: 3,
|
|
58
|
+
defaultResolver: ({ pmimode }) => {
|
|
59
|
+
const dec = Number.isFinite(pmimode?._opts?.dimDecimals)
|
|
60
|
+
? (pmimode._opts.dimDecimals | 0)
|
|
61
|
+
: undefined;
|
|
62
|
+
if (!Number.isFinite(dec)) return undefined;
|
|
63
|
+
return Math.max(0, Math.min(8, dec));
|
|
64
|
+
},
|
|
65
|
+
label: 'Decimals',
|
|
66
|
+
hint: 'Number of decimal places to display',
|
|
67
|
+
min: 0,
|
|
68
|
+
max: 8,
|
|
69
|
+
step: 1,
|
|
70
|
+
},
|
|
70
71
|
};
|
|
71
72
|
|
|
72
73
|
export class LinearDimensionAnnotation extends BaseAnnotation {
|
|
@@ -76,6 +77,11 @@ export class LinearDimensionAnnotation extends BaseAnnotation {
|
|
|
76
77
|
static longName = 'Linear Dimension';
|
|
77
78
|
static title = 'Linear';
|
|
78
79
|
static inputParamsSchema = inputParamsSchema;
|
|
80
|
+
static showContexButton(selectedItems) {
|
|
81
|
+
const refs = BaseAnnotation._collectSelectionRefs(selectedItems, ['VERTEX', 'EDGE']);
|
|
82
|
+
if (!refs.length) return false;
|
|
83
|
+
return { params: { targets: refs.slice(0, 2) } };
|
|
84
|
+
}
|
|
79
85
|
|
|
80
86
|
constructor(opts = {}) {
|
|
81
87
|
super(opts);
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import * as THREE from 'three';
|
|
5
5
|
import { BaseAnnotation } from '../BaseAnnotation.js';
|
|
6
6
|
import { getPMIStyle } from '../pmiStyle.js';
|
|
7
|
+
import { objectRepresentativePoint } from '../annUtils.js';
|
|
7
8
|
|
|
8
9
|
const inputParamsSchema = {
|
|
9
10
|
id: {
|
|
@@ -34,6 +35,14 @@ export class NoteAnnotation extends BaseAnnotation {
|
|
|
34
35
|
static longName = 'Note';
|
|
35
36
|
static title = 'Note';
|
|
36
37
|
static inputParamsSchema = inputParamsSchema;
|
|
38
|
+
static showContexButton(selectedItems) {
|
|
39
|
+
const items = BaseAnnotation._normalizeSelectionItems(selectedItems);
|
|
40
|
+
if (!items.length) return false;
|
|
41
|
+
const anchor = items[0];
|
|
42
|
+
const point = objectRepresentativePoint(null, anchor);
|
|
43
|
+
if (!point) return false;
|
|
44
|
+
return { params: { position: { x: point.x, y: point.y, z: point.z } } };
|
|
45
|
+
}
|
|
37
46
|
|
|
38
47
|
constructor(opts = {}) {
|
|
39
48
|
super(opts);
|
|
@@ -9,22 +9,6 @@ const inputParamsSchema = {
|
|
|
9
9
|
label: 'ID',
|
|
10
10
|
hint: 'unique identifier for the radial dimension',
|
|
11
11
|
},
|
|
12
|
-
decimals: {
|
|
13
|
-
type: 'number',
|
|
14
|
-
default_value: 3,
|
|
15
|
-
defaultResolver: ({ pmimode }) => {
|
|
16
|
-
const dec = Number.isFinite(pmimode?._opts?.dimDecimals)
|
|
17
|
-
? (pmimode._opts.dimDecimals | 0)
|
|
18
|
-
: undefined;
|
|
19
|
-
if (!Number.isFinite(dec)) return undefined;
|
|
20
|
-
return Math.max(0, Math.min(8, dec));
|
|
21
|
-
},
|
|
22
|
-
label: 'Decimals',
|
|
23
|
-
hint: 'Number of decimal places to display',
|
|
24
|
-
min: 0,
|
|
25
|
-
max: 8,
|
|
26
|
-
step: 1,
|
|
27
|
-
},
|
|
28
12
|
cylindricalFaceRef: {
|
|
29
13
|
type: 'reference_selection',
|
|
30
14
|
selectionFilter: ['FACE'],
|
|
@@ -68,6 +52,22 @@ const inputParamsSchema = {
|
|
|
68
52
|
label: 'Reference',
|
|
69
53
|
hint: 'Mark as reference dimension (parentheses)',
|
|
70
54
|
},
|
|
55
|
+
decimals: {
|
|
56
|
+
type: 'number',
|
|
57
|
+
default_value: 3,
|
|
58
|
+
defaultResolver: ({ pmimode }) => {
|
|
59
|
+
const dec = Number.isFinite(pmimode?._opts?.dimDecimals)
|
|
60
|
+
? (pmimode._opts.dimDecimals | 0)
|
|
61
|
+
: undefined;
|
|
62
|
+
if (!Number.isFinite(dec)) return undefined;
|
|
63
|
+
return Math.max(0, Math.min(8, dec));
|
|
64
|
+
},
|
|
65
|
+
label: 'Decimals',
|
|
66
|
+
hint: 'Number of decimal places to display',
|
|
67
|
+
min: 0,
|
|
68
|
+
max: 8,
|
|
69
|
+
step: 1,
|
|
70
|
+
},
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
export class RadialDimensionAnnotation extends BaseAnnotation {
|
|
@@ -77,6 +77,17 @@ export class RadialDimensionAnnotation extends BaseAnnotation {
|
|
|
77
77
|
static longName = 'Radial Dimension';
|
|
78
78
|
static title = 'Radial';
|
|
79
79
|
static inputParamsSchema = inputParamsSchema;
|
|
80
|
+
static showContexButton(selectedItems) {
|
|
81
|
+
const items = BaseAnnotation._normalizeSelectionItems(selectedItems);
|
|
82
|
+
const allowed = new Set(['FACE']);
|
|
83
|
+
for (const item of items) {
|
|
84
|
+
if (!BaseAnnotation._isSelectionType(item, allowed)) continue;
|
|
85
|
+
if (!hasCylindricalMetadata(item)) continue;
|
|
86
|
+
const ref = BaseAnnotation._selectionRefName(item);
|
|
87
|
+
if (ref) return { params: { cylindricalFaceRef: ref } };
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
80
91
|
|
|
81
92
|
constructor(opts = {}) {
|
|
82
93
|
super(opts);
|
|
@@ -345,6 +356,60 @@ function computeRadialPoints(pmimode, ann, ctx) {
|
|
|
345
356
|
}
|
|
346
357
|
}
|
|
347
358
|
|
|
359
|
+
function isCylindricalMetadata(meta) {
|
|
360
|
+
if (!meta || typeof meta !== 'object') return false;
|
|
361
|
+
const type = String(meta.type || '').toLowerCase();
|
|
362
|
+
if (type === 'cylindrical') {
|
|
363
|
+
const radius = Number(meta.radius);
|
|
364
|
+
return Number.isFinite(radius) && radius > 0;
|
|
365
|
+
}
|
|
366
|
+
if (type === 'conical') {
|
|
367
|
+
const r0 = Number(meta.radiusBottom);
|
|
368
|
+
const r1 = Number(meta.radiusTop);
|
|
369
|
+
if (!Number.isFinite(r0) || !Number.isFinite(r1)) return false;
|
|
370
|
+
if (Math.abs(r0 - r1) > 1e-6) return false;
|
|
371
|
+
return Math.max(r0, r1) > 0;
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function readCylindricalMetadata(obj) {
|
|
377
|
+
if (!obj) return null;
|
|
378
|
+
const ud = obj.userData || null;
|
|
379
|
+
if (ud?.metadata && isCylindricalMetadata(ud.metadata)) return ud.metadata;
|
|
380
|
+
if (typeof obj.getMetadata === 'function') {
|
|
381
|
+
try {
|
|
382
|
+
const meta = obj.getMetadata();
|
|
383
|
+
if (isCylindricalMetadata(meta)) return meta;
|
|
384
|
+
} catch { /* ignore */ }
|
|
385
|
+
}
|
|
386
|
+
const faceName = obj?.name || ud?.faceName || null;
|
|
387
|
+
let owner = obj?.parentSolid || ud?.parentSolid || obj?.parent || null;
|
|
388
|
+
while (owner) {
|
|
389
|
+
if (faceName && typeof owner.getFaceMetadata === 'function') {
|
|
390
|
+
try {
|
|
391
|
+
const meta = owner.getFaceMetadata(faceName);
|
|
392
|
+
if (isCylindricalMetadata(meta)) return meta;
|
|
393
|
+
} catch { /* ignore */ }
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
owner = owner.parent || null;
|
|
397
|
+
}
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function hasCylindricalMetadata(target) {
|
|
402
|
+
if (!target) return false;
|
|
403
|
+
if (readCylindricalMetadata(target)) return true;
|
|
404
|
+
if (Array.isArray(target.faces)) {
|
|
405
|
+
for (const face of target.faces) {
|
|
406
|
+
if (readCylindricalMetadata(face)) return true;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (target.parent && readCylindricalMetadata(target.parent)) return true;
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
|
|
348
413
|
function measureRadialValue(pmimode, ann) {
|
|
349
414
|
try {
|
|
350
415
|
const data = computeRadialPoints(pmimode, ann);
|
package/src/UI/viewer.js
CHANGED
|
@@ -533,6 +533,7 @@ export class Viewer {
|
|
|
533
533
|
this._attachRendererEvents(el);
|
|
534
534
|
|
|
535
535
|
SelectionFilter.viewer = this;
|
|
536
|
+
try { SelectionFilter._ensureSelectionFilterIndicator?.(this); } catch (_) { }
|
|
536
537
|
// Use capture on pointerup to ensure we end interactions even if pointerup fires off-element
|
|
537
538
|
window.addEventListener('pointerup', this._onPointerUp, { passive: false, capture: true });
|
|
538
539
|
document.addEventListener('dblclick', this._onGlobalDoubleClick, { passive: false, capture: true });
|
|
@@ -1476,8 +1477,15 @@ export class Viewer {
|
|
|
1476
1477
|
// ----------------------------------------
|
|
1477
1478
|
// PMI Edit Mode API
|
|
1478
1479
|
// ----------------------------------------
|
|
1480
|
+
_collapseExpandedDialogsForModeSwitch() {
|
|
1481
|
+
try { this.historyWidget?.collapseExpandedEntries?.({ clearOpenState: true, notify: false }); } catch { }
|
|
1482
|
+
try { this.assemblyConstraintsWidget?.collapseExpandedDialogs?.(); } catch { }
|
|
1483
|
+
try { this._pmiMode?.collapseExpandedDialogs?.(); } catch { }
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1479
1486
|
startPMIMode(viewEntry, viewIndex, widget = this.pmiViewsWidget) {
|
|
1480
1487
|
const alreadyActive = !!this._pmiMode;
|
|
1488
|
+
try { this._collapseExpandedDialogsForModeSwitch(); } catch { }
|
|
1481
1489
|
if (!alreadyActive) {
|
|
1482
1490
|
try { this.assemblyConstraintsWidget?.onPMIModeEnter?.(); } catch { }
|
|
1483
1491
|
}
|
|
@@ -1506,6 +1514,9 @@ export class Viewer {
|
|
|
1506
1514
|
|
|
1507
1515
|
endPMIMode() {
|
|
1508
1516
|
const hadMode = !!this._pmiMode;
|
|
1517
|
+
if (hadMode) {
|
|
1518
|
+
try { this._collapseExpandedDialogsForModeSwitch(); } catch { }
|
|
1519
|
+
}
|
|
1509
1520
|
try { if (this._pmiMode) this._pmiMode.dispose(); } catch { }
|
|
1510
1521
|
this._pmiMode = null;
|
|
1511
1522
|
if (hadMode) {
|
|
@@ -2365,24 +2376,34 @@ export class Viewer {
|
|
|
2365
2376
|
|
|
2366
2377
|
// Prefer the intersected object if it is clickable
|
|
2367
2378
|
let obj = intersection.object;
|
|
2379
|
+
if (obj && obj.type === 'POINTS' && obj.parent && String(obj.parent.type || '').toUpperCase() === SelectionFilter.VERTEX) {
|
|
2380
|
+
obj = obj.parent;
|
|
2381
|
+
}
|
|
2368
2382
|
|
|
2369
2383
|
// If the object (or its ancestors) doesn't expose onClick, climb to one that does
|
|
2370
2384
|
let target = obj;
|
|
2371
2385
|
while (target && typeof target.onClick !== 'function' && target.visible) target = target.parent;
|
|
2386
|
+
if (!target) target = obj;
|
|
2372
2387
|
if (!target) return null;
|
|
2373
2388
|
|
|
2374
2389
|
// Respect selection filter: ensure target is a permitted type, or ALL
|
|
2375
2390
|
if (typeof isAllowed === 'function') {
|
|
2376
2391
|
// Allow selecting already-selected items regardless (toggle off), consistent with SceneListing
|
|
2377
2392
|
if (!isAllowed(target.type) && !target.selected) {
|
|
2378
|
-
// Try to find a closer ancestor
|
|
2393
|
+
// Try to find a closer ancestor of allowed type
|
|
2379
2394
|
// Ascend first (e.g., FACE hit while EDGE is active should try parent SOLID only if allowed)
|
|
2380
2395
|
let t = target.parent;
|
|
2381
|
-
while (t &&
|
|
2382
|
-
if (t &&
|
|
2396
|
+
while (t && !isAllowed(t.type)) t = t.parent;
|
|
2397
|
+
if (t && isAllowed(t.type)) target = t;
|
|
2383
2398
|
else return null;
|
|
2384
2399
|
}
|
|
2385
2400
|
}
|
|
2401
|
+
if (target && typeof target.onClick !== 'function') {
|
|
2402
|
+
try {
|
|
2403
|
+
const deep = target.type === SelectionFilter.SOLID || target.type === SelectionFilter.COMPONENT;
|
|
2404
|
+
SelectionFilter.ensureSelectionHandlers?.(target, { deep });
|
|
2405
|
+
} catch { }
|
|
2406
|
+
}
|
|
2386
2407
|
return target;
|
|
2387
2408
|
}
|
|
2388
2409
|
|