brep-io-kernel 1.0.18 → 1.0.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brep-io-kernel",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "scripts": {
5
5
  "dev": "pnpm generateLicenses && pnpm build:kernel && vite --host 0.0.0.0",
6
6
  "build": "pnpm generateLicenses && vite build",
@@ -134,6 +134,9 @@ export class FeatureRegistry {
134
134
  if (!FeatureClass.longName) {
135
135
  FeatureClass.longName = FeatureClass.featureName || FeatureClass.name || FeatureClass.shortName || 'Feature';
136
136
  }
137
+ if (typeof FeatureClass.showContexButton !== 'function') {
138
+ FeatureClass.showContexButton = () => false;
139
+ }
137
140
  this.features.push(FeatureClass);
138
141
  }
139
142
 
@@ -531,6 +531,33 @@ export class PartHistory {
531
531
  return [];
532
532
  };
533
533
 
534
+ const extractFaceEdgePositions = (face) => {
535
+ if (!face) return [];
536
+ const out = [];
537
+ const addEdge = (edge) => {
538
+ const positions = extractEdgeWorldPositions(edge);
539
+ if (positions && positions.length >= 6) out.push(positions);
540
+ };
541
+
542
+ if (Array.isArray(face.edges) && face.edges.length) {
543
+ for (const edge of face.edges) addEdge(edge);
544
+ return out;
545
+ }
546
+
547
+ const faceName = face?.name || face?.userData?.faceName || null;
548
+ const parentSolid = face?.parentSolid || face?.userData?.parentSolid || face?.parent || null;
549
+ if (!faceName || !parentSolid || !Array.isArray(parentSolid.children)) return out;
550
+ for (const child of parentSolid.children) {
551
+ if (!child || child.type !== SelectionFilter.EDGE) continue;
552
+ const faceA = child?.userData?.faceA || null;
553
+ const faceB = child?.userData?.faceB || null;
554
+ if (faceA === faceName || faceB === faceName) {
555
+ addEdge(child);
556
+ }
557
+ }
558
+ return out;
559
+ };
560
+
534
561
  for (const key in schema) {
535
562
  if (!Object.prototype.hasOwnProperty.call(schema, key)) continue;
536
563
  const def = schema[key];
@@ -552,6 +579,14 @@ export class PartHistory {
552
579
  bucket[refName] = { type: 'EDGE', positions, sourceUuid, sourceFeatureId };
553
580
  }
554
581
  }
582
+ } else if (objType === SelectionFilter.FACE || objType === 'FACE' || objType === SelectionFilter.PLANE || objType === 'PLANE') {
583
+ const edgePositions = extractFaceEdgePositions(obj);
584
+ if (edgePositions && edgePositions.length) {
585
+ const snapType = (objType === SelectionFilter.PLANE || objType === 'PLANE') ? 'PLANE' : 'FACE';
586
+ for (const bucket of buckets) {
587
+ bucket[refName] = { type: snapType, edgePositions, sourceUuid, sourceFeatureId };
588
+ }
589
+ }
555
590
  } else if (objType === SelectionFilter.VERTEX || objType === 'VERTEX') {
556
591
  const pos = new THREE.Vector3();
557
592
  try {
@@ -23,6 +23,9 @@ export class SelectionFilter {
23
23
  static _selectionActionListenerBound = false;
24
24
  static _selectionActionsPending = false;
25
25
  static _selectionActionBar = null;
26
+ static _historyContextActions = new Map();
27
+ static _selectionActionSeparator = null;
28
+ static _contextSuppressReasons = new Set();
26
29
 
27
30
  constructor() {
28
31
  throw new Error("SelectionFilter is static and cannot be instantiated.");
@@ -194,6 +197,25 @@ export class SelectionFilter {
194
197
  let clone;
195
198
  try { clone = typeof origMat.clone === 'function' ? origMat.clone() : origMat; } catch { clone = origMat; }
196
199
  try { if (clone && clone.color && typeof clone.color.set === 'function') clone.color.set(SelectionFilter.hoverColor); } catch { }
200
+ try {
201
+ if (origMat && clone && origMat.resolution && clone.resolution && typeof clone.resolution.copy === 'function') {
202
+ clone.resolution.copy(origMat.resolution);
203
+ }
204
+ } catch { }
205
+ try {
206
+ if (origMat && clone && typeof origMat.dashed !== 'undefined' && typeof clone.dashed !== 'undefined') {
207
+ clone.dashed = origMat.dashed;
208
+ }
209
+ if (origMat && clone && typeof origMat.dashSize !== 'undefined' && typeof clone.dashSize !== 'undefined') {
210
+ clone.dashSize = origMat.dashSize;
211
+ }
212
+ if (origMat && clone && typeof origMat.gapSize !== 'undefined' && typeof clone.gapSize !== 'undefined') {
213
+ clone.gapSize = origMat.gapSize;
214
+ }
215
+ if (origMat && clone && typeof origMat.dashScale !== 'undefined' && typeof clone.dashScale !== 'undefined') {
216
+ clone.dashScale = origMat.dashScale;
217
+ }
218
+ } catch { }
197
219
  try {
198
220
  t.userData.__hoverOrigMat = origMat;
199
221
  t.userData.__hoverMatApplied = true;
@@ -681,14 +703,6 @@ export class SelectionFilter {
681
703
  }
682
704
 
683
705
  static _syncSelectionActions() {
684
- const actions = SelectionFilter._selectionActions;
685
- if (!actions || actions.size === 0) {
686
- try {
687
- const bar = SelectionFilter._selectionActionBar;
688
- if (bar) bar.style.display = 'none';
689
- } catch { }
690
- return;
691
- }
692
706
  const viewer = SelectionFilter.viewer;
693
707
  const bar = SelectionFilter._ensureSelectionActionBar(viewer);
694
708
  if (!bar) {
@@ -696,14 +710,16 @@ export class SelectionFilter {
696
710
  return;
697
711
  }
698
712
  SelectionFilter._selectionActionsPending = false;
713
+ const suppressed = SelectionFilter._contextSuppressReasons?.size > 0;
714
+ if (suppressed) {
715
+ try { bar.style.display = 'none'; } catch { }
716
+ return;
717
+ }
699
718
  const selection = SelectionFilter.getSelectedObjects();
700
719
  const hideAll = !!viewer?._sketchMode;
701
- let visibleCount = 0;
702
- const updateButton = (entry, show) => {
703
- if (!entry || !entry.btn) return;
704
- try { entry.btn.style.display = show ? '' : 'none'; } catch { }
705
- if (show) visibleCount += 1;
706
- };
720
+ const utilityButtons = [];
721
+ const actions = SelectionFilter._selectionActions;
722
+
707
723
  for (const id of SelectionFilter._selectionActionOrder) {
708
724
  const entry = actions.get(id);
709
725
  if (!entry) continue;
@@ -726,16 +742,237 @@ export class SelectionFilter {
726
742
  show = selection.length > 0;
727
743
  }
728
744
  }
729
- updateButton(entry, show);
730
- if (entry.btn.parentNode !== bar) {
731
- try { bar.appendChild(entry.btn); } catch { }
745
+ if (show) utilityButtons.push(entry.btn);
746
+ }
747
+
748
+ const historySpecs = SelectionFilter._getHistoryContextActionSpecs(selection, viewer);
749
+ const contextButtons = [];
750
+ const desiredIds = new Set();
751
+ for (const spec of historySpecs) {
752
+ if (!spec || !spec.id) continue;
753
+ desiredIds.add(spec.id);
754
+ const existing = SelectionFilter._historyContextActions.get(spec.id) || { id: spec.id };
755
+ existing.label = spec.label ?? existing.label ?? '';
756
+ existing.title = spec.title ?? existing.title ?? existing.label ?? '';
757
+ existing.onClick = spec.onClick ?? existing.onClick ?? null;
758
+ existing.shouldShow = typeof spec.shouldShow === 'function' ? spec.shouldShow : null;
759
+ if (!existing.btn) {
760
+ existing.btn = SelectionFilter._createSelectionActionButton(existing);
761
+ }
762
+ if (!existing.btn) continue;
763
+ try {
764
+ existing.btn.textContent = String(existing.label ?? '');
765
+ existing.btn.title = String(existing.title ?? existing.label ?? '');
766
+ existing.btn.__sabOnClick = existing.onClick;
767
+ const isIcon = String(existing.label || '').length <= 2;
768
+ existing.btn.classList.toggle('sab-icon', isIcon);
769
+ } catch { }
770
+ let show = !hideAll;
771
+ if (show && typeof existing.shouldShow === 'function') {
772
+ try { show = !!existing.shouldShow(selection, viewer); } catch { show = false; }
773
+ } else if (!existing.shouldShow) {
774
+ show = show && selection.length > 0;
775
+ }
776
+ if (show) contextButtons.push(existing.btn);
777
+ SelectionFilter._historyContextActions.set(spec.id, existing);
778
+ }
779
+
780
+ for (const [id, entry] of SelectionFilter._historyContextActions.entries()) {
781
+ if (desiredIds.has(id)) continue;
782
+ try { entry.btn?.remove?.(); } catch { }
783
+ SelectionFilter._historyContextActions.delete(id);
784
+ }
785
+
786
+ try { bar.textContent = ''; } catch { }
787
+ for (const btn of utilityButtons) {
788
+ try { bar.appendChild(btn); } catch { }
789
+ }
790
+ if (utilityButtons.length && contextButtons.length) {
791
+ const sep = SelectionFilter._ensureSelectionActionSeparator();
792
+ if (sep) {
793
+ try { bar.appendChild(sep); } catch { }
794
+ }
795
+ }
796
+ for (const btn of contextButtons) {
797
+ try { bar.appendChild(btn); } catch { }
798
+ }
799
+ try { bar.style.display = (utilityButtons.length + contextButtons.length) > 0 ? 'flex' : 'none'; } catch { }
800
+ }
801
+
802
+ static _getHistoryContextActionSpecs(selection, viewer) {
803
+ const out = [];
804
+ const items = Array.isArray(selection) ? selection : [];
805
+ const safeId = (prefix, key) => {
806
+ const raw = String(key || '').toLowerCase().replace(/[^a-z0-9_-]+/g, '-');
807
+ return `${prefix}-${raw || 'item'}`;
808
+ };
809
+ const addSpec = (spec) => {
810
+ if (spec && spec.id) out.push(spec);
811
+ };
812
+
813
+ const featureRegistry = viewer?.partHistory?.featureRegistry || null;
814
+ const features = Array.isArray(featureRegistry?.features) ? featureRegistry.features : [];
815
+ for (const FeatureClass of features) {
816
+ if (!FeatureClass) continue;
817
+ let result = null;
818
+ try { result = FeatureClass.showContexButton?.(items); } catch { result = null; }
819
+ if (!result) continue;
820
+ if (result && typeof result === 'object' && result.show === false) continue;
821
+ const label = (result && typeof result === 'object' && result.label) || FeatureClass.longName || FeatureClass.shortName || FeatureClass.name || 'Feature';
822
+ const typeKey = FeatureClass.shortName || FeatureClass.type || FeatureClass.name || label;
823
+ const params = SelectionFilter._extractContextParams(result);
824
+ addSpec({
825
+ id: safeId('ctx-feature', typeKey),
826
+ label,
827
+ title: `Create ${label}`,
828
+ onClick: () => SelectionFilter._createFeatureFromContext(viewer, typeKey, params),
829
+ });
830
+ }
831
+
832
+ const constraintRegistry = viewer?.partHistory?.assemblyConstraintRegistry || null;
833
+ const constraintClasses = typeof constraintRegistry?.listAvailable === 'function'
834
+ ? constraintRegistry.listAvailable()
835
+ : (typeof constraintRegistry?.list === 'function' ? constraintRegistry.list() : []);
836
+ if (Array.isArray(constraintClasses)) {
837
+ for (const ConstraintClass of constraintClasses) {
838
+ if (!ConstraintClass) continue;
839
+ let result = null;
840
+ try { result = ConstraintClass.showContexButton?.(items); } catch { result = null; }
841
+ if (!result) continue;
842
+ if (result && typeof result === 'object' && result.show === false) continue;
843
+ const label = (result && typeof result === 'object' && result.label) || ConstraintClass.longName || ConstraintClass.shortName || ConstraintClass.name || 'Constraint';
844
+ const typeKey = ConstraintClass.constraintType || ConstraintClass.shortName || ConstraintClass.name || label;
845
+ const params = SelectionFilter._extractContextParams(result);
846
+ addSpec({
847
+ id: safeId('ctx-constraint', typeKey),
848
+ label,
849
+ title: `Create ${label}`,
850
+ onClick: () => SelectionFilter._createConstraintFromContext(viewer, typeKey, params),
851
+ });
852
+ }
853
+ }
854
+
855
+ const pmimode = viewer?._pmiMode || null;
856
+ const annotationRegistry = viewer?.annotationRegistry || null;
857
+ if (pmimode && annotationRegistry && typeof annotationRegistry.list === 'function') {
858
+ const annClasses = annotationRegistry.list();
859
+ for (const AnnClass of annClasses) {
860
+ if (!AnnClass) continue;
861
+ let result = null;
862
+ try { result = AnnClass.showContexButton?.(items); } catch { result = null; }
863
+ if (!result) continue;
864
+ if (result && typeof result === 'object' && result.show === false) continue;
865
+ const label = (result && typeof result === 'object' && result.label) || AnnClass.longName || AnnClass.shortName || AnnClass.name || 'Annotation';
866
+ const typeKey = AnnClass.type || AnnClass.entityType || AnnClass.shortName || AnnClass.name || label;
867
+ const params = SelectionFilter._extractContextParams(result);
868
+ addSpec({
869
+ id: safeId('ctx-annotation', typeKey),
870
+ label,
871
+ title: `Create ${label}`,
872
+ onClick: () => SelectionFilter._createAnnotationFromContext(viewer, typeKey, params),
873
+ });
732
874
  }
733
875
  }
876
+
877
+ return out;
878
+ }
879
+
880
+ static async _createFeatureFromContext(viewer, typeKey, params = null) {
881
+ if (!viewer || !typeKey) return;
882
+ SelectionFilter.setContextBarSuppressed('context-create', true);
883
+ setTimeout(() => SelectionFilter.setContextBarSuppressed('context-create', false), 0);
884
+ let entry = null;
885
+ if (viewer.historyWidget && typeof viewer.historyWidget._handleAddEntry === 'function') {
886
+ try { entry = await viewer.historyWidget._handleAddEntry(typeKey); } catch { }
887
+ } else {
888
+ try { entry = await viewer.partHistory?.newFeature?.(typeKey); } catch { }
889
+ }
890
+ if (entry && params && typeof params === 'object') {
891
+ SelectionFilter._applyContextParamsToEntry(viewer, entry, params);
892
+ }
893
+ return entry;
894
+ }
895
+
896
+ static _createConstraintFromContext(viewer, typeKey, params = null) {
897
+ if (!viewer || !typeKey) return;
898
+ SelectionFilter.setContextBarSuppressed('context-create', true);
899
+ setTimeout(() => SelectionFilter.setContextBarSuppressed('context-create', false), 0);
900
+ try { viewer.partHistory?.assemblyConstraintHistory?.addConstraint?.(typeKey, params || null); } catch { }
901
+ }
902
+
903
+ static _createAnnotationFromContext(viewer, typeKey, params = null) {
904
+ if (!viewer || !typeKey) return;
905
+ SelectionFilter.setContextBarSuppressed('context-create', true);
906
+ setTimeout(() => SelectionFilter.setContextBarSuppressed('context-create', false), 0);
907
+ try { viewer._pmiMode?._annotationHistory?.createAnnotation?.(typeKey, params || null); } catch { }
908
+ }
909
+
910
+ static _extractContextParams(result) {
911
+ if (!result || result === true) return null;
912
+ if (typeof result !== 'object') return null;
913
+ if (result.params && typeof result.params === 'object') return result.params;
914
+ if (result.field) {
915
+ return { [result.field]: result.value };
916
+ }
917
+ return null;
918
+ }
919
+
920
+ static _applyContextParamsToEntry(viewer, entry, params = {}) {
921
+ if (!entry || !params || typeof params !== 'object') return;
734
922
  try {
735
- if (bar) bar.style.display = visibleCount > 0 ? 'flex' : 'none';
923
+ for (const [key, value] of Object.entries(params)) {
924
+ entry.inputParams = entry.inputParams || {};
925
+ entry.inputParams[key] = value;
926
+ }
927
+ } catch { }
928
+ const historyWidget = viewer?.historyWidget || null;
929
+ if (!historyWidget || typeof historyWidget._handleSchemaChange !== 'function') return;
930
+ try {
931
+ const id = entry?.inputParams?.id ?? entry?.id ?? null;
932
+ const entryId = String(id ?? '');
933
+ historyWidget._handleSchemaChange(entryId, entry, { key: '__context', value: params });
934
+ const refresh = () => {
935
+ try {
936
+ const form = historyWidget.getFormForEntry?.(entryId);
937
+ if (form && typeof form.refreshFromParams === 'function') {
938
+ form.refreshFromParams();
939
+ return true;
940
+ }
941
+ } catch { }
942
+ return false;
943
+ };
944
+ if (!refresh()) {
945
+ setTimeout(() => { try { refresh(); } catch { } }, 0);
946
+ }
736
947
  } catch { }
737
948
  }
738
949
 
950
+ static setContextBarSuppressed(key, active) {
951
+ if (!key) return;
952
+ const reasons = SelectionFilter._contextSuppressReasons || new Set();
953
+ SelectionFilter._contextSuppressReasons = reasons;
954
+ const had = reasons.has(key);
955
+ if (active) {
956
+ reasons.add(key);
957
+ } else {
958
+ reasons.delete(key);
959
+ }
960
+ if (had !== reasons.has(key)) {
961
+ SelectionFilter._syncSelectionActions();
962
+ }
963
+ }
964
+
965
+ static _ensureSelectionActionSeparator() {
966
+ if (SelectionFilter._selectionActionSeparator && SelectionFilter._selectionActionSeparator.isConnected) {
967
+ return SelectionFilter._selectionActionSeparator;
968
+ }
969
+ if (typeof document === 'undefined') return null;
970
+ const el = document.createElement('div');
971
+ el.className = 'selection-action-sep';
972
+ SelectionFilter._selectionActionSeparator = el;
973
+ return el;
974
+ }
975
+
739
976
  static _ensureSelectionActionBar(viewer) {
740
977
  if (SelectionFilter._selectionActionBar && SelectionFilter._selectionActionBar.isConnected) {
741
978
  return SelectionFilter._selectionActionBar;
@@ -783,6 +1020,13 @@ export class SelectionFilter {
783
1020
  font-size: 16px;
784
1021
  min-width: 36px;
785
1022
  }
1023
+ .selection-action-bar .selection-action-sep {
1024
+ height: 1px;
1025
+ width: 100%;
1026
+ background: #2c3443;
1027
+ opacity: 0.9;
1028
+ margin: 4px 0;
1029
+ }
786
1030
  `;
787
1031
  document.head.appendChild(style);
788
1032
  }