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): tex = self.Meta3DWorld.qt_output_tex gsg = base.win.getGsg() if not gsg: self.update() return if not tex.hasRamImage(): base.graphicsEngine.extractTextureData(tex, gsg) self.update() return data = tex.getRamImage().getData() tex_width = tex.getXSize() tex_height = tex.getYSize() expected_len = tex_width * tex_height * 4 if len(data) != expected_len: print(f"⚠️ 像素数据长度异常({len(data)} != {expected_len}),跳过绘制") self.update() return img = QImage(data, tex_width, tex_height, QImage.Format_ARGB32).mirrored() widget_width = self.width() widget_height = self.height() painter = QPainter(self) # 【保持宽高比的缩放】 scaled_img = img.scaled(widget_width, widget_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) # 居中绘制 x_offset = (widget_width - scaled_img.width()) // 2 y_offset = (widget_height - scaled_img.height()) // 2 painter.drawImage(x_offset, y_offset, scaled_img) painter.end() def sync_Meta3d_window_size(self, width, height): try: from panda3d.core import WindowProperties, LVecBase2i # 确保尺寸是4的倍数(RenderPipeline 要求) adjusted_width = width - width % 4 adjusted_height = height - height % 4 # 更新窗口属性 props = WindowProperties() props.setSize(adjusted_width, adjusted_height) # 对于 offscreen 渲染,直接更新窗口属性 if self.Meta3DWorld.win: self.Meta3DWorld.win.request_properties(props) # 手动触发 RenderPipeline 的尺寸更新逻辑 if hasattr(self.Meta3DWorld, 'render_pipeline'): # 更新全局分辨率 from RenderPipelineFile.rpcore.globals import Globals Globals.native_resolution = LVecBase2i(adjusted_width, adjusted_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 e: print(f"同步 Meta3D 窗口尺寸失败: {str(e)}")