diff --git a/Library/AssetDB.json b/Library/AssetDB.json index 5c04040f..43e9e982 100644 --- a/Library/AssetDB.json +++ b/Library/AssetDB.json @@ -10,7 +10,7 @@ "importer": "model_importer", "import_settings": {}, "dependency_guids": [], - "updated_at": "2026-03-19 10:14:56", + "updated_at": "2026-03-19 17:17:48", "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 e0404b26..2d325fcf 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-19 10:14:56" + "generated_at": "2026-03-19 17:17:48" } \ No newline at end of file diff --git a/core/selection.py b/core/selection.py index 8945dccb..5d03fb0d 100644 --- a/core/selection.py +++ b/core/selection.py @@ -2099,18 +2099,22 @@ class SelectionSystem: except Exception as e: print(f"同步 SSBO 选择状态失败: {e}") - self.selectedNode = nodePath - # 添加兼容性属性 - self.selectedObject = nodePath + effective_node = self._get_effective_selected_node() + if effective_node is None: + effective_node = nodePath - if self._is_valid_node(nodePath, require_attached=True): - node_name = nodePath.getName() + self.selectedNode = effective_node + # 添加兼容性属性 + self.selectedObject = effective_node + + if self._is_valid_node(effective_node, require_attached=True): + node_name = effective_node.getName() #print(f"开始为节点 {node_name} 创建选择框和坐标轴...") # 创建选择框 #print("创建选择框...") if self.show_selection_box: - self.createSelectionBox(nodePath) + self.createSelectionBox(effective_node) else: self.clearSelectionBox() @@ -2124,12 +2128,12 @@ class SelectionSystem: # 创建坐标轴 #print("创建坐标轴...") - self._updateSelectionOutline(nodePath) - self.createGizmo(nodePath) - if self._has_attached_transform_gizmo(nodePath): + self._updateSelectionOutline(effective_node) + self.createGizmo(effective_node) + if self._has_attached_transform_gizmo(effective_node): gizmo_name = "Unknown" if self._has_new_transform_gizmo(): - gizmo_name = getattr(nodePath, "getName", lambda: "TransformGizmo")() + gizmo_name = getattr(effective_node, "getName", lambda: "TransformGizmo")() elif self.gizmo and not self.gizmo.isEmpty(): gizmo_name = self.gizmo.getName() #print(f"✓ 坐标轴创建成功: {gizmo_name}") diff --git a/imgui.ini b/imgui.ini index 28dce1c8..4d4334e9 100644 --- a/imgui.ini +++ b/imgui.ini @@ -31,19 +31,19 @@ DockId=0x0000000D,0 [Window][场景树] Pos=0,20 -Size=274,1084 +Size=274,1331 Collapsed=0 DockId=0x00000007,0 [Window][属性面板] -Pos=1655,20 -Size=393,1084 +Pos=2167,20 +Size=393,1331 Collapsed=0 DockId=0x00000002,0 [Window][控制台] -Pos=276,705 -Size=1377,399 +Pos=276,952 +Size=1889,399 Collapsed=0 DockId=0x00000006,1 @@ -51,7 +51,6 @@ DockId=0x00000006,1 Pos=1950,20 Size=610,995 Collapsed=0 -DockId=0x00000005,2 [Window][中文显示测试] Pos=60,60 @@ -60,7 +59,7 @@ Collapsed=0 [Window][WindowOverViewport_11111111] Pos=0,20 -Size=2048,1084 +Size=2560,1331 Collapsed=0 [Window][测试窗口1] @@ -99,8 +98,8 @@ Size=600,500 Collapsed=0 [Window][资源管理器] -Pos=276,705 -Size=1377,399 +Pos=276,952 +Size=1889,399 Collapsed=0 DockId=0x00000006,0 @@ -150,7 +149,7 @@ Size=101,226 Collapsed=0 [Window][LUI编辑器] -Pos=1675,295 +Pos=1411,331 Size=358,569 Collapsed=0 @@ -206,7 +205,7 @@ Collapsed=0 DockId=0x00000005,2 [Window][项目另存为] -Pos=1050,576 +Pos=794,432 Size=460,240 Collapsed=0 @@ -216,7 +215,7 @@ Size=540,320 Collapsed=0 [Docking][Data] -DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=2048,1084 Split=X +DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=2560,1331 Split=X 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 diff --git a/project/project_manager.py b/project/project_manager.py index cfc60f20..c039b7b1 100644 --- a/project/project_manager.py +++ b/project/project_manager.py @@ -841,6 +841,8 @@ class ProjectManager: root_nodes = [] seen = set() + model_root_nodes = [] + auxiliary_root_nodes = [] ssbo_editor = getattr(self.world, "ssbo_editor", None) source_model_root = getattr(ssbo_editor, "source_model_root", None) if ssbo_editor else None if source_model_root and not source_model_root.isEmpty(): @@ -867,7 +869,7 @@ class ProjectManager: if node_key in seen: continue seen.add(node_key) - root_nodes.append(node) + model_root_nodes.append(node) for collection_name in ("Spotlight", "Pointlight"): for node in list(getattr(scene_manager, collection_name, []) or []): @@ -877,7 +879,10 @@ class ProjectManager: if node_key in seen: continue seen.add(node_key) - root_nodes.append(node) + auxiliary_root_nodes.append(node) + + root_nodes.extend(model_root_nodes) + root_nodes.extend(auxiliary_root_nodes) return root_nodes def _write_scene_description_from_live_scene(self, scene_entry, project_path): @@ -916,42 +921,14 @@ class ProjectManager: return False self._clearCurrentScene() - if self._load_scene_description_via_ssbo(scene_description, project_path, asset_database): - scene_manager = getattr(self.world, "scene_manager", None) - scene_components = dict(scene_description.get("scene_components", {}) or {}) - camera_state = dict(scene_components.get("camera", {}) or scene_description.get("camera", {}) or {}) - camera = getattr(self.world, "camera", None) or getattr(self.world, "cam", None) - if camera and not camera.isEmpty(): - try: - position = list(camera_state.get("position", []) or []) - rotation = list(camera_state.get("rotation", []) or []) - if len(position) >= 3: - camera.setPos(float(position[0]), float(position[1]), float(position[2])) - if len(rotation) >= 3: - camera.setHpr(float(rotation[0]), float(rotation[1]), float(rotation[2])) - if "camera_control_enabled" in camera_state: - self.world.camera_control_enabled = bool(camera_state.get("camera_control_enabled", True)) - except Exception: - pass - gui_snapshot = list((scene_components.get("gui", {}) or {}).get("elements", []) or scene_description.get("gui", []) or []) - lui_snapshot = dict(scene_components.get("lui", {}) or scene_description.get("lui", {}) or {}) - scene_paths = self._scene_paths(scene_entry or {}, project_path=project_path) if scene_entry else {} - self._write_scene_sidecars(scene_paths, gui_snapshot, lui_snapshot) - - try: - load_lui_fn = getattr(scene_manager, "_load_lui_snapshot_file", None) if scene_manager else None - if callable(load_lui_fn) and scene_paths.get("scene_file"): - temp_stub = os.path.splitext(scene_paths["scene_file"])[0] + ".bam" - load_lui_fn(temp_stub) - except Exception: - pass - return True + ssbo_loaded = self._load_scene_description_via_ssbo(scene_description, project_path, asset_database) scene_root, keep_nodes = self._build_scene_root_from_description( scene_description, project_path, asset_database, include_mode="all", + skip_asset_nodes=ssbo_loaded, ) scene_manager = getattr(self.world, "scene_manager", None) @@ -987,7 +964,8 @@ class ProjectManager: built_point_lights.append(child) if scene_manager: - scene_manager.models = built_model_nodes + if not ssbo_loaded: + scene_manager.models = built_model_nodes scene_manager.Spotlight = built_spot_lights scene_manager.Pointlight = built_point_lights update_tree_fn = getattr(scene_manager, "updateSceneTree", None) @@ -1030,7 +1008,7 @@ class ProjectManager: load_lui_fn(temp_stub) except Exception: pass - return True + return bool(ssbo_loaded or built_nodes or built_spot_lights or built_point_lights or scene_components) def _iter_top_level_scene_asset_nodes(self, scene_description): nodes = list(scene_description.get("nodes", []) or []) @@ -1176,9 +1154,12 @@ class ProjectManager: if not loaded_any: return False + clear_runtime_fn = getattr(ssbo_editor, "_clear_runtime_state", None) rebuild_fn = getattr(ssbo_editor, "_rebuild_runtime_from_source_root", None) if callable(rebuild_fn): try: + if callable(clear_runtime_fn): + clear_runtime_fn(preserve_source_models=True) rebuild_fn(highlight_root_name=None) except Exception: pass @@ -1451,7 +1432,26 @@ class ProjectManager: return True return False - def _build_scene_root_from_description(self, scene_description, project_path, asset_database, include_mode="all"): + def _node_belongs_to_asset_hierarchy(self, node, node_lookup): + if not isinstance(node, dict): + return False + + asset_guid = str(node.get("asset_guid", "") or (node.get("components", {}) or {}).get("model", {}).get("asset_guid", "") or "").strip() + if asset_guid: + return True + + parent_id = node.get("parent_id") + while parent_id: + parent_node = node_lookup.get(parent_id) + if not parent_node: + break + parent_asset_guid = str(parent_node.get("asset_guid", "") or (parent_node.get("components", {}) or {}).get("model", {}).get("asset_guid", "") or "").strip() + if parent_asset_guid: + return True + parent_id = parent_node.get("parent_id") + return False + + def _build_scene_root_from_description(self, scene_description, project_path, asset_database, include_mode="all", skip_asset_nodes=False): nodes = list(scene_description.get("nodes", []) or []) node_lookup = { str(node.get("node_id", "") or ""): dict(node) @@ -1462,6 +1462,9 @@ class ProjectManager: for node_id, node in node_lookup.items(): if not self._should_keep_scene_description_node(node_id, node, node_lookup): continue + if skip_asset_nodes: + if self._node_belongs_to_asset_hierarchy(node, node_lookup): + continue is_interactive = self._is_scene_description_node_interactive(node) if include_mode == "interactive" and not is_interactive: continue diff --git a/ssbo_component/ssbo_editor.py b/ssbo_component/ssbo_editor.py index ba1fe719..6978d24e 100644 --- a/ssbo_component/ssbo_editor.py +++ b/ssbo_component/ssbo_editor.py @@ -411,6 +411,31 @@ class SSBOEditor: if not self.controller or not self.model or not self.source_model_root: return + proxy = getattr(self, "_group_proxy", None) + if self._node_is_valid(proxy): + try: + selection_key = str(proxy.getTag("ssbo_selection_key") or "").strip() + except Exception: + selection_key = "" + if selection_key and selection_key != getattr(self.controller, "tree_root_key", None): + source_group_node = self._resolve_source_node_by_tree_key(selection_key) + if self._node_is_valid(source_group_node): + try: + group_mat = LMatrix4f(proxy.get_mat(self.base.render)) + except Exception: + try: + group_mat = LMatrix4f(proxy.getMat(self.base.render)) + except Exception: + group_mat = None + if group_mat is not None: + try: + source_group_node.set_mat(group_mat) + except Exception: + try: + source_group_node.setMat(group_mat) + except Exception: + pass + grouped_entries = {} for gid, obj_np in self.controller.id_to_object_np.items(): if not self._node_is_valid(obj_np): @@ -2168,6 +2193,58 @@ class SSBOEditor: "is_group": len(self.selected_ids) > 1 and not self._is_root_selection(), } + def get_selection_key(self): + if not self.controller or self.selected_name is None: + return None + return self.selected_name + + def get_selection_source_node(self): + if not self.controller or self.selected_name is None: + return None + source_node = self._resolve_source_node_by_tree_key(self.selected_name) + if self._node_is_valid(source_node): + return source_node + return None + + def is_source_tree_node(self, node): + if not self._node_is_valid(node) or not self._node_is_valid(self.source_model_root): + return False + current = node + while self._node_is_valid(current): + if current == self.source_model_root: + return True + try: + current = current.get_parent() + except Exception: + try: + current = current.getParent() + except Exception: + return False + return False + + def refresh_runtime_from_source(self, preserve_selection=True): + if not self._get_source_root_children(): + return False + + selected_key = self.selected_name if preserve_selection else None + try: + self._clear_runtime_state(preserve_source_models=True) + self._rebuild_runtime_from_source_root(highlight_root_name=None) + if preserve_selection and selected_key and self.controller: + try: + if ( + selected_key == getattr(self.controller, "tree_root_key", None) + or selected_key in getattr(self.controller, "tree_nodes", {}) + or selected_key in getattr(self.controller, "name_to_ids", {}) + ): + self.select_node(selected_key) + except Exception: + pass + return True + except Exception as e: + print(f"[SSBOEditor] 从 source 刷新 runtime 失败: {e}") + return False + def _sync_editor_selection_reference(self, node): selection = getattr(self.base, "selection", None) if not selection: diff --git a/ui/panels/app_actions.py b/ui/panels/app_actions.py index dc0b1fa9..a40fe287 100644 --- a/ui/panels/app_actions.py +++ b/ui/panels/app_actions.py @@ -750,7 +750,7 @@ class AppActions: return # 获取当前选中的节点 - selected_node = self._resolve_cut_copy_node(self._get_selection_node()) + selected_node = self._resolve_cut_copy_node(self._get_selection_source_node()) if not selected_node: self.add_warning_message("没有选中的节点") return @@ -785,7 +785,7 @@ class AppActions: return # 获取当前选中的节点 - selected_node = self._resolve_cut_copy_node(self._get_selection_node()) + selected_node = self._resolve_cut_copy_node(self._get_selection_source_node()) if not selected_node: self.add_warning_message("没有选中的节点") return @@ -833,7 +833,7 @@ class AppActions: # 确定粘贴目标父节点 parent_node = None if hasattr(self, 'selection') and self.selection: - selected_node = self._get_selection_node() + selected_node = self._get_selection_source_node() if selected_node and not selected_node.isEmpty(): # Paste as sibling by default (not as child of selected node), # which matches editor expectations and avoids "pasted but invisible". @@ -970,7 +970,7 @@ class AppActions: return # 获取当前选中的节点 - selected_node = self._get_selection_node() + selected_node = self._get_selection_source_node() if not selected_node: self.add_warning_message("没有选中的节点") return diff --git a/ui/panels/editor_panels_left.py b/ui/panels/editor_panels_left.py index 52b3b735..ae41e10f 100644 --- a/ui/panels/editor_panels_left.py +++ b/ui/panels/editor_panels_left.py @@ -78,8 +78,11 @@ class EditorPanelsLeftMixin: if getattr(self.app, "use_ssbo_mouse_picking", False): ssbo_editor = getattr(self.app, "ssbo_editor", None) ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None + source_model_root = getattr(ssbo_editor, "source_model_root", None) if ssbo_editor else None if ssbo_model and not ssbo_model.isEmpty() and ssbo_model.hasParent(): return [ssbo_model] + if source_model_root and not source_model_root.isEmpty(): + return [] scene_manager = getattr(self.app, "scene_manager", None) if scene_manager and hasattr(scene_manager, "models"): return [m for m in scene_manager.models if m and not m.isEmpty()] @@ -312,7 +315,24 @@ class EditorPanelsLeftMixin: # 检查是否被选中 is_selected = (self.app._get_selection_node() == node) - + 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 + is_selected = (getattr(ssbo_editor, "selected_name", None) == root_key) + except Exception: + force_ssbo_root_key = None + ssbo_editor_ref = None + # 节点可见性 is_visible = node.is_hidden() == False @@ -327,36 +347,19 @@ class EditorPanelsLeftMixin: # 处理节点选择 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) + if hasattr(self.app, 'lui_manager'): + self.app.lui_manager.selected_index = -1 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 + else: + if hasattr(self.app, 'selection') and self.app.selection: + self.app.selection.updateSelection(node) + # Clear LUI selection when a scene node is selected + if hasattr(self.app, 'lui_manager'): + self.app.lui_manager.selected_index = -1 if self.app.is_dragging and imgui.is_item_hovered(): self.app._drag_scene_tree_hover_node = node diff --git a/ui/panels/editor_panels_right.py b/ui/panels/editor_panels_right.py index 3192eba6..2c55c364 100644 --- a/ui/panels/editor_panels_right.py +++ b/ui/panels/editor_panels_right.py @@ -168,7 +168,7 @@ class EditorPanelsRightMixin( return # --- Scene Node Properties --- - selected_node = self.app._get_selection_node() + selected_node = self.app._get_selection_source_node() if selected_node and not selected_node.isEmpty(): self._draw_node_properties(selected_node) diff --git a/ui/panels/editor_panels_right_material.py b/ui/panels/editor_panels_right_material.py index 4871cd60..77f4bac0 100644 --- a/ui/panels/editor_panels_right_material.py +++ b/ui/panels/editor_panels_right_material.py @@ -19,6 +19,18 @@ class EditorPanelsRightMaterialMixin: pass return self.app._get_node_materials(node) + def _refresh_ssbo_runtime_for_material_node(self, node, preserve_selection=True): + ssbo_editor = getattr(self.app, "ssbo_editor", None) + if not ssbo_editor: + return + if not hasattr(ssbo_editor, "is_source_tree_node") or not ssbo_editor.is_source_tree_node(node): + return + if hasattr(ssbo_editor, "refresh_runtime_from_source"): + try: + ssbo_editor.refresh_runtime_from_source(preserve_selection=preserve_selection) + except Exception: + pass + def _begin_material_edit_session(self, node, session_key): self._ensure_node_materials_are_editable(node) sessions = self._ensure_material_edit_sessions() @@ -52,6 +64,7 @@ class EditorPanelsRightMaterialMixin: return after_snapshot = self.app._capture_node_material_snapshot(node) self._record_material_snapshot_command(node, before_snapshot, after_snapshot) + self._refresh_ssbo_runtime_for_material_node(node) def _apply_material_change_with_history(self, node, apply_callback): self._ensure_node_materials_are_editable(node) @@ -59,6 +72,7 @@ class EditorPanelsRightMaterialMixin: apply_callback() after_snapshot = self.app._capture_node_material_snapshot(node) self._record_material_snapshot_command(node, before_snapshot, after_snapshot) + self._refresh_ssbo_runtime_for_material_node(node) def _select_texture_for_material_with_history(self, node, material, texture_type): self._ensure_node_materials_are_editable(node) @@ -68,6 +82,7 @@ class EditorPanelsRightMaterialMixin: return False after_snapshot = self.app._capture_node_material_snapshot(node) self._record_material_snapshot_command(node, before_snapshot, after_snapshot) + self._refresh_ssbo_runtime_for_material_node(node) return True def _draw_appearance_properties(self, node): @@ -201,7 +216,7 @@ class EditorPanelsRightMaterialMixin: if imgui.is_item_activated(): self._begin_material_edit_session(node, f"material_{i}_roughness") if changed: - self._update_material_roughness(material, new_roughness) + self._update_material_roughness(material, new_roughness, node) self._finish_material_edit_session(node, f"material_{i}_roughness") except: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "粗糙度: 不可用") @@ -213,7 +228,7 @@ class EditorPanelsRightMaterialMixin: if imgui.is_item_activated(): self._begin_material_edit_session(node, f"material_{i}_metallic") if changed: - self._update_material_metallic(material, new_metallic) + self._update_material_metallic(material, new_metallic, node) self._finish_material_edit_session(node, f"material_{i}_metallic") except: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "金属性: 不可用") @@ -225,7 +240,7 @@ class EditorPanelsRightMaterialMixin: if imgui.is_item_activated(): self._begin_material_edit_session(node, f"material_{i}_ior") if changed: - self._update_material_ior(material, new_ior) + self._update_material_ior(material, new_ior, node) self._finish_material_edit_session(node, f"material_{i}_ior") except: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "折射率: 不可用") @@ -241,7 +256,7 @@ class EditorPanelsRightMaterialMixin: self._apply_material_change_with_history( node, lambda selected_preset=preset_name: ( - self._apply_material_preset(material, selected_preset), + self._apply_material_preset(material, selected_preset, node), self._apply_material_surface_state(node, material), self.app._refresh_pipeline_material_mode(node, material), ), @@ -278,6 +293,7 @@ class EditorPanelsRightMaterialMixin: imgui.separator() if imgui.button("应用材质"): self._apply_material_to_node(node) + self._refresh_ssbo_runtime_for_material_node(node) imgui.same_line() if imgui.button("重置材质"): self._apply_material_change_with_history(node, lambda: self._reset_material(node)) diff --git a/ui/panels/editor_panels_top.py b/ui/panels/editor_panels_top.py index 301ccb36..0db4da11 100644 --- a/ui/panels/editor_panels_top.py +++ b/ui/panels/editor_panels_top.py @@ -163,7 +163,7 @@ class EditorPanelsTopMixin: return os.path.basename(project_path) or "未命名项目" def _get_toolbar_selection_name(self): - selected_node = self.app._get_selection_node() + selected_node = self.app._get_selection_source_node() if selected_node and not selected_node.isEmpty(): return selected_node.getName() or "未命名对象" ssbo_summary = self.app._get_ssbo_selection_summary() diff --git a/ui/panels/panel_delegates.py b/ui/panels/panel_delegates.py index c640abc0..de2cd9e3 100644 --- a/ui/panels/panel_delegates.py +++ b/ui/panels/panel_delegates.py @@ -42,6 +42,20 @@ class PanelDelegates: node = getattr(selection, "selectedNode", None) if selection else None return node if self._node_is_valid(node, require_attached=False) else None + def _get_selection_source_node(self): + ssbo_editor = getattr(self, "ssbo_editor", None) + if ssbo_editor and hasattr(ssbo_editor, "has_active_selection") and ssbo_editor.has_active_selection(): + source_node = getattr(ssbo_editor, "get_selection_source_node", lambda: None)() + if source_node and self._node_is_valid(source_node, require_attached=False): + return source_node + return self._get_selection_node() + + def _get_selection_key(self): + ssbo_editor = getattr(self, "ssbo_editor", None) + if ssbo_editor and hasattr(ssbo_editor, "has_active_selection") and ssbo_editor.has_active_selection(): + return getattr(ssbo_editor, "get_selection_key", lambda: None)() + return None + def _get_ssbo_selection_summary(self): ssbo_editor = getattr(self, "ssbo_editor", None) if not ssbo_editor or not hasattr(ssbo_editor, "has_active_selection"): diff --git a/ui/panels/property_helpers.py b/ui/panels/property_helpers.py index dd534e65..75cf4b4e 100644 --- a/ui/panels/property_helpers.py +++ b/ui/panels/property_helpers.py @@ -1132,6 +1132,11 @@ class PropertyHelpers: ssbo_editor.sync_scene_nodes_to_pick([node]) except Exception: pass + if ssbo_editor and hasattr(ssbo_editor, "is_source_tree_node") and ssbo_editor.is_source_tree_node(node): + try: + ssbo_editor.refresh_runtime_from_source(preserve_selection=True) + except Exception: + pass except Exception as e: print(f"应用材质快照失败: {e}") @@ -1180,6 +1185,43 @@ class PropertyHelpers: except Exception as e: print(f"同步Geom材质状态失败: {e}") + def _refresh_ssbo_runtime_for_material_node(self, node, preserve_selection=True): + """If editing a SSBO source node, rebuild the runtime proxy immediately.""" + try: + ssbo_editor = getattr(self, "ssbo_editor", None) + if not ssbo_editor or not node or node.isEmpty(): + return + if not hasattr(ssbo_editor, "is_source_tree_node") or not ssbo_editor.is_source_tree_node(node): + return + if hasattr(ssbo_editor, "refresh_runtime_from_source"): + ssbo_editor.refresh_runtime_from_source(preserve_selection=preserve_selection) + except Exception: + pass + + def _sync_material_node_runtime(self, node, material=None, refresh_ssbo_runtime=True): + """Push source material edits into visible render state.""" + try: + if not node or node.isEmpty(): + return + + materials = [material] if material is not None else self._get_node_materials(node) + for current_material in materials: + if current_material is None: + continue + self._apply_material_to_geom_states(node, current_material) + self._apply_material_surface_state(node, current_material) + refresh_pipeline = getattr(self, "_refresh_pipeline_material_mode", None) + if callable(refresh_pipeline): + try: + refresh_pipeline(node, current_material) + except Exception: + pass + + if refresh_ssbo_runtime: + self._refresh_ssbo_runtime_for_material_node(node) + except Exception as e: + print(f"同步材质显示状态失败: {e}") + def _get_node_materials(self, node): """Return the editable materials currently used by a node.""" if not node or node.isEmpty(): @@ -1955,34 +1997,40 @@ class PropertyHelpers: print(f"更新材质基础颜色失败: {e}") - def _update_material_roughness(self, material, value): + def _update_material_roughness(self, material, value, node=None): """更新材质粗糙度""" try: if hasattr(material, 'set_roughness'): material.set_roughness(value) + if node is not None: + self._sync_material_node_runtime(node, material) except Exception as e: print(f"更新材质粗糙度失败: {e}") - def _update_material_metallic(self, material, value): + def _update_material_metallic(self, material, value, node=None): """更新材质金属性""" try: if hasattr(material, 'set_metallic'): material.set_metallic(value) + if node is not None: + self._sync_material_node_runtime(node, material) except Exception as e: print(f"更新材质金属性失败: {e}") - def _update_material_ior(self, material, value): + def _update_material_ior(self, material, value, node=None): """更新材质折射率""" try: if hasattr(material, 'set_refractive_index'): material.set_refractive_index(value) + if node is not None: + self._sync_material_node_runtime(node, material) except Exception as e: print(f"更新材质折射率失败: {e}") - def _apply_material_preset(self, material, preset_name): + def _apply_material_preset(self, material, preset_name, node=None): """应用材质预设""" try: from panda3d.core import Vec4, Material @@ -2050,6 +2098,8 @@ class PropertyHelpers: material.set_emission(Vec4(surface_type, 0.0, opacity, 0.0)) print(f"已应用材质预设: {preset_name}") + if node is not None: + self._sync_material_node_runtime(node, material) except Exception as e: print(f"应用材质预设失败: {e}") @@ -2059,7 +2109,7 @@ class PropertyHelpers: try: material = self._ensure_material_for_node(node) if material: - self._apply_material_surface_state(node, material) + self._sync_material_node_runtime(node, material) print("材质已应用到节点") except Exception as e: print(f"应用材质失败: {e}") @@ -2099,7 +2149,9 @@ class PropertyHelpers: material.set_refractive_index(1.5) if hasattr(material, 'set_emission'): material.set_emission(Vec4(0.0, 0.0, 1.0, 0.0)) - self._apply_material_surface_state(node, material) + self._sync_material_node_runtime(node, material, refresh_ssbo_runtime=False) + + self._refresh_ssbo_runtime_for_material_node(node) print(f"已重置材质") except Exception as e: @@ -2480,6 +2532,8 @@ class PropertyHelpers: if texture_path: stable_texture_path = os.path.normpath(os.path.abspath(texture_path)) node.setTag(f"material_texture_{texture_type}", stable_texture_path) + + self._sync_material_node_runtime(node, material) print(f"已应用{texture_type}纹理到槽位 p3d_Texture{slot}: {texture_path}") return True @@ -2512,6 +2566,12 @@ class PropertyHelpers: if node.hasTag("material_effect_parallax_enabled"): node.clearTag("material_effect_parallax_enabled") + for material in self._get_node_materials(node): + if material is None: + continue + self._sync_material_node_runtime(node, material, refresh_ssbo_runtime=False) + self._refresh_ssbo_runtime_for_material_node(node) + print("已清除所有纹理") return True except Exception as e: diff --git a/ui/panels/script_panels.py b/ui/panels/script_panels.py index 96fd943d..755513e7 100644 --- a/ui/panels/script_panels.py +++ b/ui/panels/script_panels.py @@ -261,7 +261,7 @@ class ScriptPanels: """绘制脚本挂载组""" if imgui.collapsing_header("脚本挂载"): # 显示当前选中对象 - selected_node = self._get_selection_node() + selected_node = self._get_selection_source_node() if selected_node and not selected_node.isEmpty(): imgui.text("选中对象:") diff --git a/新项目_副本/project.json b/新项目_副本/project.json deleted file mode 100644 index d053d21e..00000000 --- a/新项目_副本/project.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "新项目_副本", - "path": "D:\\IMGUI\\EG\\新项目_副本", - "last_modified": "2026-03-13 16:55:57", - "scene_file": "scenes\\scene.bam", - "created_at": "2026-03-13 16:55:57", - "version": "1.0" -} \ No newline at end of file diff --git a/新项目_副本/scenes/scene.bam b/新项目_副本/scenes/scene.bam deleted file mode 100644 index a8cccf47..00000000 Binary files a/新项目_副本/scenes/scene.bam and /dev/null differ diff --git a/新项目_副本/新项目_副本.png b/新项目_副本/新项目_副本.png deleted file mode 100644 index ba2db267..00000000 Binary files a/新项目_副本/新项目_副本.png and /dev/null differ