Merge branch '3b1b-master'

This commit is contained in:
TonyCrane 2022-02-14 14:12:21 +08:00
commit e39f81ccff
No known key found for this signature in database
GPG key ID: 2313A5058A9C637C
17 changed files with 134 additions and 60 deletions

View file

@ -1,7 +1,6 @@
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation
from manimlib.animation.composition import Succession from manimlib.animation.composition import Succession
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.mobject import Group
from manimlib.utils.bezier import integer_interpolate from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.rate_functions import linear from manimlib.utils.rate_functions import linear
@ -147,7 +146,7 @@ class Write(DrawBorderThenFill):
else: else:
self.run_time = 2 self.run_time = 2
if self.lag_ratio is None: 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): class ShowIncreasingSubsets(Animation):

View file

@ -17,7 +17,6 @@ class Broadcast(LaggedStart):
"remover": True, "remover": True,
"lag_ratio": 0.2, "lag_ratio": 0.2,
"run_time": 3, "run_time": 3,
"remover": True,
} }
def __init__(self, focal_point, **kwargs): def __init__(self, focal_point, **kwargs):

View file

@ -65,6 +65,12 @@ def parse_cli():
action="store_true", action="store_true",
help="Show window in full screen", 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( parser.add_argument(
"-g", "--save_pngs", "-g", "--save_pngs",
action="store_true", action="store_true",
@ -306,6 +312,7 @@ def get_configuration(args):
"start_at_animation_number": args.start_at_animation_number, "start_at_animation_number": args.start_at_animation_number,
"end_at_animation_number": None, "end_at_animation_number": None,
"preview": not write_file, "preview": not write_file,
"presenter_mode": args.presenter_mode,
"leave_progress_bars": args.leave_progress_bars, "leave_progress_bars": args.leave_progress_bars,
} }

View file

@ -64,6 +64,7 @@ def get_scene_config(config):
"end_at_animation_number", "end_at_animation_number",
"leave_progress_bars", "leave_progress_bars",
"preview", "preview",
"presenter_mode",
] ]
]) ])

View file

@ -277,7 +277,7 @@ class CoordinateSystem():
class Axes(VGroup, CoordinateSystem): class Axes(VGroup, CoordinateSystem):
CONFIG = { CONFIG = {
"axis_config": { "axis_config": {
"include_tip": True, "include_tip": False,
"numbers_to_exclude": [0], "numbers_to_exclude": [0],
}, },
"x_axis_config": {}, "x_axis_config": {},

View file

@ -44,6 +44,8 @@ class ParametricCurve(VMobject):
self.add_points_as_corners(points[1:]) self.add_points_as_corners(points[1:])
if self.use_smoothing: if self.use_smoothing:
self.make_approximately_smooth() self.make_approximately_smooth()
if not self.has_points():
self.set_points([self.t_func(t_min)])
return self return self

View file

@ -112,7 +112,7 @@ class Matrix(VMobject):
"\\left[", "\\left[",
"\\begin{array}{c}", "\\begin{array}{c}",
*height * ["\\quad \\\\"], *height * ["\\quad \\\\"],
"\\end{array}" "\\end{array}",
"\\right]", "\\right]",
]))[0] ]))[0]
bracket_pair.set_height( bracket_pair.set_height(

View file

@ -899,6 +899,21 @@ class Mobject(object):
self.set_depth(max_depth, **kwargs) self.set_depth(max_depth, **kwargs)
return self 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): def set_coord(self, value: float, dim: int, direction: np.ndarray = ORIGIN):
curr = self.get_coord(dim, direction) curr = self.get_coord(dim, direction)
shift_vect = np.zeros(self.dim) shift_vect = np.zeros(self.dim)
@ -1295,21 +1310,38 @@ class Mobject(object):
def match_depth(self, mobject: "Mobject", **kwargs): def match_depth(self, mobject: "Mobject", **kwargs):
return self.match_dim_size(mobject, 2, **kwargs) return self.match_dim_size(mobject, 2, **kwargs)
def match_coord(self, mobject: "Mobject", dim: int, direction: np.ndarray = ORIGIN): def match_coord(
return self.set_coord( self,
mobject.get_coord(dim, direction), mobject_or_point: "Mobject" | np.ndarray,
dim=dim, dim: int,
direction=direction, 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): def match_x(
return self.match_coord(mobject, 0, direction) 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): def match_y(
return self.match_coord(mobject, 1, direction) 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): def match_z(
return self.match_coord(mobject, 2, direction) self,
mobject_or_point: "Mobject" | np.ndarray,
direction: np.ndarray = ORIGIN
):
return self.match_coord(mobject_or_point, 2, direction)
def align_to( def align_to(
self, self,

View file

@ -318,6 +318,9 @@ class Bubble(SVGMobject):
self.content = Mobject() self.content = Mobject()
self.refresh_triangulation() self.refresh_triangulation()
def init_colors(self):
VMobject.init_colors(self)
def get_tip(self): def get_tip(self):
# TODO, find a better way # TODO, find a better way
return self.get_corner(DOWN + self.direction) - 0.6 * self.direction return self.get_corner(DOWN + self.direction) - 0.6 * self.direction

View file

@ -13,7 +13,6 @@ from manimlib.mobject.geometry import Polygon
from manimlib.mobject.geometry import Polyline from manimlib.mobject.geometry import Polyline
from manimlib.mobject.geometry import Rectangle from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.geometry import RoundedRectangle from manimlib.mobject.geometry import RoundedRectangle
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.directories import get_mobject_data_dir from manimlib.utils.directories import get_mobject_data_dir
@ -321,4 +320,4 @@ class VMobjectFromSVGPath(VMobject):
_convert_point_to_3d(*segment.__getattribute__(attr_name)) _convert_point_to_3d(*segment.__getattribute__(attr_name))
for attr_name in attr_names for attr_name in attr_names
] ]
func(*points) func(*points)

View file

@ -36,20 +36,19 @@ class SingleStringTex(VMobject):
assert(isinstance(tex_string, str)) assert(isinstance(tex_string, str))
self.tex_string = tex_string self.tex_string = tex_string
if tex_string not in tex_string_with_color_to_mob_map: 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)
full_tex = self.get_tex_file_body(tex_string) filename = tex_to_svg_file(full_tex)
filename = tex_to_svg_file(full_tex) svg_mob = SVGMobject(
svg_mob = SVGMobject( filename,
filename, height=None,
height=None, color=self.color,
color=self.color, stroke_width=self.stroke_width,
stroke_width=self.stroke_width, path_string_config={
path_string_config={ "should_subdivide_sharp_curves": True,
"should_subdivide_sharp_curves": True, "should_remove_null_curves": True,
"should_remove_null_curves": True, }
} )
) tex_string_with_color_to_mob_map[(self.color, tex_string)] = svg_mob
tex_string_with_color_to_mob_map[(self.color, tex_string)] = svg_mob
self.add(*( self.add(*(
sm.copy() sm.copy()
for sm in tex_string_with_color_to_mob_map[(self.color, tex_string)] 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 Makes Tex resiliant to unmatched braces
""" """
num_unclosed_brackets = 0 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 == "{": if char == "{":
num_unclosed_brackets += 1 num_unclosed_brackets += 1
elif char == "}": elif char == "}":

View file

@ -35,6 +35,8 @@ class PMobject(Mobject):
return self return self
def set_points(self, points: npt.ArrayLike): def set_points(self, points: npt.ArrayLike):
if len(points) == 0:
points = np.zeros((0, 3))
super().set_points(points) super().set_points(points)
self.resize_points(len(points)) self.resize_points(len(points))
return self return self
@ -54,14 +56,18 @@ class PMobject(Mobject):
if color is not None: if color is not None:
if opacity is None: if opacity is None:
opacity = self.data["rgbas"][-1, 3] opacity = self.data["rgbas"][-1, 3]
new_rgbas = np.repeat( rgbas = np.repeat(
[color_to_rgba(color, opacity)], [color_to_rgba(color, opacity)],
len(points), len(points),
axis=0 axis=0
) )
elif rgbas is not None: if rgbas is not None:
new_rgbas = rgbas self.data["rgbas"][-len(rgbas):] = rgbas
self.data["rgbas"][-len(new_rgbas):] = new_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 return self
def set_color_by_gradient(self, *colors: ManimColor): def set_color_by_gradient(self, *colors: ManimColor):

View file

@ -277,7 +277,6 @@ class DiscreteGraphScene(Scene):
def trace_cycle(self, cycle=None, color="yellow", run_time=2.0): def trace_cycle(self, cycle=None, color="yellow", run_time=2.0):
if cycle is None: if cycle is None:
cycle = self.graph.region_cycles[0] cycle = self.graph.region_cycles[0]
time_per_edge = run_time / len(cycle)
next_in_cycle = it.cycle(cycle) next_in_cycle = it.cycle(cycle)
next(next_in_cycle) # jump one ahead next(next_in_cycle) # jump one ahead
self.traced_cycle = Mobject(*[ self.traced_cycle = Mobject(*[

View file

@ -36,6 +36,7 @@ class Scene(object):
"end_at_animation_number": None, "end_at_animation_number": None,
"leave_progress_bars": False, "leave_progress_bars": False,
"preview": True, "preview": True,
"presenter_mode": False,
"linger_after_completion": True, "linger_after_completion": True,
} }
@ -62,6 +63,7 @@ class Scene(object):
# Items associated with interaction # Items associated with interaction
self.mouse_point = Point() self.mouse_point = Point()
self.mouse_drag_point = Point() self.mouse_drag_point = Point()
self.hold_on_wait = not self.presenter_mode
# Much nicer to work with deterministic scenes # Much nicer to work with deterministic scenes
if self.random_seed is not None: if self.random_seed is not None:
@ -114,7 +116,7 @@ class Scene(object):
if self.quit_interaction: if self.quit_interaction:
self.unlock_mobject_data() self.unlock_mobject_data()
def embed(self): def embed(self, close_scene_on_exit=True):
if not self.preview: if not self.preview:
# If the scene is just being # If the scene is just being
# written, ignore embed calls # 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" 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()`") " the window directly. To do so, you need to type `touch()` or `self.interact()`")
shell(local_ns=local_ns, stack_depth=2) shell(local_ns=local_ns, stack_depth=2)
# End scene when exiting an embed. # End scene when exiting an embed
raise EndSceneEarlyException() if close_scene_on_exit:
raise EndSceneEarlyException()
def __str__(self): def __str__(self):
return self.__class__.__name__ return self.__class__.__name__
@ -432,6 +435,11 @@ class Scene(object):
def unlock_mobject_data(self): def unlock_mobject_data(self):
self.camera.release_static_mobjects() 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): def begin_animations(self, animations):
for animation in animations: for animation in animations:
animation.begin() animation.begin()
@ -477,19 +485,30 @@ class Scene(object):
self.unlock_mobject_data() self.unlock_mobject_data()
@handle_play_like_call @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.update_mobjects(dt=0) # Any problems with this?
self.lock_static_mobject_data() self.lock_static_mobject_data()
time_progression = self.get_wait_time_progression(duration, stop_condition) if self.presenter_mode and not self.skip_animations and not ignore_presenter_mode:
last_t = 0 while self.hold_on_wait:
for t in time_progression: self.update_frame(dt=1 / self.camera.frame_rate)
dt = t - last_t self.hold_on_wait = True
last_t = t else:
self.update_frame(dt) time_progression = self.get_wait_time_progression(duration, stop_condition)
self.emit_frame() last_t = 0
if stop_condition is not None and stop_condition(): for t in time_progression:
time_progression.close() dt = t - last_t
break 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() self.unlock_mobject_data()
return self return self
@ -610,6 +629,10 @@ class Scene(object):
self.camera.frame.to_default_state() self.camera.frame.to_default_state()
elif char == "q": elif char == "q":
self.quit_interaction = True 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): def on_resize(self, width: int, height: int):
self.camera.reset_pixel_shape(width, height) self.camera.reset_pixel_shape(width, height)

View file

@ -287,9 +287,6 @@ class LinearTransformationScene(VectorScene):
}, },
"background_plane_kwargs": { "background_plane_kwargs": {
"color": GREY, "color": GREY,
"axis_config": {
"stroke_color": GREY_B,
},
"axis_config": { "axis_config": {
"color": GREY, "color": GREY,
}, },

View file

@ -21,10 +21,10 @@ def bezier(
n = len(points) - 1 n = len(points) - 1
def result(t): def result(t):
return sum([ return sum(
((1 - t)**(n - k)) * (t**k) * choose(n, k) * point ((1 - t)**(n - k)) * (t**k) * choose(n, k) * point
for k, point in enumerate(points) for k, point in enumerate(points)
]) )
return result return result

View file

@ -1,6 +1,6 @@
import inspect import inspect
import numpy as np import numpy as np
from scipy import special import math
from functools import lru_cache from functools import lru_cache
@ -10,7 +10,11 @@ def sigmoid(x):
@lru_cache(maxsize=10) @lru_cache(maxsize=10)
def choose(n, k): 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): def get_num_args(function):