From f9351536e4a45d7c937c1830149693d9599654e8 Mon Sep 17 00:00:00 2001 From: Elisha Hollander Date: Sun, 13 Feb 2022 05:12:41 +0200 Subject: [PATCH 1/3] minor fixes (#1737) --- manimlib/animation/creation.py | 1 - manimlib/animation/specialized.py | 1 - manimlib/mobject/matrix.py | 2 +- manimlib/mobject/svg/svg_mobject.py | 1 - manimlib/once_useful_constructs/graph_theory.py | 1 - manimlib/scene/vector_space_scene.py | 3 --- 6 files changed, 1 insertion(+), 8 deletions(-) diff --git a/manimlib/animation/creation.py b/manimlib/animation/creation.py index 8101b12a..ccc76295 100644 --- a/manimlib/animation/creation.py +++ b/manimlib/animation/creation.py @@ -1,7 +1,6 @@ from manimlib.animation.animation import Animation from manimlib.animation.composition import Succession from manimlib.mobject.types.vectorized_mobject import VMobject -from manimlib.mobject.mobject import Group from manimlib.utils.bezier import integer_interpolate from manimlib.utils.config_ops import digest_config from manimlib.utils.rate_functions import linear diff --git a/manimlib/animation/specialized.py b/manimlib/animation/specialized.py index a0812b4c..389eb93c 100644 --- a/manimlib/animation/specialized.py +++ b/manimlib/animation/specialized.py @@ -17,7 +17,6 @@ class Broadcast(LaggedStart): "remover": True, "lag_ratio": 0.2, "run_time": 3, - "remover": True, } def __init__(self, focal_point, **kwargs): diff --git a/manimlib/mobject/matrix.py b/manimlib/mobject/matrix.py index 3c30fff0..18a22b20 100644 --- a/manimlib/mobject/matrix.py +++ b/manimlib/mobject/matrix.py @@ -112,7 +112,7 @@ class Matrix(VMobject): "\\left[", "\\begin{array}{c}", *height * ["\\quad \\\\"], - "\\end{array}" + "\\end{array}", "\\right]", ]))[0] bracket_pair.set_height( diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index e134f22b..4045a9d5 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -13,7 +13,6 @@ from manimlib.mobject.geometry import Polygon from manimlib.mobject.geometry import Polyline from manimlib.mobject.geometry import Rectangle from manimlib.mobject.geometry import RoundedRectangle -from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.utils.config_ops import digest_config from manimlib.utils.directories import get_mobject_data_dir diff --git a/manimlib/once_useful_constructs/graph_theory.py b/manimlib/once_useful_constructs/graph_theory.py index 820d3b7a..9baee424 100644 --- a/manimlib/once_useful_constructs/graph_theory.py +++ b/manimlib/once_useful_constructs/graph_theory.py @@ -277,7 +277,6 @@ class DiscreteGraphScene(Scene): def trace_cycle(self, cycle=None, color="yellow", run_time=2.0): if cycle is None: cycle = self.graph.region_cycles[0] - time_per_edge = run_time / len(cycle) next_in_cycle = it.cycle(cycle) next(next_in_cycle) # jump one ahead self.traced_cycle = Mobject(*[ diff --git a/manimlib/scene/vector_space_scene.py b/manimlib/scene/vector_space_scene.py index 54651808..e85c96d2 100644 --- a/manimlib/scene/vector_space_scene.py +++ b/manimlib/scene/vector_space_scene.py @@ -287,9 +287,6 @@ class LinearTransformationScene(VectorScene): }, "background_plane_kwargs": { "color": GREY, - "axis_config": { - "stroke_color": GREY_B, - }, "axis_config": { "color": GREY, }, From 602809758e4def8668aa9c3d35630ed9b378d777 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 13 Feb 2022 15:16:16 -0800 Subject: [PATCH 2/3] Video work (#1739) * Enable setting points to a null list, and adding one point at a time. * Add refresh_locked_data * Add presenter mode to scenes with -p option * Allow for an embed by hitting e during interaction * Add set_min_height, etc. * Make sure null parametric curve has at least one point * Account for edge case where \{ is used in Tex * Allow for logging notes in wait calls, useful for presenter mode * Simplify choose, and add gen_choose for fractional amounts * Default to no top on axes * Allow match_x, match_y, etc. to take in a point * Allow wait calls to ignore presenter mode * Just use math.combo, no caching with choose(n, r) * Use generator instead of list in bezier * Bubble init_colors should override * Account for "px" values read in from an svg * Stop displaying when writing is happening * Update the way Bubble override SVG colors --- manimlib/config.py | 7 +++ manimlib/extract_scene.py | 1 + manimlib/mobject/coordinate_systems.py | 2 +- manimlib/mobject/functions.py | 2 + manimlib/mobject/mobject.py | 39 +++++++++----- manimlib/mobject/svg/drawings.py | 3 ++ manimlib/mobject/svg/svg_mobject.py | 2 +- manimlib/mobject/svg/tex_mobject.py | 33 ++++++------ manimlib/mobject/types/point_cloud_mobject.py | 14 +++-- manimlib/scene/scene.py | 51 ++++++++++++++----- manimlib/utils/bezier.py | 4 +- manimlib/utils/simple_functions.py | 8 ++- 12 files changed, 115 insertions(+), 51 deletions(-) diff --git a/manimlib/config.py b/manimlib/config.py index ed5da5a9..e22d2ea3 100644 --- a/manimlib/config.py +++ b/manimlib/config.py @@ -65,6 +65,12 @@ def parse_cli(): action="store_true", help="Show window in full screen", ) + parser.add_argument( + "-p", "--presenter_mode", + action="store_true", + help="scene will stay paused during wait calls until " + "space bar or right arrow is hit, like a slide show" + ) parser.add_argument( "-g", "--save_pngs", action="store_true", @@ -306,6 +312,7 @@ def get_configuration(args): "start_at_animation_number": args.start_at_animation_number, "end_at_animation_number": None, "preview": not write_file, + "presenter_mode": args.presenter_mode, "leave_progress_bars": args.leave_progress_bars, } diff --git a/manimlib/extract_scene.py b/manimlib/extract_scene.py index f6bbd0b9..abec96ec 100644 --- a/manimlib/extract_scene.py +++ b/manimlib/extract_scene.py @@ -64,6 +64,7 @@ def get_scene_config(config): "end_at_animation_number", "leave_progress_bars", "preview", + "presenter_mode", ] ]) diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index 46f81596..3ad01086 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -277,7 +277,7 @@ class CoordinateSystem(): class Axes(VGroup, CoordinateSystem): CONFIG = { "axis_config": { - "include_tip": True, + "include_tip": False, "numbers_to_exclude": [0], }, "x_axis_config": {}, diff --git a/manimlib/mobject/functions.py b/manimlib/mobject/functions.py index 4fe38053..3677a119 100644 --- a/manimlib/mobject/functions.py +++ b/manimlib/mobject/functions.py @@ -44,6 +44,8 @@ class ParametricCurve(VMobject): self.add_points_as_corners(points[1:]) if self.use_smoothing: self.make_approximately_smooth() + if not self.has_points(): + self.set_points([self.t_func(t_min)]) return self diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index c49a65b7..cf1889bf 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -817,6 +817,21 @@ class Mobject(object): self.set_depth(max_depth, **kwargs) return self + def set_min_width(self, min_width, **kwargs): + if self.get_width() < min_width: + self.set_width(min_width, **kwargs) + return self + + def set_min_height(self, min_height, **kwargs): + if self.get_height() < min_height: + self.set_height(min_height, **kwargs) + return self + + def set_min_depth(self, min_depth, **kwargs): + if self.get_depth() < min_depth: + self.set_depth(min_depth, **kwargs) + return self + def set_coord(self, value, dim, direction=ORIGIN): curr = self.get_coord(dim, direction) shift_vect = np.zeros(self.dim) @@ -1181,21 +1196,21 @@ class Mobject(object): def match_depth(self, mobject, **kwargs): return self.match_dim_size(mobject, 2, **kwargs) - def match_coord(self, mobject, dim, direction=ORIGIN): - return self.set_coord( - mobject.get_coord(dim, direction), - dim=dim, - direction=direction, - ) + def match_coord(self, mobject_or_point, dim, direction=ORIGIN): + if isinstance(mobject_or_point, Mobject): + coord = mobject_or_point.get_coord(dim, direction) + else: + coord = mobject_or_point[dim] + return self.set_coord(coord, dim=dim, direction=direction) - def match_x(self, mobject, direction=ORIGIN): - return self.match_coord(mobject, 0, direction) + def match_x(self, mobject_or_point, direction=ORIGIN): + return self.match_coord(mobject_or_point, 0, direction) - def match_y(self, mobject, direction=ORIGIN): - return self.match_coord(mobject, 1, direction) + def match_y(self, mobject_or_point, direction=ORIGIN): + return self.match_coord(mobject_or_point, 1, direction) - def match_z(self, mobject, direction=ORIGIN): - return self.match_coord(mobject, 2, direction) + def match_z(self, mobject_or_point, direction=ORIGIN): + return self.match_coord(mobject_or_point, 2, direction) def align_to(self, mobject_or_point, direction=ORIGIN): """ diff --git a/manimlib/mobject/svg/drawings.py b/manimlib/mobject/svg/drawings.py index 41e9e907..d6d87fff 100644 --- a/manimlib/mobject/svg/drawings.py +++ b/manimlib/mobject/svg/drawings.py @@ -318,6 +318,9 @@ class Bubble(SVGMobject): self.content = Mobject() self.refresh_triangulation() + def init_colors(self): + VMobject.init_colors(self) + def get_tip(self): # TODO, find a better way return self.get_corner(DOWN + self.direction) - 0.6 * self.direction diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index 4045a9d5..fd79dffa 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -320,4 +320,4 @@ class VMobjectFromSVGPath(VMobject): _convert_point_to_3d(*segment.__getattribute__(attr_name)) for attr_name in attr_names ] - func(*points) + func(*points) \ No newline at end of file diff --git a/manimlib/mobject/svg/tex_mobject.py b/manimlib/mobject/svg/tex_mobject.py index 1fb10f3f..c81a781b 100644 --- a/manimlib/mobject/svg/tex_mobject.py +++ b/manimlib/mobject/svg/tex_mobject.py @@ -36,20 +36,19 @@ class SingleStringTex(VMobject): assert(isinstance(tex_string, str)) self.tex_string = tex_string if tex_string not in tex_string_with_color_to_mob_map: - with display_during_execution(f" Writing \"{tex_string}\""): - full_tex = self.get_tex_file_body(tex_string) - filename = tex_to_svg_file(full_tex) - svg_mob = SVGMobject( - filename, - height=None, - color=self.color, - stroke_width=self.stroke_width, - path_string_config={ - "should_subdivide_sharp_curves": True, - "should_remove_null_curves": True, - } - ) - tex_string_with_color_to_mob_map[(self.color, tex_string)] = svg_mob + full_tex = self.get_tex_file_body(tex_string) + filename = tex_to_svg_file(full_tex) + svg_mob = SVGMobject( + filename, + height=None, + color=self.color, + stroke_width=self.stroke_width, + path_string_config={ + "should_subdivide_sharp_curves": True, + "should_remove_null_curves": True, + } + ) + tex_string_with_color_to_mob_map[(self.color, tex_string)] = svg_mob self.add(*( sm.copy() for sm in tex_string_with_color_to_mob_map[(self.color, tex_string)] @@ -140,7 +139,11 @@ class SingleStringTex(VMobject): Makes Tex resiliant to unmatched braces """ num_unclosed_brackets = 0 - for char in tex: + for i in range(len(tex)): + if i > 0 and tex[i - 1] == "\\": + # So as to not count '\{' type expressions + continue + char = tex[i] if char == "{": num_unclosed_brackets += 1 elif char == "}": diff --git a/manimlib/mobject/types/point_cloud_mobject.py b/manimlib/mobject/types/point_cloud_mobject.py index 28ccee7e..88af6565 100644 --- a/manimlib/mobject/types/point_cloud_mobject.py +++ b/manimlib/mobject/types/point_cloud_mobject.py @@ -21,6 +21,8 @@ class PMobject(Mobject): return self def set_points(self, points): + if len(points) == 0: + points = np.zeros((0, 3)) super().set_points(points) self.resize_points(len(points)) return self @@ -34,14 +36,18 @@ class PMobject(Mobject): if color is not None: if opacity is None: opacity = self.data["rgbas"][-1, 3] - new_rgbas = np.repeat( + rgbas = np.repeat( [color_to_rgba(color, opacity)], len(points), axis=0 ) - elif rgbas is not None: - new_rgbas = rgbas - self.data["rgbas"][-len(new_rgbas):] = new_rgbas + if rgbas is not None: + self.data["rgbas"][-len(rgbas):] = rgbas + return self + + def add_point(self, point, rgba=None, color=None, opacity=None): + rgbas = None if rgba is None else [rgba] + self.add_points([point], rgbas, color, opacity) return self def set_color_by_gradient(self, *colors): diff --git a/manimlib/scene/scene.py b/manimlib/scene/scene.py index 256e192e..514e2b9f 100644 --- a/manimlib/scene/scene.py +++ b/manimlib/scene/scene.py @@ -36,6 +36,7 @@ class Scene(object): "end_at_animation_number": None, "leave_progress_bars": False, "preview": True, + "presenter_mode": False, "linger_after_completion": True, } @@ -62,6 +63,7 @@ class Scene(object): # Items associated with interaction self.mouse_point = Point() self.mouse_drag_point = Point() + self.hold_on_wait = not self.presenter_mode # Much nicer to work with deterministic scenes if self.random_seed is not None: @@ -114,7 +116,7 @@ class Scene(object): if self.quit_interaction: self.unlock_mobject_data() - def embed(self): + def embed(self, close_scene_on_exit=True): if not self.preview: # If the scene is just being # written, ignore embed calls @@ -139,8 +141,9 @@ class Scene(object): log.info("Tips: Now the embed iPython terminal is open. But you can't interact with" " the window directly. To do so, you need to type `touch()` or `self.interact()`") shell(local_ns=local_ns, stack_depth=2) - # End scene when exiting an embed. - raise EndSceneEarlyException() + # End scene when exiting an embed + if close_scene_on_exit: + raise EndSceneEarlyException() def __str__(self): return self.__class__.__name__ @@ -432,6 +435,11 @@ class Scene(object): def unlock_mobject_data(self): self.camera.release_static_mobjects() + def refresh_locked_data(self): + self.unlock_mobject_data() + self.lock_static_mobject_data() + return self + def begin_animations(self, animations): for animation in animations: animation.begin() @@ -477,19 +485,30 @@ class Scene(object): self.unlock_mobject_data() @handle_play_like_call - def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None): + def wait(self, + duration=DEFAULT_WAIT_TIME, + stop_condition=None, + note=None, + ignore_presenter_mode=False): + if note: + log.info(note) self.update_mobjects(dt=0) # Any problems with this? self.lock_static_mobject_data() - time_progression = self.get_wait_time_progression(duration, stop_condition) - last_t = 0 - for t in time_progression: - dt = t - last_t - last_t = t - self.update_frame(dt) - self.emit_frame() - if stop_condition is not None and stop_condition(): - time_progression.close() - break + if self.presenter_mode and not self.skip_animations and not ignore_presenter_mode: + while self.hold_on_wait: + self.update_frame(dt=1 / self.camera.frame_rate) + self.hold_on_wait = True + else: + time_progression = self.get_wait_time_progression(duration, stop_condition) + last_t = 0 + for t in time_progression: + dt = t - last_t + last_t = t + self.update_frame(dt) + self.emit_frame() + if stop_condition is not None and stop_condition(): + time_progression.close() + break self.unlock_mobject_data() return self @@ -610,6 +629,10 @@ class Scene(object): self.camera.frame.to_default_state() elif char == "q": self.quit_interaction = True + elif char == " ": + self.hold_on_wait = False + elif char == "e": + self.embed(close_scene_on_exit=False) def on_resize(self, width: int, height: int): self.camera.reset_pixel_shape(width, height) diff --git a/manimlib/utils/bezier.py b/manimlib/utils/bezier.py index c6b750e1..75f6c668 100644 --- a/manimlib/utils/bezier.py +++ b/manimlib/utils/bezier.py @@ -14,10 +14,10 @@ def bezier(points): n = len(points) - 1 def result(t): - return sum([ + return sum( ((1 - t)**(n - k)) * (t**k) * choose(n, k) * point for k, point in enumerate(points) - ]) + ) return result diff --git a/manimlib/utils/simple_functions.py b/manimlib/utils/simple_functions.py index 5a608621..c6a7c5d1 100644 --- a/manimlib/utils/simple_functions.py +++ b/manimlib/utils/simple_functions.py @@ -1,6 +1,6 @@ import inspect import numpy as np -from scipy import special +import math from functools import lru_cache @@ -10,7 +10,11 @@ def sigmoid(x): @lru_cache(maxsize=10) def choose(n, k): - return special.comb(n, k, exact=True) + return math.comb(n, k) + + +def gen_choose(n, r): + return np.prod(np.arange(n, n - r, -1)) / math.factorial(r) def get_num_args(function): From b3b7d214adff2088f6c75cbc814cbbf8f1a9b4e4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 13 Feb 2022 20:04:05 -0800 Subject: [PATCH 3/3] Fix Write bug (#1740) * Avoid division by zero error for calling Write on null objects --- manimlib/animation/creation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/animation/creation.py b/manimlib/animation/creation.py index ccc76295..5a6a04f9 100644 --- a/manimlib/animation/creation.py +++ b/manimlib/animation/creation.py @@ -146,7 +146,7 @@ class Write(DrawBorderThenFill): else: self.run_time = 2 if self.lag_ratio is None: - self.lag_ratio = min(4.0 / length, 0.2) + self.lag_ratio = min(4.0 / (length + 1.0), 0.2) class ShowIncreasingSubsets(Animation):