mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
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
This commit is contained in:
parent
f9351536e4
commit
602809758e
12 changed files with 115 additions and 51 deletions
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
]
|
]
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -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": {},
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -817,6 +817,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, **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):
|
def set_coord(self, value, dim, direction=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)
|
||||||
|
@ -1181,21 +1196,21 @@ class Mobject(object):
|
||||||
def match_depth(self, mobject, **kwargs):
|
def match_depth(self, mobject, **kwargs):
|
||||||
return self.match_dim_size(mobject, 2, **kwargs)
|
return self.match_dim_size(mobject, 2, **kwargs)
|
||||||
|
|
||||||
def match_coord(self, mobject, dim, direction=ORIGIN):
|
def match_coord(self, mobject_or_point, dim, direction=ORIGIN):
|
||||||
return self.set_coord(
|
if isinstance(mobject_or_point, Mobject):
|
||||||
mobject.get_coord(dim, direction),
|
coord = mobject_or_point.get_coord(dim, direction)
|
||||||
dim=dim,
|
else:
|
||||||
direction=direction,
|
coord = mobject_or_point[dim]
|
||||||
)
|
return self.set_coord(coord, dim=dim, direction=direction)
|
||||||
|
|
||||||
def match_x(self, mobject, direction=ORIGIN):
|
def match_x(self, mobject_or_point, direction=ORIGIN):
|
||||||
return self.match_coord(mobject, 0, direction)
|
return self.match_coord(mobject_or_point, 0, direction)
|
||||||
|
|
||||||
def match_y(self, mobject, direction=ORIGIN):
|
def match_y(self, mobject_or_point, direction=ORIGIN):
|
||||||
return self.match_coord(mobject, 1, direction)
|
return self.match_coord(mobject_or_point, 1, direction)
|
||||||
|
|
||||||
def match_z(self, mobject, direction=ORIGIN):
|
def match_z(self, mobject_or_point, direction=ORIGIN):
|
||||||
return self.match_coord(mobject, 2, direction)
|
return self.match_coord(mobject_or_point, 2, direction)
|
||||||
|
|
||||||
def align_to(self, mobject_or_point, direction=ORIGIN):
|
def align_to(self, mobject_or_point, direction=ORIGIN):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -320,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)
|
|
@ -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 == "}":
|
||||||
|
|
|
@ -21,6 +21,8 @@ class PMobject(Mobject):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_points(self, points):
|
def set_points(self, points):
|
||||||
|
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
|
||||||
|
@ -34,14 +36,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):
|
def set_color_by_gradient(self, *colors):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -14,10 +14,10 @@ def bezier(points):
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Add table
Reference in a new issue