EG/main.py
ayuan9957 04cb0dc441 Merge branch 'hu_migrate_on_geng' into geng_migrate_on_imgui_hu
# Conflicts:
#	imgui.ini
#	ui/panels/editor_panels_top.py
2026-03-18 15:28:22 +08:00

1153 lines
44 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.11LUI 可能不可用。")
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": not self.use_ssbo_mouse_picking,
"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))
setattr(self, attr_name, visible)
if panel_key == "web" and previous != visible:
if not visible:
try:
self.editor_panels._stop_imgui_webview()
except Exception:
pass
if panel_key == "lui_editor" and hasattr(self, "lui_manager"):
try:
self.lui_manager.show_editor = visible
except Exception:
pass
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()