brep-io-kernel 1.0.17 → 1.0.19

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.
Files changed (32) hide show
  1. package/dist-kernel/brep-kernel.js +13547 -12034
  2. package/package.json +1 -1
  3. package/src/FeatureRegistry.js +3 -0
  4. package/src/PartHistory.js +141 -0
  5. package/src/UI/CADmaterials.js +2 -2
  6. package/src/UI/MainToolbar.js +39 -2
  7. package/src/UI/SelectionFilter.js +438 -0
  8. package/src/UI/featureDialogWidgets/booleanOperationField.js +33 -52
  9. package/src/UI/featureDialogWidgets/referenceSelectionField.js +10 -0
  10. package/src/UI/featureDialogs.js +841 -8
  11. package/src/UI/history/HistoryCollectionWidget.js +20 -1
  12. package/src/UI/pmi/AnnotationRegistry.js +3 -0
  13. package/src/UI/toolbarButtons/orientToFaceButton.js +3 -0
  14. package/src/UI/toolbarButtons/registerDefaultButtons.js +0 -6
  15. package/src/UI/toolbarButtons/registerSelectionButtons.js +68 -0
  16. package/src/UI/viewer.js +22 -4
  17. package/src/assemblyConstraints/AssemblyConstraintRegistry.js +3 -0
  18. package/src/features/boolean/BooleanFeature.js +15 -0
  19. package/src/features/chamfer/ChamferFeature.js +12 -0
  20. package/src/features/extrude/ExtrudeFeature.js +11 -0
  21. package/src/features/fillet/FilletFeature.js +12 -0
  22. package/src/features/hole/HoleFeature.js +15 -0
  23. package/src/features/loft/LoftFeature.js +17 -0
  24. package/src/features/mirror/MirrorFeature.js +14 -0
  25. package/src/features/patternLinear/PatternLinearFeature.js +9 -0
  26. package/src/features/patternRadial/PatternRadialFeature.js +13 -0
  27. package/src/features/plane/PlaneFeature.js +10 -0
  28. package/src/features/revolve/RevolveFeature.js +15 -0
  29. package/src/features/sketch/SketchFeature.js +11 -0
  30. package/src/features/sweep/SweepFeature.js +17 -0
  31. package/src/features/transform/TransformFeature.js +12 -0
  32. package/src/features/tube/TubeFeature.js +12 -0
@@ -1,4 +1,5 @@
1
1
  import { SchemaForm } from '../featureDialogs.js';
2
+ import { SelectionFilter } from '../SelectionFilter.js';
2
3
  import { resolveEntryId, resolveHistoryDisplayInfo } from './historyDisplayInfo.js';
3
4
  import { HISTORY_COLLECTION_WIDGET_CSS } from './historyCollectionWidget.css.js';
4
5
 
@@ -43,6 +44,8 @@ export class HistoryCollectionWidget {
43
44
  this._addMenu = null;
44
45
  this._onGlobalClick = null;
45
46
  this._globalRefreshHandler = null;
47
+ this._contextSuppressKey = `hc-${Math.random().toString(36).slice(2, 9)}`;
48
+ this._contextSuppressActive = false;
46
49
 
47
50
  this.uiElement = document.createElement('div');
48
51
  this.uiElement.className = 'history-collection-widget-host';
@@ -100,6 +103,7 @@ export class HistoryCollectionWidget {
100
103
  }
101
104
 
102
105
  dispose() {
106
+ this._setContextSuppression(false);
103
107
  if (typeof this._listenerUnsub === 'function') {
104
108
  try { this._listenerUnsub(); } catch (_) {}
105
109
  }
@@ -152,6 +156,7 @@ export class HistoryCollectionWidget {
152
156
  this._listEl.textContent = '';
153
157
 
154
158
  if (!entries.length) {
159
+ this._setContextSuppression(false);
155
160
  const empty = document.createElement('div');
156
161
  empty.className = 'hc-empty';
157
162
  empty.textContent = 'No entries yet.';
@@ -185,6 +190,7 @@ export class HistoryCollectionWidget {
185
190
  targetId = null;
186
191
  }
187
192
  this._expandedId = targetId;
193
+ this._setContextSuppression(!!this._expandedId);
188
194
 
189
195
  for (let i = 0; i < entries.length; i++) {
190
196
  const entry = entries[i];
@@ -589,12 +595,24 @@ export class HistoryCollectionWidget {
589
595
  }
590
596
 
591
597
  _notifyEntryToggle(entry, isOpen) {
598
+ this._setContextSuppression(!!isOpen);
592
599
  if (!this._onEntryToggle) return;
593
600
  try {
594
601
  this._onEntryToggle(entry || null, isOpen);
595
602
  } catch (_) { /* ignore toggle hook errors */ }
596
603
  }
597
604
 
605
+ _setContextSuppression(isOpen) {
606
+ const next = !!isOpen;
607
+ if (this._contextSuppressActive === next) return;
608
+ this._contextSuppressActive = next;
609
+ if (SelectionFilter && typeof SelectionFilter.setContextBarSuppressed === 'function') {
610
+ try {
611
+ SelectionFilter.setContextBarSuppressed(this._contextSuppressKey, next);
612
+ } catch (_) { /* ignore */ }
613
+ }
614
+ }
615
+
598
616
  async _moveEntry(id, delta) {
599
617
  if (!id) return;
600
618
  const entries = this._getEntries();
@@ -659,7 +677,7 @@ export class HistoryCollectionWidget {
659
677
  this.render();
660
678
  this._emitCollectionChange('add', entry);
661
679
  this._deferScrollToEntry(createdEntryId);
662
- return;
680
+ return entry;
663
681
  }
664
682
  const entry = await this._instantiateEntryForType(typeStr);
665
683
  if (!entry) return;
@@ -680,6 +698,7 @@ export class HistoryCollectionWidget {
680
698
  this.render();
681
699
  this._emitCollectionChange('add', entry);
682
700
  this._deferScrollToEntry(createdEntryId);
701
+ return entry;
683
702
  }
684
703
 
685
704
  _handleSchemaChange(id, entry, details) {
@@ -29,6 +29,9 @@ class AnnotationRegistry {
29
29
  if (!ctor.longName) {
30
30
  ctor.longName = ctor.featureName || ctor.name || ctor.shortName || ctor.type || 'Annotation';
31
31
  }
32
+ if (typeof ctor.showContexButton !== 'function') {
33
+ ctor.showContexButton = () => false;
34
+ }
32
35
  }
33
36
  const typeKey = normalizeKey(
34
37
  handler.type
@@ -92,7 +92,10 @@ function _orientCameraToFace(viewer, face) {
92
92
  try { if (controls?.target) controls.target.copy(target); } catch {}
93
93
  try { if (controls?._gizmos?.position) controls._gizmos.position.copy(target); } catch {}
94
94
  try { controls?.update?.(); } catch {}
95
+ try { controls?._gizmos?.updateMatrix?.(); } catch {}
96
+ try { controls?._gizmos?.updateMatrixWorld?.(true); } catch {}
95
97
  try { controls?.updateMatrixState?.(); } catch {}
98
+ try { controls?.saveState?.(); } catch {}
96
99
  try { viewer.render?.(); } catch {}
97
100
 
98
101
  return true;
@@ -4,10 +4,7 @@
4
4
  import { createSaveButton } from './saveButton.js';
5
5
  import { createUndoButton, createRedoButton } from './undoRedoButtons.js';
6
6
  import { createZoomToFitButton } from './zoomToFitButton.js';
7
- import { createOrientToFaceButton } from './orientToFaceButton.js';
8
7
  import { createWireframeToggleButton } from './wireframeToggleButton.js';
9
- import { createInspectorToggleButton } from './inspectorToggleButton.js';
10
- import { createMetadataButton } from './metadataButton.js';
11
8
  import { createImportButton } from './importButton.js';
12
9
  import { createExportButton } from './exportButton.js';
13
10
  import { createFlatPatternButton } from './flatPatternButton.js';
@@ -21,10 +18,7 @@ export function registerDefaultToolbarButtons(viewer) {
21
18
  const creators = [
22
19
  createSaveButton,
23
20
  createZoomToFitButton,
24
- createOrientToFaceButton,
25
21
  createWireframeToggleButton,
26
- createInspectorToggleButton,
27
- createMetadataButton,
28
22
  createImportButton,
29
23
  createExportButton,
30
24
  createFlatPatternButton,
@@ -0,0 +1,68 @@
1
+ import { SelectionFilter } from '../SelectionFilter.js';
2
+ import { createOrientToFaceButton } from './orientToFaceButton.js';
3
+ import { createInspectorToggleButton } from './inspectorToggleButton.js';
4
+ import { createMetadataButton } from './metadataButton.js';
5
+
6
+ const hasSelection = (items) => Array.isArray(items) && items.length > 0;
7
+ const hasType = (items, types) => {
8
+ if (!Array.isArray(items) || items.length === 0) return false;
9
+ const typeSet = new Set((types || []).map((t) => String(t || '').toUpperCase()));
10
+ if (typeSet.size === 0) return false;
11
+ return items.some((obj) => typeSet.has(String(obj?.type || '').toUpperCase()));
12
+ };
13
+
14
+ export function registerSelectionToolbarButtons(viewer) {
15
+ if (!viewer || typeof SelectionFilter?.registerSelectionAction !== 'function') return;
16
+
17
+
18
+ try {
19
+ SelectionFilter.registerSelectionAction({
20
+ id: 'selection-action-clear',
21
+ label: '␛',
22
+ title: 'Clear selection',
23
+ onClick: () => {
24
+ const scene = viewer?.partHistory?.scene || viewer?.scene || null;
25
+ if (scene) SelectionFilter.unselectAll(scene);
26
+ try { viewer?._hideSelectionOverlay?.(); } catch { }
27
+ },
28
+ shouldShow: (selection) => hasSelection(selection),
29
+ });
30
+ } catch { }
31
+
32
+
33
+
34
+ try {
35
+ const perpSpec = createOrientToFaceButton(viewer);
36
+ if (perpSpec) {
37
+ SelectionFilter.registerSelectionAction({
38
+ id: 'selection-action-perp',
39
+ ...perpSpec,
40
+ shouldShow: (selection) => hasType(selection, ['FACE', 'PLANE']),
41
+ });
42
+ }
43
+ } catch { }
44
+
45
+ try {
46
+ const inspectorSpec = createInspectorToggleButton(viewer);
47
+ if (inspectorSpec) {
48
+ SelectionFilter.registerSelectionAction({
49
+ id: 'selection-action-inspector',
50
+ ...inspectorSpec,
51
+ shouldShow: (selection) => hasSelection(selection),
52
+ });
53
+ }
54
+ } catch { }
55
+
56
+
57
+
58
+ try {
59
+ const metadataSpec = createMetadataButton(viewer);
60
+ if (metadataSpec) {
61
+ SelectionFilter.registerSelectionAction({
62
+ id: 'selection-action-metadata',
63
+ ...metadataSpec,
64
+ shouldShow: (selection) => hasSelection(selection),
65
+ });
66
+ }
67
+ } catch { }
68
+ }
package/src/UI/viewer.js CHANGED
@@ -20,6 +20,7 @@ import './expressionsManager.js'
20
20
  import { expressionsManager } from './expressionsManager.js';
21
21
  import { MainToolbar } from './MainToolbar.js';
22
22
  import { registerDefaultToolbarButtons } from './toolbarButtons/registerDefaultButtons.js';
23
+ import { registerSelectionToolbarButtons } from './toolbarButtons/registerSelectionButtons.js';
23
24
  import { FileManagerWidget } from './fileManagerWidget.js';
24
25
  import './mobile.js';
25
26
  import { SketchMode3D } from './sketcher/SketchMode3D.js';
@@ -1239,6 +1240,9 @@ export class Viewer {
1239
1240
  this.mainToolbar = new MainToolbar(this);
1240
1241
  // Register core/default toolbar buttons via the public API
1241
1242
  try { registerDefaultToolbarButtons(this); } catch { }
1243
+ // Register selection-context toolbar buttons (shown based on selection)
1244
+ try { registerSelectionToolbarButtons(this); } catch { }
1245
+ try { SelectionFilter.refreshSelectionActions?.(); } catch { }
1242
1246
  // Drain any queued custom toolbar buttons from early plugin registration
1243
1247
  try {
1244
1248
  const q = Array.isArray(this._pendingToolbarButtons) ? this._pendingToolbarButtons : [];
@@ -3471,15 +3475,29 @@ export class Viewer {
3471
3475
  // Inspector panel (toggle + update-on-click)
3472
3476
  // ----------------------------------------
3473
3477
  toggleInspectorPanel() { this._inspectorOpen ? this._closeInspectorPanel() : this._openInspectorPanel(); }
3478
+ _getInspectorSelectionTarget() {
3479
+ const last = this._lastInspectorTarget;
3480
+ if (last && last.selected) return last;
3481
+ const scene = this.partHistory?.scene || this.scene || null;
3482
+ if (!scene || typeof scene.traverse !== 'function') return null;
3483
+ let found = null;
3484
+ scene.traverse((obj) => {
3485
+ if (found || !obj || !obj.selected) return;
3486
+ found = obj;
3487
+ });
3488
+ return found;
3489
+ }
3474
3490
  _openInspectorPanel() {
3475
3491
  if (this._inspectorOpen) return;
3476
3492
  this._ensureInspectorPanel();
3477
3493
  this._inspectorEl.style.display = 'flex';
3478
3494
  this._inspectorOpen = true;
3479
- // Placeholder message until user clicks an object
3480
- try {
3481
- this._setInspectorPlaceholder('Click an object in the scene to inspect.');
3482
- } catch { }
3495
+ const target = this._getInspectorSelectionTarget();
3496
+ if (target) {
3497
+ try { this._updateInspectorFor(target); } catch { }
3498
+ return;
3499
+ }
3500
+ try { this._setInspectorPlaceholder('Click an object in the scene to inspect.'); } catch { }
3483
3501
  }
3484
3502
  _closeInspectorPanel() {
3485
3503
  if (!this._inspectorOpen) return;
@@ -54,6 +54,9 @@ export class AssemblyConstraintRegistry {
54
54
  if (!ConstraintClass.longName) {
55
55
  ConstraintClass.longName = ConstraintClass.constraintName || ConstraintClass.name || ConstraintClass.shortName || 'Constraint';
56
56
  }
57
+ if (typeof ConstraintClass.showContexButton !== 'function') {
58
+ ConstraintClass.showContexButton = () => false;
59
+ }
57
60
  const keys = this.#collectKeys(ConstraintClass);
58
61
  if (!keys.typeKey) return;
59
62
 
@@ -25,6 +25,21 @@ export class BooleanFeature {
25
25
  static shortName = "B";
26
26
  static longName = "Boolean";
27
27
  static inputParamsSchema = inputParamsSchema;
28
+ static showContexButton(selectedItems) {
29
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
30
+ const solids = items
31
+ .filter((it) => String(it?.type || '').toUpperCase() === 'SOLID')
32
+ .map((it) => it?.name)
33
+ .filter((name) => !!name);
34
+ if (solids.length < 2) return false;
35
+ const [targetSolid, ...tools] = solids;
36
+ return {
37
+ params: {
38
+ targetSolid,
39
+ boolean: { operation: 'UNION', targets: tools },
40
+ },
41
+ };
42
+ }
28
43
 
29
44
  constructor() {
30
45
  this.inputParams = {};
@@ -46,6 +46,18 @@ export class ChamferFeature {
46
46
  static shortName = "CH";
47
47
  static longName = "Chamfer";
48
48
  static inputParamsSchema = inputParamsSchema;
49
+ static showContexButton(selectedItems) {
50
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
51
+ const edges = items
52
+ .filter((it) => {
53
+ const type = String(it?.type || '').toUpperCase();
54
+ return type === 'EDGE' || type === 'FACE';
55
+ })
56
+ .map((it) => it?.name || it?.userData?.edgeName || it?.userData?.faceName)
57
+ .filter((name) => !!name);
58
+ if (!edges.length) return false;
59
+ return { params: { edges } };
60
+ }
49
61
 
50
62
  constructor() {
51
63
  this.inputParams = {};
@@ -41,6 +41,17 @@ export class ExtrudeFeature {
41
41
  static shortName = "E";
42
42
  static longName = "Extrude";
43
43
  static inputParamsSchema = inputParamsSchema;
44
+ static showContexButton(selectedItems) {
45
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
46
+ const pick = items.find((it) => {
47
+ const type = String(it?.type || '').toUpperCase();
48
+ return type === 'FACE' || type === 'SKETCH';
49
+ });
50
+ if (!pick) return false;
51
+ const name = pick?.name || pick?.userData?.faceName || pick?.userData?.edgeName || null;
52
+ if (!name) return false;
53
+ return { field: 'profile', value: name };
54
+ }
44
55
 
45
56
  constructor() {
46
57
  this.inputParams = {};
@@ -63,6 +63,18 @@ export class FilletFeature {
63
63
  static shortName = "F";
64
64
  static longName = "Fillet";
65
65
  static inputParamsSchema = inputParamsSchema;
66
+ static showContexButton(selectedItems) {
67
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
68
+ const edges = items
69
+ .filter((it) => {
70
+ const type = String(it?.type || '').toUpperCase();
71
+ return type === 'EDGE' || type === 'FACE';
72
+ })
73
+ .map((it) => it?.name || it?.userData?.edgeName || it?.userData?.faceName)
74
+ .filter((name) => !!name);
75
+ if (!edges.length) return false;
76
+ return { params: { edges } };
77
+ }
66
78
 
67
79
  constructor() {
68
80
  this.inputParams = {};
@@ -569,6 +569,21 @@ export class HoleFeature {
569
569
  static shortName = 'H';
570
570
  static longName = 'Hole';
571
571
  static inputParamsSchema = inputParamsSchema;
572
+ static showContexButton(selectedItems) {
573
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
574
+ const sketch = items.find((it) => {
575
+ const type = String(it?.type || '').toUpperCase();
576
+ if (type === 'SKETCH') return true;
577
+ if (it?.parent && String(it.parent.type || '').toUpperCase() === 'SKETCH') return true;
578
+ return false;
579
+ });
580
+ if (!sketch) return false;
581
+ const name = (String(sketch?.type || '').toUpperCase() === 'SKETCH')
582
+ ? sketch.name
583
+ : sketch.parent?.name;
584
+ if (!name) return false;
585
+ return { field: 'face', value: name };
586
+ }
572
587
 
573
588
  constructor() {
574
589
  this.inputParams = {};
@@ -58,6 +58,23 @@ export class LoftFeature {
58
58
  static shortName = "LOFT";
59
59
  static longName = "Loft";
60
60
  static inputParamsSchema = inputParamsSchema;
61
+ static showContexButton(selectedItems) {
62
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
63
+ const profiles = items
64
+ .filter((it) => {
65
+ const type = String(it?.type || '').toUpperCase();
66
+ return type === 'FACE' || type === 'SKETCH';
67
+ })
68
+ .map((it) => {
69
+ if (!it) return null;
70
+ if (String(it.type || '').toUpperCase() === 'SKETCH') return it.name || null;
71
+ if (it.parent && String(it.parent.type || '').toUpperCase() === 'SKETCH') return it.parent.name || null;
72
+ return it.name || it.userData?.faceName || null;
73
+ })
74
+ .filter((name) => !!name);
75
+ if (profiles.length < 2) return false;
76
+ return { params: { profiles } };
77
+ }
61
78
 
62
79
  constructor() {
63
80
  this.inputParams = {};
@@ -35,6 +35,20 @@ export class MirrorFeature {
35
35
  static longName = "Mirror";
36
36
 
37
37
  static inputParamsSchema = inputParamsSchema;
38
+ static showContexButton(selectedItems) {
39
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
40
+ const solids = items
41
+ .filter((it) => String(it?.type || '').toUpperCase() === 'SOLID')
42
+ .map((it) => it?.name)
43
+ .filter((name) => !!name);
44
+ const plane = items.find((it) => {
45
+ const type = String(it?.type || '').toUpperCase();
46
+ return type === 'FACE' || type === 'PLANE';
47
+ });
48
+ const planeName = plane?.name || plane?.userData?.faceName || null;
49
+ if (!solids.length || !planeName) return false;
50
+ return { params: { solids, mirrorPlane: planeName } };
51
+ }
38
52
 
39
53
  constructor() {
40
54
  this.inputParams = {};
@@ -34,6 +34,15 @@ export class PatternLinearFeature {
34
34
  static shortName = "PATLIN";
35
35
  static longName = "Pattern Linear";
36
36
  static inputParamsSchema = inputParamsSchema;
37
+ static showContexButton(selectedItems) {
38
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
39
+ const solids = items
40
+ .filter((it) => String(it?.type || '').toUpperCase() === 'SOLID')
41
+ .map((it) => it?.name)
42
+ .filter((name) => !!name);
43
+ if (!solids.length) return false;
44
+ return { params: { solids } };
45
+ }
37
46
 
38
47
  constructor() {
39
48
  this.inputParams = {};
@@ -49,6 +49,19 @@ export class PatternRadialFeature {
49
49
  static shortName = "PATRAD";
50
50
  static longName = "Pattern Radial";
51
51
  static inputParamsSchema = inputParamsSchema;
52
+ static showContexButton(selectedItems) {
53
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
54
+ const solids = items
55
+ .filter((it) => String(it?.type || '').toUpperCase() === 'SOLID')
56
+ .map((it) => it?.name)
57
+ .filter((name) => !!name);
58
+ if (!solids.length) return false;
59
+ const axis = items.find((it) => String(it?.type || '').toUpperCase() === 'EDGE');
60
+ const axisName = axis?.name || axis?.userData?.edgeName || null;
61
+ const params = { solids };
62
+ if (axisName) params.axisRef = axisName;
63
+ return { params };
64
+ }
52
65
 
53
66
  constructor() {
54
67
  this.inputParams = {};
@@ -46,6 +46,16 @@ export class PlaneFeature {
46
46
  static shortName = "P";
47
47
  static longName = "Plane";
48
48
  static inputParamsSchema = inputParamsSchema;
49
+ static showContexButton(selectedItems) {
50
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
51
+ const ref = items.find((it) => {
52
+ const type = String(it?.type || '').toUpperCase();
53
+ return type === 'FACE' || type === 'PLANE';
54
+ });
55
+ const name = ref?.name || ref?.userData?.faceName || null;
56
+ if (!name) return false;
57
+ return { field: 'datum', value: name };
58
+ }
49
59
 
50
60
  constructor() {
51
61
  this.inputParams = {};
@@ -47,6 +47,21 @@ export class RevolveFeature {
47
47
  static shortName = "R";
48
48
  static longName = "Revolve";
49
49
  static inputParamsSchema = inputParamsSchema;
50
+ static showContexButton(selectedItems) {
51
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
52
+ const profileObj = items.find((it) => {
53
+ const type = String(it?.type || '').toUpperCase();
54
+ return type === 'FACE' || type === 'SKETCH';
55
+ });
56
+ if (!profileObj) return false;
57
+ const profileName = profileObj?.name || profileObj?.userData?.faceName || null;
58
+ if (!profileName) return false;
59
+ const axisObj = items.find((it) => String(it?.type || '').toUpperCase() === 'EDGE');
60
+ const axisName = axisObj?.name || axisObj?.userData?.edgeName || null;
61
+ const params = { profile: profileName };
62
+ if (axisName) params.axis = axisName;
63
+ return { params };
64
+ }
50
65
 
51
66
  constructor() {
52
67
  this.inputParams = {};
@@ -82,6 +82,17 @@ export class SketchFeature {
82
82
  static shortName = "S";
83
83
  static longName = "Sketch";
84
84
  static inputParamsSchema = inputParamsSchema;
85
+ static showContexButton(selectedItems) {
86
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
87
+ const target = items.find((it) => {
88
+ const type = String(it?.type || '').toUpperCase();
89
+ return type === 'FACE' || type === 'PLANE';
90
+ });
91
+ if (!target) return false;
92
+ const name = target?.name || target?.userData?.faceName || null;
93
+ if (!name) return false;
94
+ return { field: 'sketchPlane', value: name };
95
+ }
85
96
 
86
97
  constructor() {
87
98
  this.inputParams = {};
@@ -48,6 +48,23 @@ export class SweepFeature {
48
48
  static shortName = "SW";
49
49
  static longName = "Sweep";
50
50
  static inputParamsSchema = inputParamsSchema;
51
+ static showContexButton(selectedItems) {
52
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
53
+ const profileObj = items.find((it) => {
54
+ const type = String(it?.type || '').toUpperCase();
55
+ return type === 'FACE' || type === 'SKETCH';
56
+ });
57
+ if (!profileObj) return false;
58
+ const profileName = profileObj?.name || profileObj?.userData?.faceName || null;
59
+ if (!profileName) return false;
60
+ const edges = items
61
+ .filter((it) => String(it?.type || '').toUpperCase() === 'EDGE')
62
+ .map((it) => it?.name || it?.userData?.edgeName)
63
+ .filter((name) => !!name);
64
+ const params = { profile: profileName };
65
+ if (edges.length) params.path = edges;
66
+ return { params };
67
+ }
51
68
 
52
69
  constructor() {
53
70
  this.inputParams = {};
@@ -57,6 +57,18 @@ export class TransformFeature {
57
57
  static shortName = "XFORM";
58
58
  static longName = "Transform";
59
59
  static inputParamsSchema = inputParamsSchema;
60
+ static showContexButton(selectedItems) {
61
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
62
+ const solids = items
63
+ .filter((it) => {
64
+ const type = String(it?.type || '').toUpperCase();
65
+ return type === 'SOLID';
66
+ })
67
+ .map((it) => it?.name)
68
+ .filter((name) => !!name);
69
+ if (!solids.length) return false;
70
+ return { params: { solids } };
71
+ }
60
72
 
61
73
  constructor() {
62
74
  this.inputParams = {};
@@ -332,6 +332,18 @@ export class TubeFeature {
332
332
  static shortName = 'TU';
333
333
  static longName = 'Tube';
334
334
  static inputParamsSchema = inputParamsSchema;
335
+ static showContexButton(selectedItems) {
336
+ const items = Array.isArray(selectedItems) ? selectedItems : [];
337
+ if (items.some((it) => String(it?.type || '').toUpperCase() !== 'EDGE')) {
338
+ return false;
339
+ }
340
+ const edges = items
341
+ .filter((it) => String(it?.type || '').toUpperCase() === 'EDGE')
342
+ .map((it) => it?.name || it?.userData?.edgeName)
343
+ .filter((name) => !!name);
344
+ if (!edges.length) return false;
345
+ return { params: { path: edges } };
346
+ }
335
347
 
336
348
  constructor() {
337
349
  this.inputParams = {};