From 70584d38c51a8009deb5280bda22934c00915f5e Mon Sep 17 00:00:00 2001 From: maharshigor Date: Thu, 19 Dec 2024 00:35:55 +0530 Subject: [PATCH] RF: Uncommented the animation sections and imports --- fury/__init__.pyi | 48 +- fury/animation/__init__.py | 4 +- fury/animation/__init__.pyi | 98 +- fury/animation/animation.py | 2904 +++++++++++++++++------------------ 4 files changed, 1525 insertions(+), 1529 deletions(-) diff --git a/fury/__init__.pyi b/fury/__init__.pyi index 1fed97558..ddd86e647 100644 --- a/fury/__init__.pyi +++ b/fury/__init__.pyi @@ -7,7 +7,7 @@ __all__ = [ # "actor", # "actors", - # "animation", + "animation", "colormap", # "convert", "data", @@ -37,7 +37,7 @@ __all__ = [ from . import ( # actors, - # animation, + animation, colormap, # convert, data, @@ -121,28 +121,28 @@ from . import ( # # main_dir_uncertainty as main_dir_uncertainty, # # tensor_ellipsoid as tensor_ellipsoid, # # ) -# from .animation import ( -# Animation as Animation, -# Timeline as Timeline, -# color_interpolator as color_interpolator, -# cubic_bezier_interpolator as cubic_bezier_interpolator, -# cubic_spline_interpolator as cubic_spline_interpolator, -# euclidean_distances as euclidean_distances, -# get_next_timestamp as get_next_timestamp, -# get_previous_timestamp as get_previous_timestamp, -# get_time_tau as get_time_tau, -# get_timestamps_from_keyframes as get_timestamps_from_keyframes, -# get_values_from_keyframes as get_values_from_keyframes, -# hsv_color_interpolator as hsv_color_interpolator, -# lab_color_interpolator as lab_color_interpolator, -# lerp as lerp, -# linear_interpolator as linear_interpolator, -# slerp as slerp, -# spline_interpolator as spline_interpolator, -# step_interpolator as step_interpolator, -# tan_cubic_spline_interpolator as tan_cubic_spline_interpolator, -# xyz_color_interpolator as xyz_color_interpolator, -# ) +from .animation import ( + Animation as Animation, + # Timeline as Timeline, + color_interpolator as color_interpolator, + cubic_bezier_interpolator as cubic_bezier_interpolator, + cubic_spline_interpolator as cubic_spline_interpolator, + euclidean_distances as euclidean_distances, + get_next_timestamp as get_next_timestamp, + get_previous_timestamp as get_previous_timestamp, + get_time_tau as get_time_tau, + get_timestamps_from_keyframes as get_timestamps_from_keyframes, + get_values_from_keyframes as get_values_from_keyframes, + hsv_color_interpolator as hsv_color_interpolator, + lab_color_interpolator as lab_color_interpolator, + lerp as lerp, + linear_interpolator as linear_interpolator, + slerp as slerp, + spline_interpolator as spline_interpolator, + step_interpolator as step_interpolator, + tan_cubic_spline_interpolator as tan_cubic_spline_interpolator, + xyz_color_interpolator as xyz_color_interpolator, +) from .colormap import ( _lab2rgb as _lab2rgb, _lab2xyz as _lab2xyz, diff --git a/fury/animation/__init__.py b/fury/animation/__init__.py index adfb48395..62d86f776 100644 --- a/fury/animation/__init__.py +++ b/fury/animation/__init__.py @@ -1,3 +1,3 @@ -# import lazy_loader as lazy +import lazy_loader as lazy -# __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__) +__getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__) diff --git a/fury/animation/__init__.pyi b/fury/animation/__init__.pyi index 4544319ae..11d144e1e 100644 --- a/fury/animation/__init__.pyi +++ b/fury/animation/__init__.pyi @@ -1,51 +1,51 @@ -# # This file is used to enable type hinting for the `fury.animation` module. -# # explicit definition of `__all__` will enable type inference for engines. +# This file is used to enable type hinting for the `fury.animation` module. +# explicit definition of `__all__` will enable type inference for engines. -# __all__ = [ -# "Animation", -# "CameraAnimation", -# # "Timeline", -# "euclidean_distances", -# "get_next_timestamp", -# "get_previous_timestamp", -# "get_time_tau", -# "get_timestamps_from_keyframes", -# "get_values_from_keyframes", -# "lerp", -# "color_interpolator", -# "cubic_bezier_interpolator", -# "cubic_spline_interpolator", -# "hsv_color_interpolator", -# "lab_color_interpolator", -# "linear_interpolator", -# "slerp", -# "spline_interpolator", -# "step_interpolator", -# "tan_cubic_spline_interpolator", -# "xyz_color_interpolator", -# ] +__all__ = [ + "Animation", + "CameraAnimation", + # "Timeline", + "euclidean_distances", + "get_next_timestamp", + "get_previous_timestamp", + "get_time_tau", + "get_timestamps_from_keyframes", + "get_values_from_keyframes", + "lerp", + "color_interpolator", + "cubic_bezier_interpolator", + "cubic_spline_interpolator", + "hsv_color_interpolator", + "lab_color_interpolator", + "linear_interpolator", + "slerp", + "spline_interpolator", + "step_interpolator", + "tan_cubic_spline_interpolator", + "xyz_color_interpolator", +] -# from .animation import Animation, CameraAnimation -# from .helpers import ( -# euclidean_distances, -# get_next_timestamp, -# get_previous_timestamp, -# get_time_tau, -# get_timestamps_from_keyframes, -# get_values_from_keyframes, -# lerp, -# ) -# from .interpolator import ( -# color_interpolator, -# cubic_bezier_interpolator, -# cubic_spline_interpolator, -# hsv_color_interpolator, -# lab_color_interpolator, -# linear_interpolator, -# slerp, -# spline_interpolator, -# step_interpolator, -# tan_cubic_spline_interpolator, -# xyz_color_interpolator, -# ) -# # from .timeline import Timeline +from .animation import Animation, CameraAnimation +from .helpers import ( + euclidean_distances, + get_next_timestamp, + get_previous_timestamp, + get_time_tau, + get_timestamps_from_keyframes, + get_values_from_keyframes, + lerp, +) +from .interpolator import ( + color_interpolator, + cubic_bezier_interpolator, + cubic_spline_interpolator, + hsv_color_interpolator, + lab_color_interpolator, + linear_interpolator, + slerp, + spline_interpolator, + step_interpolator, + tan_cubic_spline_interpolator, + xyz_color_interpolator, +) +# from .timeline import Timeline diff --git a/fury/animation/animation.py b/fury/animation/animation.py index 063f6ac54..22e042667 100644 --- a/fury/animation/animation.py +++ b/fury/animation/animation.py @@ -1,1454 +1,1450 @@ -# from collections import defaultdict -# from time import perf_counter -# from warnings import warn - -# import numpy as np -# from scipy.spatial import transform - -# from fury import utils - -# # from fury.actor import line -# from fury.animation.interpolator import ( # noqa F401 -# linear_interpolator, -# slerp, -# spline_interpolator, -# step_interpolator, -# ) -# from fury.decorators import warn_on_args_to_kwargs - -# # from fury.lib import Actor, Camera, Transform - - -# class Animation: -# """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 = None -# self._parent_animation = None -# self._scene = None -# self._start_time = 0 -# self._length = length -# self._duration = length if length else 0 -# self._loop = loop -# self._current_timestamp = 0 -# self._max_timestamp = 0 -# self._added_to_scene = True -# self._motion_path_res = motion_path_res -# self._motion_path_actor = None -# self._transform = Transform() -# self._general_callbacks = [] -# # Adding actors to the animation -# if actors is not None: -# self.add_actor(actors) - -# def update_duration(self): -# """Update and return the duration of the Animation. - -# Returns -# ------- -# float -# The duration of the animation. - -# """ -# if self._length is not None: -# self._duration = self._length -# else: -# self._duration = max( -# self._max_timestamp, -# max([0] + [anim.update_duration() for anim in self.child_animations]), -# ) - -# return self.duration - -# @property -# def duration(self): -# """Return the duration of the animation. - -# Returns -# ------- -# float -# The duration of the animation. - -# """ -# return self._duration - -# @property -# def current_timestamp(self): -# """Return the current time of the animation. - -# Returns -# ------- -# float -# The current time of the animation. - -# """ -# if self._timeline: -# return self._timeline.current_timestamp -# elif self.parent_animation: -# return self.parent_animation.current_timestamp -# return self._current_timestamp - -# def update_motion_path(self): -# """Update motion path visualization actor""" -# res = self._motion_path_res -# tl = self -# while isinstance(tl._parent_animation, Animation): -# if res: -# break -# tl = tl._parent_animation -# res = tl._motion_path_res -# if not res: -# return - -# lines = [] -# colors = [] -# if self.is_interpolatable("position"): -# ts = np.linspace(0, self.duration, res) -# [lines.append(self.get_position(t).tolist()) for t in ts] -# if self.is_interpolatable("color"): -# [colors.append(self.get_color(t)) for t in ts] -# elif len(self._actors) >= 1: -# colors = sum([i.vcolors[0] / 255 for i in self._actors]) / len( -# self._actors -# ) -# else: -# colors = [1, 1, 1] - -# if len(lines) > 0: -# lines = np.array([lines]) -# if isinstance(colors, list): -# colors = np.array([colors]) - -# mpa = line(lines, colors=colors, opacity=0.6) -# if self._scene: -# # remove old motion path actor -# if self._motion_path_actor is not None: -# 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. - -# """ -# return self._data - -# def _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() - -# if attrib not in data: -# data[attrib] = { -# "keyframes": defaultdict(dict), -# "interpolator": { -# "base": (linear_interpolator if attrib != "rotation" else slerp), -# "func": None, -# "args": defaultdict(), -# }, -# "callbacks": [], -# } -# return data.get(attrib) - -# @warn_on_args_to_kwargs() -# def get_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() -# if attrib is None: -# attribs = data.keys() -# return { -# attrib: data.get( -# attrib, {}).get("keyframes", {}) for attrib in attribs -# } -# return data.get(attrib, {}).get("keyframes", {}) - -# @warn_on_args_to_kwargs() -# def set_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) -# for par, val in kwargs.items() -# if val is not None -# }, -# } - -# if update_interpolator: -# interp = attrib_data.get("interpolator") -# interp_base = interp.get( -# "base", linear_interpolator if attrib != "rotation" else slerp -# ) -# args = interp.get("args", {}) -# self.set_interpolator(attrib, interp_base, **args) - -# if timestamp > self._max_timestamp: -# self._max_timestamp = timestamp -# if self._timeline is not None: -# self._timeline.update_duration() -# else: -# self.update_duration() -# self.update_animation(time=0) -# self.update_motion_path() - -# def set_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 - -# """ -# for t, keyframe in keyframes.items(): -# if isinstance(keyframe, dict): -# self.set_keyframe(attrib, t, **keyframe) -# else: -# self.set_keyframe(attrib, t, keyframe) - -# def is_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_animation -# parent_in_scene = True -# if parent is not None: -# parent_in_scene = parent._added_to_scene - -# if self.is_interpolatable("in_scene"): -# in_scene = parent_in_scene and self.get_value("in_scene", timestamp) -# else: -# in_scene = parent_in_scene -# return in_scene - -# def add_to_scene_at(self, timestamp): -# """Set timestamp for adding Animation to scene event. - -# Parameters -# ---------- -# timestamp: float -# Timestamp of the event. - -# """ -# if not self.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) - -# def remove_from_scene_at(self, timestamp): -# """Set timestamp for removing Animation to scene event. - -# Parameters -# ---------- -# timestamp: float -# Timestamp of the event. - -# """ -# if not self.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) - -# def _handle_scene_event(self, timestamp): -# should_be_in_scene = self.is_inside_scene_at(timestamp) -# if self._scene is not None: -# if should_be_in_scene and not self._added_to_scene: -# self._scene.add(*self._actors) -# self._added_to_scene = True -# elif not should_be_in_scene and self._added_to_scene: -# self._scene.rm(*self._actors) -# self._added_to_scene = False - -# @warn_on_args_to_kwargs() -# def set_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", {}) -# if is_evaluator: -# interp_data["base"] = None -# interp_data["func"] = interpolator -# else: -# interp_data["base"] = interpolator -# interp_data["args"] = kwargs -# # Maintain interpolator base in case new keyframes are added. -# if len(keyframes) == 0: -# return -# new_interp = interpolator(keyframes, **kwargs) -# interp_data["func"] = new_interp - -# # update motion path -# self.update_duration() -# self.update_motion_path() - -# def is_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._data -# return bool(data.get(attrib, {}).get("interpolator", {}).get("func")) - -# @warn_on_args_to_kwargs() -# def set_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: E501 -# self.set_interpolator( -# "position", interpolator, is_evaluator=is_evaluator, **kwargs -# ) - -# @warn_on_args_to_kwargs() -# def set_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) - -# @warn_on_args_to_kwargs() -# def set_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) - -# @warn_on_args_to_kwargs() -# def set_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) - -# @warn_on_args_to_kwargs() -# def set_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) - -# def get_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) -# ) -# return value - -# def get_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) -# ) - -# def set_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) - -# def set_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) - -# def set_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()) -# if no_components == 4: -# self.set_keyframe("rotation", timestamp, rotation, **kwargs) -# elif no_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, -# ) - -# def set_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) - -# def set_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) - -# def set_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) - -# def set_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) - -# def set_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) - -# def set_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) - -# def set_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) - -# def get_position(self, t): -# """Return the interpolated position. - -# Parameters -# ---------- -# t: float -# The time to interpolate position at. - -# Returns -# ------- -# ndarray(1, 3): -# The interpolated position. - -# """ -# return self.get_value("position", t) - -# def get_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) -# if len(rot) == 4: -# if as_quat: -# return rot -# r = transform.Rotation.from_quat(rot) -# degrees = r.as_euler("zxy", degrees=True)[[1, 2, 0]] -# return degrees -# elif not as_quat: -# return rot -# return transform.Rotation.from_euler( -# "zxy", rot[[2, 0, 1]], degrees=True -# ).as_quat() - -# def get_scale(self, t): -# """Return the interpolated scale. - -# Parameters -# ---------- -# t: float -# The time to interpolate scale at. - -# Returns -# ------- -# ndarray(1, 3): -# The interpolated scale. - -# """ -# return self.get_value("scale", t) - -# def get_color(self, t): -# """Return the interpolated color. - -# Parameters -# ---------- -# t: float -# The time to interpolate color value at. - -# Returns -# ------- -# ndarray(1, 3): -# The interpolated color. - -# """ -# return self.get_value("color", t) - -# def get_opacity(self, t): -# """Return the opacity value. - -# Parameters -# ---------- -# t: float -# The time to interpolate opacity at. - -# Returns -# ------- -# ndarray(1, 1): -# The interpolated opacity. - -# """ -# return self.get_value("opacity", t) - -# def add(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. - -# """ -# if isinstance(item, list): -# for a in item: -# self.add(a) -# return -# elif isinstance(item, Actor): -# self.add_actor(item) -# elif isinstance(item, Animation): -# self.add_child_animation(item) -# else: -# raise ValueError(f"Object of type {type(item)} can't be animated") - -# def add_child_animation(self, animation): -# """Add child Animation or list of Animations. - -# Parameters -# ---------- -# animation: Animation or list[Animation] -# Animation/s to be added. - -# """ -# if isinstance(animation, list): -# for a in animation: -# self.add_child_animation(a) -# return -# animation._parent_animation = self -# animation.update_motion_path() -# self._animations.append(animation) -# self.update_duration() - -# @warn_on_args_to_kwargs() -# def add_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. - -# """ -# if isinstance(actor, list): -# for a in actor: -# self.add_actor(a, static=static) -# elif static: -# if actor not in self.static_actors: -# self._static_actors.append(actor) -# else: -# if actor not in self._actors: -# actor.vcolors = utils.colors_from_actor(actor) -# self._actors.append(actor) - -# @property -# def timeline(self): -# """Return the Timeline handling the current animation. - -# Returns -# ------- -# Timeline: -# The Timeline handling the current animation, None, if there is no -# associated Timeline. - -# """ -# return self._timeline - -# @timeline.setter -# def timeline(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 = timeline -# if self._animations: -# for animation in self._animations: -# animation.timeline = timeline - -# @property -# def parent_animation(self): -# """Return the hierarchical parent Animation for current Animation. - -# Returns -# ------- -# Animation: -# The parent Animation. - -# """ -# return self._parent_animation - -# @parent_animation.setter -# def parent_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 - -# @property -# def actors(self): -# """Return a list of actors. - -# Returns -# ------- -# list: -# List of actors controlled by the Animation. - -# """ -# return self._actors - -# @property -# def child_animations(self) -> "list[Animation]": -# """Return a list of child Animations. - -# Returns -# ------- -# list: -# List of child Animations of this Animation. - -# """ -# return self._animations - -# def add_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) - -# @property -# def static_actors(self): -# """Return a list of static actors. - -# Returns -# ------- -# list: -# List of static actors. - -# """ -# return self._static_actors - -# def remove_animations(self): -# """Remove all child Animations from the Animation""" -# self._animations.clear() - -# def remove_actor(self, actor): -# """Remove an actor from the Animation. - -# Parameters -# ---------- -# actor: vtkActor -# Actor to be removed from the Animation. - -# """ -# self._actors.remove(actor) - -# def remove_actors(self): -# """Remove all actors from the Animation""" -# self._actors.clear() - -# @property -# def loop(self): -# """Get loop condition of the current animation. - -# Returns -# ------- -# bool -# Whether the animation in loop mode (True) or play one mode (False). - -# """ -# return self._loop - -# @loop.setter -# def loop(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 - -# def add_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. - -# """ -# if prop is None: -# self._general_callbacks.append(callback) -# return -# attrib = self._get_attribute_data(prop) -# attrib.get("callbacks", []).append(callback) - -# @warn_on_args_to_kwargs() -# def update_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 = True -# if time is None: -# time = perf_counter() - self._start_time -# has_handler = False - -# # handling in/out of scene events -# in_scene = self.is_inside_scene_at(time) -# self._handle_scene_event(time) - -# if self.duration: -# if self._loop and time > self.duration: -# time = time % self.duration -# elif time > self.duration: -# time = self.duration -# if isinstance(self._parent_animation, Animation): -# self._transform.DeepCopy(self._parent_animation._transform) -# else: -# self._transform.Identity() - -# self._current_timestamp = time - -# # actors properties -# if in_scene: -# if self.is_interpolatable("position"): -# position = self.get_position(time) -# self._transform.Translate(*position) - -# if self.is_interpolatable("opacity"): -# opacity = self.get_opacity(time) -# [act.GetProperty().SetOpacity(opacity) for act in self.actors] - -# if self.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) - -# if self.is_interpolatable("scale"): -# scale = self.get_scale(time) -# self._transform.Scale(*scale) - -# if self.is_interpolatable("color"): -# color = self.get_color(time) -# for act in self.actors: -# act.vcolors[:] = color * 255 -# utils.update_actor(act) - -# # update actors' transformation matrix -# [act.SetUserTransform(self._transform) for act in self.actors] - -# for attrib in self._data: -# callbacks = self._data.get(attrib, {}).get("callbacks", []) -# if callbacks != [] and self.is_interpolatable(attrib): -# value = self.get_value(attrib, time) -# [cbk(value) for cbk in callbacks] - -# # Executing general callbacks that's not related to any attribute -# [callback(time) for callback in self._general_callbacks] - -# # Also update all child Animations. -# [animation.update_animation(time=time) for animation in self._animations] - -# if self._scene and not has_handler: -# self._scene.reset_clipping_range() - -# def add_to_scene(self, scene): -# """Add this Animation, its actors and sub Animations to the scene""" -# [scene.add(actor) for actor in self._actors] -# [scene.add(static_act) for static_act in self._static_actors] -# [scene.add(animation) for animation in self._animations] - -# if self._motion_path_actor: -# scene.add(self._motion_path_actor) -# self._scene = scene -# self._added_to_scene = True -# self._start_time = perf_counter() -# self.update_animation(time=0) - -# def remove_from_scene(self, scene): -# """Remove Animation, its actors and sub Animations from the scene""" -# [scene.rm(act) for act in self.actors] -# [scene.rm(static_act) for static_act in self._static_actors] -# for anim in self.child_animations: -# anim.remove_from_scene(scene) -# if self._motion_path_actor: -# scene.rm(self._motion_path_actor) -# self._added_to_scene = False - - -# class CameraAnimation(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 - -# @property -# def camera(self) -> Camera: -# """Return the camera assigned to this animation. - -# Returns -# ------- -# Camera: -# The camera that is being animated by this CameraAnimation. - -# """ -# return self._camera - -# @camera.setter -# def camera(self, camera: Camera): -# """Set a camera to be animated. - -# Parameters -# ---------- -# camera: Camera -# The camera to be animated - -# """ -# self._camera = camera - -# def set_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) - -# def set_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) - -# def set_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) - -# def set_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) - -# def get_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. - -# """ -# return self.get_value("focal", t) - -# def get_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. - -# """ -# return self.get_value("view_up", t) - -# @warn_on_args_to_kwargs() -# def set_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) - -# @warn_on_args_to_kwargs() -# def set_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) - -# @warn_on_args_to_kwargs() -# def update_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. - -# """ -# if self._camera is None: -# if self._scene: -# self._camera = self._scene.camera() -# self.update_animation(tile=time) -# return -# else: -# if self.is_interpolatable("rotation"): -# pos = self._camera.GetPosition() -# translation = np.identity(4) -# translation[:3, 3] = pos -# # camera axis is reverted -# rot = -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()) - -# if self.is_interpolatable("position"): -# cam_pos = self.get_position(time) -# self._camera.SetPosition(cam_pos) - -# if self.is_interpolatable("focal"): -# cam_foc = self.get_focal(time) -# self._camera.SetFocalPoint(cam_foc) - -# if self.is_interpolatable("view_up"): -# cam_up = self.get_view_up(time) -# self._camera.SetViewUp(cam_up) -# elif not self.is_interpolatable("view_up"): -# # to preserve up-view as default after user interaction -# self._camera.SetViewUp(0, 1, 0) -# if self._scene: -# self._scene.reset_clipping_range() +from collections import defaultdict +from time import perf_counter +from warnings import warn + +import numpy as np +from scipy.spatial import transform + +from fury import utils + +# from fury.actor import line +from fury.animation.interpolator import ( # noqa F401 + linear_interpolator, + slerp, + spline_interpolator, + step_interpolator, +) +from fury.decorators import warn_on_args_to_kwargs + +# from fury.lib import Actor, Camera, Transform + + +class Animation: + """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 = None + self._parent_animation = None + self._scene = None + self._start_time = 0 + self._length = length + self._duration = length if length else 0 + self._loop = loop + self._current_timestamp = 0 + self._max_timestamp = 0 + self._added_to_scene = True + self._motion_path_res = motion_path_res + self._motion_path_actor = None + # self._transform = Transform() + self._general_callbacks = [] + # Adding actors to the animation + if actors is not None: + self.add_actor(actors) + + def update_duration(self): + """Update and return the duration of the Animation. + + Returns + ------- + float + The duration of the animation. + + """ + if self._length is not None: + self._duration = self._length + else: + self._duration = max( + self._max_timestamp, + max([0] + [anim.update_duration() for anim in self.child_animations]), + ) + + return self.duration + + @property + def duration(self): + """Return the duration of the animation. + + Returns + ------- + float + The duration of the animation. + + """ + return self._duration + + @property + def current_timestamp(self): + """Return the current time of the animation. + + Returns + ------- + float + The current time of the animation. + + """ + if self._timeline: + return self._timeline.current_timestamp + elif self.parent_animation: + return self.parent_animation.current_timestamp + return self._current_timestamp + + def update_motion_path(self): + """Update motion path visualization actor""" + res = self._motion_path_res + tl = self + while isinstance(tl._parent_animation, Animation): + if res: + break + tl = tl._parent_animation + res = tl._motion_path_res + if not res: + return + + lines = [] + colors = [] + if self.is_interpolatable("position"): + ts = np.linspace(0, self.duration, res) + [lines.append(self.get_position(t).tolist()) for t in ts] + if self.is_interpolatable("color"): + [colors.append(self.get_color(t)) for t in ts] + elif len(self._actors) >= 1: + colors = sum([i.vcolors[0] / 255 for i in self._actors]) / len( + self._actors + ) + else: + colors = [1, 1, 1] + + # if len(lines) > 0: + # lines = np.array([lines]) + # if isinstance(colors, list): + # colors = np.array([colors]) + + # mpa = line(lines, colors=colors, opacity=0.6) + # if self._scene: + # # remove old motion path actor + # if self._motion_path_actor is not None: + # 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. + + """ + return self._data + + def _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() + + if attrib not in data: + data[attrib] = { + "keyframes": defaultdict(dict), + "interpolator": { + "base": (linear_interpolator if attrib != "rotation" else slerp), + "func": None, + "args": defaultdict(), + }, + "callbacks": [], + } + return data.get(attrib) + + @warn_on_args_to_kwargs() + def get_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() + if attrib is None: + attribs = data.keys() + return { + attrib: data.get(attrib, {}).get("keyframes", {}) for attrib in attribs + } + return data.get(attrib, {}).get("keyframes", {}) + + @warn_on_args_to_kwargs() + def set_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) + for par, val in kwargs.items() + if val is not None + }, + } + + if update_interpolator: + interp = attrib_data.get("interpolator") + interp_base = interp.get( + "base", linear_interpolator if attrib != "rotation" else slerp + ) + args = interp.get("args", {}) + self.set_interpolator(attrib, interp_base, **args) + + if timestamp > self._max_timestamp: + self._max_timestamp = timestamp + if self._timeline is not None: + self._timeline.update_duration() + else: + self.update_duration() + self.update_animation(time=0) + self.update_motion_path() + + def set_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 + + """ + for t, keyframe in keyframes.items(): + if isinstance(keyframe, dict): + self.set_keyframe(attrib, t, **keyframe) + else: + self.set_keyframe(attrib, t, keyframe) + + def is_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_animation + parent_in_scene = True + if parent is not None: + parent_in_scene = parent._added_to_scene + + if self.is_interpolatable("in_scene"): + in_scene = parent_in_scene and self.get_value("in_scene", timestamp) + else: + in_scene = parent_in_scene + return in_scene + + def add_to_scene_at(self, timestamp): + """Set timestamp for adding Animation to scene event. + + Parameters + ---------- + timestamp: float + Timestamp of the event. + + """ + if not self.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) + + def remove_from_scene_at(self, timestamp): + """Set timestamp for removing Animation to scene event. + + Parameters + ---------- + timestamp: float + Timestamp of the event. + + """ + if not self.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) + + def _handle_scene_event(self, timestamp): + should_be_in_scene = self.is_inside_scene_at(timestamp) + if self._scene is not None: + if should_be_in_scene and not self._added_to_scene: + self._scene.add(*self._actors) + self._added_to_scene = True + elif not should_be_in_scene and self._added_to_scene: + self._scene.rm(*self._actors) + self._added_to_scene = False + + @warn_on_args_to_kwargs() + def set_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", {}) + if is_evaluator: + interp_data["base"] = None + interp_data["func"] = interpolator + else: + interp_data["base"] = interpolator + interp_data["args"] = kwargs + # Maintain interpolator base in case new keyframes are added. + if len(keyframes) == 0: + return + new_interp = interpolator(keyframes, **kwargs) + interp_data["func"] = new_interp + + # update motion path + self.update_duration() + self.update_motion_path() + + def is_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._data + return bool(data.get(attrib, {}).get("interpolator", {}).get("func")) + + @warn_on_args_to_kwargs() + def set_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: E501 + self.set_interpolator( + "position", interpolator, is_evaluator=is_evaluator, **kwargs + ) + + @warn_on_args_to_kwargs() + def set_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) + + @warn_on_args_to_kwargs() + def set_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) + + @warn_on_args_to_kwargs() + def set_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) + + @warn_on_args_to_kwargs() + def set_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) + + def get_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) + ) + return value + + def get_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) + ) + + def set_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) + + def set_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) + + def set_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()) + if no_components == 4: + self.set_keyframe("rotation", timestamp, rotation, **kwargs) + elif no_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, + ) + + def set_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) + + def set_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) + + def set_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) + + def set_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) + + def set_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) + + def set_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) + + def set_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) + + def get_position(self, t): + """Return the interpolated position. + + Parameters + ---------- + t: float + The time to interpolate position at. + + Returns + ------- + ndarray(1, 3): + The interpolated position. + + """ + return self.get_value("position", t) + + def get_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) + if len(rot) == 4: + if as_quat: + return rot + r = transform.Rotation.from_quat(rot) + degrees = r.as_euler("zxy", degrees=True)[[1, 2, 0]] + return degrees + elif not as_quat: + return rot + return transform.Rotation.from_euler( + "zxy", rot[[2, 0, 1]], degrees=True + ).as_quat() + + def get_scale(self, t): + """Return the interpolated scale. + + Parameters + ---------- + t: float + The time to interpolate scale at. + + Returns + ------- + ndarray(1, 3): + The interpolated scale. + + """ + return self.get_value("scale", t) + + def get_color(self, t): + """Return the interpolated color. + + Parameters + ---------- + t: float + The time to interpolate color value at. + + Returns + ------- + ndarray(1, 3): + The interpolated color. + + """ + return self.get_value("color", t) + + def get_opacity(self, t): + """Return the opacity value. + + Parameters + ---------- + t: float + The time to interpolate opacity at. + + Returns + ------- + ndarray(1, 1): + The interpolated opacity. + + """ + return self.get_value("opacity", t) + + def add(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. + + """ + if isinstance(item, list): + for a in item: + self.add(a) + return + # elif isinstance(item, Actor): + # self.add_actor(item) + elif isinstance(item, Animation): + self.add_child_animation(item) + else: + raise ValueError(f"Object of type {type(item)} can't be animated") + + def add_child_animation(self, animation): + """Add child Animation or list of Animations. + + Parameters + ---------- + animation: Animation or list[Animation] + Animation/s to be added. + + """ + if isinstance(animation, list): + for a in animation: + self.add_child_animation(a) + return + animation._parent_animation = self + animation.update_motion_path() + self._animations.append(animation) + self.update_duration() + + @warn_on_args_to_kwargs() + def add_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. + + """ + if isinstance(actor, list): + for a in actor: + self.add_actor(a, static=static) + elif static: + if actor not in self.static_actors: + self._static_actors.append(actor) + else: + if actor not in self._actors: + actor.vcolors = utils.colors_from_actor(actor) + self._actors.append(actor) + + @property + def timeline(self): + """Return the Timeline handling the current animation. + + Returns + ------- + Timeline: + The Timeline handling the current animation, None, if there is no + associated Timeline. + + """ + return self._timeline + + @timeline.setter + def timeline(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 = timeline + if self._animations: + for animation in self._animations: + animation.timeline = timeline + + @property + def parent_animation(self): + """Return the hierarchical parent Animation for current Animation. + + Returns + ------- + Animation: + The parent Animation. + + """ + return self._parent_animation + + @parent_animation.setter + def parent_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 + + @property + def actors(self): + """Return a list of actors. + + Returns + ------- + list: + List of actors controlled by the Animation. + + """ + return self._actors + + @property + def child_animations(self) -> "list[Animation]": + """Return a list of child Animations. + + Returns + ------- + list: + List of child Animations of this Animation. + + """ + return self._animations + + def add_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) + + @property + def static_actors(self): + """Return a list of static actors. + + Returns + ------- + list: + List of static actors. + + """ + return self._static_actors + + def remove_animations(self): + """Remove all child Animations from the Animation""" + self._animations.clear() + + def remove_actor(self, actor): + """Remove an actor from the Animation. + + Parameters + ---------- + actor: vtkActor + Actor to be removed from the Animation. + + """ + self._actors.remove(actor) + + def remove_actors(self): + """Remove all actors from the Animation""" + self._actors.clear() + + @property + def loop(self): + """Get loop condition of the current animation. + + Returns + ------- + bool + Whether the animation in loop mode (True) or play one mode (False). + + """ + return self._loop + + @loop.setter + def loop(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 + + def add_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. + + """ + if prop is None: + self._general_callbacks.append(callback) + return + attrib = self._get_attribute_data(prop) + attrib.get("callbacks", []).append(callback) + + @warn_on_args_to_kwargs() + def update_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 = True + if time is None: + time = perf_counter() - self._start_time + has_handler = False + + # handling in/out of scene events + in_scene = self.is_inside_scene_at(time) + self._handle_scene_event(time) + + if self.duration: + if self._loop and time > self.duration: + time = time % self.duration + elif time > self.duration: + time = self.duration + if isinstance(self._parent_animation, Animation): + self._transform.DeepCopy(self._parent_animation._transform) + else: + self._transform.Identity() + + self._current_timestamp = time + + # actors properties + if in_scene: + if self.is_interpolatable("position"): + position = self.get_position(time) + self._transform.Translate(*position) + + if self.is_interpolatable("opacity"): + opacity = self.get_opacity(time) + [act.GetProperty().SetOpacity(opacity) for act in self.actors] + + if self.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) + + if self.is_interpolatable("scale"): + scale = self.get_scale(time) + self._transform.Scale(*scale) + + if self.is_interpolatable("color"): + color = self.get_color(time) + for act in self.actors: + act.vcolors[:] = color * 255 + utils.update_actor(act) + + # update actors' transformation matrix + [act.SetUserTransform(self._transform) for act in self.actors] + + for attrib in self._data: + callbacks = self._data.get(attrib, {}).get("callbacks", []) + if callbacks != [] and self.is_interpolatable(attrib): + value = self.get_value(attrib, time) + [cbk(value) for cbk in callbacks] + + # Executing general callbacks that's not related to any attribute + [callback(time) for callback in self._general_callbacks] + + # Also update all child Animations. + [animation.update_animation(time=time) for animation in self._animations] + + if self._scene and not has_handler: + self._scene.reset_clipping_range() + + def add_to_scene(self, scene): + """Add this Animation, its actors and sub Animations to the scene""" + [scene.add(actor) for actor in self._actors] + [scene.add(static_act) for static_act in self._static_actors] + [scene.add(animation) for animation in self._animations] + + if self._motion_path_actor: + scene.add(self._motion_path_actor) + self._scene = scene + self._added_to_scene = True + self._start_time = perf_counter() + self.update_animation(time=0) + + def remove_from_scene(self, scene): + """Remove Animation, its actors and sub Animations from the scene""" + [scene.rm(act) for act in self.actors] + [scene.rm(static_act) for static_act in self._static_actors] + for anim in self.child_animations: + anim.remove_from_scene(scene) + if self._motion_path_actor: + scene.rm(self._motion_path_actor) + self._added_to_scene = False + + +class CameraAnimation(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 + + # @property + # def camera(self) -> Camera: + # """Return the camera assigned to this animation. + + # Returns + # ------- + # Camera: + # The camera that is being animated by this CameraAnimation. + + # """ + # return self._camera + + # @camera.setter + # def camera(self, camera: Camera): + # """Set a camera to be animated. + + # Parameters + # ---------- + # camera: Camera + # The camera to be animated + + # """ + # self._camera = camera + + def set_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) + + def set_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) + + def set_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) + + def set_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) + + def get_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. + + """ + return self.get_value("focal", t) + + def get_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. + + """ + return self.get_value("view_up", t) + + @warn_on_args_to_kwargs() + def set_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) + + @warn_on_args_to_kwargs() + def set_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) + + @warn_on_args_to_kwargs() + def update_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. + + """ + if self._camera is None: + if self._scene: + self._camera = self._scene.camera() + self.update_animation(tile=time) + return + else: + if self.is_interpolatable("rotation"): + pos = self._camera.GetPosition() + translation = np.identity(4) + translation[:3, 3] = pos + # camera axis is reverted + rot = -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()) + + if self.is_interpolatable("position"): + cam_pos = self.get_position(time) + self._camera.SetPosition(cam_pos) + + if self.is_interpolatable("focal"): + cam_foc = self.get_focal(time) + self._camera.SetFocalPoint(cam_foc) + + if self.is_interpolatable("view_up"): + cam_up = self.get_view_up(time) + self._camera.SetViewUp(cam_up) + elif not self.is_interpolatable("view_up"): + # to preserve up-view as default after user interaction + self._camera.SetViewUp(0, 1, 0) + if self._scene: + self._scene.reset_clipping_range()