2054 lines
95 KiB
Python
2054 lines
95 KiB
Python
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 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")
|
||
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)
|
||
if opened:
|
||
# If this node is also a selectable leaf, render selectable entry first.
|
||
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') and self.app.lui_manager.selected_index >= 0:
|
||
if self.app.lui_manager.luiFunction:
|
||
self.app.lui_manager.luiFunction._draw_component_properties(self.app.lui_manager, self.app.lui_manager.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
|
||
|
||
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 == "模型": # 只对模型类型进行动画检测
|
||
# 首先检查是否已经缓存了检测结果
|
||
cached_result = node.getPythonTag('animation')
|
||
if cached_result is not None:
|
||
has_animation = cached_result
|
||
else:
|
||
# 只有在未缓存时才进行检测
|
||
try:
|
||
# 使用轻量级检测:先检查文件扩展名
|
||
model_path = node.getTag("model_path")
|
||
if model_path and model_path.lower().endswith(('.glb', '.gltf', '.fbx')):
|
||
# 对于可能包含动画的格式,才进行Actor检测
|
||
actor = self._getActor(node)
|
||
if actor and actor.getAnimNames():
|
||
has_animation = True
|
||
# 缓存检测结果
|
||
node.setPythonTag('animation', has_animation)
|
||
print(f"[动画检测] {node.getName()}: {'有动画' if has_animation else '无动画'}")
|
||
else:
|
||
# 对于不太可能有动画的格式,直接标记为无动画
|
||
node.setPythonTag('animation', False)
|
||
except Exception as e:
|
||
print(f"动画检测失败: {e}")
|
||
node.setPythonTag('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):
|
||
"""绘制动画控制属性面板(优化版本,使用缓存避免重复计算)"""
|
||
# 检查是否已经缓存了动画信息
|
||
cached_anim_info = node.getPythonTag("cached_anim_info")
|
||
cached_processed_names = node.getPythonTag("cached_processed_names")
|
||
|
||
# 只有在没有缓存时才进行完整的动画检测和处理
|
||
if cached_anim_info is None or cached_processed_names is None:
|
||
# 获取Actor
|
||
actor = self._getActor(node)
|
||
if not actor:
|
||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "此模型不包含动画")
|
||
return
|
||
|
||
# 获取和分析动画名称
|
||
anim_names = actor.getAnimNames()
|
||
processed_names = self._processAnimationNames(node, anim_names)
|
||
|
||
if not processed_names:
|
||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列")
|
||
# 缓存空结果
|
||
node.setPythonTag("cached_processed_names", [])
|
||
node.setPythonTag("cached_anim_info", "无动画")
|
||
return
|
||
|
||
# 计算并缓存动画信息
|
||
format_info = self._getModelFormat(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}"
|
||
|
||
# 缓存结果
|
||
node.setPythonTag("cached_anim_info", info_text)
|
||
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 = node.getPythonTag("selected_animation")
|
||
if current_anim is None:
|
||
current_anim = processed_names[0][1] if processed_names else ""
|
||
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]
|
||
node.setPythonTag("selected_animation", selected_original)
|
||
print(f"选择动画: {selected_display} (原始名称: {selected_original})")
|
||
|
||
imgui.spacing()
|
||
|
||
# 控制按钮组
|
||
imgui.text("控制:")
|
||
|
||
# 播放按钮
|
||
if imgui.button("播放##play_animation"):
|
||
self._playAnimation(node)
|
||
imgui.same_line()
|
||
|
||
# 暂停按钮
|
||
if imgui.button("暂停##pause_animation"):
|
||
self._pauseAnimation(node)
|
||
imgui.same_line()
|
||
|
||
# 停止按钮
|
||
if imgui.button("停止##stop_animation"):
|
||
self._stopAnimation(node)
|
||
imgui.same_line()
|
||
|
||
# 循环按钮
|
||
if imgui.button("循环##loop_animation"):
|
||
self._loopAnimation(node)
|
||
|
||
imgui.spacing()
|
||
|
||
# 播放速度控制
|
||
imgui.text("播放速度:")
|
||
imgui.same_line()
|
||
|
||
# 获取当前速度
|
||
current_speed = node.getPythonTag("anim_speed")
|
||
if current_speed is None:
|
||
current_speed = 1.0
|
||
node.setPythonTag("anim_speed", current_speed)
|
||
|
||
# 速度滑块
|
||
changed, new_speed = imgui.slider_float("##anim_speed", current_speed, 0.1, 5.0, "%.1f")
|
||
if changed:
|
||
node.setPythonTag("anim_speed", new_speed)
|
||
self._setAnimationSpeed(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}")
|