I present two solutions to your problem.
Both solutions involve running multiple instances of your ParallelAnimation. Both solutions have a rewrite of your Button onClicked event removing the synchronous for loop for something that's asynchronous, either (1) event-driven, or (2) Promise-chain driven. The idea is to wait for the clockwise animation to finish before starting the counterclockwise animation.
The first solution uses the following pattern to animate:
ParallelAnimation {
id: clockwise
// ...
onFinished: Qt.callLater(nextJob)
}
ParallelAnimation {
id: counterClockwise
// ...
onFinished: Qt.callLater(nextJob)
}
Button {
onClicked: {
jobs = [
clockwise.start,
counterClockwise.start,
clockwise.start,
counterClockwise.start,
clockwise.start,
counterClockwise.start
];
Qt.callLater(nextJob);
}
}
property var jobs: []
function nextJob() {
if (!jobs || !jobs.length) return;
let job = jobs.shift();
Qt.callLater(job);
}
The jobs is declared to be an array of callable functions. Note that we write clockwise.start and not clockwise.start() since we do not want invoke the animations at the time the are entered into the array. The functions will get called when they are retrieved from the jobs array in the nextJob() function with with let job = jobs.shift();. Everytime an animation is finish, it calls nextJob(). This repeats until all jobs are processed. Clicking the Start button initializes all jobs.
Here's your entire program with the appropriate modifications:
import QtQuick 2.15
import QtQuick.Window 2.14
import QtQuick3D 1.15
import QtQuick.Controls 2.14
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
visibility: "Maximized"
property int scl: 5
property int angle: 360
Node{
id: standAloneScene
DirectionalLight {
ambientColor: Qt.rgba(1.0, 1.0, 1.0, 1.0)
}
Node {
id: sphere
Model {
id: model_sphere
source: "#Cube"
x: 200
y: 100
z: 0
materials: [
DefaultMaterial {
diffuseColor: Qt.rgba(0.053, 0.130, 0.219, 0.75)
}
]
}
}
ParallelAnimation{
id: clockwise
running: false
property int count: 3
NumberAnimation {
target: sphere
property: "eulerRotation.y"
from: 360
to: 0
duration: 2000
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: model_sphere
property: "eulerRotation.y"
from: 720
to: 0
duration: 2000
easing.type: Easing.InOutQuad
}
onFinished: Qt.callLater(nextJob)
}
ParallelAnimation{
id: counterClockwise
running: false
NumberAnimation {
target: sphere
property: "eulerRotation.y"
from: 0
to: 360
duration: 2000
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: model_sphere
property: "eulerRotation.y"
from: 0
to: 720
duration: 2000
easing.type: Easing.InOutQuad
}
onFinished: Qt.callLater(nextJob)
}
OrthographicCamera {
id: cameraOrthographicFront
eulerRotation.y: 45
eulerRotation.x: -45
x: 600
y: 800
z: 600
}
}
Rectangle {
id: view
anchors.top: parent.top
anchors.left: parent.left
width: parent.width
height: parent.height
color: "#848895"
border.color: "black"
View3D {
id: topLeftView
anchors.fill: parent
importScene: standAloneScene
camera: cameraOrthographicFront
}
Button {
id: posmoveZ
width: view.width/8
height: view.height/16
anchors.top: view.top
anchors.right: view.right
text: "start"
font.pixelSize: height
enabled: !clockwise.running && !counterClockwise.running
onClicked: {
jobs = [
clockwise.start,
counterClockwise.start,
clockwise.start,
counterClockwise.start,
clockwise.start,
counterClockwise.start
];
Qt.callLater(nextJob);
}
}
}
property var jobs: []
function nextJob() {
if (!jobs || !jobs.length) return;
let job = jobs.shift();
Qt.callLater(job);
}
}
The second solution is based on Promises and is more scalable it supports more complex animation sequences via Promise chaining.
ParallelAnimation {
id: parallelAnimation
property var resolve
property int from
property int to
// ...
function animate(from, to) {
return new Promise(function (resolve, reject) {
parallelAnimation.resolve = resolve;
parallelAnimation.reject = reject;
parallelAnimation.from = from;
parallelAnimation.to = to;
start();
} );
}
onFinished: resolve()
}
Button {
onClicked: {
parallelAnimation.animate(360, 0)
then( () => parallelAnimation.animate(0, 360))
then( () => parallelAnimation.animate(360, 0))
then( () => parallelAnimation.animate(0, 360))
then( () => parallelAnimation.animate(360, 0))
then( () => parallelAnimation.animate(0, 360))
catch( (err) => { console.error(err.message); throw err; } )
;
}
}
Below is a complete implementation of running NumberAnimation with Promise:
import QtQuick 2.15
import QtQuick.Window 2.14
import QtQuick3D 1.15
import QtQuick.Controls 2.14
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
visibility: "Maximized"
property int scl: 5
property int angle: 360
Node{
id: standAloneScene
DirectionalLight {
ambientColor: Qt.rgba(1.0, 1.0, 1.0, 1.0)
}
Node {
id: sphere
Model {
id: model_sphere
source: "#Cube"
x: 200
y: 100
z: 0
materials: [
DefaultMaterial {
diffuseColor: Qt.rgba(0.053, 0.130, 0.219, 0.75)
}
]
}
}
ParallelAnimation{
id: parallelAnimation
running: false
property var resolve: null
property var reject: null
property int from: 360
property int to: 0
NumberAnimation {
target: sphere
property: "eulerRotation.y"
from: parallelAnimation.from
to: parallelAnimation.to
duration: 2000
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: model_sphere
property: "eulerRotation.y"
from: parallelAnimation.from * 2
to: parallelAnimation.to * 2
duration: 2000
easing.type: Easing.InOutQuad
}
function animate(from, to) {
return new Promise(function (resolve, reject) {
parallelAnimation.resolve = resolve;
parallelAnimation.reject = reject;
parallelAnimation.from = from;
parallelAnimation.to = to;
parallelAnimation.restart();
} );
}
onFinished: resolve()
}
OrthographicCamera {
id: cameraOrthographicFront
eulerRotation.y: 45
eulerRotation.x: -45
x: 600
y: 800
z: 600
}
}
Rectangle {
id: view
anchors.top: parent.top
anchors.left: parent.left
width: parent.width
height: parent.height
color: "#848895"
border.color: "black"
View3D {
id: topLeftView
anchors.fill: parent
importScene: standAloneScene
camera: cameraOrthographicFront
}
Button {
id: posmoveZ
width: view.width/8
height: view.height/16
anchors.top: view.top
anchors.right: view.right
text: "start"
font.pixelSize: height
onClicked: {
parallelAnimation.animate(360, 0)
.then( () => parallelAnimation.animate(0, 360))
.then( () => parallelAnimation.animate(360, 0))
.then( () => parallelAnimation.animate(0, 360))
.then( () => parallelAnimation.animate(360, 0))
.then( () => parallelAnimation.animate(0, 360))
.catch( (err) => { console.error(err.message); throw err; } )
;
}
}
}
}