EG/demo.py
2026-01-21 17:18:14 +08:00

2817 lines
113 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.

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.world.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.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.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_context_menus()
# 绘制拖拽界面
self._draw_drag_drop_interface()
# 检查鼠标释放事件(用于处理拖拽结束)
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 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:
self._draw_scene_node(self.scene_manager.Spotlight, "定向光", "light")
if hasattr(self.scene_manager, 'Pointlight') and self.scene_manager.Pointlight:
self._draw_scene_node(self.scene_manager.Pointlight, "点光源", "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, imgui.IM_COL32(100, 150, 200, 255))
# 尝试加载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(imgui.ImVec2(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 # 确保窗口保持打开
imgui.text("对象属性")
imgui.separator()
# 获取当前选中的节点
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:
# 无选中对象时显示提示
imgui.text_colored((0.5, 0.5, 0.5, 1.0), "未选择任何对象")
imgui.text("请从场景树中选择一个对象以查看其属性")
def _draw_node_properties(self, node):
"""绘制节点属性"""
if not node or node.isEmpty():
return
# 获取节点基本信息
node_name = node.getName() or "未命名节点"
node_type = self._get_node_type_from_node(node)
# 节点名称和类型
imgui.text(f"名称: {node_name}")
imgui.text(f"类型: {node_type}")
# 状态徽章
self._draw_status_badges(node)
imgui.separator()
# 变换属性
self._draw_transform_properties(node)
imgui.separator()
# 根据节点类型显示特定属性
if node_type == "GUI元素":
self._draw_gui_properties(node)
elif node_type == "光源":
self._draw_light_properties(node)
elif node_type == "模型":
self._draw_model_properties(node)
imgui.separator()
# 操作按钮
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):
"""绘制状态徽章"""
# 可见性状态
is_visible = not node.is_hidden()
visibility_color = (0.2, 0.8, 0.2, 1.0) if is_visible else (0.8, 0.2, 0.2, 1.0)
visibility_text = "可见" if is_visible else "隐藏"
imgui.same_line()
imgui.text("状态: ")
imgui.same_line()
imgui.text_colored(visibility_color, f"{visibility_text}")
# 是否有碰撞体
has_collision = hasattr(node, 'getChild') and any('Collision' in child.getName() for child in node.getChildren() if child.getName())
if has_collision:
imgui.same_line()
imgui.text_colored((0.2, 0.4, 0.8, 1.0), "● 碰撞")
def _draw_transform_properties(self, node):
"""绘制变换属性"""
imgui.text("变换")
# 位置
pos = node.getPos()
changed, new_x = imgui.drag_float("位置 X##pos", pos.x, 0.1)
if changed: node.setX(new_x)
changed, new_y = imgui.drag_float("位置 Y##pos", pos.y, 0.1)
if changed: node.setY(new_y)
changed, new_z = imgui.drag_float("位置 Z##pos", pos.z, 0.1)
if changed: node.setZ(new_z)
# 旋转 (度数)
hpr = node.getHpr()
changed, new_h = imgui.drag_float("旋转 H##rot", hpr.x, 1.0)
if changed: node.setH(new_h)
changed, new_p = imgui.drag_float("旋转 P##rot", hpr.y, 1.0)
if changed: node.setP(new_p)
changed, new_r = imgui.drag_float("旋转 R##rot", hpr.z, 1.0)
if changed: node.setR(new_r)
# 缩放
scale = node.getScale()
changed, new_sx = imgui.drag_float("缩放 X##scale", scale.x, 0.1)
if changed: node.setSx(new_sx)
changed, new_sy = imgui.drag_float("缩放 Y##scale", scale.y, 0.1)
if changed: node.setSy(new_sy)
changed, new_sz = imgui.drag_float("缩放 Z##scale", scale.z, 0.1)
if changed: node.setSz(new_sz)
def _draw_gui_properties(self, node):
"""绘制GUI元素属性"""
imgui.text("GUI属性")
# 获取GUI元素
gui_element = None
if hasattr(node, 'getPythonTag'):
gui_element = node.getPythonTag('gui_element')
if gui_element:
# GUI类型
gui_type = getattr(gui_element, 'gui_type', 'UNKNOWN')
imgui.text(f"GUI类型: {gui_type}")
# 文本内容 (适用于按钮、标签等)
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)
# 大小
if hasattr(gui_element, 'size'):
size = gui_element.size
changed, new_w = imgui.drag_float("宽度", size[0], 1.0)
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.drag_float("高度", size[1], 1.0)
if changed and hasattr(self, 'gui_manager'):
new_size = (size[0], new_h)
self.gui_manager.editGUIElement(gui_element, 'size', new_size)
def _draw_light_properties(self, node):
"""绘制光源属性"""
imgui.text("光源属性")
# 光源颜色
if hasattr(node, 'getColor'):
color = node.getColor()
changed, new_r = imgui.drag_float("颜色 R", color.x, 0.01, 0.0, 1.0)
if changed: node.setColor(new_r, color.y, color.z, color.w)
changed, new_g = imgui.drag_float("颜色 G", color.y, 0.01, 0.0, 1.0)
if changed: node.setColor(color.x, new_g, color.z, color.w)
changed, new_b = imgui.drag_float("颜色 B", color.z, 0.01, 0.0, 1.0)
if changed: node.setColor(color.x, color.y, new_b, color.w)
# 光源强度
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()
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")
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 _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)
# 设置默认的基础颜色(如果模型没有颜色)
if model_node.getColor() == (1, 1, 1, 1): # 默认白色
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("删除节点")[0]:
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("重命名")[0]:
self._renaming_node = True
imgui.close_current_popup()
imgui.separator()
if imgui.menu_item("复制")[0]:
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("聚焦")[0]:
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.removeNode()
# 清除选择
if hasattr(self, 'selection') and self.selection:
if self.selection.selectedNode == node:
self.selection.clearSelection()
# 添加成功消息
self.add_success_message(f"已删除节点: {node.getName() or '未命名节点'}")
def _copy_node(self, node):
"""复制节点"""
if not node or node.isEmpty():
return
# 这里可以实现节点复制逻辑
# 暂时只显示消息
self.add_info_message(f"复制功能暂未实现: {node.getName() or '未命名节点'}")
demo = MyWorld()
demo.run()