8428 lines
350 KiB
Python
8428 lines
350 KiB
Python
from panda3d.core import loadPrcFileData, WindowProperties, Point3
|
||
from math import pi, sin, cos
|
||
|
||
from direct.showbase.ShowBase import ShowBase
|
||
from direct.task import Task
|
||
from direct.actor.Actor import Actor
|
||
from direct.interval.IntervalGlobal import Sequence
|
||
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
|
||
|
||
# VR调试相关变量
|
||
self.vr_debug_enabled = False
|
||
self.vr_detailed_mode = True
|
||
self.vr_performance_monitor = False
|
||
|
||
# 调试选项
|
||
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('control-z', self._on_undo)
|
||
self.accept('control-y', self._on_redo)
|
||
self.accept('control-x', self._on_cut)
|
||
self.accept('control-c', self._on_copy)
|
||
self.accept('control-v', self._on_paste)
|
||
self.accept('delete', self._on_delete_pressed)
|
||
|
||
# 滚轮事件
|
||
self.accept('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_import_model()
|
||
|
||
imgui.separator()
|
||
|
||
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.tool_manager.setCurrentTool("选择")
|
||
if imgui.menu_item("移动工具", "", False, True)[1]:
|
||
self.tool_manager.setCurrentTool("移动")
|
||
if imgui.menu_item("旋转工具", "", False, True)[1]:
|
||
self.tool_manager.setCurrentTool("旋转")
|
||
if imgui.menu_item("缩放工具", "", False, True)[1]:
|
||
self.tool_manager.setCurrentTool("缩放")
|
||
|
||
imgui.separator()
|
||
|
||
# 编辑工具
|
||
if imgui.menu_item("光照编辑", "", False, True)[1]:
|
||
self.tool_manager.setCurrentTool("光照编辑")
|
||
if imgui.menu_item("图形编辑", "", False, True)[1]:
|
||
self.tool_manager.setCurrentTool("图形编辑")
|
||
|
||
imgui.separator()
|
||
|
||
# VR子菜单
|
||
with imgui_ctx.begin_menu("VR") as vr_menu:
|
||
if vr_menu:
|
||
if imgui.menu_item("进入VR模式", "", False, True)[1]:
|
||
self._toggle_vr_mode()
|
||
if imgui.menu_item("退出VR模式", "", False, True)[1]:
|
||
self._exit_vr_mode()
|
||
|
||
imgui.separator()
|
||
|
||
if imgui.menu_item("VR状态", "", False, True)[1]:
|
||
self._show_vr_status()
|
||
if imgui.menu_item("VR设置", "", False, True)[1]:
|
||
self._show_vr_settings()
|
||
|
||
imgui.separator()
|
||
|
||
# VR调试子菜单
|
||
with imgui_ctx.begin_menu("VR调试") as vr_debug_menu:
|
||
if vr_debug_menu:
|
||
_, self.vr_debug_enabled = imgui.menu_item("启用调试输出", "", self.vr_debug_enabled, True)
|
||
|
||
if imgui.menu_item("立即显示性能报告", "", False, True)[1]:
|
||
self._show_vr_performance_report()
|
||
|
||
imgui.separator()
|
||
|
||
# 输出模式
|
||
with imgui_ctx.begin_menu("输出模式") as output_menu:
|
||
if output_menu:
|
||
if imgui.menu_item("简短模式", "", not self.vr_detailed_mode, True)[1]:
|
||
self.vr_detailed_mode = False
|
||
if imgui.menu_item("详细模式", "", self.vr_detailed_mode, True)[1]:
|
||
self.vr_detailed_mode = True
|
||
|
||
imgui.separator()
|
||
|
||
_, self.vr_performance_monitor = imgui.menu_item("启用性能监控", "", self.vr_performance_monitor, 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_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 "未命名节点"
|
||
parent = node.getParent()
|
||
|
||
# 创建删除命令
|
||
if hasattr(self, 'command_manager') and self.command_manager:
|
||
from core.Command_System import DeleteNodeCommand
|
||
command = DeleteNodeCommand(node, parent, self)
|
||
self.command_manager.execute_command(command)
|
||
print(f"[命令系统] 创建删除命令: {node_name}")
|
||
else:
|
||
# 备用方案:直接删除并执行清理
|
||
print(f"[删除] 命令管理器不可用,直接删除节点: {node_name}")
|
||
self._perform_node_cleanup(node)
|
||
node.removeNode()
|
||
|
||
print(f"[删除] 成功删除节点: {node_name}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"[删除] 删除节点失败: {e}")
|
||
return False
|
||
|
||
def _perform_node_cleanup(self, node):
|
||
"""执行节点清理逻辑"""
|
||
try:
|
||
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]
|
||
|
||
except Exception as e:
|
||
print(f"[清理] 节点清理失败: {e}")
|
||
|
||
# ==================== 对话框绘制函数 ====================
|
||
|
||
def _draw_new_project_dialog(self):
|
||
"""绘制新建项目对话框"""
|
||
if not self.show_new_project_dialog:
|
||
return
|
||
|
||
# 初始化默认值
|
||
if not hasattr(self, 'new_project_name'):
|
||
self.new_project_name = "新项目"
|
||
if not hasattr(self, 'new_project_path'):
|
||
self.new_project_path = "./projects/"
|
||
|
||
# 设置对话框标志
|
||
flags = (imgui.WindowFlags_.no_resize |
|
||
imgui.WindowFlags_.no_collapse |
|
||
imgui.WindowFlags_.modal)
|
||
|
||
# 获取屏幕尺寸,居中显示对话框
|
||
display_size = imgui.get_io().display_size
|
||
dialog_width = 400
|
||
dialog_height = 300
|
||
imgui.set_next_window_size((dialog_width, dialog_height))
|
||
imgui.set_next_window_pos(
|
||
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
|
||
)
|
||
|
||
with imgui_ctx.begin("新建项目", True, flags) as window:
|
||
if not window.opened:
|
||
self.show_new_project_dialog = False
|
||
return
|
||
|
||
imgui.text("创建新项目")
|
||
imgui.separator()
|
||
|
||
# 项目名称输入
|
||
changed, project_name = imgui.input_text("项目名称", self.new_project_name, 256)
|
||
if changed:
|
||
self.new_project_name = project_name
|
||
|
||
# 项目路径输入
|
||
changed, project_path = imgui.input_text("项目路径", self.new_project_path, 256)
|
||
if changed:
|
||
self.new_project_path = project_path
|
||
|
||
imgui.same_line()
|
||
if imgui.button("浏览..."):
|
||
self.path_browser_mode = "new_project"
|
||
self.path_browser_current_path = os.path.dirname(self.new_project_path) if self.new_project_path else os.getcwd()
|
||
self.show_path_browser = True
|
||
self._refresh_path_browser()
|
||
|
||
imgui.separator()
|
||
|
||
# 按钮区域
|
||
if imgui.button("创建"):
|
||
if self.new_project_name and self.new_project_path:
|
||
self._create_new_project(self.new_project_name, self.new_project_path)
|
||
self.show_new_project_dialog = False
|
||
|
||
imgui.same_line()
|
||
if imgui.button("取消"):
|
||
self.show_new_project_dialog = False
|
||
|
||
def _draw_open_project_dialog(self):
|
||
"""绘制打开项目对话框"""
|
||
if not self.show_open_project_dialog:
|
||
return
|
||
|
||
# 初始化默认值
|
||
if not hasattr(self, 'open_project_path'):
|
||
self.open_project_path = "./projects/"
|
||
|
||
# 设置对话框标志
|
||
flags = (imgui.WindowFlags_.no_resize |
|
||
imgui.WindowFlags_.no_collapse |
|
||
imgui.WindowFlags_.modal)
|
||
|
||
# 获取屏幕尺寸,居中显示对话框
|
||
display_size = imgui.get_io().display_size
|
||
dialog_width = 500
|
||
dialog_height = 400
|
||
imgui.set_next_window_size((dialog_width, dialog_height))
|
||
imgui.set_next_window_pos(
|
||
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
|
||
)
|
||
|
||
with imgui_ctx.begin("打开项目", True, flags) as window:
|
||
if not window.opened:
|
||
self.show_open_project_dialog = False
|
||
return
|
||
|
||
imgui.text("选择项目")
|
||
imgui.separator()
|
||
|
||
imgui.text("项目路径:")
|
||
changed, project_path = imgui.input_text("##project_path", self.open_project_path, 512)
|
||
if changed:
|
||
self.open_project_path = project_path
|
||
|
||
imgui.same_line()
|
||
if imgui.button("浏览..."):
|
||
self.path_browser_mode = "open_project"
|
||
self.path_browser_current_path = self.open_project_path if self.open_project_path else os.getcwd()
|
||
self.show_path_browser = True
|
||
self._refresh_path_browser()
|
||
|
||
imgui.separator()
|
||
|
||
# 按钮区域
|
||
if imgui.button("打开"):
|
||
if self.open_project_path:
|
||
self._open_project_path(self.open_project_path)
|
||
self.show_open_project_dialog = False
|
||
|
||
imgui.same_line()
|
||
if imgui.button("取消"):
|
||
self.show_open_project_dialog = False
|
||
|
||
def _draw_path_browser(self):
|
||
"""绘制路径选择对话框"""
|
||
if not self.show_path_browser:
|
||
return
|
||
|
||
# 设置对话框标志
|
||
flags = (imgui.WindowFlags_.no_resize |
|
||
imgui.WindowFlags_.no_collapse |
|
||
imgui.WindowFlags_.modal)
|
||
|
||
# 获取屏幕尺寸,居中显示对话框
|
||
display_size = imgui.get_io().display_size
|
||
dialog_width = 600
|
||
dialog_height = 500
|
||
imgui.set_next_window_size((dialog_width, dialog_height))
|
||
imgui.set_next_window_pos(
|
||
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
|
||
)
|
||
|
||
with imgui_ctx.begin("选择路径", True, flags) as window:
|
||
if not window.opened:
|
||
self.show_path_browser = False
|
||
return
|
||
|
||
imgui.text("选择路径")
|
||
imgui.separator()
|
||
|
||
# 当前路径显示
|
||
imgui.text("当前路径:")
|
||
imgui.same_line()
|
||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), self.path_browser_current_path)
|
||
|
||
imgui.separator()
|
||
|
||
# 路径导航按钮
|
||
if imgui.button("上级目录"):
|
||
parent_path = os.path.dirname(self.path_browser_current_path)
|
||
if parent_path != self.path_browser_current_path:
|
||
self.path_browser_current_path = parent_path
|
||
self._refresh_path_browser()
|
||
|
||
imgui.same_line()
|
||
if imgui.button("主目录"):
|
||
self.path_browser_current_path = os.path.expanduser("~")
|
||
self._refresh_path_browser()
|
||
|
||
imgui.same_line()
|
||
if imgui.button("当前目录"):
|
||
self.path_browser_current_path = os.getcwd()
|
||
self._refresh_path_browser()
|
||
|
||
imgui.separator()
|
||
|
||
# 文件和目录列表
|
||
if self.path_browser_items:
|
||
# 先显示目录
|
||
for item in self.path_browser_items:
|
||
if item['is_dir']:
|
||
# 尝试使用图标或文本标识目录
|
||
if self.icons.get('property_select_image'): # 使用现有图标作为文件夹图标
|
||
imgui.image(self.icons['property_select_image'], (16, 16))
|
||
imgui.same_line()
|
||
else:
|
||
imgui.text_colored((0.4, 0.6, 1.0, 1.0), ">")
|
||
imgui.same_line()
|
||
|
||
if imgui.selectable(item['name'], False)[0]:
|
||
self.path_browser_current_path = item['path']
|
||
self._refresh_path_browser()
|
||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||
self.path_browser_current_path = item['path']
|
||
self._refresh_path_browser()
|
||
|
||
# 显示文件(根据模式显示不同类型的文件)
|
||
if self.path_browser_mode == "open_project":
|
||
for item in self.path_browser_items:
|
||
if not item['is_dir'] and item['name'].endswith('.json'):
|
||
imgui.text_colored((1.0, 1.0, 0.7, 1.0), "[FILE]")
|
||
imgui.same_line()
|
||
if imgui.selectable(item['name'], False)[0]:
|
||
self.path_browser_selected_path = item['path']
|
||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||
# 选择包含project.json的目录
|
||
self.path_browser_current_path = os.path.dirname(item['path'])
|
||
self._apply_selected_path()
|
||
elif self.path_browser_mode == "import_model":
|
||
for item in self.path_browser_items:
|
||
if not item['is_dir']:
|
||
file_ext = os.path.splitext(item['name'])[1].lower()
|
||
# 根据文件类型显示不同颜色
|
||
if file_ext in ['.gltf', '.glb']:
|
||
color = (0.7, 1.0, 0.7, 1.0) # 绿色 - glTF
|
||
elif file_ext == '.fbx':
|
||
color = (1.0, 0.7, 0.7, 1.0) # 红色 - FBX
|
||
elif file_ext in ['.bam', '.egg']:
|
||
color = (0.7, 0.7, 1.0, 1.0) # 蓝色 - Panda3D
|
||
elif file_ext == '.obj':
|
||
color = (1.0, 1.0, 0.7, 1.0) # 黄色 - OBJ
|
||
else:
|
||
color = (0.8, 0.8, 0.8, 1.0) # 灰色 - 其他
|
||
|
||
imgui.text_colored(color, f"[{file_ext[1:].upper()}]")
|
||
imgui.same_line()
|
||
if imgui.selectable(item['name'], False)[0]:
|
||
self.path_browser_selected_path = item['path']
|
||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||
self.path_browser_selected_path = item['path']
|
||
self._apply_selected_path()
|
||
|
||
imgui.separator()
|
||
|
||
# 选中路径显示
|
||
if self.path_browser_selected_path:
|
||
imgui.text("选中路径:")
|
||
imgui.same_line()
|
||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), self.path_browser_selected_path)
|
||
|
||
# 按钮区域
|
||
if imgui.button("确定"):
|
||
self._apply_selected_path()
|
||
self.show_path_browser = False
|
||
|
||
imgui.same_line()
|
||
if imgui.button("取消"):
|
||
self.show_path_browser = False
|
||
|
||
def _draw_import_dialog(self):
|
||
"""绘制导入模型对话框"""
|
||
if not self.show_import_dialog:
|
||
return
|
||
|
||
# 设置对话框标志
|
||
flags = (imgui.WindowFlags_.no_resize |
|
||
imgui.WindowFlags_.no_collapse |
|
||
imgui.WindowFlags_.modal)
|
||
|
||
# 获取屏幕尺寸,居中显示对话框
|
||
display_size = imgui.get_io().display_size
|
||
dialog_width = 600
|
||
dialog_height = 500
|
||
imgui.set_next_window_size((dialog_width, dialog_height))
|
||
imgui.set_next_window_pos(
|
||
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
|
||
)
|
||
|
||
with imgui_ctx.begin("导入模型", True, flags) as window:
|
||
if not window.opened:
|
||
self.show_import_dialog = False
|
||
return
|
||
|
||
imgui.text("选择要导入的模型文件")
|
||
imgui.separator()
|
||
|
||
# 文件路径输入
|
||
imgui.text("文件路径:")
|
||
changed, file_path = imgui.input_text("##import_file_path", self.import_file_path, 512)
|
||
if changed:
|
||
self.import_file_path = file_path
|
||
|
||
imgui.same_line()
|
||
if imgui.button("浏览..."):
|
||
self.path_browser_mode = "import_model"
|
||
self.path_browser_current_path = os.path.dirname(self.import_file_path) if self.import_file_path else os.getcwd()
|
||
self.show_path_browser = True
|
||
self._refresh_path_browser()
|
||
|
||
imgui.separator()
|
||
|
||
# 支持的格式说明
|
||
imgui.text("支持的文件格式:")
|
||
formats_text = ", ".join(self.supported_formats)
|
||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), formats_text)
|
||
|
||
imgui.separator()
|
||
|
||
# 文件预览信息
|
||
if self.import_file_path and os.path.exists(self.import_file_path):
|
||
file_size = os.path.getsize(self.import_file_path)
|
||
imgui.text(f"文件大小: {file_size / 1024:.2f} KB")
|
||
|
||
file_ext = os.path.splitext(self.import_file_path)[1].lower()
|
||
if file_ext in self.supported_formats:
|
||
imgui.text_colored((0.176, 1.0, 0.769, 1.0), "✓ 文件格式支持")
|
||
else:
|
||
imgui.text_colored((1.0, 0.3, 0.3, 1.0), "✗ 不支持的文件格式")
|
||
else:
|
||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "请选择有效的文件路径")
|
||
|
||
imgui.separator()
|
||
|
||
# 按钮区域
|
||
can_import = (self.import_file_path and
|
||
os.path.exists(self.import_file_path) and
|
||
os.path.splitext(self.import_file_path)[1].lower() in self.supported_formats)
|
||
|
||
# 根据状态设置按钮颜色
|
||
if can_import:
|
||
if imgui.button("导入"):
|
||
self._import_model()
|
||
self.show_import_dialog = False
|
||
else:
|
||
# 禁用状态的按钮(灰色显示)
|
||
imgui.push_style_color(imgui.Col_.button, (0.3, 0.3, 0.3, 1.0))
|
||
imgui.button("导入")
|
||
imgui.pop_style_color()
|
||
|
||
imgui.same_line()
|
||
if imgui.button("取消"):
|
||
self.show_import_dialog = False
|
||
|
||
def _create_new_project(self, name, path):
|
||
"""创建新项目的实际实现"""
|
||
if not hasattr(self, 'project_manager') or not self.project_manager:
|
||
print("✗ 项目管理器未初始化")
|
||
return
|
||
|
||
try:
|
||
if self._create_new_project_impl(name, path):
|
||
print(f"✓ 项目创建成功: {name}")
|
||
else:
|
||
print(f"✗ 项目创建失败: {name}")
|
||
except Exception as e:
|
||
print(f"✗ 项目创建失败: {e}")
|
||
|
||
def _open_project_path(self, path):
|
||
"""打开项目的实际实现"""
|
||
if not hasattr(self, 'project_manager') or not self.project_manager:
|
||
print("✗ 项目管理器未初始化")
|
||
return
|
||
|
||
try:
|
||
print(f"打开项目: {path}")
|
||
if self._open_project_impl(path):
|
||
print(f"✓ 项目打开成功: {path}")
|
||
else:
|
||
print(f"✗ 项目打开失败: {path}")
|
||
except Exception as e:
|
||
print(f"✗ 项目打开失败: {e}")
|
||
|
||
# ==================== 项目管理具体实现 ====================
|
||
|
||
def _save_project_impl(self):
|
||
"""保存项目的具体实现(不依赖Qt)"""
|
||
import json
|
||
import datetime
|
||
import os
|
||
|
||
project_path = self.project_manager.current_project_path
|
||
scenes_path = os.path.join(project_path, "scenes")
|
||
|
||
# 固定的场景文件名
|
||
scene_file = os.path.join(scenes_path, "scene.bam")
|
||
|
||
# 如果存在旧文件,先删除
|
||
if os.path.exists(scene_file):
|
||
try:
|
||
os.remove(scene_file)
|
||
print(f"已删除旧场景文件: {scene_file}")
|
||
except Exception as e:
|
||
print(f"删除旧场景文件失败: {str(e)}")
|
||
return False
|
||
|
||
# 保存场景
|
||
if self.scene_manager.saveScene(scene_file, project_path):
|
||
# 更新项目配置文件
|
||
config_file = os.path.join(project_path, "project.json")
|
||
if os.path.exists(config_file):
|
||
with open(config_file, "r", encoding="utf-8") as f:
|
||
project_config = json.load(f)
|
||
|
||
# 更新最后修改时间
|
||
project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
# 记录场景文件路径
|
||
project_config["scene_file"] = os.path.relpath(scene_file, project_path)
|
||
|
||
with open(config_file, "w", encoding="utf-8") as f:
|
||
json.dump(project_config, f, ensure_ascii=False, indent=4)
|
||
|
||
# 更新项目配置
|
||
self.project_manager.project_config = project_config
|
||
return True
|
||
return False
|
||
|
||
def _open_project_impl(self, project_path):
|
||
"""打开项目的具体实现(不依赖Qt)"""
|
||
import json
|
||
import datetime
|
||
import os
|
||
|
||
try:
|
||
# 检查项目管理器是否已初始化
|
||
if not hasattr(self, 'project_manager') or not self.project_manager:
|
||
print("✗ 项目管理器未初始化")
|
||
self.add_error_message("项目管理器未初始化")
|
||
return False
|
||
|
||
# 检查场景管理器是否已初始化
|
||
if not hasattr(self, 'scene_manager') or not self.scene_manager:
|
||
print("✗ 场景管理器未初始化")
|
||
self.add_error_message("场景管理器未初始化")
|
||
return False
|
||
|
||
# 检查是否是有效的项目文件夹
|
||
config_file = os.path.join(project_path, "project.json")
|
||
if not os.path.exists(config_file):
|
||
print(f"⚠ 选择的不是有效的项目文件夹: {project_path}")
|
||
self.add_warning_message(f"选择的不是有效的项目文件夹: {project_path}")
|
||
return False
|
||
|
||
# 读取项目配置
|
||
try:
|
||
with open(config_file, "r", encoding="utf-8") as f:
|
||
project_config = json.load(f)
|
||
except Exception as e:
|
||
print(f"✗ 读取项目配置文件失败: {e}")
|
||
self.add_error_message(f"读取项目配置文件失败: {e}")
|
||
return False
|
||
|
||
# 检查场景文件
|
||
scene_file = os.path.join(project_path, "scenes", "scene.bam")
|
||
if os.path.exists(scene_file):
|
||
# 加载场景
|
||
try:
|
||
if self.scene_manager.loadScene(scene_file):
|
||
# 更新项目配置
|
||
project_config["scene_file"] = os.path.relpath(scene_file, project_path)
|
||
print(f"✓ 场景加载成功: {scene_file}")
|
||
else:
|
||
print(f"⚠ 场景加载失败: {scene_file}")
|
||
self.add_warning_message(f"场景加载失败: {scene_file}")
|
||
except Exception as e:
|
||
print(f"✗ 加载场景时发生错误: {e}")
|
||
self.add_error_message(f"加载场景时发生错误: {e}")
|
||
# 继续执行,不阻止项目打开
|
||
|
||
# 更新项目配置
|
||
project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
try:
|
||
with open(config_file, "w", encoding="utf-8") as f:
|
||
json.dump(project_config, f, ensure_ascii=False, indent=4)
|
||
except Exception as e:
|
||
print(f"✗ 保存项目配置失败: {e}")
|
||
self.add_error_message(f"保存项目配置失败: {e}")
|
||
|
||
# 更新项目状态
|
||
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)
|
||
|
||
print(f"✓ 项目打开成功: {project_path}")
|
||
self.add_success_message(f"项目打开成功: {project_name}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"✗ 打开项目时发生错误: {e}")
|
||
self.add_error_message(f"打开项目时发生错误: {e}")
|
||
return False
|
||
|
||
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_simple(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)
|
||
|
||
# 使用主删除方法
|
||
self._delete_node(node)
|
||
|
||
# 获取节点名称(在删除之前)
|
||
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 _toggle_vr_mode(self):
|
||
"""切换VR模式"""
|
||
if self.vr_manager:
|
||
if self.vr_manager.is_enabled():
|
||
self._exit_vr_mode()
|
||
else:
|
||
self.vr_manager.enable()
|
||
self.add_info_message("已进入VR模式")
|
||
else:
|
||
self.add_error_message("VR管理器未初始化")
|
||
|
||
def _exit_vr_mode(self):
|
||
"""退出VR模式"""
|
||
if self.vr_manager:
|
||
self.vr_manager.disable()
|
||
self.add_info_message("已退出VR模式")
|
||
|
||
def _show_vr_status(self):
|
||
"""显示VR状态"""
|
||
if self.vr_manager:
|
||
status = "已启用" if self.vr_manager.is_enabled() else "未启用"
|
||
self.add_info_message(f"VR状态: {status}")
|
||
|
||
# 显示设备信息
|
||
if self.vr_manager.is_enabled():
|
||
devices = self.vr_manager.get_connected_devices()
|
||
if devices:
|
||
self.add_info_message(f"连接的设备: {', '.join(devices)}")
|
||
else:
|
||
self.add_info_message("未检测到VR设备")
|
||
else:
|
||
self.add_error_message("VR管理器未初始化")
|
||
|
||
def _show_vr_settings(self):
|
||
"""显示VR设置"""
|
||
if self.vr_manager:
|
||
self.add_info_message("VR设置对话框待实现")
|
||
else:
|
||
self.add_error_message("VR管理器未初始化")
|
||
|
||
def _show_vr_performance_report(self):
|
||
"""显示VR性能报告"""
|
||
if self.vr_manager and self.vr_manager.is_enabled():
|
||
report = self.vr_manager.get_performance_report()
|
||
self.add_info_message(f"VR性能报告: {report}")
|
||
else:
|
||
self.add_info_message("VR未启用或管理器未初始化")
|
||
|
||
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()
|