EG/QPanda3D/QPanda3DWidget.py
2025-07-25 11:04:59 +08:00

261 lines
9.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8-*-
"""
Module : QPanda3DWidget
Author : Saifeddine ALOUI
Description :
This is the QWidget to be inserted in your standard PyQt5 application.
It takes a Panda3DWorld object at init time.
You should first create the Panda3DWorkd object before creating this widget.
"""
# PyQt imports
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
# Panda imports
from panda3d.core import Texture, WindowProperties, CallbackGraphicsWindow
from panda3d.core import loadPrcFileData
from QPanda3D.QPanda3D_Buttons_Translation import QPanda3D_Button_translation
from QPanda3D.QPanda3D_Keys_Translation import QPanda3D_Key_translation
from QPanda3D.QPanda3D_Modifiers_Translation import QPanda3D_Modifier_translation
__all__ = ["QPanda3DWidget"]
class QPanda3DSynchronizer(QTimer):
def __init__(self, qPanda3DWidget, FPS=60):
QTimer.__init__(self)
self.qPanda3DWidget = qPanda3DWidget
dt = 1000 / FPS
self.setInterval(int(dt))
self.timeout.connect(self.tick)
def tick(self):
taskMgr.step()
self.qPanda3DWidget.update()
def get_panda_key_modifiers(evt):
panda_mods = []
qt_mods = evt.modifiers()
for qt_mod, panda_mod in QPanda3D_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 QPanda3DWidget(QWidget):
"""
An interactive panda3D QWidget
Parent : Parent QT Widget
FPS : Number of frames per socond to refresh the screen
debug: Switch printing key events to console on/off
"""
def __init__(self, panda3DWorld, parent=None, FPS=60, debug=False):
QWidget.__init__(self, parent)
self.rp_sync_requested = False
# set fixed geometry
self.panda3DWorld = panda3DWorld
self.panda3DWorld.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.panda3DWorld.cam.node().get_lens().get_film_size()
self.initial_film_size = QSizeF(size.x, size.y)
self.initial_size = self.size()
self.synchronizer = QPanda3DSynchronizer(self, FPS)
self.synchronizer.start()
self.debug = debug
def mousePressEvent(self, evt):
button = evt.button()
try:
b = "{}{}".format(get_panda_key_modifiers_prefix(evt), QPanda3D_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), QPanda3D_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), QPanda3D_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), QPanda3D_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):
# new_width = evt.size().width()
# new_height = evt.size().height()
#
# # 更新 Panda3D 相机的 Film Size保持视野正常
# lens = self.panda3DWorld.cam.node().get_lens()
# lens.set_film_size(
# self.initial_film_size.width() * new_width / self.initial_size.width(),
# self.initial_film_size.height() * new_height / self.initial_size.height()
# )
#
# if hasattr(self.panda3DWorld, "win"):
# winprops = WindowProperties()
# winprops.set_size(new_width, new_height)
# self.panda3DWorld.win.request_properties(winprops)
lens = self.panda3DWorld.cam.node().get_lens()
lens.set_film_size(self.initial_film_size.width() * evt.size().width() / self.initial_size.width(),
self.initial_film_size.height() * evt.size().height() / self.initial_size.height())
#self.panda3DWorld.buff.setSize(evt.size().width(), evt.size().height())
def minimumSizeHint(self):
return QSize(400, 300)
# Use the paint event to pull the contents of the panda texture to the widget
# def paintEvent(self, event):
# if self.panda3DWorld.screenTexture.mightHaveRamImage():
# tex = self.panda3DWorld.screenTexture
# gsg = base.win.getGsg()
# #self.panda3DWorld.screenTexture.store(gsg)
# base.graphicsEngine.extractTextureData(tex, gsg)
# self.panda3DWorld.screenTexture.setFormat(Texture.FRgba32)
# data = self.panda3DWorld.screenTexture.getRamImage().getData()
# img = QImage(data, self.panda3DWorld.screenTexture.getXSize(), self.panda3DWorld.screenTexture.getYSize(),
# QImage.Format_ARGB32).mirrored()
# self.paintSurface.begin(self)
# self.paintSurface.drawImage(0, 0, img)
# self.paintSurface.end()
# def paintEvent(self, event):
# tex = self.panda3DWorld.qt_output_tex
#
# gsg = base.win.getGsg()
#
#
# if not self.rp_sync_requested:
# base.graphicsEngine.extractTextureData(tex, gsg)
# self.rp_sync_requested = True
# self.update()
# return
#
# if tex.hasRamImage():
# data = tex.getRamImage().getData()
# width = tex.getXSize()
# height = tex.getYSize()
#
# # ✅ 应该 data 长度 = width * height * 4
# img = QImage(data, width, height, QImage.Format_ARGB32).mirrored()
#
# painter = QPainter(self)
# painter.drawImage(0, 0, img)
# painter.end()
#
# self.rp_sync_requested = False
# else:
# print("⚠️ Texture has no RAM image yet, retrying next frame")
# self.rp_sync_requested = False
# self.update()
def paintEvent(self, event):
tex = self.panda3DWorld.qt_output_tex
gsg = base.win.getGsg()
if not tex.hasRamImage():
base.graphicsEngine.extractTextureData(tex, gsg)
self.update() # 请求下一帧更新
return
data = tex.getRamImage().getData()
width = tex.getXSize()
height = tex.getYSize()
expected_len = width * height * 4
if len(data) != expected_len:
print(f"⚠️ 像素数据长度异常({len(data)} != {expected_len}),跳过绘制")
self.update()
return
# 一切正常才绘制
img = QImage(data, width, height, QImage.Format_ARGB32).mirrored()
painter = QPainter(self)
painter.drawImage(0, 0, img)
painter.end()