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.
Files changed (37) hide show
  1. package/README.md +4 -1
  2. package/dist-kernel/brep-kernel.js +17545 -16874
  3. package/package.json +3 -2
  4. package/src/BREP/Edge.js +2 -0
  5. package/src/BREP/Face.js +2 -0
  6. package/src/BREP/SolidMethods/fillet.js +17 -3
  7. package/src/BREP/SolidMethods/visualize.js +372 -365
  8. package/src/BREP/Vertex.js +2 -17
  9. package/src/BREP/fillets/fillet.js +193 -39
  10. package/src/PartHistory.js +4 -25
  11. package/src/SketchSolver2D.js +3 -0
  12. package/src/UI/AccordionWidget.js +1 -1
  13. package/src/UI/EnvMonacoEditor.js +0 -3
  14. package/src/UI/HistoryWidget.js +3 -0
  15. package/src/UI/SceneListing.js +45 -7
  16. package/src/UI/SelectionFilter.js +469 -442
  17. package/src/UI/SelectionState.js +464 -0
  18. package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +40 -1
  19. package/src/UI/assembly/AssemblyConstraintsWidget.js +17 -3
  20. package/src/UI/assembly/constraintSelectionUtils.js +3 -182
  21. package/src/UI/{assembly/constraintFaceUtils.js → faceUtils.js} +30 -5
  22. package/src/UI/featureDialogs.js +99 -69
  23. package/src/UI/pmi/LabelOverlay.js +32 -0
  24. package/src/UI/pmi/PMIMode.js +23 -0
  25. package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +7 -1
  26. package/src/UI/toolbarButtons/orientToFaceButton.js +3 -36
  27. package/src/UI/toolbarButtons/registerDefaultButtons.js +2 -0
  28. package/src/UI/toolbarButtons/selectionStateButton.js +206 -0
  29. package/src/UI/viewer.js +16 -16
  30. package/src/assemblyConstraints/AssemblyConstraintHistory.js +18 -42
  31. package/src/assemblyConstraints/constraints/AngleConstraint.js +1 -0
  32. package/src/assemblyConstraints/constraints/DistanceConstraint.js +1 -0
  33. package/src/features/fillet/FilletFeature.js +7 -0
  34. package/src/features/selectionUtils.js +21 -5
  35. package/src/features/sketch/SketchFeature.js +2 -2
  36. package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +3 -2
  37. package/src/utils/selectionResolver.js +258 -0
@@ -0,0 +1,206 @@
1
+ import { FloatingWindow } from '../FloatingWindow.js';
2
+ import { SelectionFilter } from '../SelectionFilter.js';
3
+
4
+ const PANEL_KEY = '__selectionStatePanel';
5
+
6
+ function _ensureStyles() {
7
+ if (document.getElementById('selection-state-styles')) return;
8
+ const style = document.createElement('style');
9
+ style.id = 'selection-state-styles';
10
+ style.textContent = `
11
+ .selection-state-content {
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: 8px;
15
+ padding: 10px;
16
+ height: 100%;
17
+ box-sizing: border-box;
18
+ color: #e5e7eb;
19
+ font-size: 12px;
20
+ }
21
+ .selection-state-header {
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: space-between;
25
+ gap: 8px;
26
+ font-weight: 700;
27
+ }
28
+ .selection-state-count {
29
+ color: #cbd5f5;
30
+ font-size: 12px;
31
+ }
32
+ .selection-state-list {
33
+ flex: 1 1 auto;
34
+ min-height: 0;
35
+ overflow: auto;
36
+ background: rgba(8,10,14,.55);
37
+ border: 1px solid #1f2937;
38
+ border-radius: 8px;
39
+ padding: 8px;
40
+ display: flex;
41
+ flex-direction: column;
42
+ gap: 6px;
43
+ }
44
+ .selection-state-item {
45
+ display: grid;
46
+ grid-template-columns: auto 1fr;
47
+ gap: 6px 10px;
48
+ align-items: center;
49
+ padding: 4px 6px;
50
+ border-radius: 6px;
51
+ background: rgba(255,255,255,.02);
52
+ border: 1px solid rgba(148,163,184,.18);
53
+ }
54
+ .selection-state-type {
55
+ font-size: 10px;
56
+ font-weight: 700;
57
+ letter-spacing: 0.04em;
58
+ text-transform: uppercase;
59
+ color: #9aa0aa;
60
+ }
61
+ .selection-state-name {
62
+ font-size: 12px;
63
+ color: #e5e7eb;
64
+ word-break: break-word;
65
+ }
66
+ .selection-state-empty {
67
+ color: #9aa0aa;
68
+ font-style: italic;
69
+ padding: 6px;
70
+ }
71
+ `;
72
+ document.head.appendChild(style);
73
+ }
74
+
75
+ function _labelFor(obj) {
76
+ if (!obj) return 'Unknown';
77
+ const name = obj.name
78
+ || obj.userData?.faceName
79
+ || obj.userData?.edgeName
80
+ || obj.userData?.vertexName
81
+ || obj.userData?.solidName
82
+ || obj.userData?.name
83
+ || null;
84
+ return name || '(unnamed)';
85
+ }
86
+
87
+ class SelectionStatePanel {
88
+ constructor(viewer) {
89
+ this.viewer = viewer || null;
90
+ this.window = null;
91
+ this.root = null;
92
+ this.content = null;
93
+ this.countEl = null;
94
+ this.listEl = null;
95
+ this._boundSelectionChanged = this._handleSelectionChanged.bind(this);
96
+ try { window.addEventListener('selection-changed', this._boundSelectionChanged); } catch { }
97
+ }
98
+
99
+ toggle() {
100
+ if (this.root && this.root.style.display !== 'none') this.close();
101
+ else this.open();
102
+ }
103
+
104
+ open() {
105
+ this._ensureWindow();
106
+ if (!this.root) return;
107
+ this.root.style.display = 'flex';
108
+ this._render();
109
+ }
110
+
111
+ close() {
112
+ if (this.root) {
113
+ try { this.root.style.display = 'none'; } catch { }
114
+ }
115
+ }
116
+
117
+ _ensureWindow() {
118
+ if (this.window && this.root) return;
119
+ _ensureStyles();
120
+ const fw = new FloatingWindow({
121
+ title: 'Selection State',
122
+ width: 360,
123
+ height: 320,
124
+ right: 14,
125
+ top: 120,
126
+ shaded: false,
127
+ onClose: () => this.close(),
128
+ });
129
+
130
+ const content = document.createElement('div');
131
+ content.className = 'selection-state-content';
132
+
133
+ const header = document.createElement('div');
134
+ header.className = 'selection-state-header';
135
+ const title = document.createElement('div');
136
+ title.textContent = 'Currently selected';
137
+ const count = document.createElement('div');
138
+ count.className = 'selection-state-count';
139
+ header.appendChild(title);
140
+ header.appendChild(count);
141
+
142
+ const list = document.createElement('div');
143
+ list.className = 'selection-state-list';
144
+
145
+ content.appendChild(header);
146
+ content.appendChild(list);
147
+ fw.content.appendChild(content);
148
+
149
+ this.window = fw;
150
+ this.root = fw.root;
151
+ this.content = content;
152
+ this.countEl = count;
153
+ this.listEl = list;
154
+ }
155
+
156
+ _handleSelectionChanged() {
157
+ if (!this.root || this.root.style.display === 'none') return;
158
+ this._render();
159
+ }
160
+
161
+ _render() {
162
+ const list = this.listEl;
163
+ const countEl = this.countEl;
164
+ if (!list || !countEl) return;
165
+
166
+ const scene = this.viewer?.partHistory?.scene || this.viewer?.scene || null;
167
+ const selection = SelectionFilter.getSelectedObjects({ scene }) || [];
168
+ countEl.textContent = `${selection.length} selected`;
169
+
170
+ list.textContent = '';
171
+ if (!selection.length) {
172
+ const empty = document.createElement('div');
173
+ empty.className = 'selection-state-empty';
174
+ empty.textContent = 'No items selected.';
175
+ list.appendChild(empty);
176
+ return;
177
+ }
178
+
179
+ for (const obj of selection) {
180
+ const row = document.createElement('div');
181
+ row.className = 'selection-state-item';
182
+ const type = document.createElement('div');
183
+ type.className = 'selection-state-type';
184
+ type.textContent = String(obj?.type || 'object').toUpperCase();
185
+ const name = document.createElement('div');
186
+ name.className = 'selection-state-name';
187
+ name.textContent = _labelFor(obj);
188
+ row.appendChild(type);
189
+ row.appendChild(name);
190
+ list.appendChild(row);
191
+ }
192
+ }
193
+ }
194
+
195
+ export function createSelectionStateButton(viewer) {
196
+ if (!viewer) return null;
197
+ if (!viewer[PANEL_KEY]) {
198
+ viewer[PANEL_KEY] = new SelectionStatePanel(viewer);
199
+ }
200
+ const panel = viewer[PANEL_KEY];
201
+ return {
202
+ label: 'Sel',
203
+ title: 'Toggle selection state window',
204
+ onClick: () => panel.toggle(),
205
+ };
206
+ }
package/src/UI/viewer.js CHANGED
@@ -16,6 +16,7 @@ import { HistoryWidget } from './HistoryWidget.js';
16
16
  import { AssemblyConstraintsWidget } from './assembly/AssemblyConstraintsWidget.js';
17
17
  import { PartHistory } from '../PartHistory.js';
18
18
  import { SelectionFilter } from './SelectionFilter.js';
19
+ import { SelectionState } from './SelectionState.js';
19
20
  import './expressionsManager.js'
20
21
  import { expressionsManager } from './expressionsManager.js';
21
22
  import { MainToolbar } from './MainToolbar.js';
@@ -533,6 +534,7 @@ export class Viewer {
533
534
  this._attachRendererEvents(el);
534
535
 
535
536
  SelectionFilter.viewer = this;
537
+ try { SelectionFilter.startClickWatcher(this); } catch (_) { }
536
538
  try { SelectionFilter._ensureSelectionFilterIndicator?.(this); } catch (_) { }
537
539
  // Use capture on pointerup to ensure we end interactions even if pointerup fires off-element
538
540
  window.addEventListener('pointerup', this._onPointerUp, { passive: false, capture: true });
@@ -2185,16 +2187,8 @@ export class Viewer {
2185
2187
  const ud = obj.userData;
2186
2188
  const defaultMaterial = ud.__defaultMaterial ?? baseMaterial;
2187
2189
  if (!ud.__defaultMaterial) ud.__defaultMaterial = baseMaterial;
2188
- const isHovered = !!ud.__hoverMatApplied;
2189
- const isSelected = obj.selected === true;
2190
-
2191
2190
  const applyBase = (mat) => {
2192
- ud.__baseMaterial = mat;
2193
- if (isHovered) {
2194
- ud.__hoverOrigMat = mat;
2195
- } else if (!isSelected && mat) {
2196
- obj.material = mat;
2197
- }
2191
+ SelectionState.setBaseMaterial(obj, mat);
2198
2192
  };
2199
2193
 
2200
2194
  if (!color) {
@@ -2398,12 +2392,6 @@ export class Viewer {
2398
2392
  else return null;
2399
2393
  }
2400
2394
  }
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
- }
2407
2395
  return target;
2408
2396
  }
2409
2397
 
@@ -2587,11 +2575,23 @@ export class Viewer {
2587
2575
  SelectionFilter.EDGE,
2588
2576
  SelectionFilter.FACE,
2589
2577
  SelectionFilter.PLANE,
2578
+ SelectionFilter.SKETCH,
2579
+ SelectionFilter.DATUM,
2580
+ SelectionFilter.HELIX,
2581
+ SelectionFilter.LOOP,
2590
2582
  SelectionFilter.SOLID,
2591
2583
  SelectionFilter.COMPONENT,
2592
2584
  ].map(t => normType(t));
2585
+ const normSolid = normType(SelectionFilter.SOLID);
2586
+ const normComponent = normType(SelectionFilter.COMPONENT);
2587
+ const nonSolidAllowed = Array.from(allowedSet).some(t => t && t !== normSolid && t !== normComponent);
2593
2588
  const getPriority = (type) => {
2594
- const idx = priorityOrder.indexOf(normType(type));
2589
+ const nt = normType(type);
2590
+ if (nonSolidAllowed && (nt === normSolid || nt === normComponent)) {
2591
+ // Always push SOLID/COMPONENT to the end when any other type is allowed.
2592
+ return priorityOrder.length + 2;
2593
+ }
2594
+ const idx = priorityOrder.indexOf(nt);
2595
2595
  return idx === -1 ? priorityOrder.length : idx;
2596
2596
  };
2597
2597
  const isAllowedType = (type) => {
@@ -3,6 +3,7 @@ import { AssemblyConstraintRegistry } from './AssemblyConstraintRegistry.js';
3
3
  import { evaluateConstraintNumericValue } from './constraintExpressionUtils.js';
4
4
  import { deepClone } from '../utils/deepClone.js';
5
5
  import { normalizeTypeString } from '../utils/normalizeTypeString.js';
6
+ import { resolveSelectionObject } from '../utils/selectionResolver.js';
6
7
 
7
8
  const RESERVED_KEYS = new Set(['type', 'persistentData', '__open']);
8
9
 
@@ -102,47 +103,6 @@ function removeExistingDebugArrows(scene) {
102
103
  }
103
104
  }
104
105
 
105
- function resolveSelectionObject(scene, selection) {
106
- if (!scene || selection == null) return null;
107
- let target = selection;
108
- if (Array.isArray(selection)) {
109
- target = selection.find((item) => item != null) ?? null;
110
- }
111
- if (!target) return null;
112
- if (target.isObject3D) return target;
113
- try {
114
- if (typeof target === 'string') {
115
- if (typeof scene.traverse === 'function') {
116
- let best = null;
117
- scene.traverse((obj) => {
118
- if (!obj || obj.name !== target) return;
119
- if (!best) best = obj;
120
- const component = resolveComponentFromObject(obj);
121
- const bestComponent = best ? resolveComponentFromObject(best) : null;
122
- if (component && !bestComponent) {
123
- best = obj;
124
- }
125
- });
126
- if (best) return best;
127
- }
128
- return typeof scene.getObjectByName === 'function'
129
- ? scene.getObjectByName(target)
130
- : null;
131
- }
132
- if (typeof target?.uuid === 'string' && typeof scene.getObjectByProperty === 'function') {
133
- const found = scene.getObjectByProperty('uuid', target.uuid);
134
- if (found) return found;
135
- }
136
- if (typeof target?.name === 'string' && typeof scene.getObjectByName === 'function') {
137
- const found = scene.getObjectByName(target.name);
138
- if (found) return found;
139
- }
140
- } catch {
141
- return null;
142
- }
143
- return null;
144
- }
145
-
146
106
  function resolveComponentFromObject(obj) {
147
107
  let current = obj;
148
108
  while (current) {
@@ -152,6 +112,10 @@ function resolveComponentFromObject(obj) {
152
112
  return null;
153
113
  }
154
114
 
115
+ function scoreObjectForComponent(object) {
116
+ return resolveComponentFromObject(object) ? 1 : 0;
117
+ }
118
+
155
119
  function vectorFrom(value) {
156
120
  if (!value) return null;
157
121
  if (value instanceof THREE.Vector3) return value.clone();
@@ -894,7 +858,19 @@ export class AssemblyConstraintHistory {
894
858
 
895
859
  const updatedComponents = new Set();
896
860
 
897
- const resolveObject = (selection) => resolveSelectionObject(scene, selection);
861
+ const resolveObject = (selection) => resolveSelectionObject(scene, selection, {
862
+ scoreFn: scoreObjectForComponent,
863
+ allowJson: false,
864
+ allowUuidString: false,
865
+ allowUuidObject: true,
866
+ allowFuzzyName: false,
867
+ allowNameContains: false,
868
+ allowPath: false,
869
+ allowReference: false,
870
+ allowTarget: false,
871
+ allowSelectionName: false,
872
+ arrayMode: 'first',
873
+ });
898
874
  const resolveComponent = (selection) => {
899
875
  const obj = resolveObject(selection);
900
876
  return resolveComponentFromObject(obj);
@@ -33,6 +33,7 @@ export class AngleConstraint extends BaseAssemblyConstraint {
33
33
  static shortName = '∠';
34
34
  static longName = '∠ Angle Constraint';
35
35
  static constraintType = 'angle';
36
+ static focusField = 'angle';
36
37
  static aliases = ['angle', 'angle_between', 'angular', 'ANGL'];
37
38
  static inputParamsSchema = inputParamsSchema;
38
39
 
@@ -37,6 +37,7 @@ export class DistanceConstraint extends BaseAssemblyConstraint {
37
37
  static shortName = '⟺';
38
38
  static longName = '⟺ Distance Constraint';
39
39
  static constraintType = 'distance';
40
+ static focusField = 'distance';
40
41
  static aliases = ['distance', 'offset', 'gap', 'DIST'];
41
42
  static inputParamsSchema = inputParamsSchema;
42
43
 
@@ -52,6 +52,12 @@ const inputParamsSchema = {
52
52
  default_value: false,
53
53
  hint: "Show pre-inflate tangent overlays on the fillet tube",
54
54
  },
55
+ cleanupTinyFaceIslandsArea: {
56
+ type: "number",
57
+ step: 0.001,
58
+ default_value: 0.01,
59
+ hint: "Relabel tiny disconnected face islands below this area threshold (<= 0 disables).",
60
+ },
55
61
  debug: {
56
62
  type: "boolean",
57
63
  default_value: false,
@@ -96,6 +102,7 @@ export class FilletFeature {
96
102
  resolution: this.inputParams?.resolution,
97
103
  inflate: this.inputParams?.inflate,
98
104
  showTangentOverlays: this.inputParams?.showTangentOverlays,
105
+ cleanupTinyFaceIslandsArea: this.inputParams?.cleanupTinyFaceIslandsArea,
99
106
  debug: this.inputParams?.debug,
100
107
  });
101
108
  try { clearFilletCaches(); } catch { }
@@ -1,16 +1,32 @@
1
+ import { resolveSelectionObject as resolveSelectionObjectBase } from '../utils/selectionResolver.js';
2
+
1
3
  export function resolveSelectionObject(selection, partHistory) {
2
4
  if (!selection) return null;
3
- if (typeof selection === 'string') {
5
+ if (typeof selection !== 'string') return selection;
6
+
7
+ const nameResolver = (name) => {
4
8
  if (partHistory && typeof partHistory.getObjectByName === 'function') {
5
- return partHistory.getObjectByName(selection);
9
+ return partHistory.getObjectByName(name);
6
10
  }
7
11
  const scene = partHistory?.scene;
8
12
  if (scene && typeof scene.getObjectByName === 'function') {
9
- return scene.getObjectByName(selection);
13
+ return scene.getObjectByName(name);
10
14
  }
11
15
  return null;
12
- }
13
- return selection;
16
+ };
17
+
18
+ return resolveSelectionObjectBase(partHistory?.scene || null, selection, {
19
+ nameResolver,
20
+ allowJson: false,
21
+ allowUuid: false,
22
+ allowFuzzyName: false,
23
+ allowNameContains: false,
24
+ allowPath: false,
25
+ allowReference: false,
26
+ allowTarget: false,
27
+ allowSelectionName: false,
28
+ arrayMode: 'first',
29
+ });
14
30
  }
15
31
 
16
32
  function getFeatureEntryId(entry) {
@@ -4,6 +4,7 @@ import { BREP } from "../../BREP/BREP.js";
4
4
  const THREE = BREP.THREE;
5
5
  import { LineGeometry } from 'three/examples/jsm/Addons.js';
6
6
  import { deepClone } from '../../utils/deepClone.js';
7
+ import { SelectionState } from '../../UI/SelectionState.js';
7
8
 
8
9
  const inputParamsSchema = {
9
10
  id: {
@@ -892,8 +893,7 @@ export class SketchFeature {
892
893
  if (sketchMat) {
893
894
  sketchMat.side = THREE.DoubleSide;
894
895
  sketchMat.needsUpdate = true;
895
- face.material = sketchMat;
896
- face.userData.__baseMaterial = sketchMat;
896
+ SelectionState.setBaseMaterial(face, sketchMat);
897
897
  }
898
898
  } catch { }
899
899
  sceneGroup.add(face);
@@ -689,9 +689,10 @@ const shortestAngleDelta = (target, current) => {
689
689
 
690
690
 
691
691
  export const constraints = {
692
- tolerance,
692
+ get tolerance() { return tolerance; },
693
+ set tolerance(value) { tolerance = value; },
693
694
  constraintFunctions,
694
- }
695
+ };
695
696
 
696
697
 
697
698