I am trying to understand the animation operation in PyQt5 from the example in https://doc.qt.io/qt-5.10/qt3d-simple-cpp-example.html
I have translated the code into Python and am able to run it but there is no animation. I'm thinking I need to connect up the signals to an Update() method somewhere but I'm having troubel understanding how to do that.
Here is the PyQt5 (python 3) code:
######################################
#
# 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())
Is there something I'm missing? There is some O_OBJECT code that I dont knwo how to translate and I suspect this is where things are broken:
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)
Any suggestions would be greatly appreciated. Here is a screenshot of the (unanimated) result.
You have to 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)
Complete Code:
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())