[docs]classAnimation:"""Keyframe animation class. Animation is responsible for keyframe animations for a single or a group of actors. It's used to handle multiple attributes and properties of Fury actors such as transformations, color, and scale. It also accepts custom data and interpolates them, such as temperature. Linear interpolation is used by default to interpolate data between the main keyframes. Attributes ---------- actors : Actor or list[Actor], optional, default: None Actor/s to be animated. length : float or int, default: None, optional the fixed length of the animation. If set to None, the animation will get its duration from the keyframes being set. loop : bool, optional, default: True Whether to loop the animation (True) of play once (False). motion_path_res : int, default: None the number of line segments used to visualizer the animation's motion path (visualizing position). """@warn_on_args_to_kwargs()def__init__(self,*,actors=None,length=None,loop=True,motion_path_res=None):super().__init__()self._data=defaultdict(dict)self._animations=[]self._actors=[]self._static_actors=[]self._timeline=Noneself._parent_animation=Noneself._scene=Noneself._start_time=0self._length=lengthself._duration=lengthiflengthelse0self._loop=loopself._current_timestamp=0self._max_timestamp=0self._added_to_scene=Trueself._motion_path_res=motion_path_resself._motion_path_actor=Noneself._transform=Transform()self._general_callbacks=[]# Adding actors to the animationifactorsisnotNone:self.add_actor(actors)
[docs]defupdate_duration(self):"""Update and return the duration of the Animation. Returns ------- float The duration of the animation. """ifself._lengthisnotNone:self._duration=self._lengthelse:self._duration=max(self._max_timestamp,max([0]+[anim.update_duration()foraniminself.child_animations]),)returnself.duration
@propertydefduration(self):"""Return the duration of the animation. Returns ------- float The duration of the animation. """returnself._duration@propertydefcurrent_timestamp(self):"""Return the current time of the animation. Returns ------- float The current time of the animation. """ifself._timeline:returnself._timeline.current_timestampelifself.parent_animation:returnself.parent_animation.current_timestampreturnself._current_timestamp
[docs]defupdate_motion_path(self):"""Update motion path visualization actor"""res=self._motion_path_restl=selfwhileisinstance(tl._parent_animation,Animation):ifres:breaktl=tl._parent_animationres=tl._motion_path_resifnotres:returnlines=[]colors=[]ifself.is_interpolatable("position"):ts=np.linspace(0,self.duration,res)[lines.append(self.get_position(t).tolist())fortints]ifself.is_interpolatable("color"):[colors.append(self.get_color(t))fortints]eliflen(self._actors)>=1:colors=sum([i.vcolors[0]/255foriinself._actors])/len(self._actors)else:colors=[1,1,1]iflen(lines)>0:lines=np.array([lines])ifisinstance(colors,list):colors=np.array([colors])mpa=line(lines,colors=colors,opacity=0.6)ifself._scene:# remove old motion path actorifself._motion_path_actorisnotNone:self._scene.rm(self._motion_path_actor)self._scene.add(mpa)self._motion_path_actor=mpa
def_get_data(self):"""Get animation data. Returns ------- dict: The animation data containing keyframes and interpolators. """returnself._datadef_get_attribute_data(self,attrib):"""Get animation data for a specific attribute. Parameters ---------- attrib: str The attribute name to get data for. Returns ------- dict: The animation data for a specific attribute. """data=self._get_data()ifattribnotindata:data[attrib]={"keyframes":defaultdict(dict),"interpolator":{"base":(linear_interpolatorifattrib!="rotation"elseslerp),"func":None,"args":defaultdict(),},"callbacks":[],}returndata.get(attrib)
[docs]@warn_on_args_to_kwargs()defget_keyframes(self,*,attrib=None):"""Get a keyframe for a specific or all attributes. Parameters ---------- attrib: str, optional, default: None The name of the attribute. If None, all keyframes for all set attributes will be returned. """data=self._get_data()ifattribisNone:attribs=data.keys()return{attrib:data.get(attrib,{}).get("keyframes",{})forattribinattribs}returndata.get(attrib,{}).get("keyframes",{})
[docs]@warn_on_args_to_kwargs()defset_keyframe(self,attrib,timestamp,value,*,update_interpolator=True,**kwargs):"""Set a keyframe for a certain attribute. Parameters ---------- attrib: str The name of the attribute. timestamp: float Timestamp of the keyframe. value: ndarray or float or bool Value of the keyframe at the given timestamp. update_interpolator: bool, optional Interpolator will be reinitialized if True Other Parameters ---------------- in_cp: ndarray, shape (1, M), optional The in control point in case of using cubic Bézier interpolator. out_cp: ndarray, shape (1, M), optional The out control point in case of using cubic Bézier interpolator. in_tangent: ndarray, shape (1, M), optional The in tangent at that position for the cubic spline curve. out_tangent: ndarray, shape (1, M), optional The out tangent at that position for the cubic spline curve. """attrib_data=self._get_attribute_data(attrib)keyframes=attrib_data.get("keyframes")keyframes[timestamp]={"value":np.array(value).astype(float),**{par:np.array(val).astype(float)forpar,valinkwargs.items()ifvalisnotNone},}ifupdate_interpolator:interp=attrib_data.get("interpolator")interp_base=interp.get("base",linear_interpolatorifattrib!="rotation"elseslerp)args=interp.get("args",{})self.set_interpolator(attrib,interp_base,**args)iftimestamp>self._max_timestamp:self._max_timestamp=timestampifself._timelineisnotNone:self._timeline.update_duration()else:self.update_duration()self.update_animation(time=0)self.update_motion_path()
[docs]defset_keyframes(self,attrib,keyframes):"""Set multiple keyframes for a certain attribute. Parameters ---------- attrib: str The name of the attribute. keyframes: dict A dict object containing keyframes to be set. Notes ----- Keyframes can be on any of the following forms: >>> import numpy as np >>> key_frames_simple = {1: [1, 2, 1], 2: [3, 4, 5]} >>> key_frames_bezier = {1: {'value': [1, 2, 1]}, ... 2: {'value': [3, 4, 5], 'in_cp': [1, 2, 3]}} >>> pos_keyframes = {1: np.array([1, 2, 3]), 3: np.array([5, 5, 5])} >>> Animation.set_keyframes('position', pos_keyframes) # doctest: +SKIP """fort,keyframeinkeyframes.items():ifisinstance(keyframe,dict):self.set_keyframe(attrib,t,**keyframe)else:self.set_keyframe(attrib,t,keyframe)
[docs]defis_inside_scene_at(self,timestamp):"""Check if the Animation is set to be inside the scene at a specific timestamp. Returns ------- bool True if the Animation is set to be inside the scene at the given timestamp. Notes ----- If the parent Animation is set to be out of the scene at that time, all of their child animations will be out of the scene as well. """parent=self._parent_animationparent_in_scene=TrueifparentisnotNone:parent_in_scene=parent._added_to_sceneifself.is_interpolatable("in_scene"):in_scene=parent_in_sceneandself.get_value("in_scene",timestamp)else:in_scene=parent_in_scenereturnin_scene
[docs]defadd_to_scene_at(self,timestamp):"""Set timestamp for adding Animation to scene event. Parameters ---------- timestamp: float Timestamp of the event. """ifnotself.is_interpolatable("in_scene"):self.set_keyframe("in_scene",timestamp,True)self.set_interpolator("in_scene",step_interpolator)else:self.set_keyframe("in_scene",timestamp,True)
[docs]defremove_from_scene_at(self,timestamp):"""Set timestamp for removing Animation to scene event. Parameters ---------- timestamp: float Timestamp of the event. """ifnotself.is_interpolatable("in_scene"):self.set_keyframe("in_scene",timestamp,False)self.set_interpolator("in_scene",step_interpolator)else:self.set_keyframe("in_scene",timestamp,False)
[docs]@warn_on_args_to_kwargs()defset_interpolator(self,attrib,interpolator,*,is_evaluator=False,**kwargs):"""Set keyframes interpolator for a certain property Parameters ---------- attrib: str The name of the property. interpolator: callable The generator function of the interpolator to be used to interpolate/evaluate keyframes. is_evaluator: bool, optional Specifies whether the `interpolator` is time-only based evaluation function that does not depend on keyframes such as: def get_position(t): return np.array([np.sin(t), np.cos(t) * 5, 5]) Other Parameters ---------------- spline_degree: int, optional The degree of the spline in case of setting a spline interpolator. Notes ----- If an evaluator is used to set the values of actor's properties such as position, scale, color, rotation, or opacity, it has to return a value with the same shape as the evaluated property, i.e.: for scale, it has to return an array with shape 1x3, and for opacity, it has to return a 1x1, an int, or a float value. Examples -------- >>> Animation.set_interpolator('position', linear_interpolator) # doctest: +SKIP >>> pos_fun = lambda t: np.array([np.sin(t), np.cos(t), 0]) # doctest: +SKIP >>> Animation.set_interpolator('position', pos_fun) # doctest: +SKIP """attrib_data=self._get_attribute_data(attrib)keyframes=attrib_data.get("keyframes",{})interp_data=attrib_data.get("interpolator",{})ifis_evaluator:interp_data["base"]=Noneinterp_data["func"]=interpolatorelse:interp_data["base"]=interpolatorinterp_data["args"]=kwargs# Maintain interpolator base in case new keyframes are added.iflen(keyframes)==0:returnnew_interp=interpolator(keyframes,**kwargs)interp_data["func"]=new_interp# update motion pathself.update_duration()self.update_motion_path()
[docs]defis_interpolatable(self,attrib):"""Check whether a property is interpolatable. Parameters ---------- attrib: str The name of the property. Returns ------- bool True if the property is interpolatable by the Animation. Notes ----- True means that it's safe to use `Interpolator.interpolate(t)` for the specified property. And False means the opposite. """data=self._datareturnbool(data.get(attrib,{}).get("interpolator",{}).get("func"))
[docs]@warn_on_args_to_kwargs()defset_position_interpolator(self,interpolator,*,is_evaluator=False,**kwargs):"""Set the position interpolator. Parameters ---------- interpolator: callable The generator function of the interpolator that would handle the position keyframes. is_evaluator: bool, optional Specifies whether the `interpolator` is time-only based evaluation function that does not depend on keyframes. Other Parameters ---------------- degree: int The degree of the spline interpolation in case of setting the `spline_interpolator`. Examples -------- >>> Animation.set_position_interpolator(spline_interpolator, degree=5) # doctest: +SKIP """# noqa: E501self.set_interpolator("position",interpolator,is_evaluator=is_evaluator,**kwargs)
[docs]@warn_on_args_to_kwargs()defset_scale_interpolator(self,interpolator,*,is_evaluator=False):"""Set the scale interpolator. Parameters ---------- interpolator: callable The generator function of the interpolator that would handle the scale keyframes. is_evaluator: bool, optional Specifies whether the `interpolator` is time-only based evaluation function that does not depend on keyframes. Examples -------- >>> Animation.set_scale_interpolator(step_interpolator) # doctest: +SKIP """self.set_interpolator("scale",interpolator,is_evaluator=is_evaluator)
[docs]@warn_on_args_to_kwargs()defset_rotation_interpolator(self,interpolator,*,is_evaluator=False):"""Set the rotation interpolator . Parameters ---------- interpolator: callable The generator function of the interpolator that would handle the rotation (orientation) keyframes. is_evaluator: bool, optional Specifies whether the `interpolator` is time-only based evaluation function that does not depend on keyframes. Examples -------- >>> Animation.set_rotation_interpolator(slerp) # doctest: +SKIP """self.set_interpolator("rotation",interpolator,is_evaluator=is_evaluator)
[docs]@warn_on_args_to_kwargs()defset_color_interpolator(self,interpolator,*,is_evaluator=False):"""Set the color interpolator. Parameters ---------- interpolator: callable The generator function of the interpolator that would handle the color keyframes. is_evaluator: bool, optional Specifies whether the `interpolator` is time-only based evaluation function that does not depend on keyframes. Examples -------- >>> Animation.set_color_interpolator(lab_color_interpolator) # doctest: +SKIP """self.set_interpolator("color",interpolator,is_evaluator=is_evaluator)
[docs]@warn_on_args_to_kwargs()defset_opacity_interpolator(self,interpolator,*,is_evaluator=False):"""Set the opacity interpolator. Parameters ---------- interpolator: callable The generator function of the interpolator that would handle the opacity keyframes. is_evaluator: bool, optional Specifies whether the `interpolator` is time-only based evaluation function that does not depend on keyframes. Examples -------- >>> Animation.set_opacity_interpolator(step_interpolator) # doctest: +SKIP """self.set_interpolator("opacity",interpolator,is_evaluator=is_evaluator)
[docs]defget_value(self,attrib,timestamp):"""Return the value of an attribute at any given timestamp. Parameters ---------- attrib: str The attribute name. timestamp: float The timestamp to interpolate at. """value=(self._data.get(attrib,{}).get("interpolator",{}).get("func")(timestamp))returnvalue
[docs]defget_current_value(self,attrib):"""Return the value of an attribute at current time. Parameters ---------- attrib: str The attribute name. """return(self._data.get(attrib).get("interpolator").get("func")(self._timeline.current_timestamp))
[docs]defset_position(self,timestamp,position,**kwargs):"""Set a position keyframe at a specific timestamp. Parameters ---------- timestamp: float Timestamp of the keyframe position: ndarray, shape (1, 3) Position value Other Parameters ---------------- in_cp: float The control point in case of using `cubic Bézier interpolator` when time exceeds this timestamp. out_cp: float The control point in case of using `cubic Bézier interpolator` when time precedes this timestamp. in_tangent: ndarray, shape (1, M), optional The in tangent at that position for the cubic spline curve. out_tangent: ndarray, shape (1, M), optional The out tangent at that position for the cubic spline curve. Notes ----- `in_cp` and `out_cp` only needed when using the cubic bezier interpolation method. """self.set_keyframe("position",timestamp,position,**kwargs)
[docs]defset_position_keyframes(self,keyframes):"""Set a dict of position keyframes at once. Should be in the following form: {timestamp_1: position_1, timestamp_2: position_2} Parameters ---------- keyframes: dict A dict with timestamps as keys and positions as values. Examples -------- >>> pos_keyframes = {1, (0, 0, 0), 3, (50, 6, 6)} >>> Animation.set_position_keyframes(pos_keyframes) # doctest: +SKIP """self.set_keyframes("position",keyframes)
[docs]defset_rotation(self,timestamp,rotation,**kwargs):"""Set a rotation keyframe at a specific timestamp. Parameters ---------- timestamp: float Timestamp of the keyframe rotation: ndarray, shape(1, 3) or shape(1, 4) Rotation data in euler degrees with shape(1, 3) or in quaternions with shape(1, 4). Notes ----- Euler rotations are executed by rotating first around Z then around X, and finally around Y. """no_components=len(np.array(rotation).flatten())ifno_components==4:self.set_keyframe("rotation",timestamp,rotation,**kwargs)elifno_components==3:# user is expected to set rotation order by default as setting# orientation of a `vtkActor` ordered as z->x->y.rotation=np.asarray(rotation,dtype=float)rotation=transform.Rotation.from_euler("zxy",rotation[[2,0,1]],degrees=True).as_quat()self.set_keyframe("rotation",timestamp,rotation,**kwargs)else:warn(f"Keyframe with {no_components} components is not a "f"valid rotation data. Skipped!",stacklevel=2,)
[docs]defset_rotation_as_vector(self,timestamp,vector,**kwargs):"""Set a rotation keyframe at a specific timestamp. Parameters ---------- timestamp: float Timestamp of the keyframe vector: ndarray, shape(1, 3) Directional vector that describes the rotation. """quat=transform.Rotation.from_rotvec(vector).as_quat()self.set_keyframe("rotation",timestamp,quat,**kwargs)
[docs]defset_scale(self,timestamp,scalar,**kwargs):"""Set a scale keyframe at a specific timestamp. Parameters ---------- timestamp: float Timestamp of the keyframe scalar: ndarray, shape(1, 3) Scale keyframe value associated with the timestamp. """self.set_keyframe("scale",timestamp,scalar,**kwargs)
[docs]defset_scale_keyframes(self,keyframes):"""Set a dict of scale keyframes at once. Should be in the following form: {timestamp_1: scale_1, timestamp_2: scale_2} Parameters ---------- keyframes: dict A dict with timestamps as keys and scales as values. Examples -------- >>> scale_keyframes = {1, (1, 1, 1), 3, (2, 2, 3)} >>> Animation.set_scale_keyframes(scale_keyframes) # doctest: +SKIP """self.set_keyframes("scale",keyframes)
[docs]defset_color(self,timestamp,color,**kwargs):"""Set color keyframe at a specific timestamp. Parameters ---------- timestamp: float Timestamp of the keyframe color: ndarray, shape(1, 3) Color keyframe value associated with the timestamp. """self.set_keyframe("color",timestamp,color,**kwargs)
[docs]defset_color_keyframes(self,keyframes):"""Set a dict of color keyframes at once. Should be in the following form: {timestamp_1: color_1, timestamp_2: color_2} Parameters ---------- keyframes: dict A dict with timestamps as keys and color as values. Examples -------- >>> import numpy as np >>> color_keyframes = {1, (1, 0, 1), 3, (0, 0, 1)} >>> Animation.set_color_keyframes(color_keyframes) # doctest: +SKIP """self.set_keyframes("color",keyframes)
[docs]defset_opacity(self,timestamp,opacity,**kwargs):"""Set opacity keyframe at a specific timestamp. Parameters ---------- timestamp: float Timestamp of the keyframe opacity: ndarray, shape(1, 3) Opacity keyframe value associated with the timestamp. """self.set_keyframe("opacity",timestamp,opacity,**kwargs)
[docs]defset_opacity_keyframes(self,keyframes):"""Set a dict of opacity keyframes at once. Should be in the following form: {timestamp_1: opacity_1, timestamp_2: opacity_2} Parameters ---------- keyframes: dict(float: ndarray, shape(1, 1) or float or int) A dict with timestamps as keys and opacities as values. Notes ----- Opacity values should be between 0 and 1. Examples -------- >>> opacity = {1, (1, 1, 1), 3, (2, 2, 3)} >>> Animation.set_scale_keyframes(opacity) # doctest: +SKIP """self.set_keyframes("opacity",keyframes)
[docs]defget_position(self,t):"""Return the interpolated position. Parameters ---------- t: float The time to interpolate position at. Returns ------- ndarray(1, 3): The interpolated position. """returnself.get_value("position",t)
[docs]defget_rotation(self,t,as_quat=False):"""Return the interpolated rotation. Parameters ---------- t: float the time to interpolate rotation at. as_quat: bool Returned rotation will be as quaternion if True. Returns ------- ndarray(1, 3): The interpolated rotation as Euler degrees by default. """rot=self.get_value("rotation",t)iflen(rot)==4:ifas_quat:returnrotr=transform.Rotation.from_quat(rot)degrees=r.as_euler("zxy",degrees=True)[[1,2,0]]returndegreeselifnotas_quat:returnrotreturntransform.Rotation.from_euler("zxy",rot[[2,0,1]],degrees=True).as_quat()
[docs]defget_scale(self,t):"""Return the interpolated scale. Parameters ---------- t: float The time to interpolate scale at. Returns ------- ndarray(1, 3): The interpolated scale. """returnself.get_value("scale",t)
[docs]defget_color(self,t):"""Return the interpolated color. Parameters ---------- t: float The time to interpolate color value at. Returns ------- ndarray(1, 3): The interpolated color. """returnself.get_value("color",t)
[docs]defget_opacity(self,t):"""Return the opacity value. Parameters ---------- t: float The time to interpolate opacity at. Returns ------- ndarray(1, 1): The interpolated opacity. """returnself.get_value("opacity",t)
[docs]defadd(self,item):"""Add an item to the Animation. This item can be an Actor, Animation, list of Actors, or a list of Animations. Parameters ---------- item: Animation, vtkActor, list[Animation], or list[vtkActor] Actor/s to be animated by the Animation. """ifisinstance(item,list):forainitem:self.add(a)returnelifisinstance(item,Actor):self.add_actor(item)elifisinstance(item,Animation):self.add_child_animation(item)else:raiseValueError(f"Object of type {type(item)} can't be animated")
[docs]defadd_child_animation(self,animation):"""Add child Animation or list of Animations. Parameters ---------- animation: Animation or list[Animation] Animation/s to be added. """ifisinstance(animation,list):forainanimation:self.add_child_animation(a)returnanimation._parent_animation=selfanimation.update_motion_path()self._animations.append(animation)self.update_duration()
[docs]@warn_on_args_to_kwargs()defadd_actor(self,actor,*,static=False):"""Add an actor or list of actors to the Animation. Parameters ---------- actor: vtkActor or list(vtkActor) Actor/s to be animated by the Animation. static: bool Indicated whether the actor should be animated and controlled by the animation or just a static actor that gets added to the scene along with the Animation. """ifisinstance(actor,list):forainactor:self.add_actor(a,static=static)elifstatic:ifactornotinself.static_actors:self._static_actors.append(actor)else:ifactornotinself._actors:actor.vcolors=utils.colors_from_actor(actor)self._actors.append(actor)
@propertydeftimeline(self):"""Return the Timeline handling the current animation. Returns ------- Timeline: The Timeline handling the current animation, None, if there is no associated Timeline. """returnself._timeline@timeline.setterdeftimeline(self,timeline):"""Assign the Timeline responsible for handling the Animation. Parameters ---------- timeline: Timeline The Timeline handling the current animation, None, if there is no associated Timeline. """self._timeline=timelineifself._animations:foranimationinself._animations:animation.timeline=timeline@propertydefparent_animation(self):"""Return the hierarchical parent Animation for current Animation. Returns ------- Animation: The parent Animation. """returnself._parent_animation@parent_animation.setterdefparent_animation(self,parent_animation):"""Assign a parent Animation for the current Animation. Parameters ---------- parent_animation: Animation The parent Animation instance. """self._parent_animation=parent_animation@propertydefactors(self):"""Return a list of actors. Returns ------- list: List of actors controlled by the Animation. """returnself._actors@propertydefchild_animations(self)->"list[Animation]":"""Return a list of child Animations. Returns ------- list: List of child Animations of this Animation. """returnself._animations
[docs]defadd_static_actor(self,actor):"""Add an actor or list of actors as static actor/s which will not be controlled nor animated by the Animation. All static actors will be added to the scene when the Animation is added to the scene. Parameters ---------- actor: vtkActor or list(vtkActor) Static actor/s. """self.add_actor(actor,static=True)
@propertydefstatic_actors(self):"""Return a list of static actors. Returns ------- list: List of static actors. """returnself._static_actors
[docs]defremove_animations(self):"""Remove all child Animations from the Animation"""self._animations.clear()
[docs]defremove_actor(self,actor):"""Remove an actor from the Animation. Parameters ---------- actor: vtkActor Actor to be removed from the Animation. """self._actors.remove(actor)
[docs]defremove_actors(self):"""Remove all actors from the Animation"""self._actors.clear()
@propertydefloop(self):"""Get loop condition of the current animation. Returns ------- bool Whether the animation in loop mode (True) or play one mode (False). """returnself._loop@loop.setterdefloop(self,loop):"""Set the animation to loop or play once. Parameters ---------- loop: bool The loop condition to be set. (True) to loop the animation, and (False) to play only once. """self._loop=loop
[docs]defadd_update_callback(self,callback,prop=None):"""Add a function to be called each time animation is updated This function must accept only one argument which is the current value of the named property. Parameters ---------- callback: callable The function to be called whenever the animation is updated. prop: str, optional, default: None The name of the property. Notes ----- If no attribute name was provided, current time of the animation will be provided instead of current value for the callback. """ifpropisNone:self._general_callbacks.append(callback)returnattrib=self._get_attribute_data(prop)attrib.get("callbacks",[]).append(callback)
[docs]@warn_on_args_to_kwargs()defupdate_animation(self,*,time=None):"""Update the animation. Update the animation at a certain time. This will make sure all attributes are calculated and set to the actors at that given time. Parameters ---------- time: float or int, optional, default: None The time to update animation at. If None, the animation will play without adding it to a Timeline. """has_handler=TrueiftimeisNone:time=perf_counter()-self._start_timehas_handler=False# handling in/out of scene eventsin_scene=self.is_inside_scene_at(time)self._handle_scene_event(time)ifself.duration:ifself._loopandtime>self.duration:time=time%self.durationeliftime>self.duration:time=self.durationifisinstance(self._parent_animation,Animation):self._transform.DeepCopy(self._parent_animation._transform)else:self._transform.Identity()self._current_timestamp=time# actors propertiesifin_scene:ifself.is_interpolatable("position"):position=self.get_position(time)self._transform.Translate(*position)ifself.is_interpolatable("opacity"):opacity=self.get_opacity(time)[act.GetProperty().SetOpacity(opacity)foractinself.actors]ifself.is_interpolatable("rotation"):x,y,z=self.get_rotation(time)# Rotate in the same order as VTK defaults.self._transform.RotateZ(z)self._transform.RotateX(x)self._transform.RotateY(y)ifself.is_interpolatable("scale"):scale=self.get_scale(time)self._transform.Scale(*scale)ifself.is_interpolatable("color"):color=self.get_color(time)foractinself.actors:act.vcolors[:]=color*255utils.update_actor(act)# update actors' transformation matrix[act.SetUserTransform(self._transform)foractinself.actors]forattribinself._data:callbacks=self._data.get(attrib,{}).get("callbacks",[])ifcallbacks!=[]andself.is_interpolatable(attrib):value=self.get_value(attrib,time)[cbk(value)forcbkincallbacks]# Executing general callbacks that's not related to any attribute[callback(time)forcallbackinself._general_callbacks]# Also update all child Animations.[animation.update_animation(time=time)foranimationinself._animations]ifself._sceneandnothas_handler:self._scene.reset_clipping_range()
[docs]defadd_to_scene(self,scene):"""Add this Animation, its actors and sub Animations to the scene"""[scene.add(actor)foractorinself._actors][scene.add(static_act)forstatic_actinself._static_actors][scene.add(animation)foranimationinself._animations]ifself._motion_path_actor:scene.add(self._motion_path_actor)self._scene=sceneself._added_to_scene=Trueself._start_time=perf_counter()self.update_animation(time=0)
[docs]defremove_from_scene(self,scene):"""Remove Animation, its actors and sub Animations from the scene"""[scene.rm(act)foractinself.actors][scene.rm(static_act)forstatic_actinself._static_actors]foraniminself.child_animations:anim.remove_from_scene(scene)ifself._motion_path_actor:scene.rm(self._motion_path_actor)self._added_to_scene=False
[docs]classCameraAnimation(Animation):"""Camera keyframe animation class. This is used for animating a single camera using a set of keyframes. Attributes ---------- camera : Camera, optional, default: None Camera to be animated. If None, active camera will be animated. length : float or int, default: None, optional the fixed length of the animation. If set to None, the animation will get its duration from the keyframes being set. loop : bool, optional, default: True Whether to loop the animation (True) of play once (False). motion_path_res : int, default: None the number of line segments used to visualizer the animation's motion path (visualizing position). """@warn_on_args_to_kwargs()def__init__(self,*,camera=None,length=None,loop=True,motion_path_res=None):super(CameraAnimation,self).__init__(length=length,loop=loop,motion_path_res=motion_path_res)self._camera=camera@propertydefcamera(self)->Camera:"""Return the camera assigned to this animation. Returns ------- Camera: The camera that is being animated by this CameraAnimation. """returnself._camera@camera.setterdefcamera(self,camera:Camera):"""Set a camera to be animated. Parameters ---------- camera: Camera The camera to be animated """self._camera=camera
[docs]defset_focal(self,timestamp,position,**kwargs):"""Set camera's focal position keyframe. Parameters ---------- timestamp: float The time to interpolate opacity at. position: ndarray, shape(1, 3) The camera position """self.set_keyframe("focal",timestamp,position,**kwargs)
[docs]defset_view_up(self,timestamp,direction,**kwargs):"""Set the camera view-up direction keyframe. Parameters ---------- timestamp: float The time to interpolate at. direction: ndarray, shape(1, 3) The camera view-up direction """self.set_keyframe("view_up",timestamp,direction,**kwargs)
[docs]defset_focal_keyframes(self,keyframes):"""Set multiple camera focal position keyframes at once. Should be in the following form: {timestamp_1: focal_1, timestamp_2: focal_1, ...} Parameters ---------- keyframes: dict A dict with timestamps as keys and camera focal positions as values. Examples -------- >>> focal_pos = {0, (1, 1, 1), 3, (20, 0, 0)} >>> CameraAnimation.set_focal_keyframes(focal_pos) # doctest: +SKIP """self.set_keyframes("focal",keyframes)
[docs]defset_view_up_keyframes(self,keyframes):"""Set multiple camera view up direction keyframes. Should be in the following form: {timestamp_1: view_up_1, timestamp_2: view_up_2, ...} Parameters ---------- keyframes: dict A dict with timestamps as keys and camera view up vectors as values. Examples -------- >>> view_ups = {0, np.array([1, 0, 0]), 3, np.array([0, 1, 0])} # doctest: +SKIP >>> CameraAnimation.set_view_up_keyframes(view_ups) # doctest: +SKIP """self.set_keyframes("view_up",keyframes)
[docs]defget_focal(self,t):"""Return the interpolated camera's focal position. Parameters ---------- t: float The time to interpolate at. Returns ------- ndarray(1, 3): The interpolated camera's focal position. Notes ----- The returned focal position does not necessarily reflect the current camera's focal position, but the expected one. """returnself.get_value("focal",t)
[docs]defget_view_up(self,t):"""Return the interpolated camera's view-up directional vector. Parameters ---------- t: float The time to interpolate at. Returns ------- ndarray(1, 3): The interpolated camera view-up directional vector. Notes ----- The returned focal position does not necessarily reflect the actual camera view up directional vector, but the expected one. """returnself.get_value("view_up",t)
[docs]@warn_on_args_to_kwargs()defset_focal_interpolator(self,interpolator,*,is_evaluator=False):"""Set the camera focal position interpolator. Parameters ---------- interpolator: callable The generator function of the interpolator that would handle the interpolation of the camera focal position keyframes. is_evaluator: bool, optional Specifies whether the `interpolator` is time-only based evaluation function that does not depend on keyframes. """self.set_interpolator("focal",interpolator,is_evaluator=is_evaluator)
[docs]@warn_on_args_to_kwargs()defset_view_up_interpolator(self,interpolator,*,is_evaluator=False):"""Set the camera up-view vector animation interpolator. Parameters ---------- interpolator: callable The generator function of the interpolator that would handle the interpolation of the camera view-up keyframes. is_evaluator: bool, optional Specifies whether the `interpolator` is time-only based evaluation function that does not depend on keyframes. """self.set_interpolator("view_up",interpolator,is_evaluator=is_evaluator)
[docs]@warn_on_args_to_kwargs()defupdate_animation(self,*,time=None):"""Update the camera animation. Parameters ---------- time: float or int, optional, default: None The time to update the camera animation at. If None, the animation will play. """ifself._cameraisNone:ifself._scene:self._camera=self._scene.camera()self.update_animation(tile=time)returnelse:ifself.is_interpolatable("rotation"):pos=self._camera.GetPosition()translation=np.identity(4)translation[:3,3]=pos# camera axis is revertedrot=-self.get_rotation(time,as_quat=True)rot=transform.Rotation.from_quat(rot).as_matrix()rot=np.array([[*rot[0],0],[*rot[1],0],[*rot[2],0],[0,0,0,1]])rot=translation@rot@np.linalg.inv(translation)self._camera.SetModelTransformMatrix(rot.flatten())ifself.is_interpolatable("position"):cam_pos=self.get_position(time)self._camera.SetPosition(cam_pos)ifself.is_interpolatable("focal"):cam_foc=self.get_focal(time)self._camera.SetFocalPoint(cam_foc)ifself.is_interpolatable("view_up"):cam_up=self.get_view_up(time)self._camera.SetViewUp(cam_up)elifnotself.is_interpolatable("view_up"):# to preserve up-view as default after user interactionself._camera.SetViewUp(0,1,0)ifself._scene:self._scene.reset_clipping_range()