diff --git a/ssbo_component/ssbo_editor.py b/ssbo_component/ssbo_editor.py index d440117e..fa9c6b75 100644 --- a/ssbo_component/ssbo_editor.py +++ b/ssbo_component/ssbo_editor.py @@ -78,6 +78,11 @@ class SSBOEditor: self.pick_mask = BitMask32.bit(29) self.pick_buffer = None self._empty_pick_scene = NodePath("ssbo_pick_empty") + # Avoid heavy per-frame sync for huge group selections. + self._pick_sync_bg_limit = 256 + self._last_group_sync_mat = None + self._last_single_sync_gid = None + self._last_single_sync_mat = None # Initialize ImGui Backend if not already present if not hasattr(self.base, 'imgui_backend'): @@ -389,6 +394,11 @@ class SSBOEditor: self._sync_pick_transforms() return task.cont + def _reset_pick_sync_cache(self): + self._last_group_sync_mat = None + self._last_single_sync_gid = None + self._last_single_sync_mat = None + def _matrices_close(self, a, b, eps=1e-5): """Small helper for robust matrix change detection.""" for r in range(4): @@ -424,6 +434,45 @@ class SSBOEditor: if not self.controller: return self._sync_pick_root_transform() + if not self.selected_ids: + return + + # Group selection can contain thousands of objects. + # Only resync when proxy transform has changed. + proxy = getattr(self, "_group_proxy", None) + if proxy and not proxy.is_empty() and len(self.selected_ids) > 1: + proxy_world = proxy.get_mat(self.base.render) + if self._last_group_sync_mat and self._matrices_close(proxy_world, self._last_group_sync_mat): + return + self._last_group_sync_mat = LMatrix4f(proxy_world) + else: + self._last_group_sync_mat = None + + if len(self.selected_ids) == 1: + gid = self.selected_ids[0] + obj_np = self.controller.id_to_object_np.get(gid) + pick_np = self.controller.id_to_pick_np.get(gid) + if not (obj_np and pick_np and not obj_np.is_empty() and not pick_np.is_empty()): + return + obj_world_mat = obj_np.get_mat(self.base.render) + if ( + self._last_single_sync_gid == gid and + self._last_single_sync_mat and + self._matrices_close(obj_world_mat, self._last_single_sync_mat) + ): + return + self._last_single_sync_gid = gid + self._last_single_sync_mat = LMatrix4f(obj_world_mat) + pick_world_mat = pick_np.get_mat(self.base.render) + if not self._matrices_close(obj_world_mat, pick_world_mat): + pick_np.set_mat(self.base.render, obj_world_mat) + chunk_id = self.controller.id_to_chunk.get(gid) + if chunk_id is not None and chunk_id in self.controller.chunks: + self.controller.chunks[chunk_id]["dirty"] = True + return + + self._last_single_sync_gid = None + self._last_single_sync_mat = None for gid in self.selected_ids: obj_np = self.controller.id_to_object_np.get(gid) pick_np = self.controller.id_to_pick_np.get(gid) @@ -440,6 +489,7 @@ class SSBOEditor: def clear_selection(self): self._stop_pick_sync_task() + self._reset_pick_sync_cache() self._cleanup_group_proxy() self.selected_name = None self.selected_ids = [] @@ -488,6 +538,7 @@ class SSBOEditor: def select_node(self, key): # Clean up previous group proxy before changing selection self._cleanup_group_proxy() + self._reset_pick_sync_cache() self.selected_name = key self.selected_ids = self.controller.name_to_ids.get(key, []) @@ -532,7 +583,12 @@ class SSBOEditor: obj_np.wrt_reparent_to(proxy) self._transform_gizmo.attach(proxy) - self._start_pick_sync_task() + # For huge groups, avoid per-frame full sync; we still sync on demand + # right before picking via pick_object(). + if len(valid) <= self._pick_sync_bg_limit: + self._start_pick_sync_task() + else: + self._stop_pick_sync_task() def _rebuild_filtered_tree_rows(self): """ diff --git a/ui/panels/editor_panels.py b/ui/panels/editor_panels.py index 860d22ca..f1d9379b 100644 --- a/ui/panels/editor_panels.py +++ b/ui/panels/editor_panels.py @@ -468,8 +468,33 @@ class EditorPanels: # 处理节点选择 if imgui.is_item_clicked(): + # In SSBO mode, clicking model root should finally bind gizmo to + # SSBO group proxy (not legacy model root). So run legacy + # selection first, then force SSBO root selection at the end. + force_ssbo_root_key = None + ssbo_editor_ref = None + try: + ssbo_editor = getattr(self.app, "ssbo_editor", None) + controller = getattr(ssbo_editor, "controller", None) if ssbo_editor else None + ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None + root_key = getattr(controller, "tree_root_key", None) if controller else None + if ( + ssbo_editor and controller and ssbo_model and node == ssbo_model + and root_key and root_key in controller.tree_nodes + ): + force_ssbo_root_key = root_key + ssbo_editor_ref = ssbo_editor + except Exception: + pass + if hasattr(self.app, 'selection') and self.app.selection: self.app.selection.updateSelection(node) + + if force_ssbo_root_key and ssbo_editor_ref: + try: + ssbo_editor_ref.select_node(force_ssbo_root_key) + except Exception: + pass # Clear LUI selection when a scene node is selected if hasattr(self.app, 'lui_manager'): self.app.lui_manager.selected_index = -1