2817 lines
113 KiB
Python
2817 lines
113 KiB
Python
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()
|