brep-io-kernel 1.0.17 → 1.0.18

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.
@@ -2,11 +2,17 @@
2
2
 
3
3
  import { SelectionFilter } from './SelectionFilter.js';
4
4
  import * as THREE from 'three';
5
+ import { Line2, LineGeometry, LineMaterial } from 'three/examples/jsm/Addons.js';
5
6
  // Use hybrid translate+rotate gizmo used by the Viewer
6
7
  import { CombinedTransformControls } from './controls/CombinedTransformControls.js';
7
8
  import { getWidgetRenderer } from './featureDialogWidgets/index.js';
8
9
  import { normalizeReferenceList, normalizeReferenceName } from './featureDialogWidgets/utils.js';
9
-
10
+ const REF_PREVIEW_COLORS = {
11
+ EDGE: '#ff00ff',
12
+ FACE: '#ffc400',
13
+ PLANE: '#2eff2e',
14
+ VERTEX: '#00ffff',
15
+ };
10
16
 
11
17
 
12
18
 
@@ -29,6 +35,7 @@ export class SchemaForm {
29
35
  if (prev && prev !== el) {
30
36
  try { prev.style.filter = 'none'; } catch (_) { }
31
37
  try { prev.removeAttribute('active-reference-selection'); } catch (_) { }
38
+ try { if (typeof prev.__refPreviewCleanup === 'function') prev.__refPreviewCleanup(); } catch (_) { }
32
39
  }
33
40
  } catch (_) { }
34
41
  SchemaForm.__activeRefInput = el || null;
@@ -494,6 +501,502 @@ export class SchemaForm {
494
501
  || null;
495
502
  }
496
503
 
504
+ _getReferencePreviewCacheRoot() {
505
+ const holder = this.options?.featureRef || this;
506
+ if (!holder) return null;
507
+ if (!holder.__refPreviewCache) {
508
+ try {
509
+ Object.defineProperty(holder, '__refPreviewCache', {
510
+ value: new Map(),
511
+ configurable: true,
512
+ enumerable: false,
513
+ writable: true,
514
+ });
515
+ } catch (_) {
516
+ holder.__refPreviewCache = new Map();
517
+ }
518
+ }
519
+ return holder.__refPreviewCache;
520
+ }
521
+
522
+ _getReferencePreviewCache(inputEl) {
523
+ const root = this._getReferencePreviewCacheRoot();
524
+ if (!root) return null;
525
+ const key = (inputEl?.dataset?.key || inputEl?.dataset?.refKey || inputEl?.__refPreviewKey || '__default');
526
+ let bucket = root.get(key);
527
+ if (!bucket) {
528
+ bucket = new Map();
529
+ root.set(key, bucket);
530
+ }
531
+ return bucket;
532
+ }
533
+
534
+ _getReferencePreviewPersistentBucket(inputEl) {
535
+ const entry = this.options?.featureRef || null;
536
+ if (!entry) return null;
537
+ if (!entry.persistentData || typeof entry.persistentData !== 'object') {
538
+ entry.persistentData = {};
539
+ }
540
+ if (!entry.persistentData.__refPreviewSnapshots || typeof entry.persistentData.__refPreviewSnapshots !== 'object') {
541
+ entry.persistentData.__refPreviewSnapshots = {};
542
+ }
543
+ const key = (inputEl?.dataset?.key || inputEl?.dataset?.refKey || inputEl?.__refPreviewKey || '__default');
544
+ if (!entry.persistentData.__refPreviewSnapshots[key] || typeof entry.persistentData.__refPreviewSnapshots[key] !== 'object') {
545
+ entry.persistentData.__refPreviewSnapshots[key] = {};
546
+ }
547
+ return entry.persistentData.__refPreviewSnapshots[key];
548
+ }
549
+
550
+ _resolveReferencePreviewName(obj) {
551
+ if (!obj) return null;
552
+ const raw = obj.name != null ? String(obj.name).trim() : '';
553
+ if (raw) return raw;
554
+ const type = obj.type || 'OBJECT';
555
+ const pos = obj.position || {};
556
+ const x = Number.isFinite(pos.x) ? pos.x : 0;
557
+ const y = Number.isFinite(pos.y) ? pos.y : 0;
558
+ const z = Number.isFinite(pos.z) ? pos.z : 0;
559
+ return `${type}(${x},${y},${z})`;
560
+ }
561
+
562
+ _extractEdgeWorldPositions(obj) {
563
+ if (!obj) return [];
564
+ try {
565
+ if (typeof obj.points === 'function') {
566
+ const pts = obj.points(true);
567
+ if (Array.isArray(pts) && pts.length) {
568
+ const flat = [];
569
+ for (const p of pts) {
570
+ if (!p) continue;
571
+ const x = Number(p.x);
572
+ const y = Number(p.y);
573
+ const z = Number(p.z);
574
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) continue;
575
+ flat.push(x, y, z);
576
+ }
577
+ if (flat.length >= 6) return flat;
578
+ }
579
+ }
580
+ } catch (_) { /* ignore */ }
581
+
582
+ try {
583
+ const geom = obj.geometry;
584
+ const pos = geom && typeof geom.getAttribute === 'function' ? geom.getAttribute('position') : null;
585
+ if (!pos || pos.itemSize !== 3 || pos.count < 2) return [];
586
+ const tmp = new THREE.Vector3();
587
+ const flat = [];
588
+ for (let i = 0; i < pos.count; i++) {
589
+ tmp.set(pos.getX(i), pos.getY(i), pos.getZ(i));
590
+ tmp.applyMatrix4(obj.matrixWorld);
591
+ flat.push(tmp.x, tmp.y, tmp.z);
592
+ }
593
+ return flat.length >= 6 ? flat : [];
594
+ } catch (_) { /* ignore */ }
595
+ return [];
596
+ }
597
+
598
+ _syncPreviewLineResolution(mat) {
599
+ if (!mat || !mat.resolution || typeof mat.resolution.set !== 'function') return;
600
+ let width = 0;
601
+ let height = 0;
602
+ try {
603
+ const viewer = this.options?.viewer || null;
604
+ const el = viewer?.renderer?.domElement || null;
605
+ if (el && typeof el.getBoundingClientRect === 'function') {
606
+ const rect = el.getBoundingClientRect();
607
+ width = rect.width || rect.right - rect.left;
608
+ height = rect.height || rect.bottom - rect.top;
609
+ }
610
+ if ((!width || !height) && viewer?.container) {
611
+ width = viewer.container.clientWidth || width;
612
+ height = viewer.container.clientHeight || height;
613
+ }
614
+ } catch (_) { /* ignore */ }
615
+ if (!width || !height) {
616
+ try {
617
+ if (typeof window !== 'undefined') {
618
+ width = window.innerWidth || width;
619
+ height = window.innerHeight || height;
620
+ }
621
+ } catch (_) { /* ignore */ }
622
+ }
623
+ if (Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0) {
624
+ try { mat.resolution.set(width, height); } catch (_) { }
625
+ }
626
+ }
627
+
628
+ _createPreviewLineMaterial(sourceMat, colorHex = REF_PREVIEW_COLORS.EDGE) {
629
+ let mat = null;
630
+ if (sourceMat && typeof sourceMat.clone === 'function') {
631
+ try { mat = sourceMat.clone(); } catch (_) { mat = null; }
632
+ }
633
+ if (!mat) {
634
+ try {
635
+ mat = new LineMaterial({ color: colorHex, linewidth: 3, transparent: true, opacity: 0.95, worldUnits: false });
636
+ } catch (_) { mat = null; }
637
+ }
638
+ if (mat && mat.color && typeof mat.color.set === 'function') {
639
+ try { mat.color.set(colorHex); } catch (_) { }
640
+ }
641
+ if (mat) {
642
+ try { mat.transparent = true; } catch (_) { }
643
+ try { mat.opacity = Number.isFinite(mat.opacity) ? Math.min(0.95, mat.opacity) : 0.95; } catch (_) { }
644
+ try { mat.depthTest = false; } catch (_) { }
645
+ try { mat.depthWrite = false; } catch (_) { }
646
+ try { this._syncPreviewLineResolution(mat); } catch (_) { }
647
+ }
648
+ return mat;
649
+ }
650
+
651
+ _createPreviewMeshMaterial(sourceMat, colorHex = REF_PREVIEW_COLORS.FACE) {
652
+ let mat = null;
653
+ if (sourceMat && typeof sourceMat.clone === 'function') {
654
+ try { mat = sourceMat.clone(); } catch (_) { mat = null; }
655
+ }
656
+ if (!mat) {
657
+ try {
658
+ mat = new THREE.MeshStandardMaterial({
659
+ color: colorHex,
660
+ transparent: true,
661
+ opacity: 0.25,
662
+ depthWrite: false,
663
+ side: THREE.DoubleSide,
664
+ });
665
+ } catch (_) { mat = null; }
666
+ }
667
+ if (mat && mat.color && typeof mat.color.set === 'function') {
668
+ try { mat.color.set(colorHex); } catch (_) { }
669
+ }
670
+ if (mat) {
671
+ try { mat.transparent = true; } catch (_) { }
672
+ try { mat.opacity = 0.25; } catch (_) { }
673
+ try { mat.depthWrite = false; } catch (_) { }
674
+ try { mat.depthTest = true; } catch (_) { }
675
+ try { mat.side = THREE.DoubleSide; } catch (_) { }
676
+ try { mat.polygonOffset = true; mat.polygonOffsetFactor = -1; mat.polygonOffsetUnits = -1; } catch (_) { }
677
+ }
678
+ return mat;
679
+ }
680
+
681
+ _createPreviewPointMaterial(sourceMat, colorHex = REF_PREVIEW_COLORS.VERTEX) {
682
+ let mat = null;
683
+ if (sourceMat && typeof sourceMat.clone === 'function') {
684
+ try { mat = sourceMat.clone(); } catch (_) { mat = null; }
685
+ }
686
+ if (!mat || !mat.isPointsMaterial) {
687
+ try {
688
+ mat = new THREE.PointsMaterial({
689
+ color: colorHex,
690
+ size: 7,
691
+ sizeAttenuation: false,
692
+ transparent: true,
693
+ opacity: 0.9,
694
+ });
695
+ } catch (_) { mat = null; }
696
+ }
697
+ if (mat && mat.color && typeof mat.color.set === 'function') {
698
+ try { mat.color.set(colorHex); } catch (_) { }
699
+ }
700
+ if (mat) {
701
+ try { mat.transparent = true; } catch (_) { }
702
+ try { mat.opacity = 0.9; } catch (_) { }
703
+ }
704
+ return mat;
705
+ }
706
+
707
+ _configurePreviewObject(obj, refName, previewType) {
708
+ if (!obj) return obj;
709
+ try { obj.name = `__refPreview__${refName}`; } catch (_) { }
710
+ try {
711
+ obj.userData = obj.userData || {};
712
+ obj.userData.refPreview = true;
713
+ obj.userData.refName = refName;
714
+ obj.userData.previewType = previewType;
715
+ obj.userData.excludeFromFit = true;
716
+ } catch (_) { }
717
+ try { obj.renderOrder = Math.max(10050, obj.renderOrder || 0); } catch (_) { }
718
+ try { obj.raycast = () => { }; } catch (_) { }
719
+ try {
720
+ obj.traverse?.((child) => {
721
+ if (!child || child === obj) return;
722
+ try { child.raycast = () => { }; } catch (_) { }
723
+ try {
724
+ child.userData = child.userData || {};
725
+ child.userData.refPreview = true;
726
+ child.userData.refName = refName;
727
+ } catch (_) { }
728
+ });
729
+ } catch (_) { }
730
+ return obj;
731
+ }
732
+
733
+ _getOwningFeatureIdForObject(obj) {
734
+ let cur = obj;
735
+ let guard = 0;
736
+ while (cur && guard < 8) {
737
+ if (cur.owningFeatureID != null) return cur.owningFeatureID;
738
+ cur = cur.parent || null;
739
+ guard += 1;
740
+ }
741
+ return null;
742
+ }
743
+
744
+ _buildReferencePreviewObject(obj, refName) {
745
+ if (!obj) return null;
746
+ const type = String(obj.type || '').toUpperCase();
747
+ if (type === SelectionFilter.EDGE || type === 'EDGE') {
748
+ const positions = this._extractEdgeWorldPositions(obj);
749
+ if (!positions || positions.length < 6) return null;
750
+ const geom = new LineGeometry();
751
+ geom.setPositions(positions);
752
+ try { geom.computeBoundingSphere(); } catch (_) { }
753
+ const mat = this._createPreviewLineMaterial(obj.material, REF_PREVIEW_COLORS.EDGE);
754
+ const line = new Line2(geom, mat || undefined);
755
+ try { line.computeLineDistances?.(); } catch (_) { }
756
+ line.type = 'REF_PREVIEW_EDGE';
757
+ return this._configurePreviewObject(line, refName, 'EDGE');
758
+ }
759
+ if (type === SelectionFilter.FACE || type === SelectionFilter.PLANE || type === 'FACE' || type === 'PLANE') {
760
+ const geom = obj.geometry && typeof obj.geometry.clone === 'function' ? obj.geometry.clone() : null;
761
+ if (!geom) return null;
762
+ try { geom.applyMatrix4(obj.matrixWorld); } catch (_) { }
763
+ const color = (type === SelectionFilter.PLANE || type === 'PLANE') ? REF_PREVIEW_COLORS.PLANE : REF_PREVIEW_COLORS.FACE;
764
+ const mat = this._createPreviewMeshMaterial(obj.material, color);
765
+ const mesh = new THREE.Mesh(geom, mat || undefined);
766
+ mesh.type = (type === SelectionFilter.PLANE || type === 'PLANE') ? 'REF_PREVIEW_PLANE' : 'REF_PREVIEW_FACE';
767
+ try { mesh.matrixAutoUpdate = false; } catch (_) { }
768
+ return this._configurePreviewObject(mesh, refName, mesh.type);
769
+ }
770
+ if (type === SelectionFilter.VERTEX || type === 'VERTEX') {
771
+ const pos = new THREE.Vector3();
772
+ try {
773
+ if (typeof obj.getWorldPosition === 'function') obj.getWorldPosition(pos);
774
+ else pos.set(obj.position?.x || 0, obj.position?.y || 0, obj.position?.z || 0);
775
+ } catch (_) { }
776
+ const geom = new THREE.BufferGeometry();
777
+ geom.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0], 3));
778
+ const mat = this._createPreviewPointMaterial(obj.material, REF_PREVIEW_COLORS.VERTEX);
779
+ const pts = new THREE.Points(geom, mat || undefined);
780
+ pts.position.copy(pos);
781
+ pts.type = 'REF_PREVIEW_VERTEX';
782
+ return this._configurePreviewObject(pts, refName, 'VERTEX');
783
+ }
784
+ return null;
785
+ }
786
+
787
+ _buildReferencePreviewFromSnapshot(refName, snapshot) {
788
+ if (!snapshot || !refName) return null;
789
+ const type = String(snapshot.type || '').toUpperCase();
790
+ if (type === 'EDGE') {
791
+ const positions = Array.isArray(snapshot.positions) ? snapshot.positions : null;
792
+ if (!positions || positions.length < 6) return null;
793
+ const geom = new LineGeometry();
794
+ geom.setPositions(positions);
795
+ try { geom.computeBoundingSphere(); } catch (_) { }
796
+ const mat = this._createPreviewLineMaterial(null, REF_PREVIEW_COLORS.EDGE);
797
+ const line = new Line2(geom, mat || undefined);
798
+ try { line.computeLineDistances?.(); } catch (_) { }
799
+ line.type = 'REF_PREVIEW_EDGE';
800
+ return this._configurePreviewObject(line, refName, 'EDGE');
801
+ }
802
+ if (type === 'VERTEX') {
803
+ const pos = snapshot.position;
804
+ if (!Array.isArray(pos) || pos.length < 3) return null;
805
+ const geom = new THREE.BufferGeometry();
806
+ geom.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0], 3));
807
+ const mat = this._createPreviewPointMaterial(null, REF_PREVIEW_COLORS.VERTEX);
808
+ const pts = new THREE.Points(geom, mat || undefined);
809
+ pts.position.set(Number(pos[0]) || 0, Number(pos[1]) || 0, Number(pos[2]) || 0);
810
+ pts.type = 'REF_PREVIEW_VERTEX';
811
+ return this._configurePreviewObject(pts, refName, 'VERTEX');
812
+ }
813
+ return null;
814
+ }
815
+
816
+ _ensureReferencePreviewGroup(inputEl) {
817
+ const scene = this._getReferenceSelectionScene();
818
+ if (!scene || !inputEl) return null;
819
+ const existing = inputEl.__refPreviewGroup;
820
+ if (existing && existing.isObject3D) {
821
+ if (existing.parent !== scene) {
822
+ try { scene.add(existing); } catch (_) { }
823
+ }
824
+ return existing;
825
+ }
826
+ const group = new THREE.Group();
827
+ try { group.name = `__REF_PREVIEW_GROUP__${inputEl?.dataset?.key || ''}`; } catch (_) { }
828
+ try {
829
+ group.userData = group.userData || {};
830
+ group.userData.preventRemove = true;
831
+ group.userData.excludeFromFit = true;
832
+ group.userData.refPreview = true;
833
+ } catch (_) { }
834
+ try { group.renderOrder = 10040; } catch (_) { }
835
+ try { group.raycast = () => { }; } catch (_) { }
836
+ inputEl.__refPreviewGroup = group;
837
+ try { scene.add(group); } catch (_) { }
838
+ return group;
839
+ }
840
+
841
+ _removeReferencePreviewGroup(inputEl) {
842
+ const group = inputEl?.__refPreviewGroup;
843
+ if (!group || !group.isObject3D) return;
844
+ try { if (group.userData) group.userData.preventRemove = false; } catch (_) { }
845
+ try { if (group.parent) group.parent.remove(group); } catch (_) { }
846
+ }
847
+
848
+ _syncActiveReferenceSelectionPreview(inputEl, def = null) {
849
+ try {
850
+ const active = SchemaForm.__activeRefInput;
851
+ if (!active || active !== inputEl) return;
852
+ const scene = this._getReferenceSelectionScene();
853
+ if (!scene) return;
854
+ const names = this._collectReferenceSelectionNames(inputEl, def);
855
+ if (!names.length) {
856
+ this._removeReferencePreviewGroup(inputEl);
857
+ return;
858
+ }
859
+ const cache = this._getReferencePreviewCache(inputEl);
860
+ if (!cache || cache.size === 0) return;
861
+ const group = this._ensureReferencePreviewGroup(inputEl);
862
+ const wanted = new Set(names);
863
+ if (group && Array.isArray(group.children)) {
864
+ for (const child of group.children.slice()) {
865
+ const refName = child?.userData?.refName;
866
+ if (!refName || !wanted.has(refName)) {
867
+ try { group.remove(child); } catch (_) { }
868
+ }
869
+ }
870
+ }
871
+ for (const name of names) {
872
+ const entry = cache.get(name);
873
+ let ghost = entry && entry.object ? entry.object : entry;
874
+ if (!ghost) continue;
875
+ const sourceUuid = entry?.sourceUuid || null;
876
+ let originalPresent = false;
877
+ if (sourceUuid && typeof scene.getObjectByProperty === 'function') {
878
+ try { originalPresent = !!scene.getObjectByProperty('uuid', sourceUuid); } catch (_) { originalPresent = false; }
879
+ } else {
880
+ const real = scene.getObjectByName(name);
881
+ originalPresent = !!real;
882
+ }
883
+ if (originalPresent) {
884
+ if (ghost.parent === group) {
885
+ try { group.remove(ghost); } catch (_) { }
886
+ }
887
+ continue;
888
+ }
889
+ if (ghost.parent !== group) {
890
+ try { group.add(ghost); } catch (_) { }
891
+ }
892
+ try { ghost.visible = true; } catch (_) { }
893
+ }
894
+ } catch (_) { }
895
+ }
896
+
897
+ _seedReferencePreviewCacheFromScene(inputEl, def = null, names = null, sceneOverride = null) {
898
+ if (!inputEl) return;
899
+ const scene = sceneOverride || this._getReferenceSelectionScene();
900
+ if (!scene) return;
901
+ const list = Array.isArray(names) ? names : this._collectReferenceSelectionNames(inputEl, def);
902
+ if (!list.length) return;
903
+ const cache = this._getReferencePreviewCache(inputEl);
904
+ if (!cache) return;
905
+ const store = this._getReferencePreviewPersistentBucket(inputEl);
906
+ for (const name of list) {
907
+ if (!name || cache.has(name)) continue;
908
+ const obj = scene.getObjectByName(name);
909
+ if (obj && !obj?.userData?.refPreview) {
910
+ this._storeReferencePreviewSnapshot(inputEl, def, obj);
911
+ continue;
912
+ }
913
+ if (store && store[name]) {
914
+ const snapshot = store[name];
915
+ const ghost = this._buildReferencePreviewFromSnapshot(name, snapshot);
916
+ if (ghost) {
917
+ cache.set(name, {
918
+ object: ghost,
919
+ type: snapshot.type || null,
920
+ sourceUuid: snapshot.sourceUuid || null,
921
+ sourceFeatureId: snapshot.sourceFeatureId || null,
922
+ });
923
+ }
924
+ }
925
+ }
926
+ }
927
+
928
+ _storeReferencePreviewSnapshot(inputEl, def, obj) {
929
+ try {
930
+ if (!inputEl || !obj) return;
931
+ const refName = this._resolveReferencePreviewName(obj);
932
+ if (!refName) return;
933
+ const cache = this._getReferencePreviewCache(inputEl);
934
+ if (!cache) return;
935
+ const ghost = this._buildReferencePreviewObject(obj, refName);
936
+ if (!ghost) return;
937
+ const sourceUuid = obj.uuid || null;
938
+ const sourceFeatureId = this._getOwningFeatureIdForObject(obj);
939
+ cache.set(refName, {
940
+ object: ghost,
941
+ type: obj.type || null,
942
+ sourceUuid,
943
+ sourceFeatureId,
944
+ });
945
+ try {
946
+ const store = this._getReferencePreviewPersistentBucket(inputEl);
947
+ if (store) {
948
+ const objType = String(obj.type || '').toUpperCase();
949
+ if (objType === SelectionFilter.EDGE || objType === 'EDGE') {
950
+ const positions = this._extractEdgeWorldPositions(obj);
951
+ if (positions && positions.length >= 6) {
952
+ store[refName] = { type: 'EDGE', positions, sourceUuid, sourceFeatureId };
953
+ }
954
+ } else if (objType === SelectionFilter.VERTEX || objType === 'VERTEX') {
955
+ const pos = new THREE.Vector3();
956
+ try {
957
+ if (typeof obj.getWorldPosition === 'function') obj.getWorldPosition(pos);
958
+ else pos.set(obj.position?.x || 0, obj.position?.y || 0, obj.position?.z || 0);
959
+ } catch (_) { }
960
+ store[refName] = { type: 'VERTEX', position: [pos.x, pos.y, pos.z], sourceUuid, sourceFeatureId };
961
+ }
962
+ }
963
+ } catch (_) { }
964
+ if (SchemaForm.__activeRefInput === inputEl) {
965
+ try { this._syncActiveReferenceSelectionPreview(inputEl, def); } catch (_) { }
966
+ }
967
+ } catch (_) { }
968
+ }
969
+
970
+ _startReferencePreviewWatcher(inputEl, def) {
971
+ if (!inputEl) return;
972
+ if (this._refPreviewWatcher && this._refPreviewWatcher.inputEl === inputEl) return;
973
+ this._stopReferencePreviewWatcher();
974
+ const tick = () => {
975
+ if (!this._refPreviewWatcher || this._refPreviewWatcher.inputEl !== inputEl) return;
976
+ if (SchemaForm.__activeRefInput !== inputEl) {
977
+ this._stopReferencePreviewWatcher();
978
+ return;
979
+ }
980
+ try { this._syncActiveReferenceSelectionPreview(inputEl, def); } catch (_) { }
981
+ this._refPreviewWatcher.timer = setTimeout(tick, 300);
982
+ };
983
+ this._refPreviewWatcher = { inputEl, timer: null };
984
+ inputEl.__refPreviewCleanup = () => {
985
+ try { this._stopReferencePreviewWatcher(); } catch (_) { }
986
+ try { this._removeReferencePreviewGroup(inputEl); } catch (_) { }
987
+ };
988
+ tick();
989
+ }
990
+
991
+ _stopReferencePreviewWatcher() {
992
+ if (!this._refPreviewWatcher) return;
993
+ const timer = this._refPreviewWatcher.timer;
994
+ if (timer) {
995
+ clearTimeout(timer);
996
+ }
997
+ this._refPreviewWatcher = null;
998
+ }
999
+
497
1000
  _collectReferenceSelectionNames(inputEl, def = null) {
498
1001
  if (!inputEl) return [];
499
1002
  const isMulti = Boolean(def && def.multiple) || (inputEl.dataset && inputEl.dataset.multiple === 'true');
@@ -530,11 +1033,48 @@ export class SchemaForm {
530
1033
  const scene = this._getReferenceSelectionScene();
531
1034
  if (!scene) return;
532
1035
  const names = this._collectReferenceSelectionNames(inputEl, def);
1036
+ try { this._seedReferencePreviewCacheFromScene(inputEl, def, names, scene); } catch (_) { }
533
1037
  SelectionFilter.unselectAll(scene);
534
1038
  for (const name of names) {
535
1039
  if (!name) continue;
536
1040
  try { SelectionFilter.selectItem(scene, name); } catch (_) { }
537
1041
  }
1042
+ try { this._syncActiveReferenceSelectionPreview(inputEl, def); } catch (_) { }
1043
+ } catch (_) { }
1044
+ }
1045
+
1046
+ _hoverReferenceSelectionItem(inputEl, def, name) {
1047
+ try {
1048
+ if (!inputEl || SchemaForm.__activeRefInput !== inputEl) return;
1049
+ const normalized = normalizeReferenceName(name);
1050
+ if (!normalized) return;
1051
+ const scene = this._getReferenceSelectionScene();
1052
+ if (!scene) return;
1053
+ try { this._seedReferencePreviewCacheFromScene(inputEl, def, [normalized], scene); } catch (_) { }
1054
+ try { this._syncActiveReferenceSelectionPreview(inputEl, def); } catch (_) { }
1055
+
1056
+ let target = null;
1057
+ try { target = scene.getObjectByName(normalized); } catch (_) { target = null; }
1058
+ if (!target) {
1059
+ const cache = this._getReferencePreviewCache(inputEl);
1060
+ const entry = cache ? cache.get(normalized) : null;
1061
+ target = entry?.object || entry || null;
1062
+ }
1063
+ if (!target) {
1064
+ try { target = scene.getObjectByName(`__refPreview__${normalized}`); } catch (_) { target = null; }
1065
+ }
1066
+ if (!target) return;
1067
+ inputEl.__refChipHoverActive = true;
1068
+ try { SelectionFilter.setHoverObject(target, { ignoreFilter: true }); } catch (_) { }
1069
+ } catch (_) { }
1070
+ }
1071
+
1072
+ _clearReferenceSelectionHover(inputEl) {
1073
+ try {
1074
+ if (!inputEl || SchemaForm.__activeRefInput !== inputEl) return;
1075
+ if (!inputEl.__refChipHoverActive) return;
1076
+ inputEl.__refChipHoverActive = false;
1077
+ SelectionFilter.clearHover();
538
1078
  } catch (_) { }
539
1079
  }
540
1080
 
@@ -581,8 +1121,37 @@ export class SchemaForm {
581
1121
  SelectionFilter.SetSelectionTypes(def.selectionFilter);
582
1122
  try { window.__BREP_activeRefInput = inputEl; } catch (_) { }
583
1123
 
1124
+ // Log current selected objects for this reference field on activation
1125
+ try {
1126
+ const scene = this._getReferenceSelectionScene();
1127
+ const names = this._collectReferenceSelectionNames(inputEl, def);
1128
+ const keyLabel = inputEl?.dataset?.key ? ` (${inputEl.dataset.key})` : '';
1129
+ if (!names.length) {
1130
+ console.log(`[ReferenceSelection] Activated${keyLabel}: no selections`);
1131
+ } else {
1132
+ const cache = this._getReferencePreviewCache(inputEl);
1133
+ for (const name of names) {
1134
+ if (!name) continue;
1135
+ const obj = scene ? scene.getObjectByName(name) : null;
1136
+ const cached = cache ? cache.get(name) : null;
1137
+ const cachedObj = cached && cached.object ? cached.object : null;
1138
+ console.log(`[ReferenceSelection] Selected${keyLabel}: ${name}`, {
1139
+ object: obj || cachedObj || null,
1140
+ inScene: !!obj,
1141
+ cached: !!cachedObj,
1142
+ });
1143
+ }
1144
+ }
1145
+ } catch (_) { }
1146
+
584
1147
  // Highlight existing selections while this reference field is active
585
1148
  try { this._syncActiveReferenceSelectionHighlight(inputEl, def); } catch (_) { }
1149
+ try {
1150
+ if (typeof inputEl.__captureReferencePreview !== 'function') {
1151
+ inputEl.__captureReferencePreview = (obj) => this._storeReferencePreviewSnapshot(inputEl, def, obj);
1152
+ }
1153
+ } catch (_) { }
1154
+ try { this._startReferencePreviewWatcher(inputEl, def); } catch (_) { }
586
1155
  }
587
1156
 
588
1157
  // Activate a TransformControls session for a transform widget
@@ -1044,17 +1613,25 @@ export class SchemaForm {
1044
1613
 
1045
1614
  _stopActiveReferenceSelection() {
1046
1615
  // Clear global active if it belongs to this instance
1616
+ const activeInput = SchemaForm.__activeRefInput || null;
1047
1617
  try {
1048
- if (SchemaForm.__activeRefInput) {
1049
- try { SchemaForm.__activeRefInput.style.filter = 'none'; } catch (_) { }
1050
- try { SchemaForm.__activeRefInput.removeAttribute('active-reference-selection'); } catch (_) { }
1618
+ if (activeInput) {
1619
+ try { activeInput.style.filter = 'none'; } catch (_) { }
1620
+ try { activeInput.removeAttribute('active-reference-selection'); } catch (_) { }
1051
1621
  try {
1052
- const wrap = SchemaForm.__activeRefInput.closest('.ref-single-wrap, .ref-multi-wrap');
1622
+ const wrap = activeInput.closest('.ref-single-wrap, .ref-multi-wrap');
1053
1623
  if (wrap) wrap.classList.remove('ref-active');
1054
1624
  } catch (_) { }
1055
1625
  }
1056
1626
  } catch (_) { }
1057
- const hadActive = !!SchemaForm.__activeRefInput;
1627
+ const hadActive = !!activeInput;
1628
+ try {
1629
+ if (activeInput && typeof activeInput.__refPreviewCleanup === 'function') {
1630
+ activeInput.__refPreviewCleanup();
1631
+ } else if (activeInput) {
1632
+ this._removeReferencePreviewGroup(activeInput);
1633
+ }
1634
+ } catch (_) { }
1058
1635
  SchemaForm.__activeRefInput = null;
1059
1636
  try { if (window.__BREP_activeRefInput === undefined || window.__BREP_activeRefInput === SchemaForm.__activeRefInput) window.__BREP_activeRefInput = null; } catch (_) { }
1060
1637
  if (hadActive) {
@@ -1071,6 +1648,7 @@ export class SchemaForm {
1071
1648
  const arr = Array.isArray(values) ? values : [];
1072
1649
  const normalizedValues = normalizeReferenceList(arr);
1073
1650
  const inputEl = (this._inputs && typeof this._inputs.get === 'function') ? this._inputs.get(key) : null;
1651
+ const def = (inputEl && inputEl.__refSelectionDef) || (this.schema ? (this.schema[key] || null) : null);
1074
1652
  if (inputEl) {
1075
1653
  if (typeof inputEl.__updateSelectionMetadata === 'function') {
1076
1654
  try { inputEl.__updateSelectionMetadata(normalizedValues); } catch (_) { }
@@ -1095,9 +1673,17 @@ export class SchemaForm {
1095
1673
 
1096
1674
  // Hover highlight on chip hover
1097
1675
  chip.addEventListener('mouseenter', () => {
1676
+ if (def && def.type === 'reference_selection') {
1677
+ this._hoverReferenceSelectionItem(inputEl, def, name);
1678
+ return;
1679
+ }
1098
1680
  try { SelectionFilter.setHoverByName(this.options?.scene || null, name); } catch (_) { }
1099
1681
  });
1100
1682
  chip.addEventListener('mouseleave', () => {
1683
+ if (def && def.type === 'reference_selection') {
1684
+ this._clearReferenceSelectionHover(inputEl);
1685
+ return;
1686
+ }
1101
1687
  try { SelectionFilter.clearHover(); } catch (_) { }
1102
1688
  });
1103
1689
 
@@ -1158,7 +1744,7 @@ export class SchemaForm {
1158
1744
 
1159
1745
  try {
1160
1746
  if (inputEl && inputEl === SchemaForm.__activeRefInput) {
1161
- const def = this.schema ? (this.schema[key] || {}) : null;
1747
+ const def = (inputEl && inputEl.__refSelectionDef) || (this.schema ? (this.schema[key] || {}) : null);
1162
1748
  this._syncActiveReferenceSelectionHighlight(inputEl, def);
1163
1749
  }
1164
1750
  } catch (_) { }
@@ -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;