diff --git a/manimlib/animation/creation.py b/manimlib/animation/creation.py index 8101b12a..5a6a04f9 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 @@ -147,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): 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/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/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/mobject.py b/manimlib/mobject/mobject.py index 935c08a6..90acf22f 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -899,6 +899,21 @@ class Mobject(object): self.set_depth(max_depth, **kwargs) return self + def set_min_width(self, min_width: float, **kwargs): + if self.get_width() < min_width: + self.set_width(min_width, **kwargs) + return self + + def set_min_height(self, min_height: float, **kwargs): + if self.get_height() < min_height: + self.set_height(min_height, **kwargs) + return self + + def set_min_depth(self, min_depth: float, **kwargs): + if self.get_depth() < min_depth: + self.set_depth(min_depth, **kwargs) + return self + def set_coord(self, value: float, dim: int, direction: np.ndarray = ORIGIN): curr = self.get_coord(dim, direction) shift_vect = np.zeros(self.dim) @@ -1295,21 +1310,38 @@ class Mobject(object): def match_depth(self, mobject: "Mobject", **kwargs): return self.match_dim_size(mobject, 2, **kwargs) - def match_coord(self, mobject: "Mobject", dim: int, direction: np.ndarray = ORIGIN): - return self.set_coord( - mobject.get_coord(dim, direction), - dim=dim, - direction=direction, - ) + def match_coord( + self, + mobject_or_point: "Mobject" | np.ndarray, + dim: int, + direction: np.ndarray = 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: "Mobject", direction: np.ndarray = ORIGIN): - return self.match_coord(mobject, 0, direction) + def match_x( + self, + mobject_or_point: "Mobject" | np.ndarray, + direction: np.ndarray = ORIGIN + ): + return self.match_coord(mobject_or_point, 0, direction) - def match_y(self, mobject: "Mobject", direction: np.ndarray = ORIGIN): - return self.match_coord(mobject, 1, direction) + def match_y( + self, + mobject_or_point: "Mobject" | np.ndarray, + direction: np.ndarray = ORIGIN + ): + return self.match_coord(mobject_or_point, 1, direction) - def match_z(self, mobject: "Mobject", direction: np.ndarray = ORIGIN): - return self.match_coord(mobject, 2, direction) + def match_z( + self, + mobject_or_point: "Mobject" | np.ndarray, + direction: np.ndarray = ORIGIN + ): + return self.match_coord(mobject_or_point, 2, direction) def align_to( self, 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 e134f22b..fd79dffa 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 @@ -321,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 ffde8198..5cab597a 100644 --- a/manimlib/mobject/types/point_cloud_mobject.py +++ b/manimlib/mobject/types/point_cloud_mobject.py @@ -35,6 +35,8 @@ class PMobject(Mobject): return self def set_points(self, points: npt.ArrayLike): + if len(points) == 0: + points = np.zeros((0, 3)) super().set_points(points) self.resize_points(len(points)) return self @@ -54,14 +56,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: ManimColor): 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/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/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, }, diff --git a/manimlib/utils/bezier.py b/manimlib/utils/bezier.py index ddca0671..c21b7350 100644 --- a/manimlib/utils/bezier.py +++ b/manimlib/utils/bezier.py @@ -21,10 +21,10 @@ def bezier( 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):