import os import shutil import subprocess import sys import warnings from pathlib import Path def _maybe_relaunch_with_py311(): """On Windows, relaunch with Python 3.11 so ui/lui.pyd ABI matches.""" if __name__ != "__main__": return if os.name != "nt" or sys.version_info >= (3, 11): return if os.environ.get("EG_RELAUNCHED_PY311") == "1": return py_launcher = shutil.which("py") if not py_launcher: print(f"⚠ 当前解释器是 Python {sys.version.split()[0]},未找到 py launcher,无法自动切换到 3.11。") return try: probe = subprocess.run( [py_launcher, "-3.11", "-c", "import sys;print(sys.executable)"], capture_output=True, text=True, check=False, ) except Exception as relaunch_error: print(f"⚠ 自动切换 Python 3.11 失败: {relaunch_error}") return if probe.returncode != 0: print("⚠ 未检测到可用的 Python 3.11,LUI 可能不可用。") return target_python = (probe.stdout or "").strip().splitlines() target_python = target_python[-1].strip() if target_python else "" if not target_python or not os.path.exists(target_python): print("⚠ 检测到 Python 3.11,但无法解析解释器路径,继续使用当前解释器。") return os.environ["EG_RELAUNCHED_PY311"] = "1" # Exec directly into the real Python 3.11 executable to avoid shell prompt flicker. os.execv(target_python, [target_python, os.path.abspath(__file__), *sys.argv[1:]]) _maybe_relaunch_with_py311() PROJECT_ROOT = Path(__file__).resolve().parent THIRD_PARTY_DIR = PROJECT_ROOT / "third_party" if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) if THIRD_PARTY_DIR.is_dir() and str(THIRD_PARTY_DIR) not in sys.path: sys.path.insert(0, str(THIRD_PARTY_DIR)) 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 from panda3d.core import NodePath import p3dimgui from direct.showbase.MessengerGlobal import messenger from imgui_bundle import imgui, imgui_ctx imgui_internal = getattr(imgui, "internal", None) # 导入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.Command_System import CommandManager from core.terrain_manager import TerrainManager from scene.scene_manager import SceneManager from project.project_manager import ProjectManager from core.collision_manager import CollisionManager from core.CustomMouseController import CustomMouseController from core.resource_manager import ResourceManager from ui.lui_manager import LUIManager from ui.panels.editor_panels import EditorPanels from ui.panels.script_panels import ScriptPanels from ui.panels.dialog_panels import DialogPanels from ui.panels.interaction_panels import InteractionPanels from ui.panels.create_actions import CreateActions from ui.panels.runtime_actions import RuntimeActions from ui.panels.object_factory import ObjectFactory from ui.panels.animation_tools import AnimationTools from ui.panels.property_helpers import PropertyHelpers from ui.panels.app_actions import AppActions from ui.panels.panel_delegates import PanelDelegates from core.model_drag_drop import ModelDragDropService from ssbo_component.ssbo_editor import SSBOEditor from TransformGizmo.transform_gizmo import TransformGizmo 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(PanelDelegates, CoreWorld): PANEL_VISIBILITY_ATTRS = { "scene_tree": "showSceneTree", "property": "showPropertyPanel", "console": "showConsole", "script": "showScriptPanel", "toolbar": "showToolbar", "resources": "showResourceManager", "web": "showWebPanel", "lui_editor": "showLUIEditor", } def __init__(self): super().__init__() self._shutdown_in_progress = False # 启动后强制同步一次真实窗口尺寸,避免首次渲染仍停留在旧默认分辨率。 props = WindowProperties() self.win.request_properties(props) self._last_forced_window_sync_size = None self._remaining_window_sync_attempts = 6 self.taskMgr.doMethodLater(0.05, self._sync_window_metrics_task, "window-metrics-sync") print(f"✓ 窗口初始化完成: {self.win.getXSize()} x {self.win.getYSize()}") self.gui_elements = [] # 初始化选择和变换系统 self.selection = SelectionSystem(self) # 绑定F键用于聚焦选中节点 self.accept("f", self.onFocusKeyPressed) self.accept("F", self.onFocusKeyPressed) # 大写F self.use_ssbo_mouse_picking = True self.use_ssbo_scene_import = True if not self.use_ssbo_mouse_picking: self.accept("mouse1", self.onMouseClick) # Keep release/move bindings even in SSBO mode so gizmo drag can work. self.accept("mouse1-up", self.onMouseRelease) self.accept("mouse-move", self.onMouseMove) self.accept("drag", self.onMouseMove) # 初始化事件处理系统 self.event_handler = EventHandler(self) # 初始化工具管理系统 self.tool_manager = ToolManager(self) # 初始化脚本管理系统 self.script_manager = ScriptManager(self) # 初始化LUI管理系统 self.lui_manager = LUIManager(self) # 新的坐标系 self.newTransform = TransformGizmo(self) self._setup_transform_gizmo_light_sync() # 初始化视频管理 if VideoManager is not None: self.video_manager = VideoManager(self) else: self.video_manager = None # 初始化场景管理系统 # print(f"[DEBUG] 初始化场景管理系统...") self.scene_manager = SceneManager(self) # print(f"[DEBUG] 场景管理系统初始化完成") # 初始化项目管理系统 # print(f"[DEBUG] 初始化项目管理系统...") self.project_manager = ProjectManager(self) # print(f"[DEBUG] 项目管理系统初始化完成") self.info_panel_manager = None self.command_manager = CommandManager() # 初始化碰撞管理器 self.collision_manager = CollisionManager(self) # 初始化资源管理器 self.resource_manager = ResourceManager(self) # 初始化Actor缓存系统(用于动画控制) self._actor_cache = {} print("✓ Actor缓存系统初始化完成") # 初始化自定义鼠标控制器(视角移动) 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 # VR调试相关变量 self.vr_debug_enabled = False self.vr_detailed_mode = True self.vr_performance_monitor = False # 调试选项 self.debug_collision = True # 是否显示碰撞体 # 默认启用模型间碰撞检测(可选) if self.use_ssbo_mouse_picking: self.enableModelCollisionDetection(enable=False, frequency=0.1, threshold=0.5) else: self.enableModelCollisionDetection(enable=True, frequency=0.1, threshold=0.5) # 碰撞检测UI相关变量 self._selected_collision_shape = "球形 (Sphere)" # 默认选择的碰撞形状 # 启动脚本系统 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( wantPlaceManager=False, wantExplorerManager=False, wantTimeSliderManager=False, ) self._imgui_ini_path = PROJECT_ROOT / "imgui.ini" self._imgui_default_layout_path = PROJECT_ROOT / "config" / "default_imgui_layout.ini" imgui.get_io().set_ini_filename(self._imgui_ini_path.as_posix()) # Let ssbo_component reuse the existing imgui backend instance. self.imgui_backend = self.imgui # Initialize SSBO editor and let it own mouse1 picking. # Do not auto-load a default model here; models are loaded from import flow. self.ssbo_editor = None if self.use_ssbo_mouse_picking: self.ssbo_editor = SSBOEditor( base_app=self, render_pipeline=self.render_pipeline, model_path=None, font_path=None, ) self.ssbo_editor.bind_transform_gizmo(self.newTransform) print("SSBOEditor mouse picking enabled (waiting for imported model)") 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) self.editor_panels = EditorPanels(self) self.script_panels = ScriptPanels(self) self.dialog_panels = DialogPanels(self) self.interaction_panels = InteractionPanels(self) self.create_actions = CreateActions(self) self.runtime_actions = RuntimeActions(self) self.object_factory = ObjectFactory(self) self.animation_tools = AnimationTools(self) self.property_helpers = PropertyHelpers(self) self.app_actions = AppActions(self) self._ensure_default_imgui_layout() # 简化的初始化字体设置(只使用中文字体) try: # 先清除默认字体 self.imgui.io.fonts.clear() # 尝试加载中文字体 import platform 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._default_panel_visibility = { "scene_tree": True, "property": True, "console": True, "script": True, "toolbar": True, "resources": True, "web": False, "lui_editor": not self.use_ssbo_mouse_picking, } for attr_name in self.PANEL_VISIBILITY_ATTRS.values(): setattr(self, attr_name, False) self.reset_panel_visibility_to_defaults() self.webPanelUrl = "https://www.baidu.com" # 脚本系统状态变量 self.hotReloadEnabled = True self._new_script_name = "new_script" self._selected_template = 0 self._mount_script_index = 0 self.console_command_input = "" self._scene_tree_search_text = "" self._resource_path_input = str(self.resource_manager.current_path) self._resource_path_source = self._resource_path_input # 变换监控相关 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._transform_clipboard = {} self._transform_scale_locked = True # 颜色选择器相关 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_search_text = "" 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_build_project_dialog = False self.show_about_dialog = False self.save_as_project_name = "" self.save_as_project_path = "" self.build_output_path = "" # 路径选择对话框状态 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._resource_manager_window_rect = None self._resource_drop_targets = [] self._scene_tree_window_rect = None self._property_panel_window_rect = None self._script_panel_window_rect = None self._console_window_rect = None self._toolbar_window_rect = None self._drag_scene_tree_hover_node = None self.model_drag_drop = ModelDragDropService(self) # 导入功能状态 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 = self.script_manager.hot_reload_enabled # 初始化高度图浏览器 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('control-z', self._on_undo) self.accept('control-y', self._on_redo) self.accept('control-x', self._on_cut) self.accept('control-c', self._on_copy) self.accept('control-v', self._on_paste) self.accept('delete', self._on_delete_pressed) self.accept('escape', self._on_escape_pressed) # 滚轮事件 self.accept('wheel_up', self._on_wheel_up) self.accept('wheel_down', self._on_wheel_down) # 监听窗口关闭,确保退出主循环并结束进程。 self.accept('window-event', self._on_window_event) 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 初始化完成") def is_panel_visible(self, panel_key): attr_name = self.PANEL_VISIBILITY_ATTRS.get(panel_key) if not attr_name: return False return bool(getattr(self, attr_name, False)) def set_panel_visible(self, panel_key, visible): attr_name = self.PANEL_VISIBILITY_ATTRS.get(panel_key) if not attr_name: return visible = bool(visible) previous = bool(getattr(self, attr_name, False)) if panel_key == "web" and previous != visible: setattr(self, attr_name, visible) if not visible: try: self.editor_panels._stop_imgui_webview() except Exception: pass return if panel_key == "lui_editor" and hasattr(self, "lui_manager"): lui_available = bool(getattr(self.lui_manager, "lui_enabled", False)) if visible and not lui_available: setattr(self, attr_name, False) if hasattr(self, "messages"): self.add_warning_message("当前环境未启用LUI(缺少 lui 模块),LUI编辑器不可用") else: print("⚠ 当前环境未启用LUI(缺少 lui 模块),LUI编辑器不可用") return setattr(self, attr_name, visible) try: self.lui_manager.show_editor = visible if visible and not previous: self.lui_manager._request_window_focus = True except Exception: pass return setattr(self, attr_name, visible) def reset_panel_visibility_to_defaults(self): for panel_key, default_visible in self._default_panel_visibility.items(): self.set_panel_visible(panel_key, default_visible) def _imgui_layout_looks_valid(self, ini_text): """Conservatively check whether imgui.ini already contains a usable dock layout.""" if not ini_text or "[Docking][Data]" not in ini_text or "DockSpace" not in ini_text: return False return True def _ensure_default_imgui_layout(self): """Seed a default dock layout when imgui.ini is missing or obviously incomplete.""" template_path = getattr(self, "_imgui_default_layout_path", None) ini_path = getattr(self, "_imgui_ini_path", None) if not template_path or not ini_path or not template_path.exists(): return current_text = "" if ini_path.exists(): try: current_text = ini_path.read_text(encoding="utf-8") except UnicodeDecodeError: current_text = ini_path.read_text(encoding="utf-8", errors="ignore") except Exception as e: print(f"⚠ 读取 ImGui 布局文件失败: {e}") if self._imgui_layout_looks_valid(current_text): return try: template_text = template_path.read_text(encoding="utf-8") except Exception as e: print(f"⚠ 读取默认 ImGui 布局模板失败: {e}") return if not self._imgui_layout_looks_valid(template_text): print("⚠ 默认 ImGui 布局模板不完整,跳过自动回填") return try: if ini_path.exists() and current_text.strip(): backup_path = ini_path.with_suffix(".invalid.bak") if not backup_path.exists(): shutil.copyfile(ini_path, backup_path) print(f"ℹ 已备份无效 ImGui 布局: {backup_path}") imgui.load_ini_settings_from_memory(template_text) ini_path.write_text(template_text, encoding="utf-8") print(f"✓ 已加载默认 ImGui 布局: {template_path}") except Exception as e: print(f"⚠ 应用默认 ImGui 布局失败: {e}") def _reset_imgui_layout(self): """Force-reset the current ImGui dock layout from the default template.""" template_path = getattr(self, "_imgui_default_layout_path", None) ini_path = getattr(self, "_imgui_ini_path", None) if not template_path or not ini_path or not template_path.exists(): self.add_error_message("默认 ImGui 布局模板不存在") return try: template_text = template_path.read_text(encoding="utf-8") imgui.load_ini_settings_from_memory(template_text) ini_path.write_text(template_text, encoding="utf-8") self.reset_panel_visibility_to_defaults() self.add_success_message("ImGui 布局已重置为默认布局") except Exception as e: self.add_error_message(f"重置 ImGui 布局失败: {e}") def _on_window_event(self, window): """窗口事件处理:窗口被关闭时退出应用。""" if self._shutdown_in_progress: return try: target_window = window if window is not None else getattr(self, "win", None) if not target_window: return self.windowEvent(target_window) self._sync_window_dependent_state(target_window) if not target_window.getProperties().getOpen(): self.userExit() except Exception as e: print(f"处理窗口事件失败: {e}") def _sync_window_dependent_state(self, window): """Keep camera and picking lenses aligned with the current client size.""" try: width = int(window.getXSize()) height = int(window.getYSize()) if width <= 0 or height <= 0: return render_pipeline = getattr(self, "render_pipeline", None) if render_pipeline: try: pipeline_window_dims = getattr(render_pipeline, "_last_window_dims", None) pipeline_needs_resize = ( pipeline_window_dims is None or int(pipeline_window_dims.x) != width or int(pipeline_window_dims.y) != height ) if pipeline_needs_resize and hasattr(render_pipeline, "_handle_window_event"): render_pipeline._handle_window_event(window) except Exception as e: print(f"同步 RenderPipeline 窗口尺寸失败: {e}") aspect_ratio = float(width) / float(height) lens = getattr(self, "camLens", None) if lens: try: lens.setAspectRatio(aspect_ratio) except Exception: pass cam = getattr(self, "cam", None) if cam: try: cam_lens = cam.node().getLens() if cam_lens: cam_lens.setAspectRatio(aspect_ratio) except Exception: pass ssbo_editor = getattr(self, "ssbo_editor", None) if ssbo_editor and getattr(ssbo_editor, "pick_lens", None): try: ssbo_editor.pick_lens.setAspectRatio(aspect_ratio) except Exception: pass except Exception as e: print(f"同步窗口依赖状态失败: {e}") def _sync_window_metrics_task(self, task): """Force early window-size synchronization so RP/ImGui match the real client area.""" if self._shutdown_in_progress: return task.done try: if not getattr(self, "win", None): return task.done width = int(self.win.getXSize()) height = int(self.win.getYSize()) if width > 0 and height > 0: current_size = (width, height) if current_size != self._last_forced_window_sync_size: self._last_forced_window_sync_size = current_size messenger.send("window-event", [self.win]) print(f"✓ 同步窗口分辨率: {width} x {height}") else: self._sync_window_dependent_state(self.win) except Exception as e: print(f"同步窗口分辨率失败: {e}") if self._remaining_window_sync_attempts > 0: self._remaining_window_sync_attempts -= 1 task.delayTime = 0.15 else: task.delayTime = 0.05 return task.again def userExit(self): """统一退出流程,避免关闭窗口后进程残留。""" if self._shutdown_in_progress: return self._shutdown_in_progress = True try: monitor = getattr(self, "drag_drop_monitor", None) if monitor: try: monitor.stop() except Exception: pass webview = getattr(self, "_imgui_webview", None) if webview: try: webview.stop() except Exception: pass script_manager = getattr(self, "script_manager", None) if script_manager: try: script_manager.stop_system() except Exception: pass finally: super().userExit() # ==================== 兼容性属性 ==================== # 保留models属性以兼容现有代码 @property def models(self): """模型列表的兼容性属性""" return self.scene_manager.models @models.setter def models(self, value): """模型列表的兼容性设置器""" self.scene_manager.models = 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: selected_node = self._get_selection_node() if selected_node: self.selection.focusCameraOnSelectedNodeAdvanced() elif getattr(getattr(self, "ssbo_editor", None), "has_active_selection", lambda: False)(): return else: print("当前没有选中任何节点") except Exception as e: print(f"处理 F 键事件失败: {e}") def onMouseClick(self): """处理鼠标点击事件""" print("\n=== 鼠标点击事件触发 ===") try: # 检查鼠标是否有效 if not self.mouseWatcherNode.hasMouse(): print("❌ 鼠标无效或不在窗口内") return print("✓ 鼠标位置有效") # 获取鼠标位置 mouse_x = self.mouseWatcherNode.getMouseX() mouse_y = self.mouseWatcherNode.getMouseY() print(f"📍 鼠标标准化坐标: ({mouse_x:.3f}, {mouse_y:.3f})") # 转换为窗口坐标 winWidth, winHeight = self.win.getSize() window_x = (mouse_x + 1) * 0.5 * winWidth window_y = (1 - mouse_y) * 0.5 * winHeight print(f"📍 鼠标窗口坐标: ({window_x:.1f}, {window_y:.1f})") print(f"📐 窗口尺寸: {winWidth} x {winHeight}") # 检查ImGui是否捕获了鼠标 imgui_captured = self.processImGuiMouseClick(window_x, window_y) print(f"🖱️ ImGui捕获状态: {imgui_captured}") if imgui_captured: print("❌ ImGui处理了该事件,跳过3D场景选择") return # 调用事件处理器进行射线检测和选择 if hasattr(self, 'event_handler'): print("✓ 找到事件处理器,开始处理选择") self.event_handler.mousePressEventLeft({ 'x': window_x, 'y': window_y }) else: print("❌ 未找到事件处理器") except Exception as e: print(f"❌ 处理鼠标点击事件失败: {e}") import traceback traceback.print_exc() def onMouseRelease(self): """处理鼠标释放事件""" try: # 检查鼠标是否有效 if not self.mouseWatcherNode.hasMouse(): return # 获取鼠标位置 mouse_x = self.mouseWatcherNode.getMouseX() mouse_y = self.mouseWatcherNode.getMouseY() # 转换为窗口坐标 winWidth, winHeight = self.win.getSize() window_x = (mouse_x + 1) * 0.5 * winWidth window_y = (1 - mouse_y) * 0.5 * winHeight # 调用事件处理器 if hasattr(self, 'event_handler'): self.event_handler.mouseReleaseEventLeft({ 'x': window_x, 'y': window_y }) except Exception as e: print(f"处理鼠标释放事件失败: {e}") def onMouseMove(self): """处理鼠标移动事件""" try: # 检查鼠标是否有效 if not self.mouseWatcherNode.hasMouse(): return # 获取鼠标位置 mouse_x = self.mouseWatcherNode.getMouseX() mouse_y = self.mouseWatcherNode.getMouseY() # 转换为窗口坐标 winWidth, winHeight = self.win.getSize() window_x = (mouse_x + 1) * 0.5 * winWidth window_y = (1 - mouse_y) * 0.5 * winHeight # 调用事件处理器 if hasattr(self, 'event_handler'): self.event_handler.mouseMoveEvent({ 'x': window_x, 'y': window_y }) 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() dock_flags = imgui.DockNodeFlags_.passthru_central_node if imgui_internal is not None: # imgui_bundle不同版本下私有Dock标志位的枚举类型可能不兼容 # (直接位或会在Python 3.11的Flag枚举里触发异常) try: private_flag = imgui_internal.DockNodeFlagsPrivate_.no_window_menu_button dock_flags = imgui.DockNodeFlags_(int(dock_flags) | int(private_flag)) except Exception: # 回退:忽略私有标志,不影响DockSpace主流程 pass imgui.dock_space_over_viewport(0, viewport, dock_flags) # 在第一帧应用样式 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._resource_manager_window_rect = None self._resource_drop_targets = [] self._scene_tree_window_rect = None self._property_panel_window_rect = None self._script_panel_window_rect = None self._console_window_rect = None self._toolbar_window_rect = None self._drag_scene_tree_hover_node = None # 绘制菜单栏 self._draw_menu_bar() # 绘制停靠布局 self._draw_docked_layout(window_width, window_height) # 处理系统层外部拖入 self._process_external_drop_events() # 绘制纹理测试窗口 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_save_as_project_dialog() self._draw_build_project_dialog() self._draw_path_browser() self._draw_import_dialog() # 绘制颜色选择器 self._draw_color_picker() # 绘制字体选择器 self._draw_font_selector() 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_about_dialog() # 绘制右键菜单 self._draw_context_menus() # 绘制拖拽界面 self._draw_drag_drop_interface() # 绘制LUI编辑器 if self.is_panel_visible("lui_editor") and hasattr(self, 'lui_manager'): self.lui_manager.draw_editor() # 更新变换监控 dt = imgui.get_io().delta_time self.update_transform_monitoring(dt) def _draw_docked_layout(self, window_width, window_height): """绘制可停靠的布局(支持拖拽)""" # 左侧场景树面板 if self.is_panel_visible("scene_tree"): self._draw_scene_tree() # 资源管理器面板 if self.is_panel_visible("resources"): self._draw_resource_manager() # 属性面板 if self.is_panel_visible("property"): self._draw_property_panel() # Web面板 if self.is_panel_visible("web"): self._draw_web_panel() # 脚本面板 if self.is_panel_visible("script"): self._draw_script_panel() # 底部控制台 if self.is_panel_visible("console"): self._draw_console() # 顶部工具栏 if self.is_panel_visible("toolbar"): self._draw_toolbar() def _sync_rp_light_from_node(self, node): """将灯光包装节点的位置同步到 RenderPipeline 灯光对象。""" try: if not node or node.isEmpty() or (not node.hasPythonTag("rp_light_object")): # 兼容旧场景:仅有 light_type 标签时尝试补绑 rp_light_object if not node or node.isEmpty() or (not node.hasTag("light_type")): return False scene_manager = getattr(self, "scene_manager", None) if scene_manager: try: light_type = node.getTag("light_type") if light_type == "spot_light" and hasattr(scene_manager, "_recreateSpotLight"): scene_manager._recreateSpotLight(node) elif light_type == "point_light" and hasattr(scene_manager, "_recreatePointLight"): scene_manager._recreatePointLight(node) except Exception: pass light_obj = node.getPythonTag("rp_light_object") if node.hasPythonTag("rp_light_object") else None if not light_obj: return False world_pos = node.getPos(self.render) try: light_obj.setPos(world_pos) except Exception: try: light_obj.setPos(world_pos.x, world_pos.y, world_pos.z) except Exception: try: light_obj.pos = Point3(world_pos) except Exception: return False return True except Exception: return False def _on_transform_gizmo_drag_event(self, payload): """TransformGizmo 拖拽事件回调:实时同步灯光位置。""" try: node = payload.get("target") if isinstance(payload, dict) else None if node and (not node.isEmpty()): self._sync_rp_light_from_node(node) except Exception: pass def _setup_transform_gizmo_light_sync(self): """为 newTransform 注册灯光同步事件钩子。""" tg = getattr(self, "newTransform", None) if not tg: return try: from TransformGizmo.events import GizmoEvent hooks = { "move": { GizmoEvent.DRAG_MOVE: [self._on_transform_gizmo_drag_event], GizmoEvent.DRAG_END: [self._on_transform_gizmo_drag_event], }, "rotate": { GizmoEvent.DRAG_MOVE: [self._on_transform_gizmo_drag_event], GizmoEvent.DRAG_END: [self._on_transform_gizmo_drag_event], }, "scale": { GizmoEvent.DRAG_MOVE: [self._on_transform_gizmo_drag_event], GizmoEvent.DRAG_END: [self._on_transform_gizmo_drag_event], }, } tg.set_event_hooks(hooks, replace=False) except Exception as e: print(f"绑定 TransformGizmo 灯光同步事件失败: {e}") if __name__ == "__main__": demo = MyWorld() demo.run()