from imgui_bundle import imgui, imgui_ctx import os from pathlib import Path class EditorPanels: """Centralized UI panel renderer for top-level editor panels.""" def __init__(self, app): self.app = app def __getattr__(self, name): return getattr(self.app, name) def __setattr__(self, name, value): if name == "app" or name in self.__dict__ or hasattr(type(self), name): object.__setattr__(self, name, value) else: setattr(self.app, name, value) def draw_menu_bar(self): """绘制菜单栏""" with imgui_ctx.begin_main_menu_bar() as main_menu: if main_menu: # 文件菜单 with imgui_ctx.begin_menu("文件") as file_menu: if file_menu: if imgui.menu_item("新建项目", "Ctrl+N", False, True)[1]: self.app._on_new_project() if imgui.menu_item("打开项目", "Ctrl+O", False, True)[1]: self.app._on_open_project() imgui.separator() if imgui.menu_item("保存", "Ctrl+S", False, True)[1]: self.app._on_save_project() if imgui.menu_item("另存为", "", False, True)[1]: self.app._on_save_as_project() imgui.separator() if imgui.menu_item("退出", "Alt+F4", False, True)[1]: self.app._on_exit() # 编辑菜单 with imgui_ctx.begin_menu("编辑") as edit_menu: if edit_menu: if imgui.menu_item("撤销", "Ctrl+Z", False, True)[1]: self.app._on_undo() if imgui.menu_item("重做", "Ctrl+Y", False, True)[1]: self.app._on_redo() imgui.separator() if imgui.menu_item("剪切", "Ctrl+X", False, True)[1]: self.app._on_cut() if imgui.menu_item("复制", "Ctrl+C", False, True)[1]: self.app._on_copy() if imgui.menu_item("粘贴", "Ctrl+V", False, True)[1]: self.app._on_paste() imgui.separator() if imgui.menu_item("删除", "Del", False, True)[1]: self.app._on_delete() # 创建菜单 with imgui_ctx.begin_menu("创建") as create_menu: if create_menu: if imgui.menu_item("导入模型", "", False, True)[1]: self.app._on_import_model() imgui.separator() if imgui.menu_item("空对象", "", False, True)[1]: self.app._on_create_empty_object() # 3D对象子菜单 with imgui_ctx.begin_menu("3D对象") as three_d_menu: if three_d_menu: if imgui.menu_item("立方体", "", False, True)[1]: self.app._on_create_cube() if imgui.menu_item("球体", "", False, True)[1]: self.app._on_create_sphere() if imgui.menu_item("圆柱体", "", False, True)[1]: self.app._on_create_cylinder() if imgui.menu_item("平面", "", False, True)[1]: self.app._on_create_plane() # 3D GUI子菜单 with imgui_ctx.begin_menu("3D GUI") as three_d_gui_menu: if three_d_gui_menu: if imgui.menu_item("3D文本", "", False, True)[1]: self.app._on_create_3d_text() if imgui.menu_item("3D图片", "", False, True)[1]: self.app._on_create_3d_image() # GUI子菜单 with imgui_ctx.begin_menu("GUI") as gui_menu: if gui_menu: if imgui.menu_item("创建按钮", "", False, True)[1]: self.app._on_create_gui_button() if imgui.menu_item("创建标签", "", False, True)[1]: self.app._on_create_gui_label() if imgui.menu_item("创建输入框", "", False, True)[1]: self.app._on_create_gui_entry() if imgui.menu_item("创建图片", "", False, True)[1]: self.app._on_create_gui_image() imgui.separator() if imgui.menu_item("创建视频屏幕", "", False, True)[1]: self.app._on_create_video_screen() if imgui.menu_item("创建2D视频屏幕", "", False, True)[1]: self.app._on_create_2d_video_screen() if imgui.menu_item("创建球形视频", "", False, True)[1]: self.app._on_create_spherical_video() if imgui.menu_item("创建虚拟屏幕", "", False, True)[1]: self.app._on_create_virtual_screen() # 光源子菜单 with imgui_ctx.begin_menu("光源") as light_menu: if light_menu: if imgui.menu_item("聚光灯", "", False, True)[1]: self.app._on_create_spot_light() if imgui.menu_item("点光源", "", False, True)[1]: self.app._on_create_point_light() # 地形子菜单 with imgui_ctx.begin_menu("地形") as terrain_menu: if terrain_menu: if imgui.menu_item("创建平面地形", "", False, True)[1]: self.app._on_create_flat_terrain() if imgui.menu_item("从高度图创建地形", "", False, True)[1]: self.app._on_create_heightmap_terrain() # 脚本子菜单 with imgui_ctx.begin_menu("脚本") as script_menu: if script_menu: if imgui.menu_item("创建脚本...", "", False, True)[1]: self.app._on_create_script() if imgui.menu_item("加载脚本文件...", "", False, True)[1]: self.app._on_load_script() imgui.separator() if imgui.menu_item("重载所有脚本", "", False, True)[1]: self.app._on_reload_all_scripts() _, self.app.hotReloadEnabled = imgui.menu_item("启用热重载", "", self.app.hotReloadEnabled, True) if imgui.menu_item("脚本管理器", "", False, True)[1]: self.app._on_open_scripts_manager() # 信息面板子菜单 with imgui_ctx.begin_menu("信息面板") as info_panel_menu: if info_panel_menu: if imgui.menu_item("创建2D示例面板", "", False, True)[1]: self.app._on_create_2d_sample_panel() if imgui.menu_item("创建3D实例面板", "", False, True)[1]: self.app._on_create_3d_sample_panel() if imgui.menu_item("Web面板", "", False, True)[1]: self.app._on_create_web_panel() # 视图菜单 with imgui_ctx.begin_menu("视图") as view_menu: if view_menu: _, self.app.showToolbar = imgui.menu_item("工具栏", "", self.app.showToolbar, True) _, self.app.showSceneTree = imgui.menu_item("场景树", "", self.app.showSceneTree, True) _, self.app.showResourceManager = imgui.menu_item("资源管理器", "", self.app.showResourceManager, True) _, self.app.showPropertyPanel = imgui.menu_item("属性面板", "", self.app.showPropertyPanel, True) _, self.app.showConsole = imgui.menu_item("控制台", "", self.app.showConsole, True) _, self.app.showScriptPanel = imgui.menu_item("脚本管理", "", self.app.showScriptPanel, True) _, self.app.showLUIEditor = imgui.menu_item("LUI编辑器", "", self.app.showLUIEditor, True) # 工具菜单 with imgui_ctx.begin_menu("工具") as tools_menu: if tools_menu: # 工具切换选项 if imgui.menu_item("选择工具", "", False, True)[1]: self.app.tool_manager.setCurrentTool("选择") if imgui.menu_item("移动工具", "", False, True)[1]: self.app.tool_manager.setCurrentTool("移动") if imgui.menu_item("旋转工具", "", False, True)[1]: self.app.tool_manager.setCurrentTool("旋转") if imgui.menu_item("缩放工具", "", False, True)[1]: self.app.tool_manager.setCurrentTool("缩放") imgui.separator() # 编辑工具 if imgui.menu_item("光照编辑", "", False, True)[1]: self.app.tool_manager.setCurrentTool("光照编辑") if imgui.menu_item("图形编辑", "", False, True)[1]: self.app.tool_manager.setCurrentTool("图形编辑") imgui.separator() # VR子菜单 with imgui_ctx.begin_menu("VR") as vr_menu: if vr_menu: if imgui.menu_item("进入VR模式", "", False, True)[1]: self.app._toggle_vr_mode() if imgui.menu_item("退出VR模式", "", False, True)[1]: self.app._exit_vr_mode() imgui.separator() if imgui.menu_item("VR状态", "", False, True)[1]: self.app._show_vr_status() if imgui.menu_item("VR设置", "", False, True)[1]: self.app._show_vr_settings() imgui.separator() # VR调试子菜单 with imgui_ctx.begin_menu("VR调试") as vr_debug_menu: if vr_debug_menu: _, self.app.vr_debug_enabled = imgui.menu_item("启用调试输出", "", self.app.vr_debug_enabled, True) if imgui.menu_item("立即显示性能报告", "", False, True)[1]: self.app._show_vr_performance_report() imgui.separator() # 输出模式 with imgui_ctx.begin_menu("输出模式") as output_menu: if output_menu: if imgui.menu_item("简短模式", "", not self.app.vr_detailed_mode, True)[1]: self.app.vr_detailed_mode = False if imgui.menu_item("详细模式", "", self.app.vr_detailed_mode, True)[1]: self.app.vr_detailed_mode = True imgui.separator() _, self.app.vr_performance_monitor = imgui.menu_item("启用性能监控", "", self.app.vr_performance_monitor, True) # 窗口菜单 - 已隐藏 # with imgui_ctx.begin_menu("窗口") as window_menu: # if window_menu: # _, self.app.showDemoWindow = imgui.menu_item("ImGui演示", "", self.app.showDemoWindow, True) # if self.app.testTexture: # imgui.menu_item("关闭纹理测试", "", False, True) # else: # imgui.menu_item("显示纹理测试", "", False, True) # 帮助菜单 with imgui_ctx.begin_menu("帮助") as help_menu: if help_menu: imgui.menu_item("关于", "", False, True) imgui.menu_item("文档", "", False, True) # 右侧显示FPS imgui.set_cursor_pos_x(imgui.get_window_size().x - 140) imgui.text("%.2f FPS (%.2f ms)" % (imgui.get_io().framerate, 1000.0 / imgui.get_io().framerate)) def draw_toolbar(self): """绘制工具栏""" # 工具栏可以保持无标题栏,但允许移动和调整大小 flags = self.app.style_manager.get_window_flags("toolbar") with self.app.style_manager.begin_styled_window("工具栏", self.app.showToolbar, flags): self.app.showToolbar = True # 确保窗口保持打开 # 选择工具按钮 select_active = self.app.tool_manager.isSelectionTool() if self.app.icons.get('select'): tint_col = (1.0, 1.0, 0.0, 1.0) if select_active else (1.0, 1.0, 1.0, 1.0) if self.app.style_manager.image_button(self.app.icons['select'], (24, 24), tint_col=tint_col): self.app.tool_manager.setCurrentTool("选择") if imgui.is_item_hovered(): imgui.set_tooltip("选择工具 (Q)") imgui.same_line() else: if imgui.button("选择##select_tool"): self.app.tool_manager.setCurrentTool("选择") if select_active: draw_list = imgui.get_window_draw_list() button_min = imgui.get_item_rect_min() button_max = imgui.get_item_rect_max() draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3))) imgui.same_line() # 移动工具按钮 move_active = self.app.tool_manager.isMoveTool() if self.app.icons.get('move'): # 使用不同颜色表示活动状态 tint_col = (1.0, 1.0, 0.0, 1.0) if move_active else (1.0, 1.0, 1.0, 1.0) # 活动时显示黄色 if self.app.style_manager.image_button(self.app.icons['move'], (24, 24), tint_col=tint_col): self.app.tool_manager.setCurrentTool("移动") if imgui.is_item_hovered(): imgui.set_tooltip("移动工具 (W)") imgui.same_line() else: if imgui.button("移动##move_tool"): self.app.tool_manager.setCurrentTool("移动") if move_active: # 为活动按钮添加背景色 draw_list = imgui.get_window_draw_list() button_min = imgui.get_item_rect_min() button_max = imgui.get_item_rect_max() draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3))) imgui.same_line() # 旋转工具按钮 rotate_active = self.app.tool_manager.isRotateTool() if self.app.icons.get('rotate'): tint_col = (1.0, 1.0, 0.0, 1.0) if rotate_active else (1.0, 1.0, 1.0, 1.0) if self.app.style_manager.image_button(self.app.icons['rotate'], (24, 24), tint_col=tint_col): self.app.tool_manager.setCurrentTool("旋转") if imgui.is_item_hovered(): imgui.set_tooltip("旋转工具 (E)") imgui.same_line() else: if imgui.button("旋转##rotate_tool"): self.app.tool_manager.setCurrentTool("旋转") if rotate_active: draw_list = imgui.get_window_draw_list() button_min = imgui.get_item_rect_min() button_max = imgui.get_item_rect_max() draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3))) imgui.same_line() # 缩放工具按钮 scale_active = self.app.tool_manager.isScaleTool() if self.app.icons.get('scale'): tint_col = (1.0, 1.0, 0.0, 1.0) if scale_active else (1.0, 1.0, 1.0, 1.0) if self.app.style_manager.image_button(self.app.icons['scale'], (24, 24), tint_col=tint_col): self.app.tool_manager.setCurrentTool("缩放") if imgui.is_item_hovered(): imgui.set_tooltip("缩放工具 (R)") else: if imgui.button("缩放##scale_tool"): self.app.tool_manager.setCurrentTool("缩放") if scale_active: draw_list = imgui.get_window_draw_list() button_min = imgui.get_item_rect_min() button_max = imgui.get_item_rect_max() draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3))) imgui.same_line() imgui.separator() imgui.same_line() # 工具按钮已移除(导入、保存、播放) def _draw_scene_tree(self): """绘制场景树面板""" # 使用更少的限制性标志,允许docking flags = (imgui.WindowFlags_.no_collapse) with self.app.style_manager.begin_styled_window("场景树", self.app.showSceneTree, flags): self.app.showSceneTree = True # 确保窗口保持打开 imgui.text("场景层级") imgui.separator() # 构建动态场景树 self._build_scene_tree() def _build_scene_tree(self): """构建动态场景树""" # 渲染节点 if imgui.tree_node("渲染"): # 环境光 if hasattr(self.app, 'ambient_light') and self.app.ambient_light: self._draw_scene_node(self.app.ambient_light, "环境光", "light") # 聚光灯 if hasattr(self.app, 'scene_manager') and self.app.scene_manager: if hasattr(self.app.scene_manager, 'Spotlight') and self.app.scene_manager.Spotlight: for i, spotlight in enumerate(self.app.scene_manager.Spotlight): self._draw_scene_node(spotlight, f"聚光灯_{i+1}", "light") if hasattr(self.app.scene_manager, 'Pointlight') and self.app.scene_manager.Pointlight: for i, pointlight in enumerate(self.app.scene_manager.Pointlight): self._draw_scene_node(pointlight, f"点光源_{i+1}", "light") # 地板 if hasattr(self.app, 'ground') and self.app.ground: self._draw_scene_node(self.app.ground, "地板", "geometry") imgui.tree_pop() # 相机节点 if imgui.tree_node("相机"): if hasattr(self.app, 'camera') and self.app.camera: self._draw_scene_node(self.app.camera, "主相机", "camera") imgui.tree_pop() # 3D模型节点 if imgui.tree_node("模型"): models = [] if hasattr(self.app, 'scene_manager') and self.app.scene_manager and hasattr(self.app.scene_manager, 'models'): models.extend([m for m in self.app.scene_manager.models if m and not m.isEmpty()]) # SSBO模式下,模型可能不在 scene_manager.models 中,补充显示 ssbo_editor.model ssbo_editor = getattr(self.app, "ssbo_editor", None) ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None if ssbo_model and not ssbo_model.isEmpty() and ssbo_model.hasParent() and ssbo_model not in models: models.append(ssbo_model) if models: for i, model in enumerate(models): self._draw_scene_node(model, model.getName() or f"模型_{i+1}", "model") else: imgui.text("(空)") imgui.tree_pop() # if imgui.tree_node("GUI元素"): # if hasattr(self,'gui_manager') and self.app.gui_manager and hasattr(self.app.gui_manager,'gui_elements'): # if self.app.gui_manager.gui_elements: # for gui_element in self.app.gui_manager.gui_elements: # if gui_element and hasattr(gui_element,'node'): # gui_type = getattr(gui_element,'gui_type','GUI_UNKNOWN') # display_name = getattr(gui_element,'name',gui_type) # self._draw_scene_node(gui_element.node,display_name,"gui",gui_type) # else: # imgui.text("(空)") # else: # imgui.text("(空)") # imgui.tree_pop() # LUI元素节点 if imgui.tree_node("GUI元素"): if hasattr(self.app, 'lui_manager') and self.app.lui_manager.lui_enabled: self.app.lui_manager.draw_component_tree() imgui.tree_pop() # if imgui.tree_node("LUI元素"): # if hasattr(self.app, 'lui_manager') and self.app.lui_manager.lui_enabled: # if self.app.lui_manager.components: # for comp in self.app.lui_manager.components: # if 'node' in comp: # self._draw_scene_node(comp['node'], comp['name'], "ui") # if self.app.lui_manager.canvases: # for canvas in self.app.lui_manager.canvases: # if imgui.tree_node(f"Canvas: {canvas['name']}"): # # 实际上组件已经在 node 下了,可以通过 _draw_scene_node 递归显示 # # 但为了清晰,我们可以手动列出或者依赖递归 # self._draw_scene_node(canvas['node'], canvas['name'], "geometry") # imgui.tree_pop() # else: # imgui.text("(空)") # else: # imgui.text("(空)") # imgui.tree_pop() def _draw_scene_node(self, node, name, node_type, gui_subtype=None): """绘制单个场景节点""" if not node or node.isEmpty(): return # 检查是否被选中 is_selected = (hasattr(self.app, 'selection') and self.app.selection and hasattr(self.app.selection, 'selectedNode') and self.app.selection.selectedNode == node) # 节点可见性 is_visible = node.is_hidden() == False # 设置选择颜色 if is_selected: imgui.push_style_color(imgui.Col_.text, (0.2, 0.6, 1.0, 1.0)) node_open = False try: # 显示节点 node_open = imgui.tree_node(name) # 处理节点选择 if imgui.is_item_clicked(): 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 imgui.is_item_hovered() and imgui.is_mouse_clicked(1): self._show_node_context_menu(node, name, node_type) # 显示节点属性 imgui.same_line() if is_visible: imgui.text_colored((0.5, 1.0, 0.5, 1.0), "可见") else: imgui.text_colored((0.5, 0.5, 0.5, 1.0), "隐藏") if node_open: # SSBO模型使用虚拟层级显示(避免 flatten 后真实子级丢失) if self._draw_ssbo_virtual_children(node): pass elif node.getNumChildren() > 0: for i in range(node.getNumChildren()): child = node.getChild(i) if not child or child.isEmpty(): continue child_name = child.getName() or f"child_{i+1}" # 过滤碰撞辅助节点,避免污染场景树 if child_name.startswith("modelCollision_"): continue self._draw_scene_node(child, child_name, node_type) # tree_pop moved to finally except Exception as e: print(f"绘制场景节点时出错: {e}") finally: if node_open: imgui.tree_pop() # Ensure style stack is balanced. if is_selected: imgui.pop_style_color() def _draw_ssbo_virtual_children(self, node): """Draw SSBO controller nodes as virtual children for scene tree.""" ssbo_editor = getattr(self.app, "ssbo_editor", None) if not ssbo_editor: return False model = getattr(ssbo_editor, "model", None) controller = getattr(ssbo_editor, "controller", None) if not model or model != node or not controller: return False tree_root = controller.get_virtual_hierarchy() if hasattr(controller, "get_virtual_hierarchy") else None if not tree_root or not tree_root.get("children"): imgui.text_disabled("(无可用子节点)") return True for name in sorted(tree_root["children"].keys()): child = tree_root["children"][name] self._draw_ssbo_virtual_tree_node(ssbo_editor, child, "ssbo_root") return True def _draw_ssbo_virtual_tree_node(self, ssbo_editor, tree_node, unique_id_prefix, depth=0): """Recursively draw virtual SSBO hierarchy in scene tree.""" if not tree_node: return path = tree_node.get("path", "") display = tree_node.get("display_name") or tree_node.get("name") or path leaf_key = tree_node.get("leaf_key") group_key = tree_node.get("group_key") children = tree_node.get("children", {}) or {} label = f"{display}##{unique_id_prefix}_{path}" # Leaf: selectable to trigger SSBO selection. if not children and leaf_key: is_selected = (getattr(ssbo_editor, "selected_name", None) == leaf_key) if imgui.selectable(label, is_selected)[0]: ssbo_editor.select_node(leaf_key) if hasattr(self.app, "lui_manager"): self.app.lui_manager.selected_index = -1 return # Non-leaf: tree node only for hierarchy display. opened = imgui.tree_node(label) # Clicking non-leaf row selects its aggregate group so parent transform affects children. if group_key and imgui.is_item_clicked(0): ssbo_editor.select_node(group_key) if hasattr(self.app, "lui_manager"): self.app.lui_manager.selected_index = -1 if opened: # If this node is also a selectable leaf, render selectable entry first. if group_key: is_group_selected = (getattr(ssbo_editor, "selected_name", None) == group_key) if imgui.selectable(f"[整体] {display}##group_{unique_id_prefix}_{path}", is_group_selected)[0]: ssbo_editor.select_node(group_key) if hasattr(self.app, "lui_manager"): self.app.lui_manager.selected_index = -1 if leaf_key: is_selected = (getattr(ssbo_editor, "selected_name", None) == leaf_key) if imgui.selectable(f"[节点] {display}##leaf_{unique_id_prefix}_{path}", is_selected)[0]: ssbo_editor.select_node(leaf_key) if hasattr(self.app, "lui_manager"): self.app.lui_manager.selected_index = -1 for child_name in sorted(children.keys()): self._draw_ssbo_virtual_tree_node( ssbo_editor, children[child_name], unique_id_prefix, depth + 1, ) imgui.tree_pop() def _show_node_context_menu(self, node, name, node_type): """显示节点右键菜单""" self.app._context_menu_node = True self.app._context_menu_target = node def _draw_resource_manager(self): """绘制资源管理器面板""" # 使用面板类型的窗口标志,支持docking flags = self.app.style_manager.get_window_flags("panel") with self.app.style_manager.begin_styled_window("资源管理器", self.app.showResourceManager, flags): self.app.showResourceManager = True # 确保窗口保持打开 # 获取资源管理器实例 rm = self.app.resource_manager # 工具栏 imgui.text("文件浏览器") imgui.separator() # 导航按钮 if imgui.button("◀"): rm.navigate_back() imgui.same_line() if imgui.button("▶"): rm.navigate_forward() imgui.same_line() if imgui.button("▲"): rm.navigate_up() imgui.same_line() if imgui.button("主页"): rm.navigate_to(rm.project_root / "Resources") imgui.same_line() if imgui.button("刷新"): rm.force_refresh() # 自动刷新开关 imgui.same_line() changed, rm.auto_refresh_enabled = imgui.checkbox("自动刷新", rm.auto_refresh_enabled) if changed: rm.set_auto_refresh(rm.auto_refresh_enabled) imgui.same_line() imgui.text(" ") imgui.same_line() # 路径输入框 changed, new_path = imgui.input_text("路径", str(rm.current_path), 256) if changed: try: rm.navigate_to(Path(new_path)) except: pass # 搜索框 changed, rm.search_filter = imgui.input_text("搜索", rm.search_filter, 256) imgui.separator() # 检查自动刷新 if rm.refresh_if_needed(): # 目录内容发生变化,可以在这里添加通知逻辑 pass # 获取目录内容 dirs, files = rm.get_directory_contents(rm.current_path) # 显示目录 for dir_path in dirs: if not rm.should_show_file(dir_path): continue # 目录图标和名称 icon_name = rm.get_file_icon(dir_path.name, is_folder=True) node_open = False # 检查是否被选中 is_selected = dir_path in rm.selected_files # 使用TreeNode来显示目录 if is_selected: imgui.push_style_color(imgui.Col_.header, (100/255, 150/255, 200/255, 1.0)) # 尝试加载PNG图标 icon_texture = None try: # 直接使用图标名称,load_icon会自动添加.png icon_texture = self.app.style_manager.load_icon(f"file_types/{icon_name}") except: pass if icon_texture: # 使用PNG图标 imgui.image(icon_texture, (16, 16)) imgui.same_line() node_open = imgui.tree_node(f"{dir_path.name}") else: # 回退到文本标识符 node_open = imgui.tree_node(f"[{icon_name.upper()}]{dir_path.name}") if is_selected: imgui.pop_style_color() # 处理选择 if imgui.is_item_clicked(): if imgui.get_io().key_ctrl: # 多选模式 if is_selected: rm.selected_files.discard(dir_path) else: rm.selected_files.add(dir_path) else: # 单选模式 rm.selected_files.clear() rm.selected_files.add(dir_path) rm.focused_file = dir_path # 双击导航 if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): rm.navigate_to(dir_path) # 右键菜单 if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): rm.show_context_menu = True rm.context_menu_file = dir_path rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y) # 如果节点展开,显示子内容 if node_open: # 获取子目录内容 subdirs, subfiles = rm.get_directory_contents(dir_path) # 显示子目录 for subdir in subdirs: if not rm.should_show_file(subdir): continue # 初始化变量 subicon_name = "folder" sub_is_selected = False # 获取子目录图标名称 subicon_name = rm.get_file_icon(subdir.name, is_folder=True) sub_is_selected = subdir in rm.selected_files # 尝试加载PNG图标 subicon_texture = None try: subicon_texture = self.app.style_manager.load_icon(f"file_types/{subicon_name}") except: pass if subicon_texture: # 使用PNG图标 imgui.image(subicon_texture, (16, 16)) imgui.same_line() sub_node_open = imgui.tree_node(f" {subdir.name}") else: # 回退到文本标识符 sub_node_open = imgui.tree_node(f" [{subicon_name.upper()}]{subdir.name}") if sub_is_selected: imgui.pop_style_color() # 处理子目录的选择 if imgui.is_item_clicked(): if imgui.get_io().key_ctrl: if sub_is_selected: rm.selected_files.discard(subdir) else: rm.selected_files.add(subdir) else: rm.selected_files.clear() rm.selected_files.add(subdir) rm.focused_file = subdir # 双击子目录导航 if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): rm.navigate_to(subdir) # 右键菜单 if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): rm.show_context_menu = True rm.context_menu_file = subdir rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y) if sub_node_open: imgui.tree_pop() # 显示子文件 for subfile in subfiles: if not rm.should_show_file(subfile): continue subicon_name = rm.get_file_icon(subfile.name) sub_is_selected = subfile in rm.selected_files # 尝试加载PNG图标 subicon_texture = None try: subicon_texture = self.app.style_manager.load_icon(f"file_types/{subicon_name}") except: pass if subicon_texture: # 使用PNG图标 imgui.image(subicon_texture, (16, 16)) imgui.same_line() selected = imgui.selectable(f" {subfile.name}", sub_is_selected) else: # 回退到文本标识符 selected = imgui.selectable(f" [{subicon_name.upper()}] {subfile.name}", sub_is_selected) # 处理子文件的选择 if selected: if imgui.get_io().key_ctrl: if sub_is_selected: rm.selected_files.discard(subfile) else: rm.selected_files.add(subfile) else: rm.selected_files.clear() rm.selected_files.add(subfile) rm.focused_file = subfile # 双击子文件操作 if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): if subfile.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']: self.app._import_model_for_runtime(str(subfile)) self.app.add_info_message(f"正在导入模型: {subfile.name}") else: rm.open_file(subfile) # 右键菜单 if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): rm.show_context_menu = True rm.context_menu_file = subfile rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y) # 只有在节点展开时才调用tree_pop if node_open: imgui.tree_pop() # 处理拖拽开始 if imgui.is_item_active() and imgui.is_item_hovered(): if imgui.is_mouse_dragging(0): # 开始拖拽 drag_files = list(rm.selected_files) if rm.selected_files else [file_path] rm.start_drag(drag_files) self.app.is_dragging = True self.app.show_drag_overlay = True # 双击打开文件 if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): # 检查是否是支持的3D模型格式 if file_path.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']: # 导入3D模型 self.app.add_info_message(f"正在导入模型: {file_path.name}") self.app._import_model_for_runtime(str(file_path)) else: # 使用系统默认程序打开 rm.open_file(file_path) # 右键菜单 if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): rm.show_context_menu = True rm.context_menu_file = file_path rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y) # 右键菜单 if rm.show_context_menu and rm.context_menu_file: imgui.set_next_window_pos((rm.context_menu_position[0], rm.context_menu_position[1])) with imgui_ctx.begin_popup("context_menu", imgui.WindowFlags_.no_title_bar | imgui.WindowFlags_.no_resize | imgui.WindowFlags_.always_auto_resize) as popup: if popup: if rm.context_menu_file.is_dir(): if imgui.menu_item("打开"): rm.navigate_to(rm.context_menu_file) imgui.separator() if imgui.menu_item("重命名"): print(f"重命名文件夹: {rm.context_menu_file.name}") if imgui.menu_item("删除"): print(f"删除文件夹: {rm.context_menu_file.name}") else: if imgui.menu_item("打开"): rm.open_file(rm.context_menu_file) imgui.separator() if imgui.menu_item("导入到场景"): if rm.context_menu_file.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']: self.app.add_info_message(f"正在导入模型: {rm.context_menu_file.name}") self.app._import_model_for_runtime(str(rm.context_menu_file)) if imgui.menu_item("重命名"): print(f"重命名文件: {rm.context_menu_file.name}") if imgui.menu_item("删除"): print(f"删除文件: {rm.context_menu_file.name}") imgui.separator() if imgui.menu_item("复制路径"): imgui.set_clipboard_text(str(rm.context_menu_file)) self.app.add_info_message("路径已复制到剪贴板") if imgui.menu_item("在文件管理器中显示"): import platform import subprocess if platform.system() == "Windows": subprocess.run(["explorer", "/select,", str(rm.context_menu_file)]) elif platform.system() == "Darwin": subprocess.run(["open", "-R", str(rm.context_menu_file)]) else: subprocess.run(["xdg-open", str(rm.context_menu_file.parent)]) # 如果点击其他地方,关闭菜单 if imgui.is_mouse_clicked(0) or imgui.is_mouse_clicked(1): if not imgui.is_window_hovered(): rm.show_context_menu = False rm.context_menu_file = None def _draw_property_panel(self): """绘制属性面板""" # 使用面板类型的窗口标志,支持docking flags = self.app.style_manager.get_window_flags("panel") with self.app.style_manager.begin_styled_window("属性面板", self.app.showPropertyPanel, flags): self.app.showPropertyPanel = True # 确保窗口保持打开 # --- LUI Component Properties --- # 优先检查 LUI 组件选择 if hasattr(self.app, 'lui_manager'): lui_selected_index = getattr(self.app.lui_manager, "selected_index", -1) if lui_selected_index >= 0 and self.app.lui_manager.luiFunction: self.app.lui_manager.luiFunction._draw_component_properties( self.app.lui_manager, lui_selected_index ) return # --- Scene Node Properties --- # 获取当前选中的节点 selected_node = None if hasattr(self.app, 'selection') and self.app.selection and hasattr(self.app.selection, 'selectedNode'): selected_node = self.app.selection.selectedNode # SSBO mode may select a proxy node for gizmo operations. # Resolve proxy back to a real scene node so property panel stays meaningful. try: if (selected_node and not selected_node.isEmpty() and selected_node.hasTag("is_ssbo_proxy")): ssbo_editor = getattr(self.app, "ssbo_editor", None) controller = getattr(ssbo_editor, "controller", None) if ssbo_editor else None if ssbo_editor and controller: resolved = None if getattr(ssbo_editor, "selected_ids", None): first_gid = ssbo_editor.selected_ids[0] key = controller.id_to_name.get(first_gid) if key: resolved = controller.key_to_node.get(key) if (resolved is None or resolved.isEmpty()) and getattr(ssbo_editor, "selected_name", None): resolved = controller.key_to_node.get(ssbo_editor.selected_name) if resolved and not resolved.isEmpty(): selected_node = resolved except Exception: pass if selected_node and not selected_node.isEmpty(): self._draw_node_properties(selected_node) else: # 无选中对象时显示提示(模仿Qt版本的空状态样式) imgui.spacing() imgui.spacing() # 居中显示提示信息 window_width = imgui.get_window_width() text_width = 200 # 估算文本宽度 text_pos_x = (window_width - text_width) / 2 imgui.set_cursor_pos_x(text_pos_x) imgui.text_colored((0.5, 0.5, 0.5, 1.0), "🔍 未选择任何对象") imgui.set_cursor_pos_x(text_pos_x - 20) imgui.text("请从场景树中选择一个对象") imgui.set_cursor_pos_x(text_pos_x + 10) imgui.text("以查看其属性") imgui.spacing() imgui.spacing() # 添加一些分隔线和装饰 imgui.separator() # 显示快速提示 imgui.text("💡 快速提示:") imgui.bullet_text("单击场景树中的对象进行选择") imgui.bullet_text("使用 F 键快速聚焦到选中对象") imgui.bullet_text("使用 Delete 键删除选中对象") def _draw_node_properties(self, node): """绘制节点属性""" if not node or node.isEmpty(): # 停止变换监控 self.app.stop_transform_monitoring() return # 检查是否需要重新启动变换监控 if self.app._monitored_node != node: self.app.stop_transform_monitoring() self.app.start_transform_monitoring(node) # 获取节点基本信息 node_name = node.getName() or "未命名节点" node_type = self.app._get_node_type_from_node(node) # 添加一些间距,模仿Qt版本的布局 imgui.spacing() # 物体名称组(使用Qt版本的样式) if imgui.collapsing_header("物体名称"): # 第一行:可见性复选框和名称输入 user_visible = node.getPythonTag("user_visible") if user_visible is None: user_visible = True node.setPythonTag("user_visible", True) # 可见性复选框(模仿Qt版本的样式) changed, is_visible = imgui.checkbox("##visibility", user_visible) if changed: node.setPythonTag("user_visible", is_visible) if is_visible: node.show() else: node.hide() imgui.same_line() imgui.text("可见") imgui.same_line() imgui.spacing() imgui.same_line() # 名称输入框(模仿Qt版本的样式) imgui.text("名称:") imgui.same_line() changed, new_name = imgui.input_text("##name", node_name, 256) if changed and hasattr(self.app, 'selection'): # 更新场景树中的名称 self.app._update_node_name(node, new_name) # 添加分隔线 imgui.separator() # 状态徽章(模仿Qt版本的徽章样式) self.app._draw_status_badges(node) imgui.spacing() # 变换属性组 if imgui.collapsing_header("变换 Transform"): self.app._draw_transform_properties(node) # 根据节点类型显示特定属性组 if node_type == "GUI元素": if imgui.collapsing_header("GUI信息"): self.app._draw_gui_properties(node) elif node_type == "光源": if imgui.collapsing_header("光源属性"): self.app._draw_light_properties(node) elif node_type == "模型": if imgui.collapsing_header("模型属性"): self.app._draw_model_properties(node) # 动画控制组(只对模型显示) if imgui.collapsing_header("动画控制"): self.app._draw_animation_properties(node) # 外观属性组(通用) if imgui.collapsing_header("外观属性"): self.app._draw_appearance_properties(node) # 碰撞检测组 if imgui.collapsing_header("碰撞检测"): self.app._draw_collision_properties(node) # 操作按钮组 if imgui.collapsing_header("操作"): self.app._draw_property_actions(node) def _draw_status_badges(self, node): """绘制状态徽章(模仿Qt版本的徽章样式)""" imgui.text("状态标签: ") # 可见性状态徽章 is_visible = not node.is_hidden() visibility_color = (0.176, 1.0, 0.769, 1.0) if is_visible else (0.953, 0.616, 0.471, 1.0) visibility_text = "可见" if is_visible else "隐藏" imgui.same_line() imgui.text_colored(visibility_color, f"[{visibility_text}]") # 节点类型徽章 node_type = self._get_node_type_from_node(node) type_colors = { "GUI元素": (0.188, 0.404, 0.753, 1.0), # 主题蓝色 "光源": (1.0, 0.8, 0.2, 1.0), # 黄色 "模型": (0.6, 0.8, 1.0, 1.0), # 浅蓝色 "相机": (0.8, 0.8, 0.2, 1.0), # 橙色 "几何体": (0.5, 0.5, 0.5, 1.0), # 灰色 } if node_type in type_colors: imgui.same_line() imgui.text_colored(type_colors[node_type], f"[{node_type}]") # 功能性徽章 badges = [] # 碰撞体徽章 has_collision = hasattr(node, 'getChild') and any('Collision' in child.getName() for child in node.getChildren() if child.getName()) if has_collision: badges.append(("碰撞", (0.2, 0.4, 0.8, 1.0))) # 蓝色 # 脚本徽章 has_script = hasattr(node, 'getPythonTag') and node.getPythonTag('script') if has_script: badges.append(("脚本", (0.8, 0.4, 0.8, 1.0))) # 紫色 # 动画徽章(优化检测逻辑,避免重复创建Actor) has_animation = False if node_type == "模型": # 只对模型类型进行动画检测 model_path = node.getTag("model_path") if node.hasTag("model_path") else "" likely_anim_format = bool(model_path and model_path.lower().endswith(('.glb', '.gltf', '.fbx', '.bam', '.egg'))) # 优先使用场景标签(导入/加载时会写入) if node.hasTag("has_animations"): has_animation = node.getTag("has_animations").lower() == "true" # 再做轻量结构检测(不依赖 Actor) if not has_animation: try: has_character = node.findAllMatches("**/+Character").getNumPaths() > 0 has_bundle = node.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0 has_animation = has_character or has_bundle if has_animation: node.setTag("has_animations", "true") node.setTag("can_create_actor_from_memory", "true") except Exception: pass # 最后才尝试 Actor 检测(只缓存“有动画”,避免把失败结果永久缓存) cached_result = node.getPythonTag('animation') if cached_result is True: has_animation = True elif not has_animation and likely_anim_format: try: actor = self._getActor(node) if actor and actor.getAnimNames(): has_animation = True node.setTag("has_animations", "true") node.setPythonTag('animation', True) print(f"[动画检测] {node.getName()}: 有动画") except Exception as e: print(f"动画检测失败: {e}") elif cached_result is False and not likely_anim_format: has_animation = False else: # 对于非模型类型,检查已有的动画标签 has_animation = hasattr(node, 'getPythonTag') and node.getPythonTag('animation') if has_animation: badges.append(("动画", (0.4, 0.8, 0.4, 1.0))) # 绿色 # 材质徽章 has_material = hasattr(node, 'getMaterial') and node.getMaterial() if has_material: badges.append(("材质", (0.8, 0.6, 0.2, 1.0))) # 金色 # 绘制功能性徽章 for badge_text, badge_color in badges: imgui.same_line() imgui.text_colored(badge_color, f"[{badge_text}]") # 如果没有特殊徽章,显示默认状态 if not badges: imgui.same_line() imgui.text_colored((0.5, 0.5, 0.5, 1.0), "[标准对象]") def _draw_transform_properties(self, node): """绘制变换属性""" # 位置组 if imgui.collapsing_header("位置 Position"): # 相对位置 imgui.text("相对位置") pos = node.getPos() # X坐标 changed, new_x = imgui.input_float("X##pos_x", pos.x, 0.1, 1.0, "%.3f") if changed: node.setX(new_x) # Y坐标 changed, new_y = imgui.input_float("Y##pos_y", pos.y, 0.1, 1.0, "%.3f") if changed: node.setY(new_y) # Z坐标 changed, new_z = imgui.input_float("Z##pos_z", pos.z, 0.1, 1.0, "%.3f") if changed: node.setZ(new_z) # 世界位置 imgui.text("世界位置") world_pos = node.getPos(self.render) imgui.text(f"世界 X: {world_pos.x:.3f}") imgui.text(f"世界 Y: {world_pos.y:.3f}") imgui.text(f"世界 Z: {world_pos.z:.3f}") # 位置操作按钮 if imgui.button("重置位置##reset_pos"): node.setPos(0, 0, 0) imgui.same_line() if imgui.button("复制位置##copy_pos"): self._clipboard_pos = (pos.x, pos.y, pos.z) imgui.same_line() if imgui.button("粘贴位置##paste_pos") and hasattr(self, '_clipboard_pos'): node.setPos(self._clipboard_pos[0], self._clipboard_pos[1], self._clipboard_pos[2]) # 旋转组 if imgui.collapsing_header("旋转 Rotation"): hpr = node.getHpr() # HPR旋转 imgui.text("HPR 旋转 (度)") changed, new_h = imgui.input_float("H##rot_h", hpr.x, 1.0, 10.0, "%.1f") if changed: node.setH(new_h) changed, new_p = imgui.input_float("P##rot_p", hpr.y, 1.0, 10.0, "%.1f") if changed: node.setP(new_p) changed, new_r = imgui.input_float("R##rot_r", hpr.z, 1.0, 10.0, "%.1f") if changed: node.setR(new_r) # 旋转操作按钮 if imgui.button("重置旋转##reset_rot"): node.setHpr(0, 0, 0) imgui.same_line() if imgui.button("随机旋转##random_rot"): import random node.setHpr(random.randint(0, 360), random.randint(0, 360), random.randint(0, 360)) # 缩放组 if imgui.collapsing_header("缩放 Scale"): scale = node.getScale() # XYZ缩放 imgui.text("XYZ 缩放") changed, new_sx = imgui.input_float("X##scale_x", scale.x, 0.1, 1.0, "%.3f") if changed: node.setSx(new_sx) changed, new_sy = imgui.input_float("Y##scale_y", scale.y, 0.1, 1.0, "%.3f") if changed: node.setSy(new_sy) changed, new_sz = imgui.input_float("Z##scale_z", scale.z, 0.1, 1.0, "%.3f") if changed: node.setSz(new_sz) # 统一缩放 if imgui.button("统一缩放##uniform_scale"): uniform_scale = (scale.x + scale.y + scale.z) / 3.0 node.setScale(uniform_scale, uniform_scale, uniform_scale) imgui.same_line() if imgui.button("重置缩放##reset_scale"): node.setScale(1, 1, 1) imgui.same_line() if imgui.button("翻倍##double_scale"): node.setScale(scale.x * 2, scale.y * 2, scale.z * 2) def _draw_gui_properties(self, node): """绘制GUI元素属性""" # 获取GUI元素 gui_element = None if hasattr(node, 'getPythonTag'): gui_element = node.getPythonTag('gui_element') if not gui_element: imgui.text("无GUI元素数据") return # GUI类型信息 gui_type = getattr(gui_element, 'gui_type', 'UNKNOWN') imgui.text(f"GUI类型: {gui_type}") # 基本属性 if imgui.collapsing_header("基本属性"): # 文本内容 (适用于按钮、标签等) if hasattr(gui_element, 'text'): changed, new_text = imgui.input_text("文本内容", gui_element.text, 256) if changed and hasattr(self, 'gui_manager'): self.gui_manager.editGUIElement(gui_element, 'text', new_text) # GUI ID gui_id = getattr(gui_element, 'id', '') changed, new_id = imgui.input_text("GUI ID", gui_id, 64) if changed and hasattr(self, 'gui_manager'): gui_element.id = new_id # 变换属性 if imgui.collapsing_header("变换属性"): # 位置 pos = gui_element.getPos() imgui.text("位置") if gui_type in ["button", "label", "entry", "2d_image"]: # 2D GUI组件使用屏幕坐标 imgui.text("屏幕坐标") logical_x = pos.getX() / 0.1 logical_z = pos.getZ() / 0.1 changed, new_x = imgui.input_float("X##gui_pos_x", logical_x, 1.0, 10.0, "%.1f") if changed and hasattr(self, 'gui_manager'): gui_element.setX(new_x * 0.1) changed, new_z = imgui.input_float("Y##gui_pos_y", logical_z, 1.0, 10.0, "%.1f") if changed and hasattr(self, 'gui_manager'): gui_element.setZ(new_z * 0.1) else: # 3D GUI组件使用世界坐标 imgui.text("世界坐标") changed, new_x = imgui.input_float("X##gui_world_x", pos.getX(), 0.1, 1.0, "%.3f") if changed and hasattr(self, 'gui_manager'): gui_element.setX(new_x) changed, new_y = imgui.input_float("Y##gui_world_y", pos.getY(), 0.1, 1.0, "%.3f") if changed and hasattr(self, 'gui_manager'): gui_element.setY(new_y) changed, new_z = imgui.input_float("Z##gui_world_z", pos.getZ(), 0.1, 1.0, "%.3f") if changed and hasattr(self, 'gui_manager'): gui_element.setZ(new_z) # 缩放 scale = gui_element.getScale() imgui.text("缩放") changed, new_sx = imgui.input_float("X##gui_scale_x", scale.getX(), 0.1, 1.0, "%.3f") if changed and hasattr(self, 'gui_manager'): gui_element.setSx(new_sx) changed, new_sy = imgui.input_float("Y##gui_scale_y", scale.getY(), 0.1, 1.0, "%.3f") if changed and hasattr(self, 'gui_manager'): gui_element.setSy(new_sy) changed, new_sz = imgui.input_float("Z##gui_scale_z", scale.getZ(), 0.1, 1.0, "%.3f") if changed and hasattr(self, 'gui_manager'): gui_element.setSz(new_sz) # 旋转 hpr = gui_element.getHpr() imgui.text("旋转") changed, new_h = imgui.input_float("H##gui_rot_h", hpr.getX(), 1.0, 10.0, "%.1f") if changed and hasattr(self, 'gui_manager'): gui_element.setH(new_h) changed, new_p = imgui.input_float("P##gui_rot_p", hpr.getY(), 1.0, 10.0, "%.1f") if changed and hasattr(self, 'gui_manager'): gui_element.setP(new_p) changed, new_r = imgui.input_float("R##gui_rot_r", hpr.getZ(), 1.0, 10.0, "%.1f") if changed and hasattr(self, 'gui_manager'): gui_element.setR(new_r) # 外观属性 if imgui.collapsing_header("外观属性"): # 大小 if hasattr(gui_element, 'size'): size = gui_element.size imgui.text("大小") changed, new_w = imgui.input_float("宽度", size[0], 1.0, 10.0, "%.1f") if changed and hasattr(self, 'gui_manager'): new_size = (new_w, size[1]) self.gui_manager.editGUIElement(gui_element, 'size', new_size) changed, new_h = imgui.input_float("高度", size[1], 1.0, 10.0, "%.1f") if changed and hasattr(self, 'gui_manager'): new_size = (size[0], new_h) self.gui_manager.editGUIElement(gui_element, 'size', new_size) # 颜色 if hasattr(gui_element, 'getColor'): try: color = gui_element.getColor() # 确保颜色是有效的 if not color or (hasattr(color, '__len__') and len(color) < 3): color = (1.0, 1.0, 1.0, 1.0) # 默认白色 except: color = (1.0, 1.0, 1.0, 1.0) # 默认白色 imgui.text("颜色") # 获取颜色值 if hasattr(color, 'getX'): # 如果是Panda3D的Vec4对象 r, g, b = color.getX(), color.getY(), color.getZ() else: # 如果是元组或其他格式 r, g, b = color[0], color[1], color[2] changed, new_r = imgui.slider_float("R##gui_color_r", r, 0.0, 1.0) if changed: gui_element.setColor(new_r, g, b, 1.0) changed, new_g = imgui.slider_float("G##gui_color_g", g, 0.0, 1.0) if changed: gui_element.setColor(r, new_g, b, 1.0) changed, new_b = imgui.slider_float("B##gui_color_b", b, 0.0, 1.0) if changed: gui_element.setColor(r, g, new_b, 1.0) # 透明度 imgui.text("透明度") current_alpha = getattr(gui_element, 'alpha', 1.0) changed, new_alpha = imgui.slider_float("Alpha", current_alpha, 0.0, 1.0) if changed: gui_element.alpha = new_alpha if hasattr(gui_element, 'setTransparency'): # 将0.0-1.0范围转换为Panda3D的透明度格式 panda_transparency = int((1.0 - new_alpha) * 255) gui_element.setTransparency(panda_transparency) # 渲染顺序 imgui.text("渲染顺序") current_sort = getattr(gui_element, 'sort', 0) changed, new_sort = imgui.input_int("Sort Order", current_sort) if changed: gui_element.sort = new_sort if hasattr(gui_element, 'setBin'): gui_element.setBin('fixed', new_sort) # 字体设置(适用于文本类型的GUI元素) if gui_type in ["button", "label", "entry"]: imgui.text("字体设置") # 字体选择 current_font = getattr(gui_element, 'font_path', '') if imgui.button(f"字体: {Path(current_font).name if current_font else '默认'}##font_select"): self.show_font_selector( gui_element, 'font_path', current_font, lambda font_path: self._apply_gui_font(gui_element, font_path) ) # 字体大小 current_size = getattr(gui_element, 'font_size', 12) changed, new_size = imgui.slider_float("字体大小", current_size, 8.0, 72.0) if changed: gui_element.font_size = new_size self._apply_gui_font_size(gui_element, new_size) # 字体样式 imgui.text("字体样式") is_bold = getattr(gui_element, 'font_bold', False) changed, new_bold = imgui.checkbox("粗体", is_bold) if changed: gui_element.font_bold = new_bold self._apply_gui_font_style(gui_element) imgui.same_line() is_italic = getattr(gui_element, 'font_italic', False) changed, new_italic = imgui.checkbox("斜体", is_italic) if changed: gui_element.font_italic = new_italic self._apply_gui_font_style(gui_element) def _draw_light_properties(self, node): """绘制光源属性""" imgui.text("光源属性") # 光源颜色 if hasattr(node, 'getColor'): try: color = node.getColor() # 确保颜色是有效的 if not color or len(color) < 3: color = (1.0, 1.0, 1.0, 1.0) # 默认白色 except: color = (1.0, 1.0, 1.0, 1.0) # 默认白色 changed, new_r = imgui.drag_float("颜色 R", color[0], 0.01, 0.0, 1.0) if changed: node.setColor(new_r, color[1], color[2], color[3] if len(color) > 3 else 1.0) changed, new_g = imgui.drag_float("颜色 G", color[1], 0.01, 0.0, 1.0) if changed: node.setColor(color[0], new_g, color[2], color[3] if len(color) > 3 else 1.0) changed, new_b = imgui.drag_float("颜色 B", color[2], 0.01, 0.0, 1.0) if changed: node.setColor(color[0], color[1], new_b, color[3] if len(color) > 3 else 1.0) # 光源强度 imgui.text("光源强度: (暂不支持编辑)") def _draw_model_properties(self, node): """绘制模型属性""" # 获取模型信息 model_path = node.getTag("model_path") if node.hasTag("model_path") else "未知" imgui.text("模型路径:") imgui.same_line() imgui.text_colored((0.7, 0.7, 0.7, 1.0), model_path) # 模型基本信息 imgui.text("模型名称:") imgui.same_line() model_name = node.getName() or "未命名模型" imgui.text_colored((0.7, 0.7, 0.7, 1.0), model_name) # 模型位置信息 imgui.text("位置:") imgui.same_line() pos = node.getPos() imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"X:{pos.x:.2f} Y:{pos.y:.2f} Z:{pos.z:.2f}") # 模型缩放信息 imgui.text("缩放:") imgui.same_line() scale = node.getScale() imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"X:{scale.x:.2f} Y:{scale.y:.2f} Z:{scale.z:.2f}") # 模型旋转信息 imgui.text("旋转:") imgui.same_line() hpr = node.getHpr() imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"H:{hpr.x:.1f}° P:{hpr.y:.1f}° R:{hpr.z:.1f}°") def _draw_animation_properties(self, node): """绘制动画控制属性面板(优化版本,使用缓存避免重复计算)""" anim_node = node try: if hasattr(self, "_resolve_animation_owner_model"): resolved = self._resolve_animation_owner_model(node) if resolved and not resolved.isEmpty(): anim_node = resolved except Exception: pass # 路径兜底:当 anim_node 缺少路径时,从 scene_manager.models 中反查祖先模型 try: needs_path = (not anim_node.hasTag("model_path")) or (not anim_node.getTag("model_path")) if needs_path and hasattr(self, "scene_manager") and self.scene_manager: models = getattr(self.scene_manager, "models", []) for model in list(models): try: if not model or model.isEmpty(): continue if (model == anim_node or model.isAncestorOf(anim_node) or anim_node.isAncestorOf(model)): if model.hasTag("model_path") and model.getTag("model_path"): anim_node.setTag("model_path", model.getTag("model_path")) if model.hasTag("original_path") and model.getTag("original_path"): anim_node.setTag("original_path", model.getTag("original_path")) break if model.hasTag("saved_model_path") and model.getTag("saved_model_path"): anim_node.setTag("model_path", model.getTag("saved_model_path")) break except Exception: continue except Exception: pass # 先刷新一次模型动画标签,避免“导入后未初始化”导致误判 try: if hasattr(self, "scene_manager") and self.scene_manager and hasattr(self.scene_manager, "_processModelAnimations"): self.scene_manager._processModelAnimations(anim_node) except Exception: pass has_animation_tag = anim_node.hasTag("has_animations") and anim_node.getTag("has_animations").lower() == "true" has_animation_nodes = False try: has_animation_nodes = ( anim_node.findAllMatches("**/+Character").getNumPaths() > 0 or anim_node.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0 ) except Exception: pass # 检查是否已经缓存了动画信息 cached_anim_info = anim_node.getPythonTag("cached_anim_info") cached_processed_names = anim_node.getPythonTag("cached_processed_names") # 如果之前缓存的是“格式未知”,但现在已有路径,强制重建缓存 try: now_has_path = anim_node.hasTag("model_path") and bool(anim_node.getTag("model_path")) if now_has_path and isinstance(cached_anim_info, str) and "格式: 未知" in cached_anim_info: cached_anim_info = None cached_processed_names = None anim_node.setPythonTag("cached_anim_info", None) anim_node.setPythonTag("cached_processed_names", None) anim_node.setPythonTag("animation", None) self._clear_animation_cache(anim_node) except Exception: pass # 如果节点已被检测为有动画,但缓存是“无动画”,强制重新检测一次 if (has_animation_tag or has_animation_nodes) and (cached_anim_info == "无动画" or cached_processed_names == []): cached_anim_info = None cached_processed_names = None anim_node.setPythonTag("cached_anim_info", None) anim_node.setPythonTag("cached_processed_names", None) anim_node.setPythonTag("animation", None) # 只有在没有缓存时才进行完整的动画检测和处理 if cached_anim_info is None or cached_processed_names is None: # 获取Actor actor = self._getActor(anim_node) if not actor: if has_animation_tag or has_animation_nodes: imgui.text_colored((1.0, 0.7, 0.3, 1.0), "检测到动画结构,但当前未成功绑定Actor") else: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "此模型不包含动画") return # 获取和分析动画名称 anim_names = actor.getAnimNames() processed_names = self._processAnimationNames(anim_node, anim_names) if not processed_names: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列") # 只在明确无动画时缓存空结果,避免误缓存导致后续无法重试 if not (has_animation_tag or has_animation_nodes): anim_node.setPythonTag("cached_processed_names", []) anim_node.setPythonTag("cached_anim_info", "无动画") return anim_node.setTag("has_animations", "true") anim_node.setPythonTag("animation", True) # 计算并缓存动画信息 format_info = self._getModelFormat(anim_node) animation_info = self._analyzeAnimationQuality(actor, anim_names, format_info) info_text = f"格式: {format_info} | 动画数量: {len(processed_names)}" if animation_info: info_text += f" | {animation_info}" # 缓存结果 anim_node.setPythonTag("cached_anim_info", info_text) anim_node.setPythonTag("cached_processed_names", processed_names) else: # 使用缓存的数据 info_text = cached_anim_info processed_names = cached_processed_names # 如果缓存的空结果,直接返回 if not processed_names: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列") return # 显示动画信息(使用缓存的数据) imgui.text("信息:") imgui.same_line() imgui.text_colored((0.7, 0.7, 0.7, 1.0), info_text) imgui.spacing() # 动画选择下拉框 imgui.text("动画名称:") imgui.same_line() # 获取当前选中的动画 current_anim = anim_node.getPythonTag("selected_animation") valid_original_names = [original_name for _, original_name in processed_names] if current_anim is None or current_anim not in valid_original_names: current_anim = processed_names[0][1] if processed_names else "" anim_node.setPythonTag("selected_animation", current_anim) # 查找当前动画的索引 current_index = 0 for i, (display_name, original_name) in enumerate(processed_names): if original_name == current_anim: current_index = i break # 创建下拉框选项 animation_options = [display_name for display_name, _ in processed_names] changed, new_index = imgui.combo("##animation_combo", current_index, animation_options) if changed and new_index < len(processed_names): selected_display, selected_original = processed_names[new_index] anim_node.setPythonTag("selected_animation", selected_original) print(f"选择动画: {selected_display} (原始名称: {selected_original})") imgui.spacing() # 控制按钮组 imgui.text("控制:") # 播放按钮 if imgui.button("播放##play_animation"): self._playAnimation(anim_node) imgui.same_line() # 暂停按钮 if imgui.button("暂停##pause_animation"): self._pauseAnimation(anim_node) imgui.same_line() # 停止按钮 if imgui.button("停止##stop_animation"): self._stopAnimation(anim_node) imgui.same_line() # 循环按钮 if imgui.button("循环##loop_animation"): self._loopAnimation(anim_node) imgui.spacing() # 播放速度控制 imgui.text("播放速度:") imgui.same_line() # 获取当前速度 current_speed = anim_node.getPythonTag("anim_speed") if current_speed is None: current_speed = 1.0 anim_node.setPythonTag("anim_speed", current_speed) # 速度滑块 changed, new_speed = imgui.slider_float("##anim_speed", current_speed, 0.1, 5.0, "%.1f") if changed: anim_node.setPythonTag("anim_speed", new_speed) self._setAnimationSpeed(anim_node, new_speed) imgui.same_line() imgui.text_colored((0.7, 0.7, 0.7, 1.0), "倍速") def _draw_collision_properties(self, node): """绘制碰撞检测属性""" if not node or node.isEmpty(): return try: # 检查节点是否已有碰撞 has_collision = self._has_collision(node) # 碰撞状态徽章 imgui.text("状态:") imgui.same_line() if has_collision: imgui.text_colored((0.0, 0.8, 0.0, 1.0), "🟢 已启用") else: imgui.text_colored((0.8, 0.0, 0.0, 1.0), "🔴 未启用") imgui.separator() # 碰撞形状选择 imgui.text("碰撞形状:") imgui.same_line() # 碰撞形状选项 collision_shapes = ["球形 (Sphere)", "盒型 (Box)", "胶囊体 (Capsule)", "平面 (Plane)", "自动选择 (Auto)"] # 获取当前形状 current_shape = self._get_current_collision_shape(node) if has_collision else "球形 (Sphere)" # 形状选择下拉框 current_index = collision_shapes.index(current_shape) if current_shape in collision_shapes else 0 changed, selected_index = imgui.combo("##collision_shape", current_index, collision_shapes) if changed: # 始终更新选择的形状 selected_shape = collision_shapes[selected_index] self._selected_collision_shape = selected_shape # 如果已经有碰撞体,询问用户是否要重新创建 if has_collision: print(f"形状已更改为 {selected_shape},点击'移除碰撞'后'添加碰撞'来应用新形状") imgui.separator() # 位置偏移控件 imgui.text("位置偏移:") # 获取当前位置偏移 pos_offset = self._get_collision_position_offset(node) # X位置 changed, new_x = imgui.drag_float("X##collision_pos_x", pos_offset[0], 0.1, -100.0, 100.0, "%.2f") if changed and has_collision: self._update_collision_position(node, 'x', new_x) # Y位置 changed, new_y = imgui.drag_float("Y##collision_pos_y", pos_offset[1], 0.1, -100.0, 100.0, "%.2f") if changed and has_collision: self._update_collision_position(node, 'y', new_y) # Z位置 changed, new_z = imgui.drag_float("Z##collision_pos_z", pos_offset[2], 0.1, -100.0, 100.0, "%.2f") if changed and has_collision: self._update_collision_position(node, 'z', new_z) # 形状特定参数(始终显示,但根据状态启用/禁用) shape_type = self._get_current_collision_shape_type(node) self._draw_shape_specific_parameters(node, shape_type, has_collision) imgui.separator() # 操作按钮 if has_collision: # 显示/隐藏碰撞体按钮 is_visible = self._is_collision_visible(node) visibility_text = "隐藏碰撞体" if is_visible else "显示碰撞体" if imgui.button(visibility_text): self._toggle_collision_visibility(node) imgui.same_line() # 移除碰撞按钮 if imgui.button("移除碰撞"): self._remove_collision_from_node(node) else: # 添加碰撞按钮 if imgui.button("添加碰撞"): self._add_collision_to_node(node) imgui.separator() # 碰撞检测触发模式 imgui.text("触发模式:") # 自动检测开关 auto_enabled = self.collision_manager.model_collision_enabled if hasattr(self, 'collision_manager') else False changed, new_auto = imgui.checkbox("自动检测", auto_enabled) if changed and hasattr(self, 'collision_manager'): self.collision_manager.enableModelCollisionDetection(new_auto, 0.1, 0.5) imgui.same_line() # 手动检测按钮 if imgui.button("立即检测"): if hasattr(self, 'collision_manager'): self._manual_collision_detection() except Exception as e: print(f"绘制碰撞属性失败: {e}") import traceback traceback.print_exc() def _draw_shape_specific_parameters(self, node, shape_type, has_collision=True): """绘制形状特定参数""" try: if shape_type == "sphere": self._draw_sphere_parameters(node, has_collision) elif shape_type == "box": self._draw_box_parameters(node, has_collision) elif shape_type == "capsule": self._draw_capsule_parameters(node, has_collision) elif shape_type == "plane": self._draw_plane_parameters(node, has_collision) except Exception as e: print(f"绘制形状参数失败: {e}") def _draw_sphere_parameters(self, node, has_collision=True): """绘制球形参数""" try: imgui.text("球形参数:") imgui.same_line() # 获取当前半径 radius = self._get_sphere_radius(node) # 半径调整 changed, new_radius = imgui.drag_float("半径##sphere_radius", radius, 0.1, 0.1, 100.0, "%.2f") if changed and has_collision: self._update_sphere_radius(node, new_radius) except Exception as e: print(f"绘制球形参数失败: {e}") def _draw_box_parameters(self, node, has_collision=True): """绘制盒型参数""" try: imgui.text("盒型参数:") # 获取当前尺寸 size = self._get_box_size(node) # 尺寸调整 changed, new_x = imgui.drag_float("长度##box_length", size[0], 0.1, 0.1, 100.0, "%.2f") if changed and has_collision: self._update_box_size(node, 'x', new_x) changed, new_y = imgui.drag_float("宽度##box_width", size[1], 0.1, 0.1, 100.0, "%.2f") if changed and has_collision: self._update_box_size(node, 'y', new_y) changed, new_z = imgui.drag_float("高度##box_height", size[2], 0.1, 0.1, 100.0, "%.2f") if changed and has_collision: self._update_box_size(node, 'z', new_z) except Exception as e: print(f"绘制盒型参数失败: {e}") def _draw_capsule_parameters(self, node, has_collision=True): """绘制胶囊体参数""" try: imgui.text("胶囊体参数:") # 获取当前参数 radius = self._get_capsule_radius(node) height = self._get_capsule_height(node) # 半径调整 changed, new_radius = imgui.drag_float("半径##capsule_radius", radius, 0.1, 0.1, 100.0, "%.2f") if changed and has_collision: self._update_capsule_radius(node, new_radius) # 高度调整 changed, new_height = imgui.drag_float("高度##capsule_height", height, 0.1, 0.1, 100.0, "%.2f") if changed and has_collision: self._update_capsule_height(node, new_height) except Exception as e: print(f"绘制胶囊体参数失败: {e}") def _draw_plane_parameters(self, node, has_collision=True): """绘制平面参数""" try: imgui.text("平面参数:") # 获取当前法向量 normal = self._get_plane_normal(node) # 法向量调整 changed, new_x = imgui.drag_float("法向量 X##plane_normal_x", normal[0], 0.1, -1.0, 1.0, "%.2f") if changed and has_collision: self._update_plane_normal(node, 'x', new_x) changed, new_y = imgui.drag_float("法向量 Y##plane_normal_y", normal[1], 0.1, -1.0, 1.0, "%.2f") if changed and has_collision: self._update_plane_normal(node, 'y', new_y) changed, new_z = imgui.drag_float("法向量 Z##plane_normal_z", normal[2], 0.1, -1.0, 1.0, "%.2f") if changed and has_collision: self._update_plane_normal(node, 'z', new_z) except Exception as e: print(f"绘制平面参数失败: {e}") def _draw_property_actions(self, node): """绘制属性操作按钮""" # 重置变换 if imgui.button("重置变换"): node.setPos(0, 0, 0) node.setHpr(0, 0, 0) node.setScale(1, 1, 1) imgui.same_line() # 切换可见性 is_visible = not node.is_hidden() visibility_text = "隐藏" if is_visible else "显示" if imgui.button(visibility_text): if is_visible: node.hide() else: node.show() imgui.same_line() # 聚焦到对象 if imgui.button("聚焦"): if hasattr(self, 'selection') and self.selection: self.selection.focusCameraOnSelectedNodeAdvanced() # 删除对象 imgui.same_line() if imgui.button("删除"): if hasattr(self, 'selection') and self.selection: self.selection.deleteSelectedNode() def _draw_appearance_properties(self, node): """绘制外观属性""" # 颜色属性 if hasattr(node, 'getColor'): imgui.text("颜色") try: color = node.getColor() # 确保颜色是有效的 if not color or len(color) < 3: color = (1.0, 1.0, 1.0, 1.0) # 默认白色 except: color = (1.0, 1.0, 1.0, 1.0) # 默认白色 # 颜色滑块 changed, new_r = imgui.slider_float("R##color_r", color[0], 0.0, 1.0) if changed: new_color = (new_r, color[1], color[2], color[3] if len(color) > 3 else 1.0) node.setColor(new_color) color = new_color changed, new_g = imgui.slider_float("G##color_g", color[1], 0.0, 1.0) if changed: new_color = (color[0], new_g, color[2], color[3] if len(color) > 3 else 1.0) node.setColor(new_color) color = new_color changed, new_b = imgui.slider_float("B##color_b", color[2], 0.0, 1.0) if changed: new_color = (color[0], color[1], new_b, color[3] if len(color) > 3 else 1.0) node.setColor(new_color) color = new_color # 只有当颜色有alpha通道时才显示alpha滑块 if len(color) > 3: changed, new_a = imgui.slider_float("A##color_a", color[3], 0.0, 1.0) if changed: new_color = (color[0], color[1], color[2], new_a) node.setColor(new_color) color = new_color # 颜色预览和选择器 imgui.text("颜色预览") color_with_alpha = (color[0], color[1], color[2], color[3] if len(color) > 3 else 1.0) if imgui.color_button("颜色预览##preview", color_with_alpha, 0, (100, 30)): # 点击颜色按钮打开颜色选择器 self.show_color_picker(node, 'color', color_with_alpha) imgui.same_line() if imgui.button("选择颜色##color_picker_btn"): self.show_color_picker(node, 'color', (color.x, color.y, color.z, color.w)) # 透明度 if hasattr(node, 'setTransparency') and hasattr(node, 'getTransparency'): imgui.text("透明度") current_transparency = node.getTransparency() # 将当前的透明度值转换为0.0-1.0范围用于显示 display_transparency = 1.0 - current_transparency if current_transparency <= 1 else 0.0 changed, new_transparency = imgui.slider_float("透明度", display_transparency, 0.0, 1.0) if changed: # 将0.0-1.0范围转换回Panda3D的透明度格式 panda_transparency = int((1.0 - new_transparency) * 255) node.setTransparency(panda_transparency) # 材质属性 self._draw_material_properties(node) # 渲染状态 imgui.text("渲染状态") if imgui.button("应用材质"): self._apply_material_to_node(node) imgui.same_line() if imgui.button("重置材质"): self._reset_material(node) def _draw_material_properties(self, node): """绘制材质属性""" materials = node.find_all_materials() if not materials: imgui.text_colored((0.5, 0.5, 0.5, 1.0), "无材质") return for i, material in enumerate(materials): material_name = material.get_name() if hasattr(material, 'get_name') and material.get_name() else f"材质{i + 1}" if imgui.collapsing_header(f"材质: {material_name}"): # 材质基础颜色 base_color = self._get_material_base_color(material) if base_color: imgui.text("基础颜色") changed, new_r = imgui.slider_float(f"R##mat_r_{i}", base_color[0], 0.0, 1.0) if changed: self._update_material_base_color(material, 'r', new_r) base_color = (new_r, base_color[1], base_color[2], base_color[3]) changed, new_g = imgui.slider_float(f"G##mat_g_{i}", base_color[1], 0.0, 1.0) if changed: self._update_material_base_color(material, 'g', new_g) base_color = (base_color[0], new_g, base_color[2], base_color[3]) changed, new_b = imgui.slider_float(f"B##mat_b_{i}", base_color[2], 0.0, 1.0) if changed: self._update_material_base_color(material, 'b', new_b) base_color = (base_color[0], base_color[1], new_b, base_color[3]) changed, new_a = imgui.slider_float(f"A##mat_a_{i}", base_color[3], 0.0, 1.0) if changed: self._update_material_base_color(material, 'a', new_a) base_color = (base_color[0], base_color[1], base_color[2], new_a) # PBR属性 if hasattr(material, 'roughness') and material.roughness is not None: imgui.text("PBR属性") try: roughness_value = float(material.roughness) changed, new_roughness = imgui.slider_float(f"粗糙度##rough_{i}", roughness_value, 0.0, 1.0) if changed: self._update_material_roughness(material, new_roughness) except: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "粗糙度: 不可用") if hasattr(material, 'metallic') and material.metallic is not None: try: metallic_value = float(material.metallic) changed, new_metallic = imgui.slider_float(f"金属性##metal_{i}", metallic_value, 0.0, 1.0) if changed: self._update_material_metallic(material, new_metallic) except: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "金属性: 不可用") if hasattr(material, 'refractive_index') and material.refractive_index is not None: try: ior_value = float(material.refractive_index) changed, new_ior = imgui.slider_float(f"折射率##ior_{i}", ior_value, 1.0, 3.0) if changed: self._update_material_ior(material, new_ior) except: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "折射率: 不可用") # 材质预设 imgui.text("材质预设") presets = ["默认", "金属", "塑料", "玻璃", "木材", "混凝土"] current_preset = 0 # 默认选择 if imgui.begin_combo(f"预设##preset_{i}", presets[current_preset]): for j, preset_name in enumerate(presets): if imgui.selectable(preset_name, j == current_preset): self._apply_material_preset(material, preset_name) imgui.end_combo() # 纹理信息 imgui.text("纹理贴图") if imgui.button(f"选择漫反射贴图##diffuse_{i}"): self._select_texture_for_material(node, material, "diffuse") imgui.same_line() if imgui.button(f"选择法线贴图##normal_{i}"): self._select_texture_for_material(node, material, "normal") imgui.same_line() if imgui.button(f"选择粗糙度贴图##roughness_{i}"): self._select_texture_for_material(node, material, "roughness") if imgui.button(f"选择金属性贴图##metallic_{i}"): self._select_texture_for_material(node, material, "metallic") imgui.same_line() if imgui.button(f"选择自发光贴图##emission_{i}"): self._select_texture_for_material(node, material, "emission") imgui.same_line() if imgui.button(f"清除所有贴图##clear_{i}"): self._clear_all_textures(node) # 着色模型选择 self._draw_shading_model_panel(material, i) # 显示当前纹理信息 self._display_current_textures(node, material) def _draw_shading_model_panel(self, material, material_index): """绘制着色模型选择面板""" try: imgui.text("着色模型") # RenderPipeline支持的着色模型 shading_models = ["默认", "自发光", "透明"] current_model = 0 # 默认选择 # 安全地获取当前着色模型 try: if hasattr(material, 'emission') and material.emission is not None: current_model = int(material.emission.x) except: current_model = 0 # 着色模型选择 if imgui.begin_combo(f"着色模型##shading_{material_index}", shading_models[current_model]): for j, model_name in enumerate(shading_models): if imgui.selectable(model_name, j == current_model): self._update_shading_model(material, j) imgui.end_combo() # 如果是透明着色模型,添加透明度控制 if current_model == 3: # 透明着色模型 imgui.text("透明度设置") try: if hasattr(material, 'shading_model_param0'): current_opacity = material.shading_model_param0 else: current_opacity = 1.0 changed, new_opacity = imgui.slider_float(f"不透明度##opacity_{material_index}", current_opacity, 0.0, 1.0) if changed: self._update_transparency(material, new_opacity) except: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "透明度控制不可用") except Exception as e: print(f"绘制着色模型面板失败: {e}")