EG/demo.py
2026-01-26 10:43:48 +08:00

8279 lines
342 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
from panda3d.core import NodePath
import p3dimgui
from imgui_bundle import imgui, imgui_ctx
import sys
import os
import warnings
import threading
import time
from pathlib import Path
# 导入MyWorld类和必要的模块
from core.world import CoreWorld
from core.selection import SelectionSystem
from core.event_handler import EventHandler
from core.tool_manager import ToolManager
from core.script_system import ScriptManager
from core.patrol_system import PatrolSystem
from core.Command_System import CommandManager
from gui.gui_manager import GUIManager
from core.terrain_manager import TerrainManager
from scene.scene_manager import SceneManager
from project.project_manager import ProjectManager
from core.InfoPanelManager import InfoPanelManager
from core.collision_manager import CollisionManager
from core.CustomMouseController import CustomMouseController
from core.resource_manager import ResourceManager
# 拖拽监控类
class DragDropMonitor:
"""拖拽文件监控器"""
def __init__(self, world):
self.world = world
self.running = False
self.thread = None
# 支持的文件格式
self.supported_formats = ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']
def start(self):
"""启动监控"""
if not self.running:
self.running = True
self.thread = threading.Thread(target=self._monitor_loop, daemon=True)
self.thread.start()
def stop(self):
"""停止监控"""
self.running = False
if self.thread:
self.thread.join()
def _monitor_loop(self):
"""监控循环"""
while self.running:
try:
# 这里可以实现具体的拖拽检测逻辑
# 由于Panda3D限制我们使用简化的实现
time.sleep(0.1)
# 检查是否有新的拖拽文件
self._check_for_dropped_files()
except Exception as e:
print(f"拖拽监控错误: {e}")
time.sleep(1)
def _check_for_dropped_files(self):
"""检查是否有拖拽的文件"""
# 这里可以实现具体的文件检测逻辑
# 由于系统限制,我们提供一个占位符实现
pass
def add_file_from_external(self, file_path):
"""从外部添加文件路径(用于系统级拖拽集成)"""
if self.world:
self.add_dragged_file(file_path)
try:
# 尝试导入视频管理器,避免循环导入
import importlib.util
spec = importlib.util.spec_from_file_location("video_integration", "demo/video_integration.py")
video_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(video_module)
VideoManager = video_module.VideoManager
except:
# 如果video_integration模块不存在则跳过
VideoManager = None
print("⚠ 视频管理器模块未找到,视频功能将不可用")
warnings.filterwarnings("ignore", category=DeprecationWarning)
class MyWorld(CoreWorld):
def __init__(self):
super().__init__()
# 设置窗口为最大化模式
props = WindowProperties()
props.set_maximized(True)
self.win.request_properties(props)
print("✓ 窗口已设置为最大化模式")
# 初始化选择和变换系统
self.selection = SelectionSystem(self)
# 绑定F键用于聚焦选中节点
self.accept("f", self.onFocusKeyPressed)
self.accept("F", self.onFocusKeyPressed) # 大写F
#初始化巡检系统
self.patrol_system = PatrolSystem(self)
self.accept("p",self.onPatrolKeyPressed)
self.accept("P",self.onPatrolKeyPressed)
# 绑定鼠标事件用于3D场景选择
self.accept("mouse1", self.onMouseClick)
self.accept("mouse1-up", self.onMouseRelease)
# 尝试多种鼠标移动事件绑定方式
self.accept("mouse-move", self.onMouseMove)
self.accept("drag", self.onMouseMove)
# 初始化事件处理系统
self.event_handler = EventHandler(self)
# 初始化工具管理系统
self.tool_manager = ToolManager(self)
# 初始化脚本管理系统
self.script_manager = ScriptManager(self)
# 初始化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)
# 初始化Actor缓存系统用于动画控制
self._actor_cache = {}
print("✓ Actor缓存系统初始化完成")
# 初始化自定义鼠标控制器(视角移动)
self.mouse_controller = CustomMouseController(self)
self.mouse_controller.setUp(mouse_speed=25, move_speed=20)
print("✓ 自定义鼠标控制器初始化完成")
# 初始化VR管理器
try:
from core.vr import VRManager
self.vr_manager = VRManager(self)
print("✓ VR管理器初始化完成")
except Exception as e:
print(f"⚠ VR管理器初始化失败: {e}")
self.vr_manager = None
# 调试选项
self.debug_collision = True # 是否显示碰撞体
# 默认启用模型间碰撞检测(可选)
self.enableModelCollisionDetection(enable=True, frequency=0.1, threshold=0.5)
# 碰撞检测UI相关变量
self._selected_collision_shape = "球形 (Sphere)" # 默认选择的碰撞形状
# 启动脚本系统
self.script_manager.start_system()
self.terrain_manager = TerrainManager(self)
self.terrain_edit_radius = 3.0
self.terrain_edit_strength=0.3
self.terrain_edit_operation = "add"
# Install Dear ImGui
p3dimgui.init()
# 启用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.hotReloadEnabled = True
self._new_script_name = "new_script"
self._selected_template = 0
self._mount_script_index = 0
# 变换监控相关
self._transform_monitoring = False
self._monitored_node = None
self._last_transform_values = {}
self._transform_update_timer = 0
self._transform_update_interval = 0.05 # 50ms检查一次
self._clipboard_pos = None # 位置剪贴板
# 颜色选择器相关
self._color_picker_active = False
self._color_picker_target = None # (target_object, property_name)
self._color_picker_current_color = (1.0, 1.0, 1.0, 1.0)
self._color_picker_callback = None
# 字体选择器相关
self._font_selector_active = False
self._font_selector_target = None # (target_object, property_name)
self._font_selector_current_font = ""
self._font_selector_callback = None
self._available_fonts = [] # 可用字体列表
self._refresh_available_fonts()
# 菜单状态管理
self.show_new_project_dialog = False
self.show_open_project_dialog = False
self.show_save_as_dialog = False
# 路径选择对话框状态
self.show_path_browser = False
self.path_browser_mode = "" # "new_project" 或 "open_project"
self.path_browser_current_path = os.getcwd()
self.path_browser_selected_path = ""
self.path_browser_items = []
# 快捷键映射
self.shortcut_keys = {
'Ctrl+N': self._on_new_project,
'Ctrl+O': self._on_open_project,
'Ctrl+S': self._on_save_project,
'Alt+F4': self._on_exit
}
# 键盘状态跟踪
self.ctrl_pressed = False
self.alt_pressed = False
# 消息系统
self.messages = []
self.max_messages = 5 # 最多显示5条消息
# 剪切板系统
self.clipboard = []
self.clipboard_mode = "" # "copy" 或 "cut"
# 视角控制状态
self.camera_control_enabled = True
self.show_camera_info = False
# 拖拽导入状态
self.dragged_files = []
self.is_dragging = False
self.show_drag_overlay = False
self.drag_drop_monitor = None
# 导入功能状态
self.show_import_dialog = False
self.import_file_path = ""
self.supported_formats = [".gltf", ".glb", ".fbx", ".bam", ".egg", ".obj"]
# 创建功能状态
self.show_3d_text_dialog = False
self.show_3d_image_dialog = False
self.show_gui_button_dialog = False
self.show_gui_label_dialog = False
self.show_gui_entry_dialog = False
self.show_gui_image_dialog = False
self.show_video_screen_dialog = False
self.show_2d_video_screen_dialog = False
self.show_spherical_video_dialog = False
self.show_virtual_screen_dialog = False
self.show_spot_light_dialog = False
self.show_point_light_dialog = False
self.show_terrain_dialog = False
self.show_heightmap_browser = False
self.show_script_dialog = False
self.show_script_browser = False
# 高度图浏览器状态
self.heightmap_browser_current_path = os.getcwd()
self.heightmap_browser_selected_path = ""
self.heightmap_browser_items = []
self.heightmap_file_path = ""
self.supported_heightmap_formats = [".png", ".jpg", ".jpeg", ".bmp", ".tiff", ".tif"]
# 脚本浏览器状态
self.script_browser_current_path = os.getcwd()
self.script_browser_selected_path = ""
self.script_browser_items = []
# 对话框参数存储
self.dialog_params = {} # 存储各种对话框的参数
# 脚本系统状态
self.hotReloadEnabled = False
# 初始化高度图浏览器
self._refresh_heightmap_browser()
# 初始化脚本浏览器
self._refresh_script_browser()
self.accept('imgui-new-frame', self.__newFrame)
self.accept('`', self.__toggleImgui)
# 添加键盘事件监听用于快捷键
self.accept('control', self._on_ctrl_pressed)
self.accept('control-up', self._on_ctrl_released)
self.accept('alt', self._on_alt_pressed)
self.accept('alt-up', self._on_alt_released)
self.accept('n', self._on_n_pressed)
self.accept('o', self._on_o_pressed)
self.accept('control-s', self._on_save_project)
self.accept('f4', self._on_f4_pressed)
# 编辑功能快捷键
self.accept('z', self._on_z_pressed)
self.accept('y', self._on_y_pressed)
self.accept('x', self._on_x_pressed)
self.accept('c', self._on_c_pressed)
self.accept('v', self._on_v_pressed)
self.accept('delete', self._on_delete_pressed)
# 滚轮事件
self.accept('wheel_up', self._on_wheel_up)
self.accept('wheel_down', self._on_wheel_down)
self.testTexture = None
self.icons = {} # 初始化图标字典
# 添加初始化消息
self.add_success_message("MyWorld 初始化完成")
self.add_info_message("ImGui菜单系统已就绪")
self.add_info_message("快捷键已启用 (Ctrl+N, Ctrl+O, Ctrl+S, Alt+F4)")
# 启用拖拽导入功能
self.setup_drag_drop_support()
self.add_info_message("拖拽导入功能已启用 - 可将3D文件拖拽到窗口中导入")
print("✓ MyWorld 初始化完成")
# ==================== 兼容性属性 ====================
# 保留models属性以兼容现有代码
@property
def models(self):
"""模型列表的兼容性属性"""
return self.scene_manager.models
@models.setter
def models(self, value):
"""模型列表的兼容性设置器"""
self.scene_manager.models = value
# 保留gui_elements属性以兼容现有代码
@property
def gui_elements(self):
"""GUI元素列表的兼容性属性"""
return self.gui_manager.gui_elements
@gui_elements.setter
def gui_elements(self, value):
"""GUI元素列表的兼容性设置器"""
self.gui_manager.gui_elements = value
# 保留guiEditMode属性以兼容现有代码
@property
def guiEditMode(self):
"""GUI编辑模式的兼容性属性"""
return self.gui_manager.guiEditMode
@guiEditMode.setter
def guiEditMode(self, value):
"""GUI编辑模式的兼容性设置器"""
self.gui_manager.guiEditMode = value
# 保留currentTool属性以兼容现有代码
@property
def currentTool(self):
"""当前工具的兼容性属性"""
return self.tool_manager.currentTool
@currentTool.setter
def currentTool(self, value):
"""当前工具的兼容性设置器"""
self.tool_manager.currentTool = value
# 保留terrains属性以兼容现有代码
@property
def terrains(self):
"""地形列表的兼容性属性"""
return self.terrain_manager.terrains
@terrains.setter
def terrains(self,value):
"""地形列表的兼容性设置器"""
self.terrain_manager.terrains = value
def onFocusKeyPressed(self):
"""处理 F 键按下事件"""
try:
if hasattr(self, 'selection') and self.selection.selectedNode:
self.selection.focusCameraOnSelectedNodeAdvanced()
else:
print("当前没有选中任何节点")
except Exception as e:
print(f"处理 F 键事件失败: {e}")
def onMouseClick(self):
"""处理鼠标点击事件"""
print("\n=== 鼠标点击事件触发 ===")
try:
# 检查鼠标是否有效
if not self.mouseWatcherNode.hasMouse():
print("❌ 鼠标无效或不在窗口内")
return
print("✓ 鼠标位置有效")
# 获取鼠标位置
mouse_x = self.mouseWatcherNode.getMouseX()
mouse_y = self.mouseWatcherNode.getMouseY()
print(f"📍 鼠标标准化坐标: ({mouse_x:.3f}, {mouse_y:.3f})")
# 转换为窗口坐标
winWidth, winHeight = self.win.getSize()
window_x = (mouse_x + 1) * 0.5 * winWidth
window_y = (1 - mouse_y) * 0.5 * winHeight
print(f"📍 鼠标窗口坐标: ({window_x:.1f}, {window_y:.1f})")
print(f"📐 窗口尺寸: {winWidth} x {winHeight}")
# 检查ImGui是否捕获了鼠标
imgui_captured = self.processImGuiMouseClick(window_x, window_y)
print(f"🖱️ ImGui捕获状态: {imgui_captured}")
if imgui_captured:
print("❌ ImGui处理了该事件跳过3D场景选择")
return
# 调用事件处理器进行射线检测和选择
if hasattr(self, 'event_handler'):
print("✓ 找到事件处理器,开始处理选择")
self.event_handler.mousePressEventLeft({
'x': window_x,
'y': window_y
})
else:
print("❌ 未找到事件处理器")
except Exception as e:
print(f"❌ 处理鼠标点击事件失败: {e}")
import traceback
traceback.print_exc()
def onMouseRelease(self):
"""处理鼠标释放事件"""
try:
# 检查鼠标是否有效
if not self.mouseWatcherNode.hasMouse():
return
# 获取鼠标位置
mouse_x = self.mouseWatcherNode.getMouseX()
mouse_y = self.mouseWatcherNode.getMouseY()
# 转换为窗口坐标
winWidth, winHeight = self.win.getSize()
window_x = (mouse_x + 1) * 0.5 * winWidth
window_y = (1 - mouse_y) * 0.5 * winHeight
# 调用事件处理器
if hasattr(self, 'event_handler'):
self.event_handler.mouseReleaseEventLeft({
'x': window_x,
'y': window_y
})
except Exception as e:
print(f"处理鼠标释放事件失败: {e}")
def onMouseMove(self):
"""处理鼠标移动事件"""
try:
# 检查鼠标是否有效
if not self.mouseWatcherNode.hasMouse():
return
# 获取鼠标位置
mouse_x = self.mouseWatcherNode.getMouseX()
mouse_y = self.mouseWatcherNode.getMouseY()
# 转换为窗口坐标
winWidth, winHeight = self.win.getSize()
window_x = (mouse_x + 1) * 0.5 * winWidth
window_y = (1 - mouse_y) * 0.5 * winHeight
# 调用事件处理器
if hasattr(self, 'event_handler'):
self.event_handler.mouseMoveEvent({
'x': window_x,
'y': window_y
})
except Exception as e:
print(f"处理鼠标移动事件失败: {e}")
def onPatrolKeyPressed(self):
"""处理 P 键按下事件 - 控制巡检系统"""
try:
print("检测到 P 键按下")
if not self.patrol_system.is_patrolling:
if not self.patrol_system.patrol_points:
self.createDefaultPatrolRoute()
if self.patrol_system.start_patrol():
print("✓ 巡检已开始")
else:
print("✗ 巡检启动失败")
else:
if self.patrol_system.stop_patrol():
print("✓ 巡检已停止")
else:
print("✗ 巡检停止失败")
except Exception as e:
print(f"处理 P 键事件失败: {e}")
def createDefaultPatrolRoute(self):
"""创建默认巡检路线"""
try:
self.patrol_system.clear_patrol_points()
self.patrol_system.add_patrol_point((0, -10, 2), (0,0,0), 1.5)
self.patrol_system.add_patrol_point((10, -10, 2), (0,0,0), 1.5)
self.patrol_system.add_patrol_point((10, 5, 2), (0,0,0), 1.5)
self.patrol_system.add_patrol_point((10, 0, 5), (0,0,0), 1.5)
self.patrol_system.add_patrol_point((0, -10, 2), None, 2.5)
print("✓ 默认自动朝向巡检路线已创建")
self.patrol_system.list_patrol_points()
except Exception as e:
print(f"创建默认自动朝向巡检路线失败: {e}")
def enableModelCollisionDetection(self, enable=True, frequency=0.1, threshold=0.5):
"""启用模型间碰撞检测"""
return self.collision_manager.enableModelCollisionDetection(enable, frequency, threshold)
def __toggleImgui(self):
if not self.imgui.isKeyboardCaptured():
self.imgui.toggle()
def __newFrame(self):
# Dear ImGui commands can be placed here.
# 创建全屏DockSpace在第一帧之后创建
if imgui.get_frame_count() > 0:
viewport = imgui.get_main_viewport()
imgui.dock_space_over_viewport(0, viewport, imgui.DockNodeFlags_.passthru_central_node)
# 在第一帧应用样式
if imgui.get_frame_count() == 0:
self.style_manager.apply_style()
# 加载图标
self.icons = {
'select': self.style_manager.load_icon('select_tool'),
'move': self.style_manager.load_icon('move_tool'),
'rotate': self.style_manager.load_icon('rotate_tool'),
'scale': self.style_manager.load_icon('scale_tool'),
'success': self.style_manager.load_icon('success_icon'),
'warning': self.style_manager.load_icon('warning_icon'),
'property_select_image': self.style_manager.load_icon('property_select_image'),
'delete_fail_icon': self.style_manager.load_icon('delete_fail_icon'),
}
# 简化字体加载(只使用中文字体)
try:
# 清除现有字体
self.imgui.io.fonts.clear()
# 尝试加载中文字体
import platform
from pathlib import Path
system = platform.system().lower()
if system == "linux":
font_path = "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"
elif system == "windows":
font_path = "C:/Windows/Fonts/msyh.ttc"
elif system == "darwin":
font_path = "/System/Library/Fonts/PingFang.ttc"
else:
font_path = None
if font_path and Path(font_path).exists():
self.imgui.io.fonts.add_font_from_file_ttf(font_path, 14.0)
print(f"✓ 中文字体加载成功: {font_path}")
else:
# 回退到原来的字体
fallback_font_path = "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc"
if fallback_font_path and Path(fallback_font_path).exists():
self.imgui.io.fonts.add_font_from_file_ttf(fallback_font_path, 14.0)
print(f"✓ 回退字体加载成功: {fallback_font_path}")
else:
self.imgui.io.fonts.add_font_default()
print("✓ 使用默认字体")
except Exception as e:
print(f"⚠ 字体初始化失败: {e}")
try:
self.imgui.io.fonts.add_font_default()
print("✓ 使用备用默认字体")
except:
pass
# 获取窗口尺寸
display_size = imgui.get_io().display_size
window_width = display_size.x
window_height = display_size.y
# 绘制菜单栏
self._draw_menu_bar()
# 绘制停靠布局
self._draw_docked_layout(window_width, window_height)
# 绘制纹理测试窗口
if self.testTexture:
with self.style_manager.begin_styled_window("Texture Test", True,
imgui.WindowFlags_.always_auto_resize) as (_, windowOpen):
if not windowOpen:
self.imgui.removeTexture(self.testTexture)
self.testTexture = None
return
imgui.image(self.testTexture, (256, 256))
# 绘制ImGui演示窗口
if self.showDemoWindow:
imgui.show_demo_window()
# 绘制对话框
self._draw_new_project_dialog()
self._draw_open_project_dialog()
self._draw_path_browser()
self._draw_import_dialog()
# 绘制颜色选择器
self._draw_color_picker()
# 绘制字体选择器
self._draw_font_selector()
# 绘制创建功能对话框
self._draw_3d_text_dialog()
self._draw_3d_image_dialog()
self._draw_gui_button_dialog()
self._draw_gui_label_dialog()
self._draw_gui_entry_dialog()
self._draw_gui_image_dialog()
self._draw_video_screen_dialog()
self._draw_2d_video_screen_dialog()
self._draw_spherical_video_dialog()
self._draw_virtual_screen_dialog()
self._draw_spot_light_dialog()
self._draw_point_light_dialog()
self._draw_terrain_dialog()
self._draw_heightmap_browser()
self._draw_script_dialog()
# 绘制纹理选择对话框
self._draw_texture_file_dialog()
self._draw_script_browser()
# 绘制右键菜单
self._draw_context_menus()
# 绘制拖拽界面
self._draw_drag_drop_interface()
# 更新变换监控
dt = imgui.get_io().delta_time
self.update_transform_monitoring(dt)
# 检查鼠标释放事件(用于处理拖拽结束)
if imgui.is_mouse_released(0) and self.is_dragging:
if not imgui.is_any_window_hovered():
# 在3D视图中释放处理模型导入
self._handle_drag_drop_completion()
def _draw_docked_layout(self, window_width, window_height):
"""绘制可停靠的布局(支持拖拽)"""
# 左侧场景树面板
if self.showSceneTree:
self._draw_scene_tree()
# 资源管理器面板
if self.showResourceManager:
self._draw_resource_manager()
# 属性面板
if self.showPropertyPanel:
self._draw_property_panel()
# 脚本面板
if self.showScriptPanel:
self._draw_script_panel()
# 底部控制台
if self.showConsole:
self._draw_console()
# 顶部工具栏
if self.showToolbar:
self._draw_toolbar()
def _draw_menu_bar(self):
"""绘制菜单栏"""
with imgui_ctx.begin_main_menu_bar() as main_menu:
if main_menu:
# 文件菜单
with imgui_ctx.begin_menu("文件") as file_menu:
if file_menu:
if imgui.menu_item("新建项目", "Ctrl+N", False, True)[1]:
self._on_new_project()
if imgui.menu_item("打开项目", "Ctrl+O", False, True)[1]:
self._on_open_project()
imgui.separator()
if imgui.menu_item("保存", "Ctrl+S", False, True)[1]:
self._on_save_project()
if imgui.menu_item("另存为", "", False, True)[1]:
self._on_save_as_project()
imgui.separator()
if imgui.menu_item("退出", "Alt+F4", False, True)[1]:
self._on_exit()
# 编辑菜单
with imgui_ctx.begin_menu("编辑") as edit_menu:
if edit_menu:
if imgui.menu_item("撤销", "Ctrl+Z", False, True)[1]:
self._on_undo()
if imgui.menu_item("重做", "Ctrl+Y", False, True)[1]:
self._on_redo()
imgui.separator()
if imgui.menu_item("剪切", "Ctrl+X", False, True)[1]:
self._on_cut()
if imgui.menu_item("复制", "Ctrl+C", False, True)[1]:
self._on_copy()
if imgui.menu_item("粘贴", "Ctrl+V", False, True)[1]:
self._on_paste()
imgui.separator()
if imgui.menu_item("删除", "Del", False, True)[1]:
self._on_delete()
# 创建菜单
with imgui_ctx.begin_menu("创建") as create_menu:
if create_menu:
if imgui.menu_item("空对象", "", False, True)[1]:
self._on_create_empty_object()
# 3D对象子菜单
with imgui_ctx.begin_menu("3D对象") as three_d_menu:
if three_d_menu:
if imgui.menu_item("立方体", "", False, True)[1]:
self._on_create_cube()
if imgui.menu_item("球体", "", False, True)[1]:
self._on_create_sphere()
if imgui.menu_item("圆柱体", "", False, True)[1]:
self._on_create_cylinder()
if imgui.menu_item("平面", "", False, True)[1]:
self._on_create_plane()
# 3D GUI子菜单
with imgui_ctx.begin_menu("3D GUI") as three_d_gui_menu:
if three_d_gui_menu:
if imgui.menu_item("3D文本", "", False, True)[1]:
self._on_create_3d_text()
if imgui.menu_item("3D图片", "", False, True)[1]:
self._on_create_3d_image()
# GUI子菜单
with imgui_ctx.begin_menu("GUI") as gui_menu:
if gui_menu:
if imgui.menu_item("创建按钮", "", False, True)[1]:
self._on_create_gui_button()
if imgui.menu_item("创建标签", "", False, True)[1]:
self._on_create_gui_label()
if imgui.menu_item("创建输入框", "", False, True)[1]:
self._on_create_gui_entry()
if imgui.menu_item("创建图片", "", False, True)[1]:
self._on_create_gui_image()
imgui.separator()
if imgui.menu_item("创建视频屏幕", "", False, True)[1]:
self._on_create_video_screen()
if imgui.menu_item("创建2D视频屏幕", "", False, True)[1]:
self._on_create_2d_video_screen()
if imgui.menu_item("创建球形视频", "", False, True)[1]:
self._on_create_spherical_video()
if imgui.menu_item("创建虚拟屏幕", "", False, True)[1]:
self._on_create_virtual_screen()
# 光源子菜单
with imgui_ctx.begin_menu("光源") as light_menu:
if light_menu:
if imgui.menu_item("聚光灯", "", False, True)[1]:
self._on_create_spot_light()
if imgui.menu_item("点光源", "", False, True)[1]:
self._on_create_point_light()
# 地形子菜单
with imgui_ctx.begin_menu("地形") as terrain_menu:
if terrain_menu:
if imgui.menu_item("创建平面地形", "", False, True)[1]:
self._on_create_flat_terrain()
if imgui.menu_item("从高度图创建地形", "", False, True)[1]:
self._on_create_heightmap_terrain()
# 脚本子菜单
with imgui_ctx.begin_menu("脚本") as script_menu:
if script_menu:
if imgui.menu_item("创建脚本...", "", False, True)[1]:
self._on_create_script()
if imgui.menu_item("加载脚本文件...", "", False, True)[1]:
self._on_load_script()
imgui.separator()
if imgui.menu_item("重载所有脚本", "", False, True)[1]:
self._on_reload_all_scripts()
_, self.hotReloadEnabled = imgui.menu_item("启用热重载", "", self.hotReloadEnabled, True)
if imgui.menu_item("脚本管理器", "", False, True)[1]:
self._on_open_scripts_manager()
# 信息面板子菜单
with imgui_ctx.begin_menu("信息面板") as info_panel_menu:
if info_panel_menu:
if imgui.menu_item("创建2D示例面板", "", False, True)[1]:
self._on_create_2d_sample_panel()
if imgui.menu_item("创建3D实例面板", "", False, True)[1]:
self._on_create_3d_sample_panel()
if imgui.menu_item("Web面板", "", False, True)[1]:
self._on_create_web_panel()
# 视图菜单
with imgui_ctx.begin_menu("视图") as view_menu:
if view_menu:
_, self.showToolbar = imgui.menu_item("工具栏", "", self.showToolbar, True)
_, self.showSceneTree = imgui.menu_item("场景树", "", self.showSceneTree, True)
_, self.showResourceManager = imgui.menu_item("资源管理器", "", self.showResourceManager, True)
_, self.showPropertyPanel = imgui.menu_item("属性面板", "", self.showPropertyPanel, True)
_, self.showConsole = imgui.menu_item("控制台", "", self.showConsole, True)
_, self.showScriptPanel = imgui.menu_item("脚本管理", "", self.showScriptPanel, True)
# 工具菜单
with imgui_ctx.begin_menu("工具") as tools_menu:
if tools_menu:
if imgui.menu_item("导入模型", "", False, True)[1]:
self._on_import_model()
imgui.menu_item("地形编辑器", "", False, True)
imgui.menu_item("材质编辑器", "", False, True)
imgui.menu_item("脚本编辑器", "", False, True)
# 窗口菜单 - 已隐藏
# with imgui_ctx.begin_menu("窗口") as window_menu:
# if window_menu:
# _, self.showDemoWindow = imgui.menu_item("ImGui演示", "", self.showDemoWindow, True)
# if self.testTexture:
# imgui.menu_item("关闭纹理测试", "", False, True)
# else:
# imgui.menu_item("显示纹理测试", "", False, True)
# 帮助菜单
with imgui_ctx.begin_menu("帮助") as help_menu:
if help_menu:
imgui.menu_item("关于", "", False, True)
imgui.menu_item("文档", "", False, True)
# 右侧显示FPS
imgui.set_cursor_pos_x(imgui.get_window_size().x - 140)
imgui.text("%.2f FPS (%.2f ms)" % (imgui.get_io().framerate, 1000.0 / imgui.get_io().framerate))
def _draw_toolbar(self):
"""绘制工具栏"""
# 工具栏可以保持无标题栏,但允许移动和调整大小
flags = self.style_manager.get_window_flags("toolbar")
with self.style_manager.begin_styled_window("工具栏", self.showToolbar, flags):
self.showToolbar = True # 确保窗口保持打开
# 选择工具按钮
select_active = self.tool_manager.isSelectionTool()
if self.icons.get('select'):
tint_col = (1.0, 1.0, 0.0, 1.0) if select_active else (1.0, 1.0, 1.0, 1.0)
if self.style_manager.image_button(self.icons['select'], (24, 24), tint_col=tint_col):
self.tool_manager.setCurrentTool("选择")
if imgui.is_item_hovered():
imgui.set_tooltip("选择工具 (Q)")
imgui.same_line()
else:
if imgui.button("选择##select_tool"):
self.tool_manager.setCurrentTool("选择")
if select_active:
draw_list = imgui.get_window_draw_list()
button_min = imgui.get_item_rect_min()
button_max = imgui.get_item_rect_max()
draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3)))
imgui.same_line()
# 移动工具按钮
move_active = self.tool_manager.isMoveTool()
if self.icons.get('move'):
# 使用不同颜色表示活动状态
tint_col = (1.0, 1.0, 0.0, 1.0) if move_active else (1.0, 1.0, 1.0, 1.0) # 活动时显示黄色
if self.style_manager.image_button(self.icons['move'], (24, 24), tint_col=tint_col):
self.tool_manager.setCurrentTool("移动")
if imgui.is_item_hovered():
imgui.set_tooltip("移动工具 (W)")
imgui.same_line()
else:
if imgui.button("移动##move_tool"):
self.tool_manager.setCurrentTool("移动")
if move_active:
# 为活动按钮添加背景色
draw_list = imgui.get_window_draw_list()
button_min = imgui.get_item_rect_min()
button_max = imgui.get_item_rect_max()
draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3)))
imgui.same_line()
# 旋转工具按钮
rotate_active = self.tool_manager.isRotateTool()
if self.icons.get('rotate'):
tint_col = (1.0, 1.0, 0.0, 1.0) if rotate_active else (1.0, 1.0, 1.0, 1.0)
if self.style_manager.image_button(self.icons['rotate'], (24, 24), tint_col=tint_col):
self.tool_manager.setCurrentTool("旋转")
if imgui.is_item_hovered():
imgui.set_tooltip("旋转工具 (E)")
imgui.same_line()
else:
if imgui.button("旋转##rotate_tool"):
self.tool_manager.setCurrentTool("旋转")
if rotate_active:
draw_list = imgui.get_window_draw_list()
button_min = imgui.get_item_rect_min()
button_max = imgui.get_item_rect_max()
draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3)))
imgui.same_line()
# 缩放工具按钮
scale_active = self.tool_manager.isScaleTool()
if self.icons.get('scale'):
tint_col = (1.0, 1.0, 0.0, 1.0) if scale_active else (1.0, 1.0, 1.0, 1.0)
if self.style_manager.image_button(self.icons['scale'], (24, 24), tint_col=tint_col):
self.tool_manager.setCurrentTool("缩放")
if imgui.is_item_hovered():
imgui.set_tooltip("缩放工具 (R)")
else:
if imgui.button("缩放##scale_tool"):
self.tool_manager.setCurrentTool("缩放")
if scale_active:
draw_list = imgui.get_window_draw_list()
button_min = imgui.get_item_rect_min()
button_max = imgui.get_item_rect_max()
draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3)))
imgui.same_line()
imgui.separator()
imgui.same_line()
# 工具按钮已移除(导入、保存、播放)
def _draw_scene_tree(self):
"""绘制场景树面板"""
# 使用更少的限制性标志允许docking
flags = (imgui.WindowFlags_.no_collapse)
with self.style_manager.begin_styled_window("场景树", self.showSceneTree, flags):
self.showSceneTree = True # 确保窗口保持打开
imgui.text("场景层级")
imgui.separator()
# 构建动态场景树
self._build_scene_tree()
def _build_scene_tree(self):
"""构建动态场景树"""
# 渲染节点
if imgui.tree_node("渲染"):
# 环境光
if hasattr(self, 'ambient_light') and self.ambient_light:
self._draw_scene_node(self.ambient_light, "环境光", "light")
# 聚光灯
if hasattr(self, 'scene_manager') and self.scene_manager:
if hasattr(self.scene_manager, 'Spotlight') and self.scene_manager.Spotlight:
for i, spotlight in enumerate(self.scene_manager.Spotlight):
self._draw_scene_node(spotlight, f"聚光灯_{i+1}", "light")
if hasattr(self.scene_manager, 'Pointlight') and self.scene_manager.Pointlight:
for i, pointlight in enumerate(self.scene_manager.Pointlight):
self._draw_scene_node(pointlight, f"点光源_{i+1}", "light")
# 地板
if hasattr(self, 'ground') and self.ground:
self._draw_scene_node(self.ground, "地板", "geometry")
imgui.tree_pop()
# 相机节点
if imgui.tree_node("相机"):
if hasattr(self, 'camera') and self.camera:
self._draw_scene_node(self.camera, "主相机", "camera")
imgui.tree_pop()
# 3D模型节点
if imgui.tree_node("模型"):
if hasattr(self, 'scene_manager') and self.scene_manager and hasattr(self.scene_manager, 'models'):
if self.scene_manager.models:
for i, model in enumerate(self.scene_manager.models):
if model and not model.isEmpty():
self._draw_scene_node(model, model.getName() or f"模型_{i+1}", "model")
else:
imgui.text("(空)")
else:
imgui.text("(空)")
imgui.tree_pop()
# GUI元素节点
if imgui.tree_node("GUI元素"):
if hasattr(self, 'gui_manager') and self.gui_manager and hasattr(self.gui_manager, 'gui_elements'):
if self.gui_manager.gui_elements:
for gui_element in self.gui_manager.gui_elements:
if gui_element and hasattr(gui_element, 'node'):
gui_type = getattr(gui_element, 'gui_type', 'GUI_UNKNOWN')
display_name = getattr(gui_element, 'name', gui_type)
self._draw_scene_node(gui_element.node, display_name, "gui", gui_type)
else:
imgui.text("(空)")
else:
imgui.text("(空)")
imgui.tree_pop()
def _draw_scene_node(self, node, name, node_type, gui_subtype=None):
"""绘制单个场景节点"""
if not node or node.isEmpty():
return
# 检查是否被选中
is_selected = (hasattr(self, 'selection') and self.selection and
hasattr(self.selection, 'selectedNode') and
self.selection.selectedNode == node)
# 节点可见性
is_visible = node.is_hidden() == False
# 设置选择颜色
if is_selected:
imgui.push_style_color(imgui.Col_.text, (0.2, 0.6, 1.0, 1.0))
try:
# 显示节点
node_open = imgui.tree_node(name)
# 处理节点选择
if imgui.is_item_clicked():
if hasattr(self, 'selection') and self.selection:
self.selection.updateSelection(node)
# 右键菜单
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
self._show_node_context_menu(node, name, node_type)
# 显示节点属性
imgui.same_line()
if is_visible:
imgui.text_colored((0.5, 1.0, 0.5, 1.0), "可见")
else:
imgui.text_colored((0.5, 0.5, 0.5, 1.0), "隐藏")
if node_open:
# 如果有子节点,递归显示
if node.getNumChildren() > 0:
for i in range(node.getNumChildren()):
child = node.getChild(i)
if child and not child.isEmpty():
child_name = child.getName() or f"子节点_{i+1}"
self._draw_scene_node(child, child_name, node_type)
imgui.tree_pop()
except Exception as e:
print(f"绘制场景节点时出错: {e}")
finally:
# 确保颜色状态被恢复
if is_selected:
imgui.pop_style_color()
def _show_node_context_menu(self, node, name, node_type):
"""显示节点右键菜单"""
self._context_menu_node = True
self._context_menu_target = node
def _draw_resource_manager(self):
"""绘制资源管理器面板"""
# 使用面板类型的窗口标志支持docking
flags = self.style_manager.get_window_flags("panel")
with self.style_manager.begin_styled_window("资源管理器", self.showResourceManager, flags):
self.showResourceManager = True # 确保窗口保持打开
# 获取资源管理器实例
rm = self.resource_manager
# 工具栏
imgui.text("文件浏览器")
imgui.separator()
# 导航按钮
if imgui.button(""):
rm.navigate_back()
imgui.same_line()
if imgui.button(""):
rm.navigate_forward()
imgui.same_line()
if imgui.button(""):
rm.navigate_up()
imgui.same_line()
if imgui.button("主页"):
rm.navigate_to(rm.project_root / "Resources")
imgui.same_line()
if imgui.button("刷新"):
rm.force_refresh()
# 自动刷新开关
imgui.same_line()
changed, rm.auto_refresh_enabled = imgui.checkbox("自动刷新", rm.auto_refresh_enabled)
if changed:
rm.set_auto_refresh(rm.auto_refresh_enabled)
imgui.same_line()
imgui.text(" ")
imgui.same_line()
# 路径输入框
changed, new_path = imgui.input_text("路径", str(rm.current_path), 256)
if changed:
try:
rm.navigate_to(Path(new_path))
except:
pass
# 搜索框
changed, rm.search_filter = imgui.input_text("搜索", rm.search_filter, 256)
imgui.separator()
# 检查自动刷新
if rm.refresh_if_needed():
# 目录内容发生变化,可以在这里添加通知逻辑
pass
# 获取目录内容
dirs, files = rm.get_directory_contents(rm.current_path)
# 显示目录
for dir_path in dirs:
if not rm.should_show_file(dir_path):
continue
# 目录图标和名称
icon_name = rm.get_file_icon(dir_path.name, is_folder=True)
node_open = False
# 检查是否被选中
is_selected = dir_path in rm.selected_files
# 使用TreeNode来显示目录
if is_selected:
imgui.push_style_color(imgui.Col_.header, (100/255, 150/255, 200/255, 1.0))
# 尝试加载PNG图标
icon_texture = None
try:
# 直接使用图标名称load_icon会自动添加.png
icon_texture = self.style_manager.load_icon(f"file_types/{icon_name}")
except:
pass
if icon_texture:
# 使用PNG图标
imgui.image(icon_texture, (16, 16))
imgui.same_line()
node_open = imgui.tree_node(f"{dir_path.name}")
else:
# 回退到文本标识符
node_open = imgui.tree_node(f"[{icon_name.upper()}]{dir_path.name}")
if is_selected:
imgui.pop_style_color()
# 处理选择
if imgui.is_item_clicked():
if imgui.get_io().key_ctrl:
# 多选模式
if is_selected:
rm.selected_files.discard(dir_path)
else:
rm.selected_files.add(dir_path)
else:
# 单选模式
rm.selected_files.clear()
rm.selected_files.add(dir_path)
rm.focused_file = dir_path
# 双击导航
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
rm.navigate_to(dir_path)
# 右键菜单
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
rm.show_context_menu = True
rm.context_menu_file = dir_path
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
# 如果节点展开,显示子内容
if node_open:
# 获取子目录内容
subdirs, subfiles = rm.get_directory_contents(dir_path)
# 显示子目录
for subdir in subdirs:
if not rm.should_show_file(subdir):
continue
# 初始化变量
subicon_name = "folder"
sub_is_selected = False
# 获取子目录图标名称
subicon_name = rm.get_file_icon(subdir.name, is_folder=True)
sub_is_selected = subdir in rm.selected_files
# 尝试加载PNG图标
subicon_texture = None
try:
subicon_texture = self.style_manager.load_icon(f"file_types/{subicon_name}")
except:
pass
if subicon_texture:
# 使用PNG图标
imgui.image(subicon_texture, (16, 16))
imgui.same_line()
sub_node_open = imgui.tree_node(f" {subdir.name}")
else:
# 回退到文本标识符
sub_node_open = imgui.tree_node(f" [{subicon_name.upper()}]{subdir.name}")
if sub_is_selected:
imgui.pop_style_color()
# 处理子目录的选择
if imgui.is_item_clicked():
if imgui.get_io().key_ctrl:
if sub_is_selected:
rm.selected_files.discard(subdir)
else:
rm.selected_files.add(subdir)
else:
rm.selected_files.clear()
rm.selected_files.add(subdir)
rm.focused_file = subdir
# 双击子目录导航
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
rm.navigate_to(subdir)
# 右键菜单
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
rm.show_context_menu = True
rm.context_menu_file = subdir
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
if sub_node_open:
imgui.tree_pop()
# 显示子文件
for subfile in subfiles:
if not rm.should_show_file(subfile):
continue
subicon_name = rm.get_file_icon(subfile.name)
sub_is_selected = subfile in rm.selected_files
# 尝试加载PNG图标
subicon_texture = None
try:
subicon_texture = self.style_manager.load_icon(f"file_types/{subicon_name}")
except:
pass
if subicon_texture:
# 使用PNG图标
imgui.image(subicon_texture, (16, 16))
imgui.same_line()
selected = imgui.selectable(f" {subfile.name}", sub_is_selected)
else:
# 回退到文本标识符
selected = imgui.selectable(f" [{subicon_name.upper()}] {subfile.name}", sub_is_selected)
# 处理子文件的选择
if selected:
if imgui.get_io().key_ctrl:
if sub_is_selected:
rm.selected_files.discard(subfile)
else:
rm.selected_files.add(subfile)
else:
rm.selected_files.clear()
rm.selected_files.add(subfile)
rm.focused_file = subfile
# 双击子文件操作
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
if subfile.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']:
self.scene_manager.importModel(str(subfile))
self.add_info_message(f"正在导入模型: {subfile.name}")
else:
rm.open_file(subfile)
# 右键菜单
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
rm.show_context_menu = True
rm.context_menu_file = subfile
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
# 只有在节点展开时才调用tree_pop
if node_open:
imgui.tree_pop()
# 处理拖拽开始
if imgui.is_item_active() and imgui.is_item_hovered():
if imgui.is_mouse_dragging(0):
# 开始拖拽
drag_files = list(rm.selected_files) if rm.selected_files else [file_path]
rm.start_drag(drag_files)
self.is_dragging = True
self.show_drag_overlay = True
# 双击打开文件
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
# 检查是否是支持的3D模型格式
if file_path.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']:
# 导入3D模型
self.add_info_message(f"正在导入模型: {file_path.name}")
self.scene_manager.importModel(str(file_path))
else:
# 使用系统默认程序打开
rm.open_file(file_path)
# 右键菜单
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
rm.show_context_menu = True
rm.context_menu_file = file_path
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
# 右键菜单
if rm.show_context_menu and rm.context_menu_file:
imgui.set_next_window_pos((rm.context_menu_position[0], rm.context_menu_position[1]))
with imgui_ctx.begin_popup("context_menu", imgui.WindowFlags_.no_title_bar |
imgui.WindowFlags_.no_resize | imgui.WindowFlags_.always_auto_resize) as popup:
if popup:
if rm.context_menu_file.is_dir():
if imgui.menu_item("打开"):
rm.navigate_to(rm.context_menu_file)
imgui.separator()
if imgui.menu_item("重命名"):
print(f"重命名文件夹: {rm.context_menu_file.name}")
if imgui.menu_item("删除"):
print(f"删除文件夹: {rm.context_menu_file.name}")
else:
if imgui.menu_item("打开"):
rm.open_file(rm.context_menu_file)
imgui.separator()
if imgui.menu_item("导入到场景"):
if rm.context_menu_file.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']:
self.add_info_message(f"正在导入模型: {rm.context_menu_file.name}")
self.scene_manager.importModel(str(rm.context_menu_file))
if imgui.menu_item("重命名"):
print(f"重命名文件: {rm.context_menu_file.name}")
if imgui.menu_item("删除"):
print(f"删除文件: {rm.context_menu_file.name}")
imgui.separator()
if imgui.menu_item("复制路径"):
imgui.set_clipboard_text(str(rm.context_menu_file))
self.add_info_message("路径已复制到剪贴板")
if imgui.menu_item("在文件管理器中显示"):
import platform
import subprocess
if platform.system() == "Windows":
subprocess.run(["explorer", "/select,", str(rm.context_menu_file)])
elif platform.system() == "Darwin":
subprocess.run(["open", "-R", str(rm.context_menu_file)])
else:
subprocess.run(["xdg-open", str(rm.context_menu_file.parent)])
# 如果点击其他地方,关闭菜单
if imgui.is_mouse_clicked(0) or imgui.is_mouse_clicked(1):
if not imgui.is_window_hovered():
rm.show_context_menu = False
rm.context_menu_file = None
def _draw_property_panel(self):
"""绘制属性面板"""
# 使用面板类型的窗口标志支持docking
flags = self.style_manager.get_window_flags("panel")
with self.style_manager.begin_styled_window("属性面板", self.showPropertyPanel, flags):
self.showPropertyPanel = True # 确保窗口保持打开
# 获取当前选中的节点
selected_node = None
if hasattr(self, 'selection') and self.selection and hasattr(self.selection, 'selectedNode'):
selected_node = self.selection.selectedNode
if selected_node and not selected_node.isEmpty():
self._draw_node_properties(selected_node)
else:
# 无选中对象时显示提示模仿Qt版本的空状态样式
imgui.spacing()
imgui.spacing()
# 居中显示提示信息
window_width = imgui.get_window_width()
text_width = 200 # 估算文本宽度
text_pos_x = (window_width - text_width) / 2
imgui.set_cursor_pos_x(text_pos_x)
imgui.text_colored((0.5, 0.5, 0.5, 1.0), "🔍 未选择任何对象")
imgui.set_cursor_pos_x(text_pos_x - 20)
imgui.text("请从场景树中选择一个对象")
imgui.set_cursor_pos_x(text_pos_x + 10)
imgui.text("以查看其属性")
imgui.spacing()
imgui.spacing()
# 添加一些分隔线和装饰
imgui.separator()
# 显示快速提示
imgui.text("💡 快速提示:")
imgui.bullet_text("单击场景树中的对象进行选择")
imgui.bullet_text("使用 F 键快速聚焦到选中对象")
imgui.bullet_text("使用 Delete 键删除选中对象")
def _draw_node_properties(self, node):
"""绘制节点属性"""
if not node or node.isEmpty():
# 停止变换监控
self.stop_transform_monitoring()
return
# 检查是否需要重新启动变换监控
if self._monitored_node != node:
self.stop_transform_monitoring()
self.start_transform_monitoring(node)
# 获取节点基本信息
node_name = node.getName() or "未命名节点"
node_type = self._get_node_type_from_node(node)
# 添加一些间距模仿Qt版本的布局
imgui.spacing()
# 物体名称组使用Qt版本的样式
if imgui.collapsing_header("物体名称"):
# 第一行:可见性复选框和名称输入
user_visible = node.getPythonTag("user_visible")
if user_visible is None:
user_visible = True
node.setPythonTag("user_visible", True)
# 可见性复选框模仿Qt版本的样式
changed, is_visible = imgui.checkbox("##visibility", user_visible)
if changed:
node.setPythonTag("user_visible", is_visible)
if is_visible:
node.show()
else:
node.hide()
imgui.same_line()
imgui.text("可见")
imgui.same_line()
imgui.spacing()
imgui.same_line()
# 名称输入框模仿Qt版本的样式
imgui.text("名称:")
imgui.same_line()
changed, new_name = imgui.input_text("##name", node_name, 256)
if changed and hasattr(self, 'selection'):
# 更新场景树中的名称
self._update_node_name(node, new_name)
# 添加分隔线
imgui.separator()
# 状态徽章模仿Qt版本的徽章样式
self._draw_status_badges(node)
imgui.spacing()
# 变换属性组
if imgui.collapsing_header("变换 Transform"):
self._draw_transform_properties(node)
# 根据节点类型显示特定属性组
if node_type == "GUI元素":
if imgui.collapsing_header("GUI信息"):
self._draw_gui_properties(node)
elif node_type == "光源":
if imgui.collapsing_header("光源属性"):
self._draw_light_properties(node)
elif node_type == "模型":
if imgui.collapsing_header("模型属性"):
self._draw_model_properties(node)
# 动画控制组(只对模型显示)
if imgui.collapsing_header("动画控制"):
self._draw_animation_properties(node)
# 外观属性组(通用)
if imgui.collapsing_header("外观属性"):
self._draw_appearance_properties(node)
# 碰撞检测组
if imgui.collapsing_header("碰撞检测"):
self._draw_collision_properties(node)
# 操作按钮组
if imgui.collapsing_header("操作"):
self._draw_property_actions(node)
def _getActor(self, origin_model):
"""
获取或创建模型的Actor用于动画控制
复用Qt版本经过验证的实现方式
"""
# 检查缓存
if origin_model in self._actor_cache:
return self._actor_cache[origin_model]
# 尝试直接从内存创建
if origin_model.hasTag("can_create_actor_from_memory"):
try:
test_actor = Actor(origin_model)
anims = test_actor.getAnimNames()
self._actor_cache[origin_model] = test_actor
print(f"[Actor加载] 内存创建检测到动画: {anims}")
if anims:
return test_actor
else:
test_actor.cleanup()
test_actor.removeNode()
except Exception as e:
print(f"从内存模型创建Actor失败: {e}")
# 如果不能直接从内存创建,再尝试通过文件路径加载
filepath = origin_model.getTag("model_path")
if not filepath:
return None
print(f"[Actor加载] 尝试加载: {filepath}")
# 处理跨平台路径问题
import os
# 检查路径是否有效,如果无效则尝试修复
if not os.path.exists(filepath):
original_filepath = filepath
# 尝试多种修复策略
fixed = False
import platform
# 策略1: 处理Linux风格路径在Windows上的问题
if filepath.startswith('/') and platform.system() == "Windows":
print("[路径转换] 尝试处理Linux风格路径:", filepath)
path_parts = filepath.split('/')
print(platform.system())
if len(path_parts) > 1:
drive_letter = path_parts[1].upper() + ':\\' # 添加反斜杠确保正确路径格式
remaining_path = '\\'.join(path_parts[2:]) if len(path_parts) > 2 else ''
potential_path = os.path.join(drive_letter, remaining_path)
print(f"[路径转换] 构造的潜在路径: {potential_path}")
if os.path.exists(potential_path):
filepath = potential_path
fixed = True
print(f"[路径转换] 成功: {original_filepath} -> {filepath}")
else:
print(f"[路径转换] 文件不存在: {potential_path}")
# 策略2: 处理路径分隔符问题
if not fixed:
# 尝试规范化路径
normalized_path = os.path.normpath(filepath)
print(f"[路径规范化] 尝试规范化路径: {filepath} -> {normalized_path}")
if os.path.exists(normalized_path):
filepath = normalized_path
fixed = True
print(f"[路径规范化] 成功: {filepath}")
else:
print(f"[路径规范化] 文件不存在: {normalized_path}")
# 策略3: 在Resources目录中查找
if not fixed:
# 尝试在Resources目录中查找文件
resources_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "Resources")
filename = os.path.basename(filepath)
potential_path = os.path.join(resources_path, filename)
print(f"[Resources查找] 尝试在Resources目录查找: {potential_path}")
if os.path.exists(potential_path):
filepath = potential_path
fixed = True
print(f"[Resources查找] 成功: {filepath}")
else:
print(f"[Resources查找] 文件不存在: {potential_path}")
if fixed:
print(f"路径修复: {original_filepath} -> {filepath}")
# 更新模型标签
origin_model.setTag("model_path", filepath)
else:
print(f"[警告] 模型文件不存在: {filepath}")
return None
# 检查是否是 FBX 文件,如果是,使用专门的 FBX 动画加载器
if filepath.lower().endswith('.fbx'):
pass
#return self._createFBXActor(origin_model, filepath)
# 其他格式使用标准 Actor 加载
try:
import gltf
from panda3d.core import Filename
# 将Panda3D路径转换为操作系统特定路径
panda_filename = Filename(filepath)
os_specific_path = panda_filename.to_os_specific()
print(f"[路径转换] {filepath} -> {os_specific_path}")
print(f"[GLTF加载] 尝试加载: {os_specific_path}")
# 使用明确的设置确保动画被加载
gltf_settings = gltf.GltfSettings(skip_animations=False)
model_root = gltf.load_model(os_specific_path, gltf_settings)
model_node = NodePath(model_root)
test_actor = Actor(model_node)
anims = test_actor.getAnimNames()
test_actor.reparentTo(self.render)
self._actor_cache[origin_model] = test_actor
print(f"[Actor加载] 标准加载检测到动画: {anims}")
if not anims:
test_actor.cleanup()
test_actor.removeNode()
return None
return test_actor
except Exception as e:
print(f"创建Actor失败: {e}")
return None
def _getModelFormat(self, origin_model):
"""获取模型格式信息"""
filepath = origin_model.getTag("model_path")
original_path = origin_model.getTag("original_path")
converted_from = origin_model.getTag("converted_from")
if filepath:
ext = filepath.lower().split('.')[-1]
format_name = ext.upper()
# 如果是转换后的文件,显示转换信息
if converted_from and original_path:
original_ext = converted_from.upper()
format_name = f"{format_name} (从{original_ext}转换)"
return format_name
return "未知"
def _processAnimationNames(self, origin_model, anim_names):
"""处理和分析动画名称,返回 [(显示名称, 原始名称), ...]"""
format_info = self._getModelFormat(origin_model)
processed = []
print(f"[动画分析] 格式: {format_info}, 原始动画名称: {anim_names}")
for name in anim_names:
display_name = name
original_name = name
if format_info == "GLB":
# GLB 格式通常有真实的动画名称
if "|" in name:
# 处理类似 'Armature|mixamo.com|Layer0' 的名称
parts = name.split("|")
if "mixamo" in name.lower():
# Mixamo 动画
display_name = f"Mixamo_{parts[-1]}" if len(parts) > 1 else name
elif len(parts) > 2:
# 其他复杂命名
display_name = f"{parts[0]}_{parts[-1]}"
else:
display_name = parts[-1]
elif format_info == "FBX":
# FBX 格式可能需要特殊处理
if self._isLikelyBoneGroup(name):
# 检查是否是骨骼组而非动画
print(f"[警告] '{name}' 可能不是真正的动画序列,而是骨骼组")
display_name = f"⚠️ {name} (可能非动画)"
else:
display_name = name
elif format_info in ["EGG", "BAM"]:
# 原生格式通常命名规范
display_name = name
processed.append((display_name, original_name))
print(f"[动画分析] {original_name}{display_name}")
return processed
def _isLikelyBoneGroup(self, name):
"""判断动画名称是否更像骨骼组而不是动画序列"""
bone_indicators = ['joints', 'bones', 'skeleton', 'surface', 'mesh', 'beta', 'rig']
name_lower = name.lower()
# 如果包含这些关键词,可能是骨骼组
for indicator in bone_indicators:
if indicator in name_lower:
return True
# 如果名称太简单少于3个字符可能不是动画
if len(name) < 3:
return True
return False
def _analyzeAnimationQuality(self, actor, anim_names, format_info):
"""分析动画质量和类型(优化版本,减少详细分析以提高性能)"""
try:
valid_anims = 0
# 简化分析:只检查动画是否存在,不详细分析帧数
for anim_name in anim_names:
try:
control = actor.getAnimControl(anim_name)
if control and control.getNumFrames() > 1:
valid_anims += 1
except Exception:
# 忽略单个动画的分析错误,继续处理其他动画
continue
if valid_anims == 0:
return "⚠️ 无有效动画"
elif valid_anims < len(anim_names):
return f"⚠️ {valid_anims}/{len(anim_names)} 个有效"
else:
return f"{valid_anims} 个动画"
except Exception as e:
# 简化错误处理
return "分析异常"
def _get_node_type_from_node(self, node):
"""从节点判断其类型"""
# 检查是否为GUI元素
if hasattr(node, 'getPythonTag') and node.getPythonTag('gui_element'):
return "GUI元素"
# 检查是否为光源
node_name = node.getName() or ""
if "light" in node_name.lower() or "Light" in node_name:
return "光源"
# 检查是否为相机
if "camera" in node_name.lower() or "Camera" in node_name:
return "相机"
# 检查是否为模型
if hasattr(self, 'scene_manager') and self.scene_manager:
if hasattr(self.scene_manager, 'models') and node in self.scene_manager.models:
return "模型"
# 默认为几何体
return "几何体"
def _draw_status_badges(self, node):
"""绘制状态徽章模仿Qt版本的徽章样式"""
imgui.text("状态标签: ")
# 可见性状态徽章
is_visible = not node.is_hidden()
visibility_color = (0.176, 1.0, 0.769, 1.0) if is_visible else (0.953, 0.616, 0.471, 1.0)
visibility_text = "可见" if is_visible else "隐藏"
imgui.same_line()
imgui.text_colored(visibility_color, f"[{visibility_text}]")
# 节点类型徽章
node_type = self._get_node_type_from_node(node)
type_colors = {
"GUI元素": (0.188, 0.404, 0.753, 1.0), # 主题蓝色
"光源": (1.0, 0.8, 0.2, 1.0), # 黄色
"模型": (0.6, 0.8, 1.0, 1.0), # 浅蓝色
"相机": (0.8, 0.8, 0.2, 1.0), # 橙色
"几何体": (0.5, 0.5, 0.5, 1.0), # 灰色
}
if node_type in type_colors:
imgui.same_line()
imgui.text_colored(type_colors[node_type], f"[{node_type}]")
# 功能性徽章
badges = []
# 碰撞体徽章
has_collision = hasattr(node, 'getChild') and any('Collision' in child.getName() for child in node.getChildren() if child.getName())
if has_collision:
badges.append(("碰撞", (0.2, 0.4, 0.8, 1.0))) # 蓝色
# 脚本徽章
has_script = hasattr(node, 'getPythonTag') and node.getPythonTag('script')
if has_script:
badges.append(("脚本", (0.8, 0.4, 0.8, 1.0))) # 紫色
# 动画徽章优化检测逻辑避免重复创建Actor
has_animation = False
if node_type == "模型": # 只对模型类型进行动画检测
# 首先检查是否已经缓存了检测结果
cached_result = node.getPythonTag('animation')
if cached_result is not None:
has_animation = cached_result
else:
# 只有在未缓存时才进行检测
try:
# 使用轻量级检测:先检查文件扩展名
model_path = node.getTag("model_path")
if model_path and model_path.lower().endswith(('.glb', '.gltf', '.fbx')):
# 对于可能包含动画的格式才进行Actor检测
actor = self._getActor(node)
if actor and actor.getAnimNames():
has_animation = True
# 缓存检测结果
node.setPythonTag('animation', has_animation)
print(f"[动画检测] {node.getName()}: {'有动画' if has_animation else '无动画'}")
else:
# 对于不太可能有动画的格式,直接标记为无动画
node.setPythonTag('animation', False)
except Exception as e:
print(f"动画检测失败: {e}")
node.setPythonTag('animation', False)
else:
# 对于非模型类型,检查已有的动画标签
has_animation = hasattr(node, 'getPythonTag') and node.getPythonTag('animation')
if has_animation:
badges.append(("动画", (0.4, 0.8, 0.4, 1.0))) # 绿色
# 材质徽章
has_material = hasattr(node, 'getMaterial') and node.getMaterial()
if has_material:
badges.append(("材质", (0.8, 0.6, 0.2, 1.0))) # 金色
# 绘制功能性徽章
for badge_text, badge_color in badges:
imgui.same_line()
imgui.text_colored(badge_color, f"[{badge_text}]")
# 如果没有特殊徽章,显示默认状态
if not badges:
imgui.same_line()
imgui.text_colored((0.5, 0.5, 0.5, 1.0), "[标准对象]")
def _draw_transform_properties(self, node):
"""绘制变换属性"""
# 位置组
if imgui.collapsing_header("位置 Position"):
# 相对位置
imgui.text("相对位置")
pos = node.getPos()
# X坐标
changed, new_x = imgui.input_float("X##pos_x", pos.x, 0.1, 1.0, "%.3f")
if changed: node.setX(new_x)
# Y坐标
changed, new_y = imgui.input_float("Y##pos_y", pos.y, 0.1, 1.0, "%.3f")
if changed: node.setY(new_y)
# Z坐标
changed, new_z = imgui.input_float("Z##pos_z", pos.z, 0.1, 1.0, "%.3f")
if changed: node.setZ(new_z)
# 世界位置
imgui.text("世界位置")
world_pos = node.getPos(self.render)
imgui.text(f"世界 X: {world_pos.x:.3f}")
imgui.text(f"世界 Y: {world_pos.y:.3f}")
imgui.text(f"世界 Z: {world_pos.z:.3f}")
# 位置操作按钮
if imgui.button("重置位置##reset_pos"):
node.setPos(0, 0, 0)
imgui.same_line()
if imgui.button("复制位置##copy_pos"):
self._clipboard_pos = (pos.x, pos.y, pos.z)
imgui.same_line()
if imgui.button("粘贴位置##paste_pos") and hasattr(self, '_clipboard_pos'):
node.setPos(self._clipboard_pos[0], self._clipboard_pos[1], self._clipboard_pos[2])
# 旋转组
if imgui.collapsing_header("旋转 Rotation"):
hpr = node.getHpr()
# HPR旋转
imgui.text("HPR 旋转 (度)")
changed, new_h = imgui.input_float("H##rot_h", hpr.x, 1.0, 10.0, "%.1f")
if changed: node.setH(new_h)
changed, new_p = imgui.input_float("P##rot_p", hpr.y, 1.0, 10.0, "%.1f")
if changed: node.setP(new_p)
changed, new_r = imgui.input_float("R##rot_r", hpr.z, 1.0, 10.0, "%.1f")
if changed: node.setR(new_r)
# 旋转操作按钮
if imgui.button("重置旋转##reset_rot"):
node.setHpr(0, 0, 0)
imgui.same_line()
if imgui.button("随机旋转##random_rot"):
import random
node.setHpr(random.randint(0, 360), random.randint(0, 360), random.randint(0, 360))
# 缩放组
if imgui.collapsing_header("缩放 Scale"):
scale = node.getScale()
# XYZ缩放
imgui.text("XYZ 缩放")
changed, new_sx = imgui.input_float("X##scale_x", scale.x, 0.1, 1.0, "%.3f")
if changed: node.setSx(new_sx)
changed, new_sy = imgui.input_float("Y##scale_y", scale.y, 0.1, 1.0, "%.3f")
if changed: node.setSy(new_sy)
changed, new_sz = imgui.input_float("Z##scale_z", scale.z, 0.1, 1.0, "%.3f")
if changed: node.setSz(new_sz)
# 统一缩放
if imgui.button("统一缩放##uniform_scale"):
uniform_scale = (scale.x + scale.y + scale.z) / 3.0
node.setScale(uniform_scale, uniform_scale, uniform_scale)
imgui.same_line()
if imgui.button("重置缩放##reset_scale"):
node.setScale(1, 1, 1)
imgui.same_line()
if imgui.button("翻倍##double_scale"):
node.setScale(scale.x * 2, scale.y * 2, scale.z * 2)
def _draw_gui_properties(self, node):
"""绘制GUI元素属性"""
# 获取GUI元素
gui_element = None
if hasattr(node, 'getPythonTag'):
gui_element = node.getPythonTag('gui_element')
if not gui_element:
imgui.text("无GUI元素数据")
return
# GUI类型信息
gui_type = getattr(gui_element, 'gui_type', 'UNKNOWN')
imgui.text(f"GUI类型: {gui_type}")
# 基本属性
if imgui.collapsing_header("基本属性"):
# 文本内容 (适用于按钮、标签等)
if hasattr(gui_element, 'text'):
changed, new_text = imgui.input_text("文本内容", gui_element.text, 256)
if changed and hasattr(self, 'gui_manager'):
self.gui_manager.editGUIElement(gui_element, 'text', new_text)
# GUI ID
gui_id = getattr(gui_element, 'id', '')
changed, new_id = imgui.input_text("GUI ID", gui_id, 64)
if changed and hasattr(self, 'gui_manager'):
gui_element.id = new_id
# 变换属性
if imgui.collapsing_header("变换属性"):
# 位置
pos = gui_element.getPos()
imgui.text("位置")
if gui_type in ["button", "label", "entry", "2d_image"]:
# 2D GUI组件使用屏幕坐标
imgui.text("屏幕坐标")
logical_x = pos.getX() / 0.1
logical_z = pos.getZ() / 0.1
changed, new_x = imgui.input_float("X##gui_pos_x", logical_x, 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setX(new_x * 0.1)
changed, new_z = imgui.input_float("Y##gui_pos_y", logical_z, 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setZ(new_z * 0.1)
else:
# 3D GUI组件使用世界坐标
imgui.text("世界坐标")
changed, new_x = imgui.input_float("X##gui_world_x", pos.getX(), 0.1, 1.0, "%.3f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setX(new_x)
changed, new_y = imgui.input_float("Y##gui_world_y", pos.getY(), 0.1, 1.0, "%.3f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setY(new_y)
changed, new_z = imgui.input_float("Z##gui_world_z", pos.getZ(), 0.1, 1.0, "%.3f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setZ(new_z)
# 缩放
scale = gui_element.getScale()
imgui.text("缩放")
changed, new_sx = imgui.input_float("X##gui_scale_x", scale.getX(), 0.1, 1.0, "%.3f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setSx(new_sx)
changed, new_sy = imgui.input_float("Y##gui_scale_y", scale.getY(), 0.1, 1.0, "%.3f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setSy(new_sy)
changed, new_sz = imgui.input_float("Z##gui_scale_z", scale.getZ(), 0.1, 1.0, "%.3f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setSz(new_sz)
# 旋转
hpr = gui_element.getHpr()
imgui.text("旋转")
changed, new_h = imgui.input_float("H##gui_rot_h", hpr.getX(), 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setH(new_h)
changed, new_p = imgui.input_float("P##gui_rot_p", hpr.getY(), 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setP(new_p)
changed, new_r = imgui.input_float("R##gui_rot_r", hpr.getZ(), 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
gui_element.setR(new_r)
# 外观属性
if imgui.collapsing_header("外观属性"):
# 大小
if hasattr(gui_element, 'size'):
size = gui_element.size
imgui.text("大小")
changed, new_w = imgui.input_float("宽度", size[0], 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
new_size = (new_w, size[1])
self.gui_manager.editGUIElement(gui_element, 'size', new_size)
changed, new_h = imgui.input_float("高度", size[1], 1.0, 10.0, "%.1f")
if changed and hasattr(self, 'gui_manager'):
new_size = (size[0], new_h)
self.gui_manager.editGUIElement(gui_element, 'size', new_size)
# 颜色
if hasattr(gui_element, 'getColor'):
try:
color = gui_element.getColor()
# 确保颜色是有效的
if not color or (hasattr(color, '__len__') and len(color) < 3):
color = (1.0, 1.0, 1.0, 1.0) # 默认白色
except:
color = (1.0, 1.0, 1.0, 1.0) # 默认白色
imgui.text("颜色")
# 获取颜色值
if hasattr(color, 'getX'):
# 如果是Panda3D的Vec4对象
r, g, b = color.getX(), color.getY(), color.getZ()
else:
# 如果是元组或其他格式
r, g, b = color[0], color[1], color[2]
changed, new_r = imgui.slider_float("R##gui_color_r", r, 0.0, 1.0)
if changed: gui_element.setColor(new_r, g, b, 1.0)
changed, new_g = imgui.slider_float("G##gui_color_g", g, 0.0, 1.0)
if changed: gui_element.setColor(r, new_g, b, 1.0)
changed, new_b = imgui.slider_float("B##gui_color_b", b, 0.0, 1.0)
if changed: gui_element.setColor(r, g, new_b, 1.0)
# 透明度
imgui.text("透明度")
current_alpha = getattr(gui_element, 'alpha', 1.0)
changed, new_alpha = imgui.slider_float("Alpha", current_alpha, 0.0, 1.0)
if changed:
gui_element.alpha = new_alpha
if hasattr(gui_element, 'setTransparency'):
# 将0.0-1.0范围转换为Panda3D的透明度格式
panda_transparency = int((1.0 - new_alpha) * 255)
gui_element.setTransparency(panda_transparency)
# 渲染顺序
imgui.text("渲染顺序")
current_sort = getattr(gui_element, 'sort', 0)
changed, new_sort = imgui.input_int("Sort Order", current_sort)
if changed:
gui_element.sort = new_sort
if hasattr(gui_element, 'setBin'):
gui_element.setBin('fixed', new_sort)
# 字体设置适用于文本类型的GUI元素
if gui_type in ["button", "label", "entry"]:
imgui.text("字体设置")
# 字体选择
current_font = getattr(gui_element, 'font_path', '')
if imgui.button(f"字体: {Path(current_font).name if current_font else '默认'}##font_select"):
self.show_font_selector(
gui_element,
'font_path',
current_font,
lambda font_path: self._apply_gui_font(gui_element, font_path)
)
# 字体大小
current_size = getattr(gui_element, 'font_size', 12)
changed, new_size = imgui.slider_float("字体大小", current_size, 8.0, 72.0)
if changed:
gui_element.font_size = new_size
self._apply_gui_font_size(gui_element, new_size)
# 字体样式
imgui.text("字体样式")
is_bold = getattr(gui_element, 'font_bold', False)
changed, new_bold = imgui.checkbox("粗体", is_bold)
if changed:
gui_element.font_bold = new_bold
self._apply_gui_font_style(gui_element)
imgui.same_line()
is_italic = getattr(gui_element, 'font_italic', False)
changed, new_italic = imgui.checkbox("斜体", is_italic)
if changed:
gui_element.font_italic = new_italic
self._apply_gui_font_style(gui_element)
def _apply_gui_font(self, gui_element, font_path):
"""应用GUI元素的字体"""
try:
if hasattr(gui_element, 'setFont') and font_path:
gui_element.setFont(font_path)
gui_element.font_path = font_path
except Exception as e:
print(f"应用GUI字体失败: {e}")
def _apply_gui_font_size(self, gui_element, font_size):
"""应用GUI元素的字体大小"""
try:
if hasattr(gui_element, 'setFontSize'):
gui_element.setFontSize(font_size)
gui_element.font_size = font_size
except Exception as e:
print(f"应用GUI字体大小失败: {e}")
def _apply_gui_font_style(self, gui_element):
"""应用GUI元素的字体样式"""
try:
if hasattr(gui_element, 'setFontStyle'):
style = 0
if getattr(gui_element, 'font_bold', False):
style |= 1 # 粗体
if getattr(gui_element, 'font_italic', False):
style |= 2 # 斜体
gui_element.setFontStyle(style)
except Exception as e:
print(f"应用GUI字体样式失败: {e}")
# 特定类型的属性
if gui_type == "button":
if imgui.collapsing_header("按钮属性"):
# 按钮状态
is_pressed = getattr(gui_element, 'pressed', False)
changed, new_pressed = imgui.checkbox("按下状态", is_pressed)
if changed:
gui_element.pressed = new_pressed
# 按钮回调
callback_name = getattr(gui_element, 'callback_name', '')
changed, new_callback = imgui.input_text("回调函数", callback_name, 64)
if changed:
gui_element.callback_name = new_callback
elif gui_type == "entry":
if imgui.collapsing_header("输入框属性"):
# 输入框内容
entry_text = getattr(gui_element, 'entry_text', '')
changed, new_text = imgui.input_text("输入内容", entry_text, 256)
if changed:
gui_element.entry_text = new_text
if hasattr(gui_element, 'set'):
gui_element.set(new_text)
# 最大长度
max_length = getattr(gui_element, 'max_length', 256)
changed, new_max = imgui.input_int("最大长度", max_length)
if changed:
gui_element.max_length = max(max_length, 1)
# 密码模式
is_password = getattr(gui_element, 'is_password', False)
changed, new_password = imgui.checkbox("密码模式", is_password)
if changed:
gui_element.is_password = new_password
if hasattr(gui_element, 'obscure'):
gui_element.obscure(new_password)
elif gui_type in ["2d_image", "3d_image"]:
if imgui.collapsing_header("图像属性"):
# 图像路径
image_path = getattr(gui_element, 'image_path', '')
changed, new_path = imgui.input_text("图像路径", image_path, 256)
if changed and hasattr(self, 'gui_manager'):
gui_element.image_path = new_path
# TODO: 重新加载图像
# 图像缩放模式
scale_mode = getattr(gui_element, 'scale_mode', 'stretch')
if imgui.begin_combo("缩放模式", scale_mode):
if imgui.selectable("拉伸##stretch"):
gui_element.scale_mode = 'stretch'
if imgui.selectable("适应##fit"):
gui_element.scale_mode = 'fit'
if imgui.selectable("填充##fill"):
gui_element.scale_mode = 'fill'
imgui.end_combo()
def _draw_light_properties(self, node):
"""绘制光源属性"""
imgui.text("光源属性")
# 光源颜色
if hasattr(node, 'getColor'):
try:
color = node.getColor()
# 确保颜色是有效的
if not color or len(color) < 3:
color = (1.0, 1.0, 1.0, 1.0) # 默认白色
except:
color = (1.0, 1.0, 1.0, 1.0) # 默认白色
changed, new_r = imgui.drag_float("颜色 R", color[0], 0.01, 0.0, 1.0)
if changed: node.setColor(new_r, color[1], color[2], color[3] if len(color) > 3 else 1.0)
changed, new_g = imgui.drag_float("颜色 G", color[1], 0.01, 0.0, 1.0)
if changed: node.setColor(color[0], new_g, color[2], color[3] if len(color) > 3 else 1.0)
changed, new_b = imgui.drag_float("颜色 B", color[2], 0.01, 0.0, 1.0)
if changed: node.setColor(color[0], color[1], new_b, color[3] if len(color) > 3 else 1.0)
# 光源强度
imgui.text("光源强度: (暂不支持编辑)")
def _draw_model_properties(self, node):
"""绘制模型属性"""
# 获取模型信息
model_path = node.getTag("model_path") if node.hasTag("model_path") else "未知"
imgui.text("模型路径:")
imgui.same_line()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), model_path)
# 模型基本信息
imgui.text("模型名称:")
imgui.same_line()
model_name = node.getName() or "未命名模型"
imgui.text_colored((0.7, 0.7, 0.7, 1.0), model_name)
# 模型位置信息
imgui.text("位置:")
imgui.same_line()
pos = node.getPos()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"X:{pos.x:.2f} Y:{pos.y:.2f} Z:{pos.z:.2f}")
# 模型缩放信息
imgui.text("缩放:")
imgui.same_line()
scale = node.getScale()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"X:{scale.x:.2f} Y:{scale.y:.2f} Z:{scale.z:.2f}")
# 模型旋转信息
imgui.text("旋转:")
imgui.same_line()
hpr = node.getHpr()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"H:{hpr.x:.1f}° P:{hpr.y:.1f}° R:{hpr.z:.1f}°")
def _draw_animation_properties(self, node):
"""绘制动画控制属性面板(优化版本,使用缓存避免重复计算)"""
# 检查是否已经缓存了动画信息
cached_anim_info = node.getPythonTag("cached_anim_info")
cached_processed_names = node.getPythonTag("cached_processed_names")
# 只有在没有缓存时才进行完整的动画检测和处理
if cached_anim_info is None or cached_processed_names is None:
# 获取Actor
actor = self._getActor(node)
if not actor:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "此模型不包含动画")
return
# 获取和分析动画名称
anim_names = actor.getAnimNames()
processed_names = self._processAnimationNames(node, anim_names)
if not processed_names:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列")
# 缓存空结果
node.setPythonTag("cached_processed_names", [])
node.setPythonTag("cached_anim_info", "无动画")
return
# 计算并缓存动画信息
format_info = self._getModelFormat(node)
animation_info = self._analyzeAnimationQuality(actor, anim_names, format_info)
info_text = f"格式: {format_info} | 动画数量: {len(processed_names)}"
if animation_info:
info_text += f" | {animation_info}"
# 缓存结果
node.setPythonTag("cached_anim_info", info_text)
node.setPythonTag("cached_processed_names", processed_names)
else:
# 使用缓存的数据
info_text = cached_anim_info
processed_names = cached_processed_names
# 如果缓存的空结果,直接返回
if not processed_names:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列")
return
# 显示动画信息(使用缓存的数据)
imgui.text("信息:")
imgui.same_line()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), info_text)
imgui.spacing()
# 动画选择下拉框
imgui.text("动画名称:")
imgui.same_line()
# 获取当前选中的动画
current_anim = node.getPythonTag("selected_animation")
if current_anim is None:
current_anim = processed_names[0][1] if processed_names else ""
node.setPythonTag("selected_animation", current_anim)
# 查找当前动画的索引
current_index = 0
for i, (display_name, original_name) in enumerate(processed_names):
if original_name == current_anim:
current_index = i
break
# 创建下拉框选项
animation_options = [display_name for display_name, _ in processed_names]
changed, new_index = imgui.combo("##animation_combo", current_index, animation_options)
if changed and new_index < len(processed_names):
selected_display, selected_original = processed_names[new_index]
node.setPythonTag("selected_animation", selected_original)
print(f"选择动画: {selected_display} (原始名称: {selected_original})")
imgui.spacing()
# 控制按钮组
imgui.text("控制:")
# 播放按钮
if imgui.button("播放##play_animation"):
self._playAnimation(node)
imgui.same_line()
# 暂停按钮
if imgui.button("暂停##pause_animation"):
self._pauseAnimation(node)
imgui.same_line()
# 停止按钮
if imgui.button("停止##stop_animation"):
self._stopAnimation(node)
imgui.same_line()
# 循环按钮
if imgui.button("循环##loop_animation"):
self._loopAnimation(node)
imgui.spacing()
# 播放速度控制
imgui.text("播放速度:")
imgui.same_line()
# 获取当前速度
current_speed = node.getPythonTag("anim_speed")
if current_speed is None:
current_speed = 1.0
node.setPythonTag("anim_speed", current_speed)
# 速度滑块
changed, new_speed = imgui.slider_float("##anim_speed", current_speed, 0.1, 5.0, "%.1f")
if changed:
node.setPythonTag("anim_speed", new_speed)
self._setAnimationSpeed(node, new_speed)
imgui.same_line()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "倍速")
def _playAnimation(self, origin_model):
"""播放动画"""
actor = self._getActor(origin_model)
if not actor:
return
# 保存原始世界坐标
original_world_pos = origin_model.getPos(self.render)
original_world_hpr = origin_model.getHpr(self.render)
original_world_scale = origin_model.getScale(self.render)
# 设置Actor位置和姿态
actor.setPos(origin_model.getPos())
actor.setHpr(origin_model.getHpr())
actor.setScale(origin_model.getScale())
# 隐藏原始模型显示Actor
origin_model.hide()
actor.show()
# 创建任务来维持世界坐标不变
def maintainWorldPosition(task):
try:
if not actor.isEmpty():
actor.setPos(self.render, original_world_pos)
actor.setHpr(self.render, original_world_hpr)
actor.setScale(self.render, original_world_scale)
return task.cont
else:
return task.done
except:
return task.done
# 添加维持位置的任务
taskMgr.add(maintainWorldPosition, f"maintain_anim_pos_{id(actor)}")
# 获取当前选中的动画
current_anim = origin_model.getPythonTag("selected_animation")
if current_anim:
actor.play(current_anim)
print(f"『动画播放』:{current_anim}")
else:
# 兜底:使用第一个可用动画
anim_names = actor.getAnimNames()
if anim_names:
actor.play(anim_names[0])
print(f"『动画播放』:{anim_names[0]}")
def _pauseAnimation(self, origin_model):
"""暂停动画"""
actor = self._getActor(origin_model)
if not actor:
return
# 设置Actor位置和姿态
actor.setPos(origin_model.getPos())
actor.setHpr(origin_model.getHpr())
actor.setScale(origin_model.getScale())
# 隐藏原始模型显示Actor
origin_model.hide()
actor.show()
# 停止动画(保持当前姿势)
actor.stop()
print("『动画』暂停")
def _stopAnimation(self, origin_model):
"""停止动画"""
actor = self._getActor(origin_model)
if not actor:
return
# 停止动画
actor.stop()
# 获取当前选中的动画
current_anim = origin_model.getPythonTag("selected_animation")
if current_anim and actor.getAnimControl(current_anim):
actor.getAnimControl(current_anim).pose(0)
# 隐藏Actor显示原始模型
actor.hide()
origin_model.show()
# 移除维持位置的任务
taskMgr.remove(f"maintain_anim_pos_{id(actor)}")
print("『动画』停止切换至原始模型")
def _loopAnimation(self, origin_model):
"""循环播放动画"""
actor = self._getActor(origin_model)
if not actor:
return
# 保存原始世界坐标
original_world_pos = origin_model.getPos(self.render)
original_world_hpr = origin_model.getHpr(self.render)
original_world_scale = origin_model.getScale(self.render)
# 设置Actor位置和姿态
actor.setPos(origin_model.getPos())
actor.setHpr(origin_model.getHpr())
actor.setScale(origin_model.getScale())
# 隐藏原始模型显示Actor
origin_model.hide()
actor.show()
# 创建任务来维持世界坐标不变
def maintainWorldPosition(task):
try:
if not actor.isEmpty():
actor.setPos(self.render, original_world_pos)
actor.setHpr(self.render, original_world_hpr)
actor.setScale(self.render, original_world_scale)
return task.cont
else:
return task.done
except:
return task.done
# 添加维持位置的任务
taskMgr.add(maintainWorldPosition, f"maintain_anim_pos_{id(actor)}")
# 获取当前选中的动画
current_anim = origin_model.getPythonTag("selected_animation")
if current_anim:
actor.loop(current_anim)
print(f"[动画] 循环: {current_anim}")
else:
# 兜底:使用第一个可用动画
anim_names = actor.getAnimNames()
if anim_names:
actor.loop(anim_names[0])
print(f"[动画] 循环: {anim_names[0]}")
def _setAnimationSpeed(self, origin_model, speed):
"""设置动画播放速度"""
actor = self._getActor(origin_model)
if not actor:
return
# 获取当前选中的动画
current_anim = origin_model.getPythonTag("selected_animation")
if current_anim:
actor.setPlayRate(speed, current_anim)
print(f"[动画] 速度设为: {speed} ({current_anim})")
else:
# 兜底:尝试所有动画
anim_names = actor.getAnimNames()
for anim_name in anim_names:
actor.setPlayRate(speed, anim_name)
print(f"[动画] 速度设为: {speed} (所有动画)")
def _clear_animation_cache(self, node):
"""清除节点的动画缓存,当模型发生变化时调用"""
node.setPythonTag("cached_anim_info", None)
node.setPythonTag("cached_processed_names", None)
node.setPythonTag("animation", None) # 同时清除动画检测结果
# 如果Actor在缓存中也需要清理
if node in self._actor_cache:
actor = self._actor_cache[node]
try:
# 清理相关任务
taskMgr.remove(f"maintain_anim_pos_{id(actor)}")
# 清理Actor
if not actor.isEmpty():
actor.cleanup()
actor.removeNode()
except Exception as e:
print(f"清理Actor缓存失败: {e}")
finally:
del self._actor_cache[node]
print(f"[缓存清理] 清除节点 {node.getName()} 的动画缓存")
def _draw_collision_properties(self, node):
"""绘制碰撞检测属性"""
if not node or node.isEmpty():
return
try:
# 检查节点是否已有碰撞
has_collision = self._has_collision(node)
# 碰撞状态徽章
imgui.text("状态:")
imgui.same_line()
if has_collision:
imgui.text_colored((0.0, 0.8, 0.0, 1.0), "🟢 已启用")
else:
imgui.text_colored((0.8, 0.0, 0.0, 1.0), "🔴 未启用")
imgui.separator()
# 碰撞形状选择
imgui.text("碰撞形状:")
imgui.same_line()
# 碰撞形状选项
collision_shapes = ["球形 (Sphere)", "盒型 (Box)", "胶囊体 (Capsule)", "平面 (Plane)", "自动选择 (Auto)"]
# 获取当前形状
current_shape = self._get_current_collision_shape(node) if has_collision else "球形 (Sphere)"
# 形状选择下拉框
current_index = collision_shapes.index(current_shape) if current_shape in collision_shapes else 0
changed, selected_index = imgui.combo("##collision_shape", current_index, collision_shapes)
if changed:
# 始终更新选择的形状
selected_shape = collision_shapes[selected_index]
self._selected_collision_shape = selected_shape
# 如果已经有碰撞体,询问用户是否要重新创建
if has_collision:
print(f"形状已更改为 {selected_shape},点击'移除碰撞''添加碰撞'来应用新形状")
imgui.separator()
# 位置偏移控件
imgui.text("位置偏移:")
# 获取当前位置偏移
pos_offset = self._get_collision_position_offset(node)
# X位置
changed, new_x = imgui.drag_float("X##collision_pos_x", pos_offset[0], 0.1, -100.0, 100.0, "%.2f")
if changed and has_collision:
self._update_collision_position(node, 'x', new_x)
# Y位置
changed, new_y = imgui.drag_float("Y##collision_pos_y", pos_offset[1], 0.1, -100.0, 100.0, "%.2f")
if changed and has_collision:
self._update_collision_position(node, 'y', new_y)
# Z位置
changed, new_z = imgui.drag_float("Z##collision_pos_z", pos_offset[2], 0.1, -100.0, 100.0, "%.2f")
if changed and has_collision:
self._update_collision_position(node, 'z', new_z)
# 形状特定参数(始终显示,但根据状态启用/禁用)
shape_type = self._get_current_collision_shape_type(node)
self._draw_shape_specific_parameters(node, shape_type, has_collision)
imgui.separator()
# 操作按钮
if has_collision:
# 显示/隐藏碰撞体按钮
is_visible = self._is_collision_visible(node)
visibility_text = "隐藏碰撞体" if is_visible else "显示碰撞体"
if imgui.button(visibility_text):
self._toggle_collision_visibility(node)
imgui.same_line()
# 移除碰撞按钮
if imgui.button("移除碰撞"):
self._remove_collision_from_node(node)
else:
# 添加碰撞按钮
if imgui.button("添加碰撞"):
self._add_collision_to_node(node)
imgui.separator()
# 碰撞检测触发模式
imgui.text("触发模式:")
# 自动检测开关
auto_enabled = self.collision_manager.model_collision_enabled if hasattr(self, 'collision_manager') else False
changed, new_auto = imgui.checkbox("自动检测", auto_enabled)
if changed and hasattr(self, 'collision_manager'):
self.collision_manager.enableModelCollisionDetection(new_auto, 0.1, 0.5)
imgui.same_line()
# 手动检测按钮
if imgui.button("立即检测"):
if hasattr(self, 'collision_manager'):
self._manual_collision_detection()
except Exception as e:
print(f"绘制碰撞属性失败: {e}")
import traceback
traceback.print_exc()
def _has_collision(self, node):
"""检查节点是否有碰撞体"""
try:
if not node or node.isEmpty():
return False
# 检查是否有碰撞节点
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
return True
return False
except Exception as e:
print(f"检查碰撞状态失败: {e}")
return False
def _get_current_collision_shape(self, node):
"""获取当前碰撞形状"""
try:
if not self._has_collision(node):
return "球形 (Sphere)"
# 查找碰撞节点并判断形状
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
# 根据碰撞节点名称判断形状
if 'sphere' in name.lower():
return "球形 (Sphere)"
elif 'box' in name.lower():
return "盒型 (Box)"
elif 'capsule' in name.lower():
return "胶囊体 (Capsule)"
elif 'plane' in name.lower():
return "平面 (Plane)"
return "球形 (Sphere)" # 默认
except Exception as e:
print(f"获取碰撞形状失败: {e}")
return "球形 (Sphere)"
def _get_current_collision_shape_type(self, node):
"""获取当前碰撞形状类型(内部标识)"""
try:
shape_name = self._get_current_collision_shape(node)
if "Sphere" in shape_name:
return "sphere"
elif "Box" in shape_name:
return "box"
elif "Capsule" in shape_name:
return "capsule"
elif "Plane" in shape_name:
return "plane"
else:
return "sphere"
except Exception as e:
print(f"获取碰撞形状类型失败: {e}")
return "sphere"
def _get_collision_position_offset(self, node):
"""获取碰撞体位置偏移"""
try:
if not self._has_collision(node):
return (0.0, 0.0, 0.0)
# 查找碰撞节点并获取位置
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
pos = child.getPos()
return (pos.x, pos.y, pos.z)
return (0.0, 0.0, 0.0)
except Exception as e:
print(f"获取碰撞位置失败: {e}")
return (0.0, 0.0, 0.0)
def _is_collision_visible(self, node):
"""检查碰撞体是否可见"""
try:
if not self._has_collision(node):
return False
# 查找碰撞节点并检查可见性
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
return child.isHidden() == False
return False
except Exception as e:
print(f"检查碰撞可见性失败: {e}")
return False
def _add_collision_to_node(self, node):
"""为节点添加碰撞体"""
try:
if not node or node.isEmpty():
print("无效的节点")
return
if self._has_collision(node):
print("节点已有碰撞体")
return
# 获取选择的形状
shape_name = getattr(self, '_selected_collision_shape', '球形 (Sphere)')
if hasattr(self, 'collision_manager'):
# 使用碰撞管理器添加碰撞体
shape_type = self._get_shape_type_from_name(shape_name)
collision_node = self.collision_manager.setupAdvancedCollision(
node,
shape_type=shape_type,
mask_type='MODEL_COLLISION'
)
if collision_node:
print(f"成功为节点 {node.getName()} 添加 {shape_name} 碰撞体")
else:
print(f"添加碰撞体失败")
else:
print("碰撞管理器未初始化")
except Exception as e:
print(f"添加碰撞体失败: {e}")
import traceback
traceback.print_exc()
def _remove_collision_from_node(self, node):
"""从节点移除碰撞体"""
try:
if not node or node.isEmpty():
print("无效的节点")
return
if not self._has_collision(node):
print("节点没有碰撞体")
return
# 查找并移除碰撞节点
children_to_remove = []
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
children_to_remove.append(child)
# 移除找到的碰撞节点
for child in children_to_remove:
child.removeNode()
if children_to_remove:
print(f"成功移除节点 {node.getName()} 的碰撞体")
else:
print(f"未找到碰撞体")
except Exception as e:
print(f"移除碰撞体失败: {e}")
import traceback
traceback.print_exc()
def _toggle_collision_visibility(self, node):
"""切换碰撞体可见性"""
try:
if not node or node.isEmpty():
return
# 查找碰撞节点并切换可见性
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
if child.isHidden():
child.show()
else:
child.hide()
break
except Exception as e:
print(f"切换碰撞可见性失败: {e}")
def _update_collision_position(self, node, axis, value):
"""更新碰撞体位置"""
try:
if not node or node.isEmpty():
return
# 查找碰撞节点并更新位置
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
current_pos = child.getPos()
if axis == 'x':
child.setPos(value, current_pos.y, current_pos.z)
elif axis == 'y':
child.setPos(current_pos.x, value, current_pos.z)
elif axis == 'z':
child.setPos(current_pos.x, current_pos.y, value)
break
except Exception as e:
print(f"更新碰撞位置失败: {e}")
def _get_shape_type_from_name(self, shape_name):
"""从形状名称获取形状类型"""
if "Sphere" in shape_name:
return "sphere"
elif "Box" in shape_name:
return "box"
elif "Capsule" in shape_name:
return "capsule"
elif "Plane" in shape_name:
return "plane"
else:
return "sphere"
def _draw_shape_specific_parameters(self, node, shape_type, has_collision=True):
"""绘制形状特定参数"""
try:
if shape_type == "sphere":
self._draw_sphere_parameters(node, has_collision)
elif shape_type == "box":
self._draw_box_parameters(node, has_collision)
elif shape_type == "capsule":
self._draw_capsule_parameters(node, has_collision)
elif shape_type == "plane":
self._draw_plane_parameters(node, has_collision)
except Exception as e:
print(f"绘制形状参数失败: {e}")
def _draw_sphere_parameters(self, node, has_collision=True):
"""绘制球形参数"""
try:
imgui.text("球形参数:")
imgui.same_line()
# 获取当前半径
radius = self._get_sphere_radius(node)
# 半径调整
changed, new_radius = imgui.drag_float("半径##sphere_radius", radius, 0.1, 0.1, 100.0, "%.2f")
if changed and has_collision:
self._update_sphere_radius(node, new_radius)
except Exception as e:
print(f"绘制球形参数失败: {e}")
def _draw_box_parameters(self, node, has_collision=True):
"""绘制盒型参数"""
try:
imgui.text("盒型参数:")
# 获取当前尺寸
size = self._get_box_size(node)
# 尺寸调整
changed, new_x = imgui.drag_float("长度##box_length", size[0], 0.1, 0.1, 100.0, "%.2f")
if changed and has_collision:
self._update_box_size(node, 'x', new_x)
changed, new_y = imgui.drag_float("宽度##box_width", size[1], 0.1, 0.1, 100.0, "%.2f")
if changed and has_collision:
self._update_box_size(node, 'y', new_y)
changed, new_z = imgui.drag_float("高度##box_height", size[2], 0.1, 0.1, 100.0, "%.2f")
if changed and has_collision:
self._update_box_size(node, 'z', new_z)
except Exception as e:
print(f"绘制盒型参数失败: {e}")
def _draw_capsule_parameters(self, node, has_collision=True):
"""绘制胶囊体参数"""
try:
imgui.text("胶囊体参数:")
# 获取当前参数
radius = self._get_capsule_radius(node)
height = self._get_capsule_height(node)
# 半径调整
changed, new_radius = imgui.drag_float("半径##capsule_radius", radius, 0.1, 0.1, 100.0, "%.2f")
if changed and has_collision:
self._update_capsule_radius(node, new_radius)
# 高度调整
changed, new_height = imgui.drag_float("高度##capsule_height", height, 0.1, 0.1, 100.0, "%.2f")
if changed and has_collision:
self._update_capsule_height(node, new_height)
except Exception as e:
print(f"绘制胶囊体参数失败: {e}")
def _draw_plane_parameters(self, node, has_collision=True):
"""绘制平面参数"""
try:
imgui.text("平面参数:")
# 获取当前法向量
normal = self._get_plane_normal(node)
# 法向量调整
changed, new_x = imgui.drag_float("法向量 X##plane_normal_x", normal[0], 0.1, -1.0, 1.0, "%.2f")
if changed and has_collision:
self._update_plane_normal(node, 'x', new_x)
changed, new_y = imgui.drag_float("法向量 Y##plane_normal_y", normal[1], 0.1, -1.0, 1.0, "%.2f")
if changed and has_collision:
self._update_plane_normal(node, 'y', new_y)
changed, new_z = imgui.drag_float("法向量 Z##plane_normal_z", normal[2], 0.1, -1.0, 1.0, "%.2f")
if changed and has_collision:
self._update_plane_normal(node, 'z', new_z)
except Exception as e:
print(f"绘制平面参数失败: {e}")
def _get_sphere_radius(self, node):
"""获取球形半径"""
try:
# 从碰撞节点获取半径信息
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0:
solid = child.node().getSolid(0)
from panda3d.core import CollisionSphere
if isinstance(solid, CollisionSphere):
return solid.getRadius()
return 1.0
except Exception as e:
print(f"获取球形半径失败: {e}")
return 1.0
def _update_sphere_radius(self, node, radius):
"""更新球形半径"""
try:
# 重新创建碰撞体来更新参数
if hasattr(self, 'collision_manager'):
# 先移除旧的碰撞体
self._remove_collision_from_node(node)
# 重新创建带有新参数的碰撞体
self.collision_manager.setupAdvancedCollision(
node,
shape_type='sphere',
mask_type='MODEL_COLLISION',
radius=radius
)
print(f"更新球形半径为: {radius}")
except Exception as e:
print(f"更新球形半径失败: {e}")
def _get_box_size(self, node):
"""获取盒型尺寸"""
try:
# 从碰撞节点获取尺寸信息
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
# 尝试从碰撞体获取尺寸
if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0:
solid = child.node().getSolid(0)
from panda3d.core import CollisionBox
if isinstance(solid, CollisionBox):
min_p = solid.getMin()
max_p = solid.getMax()
return (
max_p.x - min_p.x,
max_p.y - min_p.y,
max_p.z - min_p.z
)
return (1.0, 1.0, 1.0)
except Exception as e:
print(f"获取盒型尺寸失败: {e}")
return (1.0, 1.0, 1.0)
def _update_box_size(self, node, axis, value):
"""更新盒型尺寸"""
try:
# 获取当前尺寸
current_size = self._get_box_size(node)
new_size = list(current_size)
# 更新指定轴的尺寸
if axis == 'x':
new_size[0] = value
elif axis == 'y':
new_size[1] = value
elif axis == 'z':
new_size[2] = value
# 重新创建碰撞体
if hasattr(self, 'collision_manager'):
self._remove_collision_from_node(node)
self.collision_manager.setupAdvancedCollision(
node,
shape_type='box',
mask_type='MODEL_COLLISION',
width=new_size[0],
length=new_size[1],
height=new_size[2]
)
print(f"更新盒型尺寸: {new_size}")
except Exception as e:
print(f"更新盒型尺寸失败: {e}")
def _get_capsule_radius(self, node):
"""获取胶囊体半径"""
try:
# 从碰撞节点获取半径信息
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0:
solid = child.node().getSolid(0)
from panda3d.core import CollisionCapsule
if isinstance(solid, CollisionCapsule):
return solid.getRadius()
return 1.0
except Exception as e:
print(f"获取胶囊体半径失败: {e}")
return 1.0
def _update_capsule_radius(self, node, radius):
"""更新胶囊体半径"""
try:
# 获取当前高度
height = self._get_capsule_height(node)
# 重新创建碰撞体
if hasattr(self, 'collision_manager'):
self._remove_collision_from_node(node)
self.collision_manager.setupAdvancedCollision(
node,
shape_type='capsule',
mask_type='MODEL_COLLISION',
radius=radius,
height=height
)
print(f"更新胶囊体半径为: {radius}")
except Exception as e:
print(f"更新胶囊体半径失败: {e}")
def _get_capsule_height(self, node):
"""获取胶囊体高度"""
try:
# 从碰撞节点获取高度信息
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0:
solid = child.node().getSolid(0)
from panda3d.core import CollisionCapsule
if isinstance(solid, CollisionCapsule):
point_a = solid.getPointA()
point_b = solid.getPointB()
return (point_b - point_a).length() + 2 * solid.getRadius()
return 2.0
except Exception as e:
print(f"获取胶囊体高度失败: {e}")
return 2.0
def _update_capsule_height(self, node, height):
"""更新胶囊体高度"""
try:
# 获取当前半径
radius = self._get_capsule_radius(node)
# 重新创建碰撞体
if hasattr(self, 'collision_manager'):
self._remove_collision_from_node(node)
self.collision_manager.setupAdvancedCollision(
node,
shape_type='capsule',
mask_type='MODEL_COLLISION',
radius=radius,
height=height
)
print(f"更新胶囊体高度为: {height}")
except Exception as e:
print(f"更新胶囊体高度失败: {e}")
def _get_plane_normal(self, node):
"""获取平面法向量"""
try:
# 从碰撞节点获取法向量信息
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0:
solid = child.node().getSolid(0)
from panda3d.core import CollisionPlane
if isinstance(solid, CollisionPlane):
plane = solid.getPlane()
normal = plane.getNormal()
return (normal.x, normal.y, normal.z)
return (0.0, 0.0, 1.0)
except Exception as e:
print(f"获取平面法向量失败: {e}")
return (0.0, 0.0, 1.0)
def _update_plane_normal(self, node, axis, value):
"""更新平面法向量"""
try:
# 获取当前法向量
current_normal = self._get_plane_normal(node)
new_normal = list(current_normal)
# 更新指定轴的值
if axis == 'x':
new_normal[0] = value
elif axis == 'y':
new_normal[1] = value
elif axis == 'z':
new_normal[2] = value
# 标准化法向量
from panda3d.core import Vec3
normal_vec = Vec3(*new_normal)
normal_vec.normalize()
# 重新创建碰撞体
if hasattr(self, 'collision_manager'):
self._remove_collision_from_node(node)
self.collision_manager.setupAdvancedCollision(
node,
shape_type='plane',
mask_type='MODEL_COLLISION',
normal=normal_vec
)
print(f"更新平面法向量为: ({normal_vec.x:.2f}, {normal_vec.y:.2f}, {normal_vec.z:.2f})")
except Exception as e:
print(f"更新平面法向量失败: {e}")
def _manual_collision_detection(self):
"""手动执行碰撞检测"""
try:
if hasattr(self, 'collision_manager'):
results = self.collision_manager.detectModelCollisions(log_results=True)
if results:
print(f"手动碰撞检测完成,发现 {len(results)} 个碰撞")
else:
print("手动碰撞检测完成,未发现碰撞")
except Exception as e:
print(f"手动碰撞检测失败: {e}")
def _draw_property_actions(self, node):
"""绘制属性操作按钮"""
# 重置变换
if imgui.button("重置变换"):
node.setPos(0, 0, 0)
node.setHpr(0, 0, 0)
node.setScale(1, 1, 1)
imgui.same_line()
# 切换可见性
is_visible = not node.is_hidden()
visibility_text = "隐藏" if is_visible else "显示"
if imgui.button(visibility_text):
if is_visible:
node.hide()
else:
node.show()
imgui.same_line()
# 聚焦到对象
if imgui.button("聚焦"):
if hasattr(self, 'selection') and self.selection:
self.selection.focusCameraOnSelectedNodeAdvanced()
# 删除对象
imgui.same_line()
if imgui.button("删除"):
if hasattr(self, 'selection') and self.selection:
self.selection.deleteSelectedNode()
def _update_node_name(self, node, new_name):
"""更新节点名称"""
if new_name and new_name != node.getName():
node.setName(new_name)
# 更新场景树显示
if hasattr(self, 'scene_tree'):
self.scene_tree.refresh()
def _draw_appearance_properties(self, node):
"""绘制外观属性"""
# 颜色属性
if hasattr(node, 'getColor'):
imgui.text("颜色")
try:
color = node.getColor()
# 确保颜色是有效的
if not color or len(color) < 3:
color = (1.0, 1.0, 1.0, 1.0) # 默认白色
except:
color = (1.0, 1.0, 1.0, 1.0) # 默认白色
# 颜色滑块
changed, new_r = imgui.slider_float("R##color_r", color[0], 0.0, 1.0)
if changed:
new_color = (new_r, color[1], color[2], color[3] if len(color) > 3 else 1.0)
node.setColor(new_color)
color = new_color
changed, new_g = imgui.slider_float("G##color_g", color[1], 0.0, 1.0)
if changed:
new_color = (color[0], new_g, color[2], color[3] if len(color) > 3 else 1.0)
node.setColor(new_color)
color = new_color
changed, new_b = imgui.slider_float("B##color_b", color[2], 0.0, 1.0)
if changed:
new_color = (color[0], color[1], new_b, color[3] if len(color) > 3 else 1.0)
node.setColor(new_color)
color = new_color
# 只有当颜色有alpha通道时才显示alpha滑块
if len(color) > 3:
changed, new_a = imgui.slider_float("A##color_a", color[3], 0.0, 1.0)
if changed:
new_color = (color[0], color[1], color[2], new_a)
node.setColor(new_color)
color = new_color
# 颜色预览和选择器
imgui.text("颜色预览")
color_with_alpha = (color[0], color[1], color[2], color[3] if len(color) > 3 else 1.0)
if imgui.color_button("颜色预览##preview", color_with_alpha, 0, (100, 30)):
# 点击颜色按钮打开颜色选择器
self.show_color_picker(node, 'color', color_with_alpha)
imgui.same_line()
if imgui.button("选择颜色##color_picker_btn"):
self.show_color_picker(node, 'color', (color.x, color.y, color.z, color.w))
# 透明度
if hasattr(node, 'setTransparency') and hasattr(node, 'getTransparency'):
imgui.text("透明度")
current_transparency = node.getTransparency()
# 将当前的透明度值转换为0.0-1.0范围用于显示
display_transparency = 1.0 - current_transparency if current_transparency <= 1 else 0.0
changed, new_transparency = imgui.slider_float("透明度", display_transparency, 0.0, 1.0)
if changed:
# 将0.0-1.0范围转换回Panda3D的透明度格式
panda_transparency = int((1.0 - new_transparency) * 255)
node.setTransparency(panda_transparency)
# 材质属性
self._draw_material_properties(node)
# 渲染状态
imgui.text("渲染状态")
if imgui.button("应用材质"):
self._apply_material_to_node(node)
imgui.same_line()
if imgui.button("重置材质"):
self._reset_material(node)
def _draw_material_properties(self, node):
"""绘制材质属性"""
materials = node.find_all_materials()
if not materials:
imgui.text_colored((0.5, 0.5, 0.5, 1.0), "无材质")
return
for i, material in enumerate(materials):
material_name = material.get_name() if hasattr(material, 'get_name') and material.get_name() else f"材质{i + 1}"
if imgui.collapsing_header(f"材质: {material_name}"):
# 材质基础颜色
base_color = self._get_material_base_color(material)
if base_color:
imgui.text("基础颜色")
changed, new_r = imgui.slider_float(f"R##mat_r_{i}", base_color[0], 0.0, 1.0)
if changed:
self._update_material_base_color(material, 'r', new_r)
base_color = (new_r, base_color[1], base_color[2], base_color[3])
changed, new_g = imgui.slider_float(f"G##mat_g_{i}", base_color[1], 0.0, 1.0)
if changed:
self._update_material_base_color(material, 'g', new_g)
base_color = (base_color[0], new_g, base_color[2], base_color[3])
changed, new_b = imgui.slider_float(f"B##mat_b_{i}", base_color[2], 0.0, 1.0)
if changed:
self._update_material_base_color(material, 'b', new_b)
base_color = (base_color[0], base_color[1], new_b, base_color[3])
changed, new_a = imgui.slider_float(f"A##mat_a_{i}", base_color[3], 0.0, 1.0)
if changed:
self._update_material_base_color(material, 'a', new_a)
base_color = (base_color[0], base_color[1], base_color[2], new_a)
# PBR属性
if hasattr(material, 'roughness') and material.roughness is not None:
imgui.text("PBR属性")
try:
roughness_value = float(material.roughness)
changed, new_roughness = imgui.slider_float(f"粗糙度##rough_{i}", roughness_value, 0.0, 1.0)
if changed:
self._update_material_roughness(material, new_roughness)
except:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "粗糙度: 不可用")
if hasattr(material, 'metallic') and material.metallic is not None:
try:
metallic_value = float(material.metallic)
changed, new_metallic = imgui.slider_float(f"金属性##metal_{i}", metallic_value, 0.0, 1.0)
if changed:
self._update_material_metallic(material, new_metallic)
except:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "金属性: 不可用")
if hasattr(material, 'refractive_index') and material.refractive_index is not None:
try:
ior_value = float(material.refractive_index)
changed, new_ior = imgui.slider_float(f"折射率##ior_{i}", ior_value, 1.0, 3.0)
if changed:
self._update_material_ior(material, new_ior)
except:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "折射率: 不可用")
# 材质预设
imgui.text("材质预设")
presets = ["默认", "金属", "塑料", "玻璃", "木材", "混凝土"]
current_preset = 0 # 默认选择
if imgui.begin_combo(f"预设##preset_{i}", presets[current_preset]):
for j, preset_name in enumerate(presets):
if imgui.selectable(preset_name, j == current_preset):
self._apply_material_preset(material, preset_name)
imgui.end_combo()
# 纹理信息
imgui.text("纹理贴图")
if imgui.button(f"选择漫反射贴图##diffuse_{i}"):
self._select_texture_for_material(node, material, "diffuse")
imgui.same_line()
if imgui.button(f"选择法线贴图##normal_{i}"):
self._select_texture_for_material(node, material, "normal")
imgui.same_line()
if imgui.button(f"选择粗糙度贴图##roughness_{i}"):
self._select_texture_for_material(node, material, "roughness")
if imgui.button(f"选择金属性贴图##metallic_{i}"):
self._select_texture_for_material(node, material, "metallic")
imgui.same_line()
if imgui.button(f"选择自发光贴图##emission_{i}"):
self._select_texture_for_material(node, material, "emission")
imgui.same_line()
if imgui.button(f"清除所有贴图##clear_{i}"):
self._clear_all_textures(node)
# 着色模型选择
self._draw_shading_model_panel(material, i)
# 显示当前纹理信息
self._display_current_textures(node, material)
def _get_material_base_color(self, material):
"""获取材质基础颜色"""
try:
if hasattr(material, 'base_color') and material.base_color is not None:
return (material.base_color.x, material.base_color.y, material.base_color.z, material.base_color.w)
elif hasattr(material, 'get_base_color'):
color = material.get_base_color()
return (color.x, color.y, color.z, color.w)
elif hasattr(material, 'getDiffuse'):
color = material.getDiffuse()
return (color.x, color.y, color.z, color.w if hasattr(color, 'w') else 1.0)
else:
return (1.0, 1.0, 1.0, 1.0) # 默认白色
except:
return (1.0, 1.0, 1.0, 1.0) # 默认白色
def _update_material_base_color(self, material, component, value):
"""更新材质基础颜色"""
try:
base_color = self._get_material_base_color(material)
new_color = list(base_color)
if component == 'r':
new_color[0] = value
elif component == 'g':
new_color[1] = value
elif component == 'b':
new_color[2] = value
elif component == 'a':
new_color[3] = value
new_color_tuple = tuple(new_color)
if hasattr(material, 'set_base_color'):
from panda3d.core import Vec4
material.set_base_color(Vec4(*new_color_tuple))
elif hasattr(material, 'setDiffuse'):
from panda3d.core import Vec4
material.setDiffuse(Vec4(*new_color_tuple))
except Exception as e:
print(f"更新材质基础颜色失败: {e}")
def _update_material_roughness(self, material, value):
"""更新材质粗糙度"""
try:
if hasattr(material, 'set_roughness'):
material.set_roughness(value)
except Exception as e:
print(f"更新材质粗糙度失败: {e}")
def _update_material_metallic(self, material, value):
"""更新材质金属性"""
try:
if hasattr(material, 'set_metallic'):
material.set_metallic(value)
except Exception as e:
print(f"更新材质金属性失败: {e}")
def _update_material_ior(self, material, value):
"""更新材质折射率"""
try:
if hasattr(material, 'set_refractive_index'):
material.set_refractive_index(value)
except Exception as e:
print(f"更新材质折射率失败: {e}")
def _apply_material_preset(self, material, preset_name):
"""应用材质预设"""
try:
from panda3d.core import Vec4, Material
presets = {
"默认": {
"base_color": Vec4(0.8, 0.8, 0.8, 1.0),
"roughness": 0.5,
"metallic": 0.0,
"ior": 1.5
},
"金属": {
"base_color": Vec4(0.7, 0.7, 0.8, 1.0),
"roughness": 0.2,
"metallic": 1.0,
"ior": 2.5
},
"塑料": {
"base_color": Vec4(0.9, 0.9, 0.9, 1.0),
"roughness": 0.8,
"metallic": 0.0,
"ior": 1.45
},
"玻璃": {
"base_color": Vec4(0.9, 0.9, 1.0, 0.2),
"roughness": 0.0,
"metallic": 0.0,
"ior": 1.5
},
"木材": {
"base_color": Vec4(0.6, 0.4, 0.2, 1.0),
"roughness": 0.9,
"metallic": 0.0,
"ior": 1.55
},
"混凝土": {
"base_color": Vec4(0.5, 0.5, 0.5, 1.0),
"roughness": 1.0,
"metallic": 0.0,
"ior": 1.5
}
}
if preset_name in presets:
preset = presets[preset_name]
# 应用基础颜色
if hasattr(material, 'set_base_color'):
material.set_base_color(preset["base_color"])
elif hasattr(material, 'setDiffuse'):
material.setDiffuse(preset["base_color"])
# 应用PBR属性
if hasattr(material, 'set_roughness'):
material.set_roughness(preset["roughness"])
if hasattr(material, 'set_metallic'):
material.set_metallic(preset["metallic"])
if hasattr(material, 'set_refractive_index'):
material.set_refractive_index(preset["ior"])
print(f"已应用材质预设: {preset_name}")
except Exception as e:
print(f"应用材质预设失败: {e}")
def _apply_material_to_node(self, node):
"""为节点应用材质"""
try:
from panda3d.core import Material, Vec4
# 检查是否已有材质
materials = node.find_all_materials()
if not materials:
# 创建新材质
material = Material(f"default-material-{node.getName()}")
material.setBaseColor(Vec4(0.8, 0.8, 0.8, 1.0))
material.setDiffuse(Vec4(0.8, 0.8, 0.8, 1.0))
material.setAmbient(Vec4(0.4, 0.4, 0.4, 1.0))
material.setSpecular(Vec4(0.1, 0.1, 0.1, 1.0))
material.setShininess(10.0)
node.setMaterial(material, 1)
print(f"已为新节点创建默认材质")
else:
print(f"节点已有 {len(materials)} 个材质")
except Exception as e:
print(f"应用材质失败: {e}")
def _reset_material(self, node):
"""重置节点材质"""
try:
materials = node.find_all_materials()
for material in materials:
# 重置为默认材质属性
from panda3d.core import Vec4
default_color = Vec4(0.8, 0.8, 0.8, 1.0)
if hasattr(material, 'set_base_color'):
material.set_base_color(default_color)
elif hasattr(material, 'setDiffuse'):
material.setDiffuse(default_color)
if hasattr(material, 'set_roughness'):
material.set_roughness(0.5)
if hasattr(material, 'set_metallic'):
material.set_metallic(0.0)
if hasattr(material, 'set_refractive_index'):
material.set_refractive_index(1.5)
print(f"已重置材质")
except Exception as e:
print(f"重置材质失败: {e}")
def _select_texture_for_material(self, node, material, texture_type):
"""为材质选择纹理"""
try:
# 设置当前纹理对话框状态
self._current_texture_dialog = {
'node': node,
'material': material,
'texture_type': texture_type
}
# 初始化路径
if not hasattr(self, '_texture_dialog_path'):
self._texture_dialog_path = '/home/hello/EG/Resources'
# 设置文件类型过滤
self._texture_dialog_filter = "*.png"
except Exception as e:
print(f"选择纹理失败: {e}")
def _apply_texture_to_material(self, node, material, texture_type, texture_path):
"""应用纹理到材质"""
try:
from panda3d.core import TextureStage
from direct.showbase import Loader
# 加载纹理
loader = Loader.Loader(self)
texture = loader.loadTexture(texture_path)
if not texture:
print(f"无法加载纹理: {texture_path}")
return
# 设置纹理属性
texture.setMagfilter(texture.FTLinear)
texture.setMinfilter(texture.FTLinearMipmapLinear)
# 纹理槽位映射
texture_slots = {
"diffuse": 0, # p3d_Texture0
"ior": 2, # p3d_Texture2
"normal": 1, # p3d_Texture1
"roughness": 3, # p3d_Texture3
"parallax": 4, # p3d_Texture4
"metallic": 5, # p3d_Texture5
"emission": 6, # p3d_Texture6
"ao": 7, # p3d_Texture7
"alpha": 8, # p3d_Texture8
"detail": 9, # p3d_Texture9
"gloss": 10 # p3d_Texture10
}
slot = texture_slots.get(texture_type, 0)
# 创建纹理阶段
texture_stage = TextureStage(f"{texture_type}_map")
texture_stage.setSort(slot)
texture_stage.setMode(TextureStage.MModulate)
# 应用纹理到节点
node.setTexture(texture_stage, texture)
print(f"已应用{texture_type}纹理: {texture_path}")
except Exception as e:
print(f"应用纹理失败: {e}")
def _clear_all_textures(self, node):
"""清除节点所有纹理"""
try:
# 清除所有纹理阶段
node.clearTexture()
node.clearTexture()
print("已清除所有纹理")
except Exception as e:
print(f"清除纹理失败: {e}")
def _display_current_textures(self, node, material):
"""显示当前纹理信息"""
try:
from panda3d.core import TextureStage
# 获取所有纹理阶段
texture_stages = node.findAllTextureStages()
if not texture_stages:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "当前无纹理")
return
imgui.text("当前纹理:")
for stage in texture_stages:
texture = node.getTexture(stage)
if texture:
texture_name = texture.getName() or "未命名"
stage_name = stage.getName() or "未命名"
imgui.text(f" {stage_name}: {texture_name}")
except Exception as e:
print(f"显示纹理信息失败: {e}")
def _draw_shading_model_panel(self, material, material_index):
"""绘制着色模型选择面板"""
try:
imgui.text("着色模型")
# RenderPipeline支持的着色模型
shading_models = ["默认", "自发光", "透明"]
current_model = 0 # 默认选择
# 安全地获取当前着色模型
try:
if hasattr(material, 'emission') and material.emission is not None:
current_model = int(material.emission.x)
except:
current_model = 0
# 着色模型选择
if imgui.begin_combo(f"着色模型##shading_{material_index}", shading_models[current_model]):
for j, model_name in enumerate(shading_models):
if imgui.selectable(model_name, j == current_model):
self._update_shading_model(material, j)
imgui.end_combo()
# 如果是透明着色模型,添加透明度控制
if current_model == 3: # 透明着色模型
imgui.text("透明度设置")
try:
if hasattr(material, 'shading_model_param0'):
current_opacity = material.shading_model_param0
else:
current_opacity = 1.0
changed, new_opacity = imgui.slider_float(f"不透明度##opacity_{material_index}", current_opacity, 0.0, 1.0)
if changed:
self._update_transparency(material, new_opacity)
except:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "透明度控制不可用")
except Exception as e:
print(f"绘制着色模型面板失败: {e}")
def _update_shading_model(self, material, model_index):
"""更新着色模型"""
try:
from panda3d.core import Vec4
# 根据不同的着色模型设置相应的参数
if model_index == 1: # 自发光着色模型
print("设置自发光着色模型...")
if hasattr(material, 'set_emission'):
current_emission = material.emission or Vec4(0, 0, 0, 0)
new_emission = Vec4(1.0, current_emission.y, current_emission.z, current_emission.w)
material.set_emission(new_emission)
print("自发光着色模型设置完成")
elif model_index == 3: # 透明着色模型
print("设置透明着色模型...")
if hasattr(material, 'set_emission'):
current_emission = material.emission or Vec4(0, 0, 0, 0)
new_emission = Vec4(3.0, 0, 0, 0) # 3表示透明着色模型
material.set_emission(new_emission)
# 设置默认透明度
if hasattr(material, 'shading_model_param0'):
material.shading_model_param0 = 0.8 # 默认80%不透明度
print("透明着色模型设置完成")
else: # 默认着色模型
print("设置默认着色模型...")
if hasattr(material, 'set_emission'):
current_emission = material.emission or Vec4(0, 0, 0, 0)
new_emission = Vec4(0.0, current_emission.y, current_emission.z, current_emission.w)
material.set_emission(new_emission)
print("默认着色模型设置完成")
print(f"着色模型已更新为: {model_index} ({'自发光' if model_index == 1 else '透明' if model_index == 3 else '默认'})")
except Exception as e:
print(f"更新着色模型失败: {e}")
def _update_transparency(self, material, opacity_value):
"""更新透明度"""
try:
if hasattr(material, 'shading_model_param0'):
material.shading_model_param0 = opacity_value
print(f"透明度已更新: {opacity_value}")
except Exception as e:
print(f"更新透明度失败: {e}")
def _draw_texture_file_dialog(self):
"""绘制纹理文件选择对话框"""
if not hasattr(self, '_current_texture_dialog') or not self._current_texture_dialog:
return
try:
dialog_data = self._current_texture_dialog
node = dialog_data['node']
material = dialog_data['material']
texture_type = dialog_data['texture_type']
# 设置对话框标志
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
# 获取屏幕尺寸,居中显示对话框
display_size = imgui.get_io().display_size
dialog_width = 600
dialog_height = 400
imgui.set_next_window_size((dialog_width, dialog_height))
imgui.set_next_window_pos(
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
)
# 显示文件选择对话框
opened, window_open = imgui.begin(f"选择{texture_type}纹理文件##texture_dialog", True, flags)
if not window_open:
self._current_texture_dialog = None
imgui.end()
return
imgui.text(f"选择{texture_type}纹理文件")
imgui.separator()
# 当前路径显示
current_path = getattr(self, '_texture_dialog_path', '/home/hello/EG/Resources')
imgui.text(f"当前路径: {current_path}")
imgui.separator()
# 文件类型过滤
imgui.text("支持的纹理格式:")
file_types = ["*.png", "*.jpg", "*.jpeg", "*.bmp", "*.tga", "*.dds"]
current_filter = getattr(self, '_texture_dialog_filter', "*.png")
if imgui.begin_combo("文件类型##texture_filter", current_filter):
for i, file_type in enumerate(file_types):
if imgui.selectable(file_type, i == file_types.index(current_filter)):
self._texture_dialog_filter = file_type
imgui.end_combo()
imgui.separator()
# 路径导航按钮
if imgui.button("上级目录##up_dir"):
current_path = os.path.dirname(current_path)
self._texture_dialog_path = current_path
imgui.same_line()
if imgui.button("主目录##home_dir"):
self._texture_dialog_path = '/home/hello/EG/Resources'
imgui.same_line()
if imgui.button("当前目录##current_dir"):
self._texture_dialog_path = '/home/hello/EG/Resources'
imgui.same_line()
if imgui.button("纹理目录##textures_dir"):
self._texture_dialog_path = '/home/hello/EG/Resources/textures'
imgui.separator()
# 文件列表
if imgui.begin_child("file_list##texture_files", (580, 200)):
try:
# 列出目录和文件
items = []
if os.path.exists(current_path):
for item in os.listdir(current_path):
item_path = os.path.join(current_path, item)
if os.path.isdir(item_path):
items.append(('dir', item, item_path))
elif any(item.lower().endswith(ext[1:]) for ext in file_types):
items.append(('file', item, item_path))
# 排序:目录在前,文件在后
items.sort(key=lambda x: (x[0], x[1].lower()))
for item_type, item_name, item_path in items:
if item_type == 'dir':
if imgui.selectable(f"📁 {item_name}##dir_{item_name}", False)[0]:
self._texture_dialog_path = item_path
else:
selected, _ = imgui.selectable(f"📄 {item_name}##file_{item_name}", False)
if selected:
# 应用选择的纹理
self._apply_texture_to_material(node, material, texture_type, item_path)
# 关闭对话框
self._current_texture_dialog = None
break
except Exception as e:
imgui.text_colored((1.0, 0.5, 0.5, 1.0), f"读取目录失败: {e}")
imgui.end_child()
imgui.separator()
# 路径输入框
changed, new_path = imgui.input_text("文件路径##texture_path", current_path, 512)
if changed:
self._texture_dialog_path = new_path
imgui.same_line()
if imgui.button("确认路径##confirm_path"):
if os.path.isfile(new_path):
# 检查文件扩展名
file_ext = os.path.splitext(new_path)[1].lower()
if file_ext in [ext[1:] for ext in file_types]:
self._apply_texture_to_material(node, material, texture_type, new_path)
self._current_texture_dialog = None
else:
print(f"不支持的文件格式: {file_ext}")
else:
print("请选择有效的文件")
imgui.separator()
# 按钮
if imgui.button("取消##cancel_texture"):
self._current_texture_dialog = None
imgui.end()
except Exception as e:
print(f"绘制纹理对话框失败: {e}")
# 确保在异常情况下也调用 imgui.end()
try:
imgui.end()
except:
pass
def start_transform_monitoring(self, node):
"""开始变换监控"""
if node and not node.isEmpty():
self._monitored_node = node
self._transform_monitoring = True
self._transform_update_timer = 0
# 记录初始变换值
self._update_last_transform_values()
def stop_transform_monitoring(self):
"""停止变换监控"""
self._transform_monitoring = False
self._monitored_node = None
self._last_transform_values = {}
def _update_last_transform_values(self):
"""更新最后记录的变换值"""
if self._monitored_node and not self._monitored_node.isEmpty():
try:
pos = self._monitored_node.getPos()
hpr = self._monitored_node.getHpr()
scale = self._monitored_node.getScale()
self._last_transform_values = {
'pos': (pos.x, pos.y, pos.z),
'hpr': (hpr.x, hpr.y, hpr.z),
'scale': (scale.x, scale.y, scale.z)
}
except Exception as e:
print(f"更新变换值失败: {e}")
def _check_transform_changes(self):
"""检查变换变化"""
if not self._transform_monitoring or not self._monitored_node:
return
try:
pos = self._monitored_node.getPos()
hpr = self._monitored_node.getHpr()
scale = self._monitored_node.getScale()
current_values = {
'pos': (pos.x, pos.y, pos.z),
'hpr': (hpr.x, hpr.y, hpr.z),
'scale': (scale.x, scale.y, scale.z)
}
# 检查是否有变化
if current_values != self._last_transform_values:
# 更新记录的值
self._last_transform_values = current_values
# 触发属性面板更新(通过设置更新标志)
self.property_panel_update_timer = 0
except Exception as e:
print(f"检查变换变化失败: {e}")
def update_transform_monitoring(self, dt):
"""更新变换监控(在主循环中调用)"""
if not self._transform_monitoring:
return
self._transform_update_timer += dt
if self._transform_update_timer >= self._transform_update_interval:
self._transform_update_timer = 0
self._check_transform_changes()
def show_color_picker(self, target_object, property_name, initial_color, callback=None):
"""显示颜色选择器"""
self._color_picker_active = True
self._color_picker_target = (target_object, property_name)
self._color_picker_current_color = initial_color
self._color_picker_callback = callback
def _draw_color_picker(self):
"""绘制颜色选择器对话框"""
if not self._color_picker_active:
return
# 设置对话框标志
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
# 获取屏幕尺寸,居中显示对话框
display_size = imgui.get_io().display_size
dialog_width = 300
dialog_height = 400
imgui.set_next_window_size((dialog_width, dialog_height))
imgui.set_next_window_pos(
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
)
with imgui_ctx.begin("颜色选择器", True, flags) as window:
if not window.opened:
self._color_picker_active = False
self._color_picker_target = None
return
imgui.text("选择颜色")
imgui.separator()
# 颜色编辑器
changed, new_color = imgui.color_edit4(
"颜色##color_picker",
self._color_picker_current_color
)
if changed:
self._color_picker_current_color = new_color
# 预设颜色
imgui.text("预设颜色")
preset_colors = [
(1.0, 0.0, 0.0, 1.0), # 红色
(0.0, 1.0, 0.0, 1.0), # 绿色
(0.0, 0.0, 1.0, 1.0), # 蓝色
(1.0, 1.0, 0.0, 1.0), # 黄色
(1.0, 0.0, 1.0, 1.0), # 洋红
(0.0, 1.0, 1.0, 1.0), # 青色
(1.0, 1.0, 1.0, 1.0), # 白色
(0.0, 0.0, 0.0, 1.0), # 黑色
(0.5, 0.5, 0.5, 1.0), # 灰色
(0.188, 0.404, 0.753, 1.0), # 主题蓝色
(0.176, 1.0, 0.769, 1.0), # 主题绿色
(0.953, 0.616, 0.471, 1.0), # 主题橙色
]
# 绘制预设颜色按钮
colors_per_row = 6
for i, color in enumerate(preset_colors):
if i % colors_per_row != 0:
imgui.same_line()
imgui.color_button(f"preset_{i}", color, 0, (30, 30))
if imgui.is_item_clicked():
self._color_picker_current_color = color
imgui.separator()
# 按钮区域
if imgui.button("确定"):
self._apply_color_selection()
self._color_picker_active = False
self._color_picker_target = None
imgui.same_line()
if imgui.button("取消"):
self._color_picker_active = False
self._color_picker_target = None
def _apply_color_selection(self):
"""应用颜色选择"""
if not self._color_picker_target:
return
target_object, property_name = self._color_picker_target
try:
# 应用颜色到目标对象
if hasattr(target_object, 'setColor'):
target_object.setColor(self._color_picker_current_color)
elif hasattr(target_object, property_name):
setattr(target_object, property_name, self._color_picker_current_color)
# 调用回调函数
if self._color_picker_callback:
self._color_picker_callback(self._color_picker_current_color)
except Exception as e:
print(f"应用颜色失败: {e}")
def _draw_color_button(self, label, color, size=(50, 20)):
"""绘制颜色按钮并支持点击打开颜色选择器"""
imgui.color_button(label, color, 0, size)
if imgui.is_item_clicked():
# 打开颜色选择器
self.show_color_picker(None, None, color)
def _refresh_available_fonts(self):
"""刷新可用字体列表"""
try:
import platform
from pathlib import Path
system = platform.system().lower()
font_paths = []
if system == "linux":
font_dirs = [
"/usr/share/fonts/truetype/",
"/usr/share/fonts/opentype/",
"/usr/local/share/fonts/",
"~/.fonts/"
]
elif system == "windows":
font_dirs = [
"C:/Windows/Fonts/",
]
elif system == "darwin":
font_dirs = [
"/System/Library/Fonts/",
"/Library/Fonts/",
"~/Library/Fonts/"
]
else:
font_dirs = []
# 扫描字体目录
common_fonts = []
for font_dir in font_dirs:
font_path = Path(font_dir).expanduser()
if font_path.exists():
for font_file in font_path.rglob("*.ttf"):
common_fonts.append(str(font_file))
for font_file in font_path.rglob("*.otf"):
common_fonts.append(str(font_file))
for font_file in font_path.rglob("*.ttc"):
common_fonts.append(str(font_file))
# 过滤常见字体
font_keywords = [
"arial", "helvetica", "times", "courier", "verdana", "georgia",
"comic", "impact", "trebuchet", "palatino", "garamond",
"noto", "dejavu", "liberation", "ubuntu", "roboto", "open",
"droid", "source", "wenquanyi", "wqy", "pingfang", "stheiti",
"microsoft", "msyh", "simsun", "simhei", "kaiti", "fangsong"
]
self._available_fonts = []
for font_path in common_fonts:
font_name = Path(font_path).name.lower()
if any(keyword in font_name for keyword in font_keywords):
self._available_fonts.append(font_path)
# 添加一些默认字体路径
default_fonts = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
"/usr/share/fonts/opentype/noto/NotoSans-Regular.ttf",
"C:/Windows/Fonts/arial.ttf",
"C:/Windows/Fonts/msyh.ttc",
"/System/Library/Fonts/PingFang.ttc"
]
for font_path in default_fonts:
if Path(font_path).exists() and font_path not in self._available_fonts:
self._available_fonts.append(font_path)
print(f"✓ 找到 {len(self._available_fonts)} 个可用字体")
except Exception as e:
print(f"⚠ 字体扫描失败: {e}")
self._available_fonts = []
def show_font_selector(self, target_object, property_name, current_font, callback=None):
"""显示字体选择器"""
self._font_selector_active = True
self._font_selector_target = (target_object, property_name)
self._font_selector_current_font = current_font or ""
self._font_selector_callback = callback
def _draw_font_selector(self):
"""绘制字体选择器对话框"""
if not self._font_selector_active:
return
# 设置对话框标志
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
# 获取屏幕尺寸,居中显示对话框
display_size = imgui.get_io().display_size
dialog_width = 400
dialog_height = 500
imgui.set_next_window_size((dialog_width, dialog_height))
imgui.set_next_window_pos(
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
)
with imgui_ctx.begin("字体选择器", True, flags) as window:
if not window.opened:
self._font_selector_active = False
self._font_selector_target = None
return
imgui.text("选择字体")
imgui.separator()
# 当前字体显示
imgui.text(f"当前字体: {self._font_selector_current_font or '默认'}")
# 字体搜索框
changed, search_text = imgui.input_text("搜索", "", 256)
imgui.separator()
# 字体列表
if imgui.begin_child("font_list", (380, 300)):
for font_path in self._available_fonts:
font_name = Path(font_path).name
# 搜索过滤
if search_text and search_text.lower() not in font_name.lower():
continue
# 字体项
if imgui.selectable(font_name, font_path == self._font_selector_current_font):
self._font_selector_current_font = font_path
# 显示字体路径作为工具提示
if imgui.is_item_hovered():
imgui.set_tooltip(font_path)
imgui.end_child()
imgui.separator()
# 按钮区域
if imgui.button("确定"):
self._apply_font_selection()
self._font_selector_active = False
self._font_selector_target = None
imgui.same_line()
if imgui.button("取消"):
self._font_selector_active = False
self._font_selector_target = None
imgui.same_line()
if imgui.button("刷新字体"):
self._refresh_available_fonts()
def _apply_font_selection(self):
"""应用字体选择"""
if not self._font_selector_target:
return
target_object, property_name = self._font_selector_target
try:
# 应用字体到目标对象
if hasattr(target_object, property_name):
setattr(target_object, property_name, self._font_selector_current_font)
# 调用回调函数
if self._font_selector_callback:
self._font_selector_callback(self._font_selector_current_font)
except Exception as e:
print(f"应用字体失败: {e}")
def _draw_font_selector_button(self, label, current_font):
"""绘制字体选择器按钮"""
font_name = Path(current_font).name if current_font else "默认字体"
display_text = f"{font_name[:20]}..." if len(font_name) > 20 else font_name
if imgui.button(f"{label}: {display_text}##font_selector"):
self.show_font_selector(None, None, current_font)
def _draw_console(self):
"""绘制控制台面板"""
# 使用面板类型的窗口标志支持docking
flags = self.style_manager.get_window_flags("panel")
with self.style_manager.begin_styled_window("控制台", self.showConsole, flags):
self.showConsole = True # 确保窗口保持打开
imgui.text("控制台输出")
imgui.separator()
# 显示消息系统中的消息
if hasattr(self, 'messages') and self.messages:
for message in self.messages:
# 显示时间戳
imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"[{message['timestamp']}]")
imgui.same_line()
# 根据消息类型显示图标
if message['text'].startswith(''):
if self.icons.get('success'):
imgui.image(self.icons['success'], (12, 12))
imgui.same_line()
elif message['text'].startswith(''):
if self.icons.get('delete_fail_icon'):
imgui.image(self.icons['delete_fail_icon'], (12, 12))
imgui.same_line()
elif message['text'].startswith(''):
if self.icons.get('warning'):
imgui.image(self.icons['warning'], (12, 12))
imgui.same_line()
# 显示消息文本
imgui.text_colored(message['color'], message['text'])
else:
# 默认消息
imgui.text_colored((0.157, 0.620, 1.0, 1.0), "[系统]")
imgui.same_line()
imgui.text("引擎已就绪")
# 输入框
imgui.separator()
changed, command = imgui.input_text(">", "", 256)
if changed and command:
self.add_info_message(f"执行命令: {command}")
# TODO: 实现命令执行逻辑
imgui.separator()
# 视角控制信息
imgui.text("视角控制:")
imgui.text(" WASD - 移动")
imgui.text(" Q/E - 上下")
imgui.text(" 右键拖拽 - 旋转视角")
imgui.text(" 滚轮 - 前进/后退")
# 相机位置信息
cam_pos = self.camera.getPos()
cam_hpr = self.camera.getHpr()
imgui.text(f"位置: X={cam_pos.x:.1f}, Y={cam_pos.y:.1f}, Z={cam_pos.z:.1f}")
imgui.text(f"旋转: H={cam_hpr.x:.1f}, P={cam_hpr.y:.1f}, R={cam_hpr.z:.1f}")
# 控制状态
imgui.checkbox("启用视角控制", self.camera_control_enabled)
# 重置按钮
if imgui.button("重置相机"):
self.camera.setPos(0, -20, 5)
self.camera.setHpr(0, 0, 0)
self.add_info_message("相机位置已重置")
def _draw_script_panel(self):
"""绘制脚本管理面板与Qt版本功能一致"""
# 使用面板类型的窗口标志支持docking
flags = self.style_manager.get_window_flags("panel")
with self.style_manager.begin_styled_window("脚本管理", self.showScriptPanel, flags):
self.showScriptPanel = True # 确保窗口保持打开
# 1. 脚本系统状态组
self._draw_script_status_group()
imgui.spacing()
# 2. 创建脚本组
self._draw_create_script_group()
imgui.spacing()
# 3. 可用脚本组
self._draw_available_scripts_group()
imgui.spacing()
# 4. 脚本挂载组
self._draw_script_mounting_group()
def _draw_script_status_group(self):
"""绘制脚本系统状态组"""
if imgui.collapsing_header("脚本系统状态", imgui.TreeNodeFlags_.default_open):
# 脚本系统状态
imgui.text("脚本引擎状态:")
imgui.same_line()
# 检查脚本管理器是否正常工作
if hasattr(self, 'script_manager') and self.script_manager:
if hasattr(self.script_manager, 'engine') and self.script_manager.engine:
imgui.text_colored((0.0, 1.0, 0.0, 1.0), "✓ 已启动")
else:
imgui.text_colored((1.0, 0.5, 0.0, 1.0), "⚠ 引擎未初始化")
else:
imgui.text_colored((1.0, 0.0, 0.0, 1.0), "✗ 未启动")
# 热重载状态
imgui.text("热重载状态:")
imgui.same_line()
hot_reload_enabled = False
if hasattr(self, 'script_manager') and self.script_manager:
hot_reload_enabled = getattr(self.script_manager, 'hot_reload_enabled', False)
if hot_reload_enabled:
imgui.text_colored((0.0, 1.0, 0.0, 1.0), "✓ 已启用")
else:
imgui.text_colored((1.0, 0.5, 0.0, 1.0), "✗ 已禁用")
imgui.same_line()
if imgui.button("切换热重载##toggle_hot_reload"):
self._toggle_hot_reload()
def _draw_create_script_group(self):
"""绘制创建脚本组"""
if imgui.collapsing_header("创建脚本"):
# 脚本名称输入
imgui.text("脚本名称:")
imgui.same_line()
# 获取当前脚本名称
if not hasattr(self, '_new_script_name'):
self._new_script_name = "new_script"
changed, new_name = imgui.input_text("##script_name", self._new_script_name, 256)
if changed:
self._new_script_name = new_name
# 模板选择
imgui.text("脚本模板:")
imgui.same_line()
if not hasattr(self, '_selected_template'):
self._selected_template = 0
templates = ["基础脚本", "移动脚本", "旋转脚本", "缩放脚本", "动画脚本"]
changed, selected = imgui.combo("##script_template", self._selected_template, templates)
if changed:
self._selected_template = selected
# 创建按钮
if imgui.button("创建脚本##create_script"):
self._create_new_script()
imgui.same_line()
if imgui.button("从文件创建##create_from_file"):
self._on_create_script()
def _draw_available_scripts_group(self):
"""绘制可用脚本组"""
if imgui.collapsing_header("可用脚本"):
# 刷新脚本列表
if imgui.button("刷新列表##refresh_scripts"):
self._refresh_scripts_list()
imgui.same_line()
if imgui.button("重载全部##reload_all_scripts"):
self._reload_all_scripts()
imgui.separator()
# 获取可用脚本列表
available_scripts = []
if hasattr(self, 'script_manager') and self.script_manager:
try:
available_scripts = self.script_manager.get_available_scripts()
except Exception as e:
print(f"获取脚本列表失败: {e}")
# 显示脚本列表
if available_scripts:
for i, script_name in enumerate(available_scripts):
selected, _ = imgui.selectable(f"{script_name}##script_{i}", False)
if selected:
self._on_script_selected(script_name)
# 双击编辑
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
self._edit_script(script_name)
else:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "无可用脚本")
def _draw_script_mounting_group(self):
"""绘制脚本挂载组"""
if imgui.collapsing_header("脚本挂载"):
# 显示当前选中对象
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():
imgui.text("选中对象:")
imgui.same_line()
imgui.text_colored((0.0, 1.0, 0.0, 1.0), selected_node.getName() or "未命名对象")
imgui.spacing()
# 脚本选择和挂载
imgui.text("选择脚本:")
imgui.same_line()
# 获取可用脚本
available_scripts = []
if hasattr(self, 'script_manager') and self.script_manager:
try:
available_scripts = self.script_manager.get_available_scripts()
except Exception as e:
print(f"获取脚本列表失败: {e}")
if available_scripts:
if not hasattr(self, '_mount_script_index'):
self._mount_script_index = 0
changed, selected = imgui.combo("##mount_script", self._mount_script_index, available_scripts)
if changed:
self._mount_script_index = selected
imgui.same_line()
if imgui.button("挂载##mount_script"):
if self._mount_script_index < len(available_scripts):
script_name = available_scripts[self._mount_script_index]
self._mount_script_to_selected(script_name)
else:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "无可用脚本")
imgui.spacing()
# 显示已挂载的脚本
imgui.text("已挂载脚本:")
mounted_scripts = []
if hasattr(self, 'script_manager') and self.script_manager:
try:
mounted_scripts = self.script_manager.get_scripts_on_object(selected_node)
except Exception as e:
print(f"获取已挂载脚本失败: {e}")
if mounted_scripts:
for i, script_component in enumerate(mounted_scripts):
# 从ScriptComponent获取脚本名称
script_name = getattr(script_component, 'script_name', None)
if not script_name and hasattr(script_component, '__class__'):
script_name = script_component.__class__.__name__
if not script_name:
script_name = f"Script_{i}"
imgui.text(f"{script_name}")
imgui.same_line()
if imgui.button(f"卸载##unmount_{i}"):
self._unmount_script_from_selected(script_name)
imgui.same_line()
if imgui.button(f"编辑##edit_mounted_{i}"):
self._edit_script(script_name)
else:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "无挂载脚本")
else:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "请先选择一个对象")
def _toggle_hot_reload(self):
"""切换热重载状态"""
if hasattr(self, 'script_manager') and self.script_manager:
try:
current_state = getattr(self.script_manager, 'hot_reload_enabled', False)
self.script_manager.hot_reload_enabled = not current_state
new_state = "启用" if not current_state else "禁用"
self.add_success_message(f"热重载已{new_state}")
print(f"[脚本系统] 热重载已{new_state}")
except Exception as e:
self.add_error_message(f"切换热重载失败: {str(e)}")
print(f"[脚本系统] 切换热重载失败: {e}")
def _create_new_script(self):
"""创建新脚本"""
if not hasattr(self, '_new_script_name') or not self._new_script_name.strip():
self.add_error_message("请输入脚本名称")
return
script_name = self._new_script_name.strip()
if not script_name.endswith('.py'):
script_name += '.py'
# 确定模板类型
template_map = {
0: "basic",
1: "movement",
2: "rotation",
3: "scale",
4: "animation"
}
template_type = template_map.get(getattr(self, '_selected_template', 0), "basic")
try:
if hasattr(self, 'script_manager') and self.script_manager:
result = self.script_manager.create_script_file(script_name, template_type)
if result:
self.add_success_message(f"脚本 {script_name} 创建成功")
print(f"[脚本系统] 创建脚本成功: {script_name}")
# 刷新脚本列表
self._refresh_scripts_list()
else:
self.add_error_message(f"脚本 {script_name} 创建失败")
else:
self.add_error_message("脚本管理器未初始化")
except Exception as e:
self.add_error_message(f"创建脚本失败: {str(e)}")
print(f"[脚本系统] 创建脚本失败: {e}")
def _refresh_scripts_list(self):
"""刷新脚本列表"""
try:
if hasattr(self, 'script_manager') and self.script_manager:
# 这里可以添加缓存逻辑,避免频繁刷新
available_scripts = self.script_manager.get_available_scripts()
print(f"[脚本系统] 刷新脚本列表: {len(available_scripts)} 个脚本")
self.add_success_message(f"脚本列表已刷新,共 {len(available_scripts)} 个脚本")
else:
self.add_error_message("脚本管理器未初始化")
except Exception as e:
self.add_error_message(f"刷新脚本列表失败: {str(e)}")
print(f"[脚本系统] 刷新脚本列表失败: {e}")
def _reload_all_scripts(self):
"""重载所有脚本"""
try:
if hasattr(self, 'script_manager') and self.script_manager:
# 获取所有可用脚本并逐个重载
available_scripts = self.script_manager.get_available_scripts()
success_count = 0
for script_name in available_scripts:
if self.script_manager.reload_script(script_name):
success_count += 1
self.add_success_message(f"重载完成: {success_count}/{len(available_scripts)} 个脚本成功")
print(f"[脚本系统] 重载脚本: {success_count}/{len(available_scripts)} 成功")
else:
self.add_error_message("脚本管理器未初始化")
except Exception as e:
self.add_error_message(f"重载脚本失败: {str(e)}")
print(f"[脚本系统] 重载脚本失败: {e}")
def _on_script_selected(self, script_name):
"""处理脚本选择事件"""
print(f"[脚本系统] 选择脚本: {script_name}")
self.add_info_message(f"已选择脚本: {script_name}")
def _edit_script(self, script_name):
"""编辑脚本"""
try:
if hasattr(self, 'script_manager') and self.script_manager:
# 获取脚本信息
script_info = self.script_manager.get_script_info(script_name)
if script_info and script_info.get("file"):
script_path = script_info["file"]
# 打开系统默认编辑器
import subprocess
import platform
system = platform.system()
try:
if system == "Windows":
subprocess.run(['notepad', script_path])
elif system == "Darwin": # macOS
subprocess.run(['open', script_path])
else: # Linux
subprocess.run(['xdg-open', script_path])
self.add_success_message(f"已打开脚本编辑器: {script_name}")
print(f"[脚本系统] 编辑脚本: {script_path}")
except Exception as e:
self.add_error_message(f"打开编辑器失败: {str(e)}")
else:
self.add_error_message(f"找不到脚本文件: {script_name}")
else:
self.add_error_message("脚本管理器未初始化")
except Exception as e:
self.add_error_message(f"编辑脚本失败: {str(e)}")
print(f"[脚本系统] 编辑脚本失败: {e}")
def _mount_script_to_selected(self, script_name):
"""挂载脚本到选中对象"""
selected_node = None
if hasattr(self, 'selection') and self.selection and hasattr(self.selection, 'selectedNode'):
selected_node = self.selection.selectedNode
if not selected_node or selected_node.isEmpty():
self.add_error_message("请先选择一个对象")
return
try:
if hasattr(self, 'script_manager') and self.script_manager:
script_component = self.script_manager.add_script_to_object(selected_node, script_name)
if script_component:
self.add_success_message(f"脚本 {script_name} 已挂载到 {selected_node.getName()}")
print(f"[脚本系统] 挂载脚本: {script_name} -> {selected_node.getName()}")
else:
self.add_error_message(f"挂载脚本 {script_name} 失败")
else:
self.add_error_message("脚本管理器未初始化")
except Exception as e:
self.add_error_message(f"挂载脚本失败: {str(e)}")
print(f"[脚本系统] 挂载脚本失败: {e}")
def _unmount_script_from_selected(self, script_name):
"""从选中对象卸载脚本"""
selected_node = None
if hasattr(self, 'selection') and self.selection and hasattr(self.selection, 'selectedNode'):
selected_node = self.selection.selectedNode
if not selected_node or selected_node.isEmpty():
self.add_error_message("请先选择一个对象")
return
try:
if hasattr(self, 'script_manager') and self.script_manager:
result = self.script_manager.remove_script_from_object(selected_node, script_name)
if result:
self.add_success_message(f"脚本 {script_name} 已从 {selected_node.getName()} 卸载")
print(f"[脚本系统] 卸载脚本: {script_name} <- {selected_node.getName()}")
else:
self.add_error_message(f"卸载脚本 {script_name} 失败")
else:
self.add_error_message("脚本管理器未初始化")
except Exception as e:
self.add_error_message(f"卸载脚本失败: {str(e)}")
print(f"[脚本系统] 卸载脚本失败: {e}")
# ==================== 菜单处理函数 ====================
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 processImGuiMouseClick(self, x, y):
"""处理ImGui鼠标点击事件返回是否消费了该事件"""
try:
# ImGui优先策略如果ImGui想要捕获鼠标则由ImGui处理
if hasattr(imgui, 'get_io') and imgui.get_io().want_capture_mouse:
return True
# 检查是否有任何ImGui窗口悬停
try:
if imgui.is_any_window_hovered():
return True
except AttributeError:
# 如果方法不存在,跳过这个检查
pass
# 检查鼠标是否在ImGui界面区域内
if self._is_mouse_over_imgui():
return True
# 如果以上条件都不满足则让3D场景处理该事件
return False
except Exception as e:
print(f"ImGui鼠标点击处理失败: {e}")
return False
# ==================== 消息系统 ====================
def add_message(self, text, color=(1.0, 1.0, 1.0, 1.0)):
"""添加消息到消息列表,同时输出到终端"""
import datetime
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
# 输出到终端
print(f"[{timestamp}] {text}")
# 添加到GUI消息列表
self.messages.append({
'text': text,
'color': color,
'timestamp': timestamp
})
# 限制消息数量
if len(self.messages) > self.max_messages:
self.messages = self.messages[-self.max_messages:]
def add_success_message(self, text):
"""添加成功消息"""
self.add_message(f"{text}", (0.176, 1.0, 0.769, 1.0))
def add_error_message(self, text):
"""添加错误消息"""
self.add_message(f"{text}", (1.0, 0.3, 0.3, 1.0))
def add_warning_message(self, text):
"""添加警告消息"""
self.add_message(f"{text}", (0.953, 0.616, 0.471, 1.0))
def add_info_message(self, text):
"""添加信息消息"""
self.add_message(f" {text}", (0.157, 0.620, 1.0, 1.0))
# ==================== 编辑菜单功能实现 ====================
def _on_undo(self):
"""处理撤销操作"""
try:
if hasattr(self, 'command_manager') and self.command_manager:
if self.command_manager.can_undo():
success = self.command_manager.undo()
if success:
self.add_success_message("撤销操作成功")
else:
self.add_error_message("撤销操作失败")
else:
self.add_warning_message("没有可撤销的操作")
else:
self.add_error_message("命令管理器未初始化")
except Exception as e:
self.add_error_message(f"撤销操作失败: {e}")
def _on_redo(self):
"""处理重做操作"""
try:
if hasattr(self, 'command_manager') and self.command_manager:
if self.command_manager.can_redo():
success = self.command_manager.redo()
if success:
self.add_success_message("重做操作成功")
else:
self.add_error_message("重做操作失败")
else:
self.add_warning_message("没有可重做的操作")
else:
self.add_error_message("命令管理器未初始化")
except Exception as e:
self.add_error_message(f"重做操作失败: {e}")
def _on_copy(self):
"""处理复制操作"""
try:
if not hasattr(self, 'selection') or not self.selection:
self.add_error_message("选择系统未初始化")
return
# 获取当前选中的节点
selected_node = self.selection.selectedNode
if not selected_node:
self.add_warning_message("没有选中的节点")
return
# 检查节点有效性(不能复制根节点)
if selected_node.getName() == "render":
self.add_warning_message("不能复制根节点")
return
# 序列化节点
if hasattr(self, 'scene_manager') and self.scene_manager:
node_data = self.scene_manager.serializeNodeForCopy(selected_node)
if node_data:
self.clipboard = [node_data]
self.clipboard_mode = "copy"
self.add_success_message(f"已复制节点: {selected_node.getName()}")
else:
self.add_error_message("节点序列化失败")
else:
self.add_error_message("场景管理器未初始化")
except Exception as e:
self.add_error_message(f"复制操作失败: {e}")
def _on_cut(self):
"""处理剪切操作"""
try:
if not hasattr(self, 'selection') or not self.selection:
self.add_error_message("选择系统未初始化")
return
# 获取当前选中的节点
selected_node = self.selection.selectedNode
if not selected_node:
self.add_warning_message("没有选中的节点")
return
# 检查节点有效性(不能剪切根节点和系统节点)
node_name = selected_node.getName()
if node_name == "render":
self.add_warning_message("不能剪切根节点")
return
# 序列化节点
if hasattr(self, 'scene_manager') and self.scene_manager:
node_data = self.scene_manager.serializeNodeForCopy(selected_node)
if node_data:
self.clipboard = [node_data]
self.clipboard_mode = "cut"
# 删除原节点
self._delete_node(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._delete_node(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 _delete_node(self, node):
"""删除节点的通用方法"""
try:
if not node or node.isEmpty():
return False
node_name = node.getName() or "未命名节点"
# 从场景管理器的模型列表中移除(如果是模型)
if hasattr(self, 'scene_manager') and self.scene_manager:
if node in self.scene_manager.models:
self.scene_manager.models.remove(node)
print(f"[场景管理器] 从模型列表移除: {node_name}")
# 停止所有与该节点相关的脚本
if hasattr(self, 'script_manager') and self.script_manager:
try:
# 移除该节点上的所有脚本
if node in self.script_manager.object_scripts:
del self.script_manager.object_scripts[node]
print(f"[脚本系统] 移除节点 {node_name} 的所有脚本")
except Exception as e:
print(f"[脚本系统] 移除脚本失败: {e}")
# 清理碰撞体
if hasattr(self, 'collision_manager') and self.collision_manager:
try:
self.collision_manager.remove_collision_for_node(node)
print(f"[碰撞系统] 移除节点 {node_name} 的碰撞体")
except Exception as e:
print(f"[碰撞系统] 移除碰撞体失败: {e}")
# 清理Actor缓存如果有动画
if hasattr(self, '_actor_cache') and node in self._actor_cache:
actor = self._actor_cache[node]
try:
# 清理相关任务
taskMgr.remove(f"maintain_anim_pos_{id(actor)}")
# 清理Actor
if not actor.isEmpty():
actor.cleanup()
actor.removeNode()
print(f"[动画系统] 清理节点 {node_name} 的Actor缓存")
except Exception as e:
print(f"[动画系统] 清理Actor缓存失败: {e}")
finally:
del self._actor_cache[node]
# 从父节点中移除
node.removeNode()
print(f"[删除] 成功删除节点: {node_name}")
return True
except Exception as e:
print(f"[删除] 删除节点失败: {e}")
return False
# ==================== 对话框绘制函数 ====================
def _draw_new_project_dialog(self):
"""绘制新建项目对话框"""
if not self.show_new_project_dialog:
return
# 初始化默认值
if not hasattr(self, 'new_project_name'):
self.new_project_name = "新项目"
if not hasattr(self, 'new_project_path'):
self.new_project_path = "./projects/"
# 设置对话框标志
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
# 获取屏幕尺寸,居中显示对话框
display_size = imgui.get_io().display_size
dialog_width = 400
dialog_height = 300
imgui.set_next_window_size((dialog_width, dialog_height))
imgui.set_next_window_pos(
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
)
with imgui_ctx.begin("新建项目", True, flags) as window:
if not window.opened:
self.show_new_project_dialog = False
return
imgui.text("创建新项目")
imgui.separator()
# 项目名称输入
changed, project_name = imgui.input_text("项目名称", self.new_project_name, 256)
if changed:
self.new_project_name = project_name
# 项目路径输入
changed, project_path = imgui.input_text("项目路径", self.new_project_path, 256)
if changed:
self.new_project_path = project_path
imgui.same_line()
if imgui.button("浏览..."):
self.path_browser_mode = "new_project"
self.path_browser_current_path = os.path.dirname(self.new_project_path) if self.new_project_path else os.getcwd()
self.show_path_browser = True
self._refresh_path_browser()
imgui.separator()
# 按钮区域
if imgui.button("创建"):
if self.new_project_name and self.new_project_path:
self._create_new_project(self.new_project_name, self.new_project_path)
self.show_new_project_dialog = False
imgui.same_line()
if imgui.button("取消"):
self.show_new_project_dialog = False
def _draw_open_project_dialog(self):
"""绘制打开项目对话框"""
if not self.show_open_project_dialog:
return
# 初始化默认值
if not hasattr(self, 'open_project_path'):
self.open_project_path = "./projects/"
# 设置对话框标志
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
# 获取屏幕尺寸,居中显示对话框
display_size = imgui.get_io().display_size
dialog_width = 500
dialog_height = 400
imgui.set_next_window_size((dialog_width, dialog_height))
imgui.set_next_window_pos(
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
)
with imgui_ctx.begin("打开项目", True, flags) as window:
if not window.opened:
self.show_open_project_dialog = False
return
imgui.text("选择项目")
imgui.separator()
imgui.text("项目路径:")
changed, project_path = imgui.input_text("##project_path", self.open_project_path, 512)
if changed:
self.open_project_path = project_path
imgui.same_line()
if imgui.button("浏览..."):
self.path_browser_mode = "open_project"
self.path_browser_current_path = self.open_project_path if self.open_project_path else os.getcwd()
self.show_path_browser = True
self._refresh_path_browser()
imgui.separator()
# 按钮区域
if imgui.button("打开"):
if self.open_project_path:
self._open_project_path(self.open_project_path)
self.show_open_project_dialog = False
imgui.same_line()
if imgui.button("取消"):
self.show_open_project_dialog = False
def _draw_path_browser(self):
"""绘制路径选择对话框"""
if not self.show_path_browser:
return
# 设置对话框标志
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
# 获取屏幕尺寸,居中显示对话框
display_size = imgui.get_io().display_size
dialog_width = 600
dialog_height = 500
imgui.set_next_window_size((dialog_width, dialog_height))
imgui.set_next_window_pos(
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
)
with imgui_ctx.begin("选择路径", True, flags) as window:
if not window.opened:
self.show_path_browser = False
return
imgui.text("选择路径")
imgui.separator()
# 当前路径显示
imgui.text("当前路径:")
imgui.same_line()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), self.path_browser_current_path)
imgui.separator()
# 路径导航按钮
if imgui.button("上级目录"):
parent_path = os.path.dirname(self.path_browser_current_path)
if parent_path != self.path_browser_current_path:
self.path_browser_current_path = parent_path
self._refresh_path_browser()
imgui.same_line()
if imgui.button("主目录"):
self.path_browser_current_path = os.path.expanduser("~")
self._refresh_path_browser()
imgui.same_line()
if imgui.button("当前目录"):
self.path_browser_current_path = os.getcwd()
self._refresh_path_browser()
imgui.separator()
# 文件和目录列表
if self.path_browser_items:
# 先显示目录
for item in self.path_browser_items:
if item['is_dir']:
# 尝试使用图标或文本标识目录
if self.icons.get('property_select_image'): # 使用现有图标作为文件夹图标
imgui.image(self.icons['property_select_image'], (16, 16))
imgui.same_line()
else:
imgui.text_colored((0.4, 0.6, 1.0, 1.0), ">")
imgui.same_line()
if imgui.selectable(item['name'], False)[0]:
self.path_browser_current_path = item['path']
self._refresh_path_browser()
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
self.path_browser_current_path = item['path']
self._refresh_path_browser()
# 显示文件(根据模式显示不同类型的文件)
if self.path_browser_mode == "open_project":
for item in self.path_browser_items:
if not item['is_dir'] and item['name'].endswith('.json'):
imgui.text_colored((1.0, 1.0, 0.7, 1.0), "[FILE]")
imgui.same_line()
if imgui.selectable(item['name'], False)[0]:
self.path_browser_selected_path = item['path']
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
# 选择包含project.json的目录
self.path_browser_current_path = os.path.dirname(item['path'])
self._apply_selected_path()
elif self.path_browser_mode == "import_model":
for item in self.path_browser_items:
if not item['is_dir']:
file_ext = os.path.splitext(item['name'])[1].lower()
# 根据文件类型显示不同颜色
if file_ext in ['.gltf', '.glb']:
color = (0.7, 1.0, 0.7, 1.0) # 绿色 - glTF
elif file_ext == '.fbx':
color = (1.0, 0.7, 0.7, 1.0) # 红色 - FBX
elif file_ext in ['.bam', '.egg']:
color = (0.7, 0.7, 1.0, 1.0) # 蓝色 - Panda3D
elif file_ext == '.obj':
color = (1.0, 1.0, 0.7, 1.0) # 黄色 - OBJ
else:
color = (0.8, 0.8, 0.8, 1.0) # 灰色 - 其他
imgui.text_colored(color, f"[{file_ext[1:].upper()}]")
imgui.same_line()
if imgui.selectable(item['name'], False)[0]:
self.path_browser_selected_path = item['path']
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
self.path_browser_selected_path = item['path']
self._apply_selected_path()
imgui.separator()
# 选中路径显示
if self.path_browser_selected_path:
imgui.text("选中路径:")
imgui.same_line()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), self.path_browser_selected_path)
# 按钮区域
if imgui.button("确定"):
self._apply_selected_path()
self.show_path_browser = False
imgui.same_line()
if imgui.button("取消"):
self.show_path_browser = False
def _draw_import_dialog(self):
"""绘制导入模型对话框"""
if not self.show_import_dialog:
return
# 设置对话框标志
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
# 获取屏幕尺寸,居中显示对话框
display_size = imgui.get_io().display_size
dialog_width = 600
dialog_height = 500
imgui.set_next_window_size((dialog_width, dialog_height))
imgui.set_next_window_pos(
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
)
with imgui_ctx.begin("导入模型", True, flags) as window:
if not window.opened:
self.show_import_dialog = False
return
imgui.text("选择要导入的模型文件")
imgui.separator()
# 文件路径输入
imgui.text("文件路径:")
changed, file_path = imgui.input_text("##import_file_path", self.import_file_path, 512)
if changed:
self.import_file_path = file_path
imgui.same_line()
if imgui.button("浏览..."):
self.path_browser_mode = "import_model"
self.path_browser_current_path = os.path.dirname(self.import_file_path) if self.import_file_path else os.getcwd()
self.show_path_browser = True
self._refresh_path_browser()
imgui.separator()
# 支持的格式说明
imgui.text("支持的文件格式:")
formats_text = ", ".join(self.supported_formats)
imgui.text_colored((0.7, 0.7, 0.7, 1.0), formats_text)
imgui.separator()
# 文件预览信息
if self.import_file_path and os.path.exists(self.import_file_path):
file_size = os.path.getsize(self.import_file_path)
imgui.text(f"文件大小: {file_size / 1024:.2f} KB")
file_ext = os.path.splitext(self.import_file_path)[1].lower()
if file_ext in self.supported_formats:
imgui.text_colored((0.176, 1.0, 0.769, 1.0), "✓ 文件格式支持")
else:
imgui.text_colored((1.0, 0.3, 0.3, 1.0), "✗ 不支持的文件格式")
else:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "请选择有效的文件路径")
imgui.separator()
# 按钮区域
can_import = (self.import_file_path and
os.path.exists(self.import_file_path) and
os.path.splitext(self.import_file_path)[1].lower() in self.supported_formats)
# 根据状态设置按钮颜色
if can_import:
if imgui.button("导入"):
self._import_model()
self.show_import_dialog = False
else:
# 禁用状态的按钮(灰色显示)
imgui.push_style_color(imgui.Col_.button, (0.3, 0.3, 0.3, 1.0))
imgui.button("导入")
imgui.pop_style_color()
imgui.same_line()
if imgui.button("取消"):
self.show_import_dialog = False
def _create_new_project(self, name, path):
"""创建新项目的实际实现"""
if not hasattr(self, 'project_manager') or not self.project_manager:
print("✗ 项目管理器未初始化")
return
try:
if self._create_new_project_impl(name, path):
print(f"✓ 项目创建成功: {name}")
else:
print(f"✗ 项目创建失败: {name}")
except Exception as e:
print(f"✗ 项目创建失败: {e}")
def _open_project_path(self, path):
"""打开项目的实际实现"""
if not hasattr(self, 'project_manager') or not self.project_manager:
print("✗ 项目管理器未初始化")
return
try:
print(f"打开项目: {path}")
if self._open_project_impl(path):
print(f"✓ 项目打开成功: {path}")
else:
print(f"✗ 项目打开失败: {path}")
except Exception as e:
print(f"✗ 项目打开失败: {e}")
# ==================== 项目管理具体实现 ====================
def _save_project_impl(self):
"""保存项目的具体实现不依赖Qt"""
import json
import datetime
import os
project_path = self.project_manager.current_project_path
scenes_path = os.path.join(project_path, "scenes")
# 固定的场景文件名
scene_file = os.path.join(scenes_path, "scene.bam")
# 如果存在旧文件,先删除
if os.path.exists(scene_file):
try:
os.remove(scene_file)
print(f"已删除旧场景文件: {scene_file}")
except Exception as e:
print(f"删除旧场景文件失败: {str(e)}")
return False
# 保存场景
if self.scene_manager.saveScene(scene_file, project_path):
# 更新项目配置文件
config_file = os.path.join(project_path, "project.json")
if os.path.exists(config_file):
with open(config_file, "r", encoding="utf-8") as f:
project_config = json.load(f)
# 更新最后修改时间
project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 记录场景文件路径
project_config["scene_file"] = os.path.relpath(scene_file, project_path)
with open(config_file, "w", encoding="utf-8") as f:
json.dump(project_config, f, ensure_ascii=False, indent=4)
# 更新项目配置
self.project_manager.project_config = project_config
return True
return False
def _open_project_impl(self, project_path):
"""打开项目的具体实现不依赖Qt"""
import json
import datetime
import os
# 检查是否是有效的项目文件夹
config_file = os.path.join(project_path, "project.json")
if not os.path.exists(config_file):
print(f"⚠ 选择的不是有效的项目文件夹: {project_path}")
return False
# 读取项目配置
with open(config_file, "r", encoding="utf-8") as f:
project_config = json.load(f)
# 检查场景文件
scene_file = os.path.join(project_path, "scenes", "scene.bam")
if os.path.exists(scene_file):
# 加载场景
if self.scene_manager.loadScene(scene_file):
# 更新项目配置
project_config["scene_file"] = os.path.relpath(scene_file, project_path)
project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(config_file, "w", encoding="utf-8") as f:
json.dump(project_config, f, ensure_ascii=False, indent=4)
# 更新项目状态
self.project_manager.current_project_path = project_path
self.project_manager.project_config = project_config
# 更新窗口标题
project_name = os.path.basename(project_path)
self._update_window_title(project_name)
return True
def _create_new_project_impl(self, name, path):
"""创建新项目的具体实现不依赖Qt"""
import json
import datetime
import os
full_project_path = os.path.normpath(os.path.join(path, name))
print(f"创建项目路径: {full_project_path}")
try:
# 创建项目文件夹结构
os.makedirs(full_project_path)
os.makedirs(os.path.join(full_project_path, "models")) # 模型文件夹
os.makedirs(os.path.join(full_project_path, "textures")) # 贴图文件夹
scenes_path = os.path.join(full_project_path, "scenes") # 场景文件夹
os.makedirs(scenes_path)
# 创建项目配置文件
project_config = {
"name": name,
"path": full_project_path,
"created": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"last_modified": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"version": "1.0",
"scene_file": "scenes/scene.bam"
}
# 保存项目配置
config_file = os.path.join(full_project_path, "project.json")
with open(config_file, "w", encoding="utf-8") as f:
json.dump(project_config, f, ensure_ascii=False, indent=4)
# 保存初始场景
scene_file = os.path.join(scenes_path, "scene.bam")
self.scene_manager.saveScene(scene_file, full_project_path)
# 更新项目管理器状态
self.project_manager.current_project_path = full_project_path
self.project_manager.project_config = project_config
# 更新窗口标题
self._update_window_title(name)
return True
except Exception as e:
print(f"创建项目失败: {e}")
return False
def _update_window_title(self, project_name):
"""更新窗口标题"""
try:
props = WindowProperties()
props.set_title(f"EG Engine - {project_name}")
self.win.request_properties(props)
print(f"窗口标题已更新: EG Engine - {project_name}")
except Exception as e:
print(f"更新窗口标题失败: {e}")
# ==================== 路径浏览器辅助方法 ====================
def _refresh_path_browser(self):
"""刷新路径浏览器内容"""
try:
self.path_browser_items = []
if not os.path.exists(self.path_browser_current_path):
self.add_error_message(f"路径不存在: {self.path_browser_current_path}")
return
# 获取目录中的所有项目
items = []
try:
for item_name in os.listdir(self.path_browser_current_path):
item_path = os.path.join(self.path_browser_current_path, item_name)
is_dir = os.path.isdir(item_path)
items.append({
'name': item_name,
'path': item_path,
'is_dir': is_dir
})
except PermissionError:
self.add_error_message(f"无法访问路径: {self.path_browser_current_path}")
return
# 根据模式过滤文件
if self.path_browser_mode == "import_model":
# 只显示支持的模型文件
filtered_items = []
for item in items:
if item['is_dir']:
filtered_items.append(item)
else:
file_ext = os.path.splitext(item['name'])[1].lower()
if file_ext in self.supported_formats:
filtered_items.append(item)
items = filtered_items
# 排序:目录在前,文件在后,按名称排序
items.sort(key=lambda x: (not x['is_dir'], x['name'].lower()))
self.path_browser_items = items
except Exception as e:
self.add_error_message(f"刷新路径浏览器失败: {e}")
def _apply_selected_path(self):
"""应用选择的路径"""
try:
if self.path_browser_mode == "new_project":
# 新建项目模式:直接使用当前路径
self.new_project_path = self.path_browser_current_path
self.add_info_message(f"已选择项目路径: {self.new_project_path}")
elif self.path_browser_mode == "open_project":
# 打开项目模式:使用当前路径
self.open_project_path = self.path_browser_current_path
self.add_info_message(f"已选择项目路径: {self.open_project_path}")
elif self.path_browser_mode == "import_model":
# 导入模型模式:使用选择的文件路径
self.import_file_path = self.path_browser_selected_path
self.add_info_message(f"已选择文件: {self.import_file_path}")
except Exception as e:
self.add_error_message(f"应用路径失败: {e}")
# ==================== 创建功能对话框实现 ====================
def _draw_gui_button_dialog(self):
"""绘制GUI按钮创建对话框"""
if not self.show_gui_button_dialog:
return
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
with imgui_ctx.begin("创建GUI按钮", self.show_gui_button_dialog, flags) as window:
if not window:
self.show_gui_button_dialog = False
return
# 初始化参数
if 'button_text' not in self.dialog_params:
self.dialog_params['button_text'] = "按钮"
if 'button_pos' not in self.dialog_params:
self.dialog_params['button_pos'] = [0.0, 0.0, 0.0]
if 'button_size' not in self.dialog_params:
self.dialog_params['button_size'] = [0.1, 0.1, 0.1]
imgui.text("GUI按钮参数设置")
imgui.separator()
# 文本输入
changed, self.dialog_params['button_text'] = imgui.input_text("按钮文本", self.dialog_params['button_text'], 256)
# 位置输入
changed, x = imgui.input_float("X坐标", self.dialog_params['button_pos'][0])
if changed:
self.dialog_params['button_pos'][0] = x
changed, y = imgui.input_float("Y坐标", self.dialog_params['button_pos'][1])
if changed:
self.dialog_params['button_pos'][1] = y
changed, z = imgui.input_float("Z坐标", self.dialog_params['button_pos'][2])
if changed:
self.dialog_params['button_pos'][2] = z
# 大小输入
changed, width = imgui.input_float("宽度", self.dialog_params['button_size'][0])
if changed:
self.dialog_params['button_size'][0] = width
changed, height = imgui.input_float("高度", self.dialog_params['button_size'][1])
if changed:
self.dialog_params['button_size'][1] = height
imgui.separator()
# 按钮
if imgui.button("创建"):
try:
pos = tuple(self.dialog_params['button_pos'])
text = self.dialog_params['button_text']
size = tuple(self.dialog_params['button_size'][:2])
result = self.createGUIButton(pos, text, size)
self.add_success_message(f"GUI按钮创建成功: {text}")
self.show_gui_button_dialog = False
except Exception as e:
self.add_error_message(f"创建GUI按钮失败: {str(e)}")
imgui.same_line()
if imgui.button("取消"):
self.show_gui_button_dialog = False
def _draw_gui_label_dialog(self):
"""绘制GUI标签创建对话框"""
if not self.show_gui_label_dialog:
return
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
with imgui_ctx.begin("创建GUI标签", self.show_gui_label_dialog, flags) as window:
if not window:
self.show_gui_label_dialog = False
return
# 初始化参数
if 'label_text' not in self.dialog_params:
self.dialog_params['label_text'] = "标签"
if 'label_pos' not in self.dialog_params:
self.dialog_params['label_pos'] = [0.0, 0.0, 0.0]
if 'label_size' not in self.dialog_params:
self.dialog_params['label_size'] = [0.1, 0.1, 0.1]
imgui.text("GUI标签参数设置")
imgui.separator()
# 文本输入
changed, self.dialog_params['label_text'] = imgui.input_text("标签文本", self.dialog_params['label_text'], 256)
# 位置输入
changed, x = imgui.input_float("X坐标", self.dialog_params['label_pos'][0])
if changed:
self.dialog_params['label_pos'][0] = x
changed, y = imgui.input_float("Y坐标", self.dialog_params['label_pos'][1])
if changed:
self.dialog_params['label_pos'][1] = y
changed, z = imgui.input_float("Z坐标", self.dialog_params['label_pos'][2])
if changed:
self.dialog_params['label_pos'][2] = z
# 大小输入
changed, width = imgui.input_float("宽度", self.dialog_params['label_size'][0])
if changed:
self.dialog_params['label_size'][0] = width
changed, height = imgui.input_float("高度", self.dialog_params['label_size'][1])
if changed:
self.dialog_params['label_size'][1] = height
imgui.separator()
# 按钮
if imgui.button("创建"):
try:
pos = tuple(self.dialog_params['label_pos'])
text = self.dialog_params['label_text']
size = tuple(self.dialog_params['label_size'][:2])
result = self.createGUILabel(pos, text, size)
self.add_success_message(f"GUI标签创建成功: {text}")
self.show_gui_label_dialog = False
except Exception as e:
self.add_error_message(f"创建GUI标签失败: {str(e)}")
imgui.same_line()
if imgui.button("取消"):
self.show_gui_label_dialog = False
def _draw_gui_entry_dialog(self):
"""绘制GUI输入框创建对话框"""
if not self.show_gui_entry_dialog:
return
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
with imgui_ctx.begin("创建GUI输入框", self.show_gui_entry_dialog, flags) as window:
if not window:
self.show_gui_entry_dialog = False
return
# 初始化参数
if 'entry_pos' not in self.dialog_params:
self.dialog_params['entry_pos'] = [0.0, 0.0, 0.0]
if 'entry_size' not in self.dialog_params:
self.dialog_params['entry_size'] = [0.2, 0.05, 0.1]
imgui.text("GUI输入框参数设置")
imgui.separator()
# 位置输入
changed, x = imgui.input_float("X坐标", self.dialog_params['entry_pos'][0])
if changed:
self.dialog_params['entry_pos'][0] = x
changed, y = imgui.input_float("Y坐标", self.dialog_params['entry_pos'][1])
if changed:
self.dialog_params['entry_pos'][1] = y
changed, z = imgui.input_float("Z坐标", self.dialog_params['entry_pos'][2])
if changed:
self.dialog_params['entry_pos'][2] = z
# 大小输入
changed, width = imgui.input_float("宽度", self.dialog_params['entry_size'][0])
if changed:
self.dialog_params['entry_size'][0] = width
changed, height = imgui.input_float("高度", self.dialog_params['entry_size'][1])
if changed:
self.dialog_params['entry_size'][1] = height
imgui.separator()
# 按钮
if imgui.button("创建"):
try:
pos = tuple(self.dialog_params['entry_pos'])
size = tuple(self.dialog_params['entry_size'][:2])
result = self.createGUIEntry(pos, size)
self.add_success_message("GUI输入框创建成功")
self.show_gui_entry_dialog = False
except Exception as e:
self.add_error_message(f"创建GUI输入框失败: {str(e)}")
imgui.same_line()
if imgui.button("取消"):
self.show_gui_entry_dialog = False
def _draw_gui_image_dialog(self):
"""绘制GUI图片创建对话框"""
if not self.show_gui_image_dialog:
return
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
with imgui_ctx.begin("创建GUI图片", self.show_gui_image_dialog, flags) as window:
if not window:
self.show_gui_image_dialog = False
return
# 初始化参数
if 'image_pos' not in self.dialog_params:
self.dialog_params['image_pos'] = [0.0, 0.0, 0.0]
if 'image_size' not in self.dialog_params:
self.dialog_params['image_size'] = [0.2, 0.2, 0.1]
if 'image_path' not in self.dialog_params:
self.dialog_params['image_path'] = ""
imgui.text("GUI图片参数设置")
imgui.separator()
# 位置输入
changed, x = imgui.input_float("X坐标", self.dialog_params['image_pos'][0])
if changed:
self.dialog_params['image_pos'][0] = x
changed, y = imgui.input_float("Y坐标", self.dialog_params['image_pos'][1])
if changed:
self.dialog_params['image_pos'][1] = y
changed, z = imgui.input_float("Z坐标", self.dialog_params['image_pos'][2])
if changed:
self.dialog_params['image_pos'][2] = z
# 大小输入
changed, width = imgui.input_float("宽度", self.dialog_params['image_size'][0])
if changed:
self.dialog_params['image_size'][0] = width
changed, height = imgui.input_float("高度", self.dialog_params['image_size'][1])
if changed:
self.dialog_params['image_size'][1] = height
# 图片路径
changed, self.dialog_params['image_path'] = imgui.input_text("图片路径", self.dialog_params['image_path'], 512)
imgui.separator()
# 按钮
if imgui.button("创建"):
try:
pos = tuple(self.dialog_params['image_pos'])
size = tuple(self.dialog_params['image_size'][:2])
image_path = self.dialog_params['image_path']
result = self.createGUIImage(pos, image_path, size)
self.add_success_message("GUI图片创建成功")
self.show_gui_image_dialog = False
except Exception as e:
self.add_error_message(f"创建GUI图片失败: {str(e)}")
imgui.same_line()
if imgui.button("取消"):
self.show_gui_image_dialog = False
def _draw_3d_text_dialog(self):
"""绘制3D文本创建对话框"""
if not self.show_3d_text_dialog:
return
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
with imgui_ctx.begin("创建3D文本", self.show_3d_text_dialog, flags) as window:
if not window:
self.show_3d_text_dialog = False
return
# 初始化参数
if 'text3d_text' not in self.dialog_params:
self.dialog_params['text3d_text'] = "3D文本"
if 'text3d_pos' not in self.dialog_params:
self.dialog_params['text3d_pos'] = [0.0, 0.0, 0.0]
if 'text3d_size' not in self.dialog_params:
self.dialog_params['text3d_size'] = 1.0
imgui.text("3D文本参数设置")
imgui.separator()
# 文本输入
changed, self.dialog_params['text3d_text'] = imgui.input_text("文本内容", self.dialog_params['text3d_text'], 256)
# 位置输入
changed, x = imgui.input_float("X坐标", self.dialog_params['text3d_pos'][0])
if changed:
self.dialog_params['text3d_pos'][0] = x
changed, y = imgui.input_float("Y坐标", self.dialog_params['text3d_pos'][1])
if changed:
self.dialog_params['text3d_pos'][1] = y
changed, z = imgui.input_float("Z坐标", self.dialog_params['text3d_pos'][2])
if changed:
self.dialog_params['text3d_pos'][2] = z
# 大小输入
changed, self.dialog_params['text3d_size'] = imgui.input_float("文本大小", self.dialog_params['text3d_size'])
imgui.separator()
# 按钮
if imgui.button("创建"):
try:
pos = tuple(self.dialog_params['text3d_pos'])
text = self.dialog_params['text3d_text']
size = self.dialog_params['text3d_size']
result = self.create3DText(pos, text, size)
self.add_success_message(f"3D文本创建成功: {text}")
self.show_3d_text_dialog = False
except Exception as e:
self.add_error_message(f"创建3D文本失败: {str(e)}")
imgui.same_line()
if imgui.button("取消"):
self.show_3d_text_dialog = False
def _draw_3d_image_dialog(self):
"""绘制3D图片创建对话框"""
if not self.show_3d_image_dialog:
return
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
with imgui_ctx.begin("创建3D图片", self.show_3d_image_dialog, flags) as window:
if not window:
self.show_3d_image_dialog = False
return
# 初始化参数
if 'image3d_pos' not in self.dialog_params:
self.dialog_params['image3d_pos'] = [0.0, 0.0, 0.0]
if 'image3d_size' not in self.dialog_params:
self.dialog_params['image3d_size'] = [1.0, 1.0, 1.0]
if 'image3d_path' not in self.dialog_params:
self.dialog_params['image3d_path'] = ""
imgui.text("3D图片参数设置")
imgui.separator()
# 位置输入
changed, x = imgui.input_float("X坐标", self.dialog_params['image3d_pos'][0])
if changed:
self.dialog_params['image3d_pos'][0] = x
changed, y = imgui.input_float("Y坐标", self.dialog_params['image3d_pos'][1])
if changed:
self.dialog_params['image3d_pos'][1] = y
changed, z = imgui.input_float("Z坐标", self.dialog_params['image3d_pos'][2])
if changed:
self.dialog_params['image3d_pos'][2] = z
# 大小输入
changed, width = imgui.input_float("宽度", self.dialog_params['image3d_size'][0])
if changed:
self.dialog_params['image3d_size'][0] = width
changed, height = imgui.input_float("高度", self.dialog_params['image3d_size'][1])
if changed:
self.dialog_params['image3d_size'][1] = height
# 图片路径
changed, self.dialog_params['image3d_path'] = imgui.input_text("图片路径", self.dialog_params['image3d_path'], 512)
imgui.separator()
# 按钮
if imgui.button("创建"):
try:
pos = tuple(self.dialog_params['image3d_pos'])
size = tuple(self.dialog_params['image3d_size'][:2])
image_path = self.dialog_params['image3d_path']
result = self.create3DImage(pos, image_path, size)
self.add_success_message("3D图片创建成功")
self.show_3d_image_dialog = False
except Exception as e:
self.add_error_message(f"创建3D图片失败: {str(e)}")
imgui.same_line()
if imgui.button("取消"):
self.show_3d_image_dialog = False
# 添加其他创建对话框的占位符方法
def _draw_video_screen_dialog(self):
"""绘制视频屏幕创建对话框"""
if not self.show_video_screen_dialog:
return
self.show_video_screen_dialog = False
self.add_info_message("视频屏幕创建功能开发中...")
def _draw_2d_video_screen_dialog(self):
"""绘制2D视频屏幕创建对话框"""
if not self.show_2d_video_screen_dialog:
return
self.show_2d_video_screen_dialog = False
self.add_info_message("2D视频屏幕创建功能开发中...")
def _draw_spherical_video_dialog(self):
"""绘制球形视频创建对话框"""
if not self.show_spherical_video_dialog:
return
self.show_spherical_video_dialog = False
self.add_info_message("球形视频创建功能开发中...")
def _draw_virtual_screen_dialog(self):
"""绘制虚拟屏幕创建对话框"""
if not self.show_virtual_screen_dialog:
return
self.show_virtual_screen_dialog = False
self.add_info_message("虚拟屏幕创建功能开发中...")
def _draw_spot_light_dialog(self):
"""绘制聚光灯创建对话框"""
if not self.show_spot_light_dialog:
return
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
with imgui_ctx.begin("创建聚光灯", self.show_spot_light_dialog, flags) as window:
if not window:
self.show_spot_light_dialog = False
return
# 初始化参数
if 'spotlight_pos' not in self.dialog_params:
self.dialog_params['spotlight_pos'] = [0.0, 0.0, 5.0]
if 'spotlight_color' not in self.dialog_params:
self.dialog_params['spotlight_color'] = [1.0, 1.0, 1.0]
if 'spotlight_intensity' not in self.dialog_params:
self.dialog_params['spotlight_intensity'] = 1.0
imgui.text("聚光灯参数设置")
imgui.separator()
# 位置输入
changed, x = imgui.input_float("X坐标", self.dialog_params['spotlight_pos'][0])
if changed:
self.dialog_params['spotlight_pos'][0] = x
changed, y = imgui.input_float("Y坐标", self.dialog_params['spotlight_pos'][1])
if changed:
self.dialog_params['spotlight_pos'][1] = y
changed, z = imgui.input_float("Z坐标", self.dialog_params['spotlight_pos'][2])
if changed:
self.dialog_params['spotlight_pos'][2] = z
# 颜色输入
changed, r = imgui.input_float("红色 (R)", self.dialog_params['spotlight_color'][0], 0.0, 1.0)
if changed:
self.dialog_params['spotlight_color'][0] = max(0.0, min(1.0, r))
changed, g = imgui.input_float("绿色 (G)", self.dialog_params['spotlight_color'][1], 0.0, 1.0)
if changed:
self.dialog_params['spotlight_color'][1] = max(0.0, min(1.0, g))
changed, b = imgui.input_float("蓝色 (B)", self.dialog_params['spotlight_color'][2], 0.0, 1.0)
if changed:
self.dialog_params['spotlight_color'][2] = max(0.0, min(1.0, b))
# 强度输入
changed, self.dialog_params['spotlight_intensity'] = imgui.input_float("强度", self.dialog_params['spotlight_intensity'], 0.1, 2.0)
if changed:
self.dialog_params['spotlight_intensity'] = max(0.1, self.dialog_params['spotlight_intensity'])
imgui.separator()
# 按钮
if imgui.button("创建"):
try:
pos = tuple(self.dialog_params['spotlight_pos'])
result = self.createSpotLight(pos)
if result:
# 设置颜色和强度
light = result.node()
if hasattr(light, 'setColor'):
color = tuple(self.dialog_params['spotlight_color'])
light.setColor(color + (1.0,)) # 添加alpha通道
if hasattr(light, 'setEnergy'):
light.setEnergy(self.dialog_params['spotlight_intensity'])
self.add_success_message("聚光灯创建成功")
self.show_spot_light_dialog = False
else:
self.add_error_message("聚光灯创建失败")
except Exception as e:
self.add_error_message(f"创建聚光灯失败: {str(e)}")
imgui.same_line()
if imgui.button("取消"):
self.show_spot_light_dialog = False
def _draw_point_light_dialog(self):
"""绘制点光源创建对话框"""
if not self.show_point_light_dialog:
return
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
with imgui_ctx.begin("创建点光源", self.show_point_light_dialog, flags) as window:
if not window:
self.show_point_light_dialog = False
return
# 初始化参数
if 'pointlight_pos' not in self.dialog_params:
self.dialog_params['pointlight_pos'] = [0.0, 0.0, 5.0]
if 'pointlight_color' not in self.dialog_params:
self.dialog_params['pointlight_color'] = [1.0, 1.0, 1.0]
if 'pointlight_intensity' not in self.dialog_params:
self.dialog_params['pointlight_intensity'] = 1.0
if 'pointlight_radius' not in self.dialog_params:
self.dialog_params['pointlight_radius'] = 10.0
imgui.text("点光源参数设置")
imgui.separator()
# 位置输入
changed, x = imgui.input_float("X坐标", self.dialog_params['pointlight_pos'][0])
if changed:
self.dialog_params['pointlight_pos'][0] = x
changed, y = imgui.input_float("Y坐标", self.dialog_params['pointlight_pos'][1])
if changed:
self.dialog_params['pointlight_pos'][1] = y
changed, z = imgui.input_float("Z坐标", self.dialog_params['pointlight_pos'][2])
if changed:
self.dialog_params['pointlight_pos'][2] = z
# 颜色输入
changed, r = imgui.input_float("红色 (R)", self.dialog_params['pointlight_color'][0], 0.0, 1.0)
if changed:
self.dialog_params['pointlight_color'][0] = max(0.0, min(1.0, r))
changed, g = imgui.input_float("绿色 (G)", self.dialog_params['pointlight_color'][1], 0.0, 1.0)
if changed:
self.dialog_params['pointlight_color'][1] = max(0.0, min(1.0, g))
changed, b = imgui.input_float("蓝色 (B)", self.dialog_params['pointlight_color'][2], 0.0, 1.0)
if changed:
self.dialog_params['pointlight_color'][2] = max(0.0, min(1.0, b))
# 强度输入
changed, self.dialog_params['pointlight_intensity'] = imgui.input_float("强度", self.dialog_params['pointlight_intensity'], 0.1, 2.0)
if changed:
self.dialog_params['pointlight_intensity'] = max(0.1, self.dialog_params['pointlight_intensity'])
# 半径输入
changed, self.dialog_params['pointlight_radius'] = imgui.input_float("影响半径", self.dialog_params['pointlight_radius'], 1.0, 50.0)
if changed:
self.dialog_params['pointlight_radius'] = max(1.0, self.dialog_params['pointlight_radius'])
imgui.separator()
# 按钮
if imgui.button("创建"):
try:
pos = tuple(self.dialog_params['pointlight_pos'])
result = self.createPointLight(pos)
if result:
# 设置颜色和强度
light = result.node()
if hasattr(light, 'setColor'):
color = tuple(self.dialog_params['pointlight_color'])
light.setColor(color + (1.0,)) # 添加alpha通道
if hasattr(light, 'setEnergy'):
light.setEnergy(self.dialog_params['pointlight_intensity'])
if hasattr(light, 'setAttenuation'):
# 设置衰减: (constant, linear, quadratic)
radius = self.dialog_params['pointlight_radius']
light.setAttenuation((1.0, 0.5/radius, 0.5/(radius*radius)))
self.add_success_message("点光源创建成功")
self.show_point_light_dialog = False
else:
self.add_error_message("点光源创建失败")
except Exception as e:
self.add_error_message(f"创建点光源失败: {str(e)}")
imgui.same_line()
if imgui.button("取消"):
self.show_point_light_dialog = False
def _draw_terrain_dialog(self):
"""绘制地形创建对话框"""
if not self.show_terrain_dialog:
return
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
with imgui_ctx.begin("创建平面地形", self.show_terrain_dialog, flags) as window:
if not window:
self.show_terrain_dialog = False
return
# 初始化参数
if 'terrain_width' not in self.dialog_params:
self.dialog_params['terrain_width'] = 10.0
if 'terrain_height' not in self.dialog_params:
self.dialog_params['terrain_height'] = 10.0
if 'terrain_resolution' not in self.dialog_params:
self.dialog_params['terrain_resolution'] = 129
imgui.text("平面地形参数设置")
imgui.separator()
# 尺寸输入
changed, self.dialog_params['terrain_width'] = imgui.input_float("宽度", self.dialog_params['terrain_width'], 1.0, 100.0)
if changed:
self.dialog_params['terrain_width'] = max(1.0, self.dialog_params['terrain_width'])
changed, self.dialog_params['terrain_height'] = imgui.input_float("高度", self.dialog_params['terrain_height'], 1.0, 100.0)
if changed:
self.dialog_params['terrain_height'] = max(1.0, self.dialog_params['terrain_height'])
# 分辨率输入
changed, self.dialog_params['terrain_resolution'] = imgui.input_int("分辨率", self.dialog_params['terrain_resolution'])
if changed:
# 确保分辨率是有效的 (2的幂次方 + 1)
valid_resolutions = [17, 33, 65, 129, 257, 513, 1025]
if self.dialog_params['terrain_resolution'] not in valid_resolutions:
closest_res = min(valid_resolutions, key=lambda x: abs(x - self.dialog_params['terrain_resolution']))
self.dialog_params['terrain_resolution'] = closest_res
imgui.separator()
imgui.text("有效分辨率值: 17, 33, 65, 129, 257, 513, 1025")
imgui.separator()
# 按钮
if imgui.button("创建"):
try:
width = self.dialog_params['terrain_width']
height = self.dialog_params['terrain_height']
resolution = self.dialog_params['terrain_resolution']
# 转换为地形管理器期望的格式
size = (width, height)
result = self.createFlatTerrain(size, resolution)
if result:
self.add_success_message("平面地形创建成功")
self.show_terrain_dialog = False
else:
self.add_error_message("平面地形创建失败")
except Exception as e:
self.add_error_message(f"创建平面地形失败: {str(e)}")
imgui.same_line()
if imgui.button("取消"):
self.show_terrain_dialog = False
def _draw_script_dialog(self):
"""绘制脚本创建对话框"""
if not self.show_script_dialog:
return
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
with imgui_ctx.begin("创建脚本", self.show_script_dialog, flags) as window:
if not window:
self.show_script_dialog = False
return
# 初始化参数
if 'script_name' not in self.dialog_params:
self.dialog_params['script_name'] = "new_script"
if 'script_template' not in self.dialog_params:
self.dialog_params['script_template'] = 0 # 0=basic, 1=movement
imgui.text("脚本参数设置")
imgui.separator()
# 脚本名称输入
changed, self.dialog_params['script_name'] = imgui.input_text("脚本名称", self.dialog_params['script_name'], 256)
# 模板选择
templates = ["基础模板", "移动模板"]
changed, self.dialog_params['script_template'] = imgui.combo("模板类型", self.dialog_params['script_template'], templates)
imgui.separator()
# 模板说明
if self.dialog_params['script_template'] == 0:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "基础模板: 包含基本的脚本结构")
else:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "移动模板: 包含移动相关的基本功能")
imgui.separator()
# 按钮
if imgui.button("创建"):
try:
script_name = self.dialog_params['script_name']
if not script_name:
self.add_error_message("请输入脚本名称")
return
template = "basic" if self.dialog_params['script_template'] == 0 else "movement"
result = self.createScript(script_name, template)
if result:
self.add_success_message(f"脚本创建成功: {script_name}")
# 如果启用了热重载,自动加载新脚本
if self.hotReloadEnabled:
try:
self.loadScript(result)
self.add_info_message(f"脚本已自动加载: {script_name}")
except Exception as e:
self.add_warning_message(f"脚本自动加载失败: {str(e)}")
self.show_script_dialog = False
else:
self.add_error_message("脚本创建失败")
except Exception as e:
self.add_error_message(f"创建脚本失败: {str(e)}")
imgui.same_line()
if imgui.button("取消"):
self.show_script_dialog = False
def _draw_script_browser(self):
"""绘制脚本文件浏览器"""
if not self.show_script_browser:
return
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
with imgui_ctx.begin("选择脚本文件", self.show_script_browser, flags) as window:
if not window:
self.show_script_browser = False
return
imgui.text("选择脚本文件")
imgui.separator()
# 导航按钮
if imgui.button("上级目录"):
parent_path = os.path.dirname(self.script_browser_current_path)
if parent_path != self.script_browser_current_path:
self.script_browser_current_path = parent_path
self._refresh_script_browser()
imgui.same_line()
if imgui.button("脚本目录"):
# 切换到脚本目录
if hasattr(self, 'script_manager') and self.script_manager:
scripts_dir = self.script_manager.scripts_directory
if os.path.exists(scripts_dir):
self.script_browser_current_path = scripts_dir
self._refresh_script_browser()
imgui.same_line()
if imgui.button("当前目录"):
self.script_browser_current_path = os.getcwd()
self._refresh_script_browser()
imgui.separator()
# 当前路径显示
imgui.text("当前路径:")
imgui.same_line()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), self.script_browser_current_path)
imgui.separator()
# 文件列表
if imgui.begin_child("script_file_list", (580, 300)):
for item in self.script_browser_items:
if item['is_dir']:
# 目录
imgui.text_colored((0.3, 0.8, 1.0, 1.0), f"📁 {item['name']}")
if imgui.is_item_clicked():
self.script_browser_current_path = item['path']
self._refresh_script_browser()
else:
# Python文件
imgui.text(f"📄 {item['name']}")
if imgui.is_item_clicked():
self.script_browser_selected_path = item['path']
imgui.end_child()
imgui.separator()
# 选中的文件信息
if self.script_browser_selected_path and os.path.exists(self.script_browser_selected_path):
file_size = os.path.getsize(self.script_browser_selected_path)
imgui.text(f"文件大小: {file_size / 1024:.2f} KB")
file_ext = os.path.splitext(self.script_browser_selected_path)[1].lower()
if file_ext == '.py':
imgui.text_colored((0.176, 1.0, 0.769, 1.0), "✓ Python脚本文件")
else:
imgui.text_colored((1.0, 0.3, 0.3, 1.0), "✗ 不是Python脚本文件")
else:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "请选择有效的Python脚本文件")
imgui.separator()
# 按钮
can_load = (self.script_browser_selected_path and
os.path.exists(self.script_browser_selected_path) and
os.path.splitext(self.script_browser_selected_path)[1].lower() == '.py')
if can_load:
if imgui.button("加载脚本"):
try:
result = self.loadScript(self.script_browser_selected_path)
if result:
script_name = os.path.basename(self.script_browser_selected_path)
self.add_success_message(f"脚本加载成功: {script_name}")
self.show_script_browser = False
else:
self.add_error_message("脚本加载失败")
except Exception as e:
self.add_error_message(f"加载脚本失败: {str(e)}")
else:
imgui.push_style_var(imgui.StyleVar_.alpha, 0.5)
imgui.button("加载脚本")
imgui.pop_style_var()
imgui.same_line()
if imgui.button("取消"):
self.show_script_browser = False
self.script_browser_selected_path = ""
def _refresh_script_browser(self):
"""刷新脚本浏览器内容"""
try:
self.script_browser_items = []
if not os.path.exists(self.script_browser_current_path):
self.script_browser_current_path = os.getcwd()
# 获取目录中的所有项目
items = []
try:
for item_name in os.listdir(self.script_browser_current_path):
item_path = os.path.join(self.script_browser_current_path, item_name)
if os.path.isdir(item_path):
items.append({
'name': item_name,
'path': item_path,
'is_dir': True
})
elif os.path.isfile(item_path):
file_ext = os.path.splitext(item_name)[1].lower()
if file_ext == '.py': # 只显示Python文件
items.append({
'name': item_name,
'path': item_path,
'is_dir': False
})
except PermissionError:
print(f"权限错误: 无法访问目录 {self.script_browser_current_path}")
# 排序:目录在前,文件在后
items.sort(key=lambda x: (not x['is_dir'], x['name'].lower()))
self.script_browser_items = items
except Exception as e:
print(f"刷新脚本浏览器时出错: {e}")
self.script_browser_items = []
def _draw_heightmap_browser(self):
"""绘制高度图文件浏览器"""
if not self.show_heightmap_browser:
return
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
with imgui_ctx.begin("选择高度图文件", self.show_heightmap_browser, flags) as window:
if not window:
self.show_heightmap_browser = False
return
imgui.text("选择高度图文件")
imgui.separator()
# 导航按钮
if imgui.button("上级目录"):
parent_path = os.path.dirname(self.heightmap_browser_current_path)
if parent_path != self.heightmap_browser_current_path:
self.heightmap_browser_current_path = parent_path
self._refresh_heightmap_browser()
imgui.same_line()
if imgui.button("主目录"):
self.heightmap_browser_current_path = os.getcwd()
self._refresh_heightmap_browser()
imgui.same_line()
if imgui.button("当前目录"):
self.heightmap_browser_current_path = os.getcwd()
self._refresh_heightmap_browser()
imgui.separator()
# 当前路径显示
imgui.text("当前路径:")
imgui.same_line()
imgui.text_colored((0.7, 0.7, 0.7, 1.0), self.heightmap_browser_current_path)
imgui.separator()
# 文件列表
if imgui.begin_child("file_list", (580, 300)):
for item in self.heightmap_browser_items:
if item['is_dir']:
# 目录
imgui.text_colored((0.3, 0.8, 1.0, 1.0), f"📁 {item['name']}")
if imgui.is_item_clicked():
self.heightmap_browser_current_path = item['path']
self._refresh_heightmap_browser()
else:
# 文件
imgui.text(f"📄 {item['name']}")
if imgui.is_item_clicked():
self.heightmap_browser_selected_path = item['path']
self.heightmap_file_path = item['path']
imgui.end_child()
imgui.separator()
# 支持的格式说明
imgui.text("支持的文件格式:")
formats_text = ", ".join(self.supported_heightmap_formats)
imgui.text_colored((0.7, 0.7, 0.7, 1.0), formats_text)
imgui.separator()
# 选中的文件信息
if self.heightmap_file_path and os.path.exists(self.heightmap_file_path):
file_size = os.path.getsize(self.heightmap_file_path)
imgui.text(f"文件大小: {file_size / 1024:.2f} KB")
file_ext = os.path.splitext(self.heightmap_file_path)[1].lower()
if file_ext in self.supported_heightmap_formats:
imgui.text_colored((0.176, 1.0, 0.769, 1.0), "✓ 文件格式支持")
else:
imgui.text_colored((1.0, 0.3, 0.3, 1.0), "✗ 不支持的文件格式")
else:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "请选择有效的高度图文件")
imgui.separator()
# 按钮
can_import = (self.heightmap_file_path and
os.path.exists(self.heightmap_file_path) and
os.path.splitext(self.heightmap_file_path)[1].lower() in self.supported_heightmap_formats)
if can_import:
if imgui.button("创建地形"):
try:
# 使用默认缩放参数创建地形
scale = (1.0, 1.0, 10.0) # X, Y, Z缩放
result = self.createTerrainFromHeightMap(self.heightmap_file_path, scale)
if result:
self.add_success_message("高度图地形创建成功")
self.show_heightmap_browser = False
else:
self.add_error_message("高度图地形创建失败")
except Exception as e:
self.add_error_message(f"创建高度图地形失败: {str(e)}")
else:
imgui.push_style_var(imgui.StyleVar_.alpha, 0.5)
imgui.button("创建地形")
imgui.pop_style_var()
imgui.same_line()
if imgui.button("取消"):
self.show_heightmap_browser = False
self.heightmap_file_path = ""
def _refresh_heightmap_browser(self):
"""刷新高度图浏览器内容"""
try:
self.heightmap_browser_items = []
if not os.path.exists(self.heightmap_browser_current_path):
self.heightmap_browser_current_path = os.getcwd()
# 获取目录中的所有项目
items = []
try:
for item_name in os.listdir(self.heightmap_browser_current_path):
item_path = os.path.join(self.heightmap_browser_current_path, item_name)
if os.path.isdir(item_path):
items.append({
'name': item_name,
'path': item_path,
'is_dir': True
})
elif os.path.isfile(item_path):
file_ext = os.path.splitext(item_name)[1].lower()
if file_ext in self.supported_heightmap_formats:
items.append({
'name': item_name,
'path': item_path,
'is_dir': False
})
except PermissionError:
print(f"权限错误: 无法访问目录 {self.heightmap_browser_current_path}")
# 排序:目录在前,文件在后
items.sort(key=lambda x: (not x['is_dir'], x['name'].lower()))
self.heightmap_browser_items = items
except Exception as e:
print(f"刷新高度图浏览器时出错: {e}")
self.heightmap_browser_items = []
# ==================== 导入功能实现 ====================
def _on_import_model(self):
"""处理导入模型菜单项"""
self.add_info_message("打开导入模型对话框")
self.show_import_dialog = True
def _import_model(self):
"""导入模型的具体实现"""
try:
if not self.import_file_path:
self.add_error_message("请选择要导入的文件")
return
if not os.path.exists(self.import_file_path):
self.add_error_message(f"文件不存在: {self.import_file_path}")
return
# 检查文件格式
file_ext = os.path.splitext(self.import_file_path)[1].lower()
if file_ext not in self.supported_formats:
self.add_error_message(f"不支持的文件格式: {file_ext}")
return
# 调用场景管理器导入模型
if hasattr(self, 'scene_manager') and self.scene_manager:
self.add_info_message(f"正在导入模型: {os.path.basename(self.import_file_path)}")
# 导入模型
model_node = self.scene_manager.importModel(self.import_file_path)
if model_node:
# 添加材质处理确保颜色正常
if hasattr(self.scene_manager, 'processMaterials'):
self.scene_manager.processMaterials(model_node)
self.add_info_message("已应用默认材质")
# 额外的材质处理,确保颜色正确显示
try:
# 强制刷新模型显示
model_node.clearMaterial()
model_node.clearTexture()
# 重新应用材质
if hasattr(self.scene_manager, 'processMaterials'):
self.scene_manager.processMaterials(model_node)
# 设置默认的基础颜色(如果模型没有颜色)
try:
color = model_node.getColor()
if color and len(color) >= 4 and color == (1, 1, 1, 1): # 默认白色
model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰
elif not color: # 如果没有颜色
model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰
except:
# 如果getColor失败直接设置默认颜色
model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰
except Exception as e:
self.add_warning_message(f"材质处理警告: {e}")
# 设置模型位置
model_node.setPos(0, 0, 0)
# 添加到场景管理器的模型列表
if hasattr(self.scene_manager, 'models'):
self.scene_manager.models.append(model_node)
# 选中新导入的模型
if hasattr(self, 'selection') and self.selection:
self.selection.selectNode(model_node)
self.add_success_message(f"模型导入成功: {os.path.basename(self.import_file_path)}")
else:
self.add_error_message("模型导入失败")
else:
self.add_error_message("场景管理器未初始化")
except Exception as e:
self.add_error_message(f"导入模型失败: {e}")
# 清空导入路径
self.import_file_path = ""
def setup_drag_drop_support(self):
"""设置拖拽支持"""
try:
# 启动拖拽监控线程
self.drag_drop_monitor = DragDropMonitor(self)
self.drag_drop_monitor.start()
print("✓ 拖拽监控已启动")
except Exception as e:
print(f"⚠ 拖拽监控启动失败: {e}")
def add_dragged_file(self, file_path):
"""添加拖拽的文件"""
if file_path not in self.dragged_files:
self.dragged_files.append(file_path)
self.is_dragging = True
self.show_drag_overlay = True
print(f"检测到拖拽文件: {file_path}")
def clear_dragged_files(self):
"""清空拖拽文件列表"""
self.dragged_files.clear()
self.is_dragging = False
self.show_drag_overlay = False
def process_dragged_files(self):
"""处理拖拽的文件"""
if not self.dragged_files:
return
imported_count = 0
for file_path in self.dragged_files:
if self._import_model_from_path(file_path):
imported_count += 1
if imported_count > 0:
self.add_message("success", f"成功导入 {imported_count} 个模型文件")
else:
self.add_message("error", "没有成功导入任何文件")
self.clear_dragged_files()
def _import_model_from_path(self, file_path):
"""从路径导入模型的内部方法"""
try:
# 检查文件是否存在
if not os.path.exists(file_path):
self.add_message("error", f"文件不存在: {file_path}")
return False
# 检查文件格式
supported_formats = ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']
file_ext = os.path.splitext(file_path)[1].lower()
if file_ext not in supported_formats:
self.add_message("error", f"不支持的文件格式: {file_ext}")
return False
# 导入模型
model_node = self.scene_manager.importModel(file_path)
if model_node:
# 应用材质确保颜色正常
self.scene_manager.processMaterials(model_node)
# 设置模型位置
model_node.setPos(0, 0, 0)
# 添加到选择系统
self.selection.select_node(model_node)
self.add_message("success", f"成功导入模型: {os.path.basename(file_path)}")
return True
else:
self.add_message("error", f"导入模型失败: {file_path}")
return False
except Exception as e:
self.add_message("error", f"导入模型时发生错误: {str(e)}")
return False
def _draw_drag_drop_interface(self):
"""绘制拖拽界面"""
# 检查资源管理器的拖拽状态
if self.resource_manager.is_dragging():
self.is_dragging = True
self.dragged_files = self.resource_manager.get_dragged_files()
self.show_drag_overlay = True
# 绘制拖拽覆盖层
if self.show_drag_overlay:
self._draw_drag_overlay()
# 检查是否有拖拽的文件需要处理
if self.is_dragging and self.dragged_files:
# 显示拖拽状态
self._draw_drag_status()
# 检查是否释放鼠标(结束拖拽)
if imgui.is_mouse_released(0):
self._handle_drag_drop_completion()
def _handle_drag_drop_completion(self):
"""处理拖拽完成"""
# 检查是否在3D视图中释放
mouse_pos = imgui.get_mouse_pos()
viewport = imgui.get_main_viewport()
# 简单检查如果不在任何ImGui窗口上则认为是在3D视图中
if not imgui.is_any_window_hovered():
# 导入支持的3D模型文件
imported_count = 0
for file_path in self.dragged_files:
if file_path.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']:
try:
self.scene_manager.importModel(str(file_path))
self.add_success_message(f"成功导入模型: {file_path.name}")
imported_count += 1
except Exception as e:
self.add_error_message(f"导入模型失败 {file_path.name}: {e}")
if imported_count > 0:
self.add_success_message(f"共导入 {imported_count} 个模型")
# 清除拖拽状态
self.is_dragging = False
self.dragged_files.clear()
self.show_drag_overlay = False
self.resource_manager.clear_drag()
def _draw_drag_overlay(self):
"""绘制拖拽覆盖层"""
viewport = imgui.get_main_viewport()
imgui.set_next_window_pos((0, 0))
imgui.set_next_window_size(viewport.work_size)
flags = (
imgui.WindowFlags_.no_title_bar |
imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_move |
imgui.WindowFlags_.no_scrollbar |
imgui.WindowFlags_.no_saved_settings |
imgui.WindowFlags_.no_background |
imgui.WindowFlags_.no_focus_on_appearing
)
imgui.begin("##DragOverlay", True, flags)
# 绘制半透明背景
draw_list = imgui.get_window_draw_list()
draw_list.add_rect_filled(
(0, 0), viewport.work_size,
imgui.get_color_u32((0, 0, 0, 0.1))
)
# 绘制提示文本
text_size = imgui.calc_text_size("释放以导入文件")
text_pos = (
(viewport.work_size.x - text_size.x) / 2,
(viewport.work_size.y - text_size.y) / 2
)
draw_list.add_text(
text_pos,
imgui.get_color_u32((1, 1, 1, 1)),
"释放以导入文件"
)
imgui.end()
def _draw_drag_status(self):
"""绘制拖拽状态"""
viewport = imgui.get_main_viewport()
# 在右下角显示拖拽状态
imgui.set_next_window_pos(
(viewport.work_size.x - 300, viewport.work_size.y - 150),
imgui.Cond_.first_use_ever
)
flags = (
imgui.WindowFlags_.no_title_bar |
imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_move |
imgui.WindowFlags_.no_scrollbar |
imgui.WindowFlags_.no_saved_settings
)
with imgui_ctx.begin("拖拽状态", True, flags):
imgui.text("拖拽的文件:")
for file_path in self.dragged_files:
filename = os.path.basename(file_path)
imgui.text(f"{filename}")
imgui.separator()
if imgui.button("导入所有文件"):
self.process_dragged_files()
imgui.same_line()
if imgui.button("取消"):
self.clear_dragged_files()
def _draw_context_menus(self):
"""绘制右键菜单"""
# 节点右键菜单
if hasattr(self, '_context_menu_node') and self._context_menu_node:
imgui.open_popup("节点右键菜单")
self._context_menu_node = None
if imgui.begin_popup("节点右键菜单"):
if imgui.menu_item("删除节点", "", False, True)[1]:
if hasattr(self, '_context_menu_target') and self._context_menu_target:
self._delete_node(self._context_menu_target)
imgui.close_current_popup()
if imgui.menu_item("重命名", "", False, True)[1]:
self._renaming_node = True
imgui.close_current_popup()
imgui.separator()
if imgui.menu_item("复制", "", False, True)[1]:
if hasattr(self, '_context_menu_target') and self._context_menu_target:
self._copy_node(self._context_menu_target)
imgui.close_current_popup()
if imgui.menu_item("聚焦", "", False, True)[1]:
if hasattr(self, '_context_menu_target') and self._context_menu_target:
if hasattr(self, 'selection') and self.selection:
self.selection.updateSelection(self._context_menu_target)
self.selection.focusCameraOnSelectedNodeAdvanced()
imgui.close_current_popup()
imgui.end_popup()
# 重命名对话框
if hasattr(self, '_renaming_node') and self._renaming_node:
imgui.open_popup("重命名节点")
if not hasattr(self, '_rename_buffer'):
self._rename_buffer = ""
if hasattr(self, '_context_menu_target') and self._context_menu_target:
self._rename_buffer = self._context_menu_target.getName() or ""
if imgui.begin_popup("重命名节点"):
changed, new_name = imgui.input_text("新名称", self._rename_buffer, 256)
if changed:
self._rename_buffer = new_name
if imgui.button("确定"):
if hasattr(self, '_context_menu_target') and self._context_menu_target:
self._context_menu_target.setName(self._rename_buffer)
self._renaming_node = False
imgui.close_current_popup()
imgui.same_line()
if imgui.button("取消"):
self._renaming_node = False
imgui.close_current_popup()
imgui.end_popup()
def _delete_node(self, node):
"""删除节点"""
if not node or node.isEmpty():
return
# 从场景管理器中删除
if hasattr(self, 'scene_manager') and self.scene_manager:
if hasattr(self.scene_manager, 'models') and node in self.scene_manager.models:
self.scene_manager.models.remove(node)
# 从GUI管理器中删除
if hasattr(self, 'gui_manager') and self.gui_manager:
gui_element = None
if hasattr(node, 'getPythonTag'):
gui_element = node.getPythonTag('gui_element')
if gui_element and hasattr(self.gui_manager, 'gui_elements'):
if gui_element in self.gui_manager.gui_elements:
self.gui_manager.gui_elements.remove(gui_element)
# 获取节点名称(在删除之前)
node_name = node.getName() if not node.isEmpty() else '未命名节点'
# 删除节点本身
node.removeNode()
# 清除选择
if hasattr(self, 'selection') and self.selection:
if self.selection.selectedNode == node:
self.selection.clearSelection()
# 添加成功消息
self.add_success_message(f"已删除节点: {node_name}")
def _copy_node(self, node):
"""复制节点"""
if not node or node.isEmpty():
return
# 这里可以实现节点复制逻辑
# 暂时只显示消息
self.add_info_message(f"复制功能暂未实现: {node.getName() or '未命名节点'}")
# ==================== 创建功能实现 ====================
def _on_create_empty_object(self):
"""创建空对象"""
try:
result = self.createEmptyObject()
if result:
self.add_success_message("空对象创建成功")
else:
self.add_error_message("空对象创建失败")
return result
except Exception as e:
self.add_error_message(f"创建空对象失败: {str(e)}")
def _on_create_cube(self):
"""创建立方体"""
try:
result = self.createCube()
if result:
self.add_success_message("立方体创建成功")
else:
self.add_error_message("立方体创建失败")
return result
except Exception as e:
self.add_error_message(f"创建立方体失败: {str(e)}")
def _on_create_sphere(self):
"""创建球体"""
try:
result = self.createSphere()
if result:
self.add_success_message("球体创建成功")
else:
self.add_error_message("球体创建失败")
return result
except Exception as e:
self.add_error_message(f"创建球体失败: {str(e)}")
def _on_create_cylinder(self):
"""创建圆柱体"""
try:
result = self.createCylinder()
if result:
self.add_success_message("圆柱体创建成功")
else:
self.add_error_message("圆柱体创建失败")
return result
except Exception as e:
self.add_error_message(f"创建圆柱体失败: {str(e)}")
def _on_create_plane(self):
"""创建平面"""
try:
result = self.createPlane()
if result:
self.add_success_message("平面创建成功")
else:
self.add_error_message("平面创建失败")
return result
except Exception as e:
self.add_error_message(f"创建平面失败: {str(e)}")
def _on_create_3d_text(self):
"""创建3D文本"""
self.show_3d_text_dialog = True
def _on_create_3d_image(self):
"""创建3D图片"""
self.show_3d_image_dialog = True
def _on_create_gui_button(self):
"""创建GUI按钮"""
self.show_gui_button_dialog = True
def _on_create_gui_label(self):
"""创建GUI标签"""
self.show_gui_label_dialog = True
def _on_create_gui_entry(self):
"""创建GUI输入框"""
self.show_gui_entry_dialog = True
def _on_create_gui_image(self):
"""创建GUI图片"""
self.show_gui_image_dialog = True
def _on_create_video_screen(self):
"""创建视频屏幕"""
self.show_video_screen_dialog = True
def _on_create_2d_video_screen(self):
"""创建2D视频屏幕"""
self.show_2d_video_screen_dialog = True
def _on_create_spherical_video(self):
"""创建球形视频"""
self.show_spherical_video_dialog = True
def _on_create_virtual_screen(self):
"""创建虚拟屏幕"""
self.show_virtual_screen_dialog = True
def _on_create_spot_light(self):
"""创建聚光灯"""
self.show_spot_light_dialog = True
def _on_create_point_light(self):
"""创建点光源"""
self.show_point_light_dialog = True
def _on_create_flat_terrain(self):
"""创建平面地形"""
self.show_terrain_dialog = True
def _on_create_heightmap_terrain(self):
"""从高度图创建地形"""
self.show_heightmap_browser = True
def _on_create_script(self):
"""创建脚本"""
self.show_script_dialog = True
def _on_load_script(self):
"""加载脚本文件"""
self.show_script_browser = True
def _on_reload_all_scripts(self):
"""重载所有脚本"""
try:
if hasattr(self, 'script_manager') and self.script_manager:
self.script_manager.reloadAllScripts()
self.add_success_message("所有脚本重载成功")
else:
self.add_error_message("脚本管理器未初始化")
except Exception as e:
self.add_error_message(f"重载脚本失败: {str(e)}")
def _on_open_scripts_manager(self):
"""打开脚本管理器"""
self.showScriptPanel = True
self.add_info_message("脚本管理器已打开")
def _on_create_2d_sample_panel(self):
"""创建2D示例面板"""
try:
result = self.create2DSamplePanel()
if result:
self.add_success_message("2D示例面板创建成功")
else:
self.add_error_message("2D示例面板创建失败")
except Exception as e:
self.add_error_message(f"创建2D示例面板失败: {str(e)}")
def _on_create_3d_sample_panel(self):
"""创建3D实例面板"""
try:
result = self.create3DSamplePanel()
if result:
self.add_success_message("3D实例面板创建成功")
else:
self.add_error_message("3D实例面板创建失败")
except Exception as e:
self.add_error_message(f"创建3D实例面板失败: {str(e)}")
def _on_create_web_panel(self):
"""创建Web面板"""
try:
result = self.createWebPanel()
if result:
self.add_success_message("Web面板创建成功")
else:
self.add_error_message("Web面板创建失败")
except Exception as e:
self.add_error_message(f"创建Web面板失败: {str(e)}")
# ==================== 3D对象和GUI创建方法 ====================
def createEmptyObject(self):
"""创建空对象"""
try:
from panda3d.core import NodePath
# 创建一个空节点
empty_node = NodePath("EmptyObject")
empty_node.reparentTo(self.render)
empty_node.setPos(0, 0, 0)
# 添加到场景管理器
if hasattr(self, 'scene_manager') and self.scene_manager:
self.scene_manager.models.append(empty_node)
# 更新场景树
if hasattr(self, 'updateSceneTree'):
self.updateSceneTree()
print("✓ 空对象创建成功")
return empty_node
except Exception as e:
print(f"✗ 创建空对象失败: {e}")
return None
def create3DText(self, pos=(0, 0, 0), text="3D Text", scale=1.0):
"""创建3D文本"""
try:
from panda3d.core import TextNode, NodePath
# 创建文本节点
text_node = TextNode("3DText")
text_node.setText(text)
text_node.setAlign(TextNode.ACenter)
text_node.setTextColor(1, 1, 1, 1) # 白色
# 设置中文字体
chinese_font = self._get_chinese_font()
if chinese_font:
text_node.setFont(chinese_font)
# 创建节点路径并设置位置
text_np = NodePath(text_node)
text_np.reparentTo(self.render)
text_np.setPos(pos)
text_np.setScale(scale)
# 添加到场景管理器
if hasattr(self, 'scene_manager') and self.scene_manager:
self.scene_manager.models.append(text_np)
# 更新场景树
if hasattr(self, 'updateSceneTree'):
self.updateSceneTree()
print(f"✓ 3D文本创建成功: {text}")
return text_np
except Exception as e:
print(f"✗ 创建3D文本失败: {e}")
return None
def create3DImage(self, pos=(0, 0, 0), image_path="", size=(1, 1)):
"""创建3D图片"""
try:
from panda3d.core import CardMaker, NodePath
if not image_path or not os.path.exists(image_path):
print("✗ 图片文件不存在")
return None
# 创建卡片几何体
card_maker = CardMaker("3DImage")
card_maker.setFrame(-size[0]/2, size[0]/2, -size[1]/2, size[1]/2)
card = card_maker.generate()
# 创建节点路径
image_np = NodePath(card)
image_np.reparentTo(self.render)
image_np.setPos(pos)
# 加载纹理
from panda3d.core import Texture, Filename
tex = self.loader.loadTexture(Filename.fromOsSpecific(image_path))
if tex:
image_np.setTexture(tex, 1)
else:
print("✗ 加载纹理失败")
image_np.removeNode()
return None
# 添加到场景管理器
if hasattr(self, 'scene_manager') and self.scene_manager:
self.scene_manager.models.append(image_np)
# 更新场景树
if hasattr(self, 'updateSceneTree'):
self.updateSceneTree()
print(f"✓ 3D图片创建成功: {os.path.basename(image_path)}")
return image_np
except Exception as e:
print(f"✗ 创建3D图片失败: {e}")
return None
def createCube(self, pos=(0, 0, 0), size=1.0):
"""创建立方体"""
try:
# 尝试使用Panda3D的内置几何体
from panda3d.core import NodePath, GeomNode, Geom, GeomVertexFormat, GeomVertexData, GeomVertexWriter, GeomTriangles, GeomPoints
from panda3d.core import Vec3, Vec4, RenderState, ShadeModelAttrib
# 创建顶点数据格式
format = GeomVertexFormat.getV3n3cpt2()
vdata = GeomVertexData('cube', format, Geom.UHStatic)
vdata.setNumRows(24)
vertex = GeomVertexWriter(vdata, 'vertex')
normal = GeomVertexWriter(vdata, 'normal')
color = GeomVertexWriter(vdata, 'color')
# 立方体的8个顶点
s = size / 2
vertices = [
(-s, -s, -s), (s, -s, -s), (s, s, -s), (-s, s, -s), # 底面
(-s, -s, s), (s, -s, s), (s, s, s), (-s, s, s) # 顶面
]
# 立方体的6个面每个面4个顶点
faces = [
(0, 1, 2, 3), # 底面
(4, 7, 6, 5), # 顶面
(0, 4, 5, 1), # 前面
(2, 6, 7, 3), # 后面
(0, 3, 7, 4), # 左面
(1, 5, 6, 2) # 右面
]
# 法线
normals = [
(0, 0, -1), (0, 0, 1), (0, -1, 0), (0, 1, 0), (-1, 0, 0), (1, 0, 0)
]
# 添加顶点数据
for face_idx, face in enumerate(faces):
n = normals[face_idx]
for vertex_idx in face:
v = vertices[vertex_idx]
vertex.addData3f(*v)
normal.addData3f(*n)
color.addData4f(0.8, 0.8, 0.8, 1.0) # 灰色
# 创建几何体
geom = Geom(vdata)
# 添加三角形
for face_idx in range(6):
base = face_idx * 4
# 每个面分成2个三角形
tri = GeomTriangles(Geom.UHStatic)
tri.addConsecutiveVertices(base, 3)
tri.closePrimitive()
tri2 = GeomTriangles(Geom.UHStatic)
tri2.addVertices(base + 2, base + 3, base)
tri2.closePrimitive()
geom.addPrimitive(tri)
geom.addPrimitive(tri2)
# 创建节点
geom_node = GeomNode('cube')
geom_node.addGeom(geom)
cube = NodePath(geom_node)
cube.reparentTo(self.render)
cube.setPos(pos)
# 设置渲染状态
state = RenderState.make(ShadeModelAttrib.make(ShadeModelAttrib.MSmooth))
cube.set_state(state)
# 添加到场景管理器
if hasattr(self, 'scene_manager') and self.scene_manager:
self.scene_manager.models.append(cube)
# 更新场景树
if hasattr(self, 'updateSceneTree'):
self.updateSceneTree()
print("✓ 立方体创建成功")
return cube
except Exception as e:
print(f"✗ 创建立方体失败: {e}")
return None
def createSphere(self, pos=(0, 0, 0), radius=1.0):
"""创建球体"""
try:
from panda3d.core import CardMaker, NodePath
# 创建一个简单的球体(使用多个卡片近似)
sphere_root = NodePath("Sphere")
# 创建球体的多个面来近似球形
num_segments = 6
for i in range(num_segments):
angle1 = (i / num_segments) * 360
# 创建球体片段
segment_maker = CardMaker(f"SphereSegment{i}")
segment_maker.setFrame(-radius, radius, -radius, radius)
segment = NodePath(segment_maker.generate())
segment.reparentTo(sphere_root)
# 设置位置和旋转来形成球体
segment.setPos(0, 0, 0)
segment.setH(angle1)
segment.setP(angle1/2) # 倾斜角度
segment.setScale(radius)
# 设置颜色
from panda3d.core import Vec4
sphere_root.setColor(Vec4(0.8, 0.6, 0.4, 1.0)) # 棕色
sphere_root.reparentTo(self.render)
sphere_root.setPos(pos)
# 添加到场景管理器
if hasattr(self, 'scene_manager') and self.scene_manager:
self.scene_manager.models.append(sphere_root)
# 更新场景树
if hasattr(self, 'updateSceneTree'):
self.updateSceneTree()
print("✓ 球体创建成功")
return sphere_root
except Exception as e:
print(f"✗ 创建球体失败: {e}")
return None
def createCylinder(self, pos=(0, 0, 0), radius=1.0, height=2.0):
"""创建圆柱体"""
try:
import math
from panda3d.core import CardMaker, NodePath
# 创建圆柱体
cylinder_root = NodePath("Cylinder")
# 创建圆柱体的多个侧面
num_segments = 12
for i in range(num_segments):
angle1 = (i / num_segments) * 360
angle2 = ((i + 1) / num_segments) * 360
# 计算圆柱体侧面的位置
x1 = radius * math.cos(math.radians(angle1))
y1 = radius * math.sin(math.radians(angle1))
# 创建圆柱体侧面
segment_maker = CardMaker(f"CylinderSegment{i}")
segment = NodePath(segment_maker.generate())
segment.reparentTo(cylinder_root)
# 设置位置和旋转
segment.setPos(x1, y1, 0)
segment.setH(angle1)
segment.setScale(0.5, height, 1) # 调整宽度和高度
# 设置颜色
from panda3d.core import Vec4
cylinder_root.setColor(Vec4(0.4, 0.8, 0.4, 1.0)) # 绿色
cylinder_root.reparentTo(self.render)
cylinder_root.setPos(pos)
# 添加到场景管理器
if hasattr(self, 'scene_manager') and self.scene_manager:
self.scene_manager.models.append(cylinder_root)
# 更新场景树
if hasattr(self, 'updateSceneTree'):
self.updateSceneTree()
print("✓ 圆柱体创建成功")
return cylinder_root
except Exception as e:
print(f"✗ 创建圆柱体失败: {e}")
return None
def createPlane(self, pos=(0, 0, 0), size=(1, 1)):
"""创建平面"""
try:
from panda3d.core import CardMaker, NodePath
# 创建平面
card_maker = CardMaker("Plane")
card_maker.setFrame(-size[0]/2, size[0]/2, -size[1]/2, size[1]/2)
plane = NodePath(card_maker.generate())
plane.reparentTo(self.render)
plane.setPos(pos)
# 添加到场景管理器
if hasattr(self, 'scene_manager') and self.scene_manager:
self.scene_manager.models.append(plane)
# 更新场景树
if hasattr(self, 'updateSceneTree'):
self.updateSceneTree()
print("✓ 平面创建成功")
return plane
except Exception as e:
print(f"✗ 创建平面失败: {e}")
return None
def create2DSamplePanel(self):
"""创建2D示例面板"""
try:
from core.InfoPanelManager import createSampleInfoPanel
result = createSampleInfoPanel(self.render)
return result
except Exception as e:
print(f"创建2D示例面板失败: {e}")
return None
def create3DSamplePanel(self):
"""创建3D实例面板"""
try:
if hasattr(self, 'info_panel_manager') and self.info_panel_manager:
# 创建3D信息面板
panel_id = f"3d_sample_{int(time.time())}"
result = self.info_panel_manager.create3DInfoPanel(
panel_id=panel_id,
position=(0, 0, 2),
size=(1.0, 0.6),
bg_color=(0.15, 0.25, 0.35, 0.95),
border_color=(0.3, 0.5, 0.7, 1.0),
title_color=(0.7, 0.9, 1.0, 1.0),
content_color=(0.95, 0.95, 0.95, 1.0)
)
# 添加示例内容
if result:
sample_data = {
"标题": "3D信息面板",
"状态": "运行中",
"创建时间": time.strftime("%Y-%m-%d %H:%M:%S"),
"位置": f"X:0, Y:0, Z:2"
}
self.info_panel_manager.updatePanelContent(panel_id, content=sample_data)
return result
return None
except Exception as e:
print(f"创建3D示例面板失败: {e}")
return None
def createWebPanel(self, url="https://www.example.com"):
"""创建Web面板"""
try:
if hasattr(self, 'info_panel_manager') and self.info_panel_manager:
panel_id = f"web_panel_{int(time.time())}"
result = self.info_panel_manager.createHTTPInfoPanel(
panel_id=panel_id,
url=url,
position=(0.8, 0.0),
size=(0.35, 0.4),
update_interval=5.0 # 每5秒更新一次
)
return result
return None
except Exception as e:
print(f"创建Web面板失败: {e}")
return None
# ==================== GUI创建方法 ====================
def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=0.1):
"""创建2D GUI按钮"""
try:
if hasattr(self, 'gui_manager') and self.gui_manager:
# 使用简化的创建方法不依赖QT树形控件
return self._create_simple_gui_button(pos, text, size)
return None
except Exception as e:
print(f"创建GUI按钮失败: {e}")
return None
def _create_simple_gui_button(self, pos=(0, 0, 0), text="按钮", size=0.1):
"""创建简单的GUI按钮不依赖QT树形控件"""
try:
from direct.gui.DirectGui import DirectButton
from panda3d.core import TextNode
# 转换坐标系统
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
# 设置中文字体
font = self._get_chinese_font()
# 处理scale参数
if isinstance(size, (list, tuple)) and len(size) >= 2:
scale_value = size[0] # 使用宽度作为缩放值
else:
scale_value = size
# 创建按钮
button = DirectButton(
text=text,
pos=gui_pos,
scale=scale_value,
text_font=font,
command=self._on_gui_button_click
)
# 创建包装对象
button_wrapper = type('GUIElement', (), {})()
button_wrapper.node = button
button_wrapper.name = text
button_wrapper.gui_type = "GUI_BUTTON"
button_wrapper.position = pos
button_wrapper.size = size
# 添加到GUI管理器
self.gui_manager.gui_elements.append(button_wrapper)
print(f"✓ GUI按钮创建成功: {text}")
return button_wrapper
except Exception as e:
print(f"✗ 创建简单GUI按钮失败: {e}")
return None
def _on_gui_button_click(self):
"""GUI按钮点击事件处理"""
print("GUI按钮被点击了")
def _get_chinese_font(self):
"""获取中文字体"""
try:
from panda3d.core import TextNode
import os
from pathlib import Path
# 尝试加载中文字体
font_paths = [
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", # Linux文泉驿微米黑
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", # Noto Sans CJK
"/System/Library/Fonts/PingFang.ttc", # macOS
"C:/Windows/Fonts/simhei.ttf", # Windows
"C:/Windows/Fonts/msyh.ttc" # Windows微软雅黑
]
for font_path in font_paths:
if Path(font_path).exists():
font = TextNode.getDefaultFont()
# 尝试加载字体
try:
font = self.loader.loadFont(font_path)
print(f"✓ 为GUI加载中文字体成功: {font_path}")
return font
except:
print(f"⚠️ 字体加载失败,尝试下一个: {font_path}")
continue
# 如果所有字体都加载失败,返回默认字体
print("⚠️ 无法加载中文字体,使用默认字体")
return TextNode.getDefaultFont()
except Exception as e:
print(f"⚠️ 获取中文字体失败: {e}")
from panda3d.core import TextNode
return TextNode.getDefaultFont()
def createGUILabel(self, pos=(0, 0, 0), text="标签", size=0.08):
"""创建2D GUI标签"""
try:
if hasattr(self, 'gui_manager') and self.gui_manager:
# 使用简化的创建方法不依赖QT树形控件
return self._create_simple_gui_label(pos, text, size)
return None
except Exception as e:
print(f"创建GUI标签失败: {e}")
return None
def _create_simple_gui_label(self, pos=(0, 0, 0), text="标签", size=0.08):
"""创建简单的GUI标签不依赖QT树形控件"""
try:
from direct.gui.DirectGui import DirectLabel
# 转换坐标系统
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
# 设置中文字体
font = self._get_chinese_font()
# 处理scale参数
if isinstance(size, (list, tuple)) and len(size) >= 2:
scale_value = size[0] # 使用宽度作为缩放值
else:
scale_value = size
# 创建标签
label = DirectLabel(
text=text,
pos=gui_pos,
scale=scale_value,
text_font=font,
text_fg=(1, 1, 1, 1) # 白色文字
)
# 创建包装对象
label_wrapper = type('GUIElement', (), {})()
label_wrapper.node = label
label_wrapper.name = text
label_wrapper.gui_type = "GUI_LABEL"
label_wrapper.position = pos
label_wrapper.size = size
# 添加到GUI管理器
self.gui_manager.gui_elements.append(label_wrapper)
print(f"✓ GUI标签创建成功: {text}")
return label_wrapper
except Exception as e:
print(f"✗ 创建简单GUI标签失败: {e}")
return None
def createGUIEntry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08):
"""创建2D GUI文本输入框"""
try:
if hasattr(self, 'gui_manager') and self.gui_manager:
# 使用简化的创建方法不依赖QT树形控件
return self._create_simple_gui_entry(pos, placeholder, size)
return None
except Exception as e:
print(f"创建GUI输入框失败: {e}")
return None
def _create_simple_gui_entry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08):
"""创建简单的GUI输入框不依赖QT树形控件"""
try:
from direct.gui.DirectGui import DirectEntry
# 转换坐标系统
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
# 设置中文字体
font = self._get_chinese_font()
# 处理scale参数
if isinstance(size, (list, tuple)) and len(size) >= 2:
scale_value = size[0] # 使用宽度作为缩放值
else:
scale_value = size
# 创建输入框
entry = DirectEntry(
text=placeholder,
pos=gui_pos,
scale=scale_value,
text_font=font,
width=20, # 字符宽度
numLines=1, # 行数
focus=1 # 自动获取焦点
)
# 创建包装对象
entry_wrapper = type('GUIElement', (), {})()
entry_wrapper.node = entry
entry_wrapper.name = placeholder
entry_wrapper.gui_type = "GUI_ENTRY"
entry_wrapper.position = pos
entry_wrapper.size = size
# 添加到GUI管理器
self.gui_manager.gui_elements.append(entry_wrapper)
print(f"✓ GUI输入框创建成功: {placeholder}")
return entry_wrapper
except Exception as e:
print(f"✗ 创建简单GUI输入框失败: {e}")
return None
def createGUIImage(self, pos=(0, 0, 0), image_path=None, size=2):
"""创建2D GUI图片"""
try:
if hasattr(self, 'gui_manager') and self.gui_manager:
# 使用简化的创建方法不依赖QT树形控件
return self._create_simple_gui_image(pos, image_path, size)
return None
except Exception as e:
print(f"创建GUI图片失败: {e}")
return None
def _create_simple_gui_image(self, pos=(0, 0, 0), image_path=None, size=2):
"""创建简单的GUI图片不依赖QT树形控件"""
try:
from direct.gui.DirectGui import DirectFrame
from panda3d.core import Filename
# 转换坐标系统
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
# 处理scale参数
if isinstance(size, (list, tuple)) and len(size) >= 2:
scale_value = size[0] # 使用宽度作为缩放值
else:
scale_value = size
# 创建图片框架
if image_path and os.path.exists(image_path):
# 加载纹理
tex = self.loader.loadTexture(Filename.fromOsSpecific(image_path))
image = DirectFrame(
image=tex,
pos=gui_pos,
scale=scale_value
)
image_name = os.path.basename(image_path)
else:
# 创建一个彩色框架作为占位符
image = DirectFrame(
frameColor=(0.5, 0.5, 0.5, 1.0), # 灰色
frameSize=(-scale_value, scale_value, -scale_value, scale_value),
pos=gui_pos
)
image_name = "占位符图片"
# 创建包装对象
image_wrapper = type('GUIElement', (), {})()
image_wrapper.node = image
image_wrapper.name = image_name
image_wrapper.gui_type = "GUI_IMAGE"
image_wrapper.position = pos
image_wrapper.size = size
image_wrapper.image_path = image_path
# 添加到GUI管理器
self.gui_manager.gui_elements.append(image_wrapper)
print(f"✓ GUI图片创建成功: {image_name}")
return image_wrapper
except Exception as e:
print(f"✗ 创建简单GUI图片失败: {e}")
return None
def createVideoScreen(self, pos=(0, 0, 0), size=1, video_path=None):
"""创建视频屏幕"""
try:
if hasattr(self, 'gui_manager') and self.gui_manager:
return self.gui_manager.createVideoScreen(pos, size, video_path)
return None
except Exception as e:
print(f"创建视频屏幕失败: {e}")
return None
def create2DVideoScreen(self, pos=(0, 0, 0), size=0.2, video_path=None):
"""创建2D视频屏幕"""
try:
if hasattr(self, 'gui_manager') and self.gui_manager:
return self.gui_manager.createGUI2DVideoScreen(pos, size, video_path)
return None
except Exception as e:
print(f"创建2D视频屏幕失败: {e}")
return None
def createSphericalVideo(self, pos=(0, 0, 0), radius=5.0, video_path=None):
"""创建360度视频"""
try:
if hasattr(self, 'gui_manager') and self.gui_manager:
return self.gui_manager.createSphericalVideo(pos, radius, video_path)
return None
except Exception as e:
print(f"创建球形视频失败: {e}")
return None
def createVirtualScreen(self, pos=(0, 0, 0), size=(2, 1), text="虚拟屏幕"):
"""创建虚拟屏幕"""
try:
if hasattr(self, 'gui_manager') and self.gui_manager:
return self.gui_manager.createGUIVirtualScreen(pos, size, text)
return None
except Exception as e:
print(f"创建虚拟屏幕失败: {e}")
return None
# ==================== 光源创建方法 ====================
def createSpotLight(self, pos=(0, 0, 5)):
"""创建聚光灯"""
try:
if hasattr(self, 'scene_manager') and self.scene_manager:
return self.scene_manager.createSpotLight(pos)
return None
except Exception as e:
print(f"创建聚光灯失败: {e}")
return None
def createPointLight(self, pos=(0, 0, 5)):
"""创建点光源"""
try:
if hasattr(self, 'scene_manager') and self.scene_manager:
return self.scene_manager.createPointLight(pos)
return None
except Exception as e:
print(f"创建点光源失败: {e}")
return None
# ==================== 地形创建方法 ====================
def createFlatTerrain(self, size=(10, 10), resolution=129):
"""创建平面地形"""
try:
if hasattr(self, 'terrain_manager') and self.terrain_manager:
return self.terrain_manager.createFlatTerrain(size, resolution)
return None
except Exception as e:
print(f"创建平面地形失败: {e}")
return None
def createTerrainFromHeightMap(self, heightmap_path, scale=(1.0, 1.0, 10.0)):
"""从高度图创建地形"""
try:
if hasattr(self, 'terrain_manager') and self.terrain_manager:
return self.terrain_manager.createTerrainFromHeightMap(heightmap_path, scale)
return None
except Exception as e:
print(f"创建高度图地形失败: {e}")
return None
# ==================== 脚本创建方法 ====================
def createScript(self, script_name, template="basic"):
"""创建脚本"""
try:
if hasattr(self, 'script_manager') and self.script_manager:
return self.script_manager.createScript(script_name, template)
return None
except Exception as e:
print(f"创建脚本失败: {e}")
return None
def loadScript(self, script_path):
"""加载脚本"""
try:
if hasattr(self, 'script_manager') and self.script_manager:
return self.script_manager.loadScript(script_path)
return None
except Exception as e:
print(f"加载脚本失败: {e}")
return None
demo = MyWorld()
demo.run()