Skip to content

Commit

Permalink
[splib3] Adds features and one fix (#116)
Browse files Browse the repository at this point in the history
* [splib3] list of changes:
- adds to_degrees() to numerics __init__
- adds terminationDelay to animate.py
- fixed createFromEuler (was modifing the parameter rotation, which is by default given by reference in python)
Note: the params field is for passing parameters to the
callback function not for controlling the animation system itself.

---------

Co-authored-by: Damien Marchal <[email protected]>
  • Loading branch information
EulalieCoevoet and damienmarchal authored May 22, 2024
1 parent 993ee9d commit 232e96e
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 18 deletions.
49 changes: 34 additions & 15 deletions python3/src/splib3/animation/animate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import Sofa.Core
from splib3.utils import deprecated_alias


class Animation(object):
"""An animation clip that trigger callback at regular intervales for a given duration.
Expand All @@ -27,12 +28,17 @@ def onDone(target, factor):
animate(onUpdate, {"target" : rootNode }, 12, onDone=onDone)
"""

@deprecated_alias(cb='onUpdate')
def __init__(self, duration, mode, onUpdate, params, onDone=None):
def __init__(self, duration, mode, onUpdate, params, onDone=None, terminationDelay=None):
if 'startTime' in params:
self.startTime = params['startTime']
else:
self.startTime = None
self.startTimeInit = None

self.terminationDelay = terminationDelay
self.terminationDelayInit = self.terminationDelay

self.duration = duration
self.onUpdate = onUpdate
Expand All @@ -42,6 +48,10 @@ def __init__(self, duration, mode, onUpdate, params, onDone=None):
self.direction = 1.0
self.mode = mode

def reset(self):
self.startTime = self.startTimeInit
self.terminationDelay = self.terminationDelayInit

def doOnDone(self, currentTime):
self.onDone(factor=self.factor, **self.params)

Expand All @@ -52,9 +62,9 @@ def update(self, currentTime):
if self.duration == 0.0:
self.factor = 1.0
elif self.direction > 0.0:
self.factor = (currentTime-self.startTime) / self.duration
self.factor = (currentTime - self.startTime) / self.duration
else:
self.factor = 1.0-(currentTime-self.startTime) / self.duration
self.factor = 1.0 - (currentTime - self.startTime) / self.duration

if self.factor > 1.0:
self.factor = 1.0
Expand All @@ -77,9 +87,10 @@ def __init__(self, *args, **kwargs):
self.node = args[0]
self.totalTime = 0
self.animations = []

def clearAnimations(self):
self.animations = []
self.totalTime = 0

def addAnimation(self, animation):
self.animations.append(animation)
Expand All @@ -92,20 +103,23 @@ def bwdInitGraph(self, root):

def onAnimateBeginEvent(self, event):
self.totalTime += self.node.getRoot().dt.value

nextanimations = []
for animation in self.animations:
if self.totalTime == 0:
animation.reset()
animation.update(self.totalTime)
if animation.factor < 1.0 and animation.direction > 0.0:
nextanimations.append(animation)
elif animation.factor > 0.0 and animation.direction < 0.0:
nextanimations.append(animation)
elif animation.mode == "pingpong":
animation.direction = -animation.direction
animation.startTime = None
animation.startTime = animation.duration + animation.terminationDelay + animation.startTime if animation.terminationDelay is not None else None
nextanimations.append(animation)
elif animation.mode == "loop":
animation.direction = animation.direction
animation.startTime = None
animation.startTime = animation.duration + animation.terminationDelay + animation.startTime if animation.terminationDelay is not None else None
nextanimations.append(animation)
elif animation.onDone is not None:
animation.doOnDone(self.totalTime)
Expand All @@ -115,7 +129,8 @@ def onAnimateBeginEvent(self, event):

manager = None

def animate(onUpdate, params, duration, mode="once", onDone=None):

def animate(onUpdate, params, duration, mode="once", onDone=None, terminationDelay=None):
"""Construct and starts an animation
Build a new animation from a callback function that computes the animation value,
Expand All @@ -137,18 +152,20 @@ def createScene(rootNode)
AnimationManager(rootNode)
animate(myAnimate, {"target" : rootNode }, 10)
"""
if manager == None:
if manager is None:
raise Exception("Missing manager in this scene")

manager.addAnimation(Animation(duration=duration, mode=mode, onUpdate=onUpdate, params=params, onDone=onDone))
manager.addAnimation(Animation(duration=duration, mode=mode, onUpdate=onUpdate, params=params,
onDone=onDone, terminationDelay=terminationDelay))


def removeAnimation(animation):
if manager == None:
if manager is None:
raise Exception("Missing manager in this scene")

manager.removeAnimation(animation)


def AnimationManager(node):
"""
A Controller to manage all animations in the scene
Expand All @@ -168,7 +185,8 @@ def createScene(rootNode)
"""
global manager
if manager is not None:
#Sofa.msg_info(node, "There is already one animation manager in this scene...why do you need a second one ? Resting it.")
# Sofa.msg_info(node, "There is already one animation manager
# in this scene...why do you need a second one ? Resting it.")
manager.clearAnimations()
return manager
manager = AnimationManagerController(node)
Expand All @@ -177,15 +195,16 @@ def createScene(rootNode)

# This function is just an example on how to use the animate function.
def createScene(rootNode):
def myAnimate1(target, factor):
print("I should do something on: "+target.name+" factor is: "+str(factor))
def myAnimate1(target, factor, terminationDelay=0):
print("I should do something on: " + target.getName() + " factor is: " + str(factor))

def myAnimate2(target, factor):
print("Function 2: "+target.name+" factor is: "+str(factor))
print("Function 2: " + target.getName() + " factor is: " + str(factor))

def myOnDone(target, factor):
print("onDone: "+target.name+" factor is: "+str(factor))
print("onDone: " + target.getName() + " factor is: " + str(factor))

rootNode.addObject(AnimationManager(rootNode))
animate(myAnimate1, {"target": rootNode}, 10)
animate(myAnimate2, {"target": rootNode}, 12, onDone=myOnDone)
animate(myAnimate1, {"target": rootNode}, 2, mode="loop", terminationDelay=2)
13 changes: 12 additions & 1 deletion python3/src/splib3/numerics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
RigidDofZero = [0.0,0.0,0.0,0.0,0.0,0.0,1.0]

def to_radians(v):
"""Converts degree to radians
"""Converts degrees to radians
:param v: the angle to convert
"""
Expand All @@ -58,6 +58,17 @@ def to_radians(v):
return p
return v * pi * 2.0 / 360.0

def to_degrees(v):
"""Converts radians to degrees
:param v: the angle to convert
"""
if isinstance(v, list):
p = []
for tp in v:
p.append( tp / pi / 2.0 * 360.0 )
return p
return v / pi / 2.0 * 360.0

def TRS_to_matrix(translation, rotation=None, scale=None, eulerRotation=None):
t = numpy.identity(4)
Expand Down
6 changes: 4 additions & 2 deletions python3/src/splib3/numerics/quat.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Quat(numpy.ndarray):
q = Quat.product(q1,q2)
q = Quat.createFromEuler([x,y,z])
q = Quat.createFromAxisAngle([axis],angle)
q = Quat.createFromVectors(v1, v2)
"""

def __new__(cls, *args):
Expand Down Expand Up @@ -317,13 +318,13 @@ def createFromAxisAngle(axis, angle):
return q

@staticmethod
def createFromEuler(a, axes='sxyz', inDegree=False):
def createFromEuler(v, axes='sxyz', inDegree=False):
"""Returns a quaternion from Euler angles (in radian) and axis sequence.
The quaternion is of type Quat.
Args:
a is a list of three Euler angles [x,y,z]
v is a list of three Euler angles [x,y,z]
axes : One of 24 axis sequences as string or encoded tuple
Example:
Expand All @@ -337,6 +338,7 @@ def createFromEuler(a, axes='sxyz', inDegree=False):
[ 0.5 -0.5 0.5 0.5]
"""

a = numpy.copy(v)
if inDegree:
a = [a[0] * pi / 180, a[1] * pi / 180, a[2] * pi / 180]

Expand Down

0 comments on commit 232e96e

Please sign in to comment.