diff --git a/animation/animation.py b/animation/animation.py index 410c6cc2..5797cacb 100644 --- a/animation/animation.py +++ b/animation/animation.py @@ -123,6 +123,4 @@ class Animation(object): if surrounding_scene is not None: if self.is_remover(): surrounding_scene.remove(self.mobject) - else: - surrounding_scene.add(self.mobject) return self diff --git a/mobject/mobject.py b/mobject/mobject.py index 23b3f75b..75b36fd8 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -1,6 +1,4 @@ - - import copy import itertools as it import numpy as np @@ -22,6 +20,7 @@ from utils.paths import straight_path from utils.space_ops import angle_of_vector from utils.space_ops import complex_to_R3 from utils.space_ops import rotation_matrix +from utils.simple_functions import get_num_args from functools import reduce @@ -46,6 +45,7 @@ class Mobject(Container): self.color = Color(self.color) if self.name is None: self.name = self.__class__.__name__ + self.updaters = [] self.init_points() self.generate_points() self.init_colors() @@ -98,6 +98,8 @@ class Mobject(Container): setattr(self, attr, func(getattr(self, attr))) return self + # Displaying + def get_image(self, camera=None): if camera is None: from camera.camera import Camera @@ -142,6 +144,42 @@ class Mobject(Container): self.target = self.copy() return self.target + # Updating + + def update(self, dt): + for updater in self.updaters: + num_args = get_num_args(updater) + if num_args == 1: + updater(self) + elif num_args == 2: + updater(self, dt) + else: + raise Exception( + "Mobject updater expected 1 or 2 " + "arguments, %d given" % num_args + ) + + def get_time_based_updaters(self): + return [ + udpater + for updater in self.updaters + if get_num_args(updater) == 2 + ] + + def get_updaters(self): + return self.updaters + + def add_updater(self, update_function): + self.updaters.append(update_function) + + def remove_updater(self, update_function): + while update_function in self.updaters: + self.updaters.remove(update_function) + return self + + def clear_updaters(self): + self.updaters = [] + # Transforming operations def apply_to_family(self, func): @@ -604,6 +642,7 @@ class Mobject(Container): def get_color(self): return self.color + ## def save_state(self, use_deepcopy=False): diff --git a/mobject/types/point_cloud_mobject.py b/mobject/types/point_cloud_mobject.py index dbefff92..6677501c 100644 --- a/mobject/types/point_cloud_mobject.py +++ b/mobject/types/point_cloud_mobject.py @@ -13,7 +13,7 @@ from utils.iterables import stretch_array_to_length class PMobject(Mobject): CONFIG = { - "stroke_width": DEFAULT_POINT_THICKNESS, + "stroke_width": DEFAULT_STROKE_WIDTH, } def init_points(self): diff --git a/scene/scene.py b/scene/scene.py index 2cc50c35..9fee6196 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -172,9 +172,9 @@ class Scene(Container): self.clear() ### - def continual_update(self, dt=None): - if dt is None: - dt = self.frame_duration + def continual_update(self, dt): + for mobject in self.get_mobjects(): + mobject.update(dt) for continual_animation in self.continual_animations: continual_animation.update(dt) @@ -188,7 +188,17 @@ class Scene(Container): self.continual_animations = [ca for ca in self.continual_animations if ca in continual_animations] def should_continually_update(self): - return len(self.continual_animations) > 0 or self.always_continually_update + if self.always_continually_update: + return True + if len(self.continual_animations) > 0: + return True + any_time_based_update = any([ + len(m.get_time_based_updaters()) > 0 + for m in self.get_mobjects() + ]) + if any_time_based_update: + return True + return False ### @@ -230,6 +240,7 @@ class Scene(Container): mobjects, continual_animations = self.separate_mobjects_and_continual_animations( mobjects_or_continual_animations ) + mobjects += self.foreground_mobjects self.restructure_mobjects(to_remove=mobjects) self.mobjects += mobjects self.continual_animations += continual_animations @@ -330,15 +341,24 @@ class Scene(Container): return [m.copy() for m in self.mobjects] def get_moving_mobjects(self, *animations): - moving_mobjects = list(it.chain( - [ - anim.mobject for anim in animations - if anim.mobject not in self.foreground_mobjects - ], - [ca.mobject for ca in self.continual_animations], - self.foreground_mobjects, - )) - return moving_mobjects + # Go through mobjects from start to end, and + # as soon as there's one that needs updating of + # some kind per frame, return the list from that + # point forward. + animation_mobjects = [anim.mobject for anim in animations] + ca_mobjects = [ca.mobject for ca in self.continual_animations] + mobjects = self.get_mobjects() + for i, mob in enumerate(mobjects): + update_possibilities = [ + mob in animation_mobjects, + mob in ca_mobjects, + len(mob.get_updaters()) > 0, + mob in self.foreground_mobjects + ] + for possibility in update_possibilities: + if possibility: + return mobjects[i:] + return [] def get_time_progression(self, run_time): if self.skip_animations: @@ -439,25 +459,30 @@ class Scene(Container): # This is where kwargs to play like run_time and rate_func # get applied to all animations animation.update_config(**kwargs) + # Anything animated that's not already in the + # scene gets added to the scene + if animation.mobject not in self.mobjects: + self.add(animation.mobject) moving_mobjects = self.get_moving_mobjects(*animations) # Paint all non-moving objects onto the screen, so they don't # have to be rendered every frame self.update_frame(excluded_mobjects=moving_mobjects) static_image = self.get_frame() + total_run_time = 0 for t in self.get_animation_time_progression(animations): + self.continual_update(dt=t - total_run_time) for animation in animations: animation.update(t / animation.run_time) - self.continual_update() self.update_frame(moving_mobjects, static_image) self.add_frames(self.get_frame()) - self.add(*moving_mobjects) - self.mobjects_from_last_animation = moving_mobjects + total_run_time = t + self.mobjects_from_last_animation = [ + anim.mobject for anim in animations + ] self.clean_up_animations(*animations) if self.skip_animations: - # Todo, not great that this uses a variable from - # a previous loop... - self.continual_update(t) + self.continual_update(total_run_time) else: self.continual_update(0) self.num_plays += 1 @@ -466,7 +491,6 @@ class Scene(Container): def clean_up_animations(self, *animations): for animation in animations: animation.clean_up(self) - self.add(*self.foreground_mobjects) return self def get_mobjects_from_last_animation(self): @@ -476,17 +500,20 @@ class Scene(Container): def wait(self, duration=DEFAULT_WAIT_TIME): if self.should_continually_update(): + total_time = 0 for t in self.get_time_progression(duration): - self.continual_update() + self.continual_update(dt=t - total_time) self.update_frame() self.add_frames(self.get_frame()) + total_time = t elif self.skip_animations: # Do nothing return self else: self.update_frame() - self.add_frames(*[self.get_frame()] * - int(duration / self.frame_duration)) + n_frames = int(duration / self.frame_duration) + frame = self.get_frame() + self.add_frames(*[frame] * n_frames) return self def wait_to(self, time, assert_positive=True): diff --git a/utils/simple_functions.py b/utils/simple_functions.py index bf4540d6..48f88e4f 100644 --- a/utils/simple_functions.py +++ b/utils/simple_functions.py @@ -1,5 +1,6 @@ import numpy as np import operator as op +import inspect from functools import reduce @@ -16,6 +17,10 @@ def choose(n, r): numer = reduce(op.mul, range(n, n - r, -1), 1) return numer // denom + +def get_num_args(function): + return len(inspect.signature(function).parameters) + # Just to have a less heavyweight name for this extremely common operation # # We may wish to have more fine-grained control over division by zero behavior