diff --git a/QMeta3D/Helpers/Env_Grid_Maker.py b/QMeta3D/Helpers/Env_Grid_Maker.py deleted file mode 100644 index a17d9006..00000000 --- a/QMeta3D/Helpers/Env_Grid_Maker.py +++ /dev/null @@ -1,271 +0,0 @@ -from panda3d.core import * -from direct.interval.IntervalGlobal import * - -class Env_Grid_Maker: - def __init__(self, XYPlaneShow = True, XZPlaneShow = False, YZPlaneShow = False, endCapLinesShow = True, XSize = 50, YSize = 50, ZSize = 50, gridStep = 10, subdiv = 10): - #Create line objects - self.axisLines = LineSegs() - self.gridLines = LineSegs() - self.subdivLines = LineSegs() - - #Init passed variables - self.XSize = XSize - self.YSize = YSize - self.ZSize = ZSize - self.gridStep = gridStep - self.subdiv = subdiv - - #Init default variables - - #Plane and end cap line visibility (1 is show, 0 is hide) - self.XYPlaneShow = XYPlaneShow - self.XZPlaneShow = XZPlaneShow - self.YZPlaneShow = YZPlaneShow - self.endCapLinesShow = endCapLinesShow - - #Alpha variables for each plane - #self.XYPlaneAlpha = 1 - #self.XZPlaneAlpha = 1 - #self.YZPlaneAlpha = 1 - - #Colors (RGBA passed as a VBase4 object) - self.XAxisColor = VBase4(1, 0, 0, 1) - self.YAxisColor = VBase4(0, 1, 0, 1) - self.ZAxisColor = VBase4(0, 0, 1, 1) - self.gridColor = VBase4(1, 1, 1, 1) - self.subdivColor = VBase4(.35, .35, .35, 1) - - #Line thicknesses (in pixels) - self.axisThickness = 3 - self.gridThickness = 1 - self.subdivThickness = 1 - - - def create(self): - #Set line thicknesses - self.axisLines.setThickness(self.axisThickness) - self.gridLines.setThickness(self.gridThickness) - self.subdivLines.setThickness(self.subdivThickness) - - if(self.XSize != 0): - #Draw X axis line - self.axisLines.setColor(self.XAxisColor) - self.axisLines.moveTo(-(self.XSize), 0, 0) - self.axisLines.drawTo(self.XSize, 0, 0) - - if(self.YSize != 0): - #Draw Y axis line - self.axisLines.setColor(self.YAxisColor) - self.axisLines.moveTo(0, -(self.YSize), 0) - self.axisLines.drawTo(0, self.YSize, 0) - - if(self.ZSize != 0): - #Draw Z axis line - self.axisLines.setColor(self.ZAxisColor) - self.axisLines.moveTo(0, 0, -(self.ZSize)) - self.axisLines.drawTo(0, 0, self.ZSize) - - #Check to see if primary grid lines should be drawn at all - if(self.gridStep != 0): - - #Draw primary grid lines - self.gridLines.setColor(self.gridColor) - - #Draw primary grid lines metering x axis if any x-length - if(self.XSize != 0): - if((self.YSize != 0) and (self.XYPlaneShow)): - #Draw y lines across x axis starting from center moving out - #XY Plane - for x in self.myfrange(0, self.XSize, self.gridStep): - self.gridLines.moveTo(x, -(self.YSize), 0) - self.gridLines.drawTo(x, self.YSize, 0) - self.gridLines.moveTo(-x, -(self.YSize), 0) - self.gridLines.drawTo(-x, self.YSize, 0) - - if(self.endCapLinesShow): - #Draw endcap lines - self.gridLines.moveTo(self.XSize, -(self.YSize), 0) - self.gridLines.drawTo(self.XSize, self.YSize, 0) - self.gridLines.moveTo(-(self.XSize), -(self.YSize), 0) - self.gridLines.drawTo(-(self.XSize), self.YSize, 0) - - if((self.ZSize != 0) and (self.XZPlaneShow)): - #Draw z lines across x axis starting from center moving out - #XZ Plane - for x in self.myfrange(0, self.XSize, self.gridStep): - self.gridLines.moveTo(x, 0, -(self.ZSize)) - self.gridLines.drawTo(x, 0, self.ZSize) - self.gridLines.moveTo(-x, 0, -(self.ZSize)) - self.gridLines.drawTo(-x, 0, self.ZSize) - - if(self.endCapLinesShow): - #Draw endcap lines - self.gridLines.moveTo(self.XSize, 0, -(self.ZSize)) - self.gridLines.drawTo(self.XSize, 0, self.ZSize) - self.gridLines.moveTo(-(self.XSize), 0, -(self.ZSize)) - self.gridLines.drawTo(-(self.XSize), 0, self.ZSize) - - #Draw primary grid lines metering y axis if any y-length - if(self.YSize != 0): - - if((self.YSize != 0) and (self.XYPlaneShow)): - #Draw x lines across y axis - #XY Plane - for y in self.myfrange(0, self.YSize, self.gridStep): - self.gridLines.moveTo(-(self.XSize), y, 0) - self.gridLines.drawTo(self.XSize, y, 0) - self.gridLines.moveTo(-(self.XSize), -y, 0) - self.gridLines.drawTo(self.XSize, -y, 0) - - if(self.endCapLinesShow): - #Draw endcap lines - self.gridLines.moveTo(-(self.XSize), self.YSize, 0) - self.gridLines.drawTo(self.XSize, self.YSize, 0) - self.gridLines.moveTo(-(self.XSize), -(self.YSize), 0) - self.gridLines.drawTo(self.XSize, -(self.YSize), 0) - - if((self.ZSize != 0) and (self.YZPlaneShow)): - #Draw z lines across y axis - #YZ Plane - for y in self.myfrange(0, self.YSize, self.gridStep): - self.gridLines.moveTo(0, y, -(self.ZSize)) - self.gridLines.drawTo(0, y, self.ZSize) - self.gridLines.moveTo(0, -y, -(self.ZSize)) - self.gridLines.drawTo(0, -y, self.ZSize) - - if(self.endCapLinesShow): - #Draw endcap lines - self.gridLines.moveTo(0, self.YSize, -(self.ZSize)) - self.gridLines.drawTo(0, self.YSize, self.ZSize) - self.gridLines.moveTo(0, -(self.YSize), -(self.ZSize)) - self.gridLines.drawTo(0, -(self.YSize), self.ZSize) - - #Draw primary grid lines metering z axis if any z-length - if(self.ZSize != 0): - if((self.XSize != 0) and (self.XZPlaneShow)): - #Draw x lines across z axis - #XZ Plane - for z in self.myfrange(0, self.ZSize, self.gridStep): - self.gridLines.moveTo(-(self.XSize), 0, z) - self.gridLines.drawTo(self.XSize, 0, z) - self.gridLines.moveTo(-(self.XSize), 0, -z) - self.gridLines.drawTo(self.XSize, 0, -z) - - if(self.endCapLinesShow): - #Draw endcap lines - self.gridLines.moveTo(-(self.XSize), 0, self.ZSize) - self.gridLines.drawTo(self.XSize, 0, self.ZSize) - self.gridLines.moveTo(-(self.XSize), 0, -(self.ZSize)) - self.gridLines.drawTo(self.XSize, 0, -(self.ZSize)) - - if((self.YSize != 0) and (self.YZPlaneShow)): - #Draw y lines across z axis - #YZ Plane - for z in self.myfrange(0, self.ZSize, self.gridStep): - self.gridLines.moveTo(0, -(self.YSize), z) - self.gridLines.drawTo(0, self.YSize, z) - self.gridLines.moveTo(0, -(self.YSize), -z) - self.gridLines.drawTo(0, self.YSize, -z) - - if(self.endCapLinesShow): - #Draw endcap lines - self.gridLines.moveTo(0, -(self.YSize), self.ZSize) - self.gridLines.drawTo(0, self.YSize, self.ZSize) - self.gridLines.moveTo(0, -(self.YSize), -(self.ZSize)) - self.gridLines.drawTo(0, self.YSize, -(self.ZSize)) - - #Check to see if secondary grid lines should be drawn - if(self.subdiv != 0): - - #Draw secondary grid lines - self.subdivLines.setColor(self.subdivColor) - - if(self.XSize != 0): - adjustedstep = self.gridStep / self.subdiv - if((self.YSize != 0) and (self.XYPlaneShow)): - #Draw y lines across x axis starting from center moving out - #XY - for x in self.myfrange(0, self.XSize, adjustedstep): - self.subdivLines.moveTo(x, -(self.YSize), 0) - self.subdivLines.drawTo(x, self.YSize, 0) - self.subdivLines.moveTo(-x, -(self.YSize), 0) - self.subdivLines.drawTo(-x, self.YSize, 0) - - if((self.ZSize != 0) and (self.XZPlaneShow)): - #Draw z lines across x axis starting from center moving out - #XZ - for x in self.myfrange(0, self.XSize, adjustedstep): - self.subdivLines.moveTo(x, 0, -(self.ZSize)) - self.subdivLines.drawTo(x, 0, self.ZSize) - self.subdivLines.moveTo(-x, 0, -(self.ZSize)) - self.subdivLines.drawTo(-x, 0, self.ZSize) - - if(self.YSize != 0): - if((self.YSize != 0) and (self.XYPlaneShow)): - #Draw x lines across y axis - #XY - for y in self.myfrange(0, self.YSize, adjustedstep): - self.subdivLines.moveTo(-(self.XSize), y, 0) - self.subdivLines.drawTo(self.XSize, y, 0) - self.subdivLines.moveTo(-(self.XSize), -y, 0) - self.subdivLines.drawTo(self.XSize, -y, 0) - if((self.ZSize != 0) and (self.YZPlaneShow)): - #Draw z lines across y axis - #YZ - for y in self.myfrange(0, self.YSize, adjustedstep): - self.subdivLines.moveTo(0, y, -(self.ZSize)) - self.subdivLines.drawTo(0, y, self.ZSize) - self.subdivLines.moveTo(0, -y, -(self.ZSize)) - self.subdivLines.drawTo(0, -y, self.ZSize) - - if(self.ZSize != 0): - - if((self.XSize != 0) and (self.XZPlaneShow)): - #Draw x lines across z axis - #XZ - for z in self.myfrange(0, self.ZSize, adjustedstep): - self.subdivLines.moveTo(-(self.XSize), 0, z) - self.subdivLines.drawTo(self.XSize, 0, z) - self.subdivLines.moveTo(-(self.XSize), 0, -z) - self.subdivLines.drawTo(self.XSize, 0, -z) - - if((self.YSize != 0) and (self.YZPlaneShow)): - #Draw y lines across z axis - #YZ - for z in self.myfrange(0, self.ZSize, adjustedstep): - self.subdivLines.moveTo(0, -(self.YSize), z) - self.subdivLines.drawTo(0, self.YSize, z) - self.subdivLines.moveTo(0, -(self.YSize), -z) - self.subdivLines.drawTo(0, self.YSize, -z) - - #Create ThreeAxisGrid nodes and nodepaths - #Create parent node and path - self.parentNode = PandaNode('threeaxisgrid-parentnode') - self.parentNodePath = NodePath(self.parentNode) - - #Create axis lines node and path, then reparent - self.axisLinesNode = self.axisLines.create() - self.axisLinesNodePath = NodePath(self.axisLinesNode) - self.axisLinesNodePath.reparentTo(self.parentNodePath) - - #Create grid lines node and path, then reparent - self.gridLinesNode = self.gridLines.create() - self.gridLinesNodePath = NodePath(self.gridLinesNode) - self.gridLinesNodePath.reparentTo(self.parentNodePath) - - #Create subdivision lines node and path then reparent - self.subdivLinesNode = self.subdivLines.create() - self.subdivLinesNodePath = NodePath(self.subdivLinesNode) - self.subdivLinesNodePath.reparentTo(self.parentNodePath) - - return self.parentNodePath - def myfrange(self, start, stop=None, step=None): - if stop is None: - stop = float(start) - start = 0.0 - if step is None: - step = 1.0 - cur = float(start) - while cur < stop: - yield cur - cur += step \ No newline at end of file diff --git a/QMeta3D/Helpers/__init__.py b/QMeta3D/Helpers/__init__.py deleted file mode 100644 index 18352747..00000000 --- a/QMeta3D/Helpers/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -name="QMeta3D" -__all__ = ["Env_Grid_Maker"] \ No newline at end of file diff --git a/QMeta3D/Meta3DWorld.py b/QMeta3D/Meta3DWorld.py deleted file mode 100644 index 54b319fe..00000000 --- a/QMeta3D/Meta3DWorld.py +++ /dev/null @@ -1,188 +0,0 @@ -import sys -import os - -from core.CustomMouseController import CustomMouseController - -# 获取 RenderPipelineFile 的路径 -render_pipeline_path = './RenderPipelineFile' -# 将该路径添加到 sys.path 中,确保 Python 能够找到它 -project_root = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0, project_root) -sys.path.insert(0, render_pipeline_path) - -from RenderPipelineFile.rpcore import RenderPipeline -_global_render_pipeline = None - -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * - -from panda3d.core import * -from panda3d.core import GraphicsOutput, Texture, ConfigVariableManager, WindowProperties - -from direct.showbase.ShowBase import ShowBase -import platform - -from QMeta3D.QMouseWatcherNode import QMouseWatcherNode - -from RenderPipelineFile.rpcore.render_target import RenderTarget -__all__ = ["Meta3DWorld"] - -_global_world_instance=None -import builtins - - -class Meta3DWorld(ShowBase): - def __init__(self, width=1380, height=750, is_fullscreen=False, size=1.0, clear_color=LVecBase4f(0, 0.5, 0, 1), - name="qMeta3D"): - - global _global_world_instance - global _global_render_pipeline - - _global_world_instance = self - - sort = -100 - self.parent = None - - # 设置基本配置 - loadPrcFileData("", "show-frame-rate-meter 0") - loadPrcFileData("", "window-type onscreen") # 改为正常窗口渲染 - loadPrcFileData("", f"win-size {width} {height}") - loadPrcFileData("", "win-fixed-size #f") # 允许窗口调整大小 - - # 🚀 VR性能优化配置 - loadPrcFileData("", "prefer-single-buffer true") # 减少缓冲区交换开销 - loadPrcFileData("", "gl-force-flush false") # 避免强制glFlush导致的性能损失 - loadPrcFileData("", "sync-video false") # 禁用默认VSync,让OpenVR控制 - loadPrcFileData("", "support-stencil false") # 禁用不必要的模板缓冲区 - loadPrcFileData("", "clock-mode non-real-time") - # loadPrcFileData("", "gl-debug true") - - if (is_fullscreen): - loadPrcFileData("", "fullscreen #t") - - self.render_pipeline = RenderPipeline() - self.render_pipeline.pre_showbase_init() - - ShowBase.__init__(self) - - # 初始化渲染管线并设置可调整大小的标志 - self.render_pipeline.create(self) - _global_render_pipeline = self.render_pipeline - - # 正常窗口渲染不需要额外的输出纹理 - # 注释掉离屏渲染相关的纹理创建 - # self.qt_output_tex = Texture("qt_output_tex") - # self.qt_output_tex.set_format(Texture.F_rgba8) - # self.qt_output_tex.set_component_type(Texture.T_unsigned_byte) - # self.win.add_render_texture(self.qt_output_tex, GraphicsOutput.RTM_copy_ram) - - #self.screenTexture = Texture() - #self.screenTexture.setMinfilter(Texture.FTLinear) - #self.screenTexture.setFormat(Texture.FRgba32) - #self.screenTexture.set_wrap_u(Texture.WM_clamp) - #self.screenTexture.set_wrap_v(Texture.WM_clamp) - - # buff_size_x = int(self.win.get_x_size() * size) - # buff_size_y = int(self.win.get_y_size() * size) - # winprops = WindowProperties() - # winprops.set_size(buff_size_x, buff_size_y) - # - # - # props = FrameBufferProperties() - # props.set_rgb_color(True) - # props.set_rgba_bits(8, 8, 8, 8) - # props.set_depth_bits(8) - - # self.buff = self.graphicsEngine.make_output( - # self.pipe, name, sort, - # props, winprops, - # GraphicsPipe.BF_resizeable, - # self.win.get_gsg(), self.win) - - #self.screenTexture = render_pipeline._final_stage.target.color_tex - - #self.buff.addRenderTexture(self.screenTexture, GraphicsOutput.RTMCopyRam) - # self.buff.set_sort(sort) - - - - #self.cam = self.makeCamera(self.buff) - #self.render_pipeline._showbase.cam=self.makeCamera(self.buff) - - #self.render_pipeline._showbase.cam=self.makeCamera(self.buff) - - #self.render_pipeline._showbase.camera.reparentTo(self.render) - #base.camera.reparentTo(self.render) - #self.cam.reparentTo(self.render) # 可选同步 - - #render_pipeline.set_camera(self.cam) - - #添加渲染效果 - #self.cam = self.render_pipeline._showbase.cam - #self.camNode = self.cam.node() - #self.camLens = self.camNode.get_lens() - self.render_pipeline._showbase.camera = self.render_pipeline._showbase.cam - #self.render_pipeline.daytime_mgr.update() - - - # if clear_color is None: - # self.buff.set_clear_active(GraphicsOutput.RTPColor, False) - # else: - # self.buff.set_clear_color(clear_color) - # self.buff.set_clear_active(GraphicsOutput.RTPColor, True) - - # self.disableMouse() - self.mouse_controller = CustomMouseController(self) - self.mouse_controller.setUp() - - # 添加错误处理钩子 - self.accept("transform_state_error", self._handle_transform_error) - - 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 render_pipeline(self): - """获取 RenderPipeline 实例""" - return self._render_pipeline - - - def set_parent(self, parent: QWidget): - self.parent = parent - self.mouseWatcherNode = QMouseWatcherNode(parent) - - def getAspectRatio(self, win = None): - if win is None and self.parent is not None: - return float(self.parent.width()) / float(self.parent.height()) - else: - return super().getAspectRatio(win) - -def get_render_pipeline(): - """获取全局 RenderPipeline 单例""" - if _global_render_pipeline is None: - raise RuntimeError( - "RenderPipeline has not been initialized yet. Please create a 3DWorld instance first.") - return _global_render_pipeline - -def resize_buffer(self, width: int, height: int): - props = WindowProperties() - props.set_size(width, height) - self.win.request_properties(props) - - # 重新分配输出贴图的大小 - self.qt_output_tex.set_x_size(width) - self.qt_output_tex.set_y_size(height) - - # 更新 lens 的 aspect ratio - if self.camLens: - self.camLens.set_film_size(width, height) # 或 set_aspect_ratio(width / height) - - # 强制更新窗口(有时在 Qt 内嵌时需要) - self.graphicsEngine.open_windows() \ No newline at end of file diff --git a/QMeta3D/QMeta3DWidget.py b/QMeta3D/QMeta3DWidget.py deleted file mode 100644 index 556bd8be..00000000 --- a/QMeta3D/QMeta3DWidget.py +++ /dev/null @@ -1,236 +0,0 @@ -from PyQt5 import QtWidgets, QtGui -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from direct.task.TaskManagerGlobal import taskMgr - -from QMeta3D.QMeta3D_Buttons_Translation import QMeta3D_Button_translation -from QMeta3D.QMeta3D_Keys_Translation import QMeta3D_Key_translation -from QMeta3D.QMeta3D_Modifiers_Translation import QMeta3D_Modifier_translation - -__all__ = ["QMeta3DWidget"] - - -class QMeta3DSynchronizer(QTimer): - def __init__(self, qMeta3DWidget, FPS=60): - QTimer.__init__(self) - self.qMeta3DWidget = qMeta3DWidget - dt = 1000 / FPS - self.setInterval(int(dt)) - self.timeout.connect(self.tick) - - def tick(self): - try: - # 在渲染前清理可能损坏的TransformState对象 - from panda3d.core import TransformState - TransformState.clear_cache() - - taskMgr.step() - self.qMeta3DWidget.update() - except AssertionError as e: - # 专门处理 TransformState has_mat() 断言错误 - if "has_mat" in str(e): - print(f"警告: 检测到TransformState断言错误,已静默处理: {e}") - # 尝试恢复渲染状态 - try: - # 强制清理缓存并重试 - from panda3d.core import TransformState, RenderState - TransformState.clear_cache() - RenderState.clear_cache() - taskMgr.step() - self.qMeta3DWidget.update() - except: - pass - else: - # 重新抛出其他断言错误 - raise - except Exception as e: - # 静默处理其他所有异常 - print(f"警告: 检测到异常,已静默处理: {e}") - - -def get_panda_key_modifiers(evt): - panda_mods = [] - qt_mods = evt.modifiers() - for qt_mod, panda_mod in QMeta3D_Modifier_translation.items(): - if (qt_mods & qt_mod) == qt_mod: - panda_mods.append(panda_mod) - return panda_mods - - -def get_panda_key_modifiers_prefix(evt): - # join all modifiers (except NoModifier, which is None) with '-' - prefix = "-".join([mod for mod in get_panda_key_modifiers(evt) if mod is not None]) - - # if the prefix is not empty, append a '-' - if prefix: - prefix += '-' - - return prefix - - -class QMeta3DWidget(QWidget): - def __init__(self, Meta3DWorld, parent=None, FPS=60, debug=False): - QWidget.__init__(self, parent) - self.rp_sync_requested = False - # set fixed geometry - self.Meta3DWorld = Meta3DWorld - self.Meta3DWorld.set_parent(self) - # Setup a timer in Qt that runs taskMgr.step() to simulate Panda's own main loop - # pandaTimer = QTimer(self) - # pandaTimer.timeout.connect() - # pandaTimer.start(0) - - self.setFocusPolicy(Qt.StrongFocus) - - # Setup another timer that redraws this widget in a specific FPS - # redrawTimer = QTimer(self) - # redrawTimer.timeout.connect(self.update) - # redrawTimer.start(1000/FPS) - - self.paintSurface = QPainter() - self.rotate = QTransform() - self.rotate.rotate(180) - self.out_image = QImage() - - size = self.Meta3DWorld.cam.node().get_lens().get_film_size() - self.initial_film_size = QSizeF(size.x, size.y) - self.initial_size = self.size() - - self.synchronizer = QMeta3DSynchronizer(self, FPS) - self.synchronizer.start() - - self.debug = debug - - def mousePressEvent(self, evt): - button = evt.button() - try: - b = "{}{}".format(get_panda_key_modifiers_prefix(evt), QMeta3D_Button_translation[button]) - if self.debug: - print(b) - messenger.send(b,[{"x":evt.x(),"y":evt.y()}]) - except: - print("Unimplemented button. Please send an issue on github to fix this problem") - - def mouseMoveEvent(self, evt:QtGui.QMouseEvent): - button = evt.button() - try: - b = "mouse-move" - if self.debug: - print(b) - messenger.send(b,[{"x":evt.x(),"y":evt.y()}]) - except: - print("Unimplemented button. Please send an issue on github to fix this problem") - - - def mouseReleaseEvent(self, evt): - button = evt.button() - try: - b = "{}{}-up".format(get_panda_key_modifiers_prefix(evt), QMeta3D_Button_translation[button]) - if self.debug: - print(b) - messenger.send(b,[{"x":evt.x(),"y":evt.y()}]) - except: - print("Unimplemented button. Please send an issue on github to fix this problem") - - def wheelEvent(self, evt): - delta = evt.angleDelta().y() - try: - if self.debug: - print(f"wheel {delta}") - messenger.send('wheel',[{"delta":delta}]) - except: - print("Unimplemented button. Please send an issue on github to fix this problem") - - def keyPressEvent(self, evt): - key = evt.key() - try: - k = "{}{}".format(get_panda_key_modifiers_prefix(evt), QMeta3D_Key_translation[key]) - if self.debug: - print(k) - messenger.send(k) - except: - print("Unimplemented key. Please send an issue on github to fix this problem") - - def keyReleaseEvent(self, evt): - key = evt.key() - try: - k = "{}{}-up".format(get_panda_key_modifiers_prefix(evt), QMeta3D_Key_translation[key]) - if self.debug: - print(k) - messenger.send(k) - except: - print("Unimplemented key. Please send an issue on github to fix this problem") - - def resizeEvent(self, evt): - width = evt.size().width() - height = evt.size().height() - #print(f"width:{width}") - #print(f"height:{height}") - - from Meta3DWorld import resize_buffer - - lens = self.Meta3DWorld.cam.node().get_lens() - - def minimumSizeHint(self): - return QSize(400, 300) - - def paintEvent(self, event): - # 正常窗口渲染模式下,Panda3D 会直接渲染到窗口 - # 不需要从纹理复制数据 - # 只需要确保渲染循环继续运行 - - # 触发一次渲染更新 - if hasattr(self.Meta3DWorld, 'win') and self.Meta3DWorld.win: - # 确保窗口大小匹配 - width = self.width() - height = self.height() - - # 如果窗口大小不匹配,更新窗口属性 - if (self.Meta3DWorld.win.get_x_size() != width or - self.Meta3DWorld.win.get_y_size() != height): - from panda3d.core import WindowProperties - props = WindowProperties() - props.setSize(width, height) - self.Meta3DWorld.win.request_properties(props) - - # 继续下一次更新 - self.update() - - def sync_Meta3d_window_size(self, width, height): - try: - from panda3d.core import WindowProperties - - # 更新窗口属性 - props = WindowProperties() - props.setSize(width, height) - - # 对于正常窗口渲染,直接更新窗口属性 - if self.Meta3DWorld.win: - self.Meta3DWorld.win.request_properties(props) - - # 如果使用 RenderPipeline,更新相关设置 - if hasattr(self.Meta3DWorld, 'render_pipeline'): - try: - from RenderPipelineFile.rpcore.globals import Globals - from panda3d.core import LVecBase2i - - # 更新全局分辨率 - Globals.native_resolution = LVecBase2i(width, height) - - # 重新计算渲染分辨率 - self.Meta3DWorld.render_pipeline._compute_render_resolution() - - # 通知各个管理器处理尺寸变化 - self.Meta3DWorld.render_pipeline.light_mgr.compute_tile_size() - self.Meta3DWorld.render_pipeline.stage_mgr.handle_window_resize() - - if hasattr(self.Meta3DWorld.render_pipeline, 'debugger'): - self.Meta3DWorld.render_pipeline.debugger.handle_window_resize() - - # 触发插件的窗口尺寸变化钩子 - self.Meta3DWorld.render_pipeline.plugin_mgr.trigger_hook("window_resized") - except Exception as rp_e: - print(f"RenderPipeline 尺寸更新失败: {rp_e}") - except Exception as e: - print(f"同步 Meta3D 窗口尺寸失败: {str(e)}") \ No newline at end of file diff --git a/QMeta3D/QMeta3D_Buttons_Translation.py b/QMeta3D/QMeta3D_Buttons_Translation.py deleted file mode 100644 index 3ee09486..00000000 --- a/QMeta3D/QMeta3D_Buttons_Translation.py +++ /dev/null @@ -1,43 +0,0 @@ -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -import sys -__all__ = ["QMeta3D_Keys_Translation.py"] - -QMeta3D_Button_translation ={ -Qt.NoButton:'', -Qt.AllButtons:'unknown', -Qt.LeftButton:'mouse1', -Qt.RightButton:'mouse2', -Qt.MidButton:'mouse3', -Qt.MiddleButton:'mouse3', -Qt.BackButton:'unknown', -Qt.XButton1:'unknown', -Qt.ExtraButton1:'unknown', -Qt.ForwardButton:'unknown', -Qt.XButton2:'unknown', -Qt.ExtraButton2:'unknown', -Qt.TaskButton:'unknown', -Qt.ExtraButton3:'unknown', -Qt.ExtraButton4:'unknown', -Qt.ExtraButton5:'unknown', -Qt.ExtraButton6:'unknown', -Qt.ExtraButton7:'unknown', -Qt.ExtraButton8:'unknown', -Qt.ExtraButton9:'unknown', -Qt.ExtraButton10:'unknown', -Qt.ExtraButton11:'unknown', -Qt.ExtraButton12:'unknown', -Qt.ExtraButton13:'unknown', -Qt.ExtraButton14:'unknown', -Qt.ExtraButton15:'unknown', -Qt.ExtraButton16:'unknown', -Qt.ExtraButton17:'unknown', -Qt.ExtraButton18:'unknown', -Qt.ExtraButton19:'unknown', -Qt.ExtraButton20:'unknown', -Qt.ExtraButton21:'unknown', -Qt.ExtraButton22:'unknown', -Qt.ExtraButton23:'unknown', -Qt.ExtraButton24:'unknown', -} \ No newline at end of file diff --git a/QMeta3D/QMeta3D_Keys_Translation.py b/QMeta3D/QMeta3D_Keys_Translation.py deleted file mode 100644 index 24d29e79..00000000 --- a/QMeta3D/QMeta3D_Keys_Translation.py +++ /dev/null @@ -1,477 +0,0 @@ -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -import sys -__all__ = ["QMeta3D_Keys_Translation.py"] - -QMeta3D_Key_translation ={ -Qt.Key_0:'0', -Qt.Key_1:'1', -Qt.Key_2:'2', -Qt.Key_3:'3', -Qt.Key_4:'4', -Qt.Key_5:'5', -Qt.Key_6:'6', -Qt.Key_7:'7', -Qt.Key_8:'8', -Qt.Key_9:'9', -Qt.Key_A:'a', -Qt.Key_AE:'ae', -Qt.Key_Aacute:'unknown', -Qt.Key_Acircumflex:'unknown', -Qt.Key_AddFavorite:'unknown', -Qt.Key_Adiaeresis:'unknown', -Qt.Key_Agrave:'unknown', -Qt.Key_Alt:'lalt', -Qt.Key_AltGr:'unknown', -Qt.Key_Ampersand:'unknown', -Qt.Key_Any:'unknown', -Qt.Key_Apostrophe:'unknown', -Qt.Key_ApplicationLeft:'unknown', -Qt.Key_ApplicationRight:'unknown', -Qt.Key_Aring:'unknown', -Qt.Key_AsciiCircum:'unknown', -Qt.Key_AsciiTilde:'unknown', -Qt.Key_Asterisk:'unknown', -Qt.Key_At:'unknown', -Qt.Key_Atilde:'unknown', -Qt.Key_AudioCycleTrack:'unknown', -Qt.Key_AudioForward:'unknown', -Qt.Key_AudioRandomPlay:'unknown', -Qt.Key_AudioRepeat:'unknown', -Qt.Key_AudioRewind:'unknown', -Qt.Key_Away:'unknown', -Qt.Key_B:'b', -Qt.Key_Back:'unknown', -Qt.Key_BackForward:'unknown', -Qt.Key_Backslash:'unknown', -Qt.Key_Backspace:'unknown', -Qt.Key_Backtab:'unknown', -Qt.Key_Bar:'unknown', -Qt.Key_BassBoost:'unknown', -Qt.Key_BassDown:'unknown', -Qt.Key_BassUp:'unknown', -Qt.Key_Battery:'unknown', -Qt.Key_Blue:'unknown', -Qt.Key_Bluetooth:'unknown', -Qt.Key_Book:'unknown', -Qt.Key_BraceLeft:'unknown', -Qt.Key_BraceRight:'unknown', -Qt.Key_BracketLeft:'unknown', -Qt.Key_BracketRight:'unknown', -Qt.Key_BrightnessAdjust:'unknown', -Qt.Key_C:'c', -Qt.Key_CD:'unknown', -Qt.Key_Calculator:'unknown', -Qt.Key_Calendar:'unknown', -Qt.Key_Call:'unknown', -Qt.Key_Camera:'unknown', -Qt.Key_CameraFocus:'unknown', -Qt.Key_Cancel:'unknown', -Qt.Key_CapsLock:'unknown', -Qt.Key_Ccedilla:'unknown', -Qt.Key_ChannelDown:'unknown', -Qt.Key_ChannelUp:'unknown', -Qt.Key_Clear:'unknown', -Qt.Key_ClearGrab:'unknown', -Qt.Key_Close:'unknown', -Qt.Key_Codeinput:'unknown', -Qt.Key_Colon:'unknown', -Qt.Key_Comma:'unknown', -Qt.Key_Community:'unknown', -Qt.Key_Context1:'unknown', -Qt.Key_Context2:'unknown', -Qt.Key_Context3:'unknown', -Qt.Key_Context4:'unknown', -Qt.Key_ContrastAdjust:'unknown', -Qt.Key_Control:'control', -Qt.Key_Copy:'unknown', -Qt.Key_Cut:'unknown', -Qt.Key_D:'d', -Qt.Key_DOS:'unknown', -Qt.Key_Dead_A:'unknown', -Qt.Key_Dead_Abovecomma:'unknown', -Qt.Key_Dead_Abovedot:'unknown', -Qt.Key_Dead_Abovereversedcomma:'unknown', -Qt.Key_Dead_Abovering:'unknown', -Qt.Key_Dead_Aboveverticalline:'unknown', -Qt.Key_Dead_Acute:'unknown', -Qt.Key_Dead_Belowbreve:'unknown', -Qt.Key_Dead_Belowcircumflex:'unknown', -Qt.Key_Dead_Belowcomma:'unknown', -Qt.Key_Dead_Belowdiaeresis:'unknown', -Qt.Key_Dead_Belowdot:'unknown', -Qt.Key_Dead_Belowmacron:'unknown', -Qt.Key_Dead_Belowring:'unknown', -Qt.Key_Dead_Belowtilde:'unknown', -Qt.Key_Dead_Belowverticalline:'unknown', -Qt.Key_Dead_Breve:'unknown', -Qt.Key_Dead_Capital_Schwa:'unknown', -Qt.Key_Dead_Caron:'unknown', -Qt.Key_Dead_Cedilla:'unknown', -Qt.Key_Dead_Circumflex:'unknown', -Qt.Key_Dead_Currency:'unknown', -Qt.Key_Dead_Diaeresis:'unknown', -Qt.Key_Dead_Doubleacute:'unknown', -Qt.Key_Dead_Doublegrave:'unknown', -Qt.Key_Dead_E:'unknown', -Qt.Key_Dead_Grave:'unknown', -Qt.Key_Dead_Greek:'unknown', -Qt.Key_Dead_Hook:'unknown', -Qt.Key_Dead_Horn:'unknown', -Qt.Key_Dead_I:'unknown', -Qt.Key_Dead_Invertedbreve:'unknown', -Qt.Key_Dead_Iota:'unknown', -Qt.Key_Dead_Longsolidusoverlay:'unknown', -Qt.Key_Dead_Lowline:'unknown', -Qt.Key_Dead_Macron:'unknown', -Qt.Key_Dead_O:'unknown', -Qt.Key_Dead_Ogonek:'unknown', -Qt.Key_Dead_Semivoiced_Sound:'unknown', -Qt.Key_Dead_Small_Schwa:'unknown', -Qt.Key_Dead_Stroke:'unknown', -Qt.Key_Dead_Tilde:'unknown', -Qt.Key_Dead_U:'unknown', -Qt.Key_Dead_Voiced_Sound:'unknown', -Qt.Key_Dead_a:'unknown', -Qt.Key_Dead_e:'unknown', -Qt.Key_Dead_i:'unknown', -Qt.Key_Dead_o:'unknown', -Qt.Key_Dead_u:'unknown', -Qt.Key_Delete:'delete', -Qt.Key_Direction_L:'unknown', -Qt.Key_Direction_R:'unknown', -Qt.Key_Display:'unknown', -Qt.Key_Documents:'unknown', -Qt.Key_Dollar:'unknown', -Qt.Key_Down:'arrow_down', -Qt.Key_E:'e', -Qt.Key_ETH:'unknown', -Qt.Key_Eacute:'unknown', -Qt.Key_Ecircumflex:'unknown', -Qt.Key_Ediaeresis:'unknown', -Qt.Key_Egrave:'unknown', -Qt.Key_Eisu_Shift:'unknown', -Qt.Key_Eisu_toggle:'unknown', -Qt.Key_Eject:'unknown', -Qt.Key_End:'unknown', -Qt.Key_Enter:'unknown', -Qt.Key_Equal:'unknown', -Qt.Key_Escape:'escape', -Qt.Key_Excel:'unknown', -Qt.Key_Exclam:'unknown', -Qt.Key_Execute:'unknown', -Qt.Key_Exit:'unknown', -Qt.Key_Explorer:'unknown', -Qt.Key_F:'f', -Qt.Key_F1:'f1', -Qt.Key_F10:'f10', -Qt.Key_F11:'f11', -Qt.Key_F12:'f12', -Qt.Key_F13:'f13', -Qt.Key_F14:'f14', -Qt.Key_F15:'f15', -Qt.Key_F16:'f16', -Qt.Key_F17:'f17', -Qt.Key_F18:'f18', -Qt.Key_F19:'unknown', -Qt.Key_F2:'unknown', -Qt.Key_F20:'unknown', -Qt.Key_F21:'unknown', -Qt.Key_F22:'unknown', -Qt.Key_F23:'unknown', -Qt.Key_F24:'unknown', -Qt.Key_F25:'unknown', -Qt.Key_F26:'unknown', -Qt.Key_F27:'unknown', -Qt.Key_F28:'unknown', -Qt.Key_F29:'unknown', -Qt.Key_F3:'f3', -Qt.Key_F30:'unknown', -Qt.Key_F31:'unknown', -Qt.Key_F32:'unknown', -Qt.Key_F33:'unknown', -Qt.Key_F34:'unknown', -Qt.Key_F35:'unknown', -Qt.Key_F4:'f4', -Qt.Key_F5:'f5', -Qt.Key_F6:'f6', -Qt.Key_F7:'f7', -Qt.Key_F8:'f8', -Qt.Key_F9:'f9', -Qt.Key_Favorites:'unknown', -Qt.Key_Finance:'unknown', -Qt.Key_Find:'unknown', -Qt.Key_Flip:'unknown', -Qt.Key_Forward:'unknown', -Qt.Key_G:'g', -Qt.Key_Game:'unknown', -Qt.Key_Go:'unknown', -Qt.Key_Greater:'unknown', -Qt.Key_Green:'unknown', -Qt.Key_Guide:'unknown', -Qt.Key_H:'h', -Qt.Key_Hangul:'unknown', -Qt.Key_Hangul_Banja:'unknown', -Qt.Key_Hangul_End:'unknown', -Qt.Key_Hangul_Hanja:'unknown', -Qt.Key_Hangul_Jamo:'unknown', -Qt.Key_Hangul_Jeonja:'unknown', -Qt.Key_Hangul_PostHanja:'unknown', -Qt.Key_Hangul_PreHanja:'unknown', -Qt.Key_Hangul_Romaja:'unknown', -Qt.Key_Hangul_Special:'unknown', -Qt.Key_Hangul_Start:'unknown', -Qt.Key_Hangup:'unknown', -Qt.Key_Hankaku:'unknown', -Qt.Key_Help:'unknown', -Qt.Key_Henkan:'unknown', -Qt.Key_Hibernate:'unknown', -Qt.Key_Hiragana:'unknown', -Qt.Key_Hiragana_Katakana:'unknown', -Qt.Key_History:'unknown', -Qt.Key_Home:'home', -Qt.Key_HomePage:'unknown', -Qt.Key_HotLinks:'unknown', -Qt.Key_Hyper_L:'unknown', -Qt.Key_Hyper_R:'unknown', -Qt.Key_I:'i', -Qt.Key_Iacute:'unknown', -Qt.Key_Icircumflex:'unknown', -Qt.Key_Idiaeresis:'unknown', -Qt.Key_Igrave:'unknown', -Qt.Key_Info:'unknown', -Qt.Key_Insert:'unknown', -Qt.Key_J:'j', -Qt.Key_K:'k', -Qt.Key_Kana_Lock:'unknown', -Qt.Key_Kana_Shift:'unknown', -Qt.Key_Kanji:'unknown', -Qt.Key_Katakana:'unknown', -Qt.Key_KeyboardBrightnessDown:'unknown', -Qt.Key_KeyboardBrightnessUp:'unknown', -Qt.Key_KeyboardLightOnOff:'unknown', -Qt.Key_L:'l', -Qt.Key_LastNumberRedial:'unknown', -Qt.Key_Launch0:'unknown', -Qt.Key_Launch1:'unknown', -Qt.Key_Launch2:'unknown', -Qt.Key_Launch3:'unknown', -Qt.Key_Launch4:'unknown', -Qt.Key_Launch5:'unknown', -Qt.Key_Launch6:'unknown', -Qt.Key_Launch7:'unknown', -Qt.Key_Launch8:'unknown', -Qt.Key_Launch9:'unknown', -Qt.Key_LaunchA:'unknown', -Qt.Key_LaunchB:'unknown', -Qt.Key_LaunchC:'unknown', -Qt.Key_LaunchD:'unknown', -Qt.Key_LaunchE:'unknown', -Qt.Key_LaunchF:'unknown', -Qt.Key_LaunchG:'unknown', -Qt.Key_LaunchH:'unknown', -Qt.Key_LaunchMail:'unknown', -Qt.Key_LaunchMedia:'unknown', -Qt.Key_Left:'arrow_left', -Qt.Key_Less:'unknown', -Qt.Key_LightBulb:'unknown', -Qt.Key_LogOff:'unknown', -Qt.Key_M:'m', -Qt.Key_MailForward:'unknown', -Qt.Key_Market:'unknown', -Qt.Key_Massyo:'unknown', -Qt.Key_MediaLast:'unknown', -Qt.Key_MediaNext:'unknown', -Qt.Key_MediaPause:'unknown', -Qt.Key_MediaPlay:'unknown', -Qt.Key_MediaPrevious:'unknown', -Qt.Key_MediaRecord:'unknown', -Qt.Key_MediaStop:'unknown', -Qt.Key_MediaTogglePlayPause:'unknown', -Qt.Key_Meeting:'unknown', -Qt.Key_Memo:'unknown', -Qt.Key_Menu:'unknown', -Qt.Key_MenuKB:'unknown', -Qt.Key_MenuPB:'unknown', -Qt.Key_Messenger:'unknown', -Qt.Key_Meta:'unknown', -Qt.Key_MicMute:'unknown', -Qt.Key_MicVolumeDown:'unknown', -Qt.Key_MicVolumeUp:'unknown', -Qt.Key_Minus:'unknown', -Qt.Key_Mode_switch:'unknown', -Qt.Key_MonBrightnessDown:'unknown', -Qt.Key_MonBrightnessUp:'unknown', -Qt.Key_Muhenkan:'unknown', -Qt.Key_Multi_key:'unknown', -Qt.Key_MultipleCandidate:'unknown', -Qt.Key_Music:'unknown', -Qt.Key_MySites:'unknown', -Qt.Key_N:'n', -Qt.Key_New:'unknown', -Qt.Key_News:'unknown', -Qt.Key_No:'unknown', -Qt.Key_Ntilde:'unknown', -Qt.Key_NumLock:'unknown', -Qt.Key_NumberSign:'unknown', -Qt.Key_O:'o', -Qt.Key_Oacute:'unknown', -Qt.Key_Ocircumflex:'unknown', -Qt.Key_Odiaeresis:'unknown', -Qt.Key_OfficeHome:'unknown', -Qt.Key_Ograve:'unknown', -Qt.Key_Ooblique:'unknown', -Qt.Key_Open:'unknown', -Qt.Key_OpenUrl:'unknown', -Qt.Key_Option:'unknown', -Qt.Key_Otilde:'unknown', -Qt.Key_P:'p', -Qt.Key_PageDown:'unknown', -Qt.Key_PageUp:'unknown', -Qt.Key_ParenLeft:'unknown', -Qt.Key_ParenRight:'unknown', -Qt.Key_Paste:'unknown', -Qt.Key_Pause:'unknown', -Qt.Key_Percent:'unknown', -Qt.Key_Period:'unknown', -Qt.Key_Phone:'unknown', -Qt.Key_Pictures:'unknown', -Qt.Key_Play:'unknown', -Qt.Key_Plus:'unknown', -Qt.Key_PowerDown:'unknown', -Qt.Key_PowerOff:'unknown', -Qt.Key_PreviousCandidate:'unknown', -Qt.Key_Print:'unknown', -Qt.Key_Printer:'unknown', -Qt.Key_Q:'q', -Qt.Key_Question:'unknown', -Qt.Key_QuoteDbl:'unknown', -Qt.Key_QuoteLeft:'unknown', -Qt.Key_R:'r', -Qt.Key_Red:'unknown', -Qt.Key_Redo:'unknown', -Qt.Key_Refresh:'unknown', -Qt.Key_Reload:'unknown', -Qt.Key_Reply:'unknown', -Qt.Key_Return:'unknown', -Qt.Key_Right:'arrow_right', -Qt.Key_Romaji:'unknown', -Qt.Key_RotateWindows:'unknown', -Qt.Key_RotationKB:'unknown', -Qt.Key_RotationPB:'unknown', -Qt.Key_S:'s', -Qt.Key_Save:'unknown', -Qt.Key_ScreenSaver:'unknown', -Qt.Key_ScrollLock:'unknown', -Qt.Key_Search:'unknown', -Qt.Key_Select:'unknown', -Qt.Key_Semicolon:'unknown', -Qt.Key_Send:'unknown', -Qt.Key_Settings:'unknown', -Qt.Key_Shift:'unknown', -Qt.Key_Shop:'unknown', -Qt.Key_SingleCandidate:'unknown', -Qt.Key_Slash:'unknown', -Qt.Key_Sleep:'unknown', -Qt.Key_Space:'space', -Qt.Key_Spell:'unknown', -Qt.Key_SplitScreen:'unknown', -Qt.Key_Standby:'unknown', -Qt.Key_Stop:'unknown', -Qt.Key_Subtitle:'unknown', -Qt.Key_Super_L:'unknown', -Qt.Key_Super_R:'unknown', -Qt.Key_Support:'unknown', -Qt.Key_Suspend:'unknown', -Qt.Key_SysReq:'unknown', -Qt.Key_T:'t', -Qt.Key_THORN:'unknown', -Qt.Key_Tab:'unknown', -Qt.Key_TaskPane:'unknown', -Qt.Key_Terminal:'unknown', -Qt.Key_Time:'unknown', -Qt.Key_ToDoList:'unknown', -Qt.Key_ToggleCallHangup:'unknown', -Qt.Key_Tools:'unknown', -Qt.Key_TopMenu:'unknown', -Qt.Key_TouchpadOff:'unknown', -Qt.Key_TouchpadOn:'unknown', -Qt.Key_TouchpadToggle:'unknown', -Qt.Key_Touroku:'unknown', -Qt.Key_Travel:'unknown', -Qt.Key_TrebleDown:'unknown', -Qt.Key_TrebleUp:'unknown', -Qt.Key_U:'u', -Qt.Key_UWB:'unknown', -Qt.Key_Uacute:'unknown', -Qt.Key_Ucircumflex:'unknown', -Qt.Key_Udiaeresis:'unknown', -Qt.Key_Ugrave:'unknown', -Qt.Key_Underscore:'unknown', -Qt.Key_Undo:'unknown', -Qt.Key_Up:'arrow_up', -Qt.Key_V:'v', -Qt.Key_Video:'unknown', -Qt.Key_View:'unknown', -Qt.Key_VoiceDial:'unknown', -Qt.Key_VolumeDown:'unknown', -Qt.Key_VolumeMute:'unknown', -Qt.Key_VolumeUp:'unknown', -Qt.Key_W:'w', -Qt.Key_WLAN:'unknown', -Qt.Key_WWW:'unknown', -Qt.Key_WakeUp:'unknown', -Qt.Key_WebCam:'unknown', -Qt.Key_Word:'unknown', -Qt.Key_X:'x', -Qt.Key_Xfer:'unknown', -Qt.Key_Y:'y', -Qt.Key_Yacute:'unknown', -Qt.Key_Yellow:'unknown', -Qt.Key_Yes:'unknown', -Qt.Key_Z:'z', -Qt.Key_Zenkaku:'unknown', -Qt.Key_Zenkaku_Hankaku:'unknown', -Qt.Key_Zoom:'unknown', -Qt.Key_ZoomIn:'unknown', -Qt.Key_ZoomOut:'unknown', -Qt.Key_acute:'unknown', -Qt.Key_brokenbar:'unknown', -Qt.Key_cedilla:'unknown', -Qt.Key_cent:'unknown', -Qt.Key_copyright:'unknown', -Qt.Key_currency:'unknown', -Qt.Key_degree:'unknown', -Qt.Key_diaeresis:'unknown', -Qt.Key_division:'unknown', -Qt.Key_exclamdown:'unknown', -Qt.Key_guillemotleft:'unknown', -Qt.Key_guillemotright:'unknown', -Qt.Key_hyphen:'unknown', -Qt.Key_iTouch:'unknown', -Qt.Key_macron:'unknown', -Qt.Key_masculine:'unknown', -Qt.Key_mu:'unknown', -Qt.Key_multiply:'unknown', -Qt.Key_nobreakspace:'unknown', -Qt.Key_notsign:'unknown', -Qt.Key_onehalf:'unknown', -Qt.Key_onequarter:'unknown', -Qt.Key_onesuperior:'unknown', -Qt.Key_ordfeminine:'unknown', -Qt.Key_paragraph:'unknown', -Qt.Key_periodcentered:'unknown', -Qt.Key_plusminus:'unknown', -Qt.Key_questiondown:'unknown', -Qt.Key_registered:'unknown', -Qt.Key_section:'unknown', -Qt.Key_ssharp:'unknown', -Qt.Key_sterling:'unknown', -Qt.Key_threequarters:'unknown', -Qt.Key_threesuperior:'unknown', -Qt.Key_twosuperior:'unknown', -Qt.Key_unknown:'unknown', -Qt.Key_ydiaeresis:'unknown', -Qt.Key_yen:'unknown', -} \ No newline at end of file diff --git a/QMeta3D/QMeta3D_Modifiers_Translation.py b/QMeta3D/QMeta3D_Modifiers_Translation.py deleted file mode 100644 index 45489333..00000000 --- a/QMeta3D/QMeta3D_Modifiers_Translation.py +++ /dev/null @@ -1,15 +0,0 @@ -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -import sys -__all__ = ["QMeta3D_Modifiers_Translation.py"] - -QMeta3D_Modifier_translation ={ -Qt.NoModifier:None, -Qt.ShiftModifier:'shift', -Qt.ControlModifier:'control', -Qt.AltModifier:'alt', -Qt.MetaModifier:'unknown', -Qt.KeypadModifier:'unknown', -Qt.GroupSwitchModifier:'unknown', -} \ No newline at end of file diff --git a/QMeta3D/QMouseWatcherNode.py b/QMeta3D/QMouseWatcherNode.py deleted file mode 100644 index 195e6457..00000000 --- a/QMeta3D/QMouseWatcherNode.py +++ /dev/null @@ -1,27 +0,0 @@ -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * - -from panda3d.core import * - -__all__ = ["QMouseWatcherNode"] - - -class QMouseWatcherNode(MouseWatcher): - def __init__(self, parent): - super().__init__() - - self.parent = parent - - def getMouse(self, *args, **kwargs): - pos = self.parent.mapFromGlobal(QCursor.pos()) - - rel_x = -1 + 2 * pos.x() / self.parent.width() - rel_y = -1 + 2 * pos.y() / self.parent.height() - - rel_y = -rel_y - - return LPoint2(rel_x, rel_y) - - def hasMouse(self): - return isinstance(self.parent, QWidget) diff --git a/QMeta3D/Tools/__init__.py b/QMeta3D/Tools/__init__.py deleted file mode 100644 index 0697112f..00000000 --- a/QMeta3D/Tools/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -name="QMeta3D" -__all__ = ["generate_qt_to_pd3d_translator"] \ No newline at end of file diff --git a/QMeta3D/Tools/generate_qt_to_pd3d_translator.py b/QMeta3D/Tools/generate_qt_to_pd3d_translator.py deleted file mode 100644 index d3b2c071..00000000 --- a/QMeta3D/Tools/generate_qt_to_pd3d_translator.py +++ /dev/null @@ -1,11 +0,0 @@ -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * - -H=Qt.__dict__ -lst = [a for a in H if a.startswith("Key_")] -QMeta3D_Key_translation = "QMeta3D_Key_translation ={" -for i in range(len(lst)): - QMeta3D_Key_translation += "Qt.{}:'unknown',\n".format(lst[i]) -QMeta3D_Key_translation += "}" -print(QMeta3D_Key_translation) \ No newline at end of file diff --git a/QMeta3D/__init__.py b/QMeta3D/__init__.py deleted file mode 100644 index e5c409c5..00000000 --- a/QMeta3D/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -name="QMeta3D" -__all__ = ["QMeta3DWidget.py", "Meta3DWorld.py", "QMeta3D_Keys_Translation.py"] \ No newline at end of file diff --git a/core/render_pipeline_utils.py b/core/render_pipeline_utils.py new file mode 100644 index 00000000..5e15484f --- /dev/null +++ b/core/render_pipeline_utils.py @@ -0,0 +1,20 @@ +""" +渲染管线工具模块 +提供全局渲染管线实例的访问功能 +""" + +# 全局渲染管线实例 +_global_render_pipeline = None + +def get_render_pipeline(): + """获取全局 RenderPipeline 单例""" + global _global_render_pipeline + if _global_render_pipeline is None: + raise RuntimeError( + "RenderPipeline has not been initialized yet. Please create a CoreWorld instance first.") + return _global_render_pipeline + +def set_render_pipeline(pipeline): + """设置全局渲染管线实例""" + global _global_render_pipeline + _global_render_pipeline = pipeline \ No newline at end of file diff --git a/core/world.py b/core/world.py index 1132f056..77077142 100644 --- a/core/world.py +++ b/core/world.py @@ -1,1192 +1,1248 @@ - -import math -import warnings - -from direct.actor.Actor import Actor - -warnings.filterwarnings("ignore", category=DeprecationWarning) - -from QMeta3D.Meta3DWorld import Meta3DWorld -from panda3d.core import (CardMaker, Vec4, Vec3, AmbientLight, DirectionalLight, - Point3, WindowProperties,Material,LColor) -from direct.showbase.ShowBaseGlobal import globalClock -from scene.scene_manager import SceneManager - -# 尝试导入插件管理器(如果存在) -try: - from plugins.plugin_manager import PluginManager - PLUGIN_SUPPORT = True -except ImportError: - PLUGIN_SUPPORT = False - print("⚠ 插件管理器未找到,插件功能将不可用") - -class CoreWorld(Meta3DWorld): - """核心世界功能类 - 负责基础的3D世界设置和核心功能""" - - def __init__(self): - super().__init__() - - # 初始化基础属性 - self.qtWidget = None # Qt部件引用(用于获取准确的渲染区域尺寸) - - # 设置相机控制参数 - #self.cameraSpeed = 200.0 # 移动速度 - self.cameraSpeed=20.0 - #self.cameraRotateSpeed = 40.0 # 旋转速度 - self.cameraRotateSpeed = 10.0 - - # 鼠标控制相关变量 - self.lastMouseX = 0 - self.lastMouseY = 0 - self.mouseRightPressed = False - - # 初始化插件管理器(如果支持) - if PLUGIN_SUPPORT: - self.plugin_manager = PluginManager(self) - else: - self.plugin_manager = None - - # 初始化世界 - self._setupResourcePaths() - self._setupCamera() - self._setupLighting() - self._setupGround() - self._loadFont() - - 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}") - - # 设置纹理搜索路径 - from panda3d.core import getTexturePath - texture_path = getTexturePath() - if not texture_path.findFile(resources_filename): - texture_path.appendDirectory(resources_filename) - - 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 - - # 创建材质编辑器组件 - from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel - - material_widget = QWidget() - layout = QVBoxLayout() - - # 添加基本的材质编辑控件 - layout.addWidget(QLabel("材质编辑器")) - # 这里可以添加更多的材质编辑控件 - - 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失败") - + +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 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") # 允许窗口调整大小 + + # 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") + + if is_fullscreen: + loadPrcFileData("", "fullscreen #t") + + # 创建渲染管线 + self.render_pipeline = RenderPipeline() + self.render_pipeline.pre_showbase_init() + + # 初始化 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失败") + diff --git a/core/world.py.backup b/core/world.py.backup new file mode 100644 index 00000000..1132f056 --- /dev/null +++ b/core/world.py.backup @@ -0,0 +1,1192 @@ + +import math +import warnings + +from direct.actor.Actor import Actor + +warnings.filterwarnings("ignore", category=DeprecationWarning) + +from QMeta3D.Meta3DWorld import Meta3DWorld +from panda3d.core import (CardMaker, Vec4, Vec3, AmbientLight, DirectionalLight, + Point3, WindowProperties,Material,LColor) +from direct.showbase.ShowBaseGlobal import globalClock +from scene.scene_manager import SceneManager + +# 尝试导入插件管理器(如果存在) +try: + from plugins.plugin_manager import PluginManager + PLUGIN_SUPPORT = True +except ImportError: + PLUGIN_SUPPORT = False + print("⚠ 插件管理器未找到,插件功能将不可用") + +class CoreWorld(Meta3DWorld): + """核心世界功能类 - 负责基础的3D世界设置和核心功能""" + + def __init__(self): + super().__init__() + + # 初始化基础属性 + self.qtWidget = None # Qt部件引用(用于获取准确的渲染区域尺寸) + + # 设置相机控制参数 + #self.cameraSpeed = 200.0 # 移动速度 + self.cameraSpeed=20.0 + #self.cameraRotateSpeed = 40.0 # 旋转速度 + self.cameraRotateSpeed = 10.0 + + # 鼠标控制相关变量 + self.lastMouseX = 0 + self.lastMouseY = 0 + self.mouseRightPressed = False + + # 初始化插件管理器(如果支持) + if PLUGIN_SUPPORT: + self.plugin_manager = PluginManager(self) + else: + self.plugin_manager = None + + # 初始化世界 + self._setupResourcePaths() + self._setupCamera() + self._setupLighting() + self._setupGround() + self._loadFont() + + 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}") + + # 设置纹理搜索路径 + from panda3d.core import getTexturePath + texture_path = getTexturePath() + if not texture_path.findFile(resources_filename): + texture_path.appendDirectory(resources_filename) + + 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 + + # 创建材质编辑器组件 + from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel + + material_widget = QWidget() + layout = QVBoxLayout() + + # 添加基本的材质编辑控件 + layout.addWidget(QLabel("材质编辑器")) + # 这里可以添加更多的材质编辑控件 + + 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失败") + diff --git a/scene/scene_manager.py b/scene/scene_manager.py index 23be63e0..1a2e9eae 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -24,7 +24,7 @@ import inspect from pathlib import Path from panda3d.egg import EggData, EggVertexPool from direct.actor.Actor import Actor -from QMeta3D.Meta3DWorld import get_render_pipeline +from core.render_pipeline_utils import get_render_pipeline from RenderPipelineFile.rpplugins.smaa.jitters import halton_seq from scene import util @@ -2540,7 +2540,7 @@ class SceneManager: """重新创建点光源""" try: from RenderPipelineFile.rpcore import PointLight - from QMeta3D.Meta3DWorld import get_render_pipeline + from core.render_pipeline_utils import get_render_pipeline # 创建点光源对象 light = PointLight() @@ -2711,7 +2711,7 @@ class SceneManager: """创建聚光灯 - 支持多选创建,优化版本""" try: from RenderPipelineFile.rpcore import SpotLight - from QMeta3D.Meta3DWorld import get_render_pipeline + from core.render_pipeline_utils import get_render_pipeline from panda3d.core import Vec3, NodePath from PyQt5.QtCore import Qt @@ -2820,7 +2820,7 @@ class SceneManager: """创建点光源 - 支持多选创建,优化版本""" try: from RenderPipelineFile.rpcore import PointLight - from QMeta3D.Meta3DWorld import get_render_pipeline + from core.render_pipeline_utils import get_render_pipeline from panda3d.core import Vec3, NodePath from PyQt5.QtCore import Qt @@ -2975,7 +2975,7 @@ class SceneManager: return # 获取RenderPipeline实例 - from QMeta3D.Meta3DWorld import get_render_pipeline + from core.render_pipeline_utils import get_render_pipeline render_pipeline = get_render_pipeline() if not render_pipeline: @@ -4537,7 +4537,7 @@ except Exception as e: # 检查是否使用RenderPipeline if hasattr(self.world, 'render_pipeline') and self.world.render_pipeline: from RenderPipelineFile.rpcore import SpotLight - from QMeta3D.Meta3DWorld import get_render_pipeline + from core.render_pipeline_utils import get_render_pipeline # 创建RenderPipeline聚光灯 from panda3d.core import Vec3 @@ -4615,7 +4615,7 @@ except Exception as e: # 检查是否使用RenderPipeline if hasattr(self.world, 'render_pipeline') and self.world.render_pipeline: from RenderPipelineFile.rpcore import PointLight - from QMeta3D.Meta3DWorld import get_render_pipeline + from core.render_pipeline_utils import get_render_pipeline # 创建RenderPipeline点光源 pointlight = PointLight()