如何让动画在 PyQt5 Qt3D 中正常工作?

How do I get the animation to work correctly in PyQt5 Qt3D?

我试图从中的示例中了解 PyQt5 中的动画操作 https://doc.qt.io/qt-5.10/qt3d-simple-cpp-example.html

我已将代码翻译成 Python 并且能够 运行 但没有动画。我在想我需要将信号连接到某处的 Update() 方法,但我正在理解如何做到这一点。

这里是 PyQt5 (python 3) 代码:

######################################
#
# Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
# Contact: https:#www.qt.io/licensing/
#
# This file is part of the Qt3D module of the Qt Toolkit.
#
# $QT_BEGIN_LICENSE:BSD$
# Commercial License Usage
# Licensees holding valid commercial Qt licenses may use this file in
# accordance with the commercial license agreement provided with the
# Software or, alternatively, in accordance with the terms contained in
# a written agreement between you and The Qt Company. For licensing terms
# and conditions see https:#www.qt.io/terms-conditions. For further
# information use the contact form at https:#www.qt.io/contact-us.
#
# BSD License Usage
# Alternatively, you may use this file under the terms of the BSD license
# as follows:
#
# "Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
#   * Neither the name of The Qt Company Ltd nor the names of its
#     contributors may be used to endorse or promote products derived
#     from this software without specific prior written permission.
#
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES LOSS OF USE,
# DATA, OR PROFITS OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
#
# $QT_END_LICENSE$
#
######################################/

# see https://doc.qt.io/qt-5.10/qt3d-examples.html

########################
# requirements.txt
# PyQt3D==5.10.1
# PyQt5==5.10.1
# pyqt5-tools==5.9.0.1.2
# QScintilla==2.10.4
# sip==4.19.8

from PyQt5.Qt3DCore import QEntity, QTransform
from PyQt5.Qt3DExtras import QTorusMesh, QPhongMaterial, \
    QSphereMesh, Qt3DWindow, QOrbitCameraController

from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QVector3D, QQuaternion, QMatrix4x4
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QPropertyAnimation

import sys


def fuzzyCompareDouble(p1, p2):
    """
    compares 2 double as points
    """
    return abs(p1 - p2) * 100000. <= min(abs(p1), abs(p2))

class OrbitTransformController(QTransform):
    targetChanged = pyqtSignal()
    angleChanged = pyqtSignal()
    radiusChanged = pyqtSignal()

    def __init__(self, parent):
        super().__init__(parent)
        self.m_target = QTransform()
        self.m_matrix = QMatrix4x4()
        self.m_radius = 1.0
        self.m_angle = 0.0
        #self.target.connect(self.targetChanged.emit)
        #self.angle.connect(self.angleChanged.emit)
        #self.radius.connect(self.radiusChanged.emit)

    def setTarget(self, target):
        if (self.m_target != target):
            self.m_target = target
        self.targetChanged.emit()

    def target(self, ): #  : # method of "", returning Qt3DCore.QTransform *OrbitTransformController (const)
        return self.m_target

    def setRadius(self, radius): #  : # method of "", returning void OrbitTransformController ()
        if not fuzzyCompareDouble(radius, self.m_radius):
            self.m_radius = radius
        self.radiusChanged.emit()

    def radius(self, ): #  : # method of "", returning float OrbitTransformController (const)
        return self.m_radius

    def setAngle(self, angle): #  : # method of "", returning void OrbitTransformController ()
        if not fuzzyCompareDouble(angle, self.m_angle):
            self.m_angle = angle
        print("setting angle %f" % angle)
        self.updateMatrix()
        self.angleChanged.emit()

    def angle(self, ): #  : # method of "", returning float OrbitTransformController (const)
        return self.m_angle

    def updateMatrix(self, ): #  : # method of "", returning void OrbitTransformController ()
        self.m_matrix.setToIdentity()
        self.m_matrix.rotate(self.m_angle, QVector3D(0.0, 1.0, 0.0))
        self.m_matrix.translate(self.m_radius, 0.0, 0.0)
        self.m_target.setMatrix(self.m_matrix)

def createScene():
    # Root entity
    rootEntity = QEntity()

    # Material
    material = QPhongMaterial(rootEntity)

    # Torus
    torusEntity = QEntity(rootEntity)
    # Qt3DExtras.QTorusMesh *
    torusMesh = QTorusMesh()
    torusMesh.setRadius(5)
    torusMesh.setMinorRadius(1)
    torusMesh.setRings(100)
    torusMesh.setSlices(20)

    #Qt3DCore.QTransform *
    torusTransform = QTransform()
    torusTransform.setScale3D(QVector3D(1.5, 1, 0.5))
    torusTransform.setRotation(QQuaternion.fromAxisAndAngle(QVector3D(1, 0, 0), 45.0))

    torusEntity.addComponent(torusMesh)
    torusEntity.addComponent(torusTransform)
    torusEntity.addComponent(material)

    # Sphere
    sphereEntity = QEntity(rootEntity)
    sphereMesh = QSphereMesh()
    sphereMesh.setRadius(3)

    # Qt3DCore.QTransform *
    sphereTransform = QTransform()
    #OrbitTransformController *
    controller = OrbitTransformController(sphereTransform)
    controller.setTarget(sphereTransform)
    controller.setRadius(20.0)
    # QPropertyAnimation *
    sphereRotateTransformAnimation = QPropertyAnimation(sphereTransform, b"angle")
    sphereRotateTransformAnimation.setTargetObject(controller)
    # sphereRotateTransformAnimation.setPropertyName("angle")  This is included when the object is created
    sphereRotateTransformAnimation.setStartValue(0)
    sphereRotateTransformAnimation.setEndValue(360)
    sphereRotateTransformAnimation.setDuration(10000)
    sphereRotateTransformAnimation.setLoopCount(-1)
    sphereRotateTransformAnimation.start()

    sphereEntity.addComponent(sphereMesh)
    sphereEntity.addComponent(sphereTransform)
    sphereEntity.addComponent(material)

    return rootEntity


if __name__ == "__main__":
    app = QApplication(sys.argv)
    view = Qt3DWindow()
    scene = createScene()

    # Camera
    camera = view.camera()
    camera.lens().setPerspectiveProjection(45.0, 16.0/9.0, 0.1, 1000.0)
    camera.setPosition(QVector3D(0, 0, 40.0))
    camera.setViewCenter(QVector3D(0, 0, 0))

    # For camera controls
    camController = QOrbitCameraController(scene)
    camController.setLinearSpeed( 50.0 )
    camController.setLookSpeed( 180.0 )
    camController.setCamera(camera)

    view.setRootEntity(scene)
    view.show()

    sys.exit(app.exec())

有什么我想念的吗?有一些 O_OBJECT 代码我不知道如何翻译,我怀疑这就是问题所在:

Q_OBJECT
Q_PROPERTY(Qt3DCore::QTransform* target READ target WRITE setTarget NOTIFY targetChanged)
Q_PROPERTY(float radius READ radius WRITE setRadius NOTIFY radiusChanged)
Q_PROPERTY(float angle READ angle WRITE setAngle NOTIFY angleChanged)

如有任何建议,我们将不胜感激。 这是(非动画)结果的屏幕截图。

你必须pyqtProperty:

angle = pyqtProperty(float, fget=angle, fset=setAngle, notify=angleChanged)
radius = pyqtProperty(float, fget=radius, fset=setRadius, notify=radiusChanged)
target = pyqtProperty(QTransform, fget=target, fset=setTarget, notify=targetChanged)

完整代码:

from PyQt5.Qt3DCore import QEntity, QTransform
from PyQt5.Qt3DExtras import QTorusMesh, QPhongMaterial, \
    QSphereMesh, Qt3DWindow, QOrbitCameraController

from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QVector3D, QQuaternion, QMatrix4x4
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QPropertyAnimation, pyqtProperty

import sys


def fuzzyCompareDouble(p1, p2):
    """
    compares 2 double as points
    """
    return abs(p1 - p2) * 100000. <= min(abs(p1), abs(p2))

class OrbitTransformController(QTransform):
    targetChanged = pyqtSignal()
    angleChanged = pyqtSignal()
    radiusChanged = pyqtSignal()

    def __init__(self, parent):
        super().__init__(parent)
        self.m_target = QTransform()
        self.m_matrix = QMatrix4x4()
        self.m_radius = 1.0
        self.m_angle = 0.0

    def target(self):
        return self.m_target

    def setTarget(self, target):
        if self.m_target == target:
            return
        self.m_target = target
        self.targetChanged.emit()


    def setRadius(self, radius):
        if fuzzyCompareDouble(radius, self.m_radius):
            return
        self.m_radius = radius
        self.radiusChanged.emit()

    def radius(self, ): #  : # method of "", returning float OrbitTransformController (const)
        return self.m_radius

    def setAngle(self, angle): #  : # method of "", returning void OrbitTransformController ()
        if fuzzyCompareDouble(angle, self.m_angle):
            return
        self.m_angle = angle
        self.updateMatrix()
        self.angleChanged.emit()

    def angle(self): #  : # method of "", returning float OrbitTransformController (const)
        return self.m_angle

    def updateMatrix(self, ): #  : # method of "", returning void OrbitTransformController ()
        self.m_matrix.setToIdentity()
        self.m_matrix.rotate(self.m_angle, QVector3D(0.0, 1.0, 0.0))
        self.m_matrix.translate(self.m_radius, 0.0, 0.0)
        self.m_target.setMatrix(self.m_matrix)

    angle = pyqtProperty(float, fget=angle, fset=setAngle, notify=angleChanged)
    radius = pyqtProperty(float, fget=radius, fset=setRadius, notify=radiusChanged)
    target = pyqtProperty(float, fget=target, fset=setTarget, notify=angleChanged)

def createScene():
    # Root entity
    rootEntity = QEntity()

    # Material
    material = QPhongMaterial(rootEntity)

    # Torus
    torusEntity = QEntity(rootEntity)
    # Qt3DExtras.QTorusMesh *
    torusMesh = QTorusMesh()
    torusMesh.setRadius(5)
    torusMesh.setMinorRadius(1)
    torusMesh.setRings(100)
    torusMesh.setSlices(20)

    #Qt3DCore.QTransform *
    torusTransform = QTransform()
    torusTransform.setScale3D(QVector3D(1.5, 1, 0.5))
    torusTransform.setRotation(QQuaternion.fromAxisAndAngle(QVector3D(1, 0, 0), 45.0))

    torusEntity.addComponent(torusMesh)
    torusEntity.addComponent(torusTransform)
    torusEntity.addComponent(material)

    # Sphere
    sphereEntity = QEntity(rootEntity)
    sphereMesh = QSphereMesh()
    sphereMesh.setRadius(3)

    # Qt3DCore.QTransform *
    sphereTransform = QTransform()
    #OrbitTransformController *
    controller = OrbitTransformController(sphereTransform)
    controller.setTarget(sphereTransform)
    controller.setRadius(20.0)
    # QPropertyAnimation *
    sphereRotateTransformAnimation = QPropertyAnimation(sphereTransform)
    sphereRotateTransformAnimation.setTargetObject(controller)
    sphereRotateTransformAnimation.setPropertyName(b"angle")
    sphereRotateTransformAnimation.setStartValue(0)
    sphereRotateTransformAnimation.setEndValue(360)
    sphereRotateTransformAnimation.setDuration(10000)
    sphereRotateTransformAnimation.setLoopCount(-1)
    sphereRotateTransformAnimation.start()

    sphereEntity.addComponent(sphereMesh)
    sphereEntity.addComponent(sphereTransform)
    sphereEntity.addComponent(material)

    return rootEntity


if __name__ == "__main__":
    app = QApplication(sys.argv)
    view = Qt3DWindow()
    scene = createScene()

    # Camera
    camera = view.camera()
    camera.lens().setPerspectiveProjection(45.0, 16.0/9.0, 0.1, 1000.0)
    camera.setPosition(QVector3D(0, 0, 40.0))
    camera.setViewCenter(QVector3D(0, 0, 0))

    # For camera controls
    camController = QOrbitCameraController(scene)
    camController.setLinearSpeed( 50.0 )
    camController.setLookSpeed( 180.0 )
    camController.setCamera(camera)

    view.setRootEntity(scene)
    view.show()

    sys.exit(app.exec())