import math import sys import os import warnings from direct.actor.Actor import Actor warnings.filterwarnings("ignore", category=DeprecationWarning) from panda3d.core import (CardMaker, Vec4, Vec3, AmbientLight, DirectionalLight, Point3, WindowProperties, Material, LColor, loadPrcFileData) from direct.showbase.ShowBase import ShowBase from direct.showbase.ShowBaseGlobal import globalClock from scene.scene_manager import SceneManager # 设置 RenderPipelineFile 路径 project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) render_pipeline_path = os.path.join(project_root, "RenderPipelineFile") sys.path.insert(0, render_pipeline_path) from RenderPipelineFile.rpcore import RenderPipeline from ssbo_component.ssbo_editor import SSBOEditor # 从渲染管线工具模块导入全局函数 from core.render_pipeline_utils import get_render_pipeline, set_render_pipeline # 尝试导入插件管理器(如果存在) try: from plugins.plugin_manager import PluginManager PLUGIN_SUPPORT = True except ImportError: PLUGIN_SUPPORT = False print("⚠ 插件管理器未找到,插件功能将不可用") class CoreWorld(ShowBase): """核心世界功能类 - 负责基础的3D世界设置和核心功能""" def __init__(self, width=1380, height=750, is_fullscreen=False, clear_color=LColor(0, 0.5, 0, 1)): global _global_render_pipeline # 初始化基础属性 self.qtWidget = None # Qt部件引用(用于获取准确的渲染区域尺寸) # 设置基本配置 loadPrcFileData("", "show-frame-rate-meter 0") loadPrcFileData("", "window-type onscreen") loadPrcFileData("", f"win-size {width} {height}") loadPrcFileData("", "win-fixed-size #f") # 允许窗口调整大小 # Performance preset to match the standalone smooth runner loadPrcFileData("", "threading-model /Draw") loadPrcFileData("", "pstats-gpu-timing #f") loadPrcFileData("", "gl-debug #f") loadPrcFileData("", "gl-debug-object-labels #f") # VR性能优化配置 loadPrcFileData("", "prefer-single-buffer true") loadPrcFileData("", "gl-force-flush false") loadPrcFileData("", "sync-video false") loadPrcFileData("", "support-stencil false") loadPrcFileData("", "clock-mode non-real-time") loadPrcFileData("", "support-threads false") if is_fullscreen: loadPrcFileData("", "fullscreen #t") # 创建渲染管线 self.render_pipeline = RenderPipeline() self.render_pipeline.pre_showbase_init() # 强制开启多线程支持 (Video播放必需) loadPrcFileData("force_threads", "support-threads #f") # 初始化 ShowBase ShowBase.__init__(self) # 创建渲染管线 self.render_pipeline.create(self) set_render_pipeline(self.render_pipeline) # 设置相机 self.render_pipeline._showbase.camera = self.render_pipeline._showbase.cam # 设置相机控制参数 self.cameraSpeed = 20.0 self.cameraRotateSpeed = 10.0 # 鼠标控制相关变量 self.lastMouseX = 0 self.lastMouseY = 0 self.mouseRightPressed = False # 初始化自定义鼠标控制器 from core.CustomMouseController import CustomMouseController self.mouse_controller = CustomMouseController(self) self.mouse_controller.setUp() # 初始化插件管理器(如果支持) if PLUGIN_SUPPORT: self.plugin_manager = PluginManager(self) else: self.plugin_manager = None # 添加错误处理钩子 self.accept("transform_state_error", self._handle_transform_error) # 初始化世界 self._setupResourcePaths() self._setupCamera() self._setupLighting() self._setupGround() self._loadFont() def _handle_transform_error(self): """处理TransformState相关的错误""" try: from panda3d.core import TransformState, RenderState TransformState.clear_cache() RenderState.clear_cache() print("已清理TransformState和RenderState缓存") except Exception as e: print(f"清理缓存时出错: {e}") def _setupResourcePaths(self): """设置Panda3D资源搜索路径,确保能正确找到Resources文件夹中的模型和贴图""" try: import os from panda3d.core import getModelPath, DSearchPath, Filename # 获取项目根目录 project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) resources_dir = os.path.join(project_root, "Resources") # 确保Resources目录存在 if not os.path.exists(resources_dir): os.makedirs(resources_dir, exist_ok=True) print(f"✓ 创建Resources目录: {resources_dir}") # 添加Resources目录到Panda3D模型搜索路径 model_path = getModelPath() resources_filename = Filename.from_os_specific(resources_dir) # 检查路径是否已存在,避免重复添加 if not model_path.findFile(resources_filename): model_path.appendDirectory(resources_filename) print(f"✓ 添加Resources到模型搜索路径: {resources_dir}") # 添加core目录到搜索路径 core_dir = os.path.join(project_root, "core") core_filename = Filename.from_os_specific(core_dir) if not model_path.findFile(core_filename): model_path.appendDirectory(core_filename) print(f"✓ 添加core目录到模型搜索路径: {core_dir}") # 添加RenderPipeline目录到搜索路径 rp_dir = os.path.join(project_root, "RenderPipelineFile") rp_filename = Filename.from_os_specific(rp_dir) if not model_path.findFile(rp_filename): model_path.appendDirectory(rp_filename) print(f"✓ 添加RenderPipeline目录到模型搜索路径: {rp_dir}") # 添加RenderPipeline data目录到搜索路径 rp_data_dir = os.path.join(rp_dir, "data") rp_data_filename = Filename.from_os_specific(rp_data_dir) if not model_path.findFile(rp_data_filename): model_path.appendDirectory(rp_data_filename) print(f"✓ 添加RenderPipeline data目录到模型搜索路径: {rp_data_dir}") # 同时添加各个子目录到搜索路径 subdirs = ['models', 'textures', 'animations', 'icons', 'materials'] for subdir in subdirs: subdir_path = os.path.join(resources_dir, subdir) if os.path.exists(subdir_path): subdir_filename = Filename.from_os_specific(subdir_path) if not model_path.findFile(subdir_filename): model_path.appendDirectory(subdir_filename) print(f"✓ 添加子目录到搜索路径: {subdir}") else: # 创建不存在的子目录 os.makedirs(subdir_path, exist_ok=True) subdir_filename = Filename.from_os_specific(subdir_path) model_path.appendDirectory(subdir_filename) print(f"✓ 创建并添加子目录: {subdir}") # 设置纹理搜索路径 try: from panda3d.core import getTexturePath texture_path = getTexturePath() if not texture_path.findFile(resources_filename): texture_path.appendDirectory(resources_filename) except ImportError: # 新版本 Panda3D 中 getTexturePath 可能不可用 print(" 注意: getTexturePath 不可用,使用默认纹理路径") for subdir in ['textures', 'materials', 'icons']: subdir_path = os.path.join(resources_dir, subdir) if os.path.exists(subdir_path): subdir_filename = Filename.from_os_specific(subdir_path) if not texture_path.findFile(subdir_filename): texture_path.appendDirectory(subdir_filename) print(f"✓ 资源路径设置完成") print(f" 项目根目录: {project_root}") print(f" Resources目录: {resources_dir}") except Exception as e: print(f"⚠️ 设置资源路径失败: {e}") def diagnose_fbx_loading(self, fbx_path): """诊断FBX加载状态""" print("=== FBX加载诊断 ===") # 检查文件存在 import os if not os.path.exists(fbx_path): print("❌ FBX文件不存在") return None try: # 尝试加载 actor = Actor(fbx_path) # 检查动画 anims = actor.getAnimNames() print(f"✓ 找到 {len(anims)} 个动画: {anims}") # 检查PartBundle bundles = actor.getPartBundles() print(f"✓ 找到 {len(bundles)} 个PartBundle") # 测试动画控制器 if anims: control = actor.getAnimControl(anims[0]) if control: print(f"✓ 动画控制器创建成功: {anims[0]}") return actor else: print(f"❌ 动画控制器创建失败: {anims[0]}") return actor except Exception as e: print(f"❌ FBX加载异常: {e}") return None def diagnose_actor_animation(self,model_path, anim_path, anim_name="walk"): print("=== 开始诊断 Actor 动画问题 ===") import os from direct.actor.Actor import Actor from panda3d.core import Loader from panda3d.core import Filename # 检查文件存在性 print(f"1. 文件检查:") print(f" 模型文件存在: {os.path.exists(model_path)}") print(f" 动画文件存在: {os.path.exists(anim_path)}") if not os.path.exists(model_path): print("❌ 模型文件不存在,请检查路径") return None if not os.path.exists(anim_path): print("❌ 动画文件不存在,请检查路径") return None # 2. 检查模型结构 print(f"2. 模型结构检查:") try: # 先用普通loader加载检查结构 temp_model = self.loader.loadModel(model_path) if temp_model is None: print("❌ 无法加载模型文件") return None # 检查Character节点 bundleNP = temp_model.find("**/+Character") if bundleNP.isEmpty(): print("❌ 模型不包含Character节点 - 这是动画模型必需的") print(" 建议: 确保模型文件是正确的角色模型,包含骨骼结构") return None else: print("✓ 模型包含Character节点") temp_model.removeNode() # 清理临时模型 except Exception as e: print(f"❌ 模型加载失败: {e}") return None # 3. 检查动画文件结构 print(f"3. 动画文件检查:") try: temp_anim = self.loader.loadModel(anim_path) if temp_anim is None: print("❌ 无法加载动画文件") return None # 检查AnimBundleNode animBundleNP = temp_anim.find('**/+AnimBundleNode') if animBundleNP.isEmpty(): print("❌ 动画文件不包含AnimBundleNode") print(" 建议: 确保动画文件是正确导出的动画数据") return None else: print("✓ 动画文件包含AnimBundleNode") temp_anim.removeNode() # 清理临时动画 except Exception as e: print(f"❌ 动画文件加载失败: {e}") # 4. 尝试创建Actor print(f"4. Actor创建测试:") # 方法1: 构造函数方式 try: print(" 尝试方法1: 构造函数加载") actor = Actor(model_path, {anim_name: anim_path}) print(f" 动画列表: {actor.getAnimNames()}") # 强制同步绑定 print(" 尝试强制绑定动画...") actor.bindAnim(anim_name, allowAsyncBind=False) control = actor.getAnimControl(anim_name) if control is not None: print("✓ 方法1成功 - 动画控制器创建成功") return actor else: print("❌ 方法1失败 - 动画控制器为None") except Exception as e: print(f"❌ 方法1异常: {e}") # 方法2: 分步加载 try: print(" 尝试方法2: 分步加载") actor = Actor() actor.loadModel(model_path) actor.loadAnims({anim_name: anim_path}) # 强制绑定 actor.bindAnim(anim_name, allowAsyncBind=False) control = actor.getAnimControl(anim_name) if control is not None: print("✓ 方法2成功 - 动画控制器创建成功") return actor else: print("❌ 方法2失败 - 动画控制器为None") except Exception as e: print(f"❌ 方法2异常: {e}") # 5. 深度诊断 print(f"5. 深度诊断:") try: actor = Actor() actor.loadModel(model_path) # 检查PartBundle bundles = actor.getPartBundles() print(f" PartBundle数量: {len(bundles)}") if len(bundles) == 0: print("❌ 没有找到PartBundle - 模型可能不是正确的角色模型") return None # 检查动画绑定过程 print(" 尝试手动绑定动画...") actor.loadAnims({anim_name: anim_path}) # 获取详细的绑定信息 actor_info = actor.getActorInfo() print(f" Actor信息: {actor_info}") return None except Exception as e: print(f"❌ 深度诊断异常: {e}") return None print("❌ 所有方法都失败了") return None def _setYCModel(self): model = self.loader.loadModel("/home/tiger/文档/Tzjyc_GLTF/tzjyc.gltf") model.reparentTo(self.render) model.setScale(0.25) model.setPos(-8, 42, 0) model.setHpr(0, 90, 0) def _setupCamera(self): """设置相机位置和朝向""" self.cam.setPos(0, -50, 20) self.cam.lookAt(0, 0, 0) self.camLens.setFov(80) self.cam.setTag("is_scene_element", "1") self.cam.setTag("tree_item_type", "CAMERA_NODE") print("✓ 相机设置完成") def _setupLighting(self): """设置基础光照系统""" # 环境光 alight = AmbientLight('alight') alight.setColor((0.2, 0.2, 0.2, 1)) alnp = self.render.attachNewNode(alight) self.render.setLight(alnp) # 定向光(模拟太阳光) dlight = DirectionalLight('dlight') dlight.setColor((0.8, 0.8, 0.8, 1)) dlnp = self.render.attachNewNode(dlight) dlnp.setHpr(45, -45, 0) # 设置光照方向 self.render.setLight(dlnp) # 保存光源引用 self.ambient_light = alnp self.directional_light = dlnp print("✓ 光照系统设置完成") def _setupGround(self): """创建地板""" cm = CardMaker('ground') cm.setFrame(-50, 50, -50, 50) # 创建地板节点 self.ground = self.render.attachNewNode(cm.generate()) self.ground.setP(-90) self.ground.setZ(-1.0) self.ground.setColor(0.8, 0.8, 0.8, 1) self.ground.setTag("is_scene_element", "1") self.ground.setTag("tree_item_type", "SCENE_NODE") # 创建支持贴图的材质 mat = Material() mat.setName("GroundMaterial") color = LColor(1, 1, 1, 0.8) mat.set_base_color(color) mat.set_roughness(1) # 设置合适的初始粗糙度 mat.set_metallic(0.5) # 设置较低的初始金属性 self.ground.set_material(mat) # #创建第二个相同的地面,位置稍有偏移 # self.ground2 = self.render.attachNewNode(cm.generate()) # self.ground2.setH(-90) # self.ground2.setZ(-1.0) # self.ground2.setX(50) # 在X轴方向偏移 # self.ground2.setZ(49) # 在X轴方向偏移 # self.ground2.setColor(0.8, 0.8, 0.8, 1) # self.ground2.set_material(mat) # self.ground2.setTag("is_scene_element", "1") # self.ground2.setTag("tree_item_type", "SCENE_NODE") # # # 创建第三个相同的地面,位置在另一个方向 # self.ground3 = self.render.attachNewNode(cm.generate()) # self.ground3.setH(90) # self.ground3.setZ(-1.0) # self.ground3.setX(-50) # 在X轴负方向偏移 # self.ground3.setZ(49) # 在X轴负方向偏移 # self.ground3.setColor(0.8, 0.8, 0.8, 1) # self.ground3.set_material(mat) # self.ground3.setTag("is_scene_element", "1") # self.ground3.setTag("tree_item_type", "SCENE_NODE") # # self.ground4 = self.render.attachNewNode(cm.generate()) # # self.ground3.setR(90) # self.ground4.setZ(-1.0) # self.ground4.setY(50) # 在X轴负方向偏移 # self.ground4.setZ(49) # 在X轴负方向偏移 # self.ground4.setColor(0.8, 0.8, 0.8, 1) # self.ground4.set_material(mat) # self.ground4.setTag("is_scene_element", "1") # self.ground4.setTag("tree_item_type", "SCENE_NODE") # # self.ground5 = self.render.attachNewNode(cm.generate()) # self.ground5.setP(180) # self.ground5.setZ(-1) # self.ground5.setY(-50) # 在X轴负方向偏移 # self.ground5.setZ(49) # 在X轴负方向偏移 # self.ground5.setColor(0.8, 0.8, 0.8, 1) # self.ground5.set_material(mat) # self.ground5.setTag("is_scene_element", "1") # self.ground5.setTag("tree_item_type", "SCENE_NODE") # # self.ground6 = self.render.attachNewNode(cm.generate()) # self.ground6.setP(90) # self.ground6.setZ(-1) # self.ground6.setZ(99) # 在X轴负方向偏移 # self.ground6.setColor(0.8, 0.8, 0.8, 1) # self.ground6.set_material(mat) # self.ground6.setTag("is_scene_element", "1") # self.ground6.setTag("tree_item_type", "SCENE_NODE") # 应用默认PBR效果,确保支持贴图 try: if hasattr(self, 'render_pipeline') and self.render_pipeline: self.render_pipeline.set_effect( self.ground, "effects/default.yaml", { "normal_mapping": True, "render_gbuffer": True, "alpha_testing": False, "parallax_mapping": False, "render_shadow": True, "render_envmap": True }, 50 ) # # 为其他两个地面也应用相同的效果 # self.render_pipeline.set_effect( # self.ground2, # "effects/default.yaml", # { # "normal_mapping": True, # "render_gbuffer": True, # "alpha_testing": False, # "parallax_mapping": False, # "render_shadow": True, # "render_envmap": True # }, # 50 # ) # self.render_pipeline.set_effect( # self.ground3, # "effects/default.yaml", # { # "normal_mapping": True, # "render_gbuffer": True, # "alpha_testing": False, # "parallax_mapping": False, # "render_shadow": True, # "render_envmap": True # }, # 50 # ) # self.render_pipeline.set_effect( # self.ground4, # "effects/default.yaml", # { # "normal_mapping": True, # "render_gbuffer": True, # "alpha_testing": False, # "parallax_mapping": False, # "render_shadow": True, # "render_envmap": True # }, # 50 # ) # self.render_pipeline.set_effect( # self.ground5, # "effects/default.yaml", # { # "normal_mapping": True, # "render_gbuffer": True, # "alpha_testing": False, # "parallax_mapping": False, # "render_shadow": True, # "render_envmap": True # }, # 50 # ) # self.render_pipeline.set_effect( # self.ground6, # "effects/default.yaml", # { # "normal_mapping": True, # "render_gbuffer": True, # "alpha_testing": False, # "parallax_mapping": False, # "render_shadow": True, # "render_envmap": True # }, # 50 # ) # print("✓ 地板PBR效果已应用") else: print("⚠️ RenderPipeline未初始化,地板将使用基础渲染") except Exception as e: print(f"⚠️ 地板PBR效果应用失败: {e}") print("✓ 地板创建完成(支持材质贴图)") # def _loadFont(self): # """加载中文字体""" # try: # self.chinese_font = self.loader.loadFont('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc') # if not self.chinese_font: # print("警告: 无法加载中文字体,将使用默认字体") # else: # print("✓ 中文字体加载成功") # except: # print("警告: 无法加载中文字体,将使用默认字体") # self.chinese_font = None def _loadFont(self): """加载中文字体 - 跨平台,Panda3D 友好路径""" try: import os import platform from pathlib import Path from panda3d.core import Filename self.chinese_font = None # 初始化 # --- 平台与项目根 --- system = platform.system().lower() project_root = Path(__file__).resolve().parent.parent # 上两级 # --- 候选字体路径(按平台) --- if system == "windows": win_dir = os.environ.get("WINDIR") or r"C:\Windows" win_fonts_dir = Path(win_dir) / "Fonts" font_candidates = [ project_root / "RenderPipelineFile" / "data" / "font" / "msyh.ttc", win_fonts_dir / "msyh.ttc", win_fonts_dir / "msyh.ttf", win_fonts_dir / "simhei.ttf", win_fonts_dir / "simsun.ttc", ] elif system == "darwin": # macOS font_candidates = [ Path("/System/Library/Fonts/PingFang.ttc"), Path("/System/Library/Fonts/STHeiti.ttc"), Path("/Library/Fonts/Songti.ttc"), ] else: # Linux / 其他 font_candidates = [ Path("/usr/share/fonts/truetype/wqy/wqy-microhei.ttc"), Path("/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc"), Path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"), ] # --- 逐个尝试加载 --- for os_path in font_candidates: os_path_str = str(os_path) if not os.path.exists(os_path_str): # 文件确实不存在就跳过 continue # 1) 先把 OS 路径转换成 Panda 内部路径(/d/... 或 /usr/...) panda_fn = Filename.fromOsSpecific(os_path_str) panda_fn.makeTrueCase() # Windows 上修正大小写(更稳) panda_internal = panda_fn.getFullpath() # 取“内部样式”的字符串 # 2) 打印便于对照 Panda 的日志 try: print(f"[FontLoader] 尝试加载: {panda_internal}") except Exception: pass try: # 3) 传“字符串”给 loader.loadFont(关键修复点) font = self.loader.loadFont(panda_internal) if font: self.chinese_font = font print("✓ 中文字体加载成功:", os_path_str) break else: print("[FontLoader] 返回空字体对象,继续尝试下一个候选...") except TypeError as te: # 某些旧版绑定只接受 str,不接受 Filename;此处已传 str,若仍报错再兜底一次 try: font = self.loader.loadFont(str(panda_fn)) # 退一步:str(Filename) if font: self.chinese_font = font print("✓ 中文字体加载成功(备用API):", os_path_str) break else: print("[FontLoader] 备用API仍返回空字体对象,继续…") except Exception as te2: print(f"[FontLoader] 加载失败(TypeError),继续下一个: {os_path},原因: {te2}") except Exception as e: print(f"[FontLoader] 加载失败,继续下一个: {os_path},原因: {e}") # --- 兜底 --- if not self.chinese_font: print("警告: 无法加载中文字体,将使用默认字体(可能导致中文显示不全)") self.chinese_font = None except Exception as e: print(f"警告: 加载中文字体时发生错误: {e}") self.chinese_font = None def setQtWidget(self, widget): """设置Qt部件引用""" self.qtWidget = widget print(f"✓ 设置Qt部件引用: {widget}") def getWindowSize(self): """获取准确的窗口尺寸""" if self.qtWidget: # 优先使用Qt部件的实际尺寸 width, height = self.qtWidget.getActualSize() if width > 0 and height > 0: return width, height # 备用方案:使用Panda3D窗口尺寸 if hasattr(self, 'win') and self.win: width = self.win.getXSize() height = self.win.getYSize() # print(f"从Panda3D窗口获取尺寸!!!!!!!!!!!!!!!!!!!!!: {width} x {height}") return width, height # 最后的默认值 print("使用默认窗口尺寸: 800 x 600") return 800, 600 # ==================== 相机控制功能 ==================== def wheelForward(self, data=None): """处理滚轮向前滚动(前进)""" # 获取相机的前向向量 forward = self.cam.getMat().getRow3(1) # 计算移动距离 distance = self.cameraSpeed * globalClock.getDt() # 更新相机位置 currentPos = self.cam.getPos() newPos = currentPos + forward * distance self.cam.setPos(newPos) def wheelBackward(self, data=None): """处理滚轮向后滚动(后退)""" # 获取相机的前向向量 forward = self.cam.getMat().getRow3(1) # 计算移动距离 distance = self.cameraSpeed * globalClock.getDt() # 更新相机位置 currentPos = self.cam.getPos() newPos = currentPos - forward * distance self.cam.setPos(newPos) def moveCamera(self, x, y, z): """移动相机位置(垂直移动)""" # 获取相机的上向量 upVector = self.cam.getMat().getRow3(2) # 计算移动距离 distance = self.cameraSpeed * globalClock.getDt() # 更新相机位置 currentPos = self.cam.getPos() newPos = currentPos + upVector * z * distance self.cam.setPos(newPos) # ==================== 鼠标事件处理 ==================== def mousePressEventRight(self, evt): """处理鼠标右键按下事件""" #print("右键按下") self.mouseRightPressed = True self.lastMouseX = evt['x'] self.lastMouseY = evt['y'] # # # 通过 Qt 窗口隐藏光标并捕获鼠标 # try: # if hasattr(self, 'qtWidget') and self.qtWidget: # from PyQt5.QtCore import Qt # self.qtWidget.setCursor(Qt.BlankCursor) # # 捕获鼠标,使其无法离开窗口 # self.qtWidget.grabMouse() # except Exception as e: # print(f"通过 Qt 隐藏光标时出错: {e}") def mouseReleaseEventRight(self, evt): """处理鼠标右键释放事件""" #print("右键释放") self.mouseRightPressed = False # # 恢复 Qt 窗口光标并释放鼠标捕获 # try: # if hasattr(self, 'qtWidget') and self.qtWidget: # from PyQt5.QtCore import Qt # self.qtWidget.unsetCursor() # 恢复默认光标 # # 释放鼠标捕获 # self.qtWidget.releaseMouse() # except Exception as e: # print(f"恢复 Qt 光标时出错: {e}") def mouseMoveEvent(self, evt): """处理鼠标移动事件 - 只处理相机旋转""" if not evt: return if self.mouseRightPressed: # 计算鼠标移动距离 dx = evt.get('x', 0) - self.lastMouseX dy = evt.get('y', 0) - self.lastMouseY # 计算旋转角度 rotateSpeed = self.cameraRotateSpeed * globalClock.getDt() # 更新相机朝向 currentH = self.cam.getH() currentP = self.cam.getP() # 限制俯仰角度在-90到90度之间 newP = max(-90, min(90, currentP - dy * rotateSpeed)) self.cam.setH(currentH - dx * rotateSpeed) self.cam.setP(newP) # 更新鼠标位置 self.lastMouseX = evt.get('x', 0) self.lastMouseY = evt.get('y', 0) # ==================== 其他基础功能 ==================== def getChineseFont(self): """获取中文字体""" return self.chinese_font def getGroundNode(self): """获取地板节点""" return self.ground def getAmbientLight(self): """获取环境光""" return self.ambient_light def getDirectionalLight(self): """获取定向光""" return self.directional_light def start_day_night_cycle(self, duration_seconds=10.0): """让天空盒在 duration_seconds 秒内从 0:00 过渡到 24:00""" self._cycle_start_time = self.taskMgr.globalClock.get_real_time() self._cycle_duration = duration_seconds self.taskMgr.add(self._day_night_cycle_task, "day_night_cycle_task") def _day_night_cycle_task(self, task): elapsed = self.taskMgr.globalClock.get_real_time() - self._cycle_start_time t = (elapsed % self._cycle_duration) / self._cycle_duration # 始终在 0~1 循环 self.render_pipeline.daytime_mgr.time = 24.0 * t return task.cont def set_daytime(self, time_str): """设置时间""" try: if hasattr(self, 'render_pipeline') and self.render_pipeline: self.render_pipeline.daytime_mgr.time = time_str print(f"当前时间设置为: {time_str}") else: print(f"⚠️ RenderPipeline 不可用,无法设置时间: {time_str}") except Exception as e: print(f"设置时间失败: {e}") def get_render_pipeline(self): """获取 RenderPipeline 实例""" return getattr(self, 'render_pipeline', None) def _setupSkybox(self): # 加载天空盒模型 self.skybox = self.loader.loadModel("data/builtin_models/skybox/skybox.bam") self.skybox.reparentTo(self.camera) # 绑定到相机 self.skybox.setScale(500) self.skybox.setBin('background', 0) self.skybox.setDepthWrite(False) self.skybox.setLightOff() self.skybox.setCompass() # 始终朝向固定 print("✓ 静态天空盒加载完成") def createDirectionalLight(self): from RenderPipelineFile.rpcore import light_manager from panda3d.core import DirectionalLight, Vec3 dlight = DirectionalLight("1") dlight_np = self.render.attachNewNode(dlight) #light_manager.add_light(dlight) dlight_np.setHpr(45,-45,0) self.render.setLight(dlight_np) dlight.setColor((1,1,1,1)) dlight.setShadowCaster(True,2048,2048) print("平行光创建完成") # dlight.direction = Vec3(0, 0, -1) # 光照方向 # dlight.fov = self.lamp_fov # 光源角度(类似手电筒) # dlight.set_color_from_temperature(1 * 1000.0) # 色温(K) # dlight.energy = self.half_energy # 光照强度 # dlight.radius = self.lamp_radius # 影响范围 # dlight.casts_shadows = True # 是否投射阴影 # dlight.shadow_map_resolution = 256 # 阴影分辨率 # dlight.setPos(0,0,10) def check_material_editor_connection(self): """检查材质编辑器连接状态""" try: # 确保 RenderPipeline 已完全初始化 if not hasattr(self, 'render_pipeline') or not self.render_pipeline: print("RenderPipeline 未初始化") return False # 检查网络监听器是否存在 if not hasattr(self.render_pipeline, '_listener'): print("NetworkCommunication 监听器未初始化") return False from RenderPipelineFile.rpcore.util.network_communication import NetworkCommunication import tempfile import os import time # 使用唯一的测试文件名 import uuid test_filename = f"test_materials_{uuid.uuid4().hex[:8]}.data" temp_path = os.path.join(tempfile.gettempdir(), test_filename) print(f"测试材质编辑器连接,文件路径: {temp_path}") # 确保测试文件不存在 if os.path.exists(temp_path): os.remove(temp_path) # 发送导出命令 NetworkCommunication.send_async( NetworkCommunication.MATERIAL_PORT, f"dump_materials {temp_path}" ) # 大幅增加等待时间,因为网络命令处理可能有延迟 for i in range(60): # 等待最多30秒 time.sleep(0.5) if os.path.exists(temp_path): # 等待文件写入完成 time.sleep(1.0) try: with open(temp_path, 'r') as f: content = f.read().strip() print(f"材质编辑器连接测试成功,文件内容: {len(content)} 字符") os.remove(temp_path) return True except: print("文件读取失败,继续等待...") continue if i % 20 == 0 and i > 0: # 每10秒打印一次状态 print(f"等待材质文件创建... ({i // 2}s)") print("材质编辑器连接测试超时") return False except Exception as e: print(f"材质编辑器连接测试出错: {e}") return False def create_material_editor_widget(self): """创建材质编辑器组件""" try: # 确保 RenderPipeline 已完全初始化 if not hasattr(self, 'render_pipeline') or not self.render_pipeline: print("RenderPipeline 未初始化") return None # 检查网络连接 if not self.check_material_editor_connection(): print("无法连接到材质编辑器网络服务") return None # 创建材质编辑器组件 # 使用纯 Panda3D 实现,不依赖 PyQt5 print("材质编辑器组件已创建(使用 Panda3D 原生实现)") material_widget.setLayout(layout) return material_widget except Exception as e: print(f"创建材质编辑器失败: {e}") return None def _delayed_material_test(self, task): """延迟执行的材质编辑器连接测试""" print("开始延迟材质编辑器连接测试...") success = self.check_material_editor_connection() if success: print("✓ 材质编辑器连接正常") else: print("✗ 材质编辑器连接失败") return task.done def launch_day_time_editor(self): # 检查是否已经启动 if hasattr(self, '_day_time_editor_process') and self._day_time_editor_process: if self._day_time_editor_process.poll() is None: # 进程仍在运行 print("Day Time Editor 已经在运行") return True import subprocess import os import sys try: if not hasattr(self.world,'render_pipeline') or not self.world.render_pipeline: print("错误:renderpipeline未初始化") return False base_path = self.world.render_pipeline.mount_mgr.base_path editor_path = os.path.join(base_path,"toolkit/day_time_editor/main.py") if not os.path.exists(editor_path): print("错误文件不存在") return False self._day_time_editor_process = subprocess.Popen([sys.executable, editor_path]) print("Day Time Editor 已启动") return True except Exception as e: print(f"启动 time editor失败") def setup_material_editor_network(self): """设置材质编辑器网络通信""" from RenderPipelineFile.rpcore.util.network_communication import NetworkCommunication def handle_material_requests(message): """处理材质编辑器的网络请求""" try: print(f"收到材质编辑器请求: {message}") parts = message.strip().split() if not parts: return command = parts[0] if command == "dump_materials": # 处理导出材质列表请求 path = parts[1] if len(parts) > 1 else "" if path: self.export_materials_to_file(path) elif command.startswith("update_material"): # 处理更新材质请求 data = message[len("update_material "):].strip() self.update_material_from_editor(data.split()) except Exception as e: print(f"处理材质编辑器请求失败: {e}") # 注册网络消息处理器 try: NetworkCommunication.listen_threaded( NetworkCommunication.MATERIAL_PORT, handle_material_requests ) print("✓ 材质编辑器网络通信已设置") except Exception as e: print(f"设置材质编辑器网络通信失败: {e}") def export_materials_to_file(self, path): """导出材质列表到文件""" try: print(f"导出材质列表到: {path}") materials = [] # 收集所有材质 def collect_materials(node): if node.hasMaterial(): material = node.getMaterial() if material: materials.append(material) for child in node.getChildren(): collect_materials(child) collect_materials(self.render) # 写入文件 with open(path, "w") as f: for i, material in enumerate(materials): name = f"{i}-{material.getName() or 'unnamed'}" # 获取材质属性,使用默认值如果不存在 base_color = material.getBaseColor() if hasattr(material, 'getBaseColor') else Vec4(0.6, 0.6, 0.6, 1.0) roughness = material.getRoughness() if hasattr(material, 'getRoughness') else 0.5 metallic = material.getMetallic() if hasattr(material, 'getMetallic') else 0.0 # 写入材质数据 f.write(("{} " * 11).format( name, base_color.x, base_color.y, base_color.z, roughness, 0.5, # refractive_index metallic, 0, # shading_model 1.0, # normal_strength 0.0, # param1 0.0 # param2 ) + "\n") print(f"✓ 成功导出 {len(materials)} 个材质到 {path}") except Exception as e: print(f"导出材质列表失败: {e}") def update_material_from_editor(self, data): """从编辑器更新材质""" try: if len(data) < 11: print(f"材质数据不完整: {data}") return name_parts = data[0].split("-") if len(name_parts) < 2: print(f"材质名称格式错误: {data[0]}") return index = int(name_parts[0]) name = "-".join(name_parts[1:]) # 收集所有材质 materials = [] def collect_materials(node): if node.hasMaterial(): material = node.getMaterial() if material: materials.append(material) for child in node.getChildren(): collect_materials(child) collect_materials(self.render) # 查找匹配的材质 if index < len(materials): material = materials[index] # 更新材质属性 material.setBaseColor(Vec4(float(data[1]), float(data[2]), float(data[3]), 1.0)) material.setRoughness(float(data[4])) material.setMetallic(float(data[6])) print(f"✓ 更新材质 {name}: 颜色=({data[1]}, {data[2]}, {data[3]}), 粗糙度={data[4]}, 金属度={data[6]}") else: print(f"未找到索引为 {index} 的材质") except Exception as e: print(f"更新材质失败: {e}") def launch_material_editor(self): """启动材质编辑器""" import subprocess import os import sys try: if not self.render_pipeline: print("错误:renderpipeline未初始化") return False base_path = self.render_pipeline.mount_mgr.base_path editor_path = os.path.join(base_path,"toolkit/material_editor/main.py") if not os.path.exists(editor_path): print("错误文件不存在") return False self._material_editor_process = subprocess.Popen([sys.executable, editor_path]) print("Material Editor 已启动") return True except Exception as e: print(f"启动 Material editor失败") def create_sample_materials(self): """创建一些示例材质供编辑器使用""" from panda3d.core import Material, Vec4, CardMaker # 创建几个测试几何体,每个都有不同的材质 sample_materials = [ {"name": "MetalMaterial", "color": Vec4(0.7, 0.7, 0.8, 1.0), "metallic": True, "roughness": 0.2}, {"name": "PlasticMaterial", "color": Vec4(0.8, 0.2, 0.2, 1.0), "metallic": False, "roughness": 0.8}, {"name": "GlassMaterial", "color": Vec4(0.9, 0.9, 1.0, 0.3), "metallic": False, "roughness": 0.1}, {"name": "WoodMaterial", "color": Vec4(0.6, 0.4, 0.2, 1.0), "metallic": False, "roughness": 0.7}, {"name": "TestPlaneMaterial", "color": Vec4(0.8, 0.8, 0.8, 1.0), "metallic": False, "roughness": 1.0} ] for i, mat_data in enumerate(sample_materials): # 创建几何体 if mat_data["name"] == "TestPlaneMaterial": # 创建一个大的测试平面,类似Blender中的平面 cm = CardMaker('test_plane') cm.setFrame(-5, 5, -5, 5) # 更大的平面 geom_node = self.render.attachNewNode(cm.generate()) geom_node.setPos(0, 15, 0) # 放在前面显眼位置 geom_node.setP(-90) # 水平放置 print("✓ 创建大型测试平面,适合测试粗糙度贴图") else: cm = CardMaker(f'sample_geom_{i}') cm.setFrame(-1, 1, -1, 1) geom_node = self.render.attachNewNode(cm.generate()) geom_node.setPos(i * 3 - 6, 10, 1) geom_node.setP(-90) # 水平放置 # 创建材质并确保名称正确设置 material = Material() material.setName(mat_data["name"]) # 明确设置材质名称 material.setBaseColor(mat_data["color"]) material.setRoughness(mat_data["roughness"]) material.setMetallic(1.0 if mat_data["metallic"] else 0.0) # 应用材质 geom_node.setMaterial(material) print(f"✓ 创建示例材质: {mat_data['name']}") # 延迟一下确保材质完全创建 import time time.sleep(0.1) def load_test_models_with_materials(self): """加载一些测试模型以提供更多材质供编辑""" try: # 你可以在这里加载你的模型文件 # test_model = self.loader.loadModel("models/your_model.gltf") # if test_model: # test_model.reparentTo(self.render) # test_model.setPos(5, 0, 0) # test_model.setScale(1.0) # print(f"✓ 测试模型已加载") # 创建示例材质 self.create_sample_materials() except Exception as e: print(f"加载测试模型失败: {e}") # 创建示例材质作为备选 self.create_sample_materials() def launch_plugin_configurator(self): """启动材质编辑器""" import subprocess import os import sys try: if not self.render_pipeline: print("错误:renderpipeline未初始化") return False base_path = self.render_pipeline.mount_mgr.base_path editor_path = os.path.join(base_path, "toolkit/plugin_configurator/main.py") if not os.path.exists(editor_path): print("错误文件不存在") return False self._material_editor_process = subprocess.Popen([sys.executable, editor_path]) print("plugin_configurator 已启动") return True except Exception as e: print(f"启动plugin_configurator失败")