diff --git a/Library/AssetDB.json b/Library/AssetDB.json index 8ec8c9fe..5c04040f 100644 --- a/Library/AssetDB.json +++ b/Library/AssetDB.json @@ -10,7 +10,7 @@ "importer": "model_importer", "import_settings": {}, "dependency_guids": [], - "updated_at": "2026-03-18 18:31:18", + "updated_at": "2026-03-19 10:14:56", "imported_cache": { "root": "Library/Imported/90ece77e67b54ccda38d2de71cb4694d", "model_bam": "Library/Imported/90ece77e67b54ccda38d2de71cb4694d/model.bam", diff --git a/Library/Imported/90ece77e67b54ccda38d2de71cb4694d/import_info.json b/Library/Imported/90ece77e67b54ccda38d2de71cb4694d/import_info.json index 43cb083b..e0404b26 100644 --- a/Library/Imported/90ece77e67b54ccda38d2de71cb4694d/import_info.json +++ b/Library/Imported/90ece77e67b54ccda38d2de71cb4694d/import_info.json @@ -3,5 +3,5 @@ "asset_path": "Assets/Models/box1.glb", "asset_type": "model", "source_hash": "fc694905c47a9b0005d77b701cc41852b56ef08c7406829a306e98f3ce158a64", - "generated_at": "2026-03-18 18:31:18" + "generated_at": "2026-03-19 10:14:56" } \ No newline at end of file diff --git a/core/resource_manager.py b/core/resource_manager.py index 332739c4..8ea32ae4 100644 --- a/core/resource_manager.py +++ b/core/resource_manager.py @@ -14,13 +14,22 @@ import time class ResourceManager: """ImGui资源管理器类""" + + DEFAULT_ASSET_SUBDIRS = ( + "Models", + "Textures", + "Audio", + "Video", + "UI", + "Scripts", + ) def __init__(self, world): self.world = world self.project_root = Path(__file__).resolve().parent.parent self.project_path: Optional[Path] = None - # 当前浏览路径,默认从Resources目录开始 + # 当前浏览路径,默认从统一的 Assets 结构开始 self.current_path = self._get_default_root_path() # 历史记录,用于前进后退导航 @@ -63,15 +72,29 @@ class ResourceManager: assets_root = Path(current_project_path) / "Assets" elif self.project_path: assets_root = self.project_path / "Assets" + else: + assets_root = self.project_root / "Assets" - if assets_root and assets_root.exists(): - return assets_root.resolve() + if assets_root: + self._ensure_assets_structure(assets_root) + if assets_root.exists(): + return assets_root.resolve() legacy_resources = self.project_root / "Resources" if legacy_resources.exists(): return legacy_resources.resolve() return self.project_root.resolve() + def _ensure_assets_structure(self, assets_root: Path): + """确保资源浏览器默认使用统一的 Assets 目录结构。""" + try: + assets_root.mkdir(parents=True, exist_ok=True) + for subdir in self.DEFAULT_ASSET_SUBDIRS: + (assets_root / subdir).mkdir(parents=True, exist_ok=True) + except OSError: + # 目录创建失败时,保留后续兼容回退逻辑。 + pass + def set_project_path(self, project_path: str): self.project_path = Path(project_path).resolve() if project_path else None self.current_path = self._get_default_root_path() diff --git a/core/selection.py b/core/selection.py index d2fd78b4..8945dccb 100644 --- a/core/selection.py +++ b/core/selection.py @@ -189,6 +189,9 @@ class SelectionSystem: node = ssbo_editor.get_selection_scene_node() if self._is_valid_node(node, require_attached=True): return node + fallback_model = getattr(ssbo_editor, "model", None) + if self._is_valid_node(fallback_model, require_attached=True): + return fallback_model return None except Exception: pass diff --git a/imgui.ini b/imgui.ini index 856dd288..b87f47b9 100644 --- a/imgui.ini +++ b/imgui.ini @@ -84,12 +84,12 @@ Size=400,300 Collapsed=0 [Window][选择路径] -Pos=724,302 +Pos=980,446 Size=600,500 Collapsed=0 [Window][打开项目] -Pos=774,352 +Pos=1030,496 Size=500,400 Collapsed=0 @@ -206,7 +206,7 @@ Collapsed=0 DockId=0x00000005,2 [Window][项目另存为] -Pos=794,432 +Pos=1050,576 Size=460,240 Collapsed=0 @@ -220,7 +220,7 @@ DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=2048,1084 Split= DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=1525,1012 Split=X DockNode ID=0x00000007 Parent=0x00000001 SizeRef=274,1084 Selected=0xE0015051 DockNode ID=0x00000008 Parent=0x00000001 SizeRef=1249,1084 Split=Y - DockNode ID=0x00000005 Parent=0x00000008 SizeRef=2048,610 Split=Y + DockNode ID=0x00000005 Parent=0x00000008 SizeRef=2048,970 Split=Y DockNode ID=0x0000000D Parent=0x00000005 SizeRef=1318,383 HiddenTabBar=1 Selected=0x43A39006 DockNode ID=0x0000000E Parent=0x00000005 SizeRef=1318,363 CentralNode=1 Selected=0xE0015051 DockNode ID=0x00000006 Parent=0x00000008 SizeRef=2048,400 Selected=0x3A2E05C3 diff --git a/ssbo_component/ssbo_editor.py b/ssbo_component/ssbo_editor.py index 417f966d..69320120 100644 --- a/ssbo_component/ssbo_editor.py +++ b/ssbo_component/ssbo_editor.py @@ -899,6 +899,50 @@ class SSBOEditor: self._rebuild_or_clear_runtime_from_current_source() return target + def delete_selected_source_node(self): + """Delete the current SSBO selection from source_model_root and rebuild runtime.""" + if not self.controller or self.selected_name is None: + return False + + target = self._resolve_source_node_by_tree_key(self.selected_name) + if not self._node_is_valid(target): + return False + + source_root = self._ensure_source_model_root() + if target == source_root: + children = self._get_source_root_children() + if not children: + return False + if self.controller and self.model: + self._snapshot_top_level_transforms_to_source_root() + self._clear_runtime_state(preserve_source_models=True) + for child in children: + try: + child.detach_node() + except Exception: + try: + child.detachNode() + except Exception: + continue + self.clear_selection(sync_world_selection=False) + self._rebuild_or_clear_runtime_from_current_source() + return True + + if self.controller and self.model: + self._snapshot_top_level_transforms_to_source_root() + self._clear_runtime_state(preserve_source_models=True) + try: + target.detach_node() + except Exception: + try: + target.detachNode() + except Exception: + return False + + self.clear_selection(sync_world_selection=False) + self._rebuild_or_clear_runtime_from_current_source() + return True + def attach_source_child(self, child_np, highlight_root_name=None): if not self._node_is_valid(child_np): return None diff --git a/ui/panels/app_actions.py b/ui/panels/app_actions.py index 08cca488..8c2fdd42 100644 --- a/ui/panels/app_actions.py +++ b/ui/panels/app_actions.py @@ -1003,6 +1003,42 @@ class AppActions: ssbo_editor = getattr(self, "ssbo_editor", None) ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None deleting_ssbo_root = bool(ssbo_model and (node == ssbo_model)) + deleting_ssbo_selection = bool( + ssbo_editor and + hasattr(ssbo_editor, "has_active_selection") and + ssbo_editor.has_active_selection() + ) + + if deleting_ssbo_selection and hasattr(ssbo_editor, "delete_selected_source_node"): + deleted = bool(ssbo_editor.delete_selected_source_node()) + if deleted: + scene_manager = getattr(self, "scene_manager", None) + if scene_manager and hasattr(scene_manager, "models"): + rebuilt_model = getattr(ssbo_editor, "model", None) + scene_manager.models = [] + if rebuilt_model and not rebuilt_model.isEmpty(): + scene_manager.models.append(rebuilt_model) + print(f"[SSBO] 已从源场景树删除当前选择并重建: {node_name}") + return True + + if deleting_ssbo_root and ssbo_editor and hasattr(ssbo_editor, "detach_source_child"): + detached = ssbo_editor.detach_source_child(child_name=node_name) + scene_manager = getattr(self, "scene_manager", None) + if scene_manager and hasattr(scene_manager, "models"): + scene_manager.models = [ + model for model in scene_manager.models + if model and not model.isEmpty() and model != node + ] + rebuilt_model = getattr(ssbo_editor, "model", None) + if rebuilt_model and not rebuilt_model.isEmpty() and rebuilt_model not in scene_manager.models: + scene_manager.models.append(rebuilt_model) + if detached is not None: + try: + ssbo_editor.clear_selection(sync_world_selection=False) + except Exception: + pass + print(f"[SSBO] 已从源模型树移除并重建运行时: {node_name}") + return True # 创建删除命令 if hasattr(self, 'command_manager') and self.command_manager: diff --git a/ui/panels/editor_panels_left.py b/ui/panels/editor_panels_left.py index 0864ede2..0c481203 100644 --- a/ui/panels/editor_panels_left.py +++ b/ui/panels/editor_panels_left.py @@ -212,9 +212,11 @@ class EditorPanelsLeftMixin: for i, model in enumerate(models): if ssbo_model and controller and model == ssbo_model: - for child_key in self._get_ssbo_top_level_model_keys(): - self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key) - continue + top_level_keys = self._get_ssbo_top_level_model_keys() + if top_level_keys: + for child_key in top_level_keys: + self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key) + continue self._draw_scene_node(model, model.getName() or f"模型_{i + 1}", "model") imgui.tree_pop() elif not render_entries and not camera_entries and not model_display_count and self._get_scene_tree_filter(): @@ -403,6 +405,12 @@ class EditorPanelsLeftMixin: ssbo_editor.select_node(key) if hasattr(self.app, "lui_manager"): self.app.lui_manager.selected_index = -1 + if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): + try: + ssbo_editor.select_node(key) + except Exception: + pass + self._show_node_context_menu(getattr(ssbo_editor, "model", None), display, "model") else: # Branch node: tree node flags = imgui.TreeNodeFlags_.open_on_arrow @@ -414,6 +422,12 @@ class EditorPanelsLeftMixin: ssbo_editor.select_node(key) if hasattr(self.app, "lui_manager"): self.app.lui_manager.selected_index = -1 + if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): + try: + ssbo_editor.select_node(key) + except Exception: + pass + self._show_node_context_menu(getattr(ssbo_editor, "model", None), display, "model") if opened: for child_key in children: self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key) diff --git a/ui/panels/panel_delegates.py b/ui/panels/panel_delegates.py index 7972aa41..c640abc0 100644 --- a/ui/panels/panel_delegates.py +++ b/ui/panels/panel_delegates.py @@ -30,6 +30,9 @@ class PanelDelegates: ssbo_node = ssbo_editor.get_selection_scene_node() if ssbo_node and self._node_is_valid(ssbo_node, require_attached=False): return ssbo_node + fallback_model = getattr(ssbo_editor, "model", None) + if fallback_model and self._node_is_valid(fallback_model, require_attached=False): + return fallback_model return None selection = getattr(self, "selection", None)