252 lines
9.0 KiB
Python
252 lines
9.0 KiB
Python
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)}") |