from panda3d.core import loadPrcFileData, WindowProperties, Point3 from math import pi, sin, cos from direct.showbase.ShowBase import ShowBase from direct.task import Task from direct.actor.Actor import Actor from direct.interval.IntervalGlobal import Sequence import p3dimgui from imgui_bundle import imgui, imgui_ctx import sys import os import warnings import threading import time from pathlib import Path # 导入MyWorld类和必要的模块 from core.world import CoreWorld from core.selection import SelectionSystem from core.event_handler import EventHandler from core.tool_manager import ToolManager from core.script_system import ScriptManager from core.patrol_system import PatrolSystem from core.Command_System import CommandManager from gui.gui_manager import GUIManager from core.terrain_manager import TerrainManager from scene.scene_manager import SceneManager from project.project_manager import ProjectManager from core.InfoPanelManager import InfoPanelManager from core.collision_manager import CollisionManager from core.CustomMouseController import CustomMouseController from core.resource_manager import ResourceManager # 拖拽监控类 class DragDropMonitor: """拖拽文件监控器""" def __init__(self, world): self.world = world self.running = False self.thread = None # 支持的文件格式 self.supported_formats = ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj'] def start(self): """启动监控""" if not self.running: self.running = True self.thread = threading.Thread(target=self._monitor_loop, daemon=True) self.thread.start() def stop(self): """停止监控""" self.running = False if self.thread: self.thread.join() def _monitor_loop(self): """监控循环""" while self.running: try: # 这里可以实现具体的拖拽检测逻辑 # 由于Panda3D限制,我们使用简化的实现 time.sleep(0.1) # 检查是否有新的拖拽文件 self._check_for_dropped_files() except Exception as e: print(f"拖拽监控错误: {e}") time.sleep(1) def _check_for_dropped_files(self): """检查是否有拖拽的文件""" # 这里可以实现具体的文件检测逻辑 # 由于系统限制,我们提供一个占位符实现 pass def add_file_from_external(self, file_path): """从外部添加文件路径(用于系统级拖拽集成)""" if self.world: self.add_dragged_file(file_path) try: # 尝试导入视频管理器,避免循环导入 import importlib.util spec = importlib.util.spec_from_file_location("video_integration", "demo/video_integration.py") video_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(video_module) VideoManager = video_module.VideoManager except: # 如果video_integration模块不存在,则跳过 VideoManager = None print("⚠ 视频管理器模块未找到,视频功能将不可用") warnings.filterwarnings("ignore", category=DeprecationWarning) class MyWorld(CoreWorld): def __init__(self): super().__init__() # 设置窗口为最大化模式 props = WindowProperties() props.set_maximized(True) self.win.request_properties(props) print("✓ 窗口已设置为最大化模式") # 初始化选择和变换系统 self.selection = SelectionSystem(self) # 绑定F键用于聚焦选中节点 self.accept("f", self.onFocusKeyPressed) self.accept("F", self.onFocusKeyPressed) # 大写F #初始化巡检系统 self.patrol_system = PatrolSystem(self) self.accept("p",self.onPatrolKeyPressed) self.accept("P",self.onPatrolKeyPressed) # 初始化事件处理系统 self.event_handler = EventHandler(self) # 初始化工具管理系统 self.tool_manager = ToolManager(self) # 初始化脚本管理系统 self.script_manager = ScriptManager(self) # 初始化GUI管理系统 self.gui_manager = GUIManager(self) # 初始化视频管理 if VideoManager is not None: self.video_manager = VideoManager(self) else: self.video_manager = None # 初始化场景管理系统 self.scene_manager = SceneManager(self) # 初始化项目管理系统 self.project_manager = ProjectManager(self) self.info_panel_manager = InfoPanelManager(self) self.command_manager = CommandManager() # 初始化碰撞管理器 self.collision_manager = CollisionManager(self) # 初始化资源管理器 self.resource_manager = ResourceManager(self) # 初始化自定义鼠标控制器(视角移动) self.mouse_controller = CustomMouseController(self) self.mouse_controller.setUp(mouse_speed=25, move_speed=20) print("✓ 自定义鼠标控制器初始化完成") # 初始化VR管理器 try: from core.vr import VRManager self.vr_manager = VRManager(self) print("✓ VR管理器初始化完成") except Exception as e: print(f"⚠ VR管理器初始化失败: {e}") self.vr_manager = None # 调试选项 self.debug_collision = True # 是否显示碰撞体 # 默认启用模型间碰撞检测(可选) self.enableModelCollisionDetection(enable=True, frequency=0.1, threshold=0.5) # 启动脚本系统 self.script_manager.start_system() self.terrain_manager = TerrainManager(self) self.terrain_edit_radius = 3.0 self.terrain_edit_strength=0.3 self.terrain_edit_operation = "add" # Install Dear ImGui p3dimgui.init() # 启用ImGui Docking功能 imgui.get_io().config_flags |= imgui.ConfigFlags_.docking_enable print("✓ ImGui Docking功能已启用") # 初始化样式管理器 from core.imgui_style_manager import ImGuiStyleManager self.style_manager = ImGuiStyleManager(self.imgui, self) # 简化的初始化字体设置(只使用中文字体) try: # 先清除默认字体 self.imgui.io.fonts.clear() # 尝试加载中文字体 import platform from pathlib import Path system = platform.system().lower() if system == "linux": font_path = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc" elif system == "windows": font_path = "C:/Windows/Fonts/msyh.ttc" elif system == "darwin": font_path = "/System/Library/Fonts/PingFang.ttc" else: font_path = None if font_path and Path(font_path).exists(): self.imgui.io.fonts.add_font_from_file_ttf(font_path, 14.0) print(f"✓ 初始化中文字体成功: {font_path}") else: # 回退到原来的字体 fallback_font_path = "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc" if fallback_font_path and Path(fallback_font_path).exists(): self.imgui.io.fonts.add_font_from_file_ttf(fallback_font_path, 14.0) print(f"✓ 初始化回退字体成功: {fallback_font_path}") else: self.imgui.io.fonts.add_font_default() print("✓ 初始化使用默认字体") except Exception as e: print(f"⚠ 初始化字体系统失败: {e}") # 备用方案:使用默认字体 try: self.imgui.io.fonts.add_font_default() print("✓ 使用备用默认字体") except: pass # Disable the camera trackball controls. self.disableMouse() self.showDemoWindow = False # UI状态管理 self.showSceneTree = True self.showPropertyPanel = True self.showConsole = True self.showScriptPanel = True self.showToolbar = True self.showResourceManager = True # 变换监控相关 self._transform_monitoring = False self._monitored_node = None self._last_transform_values = {} self._transform_update_timer = 0 self._transform_update_interval = 0.05 # 50ms检查一次 self._clipboard_pos = None # 位置剪贴板 # 颜色选择器相关 self._color_picker_active = False self._color_picker_target = None # (target_object, property_name) self._color_picker_current_color = (1.0, 1.0, 1.0, 1.0) self._color_picker_callback = None # 字体选择器相关 self._font_selector_active = False self._font_selector_target = None # (target_object, property_name) self._font_selector_current_font = "" self._font_selector_callback = None self._available_fonts = [] # 可用字体列表 self._refresh_available_fonts() # 菜单状态管理 self.show_new_project_dialog = False self.show_open_project_dialog = False self.show_save_as_dialog = False # 路径选择对话框状态 self.show_path_browser = False self.path_browser_mode = "" # "new_project" 或 "open_project" self.path_browser_current_path = os.getcwd() self.path_browser_selected_path = "" self.path_browser_items = [] # 快捷键映射 self.shortcut_keys = { 'Ctrl+N': self._on_new_project, 'Ctrl+O': self._on_open_project, 'Ctrl+S': self._on_save_project, 'Alt+F4': self._on_exit } # 键盘状态跟踪 self.ctrl_pressed = False self.alt_pressed = False # 消息系统 self.messages = [] self.max_messages = 5 # 最多显示5条消息 # 剪切板系统 self.clipboard = [] self.clipboard_mode = "" # "copy" 或 "cut" # 视角控制状态 self.camera_control_enabled = True self.show_camera_info = False # 拖拽导入状态 self.dragged_files = [] self.is_dragging = False self.show_drag_overlay = False self.drag_drop_monitor = None # 导入功能状态 self.show_import_dialog = False self.import_file_path = "" self.supported_formats = [".gltf", ".glb", ".fbx", ".bam", ".egg", ".obj"] # 创建功能状态 self.show_3d_text_dialog = False self.show_3d_image_dialog = False self.show_gui_button_dialog = False self.show_gui_label_dialog = False self.show_gui_entry_dialog = False self.show_gui_image_dialog = False self.show_video_screen_dialog = False self.show_2d_video_screen_dialog = False self.show_spherical_video_dialog = False self.show_virtual_screen_dialog = False self.show_spot_light_dialog = False self.show_point_light_dialog = False self.show_terrain_dialog = False self.show_heightmap_browser = False self.show_script_dialog = False self.show_script_browser = False # 高度图浏览器状态 self.heightmap_browser_current_path = os.getcwd() self.heightmap_browser_selected_path = "" self.heightmap_browser_items = [] self.heightmap_file_path = "" self.supported_heightmap_formats = [".png", ".jpg", ".jpeg", ".bmp", ".tiff", ".tif"] # 脚本浏览器状态 self.script_browser_current_path = os.getcwd() self.script_browser_selected_path = "" self.script_browser_items = [] # 对话框参数存储 self.dialog_params = {} # 存储各种对话框的参数 # 脚本系统状态 self.hotReloadEnabled = False # 初始化高度图浏览器 self._refresh_heightmap_browser() # 初始化脚本浏览器 self._refresh_script_browser() self.accept('imgui-new-frame', self.__newFrame) self.accept('`', self.__toggleImgui) # 添加键盘事件监听用于快捷键 self.accept('control', self._on_ctrl_pressed) self.accept('control-up', self._on_ctrl_released) self.accept('alt', self._on_alt_pressed) self.accept('alt-up', self._on_alt_released) self.accept('n', self._on_n_pressed) self.accept('o', self._on_o_pressed) self.accept('control-s', self._on_save_project) self.accept('f4', self._on_f4_pressed) # 编辑功能快捷键 self.accept('z', self._on_z_pressed) self.accept('y', self._on_y_pressed) self.accept('x', self._on_x_pressed) self.accept('c', self._on_c_pressed) self.accept('v', self._on_v_pressed) self.accept('delete', self._on_delete_pressed) # 滚轮事件 self.accept('wheel_up', self._on_wheel_up) self.accept('wheel_down', self._on_wheel_down) self.testTexture = None self.icons = {} # 初始化图标字典 # 添加初始化消息 self.add_success_message("MyWorld 初始化完成") self.add_info_message("ImGui菜单系统已就绪") self.add_info_message("快捷键已启用 (Ctrl+N, Ctrl+O, Ctrl+S, Alt+F4)") # 启用拖拽导入功能 self.setup_drag_drop_support() self.add_info_message("拖拽导入功能已启用 - 可将3D文件拖拽到窗口中导入") print("✓ MyWorld 初始化完成") # ==================== 兼容性属性 ==================== # 保留models属性以兼容现有代码 @property def models(self): """模型列表的兼容性属性""" return self.scene_manager.models @models.setter def models(self, value): """模型列表的兼容性设置器""" self.scene_manager.models = value # 保留gui_elements属性以兼容现有代码 @property def gui_elements(self): """GUI元素列表的兼容性属性""" return self.gui_manager.gui_elements @gui_elements.setter def gui_elements(self, value): """GUI元素列表的兼容性设置器""" self.gui_manager.gui_elements = value # 保留guiEditMode属性以兼容现有代码 @property def guiEditMode(self): """GUI编辑模式的兼容性属性""" return self.gui_manager.guiEditMode @guiEditMode.setter def guiEditMode(self, value): """GUI编辑模式的兼容性设置器""" self.gui_manager.guiEditMode = value # 保留currentTool属性以兼容现有代码 @property def currentTool(self): """当前工具的兼容性属性""" return self.tool_manager.currentTool @currentTool.setter def currentTool(self, value): """当前工具的兼容性设置器""" self.tool_manager.currentTool = value # 保留terrains属性以兼容现有代码 @property def terrains(self): """地形列表的兼容性属性""" return self.terrain_manager.terrains @terrains.setter def terrains(self,value): """地形列表的兼容性设置器""" self.terrain_manager.terrains = value def onFocusKeyPressed(self): """处理 F 键按下事件""" try: if hasattr(self, 'selection') and self.selection.selectedNode: self.selection.focusCameraOnSelectedNodeAdvanced() else: print("当前没有选中任何节点") except Exception as e: print(f"处理 F 键事件失败: {e}") def onPatrolKeyPressed(self): """处理 P 键按下事件 - 控制巡检系统""" try: print("检测到 P 键按下") if not self.patrol_system.is_patrolling: if not self.patrol_system.patrol_points: self.createDefaultPatrolRoute() if self.patrol_system.start_patrol(): print("✓ 巡检已开始") else: print("✗ 巡检启动失败") else: if self.patrol_system.stop_patrol(): print("✓ 巡检已停止") else: print("✗ 巡检停止失败") except Exception as e: print(f"处理 P 键事件失败: {e}") def createDefaultPatrolRoute(self): """创建默认巡检路线""" try: self.patrol_system.clear_patrol_points() self.patrol_system.add_patrol_point((0, -10, 2), (0,0,0), 1.5) self.patrol_system.add_patrol_point((10, -10, 2), (0,0,0), 1.5) self.patrol_system.add_patrol_point((10, 5, 2), (0,0,0), 1.5) self.patrol_system.add_patrol_point((10, 0, 5), (0,0,0), 1.5) self.patrol_system.add_patrol_point((0, -10, 2), None, 2.5) print("✓ 默认自动朝向巡检路线已创建") self.patrol_system.list_patrol_points() except Exception as e: print(f"创建默认自动朝向巡检路线失败: {e}") def enableModelCollisionDetection(self, enable=True, frequency=0.1, threshold=0.5): """启用模型间碰撞检测""" return self.collision_manager.enableModelCollisionDetection(enable, frequency, threshold) def __toggleImgui(self): if not self.imgui.isKeyboardCaptured(): self.imgui.toggle() def __newFrame(self): # Dear ImGui commands can be placed here. # 创建全屏DockSpace(在第一帧之后创建) if imgui.get_frame_count() > 0: viewport = imgui.get_main_viewport() imgui.dock_space_over_viewport(0, viewport, imgui.DockNodeFlags_.passthru_central_node) # 在第一帧应用样式 if imgui.get_frame_count() == 0: self.style_manager.apply_style() # 加载图标 self.icons = { 'select': self.style_manager.load_icon('select_tool'), 'move': self.style_manager.load_icon('move_tool'), 'rotate': self.style_manager.load_icon('rotate_tool'), 'scale': self.style_manager.load_icon('scale_tool'), 'success': self.style_manager.load_icon('success_icon'), 'warning': self.style_manager.load_icon('warning_icon'), 'property_select_image': self.style_manager.load_icon('property_select_image'), 'delete_fail_icon': self.style_manager.load_icon('delete_fail_icon'), } # 简化字体加载(只使用中文字体) try: # 清除现有字体 self.imgui.io.fonts.clear() # 尝试加载中文字体 import platform from pathlib import Path system = platform.system().lower() if system == "linux": font_path = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc" elif system == "windows": font_path = "C:/Windows/Fonts/msyh.ttc" elif system == "darwin": font_path = "/System/Library/Fonts/PingFang.ttc" else: font_path = None if font_path and Path(font_path).exists(): self.imgui.io.fonts.add_font_from_file_ttf(font_path, 14.0) print(f"✓ 中文字体加载成功: {font_path}") else: # 回退到原来的字体 fallback_font_path = "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc" if fallback_font_path and Path(fallback_font_path).exists(): self.imgui.io.fonts.add_font_from_file_ttf(fallback_font_path, 14.0) print(f"✓ 回退字体加载成功: {fallback_font_path}") else: self.imgui.io.fonts.add_font_default() print("✓ 使用默认字体") except Exception as e: print(f"⚠ 字体初始化失败: {e}") try: self.imgui.io.fonts.add_font_default() print("✓ 使用备用默认字体") except: pass # 获取窗口尺寸 display_size = imgui.get_io().display_size window_width = display_size.x window_height = display_size.y # 绘制菜单栏 self._draw_menu_bar() # 绘制停靠布局 self._draw_docked_layout(window_width, window_height) # 绘制纹理测试窗口 if self.testTexture: with self.style_manager.begin_styled_window("Texture Test", True, imgui.WindowFlags_.always_auto_resize) as (_, windowOpen): if not windowOpen: self.imgui.removeTexture(self.testTexture) self.testTexture = None return imgui.image(self.testTexture, (256, 256)) # 绘制ImGui演示窗口 if self.showDemoWindow: imgui.show_demo_window() # 绘制对话框 self._draw_new_project_dialog() self._draw_open_project_dialog() self._draw_path_browser() self._draw_import_dialog() # 绘制颜色选择器 self._draw_color_picker() # 绘制字体选择器 self._draw_font_selector() # 绘制创建功能对话框 self._draw_3d_text_dialog() self._draw_3d_image_dialog() self._draw_gui_button_dialog() self._draw_gui_label_dialog() self._draw_gui_entry_dialog() self._draw_gui_image_dialog() self._draw_video_screen_dialog() self._draw_2d_video_screen_dialog() self._draw_spherical_video_dialog() self._draw_virtual_screen_dialog() self._draw_spot_light_dialog() self._draw_point_light_dialog() self._draw_terrain_dialog() self._draw_heightmap_browser() self._draw_script_dialog() # 绘制纹理选择对话框 self._draw_texture_file_dialog() self._draw_script_browser() # 绘制右键菜单 self._draw_context_menus() # 绘制拖拽界面 self._draw_drag_drop_interface() # 更新变换监控 dt = imgui.get_io().delta_time self.update_transform_monitoring(dt) # 检查鼠标释放事件(用于处理拖拽结束) if imgui.is_mouse_released(0) and self.is_dragging: if not imgui.is_any_window_hovered(): # 在3D视图中释放,处理模型导入 self._handle_drag_drop_completion() def _draw_docked_layout(self, window_width, window_height): """绘制可停靠的布局(支持拖拽)""" # 左侧场景树面板 if self.showSceneTree: self._draw_scene_tree() # 资源管理器面板 if self.showResourceManager: self._draw_resource_manager() # 属性面板 if self.showPropertyPanel: self._draw_property_panel() # 脚本面板 if self.showScriptPanel: self._draw_script_panel() # 底部控制台 if self.showConsole: self._draw_console() # 顶部工具栏 if self.showToolbar: self._draw_toolbar() 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._on_new_project() if imgui.menu_item("打开项目", "Ctrl+O", False, True)[1]: self._on_open_project() imgui.separator() if imgui.menu_item("保存", "Ctrl+S", False, True)[1]: self._on_save_project() if imgui.menu_item("另存为", "", False, True)[1]: self._on_save_as_project() imgui.separator() if imgui.menu_item("退出", "Alt+F4", False, True)[1]: self._on_exit() # 编辑菜单 with imgui_ctx.begin_menu("编辑") as edit_menu: if edit_menu: if imgui.menu_item("撤销", "Ctrl+Z", False, True)[1]: self._on_undo() if imgui.menu_item("重做", "Ctrl+Y", False, True)[1]: self._on_redo() imgui.separator() if imgui.menu_item("剪切", "Ctrl+X", False, True)[1]: self._on_cut() if imgui.menu_item("复制", "Ctrl+C", False, True)[1]: self._on_copy() if imgui.menu_item("粘贴", "Ctrl+V", False, True)[1]: self._on_paste() imgui.separator() if imgui.menu_item("删除", "Del", False, True)[1]: self._on_delete() # 创建菜单 with imgui_ctx.begin_menu("创建") as create_menu: if create_menu: if imgui.menu_item("空对象", "", False, True)[1]: self._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._on_create_cube() if imgui.menu_item("球体", "", False, True)[1]: self._on_create_sphere() if imgui.menu_item("圆柱体", "", False, True)[1]: self._on_create_cylinder() if imgui.menu_item("平面", "", False, True)[1]: self._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._on_create_3d_text() if imgui.menu_item("3D图片", "", False, True)[1]: self._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._on_create_gui_button() if imgui.menu_item("创建标签", "", False, True)[1]: self._on_create_gui_label() if imgui.menu_item("创建输入框", "", False, True)[1]: self._on_create_gui_entry() if imgui.menu_item("创建图片", "", False, True)[1]: self._on_create_gui_image() imgui.separator() if imgui.menu_item("创建视频屏幕", "", False, True)[1]: self._on_create_video_screen() if imgui.menu_item("创建2D视频屏幕", "", False, True)[1]: self._on_create_2d_video_screen() if imgui.menu_item("创建球形视频", "", False, True)[1]: self._on_create_spherical_video() if imgui.menu_item("创建虚拟屏幕", "", False, True)[1]: self._on_create_virtual_screen() # 光源子菜单 with imgui_ctx.begin_menu("光源") as light_menu: if light_menu: if imgui.menu_item("聚光灯", "", False, True)[1]: self._on_create_spot_light() if imgui.menu_item("点光源", "", False, True)[1]: self._on_create_point_light() # 地形子菜单 with imgui_ctx.begin_menu("地形") as terrain_menu: if terrain_menu: if imgui.menu_item("创建平面地形", "", False, True)[1]: self._on_create_flat_terrain() if imgui.menu_item("从高度图创建地形", "", False, True)[1]: self._on_create_heightmap_terrain() # 脚本子菜单 with imgui_ctx.begin_menu("脚本") as script_menu: if script_menu: if imgui.menu_item("创建脚本...", "", False, True)[1]: self._on_create_script() if imgui.menu_item("加载脚本文件...", "", False, True)[1]: self._on_load_script() imgui.separator() if imgui.menu_item("重载所有脚本", "", False, True)[1]: self._on_reload_all_scripts() _, self.hotReloadEnabled = imgui.menu_item("启用热重载", "", self.hotReloadEnabled, True) if imgui.menu_item("脚本管理器", "", False, True)[1]: self._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._on_create_2d_sample_panel() if imgui.menu_item("创建3D实例面板", "", False, True)[1]: self._on_create_3d_sample_panel() if imgui.menu_item("Web面板", "", False, True)[1]: self._on_create_web_panel() # 视图菜单 with imgui_ctx.begin_menu("视图") as view_menu: if view_menu: _, self.showToolbar = imgui.menu_item("工具栏", "", self.showToolbar, True) _, self.showSceneTree = imgui.menu_item("场景树", "", self.showSceneTree, True) _, self.showResourceManager = imgui.menu_item("资源管理器", "", self.showResourceManager, True) _, self.showPropertyPanel = imgui.menu_item("属性面板", "", self.showPropertyPanel, True) _, self.showConsole = imgui.menu_item("控制台", "", self.showConsole, True) _, self.showScriptPanel = imgui.menu_item("脚本管理", "", self.showScriptPanel, True) # 工具菜单 with imgui_ctx.begin_menu("工具") as tools_menu: if tools_menu: if imgui.menu_item("导入模型", "", False, True)[1]: self._on_import_model() imgui.menu_item("地形编辑器", "", False, True) imgui.menu_item("材质编辑器", "", False, True) imgui.menu_item("脚本编辑器", "", False, True) # 窗口菜单 with imgui_ctx.begin_menu("窗口") as window_menu: if window_menu: _, self.showDemoWindow = imgui.menu_item("ImGui演示", "", self.showDemoWindow, True) if self.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.style_manager.get_window_flags("toolbar") with self.style_manager.begin_styled_window("工具栏", self.showToolbar, flags): self.showToolbar = True # 确保窗口保持打开 # 工具按钮组 if self.icons.get('select'): if self.style_manager.image_button(self.icons['select'], (24, 24)): print("选择工具") if imgui.is_item_hovered(): imgui.set_tooltip("选择工具 (Q)") imgui.same_line() else: if imgui.button("选择"): print("选择工具") imgui.same_line() if self.icons.get('move'): if self.style_manager.image_button(self.icons['move'], (24, 24)): print("移动工具") if imgui.is_item_hovered(): imgui.set_tooltip("移动工具 (W)") imgui.same_line() else: if imgui.button("移动"): print("移动工具") imgui.same_line() if self.icons.get('rotate'): if self.style_manager.image_button(self.icons['rotate'], (24, 24)): print("旋转工具") if imgui.is_item_hovered(): imgui.set_tooltip("旋转工具 (E)") imgui.same_line() else: if imgui.button("旋转"): print("旋转工具") imgui.same_line() if self.icons.get('scale'): if self.style_manager.image_button(self.icons['scale'], (24, 24)): print("缩放工具") if imgui.is_item_hovered(): imgui.set_tooltip("缩放工具 (R)") else: if imgui.button("缩放"): print("缩放工具") imgui.same_line() imgui.separator() imgui.same_line() # 其他工具按钮 if imgui.button("导入"): print("导入模型") imgui.same_line() if imgui.button("保存"): print("保存场景") imgui.same_line() if imgui.button("播放"): print("播放动画") def _draw_scene_tree(self): """绘制场景树面板""" # 使用更少的限制性标志,允许docking flags = (imgui.WindowFlags_.no_collapse) with self.style_manager.begin_styled_window("场景树", self.showSceneTree, flags): self.showSceneTree = True # 确保窗口保持打开 imgui.text("场景层级") imgui.separator() # 构建动态场景树 self._build_scene_tree() def _build_scene_tree(self): """构建动态场景树""" # 渲染节点 if imgui.tree_node("渲染"): # 环境光 if hasattr(self, 'ambient_light') and self.ambient_light: self._draw_scene_node(self.ambient_light, "环境光", "light") # 聚光灯 if hasattr(self, 'scene_manager') and self.scene_manager: if hasattr(self.scene_manager, 'Spotlight') and self.scene_manager.Spotlight: for i, spotlight in enumerate(self.scene_manager.Spotlight): self._draw_scene_node(spotlight, f"聚光灯_{i+1}", "light") if hasattr(self.scene_manager, 'Pointlight') and self.scene_manager.Pointlight: for i, pointlight in enumerate(self.scene_manager.Pointlight): self._draw_scene_node(pointlight, f"点光源_{i+1}", "light") # 地板 if hasattr(self, 'ground') and self.ground: self._draw_scene_node(self.ground, "地板", "geometry") imgui.tree_pop() # 相机节点 if imgui.tree_node("相机"): if hasattr(self, 'camera') and self.camera: self._draw_scene_node(self.camera, "主相机", "camera") imgui.tree_pop() # 3D模型节点 if imgui.tree_node("模型"): if hasattr(self, 'scene_manager') and self.scene_manager and hasattr(self.scene_manager, 'models'): if self.scene_manager.models: for i, model in enumerate(self.scene_manager.models): if model and not model.isEmpty(): self._draw_scene_node(model, model.getName() or f"模型_{i+1}", "model") else: imgui.text("(空)") else: imgui.text("(空)") imgui.tree_pop() # GUI元素节点 if imgui.tree_node("GUI元素"): if hasattr(self, 'gui_manager') and self.gui_manager and hasattr(self.gui_manager, 'gui_elements'): if self.gui_manager.gui_elements: for gui_element in self.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() def _draw_scene_node(self, node, name, node_type, gui_subtype=None): """绘制单个场景节点""" if not node or node.isEmpty(): return # 检查是否被选中 is_selected = (hasattr(self, 'selection') and self.selection and hasattr(self.selection, 'selectedNode') and self.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)) try: # 显示节点 node_open = imgui.tree_node(name) # 处理节点选择 if imgui.is_item_clicked(): if hasattr(self, 'selection') and self.selection: self.selection.updateSelection(node) # 右键菜单 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: # 如果有子节点,递归显示 if node.getNumChildren() > 0: for i in range(node.getNumChildren()): child = node.getChild(i) if child and not child.isEmpty(): child_name = child.getName() or f"子节点_{i+1}" self._draw_scene_node(child, child_name, node_type) imgui.tree_pop() except Exception as e: print(f"绘制场景节点时出错: {e}") finally: # 确保颜色状态被恢复 if is_selected: imgui.pop_style_color() def _show_node_context_menu(self, node, name, node_type): """显示节点右键菜单""" self._context_menu_node = True self._context_menu_target = node def _draw_resource_manager(self): """绘制资源管理器面板""" # 使用面板类型的窗口标志,支持docking flags = self.style_manager.get_window_flags("panel") with self.style_manager.begin_styled_window("资源管理器", self.showResourceManager, flags): self.showResourceManager = True # 确保窗口保持打开 # 获取资源管理器实例 rm = self.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.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.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.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.scene_manager.importModel(str(subfile)) self.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.is_dragging = True self.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.add_info_message(f"正在导入模型: {file_path.name}") self.scene_manager.importModel(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.add_info_message(f"正在导入模型: {rm.context_menu_file.name}") self.scene_manager.importModel(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.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.style_manager.get_window_flags("panel") with self.style_manager.begin_styled_window("属性面板", self.showPropertyPanel, flags): self.showPropertyPanel = True # 确保窗口保持打开 # 获取当前选中的节点 selected_node = None if hasattr(self, 'selection') and self.selection and hasattr(self.selection, 'selectedNode'): selected_node = self.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.stop_transform_monitoring() return # 检查是否需要重新启动变换监控 if self._monitored_node != node: self.stop_transform_monitoring() self.start_transform_monitoring(node) # 获取节点基本信息 node_name = node.getName() or "未命名节点" node_type = self._get_node_type_from_node(node) # 添加一些间距,模仿Qt版本的布局 imgui.spacing() # 物体名称组(使用Qt版本的样式) if imgui.collapsing_header("物体名称", True): # 第一行:可见性复选框和名称输入 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, 'selection'): # 更新场景树中的名称 self._update_node_name(node, new_name) # 添加分隔线 imgui.separator() # 状态徽章(模仿Qt版本的徽章样式) self._draw_status_badges(node) imgui.spacing() # 变换属性组 if imgui.collapsing_header("变换 Transform", True): self._draw_transform_properties(node) # 根据节点类型显示特定属性组 if node_type == "GUI元素": if imgui.collapsing_header("GUI信息"): self._draw_gui_properties(node) elif node_type == "光源": if imgui.collapsing_header("光源属性"): self._draw_light_properties(node) elif node_type == "模型": if imgui.collapsing_header("模型属性"): self._draw_model_properties(node) # 外观属性组(通用) if imgui.collapsing_header("外观属性"): self._draw_appearance_properties(node) # 操作按钮组 if imgui.collapsing_header("操作"): self._draw_property_actions(node) def _get_node_type_from_node(self, node): """从节点判断其类型""" # 检查是否为GUI元素 if hasattr(node, 'getPythonTag') and node.getPythonTag('gui_element'): return "GUI元素" # 检查是否为光源 node_name = node.getName() or "" if "light" in node_name.lower() or "Light" in node_name: return "光源" # 检查是否为相机 if "camera" in node_name.lower() or "Camera" in node_name: return "相机" # 检查是否为模型 if hasattr(self, 'scene_manager') and self.scene_manager: if hasattr(self.scene_manager, 'models') and node in self.scene_manager.models: return "模型" # 默认为几何体 return "几何体" 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))) # 紫色 # 动画徽章 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", True): # 相对位置 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("基本属性", True): # 文本内容 (适用于按钮、标签等) 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 _apply_gui_font(self, gui_element, font_path): """应用GUI元素的字体""" try: if hasattr(gui_element, 'setFont') and font_path: gui_element.setFont(font_path) gui_element.font_path = font_path except Exception as e: print(f"应用GUI字体失败: {e}") def _apply_gui_font_size(self, gui_element, font_size): """应用GUI元素的字体大小""" try: if hasattr(gui_element, 'setFontSize'): gui_element.setFontSize(font_size) gui_element.font_size = font_size except Exception as e: print(f"应用GUI字体大小失败: {e}") def _apply_gui_font_style(self, gui_element): """应用GUI元素的字体样式""" try: if hasattr(gui_element, 'setFontStyle'): style = 0 if getattr(gui_element, 'font_bold', False): style |= 1 # 粗体 if getattr(gui_element, 'font_italic', False): style |= 2 # 斜体 gui_element.setFontStyle(style) except Exception as e: print(f"应用GUI字体样式失败: {e}") # 特定类型的属性 if gui_type == "button": if imgui.collapsing_header("按钮属性"): # 按钮状态 is_pressed = getattr(gui_element, 'pressed', False) changed, new_pressed = imgui.checkbox("按下状态", is_pressed) if changed: gui_element.pressed = new_pressed # 按钮回调 callback_name = getattr(gui_element, 'callback_name', '') changed, new_callback = imgui.input_text("回调函数", callback_name, 64) if changed: gui_element.callback_name = new_callback elif gui_type == "entry": if imgui.collapsing_header("输入框属性"): # 输入框内容 entry_text = getattr(gui_element, 'entry_text', '') changed, new_text = imgui.input_text("输入内容", entry_text, 256) if changed: gui_element.entry_text = new_text if hasattr(gui_element, 'set'): gui_element.set(new_text) # 最大长度 max_length = getattr(gui_element, 'max_length', 256) changed, new_max = imgui.input_int("最大长度", max_length) if changed: gui_element.max_length = max(max_length, 1) # 密码模式 is_password = getattr(gui_element, 'is_password', False) changed, new_password = imgui.checkbox("密码模式", is_password) if changed: gui_element.is_password = new_password if hasattr(gui_element, 'obscure'): gui_element.obscure(new_password) elif gui_type in ["2d_image", "3d_image"]: if imgui.collapsing_header("图像属性"): # 图像路径 image_path = getattr(gui_element, 'image_path', '') changed, new_path = imgui.input_text("图像路径", image_path, 256) if changed and hasattr(self, 'gui_manager'): gui_element.image_path = new_path # TODO: 重新加载图像 # 图像缩放模式 scale_mode = getattr(gui_element, 'scale_mode', 'stretch') if imgui.begin_combo("缩放模式", scale_mode): if imgui.selectable("拉伸##stretch"): gui_element.scale_mode = 'stretch' if imgui.selectable("适应##fit"): gui_element.scale_mode = 'fit' if imgui.selectable("填充##fill"): gui_element.scale_mode = 'fill' imgui.end_combo() 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): """绘制模型属性""" imgui.text("模型属性") # 模型路径 imgui.text("模型路径: (暂不支持显示)") # 材质数量 imgui.text("材质数量: (暂不支持显示)") 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 _update_node_name(self, node, new_name): """更新节点名称""" if new_name and new_name != node.getName(): node.setName(new_name) # 更新场景树显示 if hasattr(self, 'scene_tree'): self.scene_tree.refresh() 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}", True): # 材质基础颜色 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 _get_material_base_color(self, material): """获取材质基础颜色""" try: if hasattr(material, 'base_color') and material.base_color is not None: return (material.base_color.x, material.base_color.y, material.base_color.z, material.base_color.w) elif hasattr(material, 'get_base_color'): color = material.get_base_color() return (color.x, color.y, color.z, color.w) elif hasattr(material, 'getDiffuse'): color = material.getDiffuse() return (color.x, color.y, color.z, color.w if hasattr(color, 'w') else 1.0) else: return (1.0, 1.0, 1.0, 1.0) # 默认白色 except: return (1.0, 1.0, 1.0, 1.0) # 默认白色 def _update_material_base_color(self, material, component, value): """更新材质基础颜色""" try: base_color = self._get_material_base_color(material) new_color = list(base_color) if component == 'r': new_color[0] = value elif component == 'g': new_color[1] = value elif component == 'b': new_color[2] = value elif component == 'a': new_color[3] = value new_color_tuple = tuple(new_color) if hasattr(material, 'set_base_color'): from panda3d.core import Vec4 material.set_base_color(Vec4(*new_color_tuple)) elif hasattr(material, 'setDiffuse'): from panda3d.core import Vec4 material.setDiffuse(Vec4(*new_color_tuple)) except Exception as e: print(f"更新材质基础颜色失败: {e}") def _update_material_roughness(self, material, value): """更新材质粗糙度""" try: if hasattr(material, 'set_roughness'): material.set_roughness(value) except Exception as e: print(f"更新材质粗糙度失败: {e}") def _update_material_metallic(self, material, value): """更新材质金属性""" try: if hasattr(material, 'set_metallic'): material.set_metallic(value) except Exception as e: print(f"更新材质金属性失败: {e}") def _update_material_ior(self, material, value): """更新材质折射率""" try: if hasattr(material, 'set_refractive_index'): material.set_refractive_index(value) except Exception as e: print(f"更新材质折射率失败: {e}") def _apply_material_preset(self, material, preset_name): """应用材质预设""" try: from panda3d.core import Vec4, Material presets = { "默认": { "base_color": Vec4(0.8, 0.8, 0.8, 1.0), "roughness": 0.5, "metallic": 0.0, "ior": 1.5 }, "金属": { "base_color": Vec4(0.7, 0.7, 0.8, 1.0), "roughness": 0.2, "metallic": 1.0, "ior": 2.5 }, "塑料": { "base_color": Vec4(0.9, 0.9, 0.9, 1.0), "roughness": 0.8, "metallic": 0.0, "ior": 1.45 }, "玻璃": { "base_color": Vec4(0.9, 0.9, 1.0, 0.2), "roughness": 0.0, "metallic": 0.0, "ior": 1.5 }, "木材": { "base_color": Vec4(0.6, 0.4, 0.2, 1.0), "roughness": 0.9, "metallic": 0.0, "ior": 1.55 }, "混凝土": { "base_color": Vec4(0.5, 0.5, 0.5, 1.0), "roughness": 1.0, "metallic": 0.0, "ior": 1.5 } } if preset_name in presets: preset = presets[preset_name] # 应用基础颜色 if hasattr(material, 'set_base_color'): material.set_base_color(preset["base_color"]) elif hasattr(material, 'setDiffuse'): material.setDiffuse(preset["base_color"]) # 应用PBR属性 if hasattr(material, 'set_roughness'): material.set_roughness(preset["roughness"]) if hasattr(material, 'set_metallic'): material.set_metallic(preset["metallic"]) if hasattr(material, 'set_refractive_index'): material.set_refractive_index(preset["ior"]) print(f"已应用材质预设: {preset_name}") except Exception as e: print(f"应用材质预设失败: {e}") def _apply_material_to_node(self, node): """为节点应用材质""" try: from panda3d.core import Material, Vec4 # 检查是否已有材质 materials = node.find_all_materials() if not materials: # 创建新材质 material = Material(f"default-material-{node.getName()}") material.setBaseColor(Vec4(0.8, 0.8, 0.8, 1.0)) material.setDiffuse(Vec4(0.8, 0.8, 0.8, 1.0)) material.setAmbient(Vec4(0.4, 0.4, 0.4, 1.0)) material.setSpecular(Vec4(0.1, 0.1, 0.1, 1.0)) material.setShininess(10.0) node.setMaterial(material, 1) print(f"已为新节点创建默认材质") else: print(f"节点已有 {len(materials)} 个材质") except Exception as e: print(f"应用材质失败: {e}") def _reset_material(self, node): """重置节点材质""" try: materials = node.find_all_materials() for material in materials: # 重置为默认材质属性 from panda3d.core import Vec4 default_color = Vec4(0.8, 0.8, 0.8, 1.0) if hasattr(material, 'set_base_color'): material.set_base_color(default_color) elif hasattr(material, 'setDiffuse'): material.setDiffuse(default_color) if hasattr(material, 'set_roughness'): material.set_roughness(0.5) if hasattr(material, 'set_metallic'): material.set_metallic(0.0) if hasattr(material, 'set_refractive_index'): material.set_refractive_index(1.5) print(f"已重置材质") except Exception as e: print(f"重置材质失败: {e}") def _select_texture_for_material(self, node, material, texture_type): """为材质选择纹理""" try: # 设置当前纹理对话框状态 self._current_texture_dialog = { 'node': node, 'material': material, 'texture_type': texture_type } # 初始化路径 if not hasattr(self, '_texture_dialog_path'): self._texture_dialog_path = '/home/hello/EG/Resources' # 设置文件类型过滤 self._texture_dialog_filter = "*.png" except Exception as e: print(f"选择纹理失败: {e}") def _apply_texture_to_material(self, node, material, texture_type, texture_path): """应用纹理到材质""" try: from panda3d.core import TextureStage from direct.showbase import Loader # 加载纹理 loader = Loader.Loader(self) texture = loader.loadTexture(texture_path) if not texture: print(f"无法加载纹理: {texture_path}") return # 设置纹理属性 texture.setMagfilter(texture.FTLinear) texture.setMinfilter(texture.FTLinearMipmapLinear) # 纹理槽位映射 texture_slots = { "diffuse": 0, # p3d_Texture0 "ior": 2, # p3d_Texture2 "normal": 1, # p3d_Texture1 "roughness": 3, # p3d_Texture3 "parallax": 4, # p3d_Texture4 "metallic": 5, # p3d_Texture5 "emission": 6, # p3d_Texture6 "ao": 7, # p3d_Texture7 "alpha": 8, # p3d_Texture8 "detail": 9, # p3d_Texture9 "gloss": 10 # p3d_Texture10 } slot = texture_slots.get(texture_type, 0) # 创建纹理阶段 texture_stage = TextureStage(f"{texture_type}_map") texture_stage.setSort(slot) texture_stage.setMode(TextureStage.MModulate) # 应用纹理到节点 node.setTexture(texture_stage, texture) print(f"已应用{texture_type}纹理: {texture_path}") except Exception as e: print(f"应用纹理失败: {e}") def _clear_all_textures(self, node): """清除节点所有纹理""" try: # 清除所有纹理阶段 node.clearTexture() node.clearTexture() print("已清除所有纹理") except Exception as e: print(f"清除纹理失败: {e}") def _display_current_textures(self, node, material): """显示当前纹理信息""" try: from panda3d.core import TextureStage # 获取所有纹理阶段 texture_stages = node.findAllTextureStages() if not texture_stages: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "当前无纹理") return imgui.text("当前纹理:") for stage in texture_stages: texture = node.getTexture(stage) if texture: texture_name = texture.getName() or "未命名" stage_name = stage.getName() or "未命名" imgui.text(f" {stage_name}: {texture_name}") except Exception as e: print(f"显示纹理信息失败: {e}") 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}") def _update_shading_model(self, material, model_index): """更新着色模型""" try: from panda3d.core import Vec4 # 根据不同的着色模型设置相应的参数 if model_index == 1: # 自发光着色模型 print("设置自发光着色模型...") if hasattr(material, 'set_emission'): current_emission = material.emission or Vec4(0, 0, 0, 0) new_emission = Vec4(1.0, current_emission.y, current_emission.z, current_emission.w) material.set_emission(new_emission) print("自发光着色模型设置完成") elif model_index == 3: # 透明着色模型 print("设置透明着色模型...") if hasattr(material, 'set_emission'): current_emission = material.emission or Vec4(0, 0, 0, 0) new_emission = Vec4(3.0, 0, 0, 0) # 3表示透明着色模型 material.set_emission(new_emission) # 设置默认透明度 if hasattr(material, 'shading_model_param0'): material.shading_model_param0 = 0.8 # 默认80%不透明度 print("透明着色模型设置完成") else: # 默认着色模型 print("设置默认着色模型...") if hasattr(material, 'set_emission'): current_emission = material.emission or Vec4(0, 0, 0, 0) new_emission = Vec4(0.0, current_emission.y, current_emission.z, current_emission.w) material.set_emission(new_emission) print("默认着色模型设置完成") print(f"着色模型已更新为: {model_index} ({'自发光' if model_index == 1 else '透明' if model_index == 3 else '默认'})") except Exception as e: print(f"更新着色模型失败: {e}") def _update_transparency(self, material, opacity_value): """更新透明度""" try: if hasattr(material, 'shading_model_param0'): material.shading_model_param0 = opacity_value print(f"透明度已更新: {opacity_value}") except Exception as e: print(f"更新透明度失败: {e}") def _draw_texture_file_dialog(self): """绘制纹理文件选择对话框""" if not hasattr(self, '_current_texture_dialog') or not self._current_texture_dialog: return try: dialog_data = self._current_texture_dialog node = dialog_data['node'] material = dialog_data['material'] texture_type = dialog_data['texture_type'] # 设置对话框标志 flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) # 获取屏幕尺寸,居中显示对话框 display_size = imgui.get_io().display_size dialog_width = 600 dialog_height = 400 imgui.set_next_window_size((dialog_width, dialog_height)) imgui.set_next_window_pos( ((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2) ) # 显示文件选择对话框 opened, window_open = imgui.begin(f"选择{texture_type}纹理文件##texture_dialog", True, flags) if not window_open: self._current_texture_dialog = None imgui.end() return imgui.text(f"选择{texture_type}纹理文件") imgui.separator() # 当前路径显示 current_path = getattr(self, '_texture_dialog_path', '/home/hello/EG/Resources') imgui.text(f"当前路径: {current_path}") imgui.separator() # 文件类型过滤 imgui.text("支持的纹理格式:") file_types = ["*.png", "*.jpg", "*.jpeg", "*.bmp", "*.tga", "*.dds"] current_filter = getattr(self, '_texture_dialog_filter', "*.png") if imgui.begin_combo("文件类型##texture_filter", current_filter): for i, file_type in enumerate(file_types): if imgui.selectable(file_type, i == file_types.index(current_filter)): self._texture_dialog_filter = file_type imgui.end_combo() imgui.separator() # 路径导航按钮 if imgui.button("上级目录##up_dir"): current_path = os.path.dirname(current_path) self._texture_dialog_path = current_path imgui.same_line() if imgui.button("主目录##home_dir"): self._texture_dialog_path = '/home/hello/EG/Resources' imgui.same_line() if imgui.button("当前目录##current_dir"): self._texture_dialog_path = '/home/hello/EG/Resources' imgui.same_line() if imgui.button("纹理目录##textures_dir"): self._texture_dialog_path = '/home/hello/EG/Resources/textures' imgui.separator() # 文件列表 if imgui.begin_child("file_list##texture_files", (580, 200)): try: # 列出目录和文件 items = [] if os.path.exists(current_path): for item in os.listdir(current_path): item_path = os.path.join(current_path, item) if os.path.isdir(item_path): items.append(('dir', item, item_path)) elif any(item.lower().endswith(ext[1:]) for ext in file_types): items.append(('file', item, item_path)) # 排序:目录在前,文件在后 items.sort(key=lambda x: (x[0], x[1].lower())) for item_type, item_name, item_path in items: if item_type == 'dir': if imgui.selectable(f"📁 {item_name}##dir_{item_name}", False)[0]: self._texture_dialog_path = item_path else: selected, _ = imgui.selectable(f"📄 {item_name}##file_{item_name}", False) if selected: # 应用选择的纹理 self._apply_texture_to_material(node, material, texture_type, item_path) # 关闭对话框 self._current_texture_dialog = None break except Exception as e: imgui.text_colored((1.0, 0.5, 0.5, 1.0), f"读取目录失败: {e}") imgui.end_child() imgui.separator() # 路径输入框 changed, new_path = imgui.input_text("文件路径##texture_path", current_path, 512) if changed: self._texture_dialog_path = new_path imgui.same_line() if imgui.button("确认路径##confirm_path"): if os.path.isfile(new_path): # 检查文件扩展名 file_ext = os.path.splitext(new_path)[1].lower() if file_ext in [ext[1:] for ext in file_types]: self._apply_texture_to_material(node, material, texture_type, new_path) self._current_texture_dialog = None else: print(f"不支持的文件格式: {file_ext}") else: print("请选择有效的文件") imgui.separator() # 按钮 if imgui.button("取消##cancel_texture"): self._current_texture_dialog = None imgui.end() except Exception as e: print(f"绘制纹理对话框失败: {e}") # 确保在异常情况下也调用 imgui.end() try: imgui.end() except: pass def start_transform_monitoring(self, node): """开始变换监控""" if node and not node.isEmpty(): self._monitored_node = node self._transform_monitoring = True self._transform_update_timer = 0 # 记录初始变换值 self._update_last_transform_values() def stop_transform_monitoring(self): """停止变换监控""" self._transform_monitoring = False self._monitored_node = None self._last_transform_values = {} def _update_last_transform_values(self): """更新最后记录的变换值""" if self._monitored_node and not self._monitored_node.isEmpty(): try: pos = self._monitored_node.getPos() hpr = self._monitored_node.getHpr() scale = self._monitored_node.getScale() self._last_transform_values = { 'pos': (pos.x, pos.y, pos.z), 'hpr': (hpr.x, hpr.y, hpr.z), 'scale': (scale.x, scale.y, scale.z) } except Exception as e: print(f"更新变换值失败: {e}") def _check_transform_changes(self): """检查变换变化""" if not self._transform_monitoring or not self._monitored_node: return try: pos = self._monitored_node.getPos() hpr = self._monitored_node.getHpr() scale = self._monitored_node.getScale() current_values = { 'pos': (pos.x, pos.y, pos.z), 'hpr': (hpr.x, hpr.y, hpr.z), 'scale': (scale.x, scale.y, scale.z) } # 检查是否有变化 if current_values != self._last_transform_values: # 更新记录的值 self._last_transform_values = current_values # 触发属性面板更新(通过设置更新标志) self.property_panel_update_timer = 0 except Exception as e: print(f"检查变换变化失败: {e}") def update_transform_monitoring(self, dt): """更新变换监控(在主循环中调用)""" if not self._transform_monitoring: return self._transform_update_timer += dt if self._transform_update_timer >= self._transform_update_interval: self._transform_update_timer = 0 self._check_transform_changes() def show_color_picker(self, target_object, property_name, initial_color, callback=None): """显示颜色选择器""" self._color_picker_active = True self._color_picker_target = (target_object, property_name) self._color_picker_current_color = initial_color self._color_picker_callback = callback def _draw_color_picker(self): """绘制颜色选择器对话框""" if not self._color_picker_active: return # 设置对话框标志 flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) # 获取屏幕尺寸,居中显示对话框 display_size = imgui.get_io().display_size dialog_width = 300 dialog_height = 400 imgui.set_next_window_size((dialog_width, dialog_height)) imgui.set_next_window_pos( ((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2) ) with imgui_ctx.begin("颜色选择器", True, flags) as window: if not window.opened: self._color_picker_active = False self._color_picker_target = None return imgui.text("选择颜色") imgui.separator() # 颜色编辑器 changed, new_color = imgui.color_edit4( "颜色##color_picker", self._color_picker_current_color ) if changed: self._color_picker_current_color = new_color # 预设颜色 imgui.text("预设颜色") preset_colors = [ (1.0, 0.0, 0.0, 1.0), # 红色 (0.0, 1.0, 0.0, 1.0), # 绿色 (0.0, 0.0, 1.0, 1.0), # 蓝色 (1.0, 1.0, 0.0, 1.0), # 黄色 (1.0, 0.0, 1.0, 1.0), # 洋红 (0.0, 1.0, 1.0, 1.0), # 青色 (1.0, 1.0, 1.0, 1.0), # 白色 (0.0, 0.0, 0.0, 1.0), # 黑色 (0.5, 0.5, 0.5, 1.0), # 灰色 (0.188, 0.404, 0.753, 1.0), # 主题蓝色 (0.176, 1.0, 0.769, 1.0), # 主题绿色 (0.953, 0.616, 0.471, 1.0), # 主题橙色 ] # 绘制预设颜色按钮 colors_per_row = 6 for i, color in enumerate(preset_colors): if i % colors_per_row != 0: imgui.same_line() imgui.color_button(f"preset_{i}", color, 0, (30, 30)) if imgui.is_item_clicked(): self._color_picker_current_color = color imgui.separator() # 按钮区域 if imgui.button("确定"): self._apply_color_selection() self._color_picker_active = False self._color_picker_target = None imgui.same_line() if imgui.button("取消"): self._color_picker_active = False self._color_picker_target = None def _apply_color_selection(self): """应用颜色选择""" if not self._color_picker_target: return target_object, property_name = self._color_picker_target try: # 应用颜色到目标对象 if hasattr(target_object, 'setColor'): target_object.setColor(self._color_picker_current_color) elif hasattr(target_object, property_name): setattr(target_object, property_name, self._color_picker_current_color) # 调用回调函数 if self._color_picker_callback: self._color_picker_callback(self._color_picker_current_color) except Exception as e: print(f"应用颜色失败: {e}") def _draw_color_button(self, label, color, size=(50, 20)): """绘制颜色按钮并支持点击打开颜色选择器""" imgui.color_button(label, color, 0, size) if imgui.is_item_clicked(): # 打开颜色选择器 self.show_color_picker(None, None, color) def _refresh_available_fonts(self): """刷新可用字体列表""" try: import platform from pathlib import Path system = platform.system().lower() font_paths = [] if system == "linux": font_dirs = [ "/usr/share/fonts/truetype/", "/usr/share/fonts/opentype/", "/usr/local/share/fonts/", "~/.fonts/" ] elif system == "windows": font_dirs = [ "C:/Windows/Fonts/", ] elif system == "darwin": font_dirs = [ "/System/Library/Fonts/", "/Library/Fonts/", "~/Library/Fonts/" ] else: font_dirs = [] # 扫描字体目录 common_fonts = [] for font_dir in font_dirs: font_path = Path(font_dir).expanduser() if font_path.exists(): for font_file in font_path.rglob("*.ttf"): common_fonts.append(str(font_file)) for font_file in font_path.rglob("*.otf"): common_fonts.append(str(font_file)) for font_file in font_path.rglob("*.ttc"): common_fonts.append(str(font_file)) # 过滤常见字体 font_keywords = [ "arial", "helvetica", "times", "courier", "verdana", "georgia", "comic", "impact", "trebuchet", "palatino", "garamond", "noto", "dejavu", "liberation", "ubuntu", "roboto", "open", "droid", "source", "wenquanyi", "wqy", "pingfang", "stheiti", "microsoft", "msyh", "simsun", "simhei", "kaiti", "fangsong" ] self._available_fonts = [] for font_path in common_fonts: font_name = Path(font_path).name.lower() if any(keyword in font_name for keyword in font_keywords): self._available_fonts.append(font_path) # 添加一些默认字体路径 default_fonts = [ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", "/usr/share/fonts/opentype/noto/NotoSans-Regular.ttf", "C:/Windows/Fonts/arial.ttf", "C:/Windows/Fonts/msyh.ttc", "/System/Library/Fonts/PingFang.ttc" ] for font_path in default_fonts: if Path(font_path).exists() and font_path not in self._available_fonts: self._available_fonts.append(font_path) print(f"✓ 找到 {len(self._available_fonts)} 个可用字体") except Exception as e: print(f"⚠ 字体扫描失败: {e}") self._available_fonts = [] def show_font_selector(self, target_object, property_name, current_font, callback=None): """显示字体选择器""" self._font_selector_active = True self._font_selector_target = (target_object, property_name) self._font_selector_current_font = current_font or "" self._font_selector_callback = callback def _draw_font_selector(self): """绘制字体选择器对话框""" if not self._font_selector_active: return # 设置对话框标志 flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) # 获取屏幕尺寸,居中显示对话框 display_size = imgui.get_io().display_size dialog_width = 400 dialog_height = 500 imgui.set_next_window_size((dialog_width, dialog_height)) imgui.set_next_window_pos( ((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2) ) with imgui_ctx.begin("字体选择器", True, flags) as window: if not window.opened: self._font_selector_active = False self._font_selector_target = None return imgui.text("选择字体") imgui.separator() # 当前字体显示 imgui.text(f"当前字体: {self._font_selector_current_font or '默认'}") # 字体搜索框 changed, search_text = imgui.input_text("搜索", "", 256) imgui.separator() # 字体列表 if imgui.begin_child("font_list", (380, 300)): for font_path in self._available_fonts: font_name = Path(font_path).name # 搜索过滤 if search_text and search_text.lower() not in font_name.lower(): continue # 字体项 if imgui.selectable(font_name, font_path == self._font_selector_current_font): self._font_selector_current_font = font_path # 显示字体路径作为工具提示 if imgui.is_item_hovered(): imgui.set_tooltip(font_path) imgui.end_child() imgui.separator() # 按钮区域 if imgui.button("确定"): self._apply_font_selection() self._font_selector_active = False self._font_selector_target = None imgui.same_line() if imgui.button("取消"): self._font_selector_active = False self._font_selector_target = None imgui.same_line() if imgui.button("刷新字体"): self._refresh_available_fonts() def _apply_font_selection(self): """应用字体选择""" if not self._font_selector_target: return target_object, property_name = self._font_selector_target try: # 应用字体到目标对象 if hasattr(target_object, property_name): setattr(target_object, property_name, self._font_selector_current_font) # 调用回调函数 if self._font_selector_callback: self._font_selector_callback(self._font_selector_current_font) except Exception as e: print(f"应用字体失败: {e}") def _draw_font_selector_button(self, label, current_font): """绘制字体选择器按钮""" font_name = Path(current_font).name if current_font else "默认字体" display_text = f"{font_name[:20]}..." if len(font_name) > 20 else font_name if imgui.button(f"{label}: {display_text}##font_selector"): self.show_font_selector(None, None, current_font) def _draw_console(self): """绘制控制台面板""" # 使用面板类型的窗口标志,支持docking flags = self.style_manager.get_window_flags("panel") with self.style_manager.begin_styled_window("控制台", self.showConsole, flags): self.showConsole = True # 确保窗口保持打开 imgui.text("控制台输出") imgui.separator() # 显示消息系统中的消息 if hasattr(self, 'messages') and self.messages: for message in self.messages: # 显示时间戳 imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"[{message['timestamp']}]") imgui.same_line() # 根据消息类型显示图标 if message['text'].startswith('✓'): if self.icons.get('success'): imgui.image(self.icons['success'], (12, 12)) imgui.same_line() elif message['text'].startswith('✗'): if self.icons.get('delete_fail_icon'): imgui.image(self.icons['delete_fail_icon'], (12, 12)) imgui.same_line() elif message['text'].startswith('⚠'): if self.icons.get('warning'): imgui.image(self.icons['warning'], (12, 12)) imgui.same_line() # 显示消息文本 imgui.text_colored(message['color'], message['text']) else: # 默认消息 imgui.text_colored((0.157, 0.620, 1.0, 1.0), "[系统]") imgui.same_line() imgui.text("引擎已就绪") # 输入框 imgui.separator() changed, command = imgui.input_text(">", "", 256) if changed and command: self.add_info_message(f"执行命令: {command}") # TODO: 实现命令执行逻辑 imgui.separator() # 视角控制信息 imgui.text("视角控制:") imgui.text(" WASD - 移动") imgui.text(" Q/E - 上下") imgui.text(" 右键拖拽 - 旋转视角") imgui.text(" 滚轮 - 前进/后退") # 相机位置信息 cam_pos = self.camera.getPos() cam_hpr = self.camera.getHpr() imgui.text(f"位置: X={cam_pos.x:.1f}, Y={cam_pos.y:.1f}, Z={cam_pos.z:.1f}") imgui.text(f"旋转: H={cam_hpr.x:.1f}, P={cam_hpr.y:.1f}, R={cam_hpr.z:.1f}") # 控制状态 imgui.checkbox("启用视角控制", self.camera_control_enabled) # 重置按钮 if imgui.button("重置相机"): self.camera.setPos(0, -20, 5) self.camera.setHpr(0, 0, 0) self.add_info_message("相机位置已重置") def _draw_script_panel(self): """绘制脚本管理面板""" # 使用面板类型的窗口标志,支持docking flags = self.style_manager.get_window_flags("panel") with self.style_manager.begin_styled_window("脚本管理", self.showScriptPanel, flags): self.showScriptPanel = True # 确保窗口保持打开 imgui.text("脚本列表") imgui.separator() # 模拟脚本列表 selected, _ = imgui.selectable("main.py", False) if selected: print("选择脚本: main.py") selected, _ = imgui.selectable("player_controller.py", False) if selected: print("选择脚本: player_controller.py") selected, _ = imgui.selectable("ui_manager.py", False) if selected: print("选择脚本: ui_manager.py") imgui.separator() # 脚本操作按钮 if imgui.button("新建脚本"): print("新建脚本") imgui.same_line() if imgui.button("编辑"): print("编辑脚本") imgui.same_line() if imgui.button("重载"): print("重载脚本") imgui.separator() imgui.text("脚本输出:") imgui.text("脚本引擎已启动") imgui.text("热重载监控已启动") # ==================== 菜单处理函数 ==================== def _on_new_project(self): """处理新建项目菜单项""" self.add_info_message("打开新建项目对话框") self.show_new_project_dialog = True def _on_open_project(self): """处理打开项目菜单项""" self.add_info_message("打开项目对话框") self.show_open_project_dialog = True def _on_save_project(self): """处理保存项目菜单项""" if hasattr(self, 'project_manager') and self.project_manager: try: # 检查是否有当前项目路径 if not self.project_manager.current_project_path: self.add_warning_message("没有当前项目路径,请先创建或打开项目") self.show_save_as_dialog = True return # 直接调用保存逻辑,避免Qt依赖 if self._save_project_impl(): self.add_success_message("项目保存成功") else: self.add_error_message("项目保存失败") except Exception as e: self.add_error_message(f"项目保存失败: {e}") else: self.add_error_message("项目管理器未初始化") def _on_save_as_project(self): """处理另存为项目菜单项""" self.add_info_message("另存为项目(功能待实现)") # TODO: 实现另存为对话框 # self.show_save_as_dialog = True def _on_exit(self): """处理退出菜单项""" self.add_info_message("退出应用程序") self.userExit() # ==================== 键盘事件处理函数 ==================== def _on_ctrl_pressed(self): """Ctrl键按下""" self.ctrl_pressed = True def _on_ctrl_released(self): """Ctrl键释放""" self.ctrl_pressed = False def _on_alt_pressed(self): """Alt键按下""" self.alt_pressed = True def _on_alt_released(self): """Alt键释放""" self.alt_pressed = False def _on_n_pressed(self): """N键按下 - 检查Ctrl+N组合键""" if self.ctrl_pressed: self._on_new_project() def _on_o_pressed(self): """O键按下 - 检查Ctrl+O组合键""" if self.ctrl_pressed: self._on_open_project() def _on_f4_pressed(self): """F4键按下 - 检查Alt+F4组合键""" if self.alt_pressed: self._on_exit() def _on_z_pressed(self): """Z键按下 - 检查Ctrl+Z组合键(撤销)""" if self.ctrl_pressed: self._on_undo() def _on_y_pressed(self): """Y键按下 - 检查Ctrl+Y组合键(重做)""" if self.ctrl_pressed: self._on_redo() def _on_x_pressed(self): """X键按下 - 检查Ctrl+X组合键(剪切)""" if self.ctrl_pressed: self._on_cut() def _on_c_pressed(self): """C键按下 - 检查Ctrl+C组合键(复制)""" if self.ctrl_pressed: self._on_copy() def _on_v_pressed(self): """V键按下 - 检查Ctrl+V组合键(粘贴)""" if self.ctrl_pressed: self._on_paste() def _on_delete_pressed(self): """Delete键按下 - 删除选中节点""" self._on_delete() def _on_wheel_up(self): """滚轮向上滚动 - 相机前进""" try: if not self.camera_control_enabled: return # 检查鼠标是否在ImGui窗口上 if self._is_mouse_over_imgui(): return # 沿相机前向向量移动 forward = self.camera.getMat().getRow3(1) distance = 20.0 * globalClock.getDt() currentPos = self.camera.getPos() newPos = currentPos + forward * distance self.camera.setPos(newPos) except Exception as e: print(f"滚轮前进失败: {e}") def _on_wheel_down(self): """滚轮向下滚动 - 相机后退""" try: # 检查鼠标是否在ImGui窗口上 if self._is_mouse_over_imgui(): return # 沿相机前向向量移动 forward = self.camera.getMat().getRow3(1) distance = -20.0 * globalClock.getDt() currentPos = self.camera.getPos() newPos = currentPos + forward * distance self.camera.setPos(newPos) except Exception as e: print(f"滚轮后退失败: {e}") def _is_mouse_over_imgui(self): """检测鼠标是否在ImGui窗口上""" try: # 检查是否有任何ImGui窗口想要捕获鼠标 if hasattr(imgui, 'get_io') and imgui.get_io().want_capture_mouse: return True # 检查鼠标是否在任何ImGui窗口内 mouse_pos = self.mouseWatcherNode.getMouse() if not mouse_pos: return False # 简单的边界检查(可以根据需要扩展) display_size = imgui.get_io().display_size mouse_x = mouse_pos.get_x() * display_size.x / 2 + display_size.x / 2 mouse_y = display_size.y - (mouse_pos.get_y() * display_size.y / 2 + display_size.y / 2) # 检查是否在常见的ImGui界面区域内 # 这里可以根据实际的ImGui窗口位置进行更精确的检测 if mouse_x < 300 and mouse_y < 200: # 左上角区域(菜单栏) return True if mouse_x < 300 and mouse_y > display_size.y - 200: # 左下角区域(工具栏) return True if mouse_x > display_size.x - 300 and mouse_y < 200: # 右上角区域 return True return False except Exception as e: print(f"ImGui界面检测失败: {e}") return False # ==================== 消息系统 ==================== def add_message(self, text, color=(1.0, 1.0, 1.0, 1.0)): """添加消息到消息列表,同时输出到终端""" import datetime timestamp = datetime.datetime.now().strftime("%H:%M:%S") # 输出到终端 print(f"[{timestamp}] {text}") # 添加到GUI消息列表 self.messages.append({ 'text': text, 'color': color, 'timestamp': timestamp }) # 限制消息数量 if len(self.messages) > self.max_messages: self.messages = self.messages[-self.max_messages:] def add_success_message(self, text): """添加成功消息""" self.add_message(f"✓ {text}", (0.176, 1.0, 0.769, 1.0)) def add_error_message(self, text): """添加错误消息""" self.add_message(f"✗ {text}", (1.0, 0.3, 0.3, 1.0)) def add_warning_message(self, text): """添加警告消息""" self.add_message(f"⚠ {text}", (0.953, 0.616, 0.471, 1.0)) def add_info_message(self, text): """添加信息消息""" self.add_message(f"ℹ {text}", (0.157, 0.620, 1.0, 1.0)) # ==================== 编辑菜单功能实现 ==================== def _on_undo(self): """处理撤销操作""" try: if hasattr(self, 'command_manager') and self.command_manager: if self.command_manager.can_undo(): success = self.command_manager.undo() if success: self.add_success_message("撤销操作成功") else: self.add_error_message("撤销操作失败") else: self.add_warning_message("没有可撤销的操作") else: self.add_error_message("命令管理器未初始化") except Exception as e: self.add_error_message(f"撤销操作失败: {e}") def _on_redo(self): """处理重做操作""" try: if hasattr(self, 'command_manager') and self.command_manager: if self.command_manager.can_redo(): success = self.command_manager.redo() if success: self.add_success_message("重做操作成功") else: self.add_error_message("重做操作失败") else: self.add_warning_message("没有可重做的操作") else: self.add_error_message("命令管理器未初始化") except Exception as e: self.add_error_message(f"重做操作失败: {e}") def _on_copy(self): """处理复制操作""" try: if not hasattr(self, 'selection') or not self.selection: self.add_error_message("选择系统未初始化") return # 获取当前选中的节点 selected_node = self.selection.selectedNode if not selected_node: self.add_warning_message("没有选中的节点") return # 检查节点有效性(不能复制根节点) if selected_node.getName() == "render": self.add_warning_message("不能复制根节点") return # 序列化节点 if hasattr(self, 'scene_manager') and self.scene_manager: node_data = self.scene_manager.serializeNodeForCopy(selected_node) if node_data: self.clipboard = [node_data] self.clipboard_mode = "copy" self.add_success_message(f"已复制节点: {selected_node.getName()}") else: self.add_error_message("节点序列化失败") else: self.add_error_message("场景管理器未初始化") except Exception as e: self.add_error_message(f"复制操作失败: {e}") def _on_cut(self): """处理剪切操作""" try: if not hasattr(self, 'selection') or not self.selection: self.add_error_message("选择系统未初始化") return # 获取当前选中的节点 selected_node = self.selection.selectedNode if not selected_node: self.add_warning_message("没有选中的节点") return # 检查节点有效性(不能剪切根节点和系统节点) node_name = selected_node.getName() if node_name == "render": self.add_warning_message("不能剪切根节点") return # 序列化节点 if hasattr(self, 'scene_manager') and self.scene_manager: node_data = self.scene_manager.serializeNodeForCopy(selected_node) if node_data: self.clipboard = [node_data] self.clipboard_mode = "cut" # 删除原节点 self.scene_manager.deleteNode(selected_node) self.selection.clearSelection() self.add_success_message(f"已剪切节点: {node_name}") else: self.add_error_message("节点序列化失败") else: self.add_error_message("场景管理器未初始化") except Exception as e: self.add_error_message(f"剪切操作失败: {e}") def _on_paste(self): """处理粘贴操作""" try: if not self.clipboard: self.add_warning_message("剪切板为空") return if not hasattr(self, 'scene_manager') or not self.scene_manager: self.add_error_message("场景管理器未初始化") return # 确定粘贴目标父节点 parent_node = None if hasattr(self, 'selection') and self.selection: selected_node = self.selection.selectedNode if selected_node: parent_node = selected_node # 如果没有选中节点,使用渲染根节点 if not parent_node: parent_node = self.render # 反序列化并添加节点 for node_data in self.clipboard: new_node = self.scene_manager.deserializeNode(node_data, parent_node) if new_node: self.add_success_message(f"已粘贴节点: {new_node.getName()}") # 如果是剪切模式,清空剪切板 if self.clipboard_mode == "cut": self.clipboard = [] self.clipboard_mode = "" else: self.add_error_message("节点反序列化失败") except Exception as e: self.add_error_message(f"粘贴操作失败: {e}") def _on_delete(self): """处理删除操作""" try: if not hasattr(self, 'selection') or not self.selection: self.add_error_message("选择系统未初始化") return # 获取当前选中的节点 selected_node = self.selection.selectedNode if not selected_node: self.add_warning_message("没有选中的节点") return # 检查节点有效性(不能删除根节点) node_name = selected_node.getName() if node_name == "render": self.add_warning_message("不能删除根节点") return # 删除节点 if hasattr(self, 'scene_manager') and self.scene_manager: self.scene_manager.deleteNode(selected_node) self.selection.clearSelection() self.add_success_message(f"已删除节点: {node_name}") else: self.add_error_message("场景管理器未初始化") except Exception as e: self.add_error_message(f"删除操作失败: {e}") # ==================== 对话框绘制函数 ==================== def _draw_new_project_dialog(self): """绘制新建项目对话框""" if not self.show_new_project_dialog: return # 初始化默认值 if not hasattr(self, 'new_project_name'): self.new_project_name = "新项目" if not hasattr(self, 'new_project_path'): self.new_project_path = "./projects/" # 设置对话框标志 flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) # 获取屏幕尺寸,居中显示对话框 display_size = imgui.get_io().display_size dialog_width = 400 dialog_height = 300 imgui.set_next_window_size((dialog_width, dialog_height)) imgui.set_next_window_pos( ((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2) ) with imgui_ctx.begin("新建项目", True, flags) as window: if not window.opened: self.show_new_project_dialog = False return imgui.text("创建新项目") imgui.separator() # 项目名称输入 changed, project_name = imgui.input_text("项目名称", self.new_project_name, 256) if changed: self.new_project_name = project_name # 项目路径输入 changed, project_path = imgui.input_text("项目路径", self.new_project_path, 256) if changed: self.new_project_path = project_path imgui.same_line() if imgui.button("浏览..."): self.path_browser_mode = "new_project" self.path_browser_current_path = os.path.dirname(self.new_project_path) if self.new_project_path else os.getcwd() self.show_path_browser = True self._refresh_path_browser() imgui.separator() # 按钮区域 if imgui.button("创建"): if self.new_project_name and self.new_project_path: self._create_new_project(self.new_project_name, self.new_project_path) self.show_new_project_dialog = False imgui.same_line() if imgui.button("取消"): self.show_new_project_dialog = False def _draw_open_project_dialog(self): """绘制打开项目对话框""" if not self.show_open_project_dialog: return # 初始化默认值 if not hasattr(self, 'open_project_path'): self.open_project_path = "./projects/" # 设置对话框标志 flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) # 获取屏幕尺寸,居中显示对话框 display_size = imgui.get_io().display_size dialog_width = 500 dialog_height = 400 imgui.set_next_window_size((dialog_width, dialog_height)) imgui.set_next_window_pos( ((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2) ) with imgui_ctx.begin("打开项目", True, flags) as window: if not window.opened: self.show_open_project_dialog = False return imgui.text("选择项目") imgui.separator() imgui.text("项目路径:") changed, project_path = imgui.input_text("##project_path", self.open_project_path, 512) if changed: self.open_project_path = project_path imgui.same_line() if imgui.button("浏览..."): self.path_browser_mode = "open_project" self.path_browser_current_path = self.open_project_path if self.open_project_path else os.getcwd() self.show_path_browser = True self._refresh_path_browser() imgui.separator() # 按钮区域 if imgui.button("打开"): if self.open_project_path: self._open_project_path(self.open_project_path) self.show_open_project_dialog = False imgui.same_line() if imgui.button("取消"): self.show_open_project_dialog = False def _draw_path_browser(self): """绘制路径选择对话框""" if not self.show_path_browser: return # 设置对话框标志 flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) # 获取屏幕尺寸,居中显示对话框 display_size = imgui.get_io().display_size dialog_width = 600 dialog_height = 500 imgui.set_next_window_size((dialog_width, dialog_height)) imgui.set_next_window_pos( ((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2) ) with imgui_ctx.begin("选择路径", True, flags) as window: if not window.opened: self.show_path_browser = False return imgui.text("选择路径") imgui.separator() # 当前路径显示 imgui.text("当前路径:") imgui.same_line() imgui.text_colored((0.7, 0.7, 0.7, 1.0), self.path_browser_current_path) imgui.separator() # 路径导航按钮 if imgui.button("上级目录"): parent_path = os.path.dirname(self.path_browser_current_path) if parent_path != self.path_browser_current_path: self.path_browser_current_path = parent_path self._refresh_path_browser() imgui.same_line() if imgui.button("主目录"): self.path_browser_current_path = os.path.expanduser("~") self._refresh_path_browser() imgui.same_line() if imgui.button("当前目录"): self.path_browser_current_path = os.getcwd() self._refresh_path_browser() imgui.separator() # 文件和目录列表 if self.path_browser_items: # 先显示目录 for item in self.path_browser_items: if item['is_dir']: # 尝试使用图标或文本标识目录 if self.icons.get('property_select_image'): # 使用现有图标作为文件夹图标 imgui.image(self.icons['property_select_image'], (16, 16)) imgui.same_line() else: imgui.text_colored((0.4, 0.6, 1.0, 1.0), ">") imgui.same_line() if imgui.selectable(item['name'], False)[0]: self.path_browser_current_path = item['path'] self._refresh_path_browser() if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): self.path_browser_current_path = item['path'] self._refresh_path_browser() # 显示文件(根据模式显示不同类型的文件) if self.path_browser_mode == "open_project": for item in self.path_browser_items: if not item['is_dir'] and item['name'].endswith('.json'): imgui.text_colored((1.0, 1.0, 0.7, 1.0), "[FILE]") imgui.same_line() if imgui.selectable(item['name'], False)[0]: self.path_browser_selected_path = item['path'] if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): # 选择包含project.json的目录 self.path_browser_current_path = os.path.dirname(item['path']) self._apply_selected_path() elif self.path_browser_mode == "import_model": for item in self.path_browser_items: if not item['is_dir']: file_ext = os.path.splitext(item['name'])[1].lower() # 根据文件类型显示不同颜色 if file_ext in ['.gltf', '.glb']: color = (0.7, 1.0, 0.7, 1.0) # 绿色 - glTF elif file_ext == '.fbx': color = (1.0, 0.7, 0.7, 1.0) # 红色 - FBX elif file_ext in ['.bam', '.egg']: color = (0.7, 0.7, 1.0, 1.0) # 蓝色 - Panda3D elif file_ext == '.obj': color = (1.0, 1.0, 0.7, 1.0) # 黄色 - OBJ else: color = (0.8, 0.8, 0.8, 1.0) # 灰色 - 其他 imgui.text_colored(color, f"[{file_ext[1:].upper()}]") imgui.same_line() if imgui.selectable(item['name'], False)[0]: self.path_browser_selected_path = item['path'] if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): self.path_browser_selected_path = item['path'] self._apply_selected_path() imgui.separator() # 选中路径显示 if self.path_browser_selected_path: imgui.text("选中路径:") imgui.same_line() imgui.text_colored((0.7, 0.7, 0.7, 1.0), self.path_browser_selected_path) # 按钮区域 if imgui.button("确定"): self._apply_selected_path() self.show_path_browser = False imgui.same_line() if imgui.button("取消"): self.show_path_browser = False def _draw_import_dialog(self): """绘制导入模型对话框""" if not self.show_import_dialog: return # 设置对话框标志 flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) # 获取屏幕尺寸,居中显示对话框 display_size = imgui.get_io().display_size dialog_width = 600 dialog_height = 500 imgui.set_next_window_size((dialog_width, dialog_height)) imgui.set_next_window_pos( ((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2) ) with imgui_ctx.begin("导入模型", True, flags) as window: if not window.opened: self.show_import_dialog = False return imgui.text("选择要导入的模型文件") imgui.separator() # 文件路径输入 imgui.text("文件路径:") changed, file_path = imgui.input_text("##import_file_path", self.import_file_path, 512) if changed: self.import_file_path = file_path imgui.same_line() if imgui.button("浏览..."): self.path_browser_mode = "import_model" self.path_browser_current_path = os.path.dirname(self.import_file_path) if self.import_file_path else os.getcwd() self.show_path_browser = True self._refresh_path_browser() imgui.separator() # 支持的格式说明 imgui.text("支持的文件格式:") formats_text = ", ".join(self.supported_formats) imgui.text_colored((0.7, 0.7, 0.7, 1.0), formats_text) imgui.separator() # 文件预览信息 if self.import_file_path and os.path.exists(self.import_file_path): file_size = os.path.getsize(self.import_file_path) imgui.text(f"文件大小: {file_size / 1024:.2f} KB") file_ext = os.path.splitext(self.import_file_path)[1].lower() if file_ext in self.supported_formats: imgui.text_colored((0.176, 1.0, 0.769, 1.0), "✓ 文件格式支持") else: imgui.text_colored((1.0, 0.3, 0.3, 1.0), "✗ 不支持的文件格式") else: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "请选择有效的文件路径") imgui.separator() # 按钮区域 can_import = (self.import_file_path and os.path.exists(self.import_file_path) and os.path.splitext(self.import_file_path)[1].lower() in self.supported_formats) # 根据状态设置按钮颜色 if can_import: if imgui.button("导入"): self._import_model() self.show_import_dialog = False else: # 禁用状态的按钮(灰色显示) imgui.push_style_color(imgui.Col_.button, (0.3, 0.3, 0.3, 1.0)) imgui.button("导入") imgui.pop_style_color() imgui.same_line() if imgui.button("取消"): self.show_import_dialog = False def _create_new_project(self, name, path): """创建新项目的实际实现""" if not hasattr(self, 'project_manager') or not self.project_manager: print("✗ 项目管理器未初始化") return try: if self._create_new_project_impl(name, path): print(f"✓ 项目创建成功: {name}") else: print(f"✗ 项目创建失败: {name}") except Exception as e: print(f"✗ 项目创建失败: {e}") def _open_project_path(self, path): """打开项目的实际实现""" if not hasattr(self, 'project_manager') or not self.project_manager: print("✗ 项目管理器未初始化") return try: print(f"打开项目: {path}") if self._open_project_impl(path): print(f"✓ 项目打开成功: {path}") else: print(f"✗ 项目打开失败: {path}") except Exception as e: print(f"✗ 项目打开失败: {e}") # ==================== 项目管理具体实现 ==================== def _save_project_impl(self): """保存项目的具体实现(不依赖Qt)""" import json import datetime import os project_path = self.project_manager.current_project_path scenes_path = os.path.join(project_path, "scenes") # 固定的场景文件名 scene_file = os.path.join(scenes_path, "scene.bam") # 如果存在旧文件,先删除 if os.path.exists(scene_file): try: os.remove(scene_file) print(f"已删除旧场景文件: {scene_file}") except Exception as e: print(f"删除旧场景文件失败: {str(e)}") return False # 保存场景 if self.scene_manager.saveScene(scene_file, project_path): # 更新项目配置文件 config_file = os.path.join(project_path, "project.json") if os.path.exists(config_file): with open(config_file, "r", encoding="utf-8") as f: project_config = json.load(f) # 更新最后修改时间 project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 记录场景文件路径 project_config["scene_file"] = os.path.relpath(scene_file, project_path) with open(config_file, "w", encoding="utf-8") as f: json.dump(project_config, f, ensure_ascii=False, indent=4) # 更新项目配置 self.project_manager.project_config = project_config return True return False def _open_project_impl(self, project_path): """打开项目的具体实现(不依赖Qt)""" import json import datetime import os # 检查是否是有效的项目文件夹 config_file = os.path.join(project_path, "project.json") if not os.path.exists(config_file): print(f"⚠ 选择的不是有效的项目文件夹: {project_path}") return False # 读取项目配置 with open(config_file, "r", encoding="utf-8") as f: project_config = json.load(f) # 检查场景文件 scene_file = os.path.join(project_path, "scenes", "scene.bam") if os.path.exists(scene_file): # 加载场景 if self.scene_manager.loadScene(scene_file): # 更新项目配置 project_config["scene_file"] = os.path.relpath(scene_file, project_path) project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") with open(config_file, "w", encoding="utf-8") as f: json.dump(project_config, f, ensure_ascii=False, indent=4) # 更新项目状态 self.project_manager.current_project_path = project_path self.project_manager.project_config = project_config # 更新窗口标题 project_name = os.path.basename(project_path) self._update_window_title(project_name) return True def _create_new_project_impl(self, name, path): """创建新项目的具体实现(不依赖Qt)""" import json import datetime import os full_project_path = os.path.normpath(os.path.join(path, name)) print(f"创建项目路径: {full_project_path}") try: # 创建项目文件夹结构 os.makedirs(full_project_path) os.makedirs(os.path.join(full_project_path, "models")) # 模型文件夹 os.makedirs(os.path.join(full_project_path, "textures")) # 贴图文件夹 scenes_path = os.path.join(full_project_path, "scenes") # 场景文件夹 os.makedirs(scenes_path) # 创建项目配置文件 project_config = { "name": name, "path": full_project_path, "created": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "last_modified": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "version": "1.0", "scene_file": "scenes/scene.bam" } # 保存项目配置 config_file = os.path.join(full_project_path, "project.json") with open(config_file, "w", encoding="utf-8") as f: json.dump(project_config, f, ensure_ascii=False, indent=4) # 保存初始场景 scene_file = os.path.join(scenes_path, "scene.bam") self.scene_manager.saveScene(scene_file, full_project_path) # 更新项目管理器状态 self.project_manager.current_project_path = full_project_path self.project_manager.project_config = project_config # 更新窗口标题 self._update_window_title(name) return True except Exception as e: print(f"创建项目失败: {e}") return False def _update_window_title(self, project_name): """更新窗口标题""" try: props = WindowProperties() props.set_title(f"EG Engine - {project_name}") self.win.request_properties(props) print(f"窗口标题已更新: EG Engine - {project_name}") except Exception as e: print(f"更新窗口标题失败: {e}") # ==================== 路径浏览器辅助方法 ==================== def _refresh_path_browser(self): """刷新路径浏览器内容""" try: self.path_browser_items = [] if not os.path.exists(self.path_browser_current_path): self.add_error_message(f"路径不存在: {self.path_browser_current_path}") return # 获取目录中的所有项目 items = [] try: for item_name in os.listdir(self.path_browser_current_path): item_path = os.path.join(self.path_browser_current_path, item_name) is_dir = os.path.isdir(item_path) items.append({ 'name': item_name, 'path': item_path, 'is_dir': is_dir }) except PermissionError: self.add_error_message(f"无法访问路径: {self.path_browser_current_path}") return # 根据模式过滤文件 if self.path_browser_mode == "import_model": # 只显示支持的模型文件 filtered_items = [] for item in items: if item['is_dir']: filtered_items.append(item) else: file_ext = os.path.splitext(item['name'])[1].lower() if file_ext in self.supported_formats: filtered_items.append(item) items = filtered_items # 排序:目录在前,文件在后,按名称排序 items.sort(key=lambda x: (not x['is_dir'], x['name'].lower())) self.path_browser_items = items except Exception as e: self.add_error_message(f"刷新路径浏览器失败: {e}") def _apply_selected_path(self): """应用选择的路径""" try: if self.path_browser_mode == "new_project": # 新建项目模式:直接使用当前路径 self.new_project_path = self.path_browser_current_path self.add_info_message(f"已选择项目路径: {self.new_project_path}") elif self.path_browser_mode == "open_project": # 打开项目模式:使用当前路径 self.open_project_path = self.path_browser_current_path self.add_info_message(f"已选择项目路径: {self.open_project_path}") elif self.path_browser_mode == "import_model": # 导入模型模式:使用选择的文件路径 self.import_file_path = self.path_browser_selected_path self.add_info_message(f"已选择文件: {self.import_file_path}") except Exception as e: self.add_error_message(f"应用路径失败: {e}") # ==================== 创建功能对话框实现 ==================== def _draw_gui_button_dialog(self): """绘制GUI按钮创建对话框""" if not self.show_gui_button_dialog: return flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) with imgui_ctx.begin("创建GUI按钮", self.show_gui_button_dialog, flags) as window: if not window: self.show_gui_button_dialog = False return # 初始化参数 if 'button_text' not in self.dialog_params: self.dialog_params['button_text'] = "按钮" if 'button_pos' not in self.dialog_params: self.dialog_params['button_pos'] = [0.0, 0.0, 0.0] if 'button_size' not in self.dialog_params: self.dialog_params['button_size'] = [0.1, 0.1, 0.1] imgui.text("GUI按钮参数设置") imgui.separator() # 文本输入 changed, self.dialog_params['button_text'] = imgui.input_text("按钮文本", self.dialog_params['button_text'], 256) # 位置输入 changed, x = imgui.input_float("X坐标", self.dialog_params['button_pos'][0]) if changed: self.dialog_params['button_pos'][0] = x changed, y = imgui.input_float("Y坐标", self.dialog_params['button_pos'][1]) if changed: self.dialog_params['button_pos'][1] = y changed, z = imgui.input_float("Z坐标", self.dialog_params['button_pos'][2]) if changed: self.dialog_params['button_pos'][2] = z # 大小输入 changed, width = imgui.input_float("宽度", self.dialog_params['button_size'][0]) if changed: self.dialog_params['button_size'][0] = width changed, height = imgui.input_float("高度", self.dialog_params['button_size'][1]) if changed: self.dialog_params['button_size'][1] = height imgui.separator() # 按钮 if imgui.button("创建"): try: pos = tuple(self.dialog_params['button_pos']) text = self.dialog_params['button_text'] size = tuple(self.dialog_params['button_size'][:2]) result = self.createGUIButton(pos, text, size) self.add_success_message(f"GUI按钮创建成功: {text}") self.show_gui_button_dialog = False except Exception as e: self.add_error_message(f"创建GUI按钮失败: {str(e)}") imgui.same_line() if imgui.button("取消"): self.show_gui_button_dialog = False def _draw_gui_label_dialog(self): """绘制GUI标签创建对话框""" if not self.show_gui_label_dialog: return flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) with imgui_ctx.begin("创建GUI标签", self.show_gui_label_dialog, flags) as window: if not window: self.show_gui_label_dialog = False return # 初始化参数 if 'label_text' not in self.dialog_params: self.dialog_params['label_text'] = "标签" if 'label_pos' not in self.dialog_params: self.dialog_params['label_pos'] = [0.0, 0.0, 0.0] if 'label_size' not in self.dialog_params: self.dialog_params['label_size'] = [0.1, 0.1, 0.1] imgui.text("GUI标签参数设置") imgui.separator() # 文本输入 changed, self.dialog_params['label_text'] = imgui.input_text("标签文本", self.dialog_params['label_text'], 256) # 位置输入 changed, x = imgui.input_float("X坐标", self.dialog_params['label_pos'][0]) if changed: self.dialog_params['label_pos'][0] = x changed, y = imgui.input_float("Y坐标", self.dialog_params['label_pos'][1]) if changed: self.dialog_params['label_pos'][1] = y changed, z = imgui.input_float("Z坐标", self.dialog_params['label_pos'][2]) if changed: self.dialog_params['label_pos'][2] = z # 大小输入 changed, width = imgui.input_float("宽度", self.dialog_params['label_size'][0]) if changed: self.dialog_params['label_size'][0] = width changed, height = imgui.input_float("高度", self.dialog_params['label_size'][1]) if changed: self.dialog_params['label_size'][1] = height imgui.separator() # 按钮 if imgui.button("创建"): try: pos = tuple(self.dialog_params['label_pos']) text = self.dialog_params['label_text'] size = tuple(self.dialog_params['label_size'][:2]) result = self.createGUILabel(pos, text, size) self.add_success_message(f"GUI标签创建成功: {text}") self.show_gui_label_dialog = False except Exception as e: self.add_error_message(f"创建GUI标签失败: {str(e)}") imgui.same_line() if imgui.button("取消"): self.show_gui_label_dialog = False def _draw_gui_entry_dialog(self): """绘制GUI输入框创建对话框""" if not self.show_gui_entry_dialog: return flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) with imgui_ctx.begin("创建GUI输入框", self.show_gui_entry_dialog, flags) as window: if not window: self.show_gui_entry_dialog = False return # 初始化参数 if 'entry_pos' not in self.dialog_params: self.dialog_params['entry_pos'] = [0.0, 0.0, 0.0] if 'entry_size' not in self.dialog_params: self.dialog_params['entry_size'] = [0.2, 0.05, 0.1] imgui.text("GUI输入框参数设置") imgui.separator() # 位置输入 changed, x = imgui.input_float("X坐标", self.dialog_params['entry_pos'][0]) if changed: self.dialog_params['entry_pos'][0] = x changed, y = imgui.input_float("Y坐标", self.dialog_params['entry_pos'][1]) if changed: self.dialog_params['entry_pos'][1] = y changed, z = imgui.input_float("Z坐标", self.dialog_params['entry_pos'][2]) if changed: self.dialog_params['entry_pos'][2] = z # 大小输入 changed, width = imgui.input_float("宽度", self.dialog_params['entry_size'][0]) if changed: self.dialog_params['entry_size'][0] = width changed, height = imgui.input_float("高度", self.dialog_params['entry_size'][1]) if changed: self.dialog_params['entry_size'][1] = height imgui.separator() # 按钮 if imgui.button("创建"): try: pos = tuple(self.dialog_params['entry_pos']) size = tuple(self.dialog_params['entry_size'][:2]) result = self.createGUIEntry(pos, size) self.add_success_message("GUI输入框创建成功") self.show_gui_entry_dialog = False except Exception as e: self.add_error_message(f"创建GUI输入框失败: {str(e)}") imgui.same_line() if imgui.button("取消"): self.show_gui_entry_dialog = False def _draw_gui_image_dialog(self): """绘制GUI图片创建对话框""" if not self.show_gui_image_dialog: return flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) with imgui_ctx.begin("创建GUI图片", self.show_gui_image_dialog, flags) as window: if not window: self.show_gui_image_dialog = False return # 初始化参数 if 'image_pos' not in self.dialog_params: self.dialog_params['image_pos'] = [0.0, 0.0, 0.0] if 'image_size' not in self.dialog_params: self.dialog_params['image_size'] = [0.2, 0.2, 0.1] if 'image_path' not in self.dialog_params: self.dialog_params['image_path'] = "" imgui.text("GUI图片参数设置") imgui.separator() # 位置输入 changed, x = imgui.input_float("X坐标", self.dialog_params['image_pos'][0]) if changed: self.dialog_params['image_pos'][0] = x changed, y = imgui.input_float("Y坐标", self.dialog_params['image_pos'][1]) if changed: self.dialog_params['image_pos'][1] = y changed, z = imgui.input_float("Z坐标", self.dialog_params['image_pos'][2]) if changed: self.dialog_params['image_pos'][2] = z # 大小输入 changed, width = imgui.input_float("宽度", self.dialog_params['image_size'][0]) if changed: self.dialog_params['image_size'][0] = width changed, height = imgui.input_float("高度", self.dialog_params['image_size'][1]) if changed: self.dialog_params['image_size'][1] = height # 图片路径 changed, self.dialog_params['image_path'] = imgui.input_text("图片路径", self.dialog_params['image_path'], 512) imgui.separator() # 按钮 if imgui.button("创建"): try: pos = tuple(self.dialog_params['image_pos']) size = tuple(self.dialog_params['image_size'][:2]) image_path = self.dialog_params['image_path'] result = self.createGUIImage(pos, image_path, size) self.add_success_message("GUI图片创建成功") self.show_gui_image_dialog = False except Exception as e: self.add_error_message(f"创建GUI图片失败: {str(e)}") imgui.same_line() if imgui.button("取消"): self.show_gui_image_dialog = False def _draw_3d_text_dialog(self): """绘制3D文本创建对话框""" if not self.show_3d_text_dialog: return flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) with imgui_ctx.begin("创建3D文本", self.show_3d_text_dialog, flags) as window: if not window: self.show_3d_text_dialog = False return # 初始化参数 if 'text3d_text' not in self.dialog_params: self.dialog_params['text3d_text'] = "3D文本" if 'text3d_pos' not in self.dialog_params: self.dialog_params['text3d_pos'] = [0.0, 0.0, 0.0] if 'text3d_size' not in self.dialog_params: self.dialog_params['text3d_size'] = 1.0 imgui.text("3D文本参数设置") imgui.separator() # 文本输入 changed, self.dialog_params['text3d_text'] = imgui.input_text("文本内容", self.dialog_params['text3d_text'], 256) # 位置输入 changed, x = imgui.input_float("X坐标", self.dialog_params['text3d_pos'][0]) if changed: self.dialog_params['text3d_pos'][0] = x changed, y = imgui.input_float("Y坐标", self.dialog_params['text3d_pos'][1]) if changed: self.dialog_params['text3d_pos'][1] = y changed, z = imgui.input_float("Z坐标", self.dialog_params['text3d_pos'][2]) if changed: self.dialog_params['text3d_pos'][2] = z # 大小输入 changed, self.dialog_params['text3d_size'] = imgui.input_float("文本大小", self.dialog_params['text3d_size']) imgui.separator() # 按钮 if imgui.button("创建"): try: pos = tuple(self.dialog_params['text3d_pos']) text = self.dialog_params['text3d_text'] size = self.dialog_params['text3d_size'] result = self.create3DText(pos, text, size) self.add_success_message(f"3D文本创建成功: {text}") self.show_3d_text_dialog = False except Exception as e: self.add_error_message(f"创建3D文本失败: {str(e)}") imgui.same_line() if imgui.button("取消"): self.show_3d_text_dialog = False def _draw_3d_image_dialog(self): """绘制3D图片创建对话框""" if not self.show_3d_image_dialog: return flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) with imgui_ctx.begin("创建3D图片", self.show_3d_image_dialog, flags) as window: if not window: self.show_3d_image_dialog = False return # 初始化参数 if 'image3d_pos' not in self.dialog_params: self.dialog_params['image3d_pos'] = [0.0, 0.0, 0.0] if 'image3d_size' not in self.dialog_params: self.dialog_params['image3d_size'] = [1.0, 1.0, 1.0] if 'image3d_path' not in self.dialog_params: self.dialog_params['image3d_path'] = "" imgui.text("3D图片参数设置") imgui.separator() # 位置输入 changed, x = imgui.input_float("X坐标", self.dialog_params['image3d_pos'][0]) if changed: self.dialog_params['image3d_pos'][0] = x changed, y = imgui.input_float("Y坐标", self.dialog_params['image3d_pos'][1]) if changed: self.dialog_params['image3d_pos'][1] = y changed, z = imgui.input_float("Z坐标", self.dialog_params['image3d_pos'][2]) if changed: self.dialog_params['image3d_pos'][2] = z # 大小输入 changed, width = imgui.input_float("宽度", self.dialog_params['image3d_size'][0]) if changed: self.dialog_params['image3d_size'][0] = width changed, height = imgui.input_float("高度", self.dialog_params['image3d_size'][1]) if changed: self.dialog_params['image3d_size'][1] = height # 图片路径 changed, self.dialog_params['image3d_path'] = imgui.input_text("图片路径", self.dialog_params['image3d_path'], 512) imgui.separator() # 按钮 if imgui.button("创建"): try: pos = tuple(self.dialog_params['image3d_pos']) size = tuple(self.dialog_params['image3d_size'][:2]) image_path = self.dialog_params['image3d_path'] result = self.create3DImage(pos, image_path, size) self.add_success_message("3D图片创建成功") self.show_3d_image_dialog = False except Exception as e: self.add_error_message(f"创建3D图片失败: {str(e)}") imgui.same_line() if imgui.button("取消"): self.show_3d_image_dialog = False # 添加其他创建对话框的占位符方法 def _draw_video_screen_dialog(self): """绘制视频屏幕创建对话框""" if not self.show_video_screen_dialog: return self.show_video_screen_dialog = False self.add_info_message("视频屏幕创建功能开发中...") def _draw_2d_video_screen_dialog(self): """绘制2D视频屏幕创建对话框""" if not self.show_2d_video_screen_dialog: return self.show_2d_video_screen_dialog = False self.add_info_message("2D视频屏幕创建功能开发中...") def _draw_spherical_video_dialog(self): """绘制球形视频创建对话框""" if not self.show_spherical_video_dialog: return self.show_spherical_video_dialog = False self.add_info_message("球形视频创建功能开发中...") def _draw_virtual_screen_dialog(self): """绘制虚拟屏幕创建对话框""" if not self.show_virtual_screen_dialog: return self.show_virtual_screen_dialog = False self.add_info_message("虚拟屏幕创建功能开发中...") def _draw_spot_light_dialog(self): """绘制聚光灯创建对话框""" if not self.show_spot_light_dialog: return flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) with imgui_ctx.begin("创建聚光灯", self.show_spot_light_dialog, flags) as window: if not window: self.show_spot_light_dialog = False return # 初始化参数 if 'spotlight_pos' not in self.dialog_params: self.dialog_params['spotlight_pos'] = [0.0, 0.0, 5.0] if 'spotlight_color' not in self.dialog_params: self.dialog_params['spotlight_color'] = [1.0, 1.0, 1.0] if 'spotlight_intensity' not in self.dialog_params: self.dialog_params['spotlight_intensity'] = 1.0 imgui.text("聚光灯参数设置") imgui.separator() # 位置输入 changed, x = imgui.input_float("X坐标", self.dialog_params['spotlight_pos'][0]) if changed: self.dialog_params['spotlight_pos'][0] = x changed, y = imgui.input_float("Y坐标", self.dialog_params['spotlight_pos'][1]) if changed: self.dialog_params['spotlight_pos'][1] = y changed, z = imgui.input_float("Z坐标", self.dialog_params['spotlight_pos'][2]) if changed: self.dialog_params['spotlight_pos'][2] = z # 颜色输入 changed, r = imgui.input_float("红色 (R)", self.dialog_params['spotlight_color'][0], 0.0, 1.0) if changed: self.dialog_params['spotlight_color'][0] = max(0.0, min(1.0, r)) changed, g = imgui.input_float("绿色 (G)", self.dialog_params['spotlight_color'][1], 0.0, 1.0) if changed: self.dialog_params['spotlight_color'][1] = max(0.0, min(1.0, g)) changed, b = imgui.input_float("蓝色 (B)", self.dialog_params['spotlight_color'][2], 0.0, 1.0) if changed: self.dialog_params['spotlight_color'][2] = max(0.0, min(1.0, b)) # 强度输入 changed, self.dialog_params['spotlight_intensity'] = imgui.input_float("强度", self.dialog_params['spotlight_intensity'], 0.1, 2.0) if changed: self.dialog_params['spotlight_intensity'] = max(0.1, self.dialog_params['spotlight_intensity']) imgui.separator() # 按钮 if imgui.button("创建"): try: pos = tuple(self.dialog_params['spotlight_pos']) result = self.createSpotLight(pos) if result: # 设置颜色和强度 light = result.node() if hasattr(light, 'setColor'): color = tuple(self.dialog_params['spotlight_color']) light.setColor(color + (1.0,)) # 添加alpha通道 if hasattr(light, 'setEnergy'): light.setEnergy(self.dialog_params['spotlight_intensity']) self.add_success_message("聚光灯创建成功") self.show_spot_light_dialog = False else: self.add_error_message("聚光灯创建失败") except Exception as e: self.add_error_message(f"创建聚光灯失败: {str(e)}") imgui.same_line() if imgui.button("取消"): self.show_spot_light_dialog = False def _draw_point_light_dialog(self): """绘制点光源创建对话框""" if not self.show_point_light_dialog: return flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) with imgui_ctx.begin("创建点光源", self.show_point_light_dialog, flags) as window: if not window: self.show_point_light_dialog = False return # 初始化参数 if 'pointlight_pos' not in self.dialog_params: self.dialog_params['pointlight_pos'] = [0.0, 0.0, 5.0] if 'pointlight_color' not in self.dialog_params: self.dialog_params['pointlight_color'] = [1.0, 1.0, 1.0] if 'pointlight_intensity' not in self.dialog_params: self.dialog_params['pointlight_intensity'] = 1.0 if 'pointlight_radius' not in self.dialog_params: self.dialog_params['pointlight_radius'] = 10.0 imgui.text("点光源参数设置") imgui.separator() # 位置输入 changed, x = imgui.input_float("X坐标", self.dialog_params['pointlight_pos'][0]) if changed: self.dialog_params['pointlight_pos'][0] = x changed, y = imgui.input_float("Y坐标", self.dialog_params['pointlight_pos'][1]) if changed: self.dialog_params['pointlight_pos'][1] = y changed, z = imgui.input_float("Z坐标", self.dialog_params['pointlight_pos'][2]) if changed: self.dialog_params['pointlight_pos'][2] = z # 颜色输入 changed, r = imgui.input_float("红色 (R)", self.dialog_params['pointlight_color'][0], 0.0, 1.0) if changed: self.dialog_params['pointlight_color'][0] = max(0.0, min(1.0, r)) changed, g = imgui.input_float("绿色 (G)", self.dialog_params['pointlight_color'][1], 0.0, 1.0) if changed: self.dialog_params['pointlight_color'][1] = max(0.0, min(1.0, g)) changed, b = imgui.input_float("蓝色 (B)", self.dialog_params['pointlight_color'][2], 0.0, 1.0) if changed: self.dialog_params['pointlight_color'][2] = max(0.0, min(1.0, b)) # 强度输入 changed, self.dialog_params['pointlight_intensity'] = imgui.input_float("强度", self.dialog_params['pointlight_intensity'], 0.1, 2.0) if changed: self.dialog_params['pointlight_intensity'] = max(0.1, self.dialog_params['pointlight_intensity']) # 半径输入 changed, self.dialog_params['pointlight_radius'] = imgui.input_float("影响半径", self.dialog_params['pointlight_radius'], 1.0, 50.0) if changed: self.dialog_params['pointlight_radius'] = max(1.0, self.dialog_params['pointlight_radius']) imgui.separator() # 按钮 if imgui.button("创建"): try: pos = tuple(self.dialog_params['pointlight_pos']) result = self.createPointLight(pos) if result: # 设置颜色和强度 light = result.node() if hasattr(light, 'setColor'): color = tuple(self.dialog_params['pointlight_color']) light.setColor(color + (1.0,)) # 添加alpha通道 if hasattr(light, 'setEnergy'): light.setEnergy(self.dialog_params['pointlight_intensity']) if hasattr(light, 'setAttenuation'): # 设置衰减: (constant, linear, quadratic) radius = self.dialog_params['pointlight_radius'] light.setAttenuation((1.0, 0.5/radius, 0.5/(radius*radius))) self.add_success_message("点光源创建成功") self.show_point_light_dialog = False else: self.add_error_message("点光源创建失败") except Exception as e: self.add_error_message(f"创建点光源失败: {str(e)}") imgui.same_line() if imgui.button("取消"): self.show_point_light_dialog = False def _draw_terrain_dialog(self): """绘制地形创建对话框""" if not self.show_terrain_dialog: return flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) with imgui_ctx.begin("创建平面地形", self.show_terrain_dialog, flags) as window: if not window: self.show_terrain_dialog = False return # 初始化参数 if 'terrain_width' not in self.dialog_params: self.dialog_params['terrain_width'] = 10.0 if 'terrain_height' not in self.dialog_params: self.dialog_params['terrain_height'] = 10.0 if 'terrain_resolution' not in self.dialog_params: self.dialog_params['terrain_resolution'] = 129 imgui.text("平面地形参数设置") imgui.separator() # 尺寸输入 changed, self.dialog_params['terrain_width'] = imgui.input_float("宽度", self.dialog_params['terrain_width'], 1.0, 100.0) if changed: self.dialog_params['terrain_width'] = max(1.0, self.dialog_params['terrain_width']) changed, self.dialog_params['terrain_height'] = imgui.input_float("高度", self.dialog_params['terrain_height'], 1.0, 100.0) if changed: self.dialog_params['terrain_height'] = max(1.0, self.dialog_params['terrain_height']) # 分辨率输入 changed, self.dialog_params['terrain_resolution'] = imgui.input_int("分辨率", self.dialog_params['terrain_resolution']) if changed: # 确保分辨率是有效的 (2的幂次方 + 1) valid_resolutions = [17, 33, 65, 129, 257, 513, 1025] if self.dialog_params['terrain_resolution'] not in valid_resolutions: closest_res = min(valid_resolutions, key=lambda x: abs(x - self.dialog_params['terrain_resolution'])) self.dialog_params['terrain_resolution'] = closest_res imgui.separator() imgui.text("有效分辨率值: 17, 33, 65, 129, 257, 513, 1025") imgui.separator() # 按钮 if imgui.button("创建"): try: width = self.dialog_params['terrain_width'] height = self.dialog_params['terrain_height'] resolution = self.dialog_params['terrain_resolution'] # 转换为地形管理器期望的格式 size = (width, height) result = self.createFlatTerrain(size, resolution) if result: self.add_success_message("平面地形创建成功") self.show_terrain_dialog = False else: self.add_error_message("平面地形创建失败") except Exception as e: self.add_error_message(f"创建平面地形失败: {str(e)}") imgui.same_line() if imgui.button("取消"): self.show_terrain_dialog = False def _draw_script_dialog(self): """绘制脚本创建对话框""" if not self.show_script_dialog: return flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) with imgui_ctx.begin("创建脚本", self.show_script_dialog, flags) as window: if not window: self.show_script_dialog = False return # 初始化参数 if 'script_name' not in self.dialog_params: self.dialog_params['script_name'] = "new_script" if 'script_template' not in self.dialog_params: self.dialog_params['script_template'] = 0 # 0=basic, 1=movement imgui.text("脚本参数设置") imgui.separator() # 脚本名称输入 changed, self.dialog_params['script_name'] = imgui.input_text("脚本名称", self.dialog_params['script_name'], 256) # 模板选择 templates = ["基础模板", "移动模板"] changed, self.dialog_params['script_template'] = imgui.combo("模板类型", self.dialog_params['script_template'], templates) imgui.separator() # 模板说明 if self.dialog_params['script_template'] == 0: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "基础模板: 包含基本的脚本结构") else: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "移动模板: 包含移动相关的基本功能") imgui.separator() # 按钮 if imgui.button("创建"): try: script_name = self.dialog_params['script_name'] if not script_name: self.add_error_message("请输入脚本名称") return template = "basic" if self.dialog_params['script_template'] == 0 else "movement" result = self.createScript(script_name, template) if result: self.add_success_message(f"脚本创建成功: {script_name}") # 如果启用了热重载,自动加载新脚本 if self.hotReloadEnabled: try: self.loadScript(result) self.add_info_message(f"脚本已自动加载: {script_name}") except Exception as e: self.add_warning_message(f"脚本自动加载失败: {str(e)}") self.show_script_dialog = False else: self.add_error_message("脚本创建失败") except Exception as e: self.add_error_message(f"创建脚本失败: {str(e)}") imgui.same_line() if imgui.button("取消"): self.show_script_dialog = False def _draw_script_browser(self): """绘制脚本文件浏览器""" if not self.show_script_browser: return flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) with imgui_ctx.begin("选择脚本文件", self.show_script_browser, flags) as window: if not window: self.show_script_browser = False return imgui.text("选择脚本文件") imgui.separator() # 导航按钮 if imgui.button("上级目录"): parent_path = os.path.dirname(self.script_browser_current_path) if parent_path != self.script_browser_current_path: self.script_browser_current_path = parent_path self._refresh_script_browser() imgui.same_line() if imgui.button("脚本目录"): # 切换到脚本目录 if hasattr(self, 'script_manager') and self.script_manager: scripts_dir = self.script_manager.scripts_directory if os.path.exists(scripts_dir): self.script_browser_current_path = scripts_dir self._refresh_script_browser() imgui.same_line() if imgui.button("当前目录"): self.script_browser_current_path = os.getcwd() self._refresh_script_browser() imgui.separator() # 当前路径显示 imgui.text("当前路径:") imgui.same_line() imgui.text_colored((0.7, 0.7, 0.7, 1.0), self.script_browser_current_path) imgui.separator() # 文件列表 if imgui.begin_child("script_file_list", (580, 300)): for item in self.script_browser_items: if item['is_dir']: # 目录 imgui.text_colored((0.3, 0.8, 1.0, 1.0), f"📁 {item['name']}") if imgui.is_item_clicked(): self.script_browser_current_path = item['path'] self._refresh_script_browser() else: # Python文件 imgui.text(f"📄 {item['name']}") if imgui.is_item_clicked(): self.script_browser_selected_path = item['path'] imgui.end_child() imgui.separator() # 选中的文件信息 if self.script_browser_selected_path and os.path.exists(self.script_browser_selected_path): file_size = os.path.getsize(self.script_browser_selected_path) imgui.text(f"文件大小: {file_size / 1024:.2f} KB") file_ext = os.path.splitext(self.script_browser_selected_path)[1].lower() if file_ext == '.py': imgui.text_colored((0.176, 1.0, 0.769, 1.0), "✓ Python脚本文件") else: imgui.text_colored((1.0, 0.3, 0.3, 1.0), "✗ 不是Python脚本文件") else: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "请选择有效的Python脚本文件") imgui.separator() # 按钮 can_load = (self.script_browser_selected_path and os.path.exists(self.script_browser_selected_path) and os.path.splitext(self.script_browser_selected_path)[1].lower() == '.py') if can_load: if imgui.button("加载脚本"): try: result = self.loadScript(self.script_browser_selected_path) if result: script_name = os.path.basename(self.script_browser_selected_path) self.add_success_message(f"脚本加载成功: {script_name}") self.show_script_browser = False else: self.add_error_message("脚本加载失败") except Exception as e: self.add_error_message(f"加载脚本失败: {str(e)}") else: imgui.push_style_var(imgui.StyleVar_.alpha, 0.5) imgui.button("加载脚本") imgui.pop_style_var() imgui.same_line() if imgui.button("取消"): self.show_script_browser = False self.script_browser_selected_path = "" def _refresh_script_browser(self): """刷新脚本浏览器内容""" try: self.script_browser_items = [] if not os.path.exists(self.script_browser_current_path): self.script_browser_current_path = os.getcwd() # 获取目录中的所有项目 items = [] try: for item_name in os.listdir(self.script_browser_current_path): item_path = os.path.join(self.script_browser_current_path, item_name) if os.path.isdir(item_path): items.append({ 'name': item_name, 'path': item_path, 'is_dir': True }) elif os.path.isfile(item_path): file_ext = os.path.splitext(item_name)[1].lower() if file_ext == '.py': # 只显示Python文件 items.append({ 'name': item_name, 'path': item_path, 'is_dir': False }) except PermissionError: print(f"权限错误: 无法访问目录 {self.script_browser_current_path}") # 排序:目录在前,文件在后 items.sort(key=lambda x: (not x['is_dir'], x['name'].lower())) self.script_browser_items = items except Exception as e: print(f"刷新脚本浏览器时出错: {e}") self.script_browser_items = [] def _draw_heightmap_browser(self): """绘制高度图文件浏览器""" if not self.show_heightmap_browser: return flags = (imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.modal) with imgui_ctx.begin("选择高度图文件", self.show_heightmap_browser, flags) as window: if not window: self.show_heightmap_browser = False return imgui.text("选择高度图文件") imgui.separator() # 导航按钮 if imgui.button("上级目录"): parent_path = os.path.dirname(self.heightmap_browser_current_path) if parent_path != self.heightmap_browser_current_path: self.heightmap_browser_current_path = parent_path self._refresh_heightmap_browser() imgui.same_line() if imgui.button("主目录"): self.heightmap_browser_current_path = os.getcwd() self._refresh_heightmap_browser() imgui.same_line() if imgui.button("当前目录"): self.heightmap_browser_current_path = os.getcwd() self._refresh_heightmap_browser() imgui.separator() # 当前路径显示 imgui.text("当前路径:") imgui.same_line() imgui.text_colored((0.7, 0.7, 0.7, 1.0), self.heightmap_browser_current_path) imgui.separator() # 文件列表 if imgui.begin_child("file_list", (580, 300)): for item in self.heightmap_browser_items: if item['is_dir']: # 目录 imgui.text_colored((0.3, 0.8, 1.0, 1.0), f"📁 {item['name']}") if imgui.is_item_clicked(): self.heightmap_browser_current_path = item['path'] self._refresh_heightmap_browser() else: # 文件 imgui.text(f"📄 {item['name']}") if imgui.is_item_clicked(): self.heightmap_browser_selected_path = item['path'] self.heightmap_file_path = item['path'] imgui.end_child() imgui.separator() # 支持的格式说明 imgui.text("支持的文件格式:") formats_text = ", ".join(self.supported_heightmap_formats) imgui.text_colored((0.7, 0.7, 0.7, 1.0), formats_text) imgui.separator() # 选中的文件信息 if self.heightmap_file_path and os.path.exists(self.heightmap_file_path): file_size = os.path.getsize(self.heightmap_file_path) imgui.text(f"文件大小: {file_size / 1024:.2f} KB") file_ext = os.path.splitext(self.heightmap_file_path)[1].lower() if file_ext in self.supported_heightmap_formats: imgui.text_colored((0.176, 1.0, 0.769, 1.0), "✓ 文件格式支持") else: imgui.text_colored((1.0, 0.3, 0.3, 1.0), "✗ 不支持的文件格式") else: imgui.text_colored((0.7, 0.7, 0.7, 1.0), "请选择有效的高度图文件") imgui.separator() # 按钮 can_import = (self.heightmap_file_path and os.path.exists(self.heightmap_file_path) and os.path.splitext(self.heightmap_file_path)[1].lower() in self.supported_heightmap_formats) if can_import: if imgui.button("创建地形"): try: # 使用默认缩放参数创建地形 scale = (1.0, 1.0, 10.0) # X, Y, Z缩放 result = self.createTerrainFromHeightMap(self.heightmap_file_path, scale) if result: self.add_success_message("高度图地形创建成功") self.show_heightmap_browser = False else: self.add_error_message("高度图地形创建失败") except Exception as e: self.add_error_message(f"创建高度图地形失败: {str(e)}") else: imgui.push_style_var(imgui.StyleVar_.alpha, 0.5) imgui.button("创建地形") imgui.pop_style_var() imgui.same_line() if imgui.button("取消"): self.show_heightmap_browser = False self.heightmap_file_path = "" def _refresh_heightmap_browser(self): """刷新高度图浏览器内容""" try: self.heightmap_browser_items = [] if not os.path.exists(self.heightmap_browser_current_path): self.heightmap_browser_current_path = os.getcwd() # 获取目录中的所有项目 items = [] try: for item_name in os.listdir(self.heightmap_browser_current_path): item_path = os.path.join(self.heightmap_browser_current_path, item_name) if os.path.isdir(item_path): items.append({ 'name': item_name, 'path': item_path, 'is_dir': True }) elif os.path.isfile(item_path): file_ext = os.path.splitext(item_name)[1].lower() if file_ext in self.supported_heightmap_formats: items.append({ 'name': item_name, 'path': item_path, 'is_dir': False }) except PermissionError: print(f"权限错误: 无法访问目录 {self.heightmap_browser_current_path}") # 排序:目录在前,文件在后 items.sort(key=lambda x: (not x['is_dir'], x['name'].lower())) self.heightmap_browser_items = items except Exception as e: print(f"刷新高度图浏览器时出错: {e}") self.heightmap_browser_items = [] # ==================== 导入功能实现 ==================== def _on_import_model(self): """处理导入模型菜单项""" self.add_info_message("打开导入模型对话框") self.show_import_dialog = True def _import_model(self): """导入模型的具体实现""" try: if not self.import_file_path: self.add_error_message("请选择要导入的文件") return if not os.path.exists(self.import_file_path): self.add_error_message(f"文件不存在: {self.import_file_path}") return # 检查文件格式 file_ext = os.path.splitext(self.import_file_path)[1].lower() if file_ext not in self.supported_formats: self.add_error_message(f"不支持的文件格式: {file_ext}") return # 调用场景管理器导入模型 if hasattr(self, 'scene_manager') and self.scene_manager: self.add_info_message(f"正在导入模型: {os.path.basename(self.import_file_path)}") # 导入模型 model_node = self.scene_manager.importModel(self.import_file_path) if model_node: # 添加材质处理确保颜色正常 if hasattr(self.scene_manager, 'processMaterials'): self.scene_manager.processMaterials(model_node) self.add_info_message("已应用默认材质") # 额外的材质处理,确保颜色正确显示 try: # 强制刷新模型显示 model_node.clearMaterial() model_node.clearTexture() # 重新应用材质 if hasattr(self.scene_manager, 'processMaterials'): self.scene_manager.processMaterials(model_node) # 设置默认的基础颜色(如果模型没有颜色) try: color = model_node.getColor() if color and len(color) >= 4 and color == (1, 1, 1, 1): # 默认白色 model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰 elif not color: # 如果没有颜色 model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰 except: # 如果getColor失败,直接设置默认颜色 model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰 except Exception as e: self.add_warning_message(f"材质处理警告: {e}") # 设置模型位置 model_node.setPos(0, 0, 0) # 添加到场景管理器的模型列表 if hasattr(self.scene_manager, 'models'): self.scene_manager.models.append(model_node) # 选中新导入的模型 if hasattr(self, 'selection') and self.selection: self.selection.selectNode(model_node) self.add_success_message(f"模型导入成功: {os.path.basename(self.import_file_path)}") else: self.add_error_message("模型导入失败") else: self.add_error_message("场景管理器未初始化") except Exception as e: self.add_error_message(f"导入模型失败: {e}") # 清空导入路径 self.import_file_path = "" def setup_drag_drop_support(self): """设置拖拽支持""" try: # 启动拖拽监控线程 self.drag_drop_monitor = DragDropMonitor(self) self.drag_drop_monitor.start() print("✓ 拖拽监控已启动") except Exception as e: print(f"⚠ 拖拽监控启动失败: {e}") def add_dragged_file(self, file_path): """添加拖拽的文件""" if file_path not in self.dragged_files: self.dragged_files.append(file_path) self.is_dragging = True self.show_drag_overlay = True print(f"检测到拖拽文件: {file_path}") def clear_dragged_files(self): """清空拖拽文件列表""" self.dragged_files.clear() self.is_dragging = False self.show_drag_overlay = False def process_dragged_files(self): """处理拖拽的文件""" if not self.dragged_files: return imported_count = 0 for file_path in self.dragged_files: if self._import_model_from_path(file_path): imported_count += 1 if imported_count > 0: self.add_message("success", f"成功导入 {imported_count} 个模型文件") else: self.add_message("error", "没有成功导入任何文件") self.clear_dragged_files() def _import_model_from_path(self, file_path): """从路径导入模型的内部方法""" try: # 检查文件是否存在 if not os.path.exists(file_path): self.add_message("error", f"文件不存在: {file_path}") return False # 检查文件格式 supported_formats = ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj'] file_ext = os.path.splitext(file_path)[1].lower() if file_ext not in supported_formats: self.add_message("error", f"不支持的文件格式: {file_ext}") return False # 导入模型 model_node = self.scene_manager.importModel(file_path) if model_node: # 应用材质确保颜色正常 self.scene_manager.processMaterials(model_node) # 设置模型位置 model_node.setPos(0, 0, 0) # 添加到选择系统 self.selection.select_node(model_node) self.add_message("success", f"成功导入模型: {os.path.basename(file_path)}") return True else: self.add_message("error", f"导入模型失败: {file_path}") return False except Exception as e: self.add_message("error", f"导入模型时发生错误: {str(e)}") return False def _draw_drag_drop_interface(self): """绘制拖拽界面""" # 检查资源管理器的拖拽状态 if self.resource_manager.is_dragging(): self.is_dragging = True self.dragged_files = self.resource_manager.get_dragged_files() self.show_drag_overlay = True # 绘制拖拽覆盖层 if self.show_drag_overlay: self._draw_drag_overlay() # 检查是否有拖拽的文件需要处理 if self.is_dragging and self.dragged_files: # 显示拖拽状态 self._draw_drag_status() # 检查是否释放鼠标(结束拖拽) if imgui.is_mouse_released(0): self._handle_drag_drop_completion() def _handle_drag_drop_completion(self): """处理拖拽完成""" # 检查是否在3D视图中释放 mouse_pos = imgui.get_mouse_pos() viewport = imgui.get_main_viewport() # 简单检查:如果不在任何ImGui窗口上,则认为是在3D视图中 if not imgui.is_any_window_hovered(): # 导入支持的3D模型文件 imported_count = 0 for file_path in self.dragged_files: if file_path.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']: try: self.scene_manager.importModel(str(file_path)) self.add_success_message(f"成功导入模型: {file_path.name}") imported_count += 1 except Exception as e: self.add_error_message(f"导入模型失败 {file_path.name}: {e}") if imported_count > 0: self.add_success_message(f"共导入 {imported_count} 个模型") # 清除拖拽状态 self.is_dragging = False self.dragged_files.clear() self.show_drag_overlay = False self.resource_manager.clear_drag() def _draw_drag_overlay(self): """绘制拖拽覆盖层""" viewport = imgui.get_main_viewport() imgui.set_next_window_pos((0, 0)) imgui.set_next_window_size(viewport.work_size) flags = ( imgui.WindowFlags_.no_title_bar | imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_move | imgui.WindowFlags_.no_scrollbar | imgui.WindowFlags_.no_saved_settings | imgui.WindowFlags_.no_background | imgui.WindowFlags_.no_focus_on_appearing ) imgui.begin("##DragOverlay", True, flags) # 绘制半透明背景 draw_list = imgui.get_window_draw_list() draw_list.add_rect_filled( (0, 0), viewport.work_size, imgui.get_color_u32((0, 0, 0, 0.1)) ) # 绘制提示文本 text_size = imgui.calc_text_size("释放以导入文件") text_pos = ( (viewport.work_size.x - text_size.x) / 2, (viewport.work_size.y - text_size.y) / 2 ) draw_list.add_text( text_pos, imgui.get_color_u32((1, 1, 1, 1)), "释放以导入文件" ) imgui.end() def _draw_drag_status(self): """绘制拖拽状态""" viewport = imgui.get_main_viewport() # 在右下角显示拖拽状态 imgui.set_next_window_pos( (viewport.work_size.x - 300, viewport.work_size.y - 150), imgui.Cond_.first_use_ever ) flags = ( imgui.WindowFlags_.no_title_bar | imgui.WindowFlags_.no_resize | imgui.WindowFlags_.no_move | imgui.WindowFlags_.no_scrollbar | imgui.WindowFlags_.no_saved_settings ) with imgui_ctx.begin("拖拽状态", True, flags): imgui.text("拖拽的文件:") for file_path in self.dragged_files: filename = os.path.basename(file_path) imgui.text(f" • {filename}") imgui.separator() if imgui.button("导入所有文件"): self.process_dragged_files() imgui.same_line() if imgui.button("取消"): self.clear_dragged_files() def _draw_context_menus(self): """绘制右键菜单""" # 节点右键菜单 if hasattr(self, '_context_menu_node') and self._context_menu_node: imgui.open_popup("节点右键菜单") self._context_menu_node = None if imgui.begin_popup("节点右键菜单"): if imgui.menu_item("删除节点", "", False, True)[1]: if hasattr(self, '_context_menu_target') and self._context_menu_target: self._delete_node(self._context_menu_target) imgui.close_current_popup() if imgui.menu_item("重命名", "", False, True)[1]: self._renaming_node = True imgui.close_current_popup() imgui.separator() if imgui.menu_item("复制", "", False, True)[1]: if hasattr(self, '_context_menu_target') and self._context_menu_target: self._copy_node(self._context_menu_target) imgui.close_current_popup() if imgui.menu_item("聚焦", "", False, True)[1]: if hasattr(self, '_context_menu_target') and self._context_menu_target: if hasattr(self, 'selection') and self.selection: self.selection.updateSelection(self._context_menu_target) self.selection.focusCameraOnSelectedNodeAdvanced() imgui.close_current_popup() imgui.end_popup() # 重命名对话框 if hasattr(self, '_renaming_node') and self._renaming_node: imgui.open_popup("重命名节点") if not hasattr(self, '_rename_buffer'): self._rename_buffer = "" if hasattr(self, '_context_menu_target') and self._context_menu_target: self._rename_buffer = self._context_menu_target.getName() or "" if imgui.begin_popup("重命名节点"): changed, new_name = imgui.input_text("新名称", self._rename_buffer, 256) if changed: self._rename_buffer = new_name if imgui.button("确定"): if hasattr(self, '_context_menu_target') and self._context_menu_target: self._context_menu_target.setName(self._rename_buffer) self._renaming_node = False imgui.close_current_popup() imgui.same_line() if imgui.button("取消"): self._renaming_node = False imgui.close_current_popup() imgui.end_popup() def _delete_node(self, node): """删除节点""" if not node or node.isEmpty(): return # 从场景管理器中删除 if hasattr(self, 'scene_manager') and self.scene_manager: if hasattr(self.scene_manager, 'models') and node in self.scene_manager.models: self.scene_manager.models.remove(node) # 从GUI管理器中删除 if hasattr(self, 'gui_manager') and self.gui_manager: gui_element = None if hasattr(node, 'getPythonTag'): gui_element = node.getPythonTag('gui_element') if gui_element and hasattr(self.gui_manager, 'gui_elements'): if gui_element in self.gui_manager.gui_elements: self.gui_manager.gui_elements.remove(gui_element) # 获取节点名称(在删除之前) node_name = node.getName() if not node.isEmpty() else '未命名节点' # 删除节点本身 node.removeNode() # 清除选择 if hasattr(self, 'selection') and self.selection: if self.selection.selectedNode == node: self.selection.clearSelection() # 添加成功消息 self.add_success_message(f"已删除节点: {node_name}") def _copy_node(self, node): """复制节点""" if not node or node.isEmpty(): return # 这里可以实现节点复制逻辑 # 暂时只显示消息 self.add_info_message(f"复制功能暂未实现: {node.getName() or '未命名节点'}") # ==================== 创建功能实现 ==================== def _on_create_empty_object(self): """创建空对象""" try: result = self.createEmptyObject() if result: self.add_success_message("空对象创建成功") else: self.add_error_message("空对象创建失败") return result except Exception as e: self.add_error_message(f"创建空对象失败: {str(e)}") def _on_create_cube(self): """创建立方体""" try: result = self.createCube() if result: self.add_success_message("立方体创建成功") else: self.add_error_message("立方体创建失败") return result except Exception as e: self.add_error_message(f"创建立方体失败: {str(e)}") def _on_create_sphere(self): """创建球体""" try: result = self.createSphere() if result: self.add_success_message("球体创建成功") else: self.add_error_message("球体创建失败") return result except Exception as e: self.add_error_message(f"创建球体失败: {str(e)}") def _on_create_cylinder(self): """创建圆柱体""" try: result = self.createCylinder() if result: self.add_success_message("圆柱体创建成功") else: self.add_error_message("圆柱体创建失败") return result except Exception as e: self.add_error_message(f"创建圆柱体失败: {str(e)}") def _on_create_plane(self): """创建平面""" try: result = self.createPlane() if result: self.add_success_message("平面创建成功") else: self.add_error_message("平面创建失败") return result except Exception as e: self.add_error_message(f"创建平面失败: {str(e)}") def _on_create_3d_text(self): """创建3D文本""" self.show_3d_text_dialog = True def _on_create_3d_image(self): """创建3D图片""" self.show_3d_image_dialog = True def _on_create_gui_button(self): """创建GUI按钮""" self.show_gui_button_dialog = True def _on_create_gui_label(self): """创建GUI标签""" self.show_gui_label_dialog = True def _on_create_gui_entry(self): """创建GUI输入框""" self.show_gui_entry_dialog = True def _on_create_gui_image(self): """创建GUI图片""" self.show_gui_image_dialog = True def _on_create_video_screen(self): """创建视频屏幕""" self.show_video_screen_dialog = True def _on_create_2d_video_screen(self): """创建2D视频屏幕""" self.show_2d_video_screen_dialog = True def _on_create_spherical_video(self): """创建球形视频""" self.show_spherical_video_dialog = True def _on_create_virtual_screen(self): """创建虚拟屏幕""" self.show_virtual_screen_dialog = True def _on_create_spot_light(self): """创建聚光灯""" self.show_spot_light_dialog = True def _on_create_point_light(self): """创建点光源""" self.show_point_light_dialog = True def _on_create_flat_terrain(self): """创建平面地形""" self.show_terrain_dialog = True def _on_create_heightmap_terrain(self): """从高度图创建地形""" self.show_heightmap_browser = True def _on_create_script(self): """创建脚本""" self.show_script_dialog = True def _on_load_script(self): """加载脚本文件""" self.show_script_browser = True def _on_reload_all_scripts(self): """重载所有脚本""" try: if hasattr(self, 'script_manager') and self.script_manager: self.script_manager.reloadAllScripts() self.add_success_message("所有脚本重载成功") else: self.add_error_message("脚本管理器未初始化") except Exception as e: self.add_error_message(f"重载脚本失败: {str(e)}") def _on_open_scripts_manager(self): """打开脚本管理器""" self.showScriptPanel = True self.add_info_message("脚本管理器已打开") def _on_create_2d_sample_panel(self): """创建2D示例面板""" try: result = self.create2DSamplePanel() if result: self.add_success_message("2D示例面板创建成功") else: self.add_error_message("2D示例面板创建失败") except Exception as e: self.add_error_message(f"创建2D示例面板失败: {str(e)}") def _on_create_3d_sample_panel(self): """创建3D实例面板""" try: result = self.create3DSamplePanel() if result: self.add_success_message("3D实例面板创建成功") else: self.add_error_message("3D实例面板创建失败") except Exception as e: self.add_error_message(f"创建3D实例面板失败: {str(e)}") def _on_create_web_panel(self): """创建Web面板""" try: result = self.createWebPanel() if result: self.add_success_message("Web面板创建成功") else: self.add_error_message("Web面板创建失败") except Exception as e: self.add_error_message(f"创建Web面板失败: {str(e)}") # ==================== 3D对象和GUI创建方法 ==================== def createEmptyObject(self): """创建空对象""" try: from panda3d.core import NodePath # 创建一个空节点 empty_node = NodePath("EmptyObject") empty_node.reparentTo(self.render) empty_node.setPos(0, 0, 0) # 添加到场景管理器 if hasattr(self, 'scene_manager') and self.scene_manager: self.scene_manager.models.append(empty_node) # 更新场景树 if hasattr(self, 'updateSceneTree'): self.updateSceneTree() print("✓ 空对象创建成功") return empty_node except Exception as e: print(f"✗ 创建空对象失败: {e}") return None def create3DText(self, pos=(0, 0, 0), text="3D Text", scale=1.0): """创建3D文本""" try: from panda3d.core import TextNode, NodePath # 创建文本节点 text_node = TextNode("3DText") text_node.setText(text) text_node.setAlign(TextNode.ACenter) text_node.setTextColor(1, 1, 1, 1) # 白色 # 设置中文字体 chinese_font = self._get_chinese_font() if chinese_font: text_node.setFont(chinese_font) # 创建节点路径并设置位置 text_np = NodePath(text_node) text_np.reparentTo(self.render) text_np.setPos(pos) text_np.setScale(scale) # 添加到场景管理器 if hasattr(self, 'scene_manager') and self.scene_manager: self.scene_manager.models.append(text_np) # 更新场景树 if hasattr(self, 'updateSceneTree'): self.updateSceneTree() print(f"✓ 3D文本创建成功: {text}") return text_np except Exception as e: print(f"✗ 创建3D文本失败: {e}") return None def create3DImage(self, pos=(0, 0, 0), image_path="", size=(1, 1)): """创建3D图片""" try: from panda3d.core import CardMaker, NodePath if not image_path or not os.path.exists(image_path): print("✗ 图片文件不存在") return None # 创建卡片几何体 card_maker = CardMaker("3DImage") card_maker.setFrame(-size[0]/2, size[0]/2, -size[1]/2, size[1]/2) card = card_maker.generate() # 创建节点路径 image_np = NodePath(card) image_np.reparentTo(self.render) image_np.setPos(pos) # 加载纹理 from panda3d.core import Texture, Filename tex = self.loader.loadTexture(Filename.fromOsSpecific(image_path)) if tex: image_np.setTexture(tex, 1) else: print("✗ 加载纹理失败") image_np.removeNode() return None # 添加到场景管理器 if hasattr(self, 'scene_manager') and self.scene_manager: self.scene_manager.models.append(image_np) # 更新场景树 if hasattr(self, 'updateSceneTree'): self.updateSceneTree() print(f"✓ 3D图片创建成功: {os.path.basename(image_path)}") return image_np except Exception as e: print(f"✗ 创建3D图片失败: {e}") return None def createCube(self, pos=(0, 0, 0), size=1.0): """创建立方体""" try: # 尝试使用Panda3D的内置几何体 from panda3d.core import NodePath, GeomNode, Geom, GeomVertexFormat, GeomVertexData, GeomVertexWriter, GeomTriangles, GeomPoints from panda3d.core import Vec3, Vec4, RenderState, ShadeModelAttrib # 创建顶点数据格式 format = GeomVertexFormat.getV3n3cpt2() vdata = GeomVertexData('cube', format, Geom.UHStatic) vdata.setNumRows(24) vertex = GeomVertexWriter(vdata, 'vertex') normal = GeomVertexWriter(vdata, 'normal') color = GeomVertexWriter(vdata, 'color') # 立方体的8个顶点 s = size / 2 vertices = [ (-s, -s, -s), (s, -s, -s), (s, s, -s), (-s, s, -s), # 底面 (-s, -s, s), (s, -s, s), (s, s, s), (-s, s, s) # 顶面 ] # 立方体的6个面,每个面4个顶点 faces = [ (0, 1, 2, 3), # 底面 (4, 7, 6, 5), # 顶面 (0, 4, 5, 1), # 前面 (2, 6, 7, 3), # 后面 (0, 3, 7, 4), # 左面 (1, 5, 6, 2) # 右面 ] # 法线 normals = [ (0, 0, -1), (0, 0, 1), (0, -1, 0), (0, 1, 0), (-1, 0, 0), (1, 0, 0) ] # 添加顶点数据 for face_idx, face in enumerate(faces): n = normals[face_idx] for vertex_idx in face: v = vertices[vertex_idx] vertex.addData3f(*v) normal.addData3f(*n) color.addData4f(0.8, 0.8, 0.8, 1.0) # 灰色 # 创建几何体 geom = Geom(vdata) # 添加三角形 for face_idx in range(6): base = face_idx * 4 # 每个面分成2个三角形 tri = GeomTriangles(Geom.UHStatic) tri.addConsecutiveVertices(base, 3) tri.closePrimitive() tri2 = GeomTriangles(Geom.UHStatic) tri2.addVertices(base + 2, base + 3, base) tri2.closePrimitive() geom.addPrimitive(tri) geom.addPrimitive(tri2) # 创建节点 geom_node = GeomNode('cube') geom_node.addGeom(geom) cube = NodePath(geom_node) cube.reparentTo(self.render) cube.setPos(pos) # 设置渲染状态 state = RenderState.make(ShadeModelAttrib.make(ShadeModelAttrib.MSmooth)) cube.set_state(state) # 添加到场景管理器 if hasattr(self, 'scene_manager') and self.scene_manager: self.scene_manager.models.append(cube) # 更新场景树 if hasattr(self, 'updateSceneTree'): self.updateSceneTree() print("✓ 立方体创建成功") return cube except Exception as e: print(f"✗ 创建立方体失败: {e}") return None def createSphere(self, pos=(0, 0, 0), radius=1.0): """创建球体""" try: from panda3d.core import CardMaker, NodePath # 创建一个简单的球体(使用多个卡片近似) sphere_root = NodePath("Sphere") # 创建球体的多个面来近似球形 num_segments = 6 for i in range(num_segments): angle1 = (i / num_segments) * 360 # 创建球体片段 segment_maker = CardMaker(f"SphereSegment{i}") segment_maker.setFrame(-radius, radius, -radius, radius) segment = NodePath(segment_maker.generate()) segment.reparentTo(sphere_root) # 设置位置和旋转来形成球体 segment.setPos(0, 0, 0) segment.setH(angle1) segment.setP(angle1/2) # 倾斜角度 segment.setScale(radius) # 设置颜色 from panda3d.core import Vec4 sphere_root.setColor(Vec4(0.8, 0.6, 0.4, 1.0)) # 棕色 sphere_root.reparentTo(self.render) sphere_root.setPos(pos) # 添加到场景管理器 if hasattr(self, 'scene_manager') and self.scene_manager: self.scene_manager.models.append(sphere_root) # 更新场景树 if hasattr(self, 'updateSceneTree'): self.updateSceneTree() print("✓ 球体创建成功") return sphere_root except Exception as e: print(f"✗ 创建球体失败: {e}") return None def createCylinder(self, pos=(0, 0, 0), radius=1.0, height=2.0): """创建圆柱体""" try: import math from panda3d.core import CardMaker, NodePath # 创建圆柱体 cylinder_root = NodePath("Cylinder") # 创建圆柱体的多个侧面 num_segments = 12 for i in range(num_segments): angle1 = (i / num_segments) * 360 angle2 = ((i + 1) / num_segments) * 360 # 计算圆柱体侧面的位置 x1 = radius * math.cos(math.radians(angle1)) y1 = radius * math.sin(math.radians(angle1)) # 创建圆柱体侧面 segment_maker = CardMaker(f"CylinderSegment{i}") segment = NodePath(segment_maker.generate()) segment.reparentTo(cylinder_root) # 设置位置和旋转 segment.setPos(x1, y1, 0) segment.setH(angle1) segment.setScale(0.5, height, 1) # 调整宽度和高度 # 设置颜色 from panda3d.core import Vec4 cylinder_root.setColor(Vec4(0.4, 0.8, 0.4, 1.0)) # 绿色 cylinder_root.reparentTo(self.render) cylinder_root.setPos(pos) # 添加到场景管理器 if hasattr(self, 'scene_manager') and self.scene_manager: self.scene_manager.models.append(cylinder_root) # 更新场景树 if hasattr(self, 'updateSceneTree'): self.updateSceneTree() print("✓ 圆柱体创建成功") return cylinder_root except Exception as e: print(f"✗ 创建圆柱体失败: {e}") return None def createPlane(self, pos=(0, 0, 0), size=(1, 1)): """创建平面""" try: from panda3d.core import CardMaker, NodePath # 创建平面 card_maker = CardMaker("Plane") card_maker.setFrame(-size[0]/2, size[0]/2, -size[1]/2, size[1]/2) plane = NodePath(card_maker.generate()) plane.reparentTo(self.render) plane.setPos(pos) # 添加到场景管理器 if hasattr(self, 'scene_manager') and self.scene_manager: self.scene_manager.models.append(plane) # 更新场景树 if hasattr(self, 'updateSceneTree'): self.updateSceneTree() print("✓ 平面创建成功") return plane except Exception as e: print(f"✗ 创建平面失败: {e}") return None def create2DSamplePanel(self): """创建2D示例面板""" try: from core.InfoPanelManager import createSampleInfoPanel result = createSampleInfoPanel(self.render) return result except Exception as e: print(f"创建2D示例面板失败: {e}") return None def create3DSamplePanel(self): """创建3D实例面板""" try: if hasattr(self, 'info_panel_manager') and self.info_panel_manager: # 创建3D信息面板 panel_id = f"3d_sample_{int(time.time())}" result = self.info_panel_manager.create3DInfoPanel( panel_id=panel_id, position=(0, 0, 2), size=(1.0, 0.6), bg_color=(0.15, 0.25, 0.35, 0.95), border_color=(0.3, 0.5, 0.7, 1.0), title_color=(0.7, 0.9, 1.0, 1.0), content_color=(0.95, 0.95, 0.95, 1.0) ) # 添加示例内容 if result: sample_data = { "标题": "3D信息面板", "状态": "运行中", "创建时间": time.strftime("%Y-%m-%d %H:%M:%S"), "位置": f"X:0, Y:0, Z:2" } self.info_panel_manager.updatePanelContent(panel_id, content=sample_data) return result return None except Exception as e: print(f"创建3D示例面板失败: {e}") return None def createWebPanel(self, url="https://www.example.com"): """创建Web面板""" try: if hasattr(self, 'info_panel_manager') and self.info_panel_manager: panel_id = f"web_panel_{int(time.time())}" result = self.info_panel_manager.createHTTPInfoPanel( panel_id=panel_id, url=url, position=(0.8, 0.0), size=(0.35, 0.4), update_interval=5.0 # 每5秒更新一次 ) return result return None except Exception as e: print(f"创建Web面板失败: {e}") return None # ==================== GUI创建方法 ==================== def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=0.1): """创建2D GUI按钮""" try: if hasattr(self, 'gui_manager') and self.gui_manager: # 使用简化的创建方法,不依赖QT树形控件 return self._create_simple_gui_button(pos, text, size) return None except Exception as e: print(f"创建GUI按钮失败: {e}") return None def _create_simple_gui_button(self, pos=(0, 0, 0), text="按钮", size=0.1): """创建简单的GUI按钮,不依赖QT树形控件""" try: from direct.gui.DirectGui import DirectButton from panda3d.core import TextNode # 转换坐标系统 gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1) # 设置中文字体 font = self._get_chinese_font() # 处理scale参数 if isinstance(size, (list, tuple)) and len(size) >= 2: scale_value = size[0] # 使用宽度作为缩放值 else: scale_value = size # 创建按钮 button = DirectButton( text=text, pos=gui_pos, scale=scale_value, text_font=font, command=self._on_gui_button_click ) # 创建包装对象 button_wrapper = type('GUIElement', (), {})() button_wrapper.node = button button_wrapper.name = text button_wrapper.gui_type = "GUI_BUTTON" button_wrapper.position = pos button_wrapper.size = size # 添加到GUI管理器 self.gui_manager.gui_elements.append(button_wrapper) print(f"✓ GUI按钮创建成功: {text}") return button_wrapper except Exception as e: print(f"✗ 创建简单GUI按钮失败: {e}") return None def _on_gui_button_click(self): """GUI按钮点击事件处理""" print("GUI按钮被点击了") def _get_chinese_font(self): """获取中文字体""" try: from panda3d.core import TextNode import os from pathlib import Path # 尝试加载中文字体 font_paths = [ "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", # Linux文泉驿微米黑 "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", # Noto Sans CJK "/System/Library/Fonts/PingFang.ttc", # macOS "C:/Windows/Fonts/simhei.ttf", # Windows "C:/Windows/Fonts/msyh.ttc" # Windows微软雅黑 ] for font_path in font_paths: if Path(font_path).exists(): font = TextNode.getDefaultFont() # 尝试加载字体 try: font = self.loader.loadFont(font_path) print(f"✓ 为GUI加载中文字体成功: {font_path}") return font except: print(f"⚠️ 字体加载失败,尝试下一个: {font_path}") continue # 如果所有字体都加载失败,返回默认字体 print("⚠️ 无法加载中文字体,使用默认字体") return TextNode.getDefaultFont() except Exception as e: print(f"⚠️ 获取中文字体失败: {e}") from panda3d.core import TextNode return TextNode.getDefaultFont() def createGUILabel(self, pos=(0, 0, 0), text="标签", size=0.08): """创建2D GUI标签""" try: if hasattr(self, 'gui_manager') and self.gui_manager: # 使用简化的创建方法,不依赖QT树形控件 return self._create_simple_gui_label(pos, text, size) return None except Exception as e: print(f"创建GUI标签失败: {e}") return None def _create_simple_gui_label(self, pos=(0, 0, 0), text="标签", size=0.08): """创建简单的GUI标签,不依赖QT树形控件""" try: from direct.gui.DirectGui import DirectLabel # 转换坐标系统 gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1) # 设置中文字体 font = self._get_chinese_font() # 处理scale参数 if isinstance(size, (list, tuple)) and len(size) >= 2: scale_value = size[0] # 使用宽度作为缩放值 else: scale_value = size # 创建标签 label = DirectLabel( text=text, pos=gui_pos, scale=scale_value, text_font=font, text_fg=(1, 1, 1, 1) # 白色文字 ) # 创建包装对象 label_wrapper = type('GUIElement', (), {})() label_wrapper.node = label label_wrapper.name = text label_wrapper.gui_type = "GUI_LABEL" label_wrapper.position = pos label_wrapper.size = size # 添加到GUI管理器 self.gui_manager.gui_elements.append(label_wrapper) print(f"✓ GUI标签创建成功: {text}") return label_wrapper except Exception as e: print(f"✗ 创建简单GUI标签失败: {e}") return None def createGUIEntry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08): """创建2D GUI文本输入框""" try: if hasattr(self, 'gui_manager') and self.gui_manager: # 使用简化的创建方法,不依赖QT树形控件 return self._create_simple_gui_entry(pos, placeholder, size) return None except Exception as e: print(f"创建GUI输入框失败: {e}") return None def _create_simple_gui_entry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08): """创建简单的GUI输入框,不依赖QT树形控件""" try: from direct.gui.DirectGui import DirectEntry # 转换坐标系统 gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1) # 设置中文字体 font = self._get_chinese_font() # 处理scale参数 if isinstance(size, (list, tuple)) and len(size) >= 2: scale_value = size[0] # 使用宽度作为缩放值 else: scale_value = size # 创建输入框 entry = DirectEntry( text=placeholder, pos=gui_pos, scale=scale_value, text_font=font, width=20, # 字符宽度 numLines=1, # 行数 focus=1 # 自动获取焦点 ) # 创建包装对象 entry_wrapper = type('GUIElement', (), {})() entry_wrapper.node = entry entry_wrapper.name = placeholder entry_wrapper.gui_type = "GUI_ENTRY" entry_wrapper.position = pos entry_wrapper.size = size # 添加到GUI管理器 self.gui_manager.gui_elements.append(entry_wrapper) print(f"✓ GUI输入框创建成功: {placeholder}") return entry_wrapper except Exception as e: print(f"✗ 创建简单GUI输入框失败: {e}") return None def createGUIImage(self, pos=(0, 0, 0), image_path=None, size=2): """创建2D GUI图片""" try: if hasattr(self, 'gui_manager') and self.gui_manager: # 使用简化的创建方法,不依赖QT树形控件 return self._create_simple_gui_image(pos, image_path, size) return None except Exception as e: print(f"创建GUI图片失败: {e}") return None def _create_simple_gui_image(self, pos=(0, 0, 0), image_path=None, size=2): """创建简单的GUI图片,不依赖QT树形控件""" try: from direct.gui.DirectGui import DirectFrame from panda3d.core import Filename # 转换坐标系统 gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1) # 处理scale参数 if isinstance(size, (list, tuple)) and len(size) >= 2: scale_value = size[0] # 使用宽度作为缩放值 else: scale_value = size # 创建图片框架 if image_path and os.path.exists(image_path): # 加载纹理 tex = self.loader.loadTexture(Filename.fromOsSpecific(image_path)) image = DirectFrame( image=tex, pos=gui_pos, scale=scale_value ) image_name = os.path.basename(image_path) else: # 创建一个彩色框架作为占位符 image = DirectFrame( frameColor=(0.5, 0.5, 0.5, 1.0), # 灰色 frameSize=(-scale_value, scale_value, -scale_value, scale_value), pos=gui_pos ) image_name = "占位符图片" # 创建包装对象 image_wrapper = type('GUIElement', (), {})() image_wrapper.node = image image_wrapper.name = image_name image_wrapper.gui_type = "GUI_IMAGE" image_wrapper.position = pos image_wrapper.size = size image_wrapper.image_path = image_path # 添加到GUI管理器 self.gui_manager.gui_elements.append(image_wrapper) print(f"✓ GUI图片创建成功: {image_name}") return image_wrapper except Exception as e: print(f"✗ 创建简单GUI图片失败: {e}") return None def createVideoScreen(self, pos=(0, 0, 0), size=1, video_path=None): """创建视频屏幕""" try: if hasattr(self, 'gui_manager') and self.gui_manager: return self.gui_manager.createVideoScreen(pos, size, video_path) return None except Exception as e: print(f"创建视频屏幕失败: {e}") return None def create2DVideoScreen(self, pos=(0, 0, 0), size=0.2, video_path=None): """创建2D视频屏幕""" try: if hasattr(self, 'gui_manager') and self.gui_manager: return self.gui_manager.createGUI2DVideoScreen(pos, size, video_path) return None except Exception as e: print(f"创建2D视频屏幕失败: {e}") return None def createSphericalVideo(self, pos=(0, 0, 0), radius=5.0, video_path=None): """创建360度视频""" try: if hasattr(self, 'gui_manager') and self.gui_manager: return self.gui_manager.createSphericalVideo(pos, radius, video_path) return None except Exception as e: print(f"创建球形视频失败: {e}") return None def createVirtualScreen(self, pos=(0, 0, 0), size=(2, 1), text="虚拟屏幕"): """创建虚拟屏幕""" try: if hasattr(self, 'gui_manager') and self.gui_manager: return self.gui_manager.createGUIVirtualScreen(pos, size, text) return None except Exception as e: print(f"创建虚拟屏幕失败: {e}") return None # ==================== 光源创建方法 ==================== def createSpotLight(self, pos=(0, 0, 5)): """创建聚光灯""" try: if hasattr(self, 'scene_manager') and self.scene_manager: return self.scene_manager.createSpotLight(pos) return None except Exception as e: print(f"创建聚光灯失败: {e}") return None def createPointLight(self, pos=(0, 0, 5)): """创建点光源""" try: if hasattr(self, 'scene_manager') and self.scene_manager: return self.scene_manager.createPointLight(pos) return None except Exception as e: print(f"创建点光源失败: {e}") return None # ==================== 地形创建方法 ==================== def createFlatTerrain(self, size=(10, 10), resolution=129): """创建平面地形""" try: if hasattr(self, 'terrain_manager') and self.terrain_manager: return self.terrain_manager.createFlatTerrain(size, resolution) return None except Exception as e: print(f"创建平面地形失败: {e}") return None def createTerrainFromHeightMap(self, heightmap_path, scale=(1.0, 1.0, 10.0)): """从高度图创建地形""" try: if hasattr(self, 'terrain_manager') and self.terrain_manager: return self.terrain_manager.createTerrainFromHeightMap(heightmap_path, scale) return None except Exception as e: print(f"创建高度图地形失败: {e}") return None # ==================== 脚本创建方法 ==================== def createScript(self, script_name, template="basic"): """创建脚本""" try: if hasattr(self, 'script_manager') and self.script_manager: return self.script_manager.createScript(script_name, template) return None except Exception as e: print(f"创建脚本失败: {e}") return None def loadScript(self, script_path): """加载脚本""" try: if hasattr(self, 'script_manager') and self.script_manager: return self.script_manager.loadScript(script_path) return None except Exception as e: print(f"加载脚本失败: {e}") return None demo = MyWorld() demo.run()