Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3abe3e8
Comment tweak
3b1b Dec 11, 2024
c03336d
Directly print traceback
3b1b Dec 11, 2024
b6f5593
Make InteracrtiveSceneEmbed into a class
3b1b Dec 11, 2024
f9a747a
Move remaining checkpoint_paste logic into scene_embed.py
3b1b Dec 11, 2024
482a055
Change null key to be the empty string
3b1b Dec 11, 2024
1566ce2
Ensure temporary svg paths for Text are deleted
3b1b Dec 11, 2024
22bc068
Remove unused dict_ops.py functions
3b1b Dec 11, 2024
53234bf
Remove break_into_partial_movies from file_writer configuration
3b1b Dec 11, 2024
7f9a2cf
Rewrite guarantee_existence using Path
3b1b Dec 11, 2024
390064f
Clean up SceneFileWriter
3b1b Dec 11, 2024
8f93bec
Remove --save_pngs CLI arg (which did nothing)
3b1b Dec 11, 2024
66d949b
Add --subdivide CLI arg
3b1b Dec 11, 2024
c9dd2b3
Remove add_extension_if_not_present
3b1b Dec 11, 2024
bce7b03
Remove get_sorted_integer_files
3b1b Dec 11, 2024
14bb485
Have find_file return Path
3b1b Dec 11, 2024
dbcec1f
Minor clean up
3b1b Dec 11, 2024
4dfd4a8
Clean up num_tex_symbols
3b1b Dec 11, 2024
0b1d102
Fix find_file
3b1b Dec 11, 2024
7fbbeb6
Merge branch 'master' of github.com:3b1b/manim into video-work
3b1b Dec 12, 2024
82e24f8
Minor cleanup for extract_scene.py
3b1b Dec 12, 2024
c1b52bf
Add preview_frame_while_skipping option to scene config
3b1b Dec 12, 2024
8ff8f98
Use shell.showtraceback function
3b1b Dec 12, 2024
d8e06c6
Move keybindings to config, instead of in-place constants
3b1b Dec 12, 2024
8cc5195
Replace DEGREES -> DEG
3b1b Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/documentation/constants.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Mathematical constant

PI = np.pi
TAU = 2 * PI
DEGREES = TAU / 360
DEG = TAU / 360

Text
----
Expand Down
12 changes: 6 additions & 6 deletions docs/source/getting_started/example_scenes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ InteractiveDevlopment
self.play(ReplacementTransform(square, circle))
self.wait()
self.play(circle.animate.stretch(4, 0))
self.play(Rotate(circle, 90 * DEGREES))
self.play(Rotate(circle, 90 * DEG))
self.play(circle.animate.shift(2 * RIGHT).scale(0.25))

text = Text("""
Expand Down Expand Up @@ -221,7 +221,7 @@ TexTransformExample
self.play(
TransformMatchingTex(
lines[0].copy(), lines[1],
path_arc=90 * DEGREES,
path_arc=90 * DEG,
),
**play_kw
)
Expand Down Expand Up @@ -599,8 +599,8 @@ SurfaceExample
# Set perspective
frame = self.camera.frame
frame.set_euler_angles(
theta=-30 * DEGREES,
phi=70 * DEGREES,
theta=-30 * DEG,
phi=70 * DEG,
)

surface = surfaces[0]
Expand All @@ -624,8 +624,8 @@ SurfaceExample
self.play(
Transform(surface, surfaces[2]),
# Move camera frame during the transition
frame.animate.increment_phi(-10 * DEGREES),
frame.animate.increment_theta(-20 * DEGREES),
frame.animate.increment_phi(-10 * DEG),
frame.animate.increment_theta(-20 * DEG),
run_time=3
)
# Add ambient rotation
Expand Down
10 changes: 5 additions & 5 deletions example_scenes.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def construct(self):
# to go to a non-equal substring from the target,
# use the key map.
key_map={"+": "-"},
path_arc=90 * DEGREES,
path_arc=90 * DEG,
),
)
self.wait()
Expand All @@ -203,7 +203,7 @@ def construct(self):
TransformMatchingStrings(
lines[2].copy(), lines[3],
key_map={"2": R"\sqrt"},
path_arc=-30 * DEGREES,
path_arc=-30 * DEG,
),
)
self.wait(2)
Expand Down Expand Up @@ -616,8 +616,8 @@ def construct(self):
self.play(
Transform(surface, surfaces[2]),
# Move camera frame during the transition
self.frame.animate.increment_phi(-10 * DEGREES),
self.frame.animate.increment_theta(-20 * DEGREES),
self.frame.animate.increment_phi(-10 * DEG),
self.frame.animate.increment_theta(-20 * DEG),
run_time=3
)
# Add ambient rotation
Expand Down Expand Up @@ -666,7 +666,7 @@ def construct(self):
self.play(ReplacementTransform(square, circle))
self.wait()
self.play(circle.animate.stretch(4, 0))
self.play(Rotate(circle, 90 * DEGREES))
self.play(Rotate(circle, 90 * DEG))
self.play(circle.animate.shift(2 * RIGHT).scale(0.25))

text = Text("""
Expand Down
4 changes: 2 additions & 2 deletions manimlib/animation/indication.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS
from manimlib.constants import ORIGIN, RIGHT, UP
from manimlib.constants import SMALL_BUFF
from manimlib.constants import DEGREES
from manimlib.constants import DEG
from manimlib.constants import TAU
from manimlib.constants import GREY, YELLOW
from manimlib.mobject.geometry import Circle
Expand Down Expand Up @@ -395,7 +395,7 @@ def interpolate_submobject(


class TurnInsideOut(Transform):
def __init__(self, mobject: Mobject, path_arc: float = 90 * DEGREES, **kwargs):
def __init__(self, mobject: Mobject, path_arc: float = 90 * DEG, **kwargs):
super().__init__(mobject, path_arc=path_arc, **kwargs)

def create_target(self) -> Mobject:
Expand Down
4 changes: 2 additions & 2 deletions manimlib/animation/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import numpy as np

from manimlib.animation.animation import Animation
from manimlib.constants import DEGREES
from manimlib.constants import DEG
from manimlib.constants import OUT
from manimlib.mobject.mobject import Group
from manimlib.mobject.mobject import Mobject
Expand Down Expand Up @@ -314,7 +314,7 @@ def init_path_func(self) -> None:


class CyclicReplace(Transform):
def __init__(self, *mobjects: Mobject, path_arc=90 * DEGREES, **kwargs):
def __init__(self, *mobjects: Mobject, path_arc=90 * DEG, **kwargs):
super().__init__(Group(*mobjects), path_arc=path_arc, **kwargs)

def create_target(self) -> Mobject:
Expand Down
8 changes: 4 additions & 4 deletions manimlib/camera/camera_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import numpy as np
from scipy.spatial.transform import Rotation

from manimlib.constants import DEGREES, RADIANS
from manimlib.constants import DEG, RADIANS
from manimlib.constants import FRAME_SHAPE
from manimlib.constants import DOWN, LEFT, ORIGIN, OUT, RIGHT, UP
from manimlib.constants import PI
Expand All @@ -26,7 +26,7 @@ def __init__(
frame_shape: tuple[float, float] = FRAME_SHAPE,
center_point: Vect3 = ORIGIN,
# Field of view in the y direction
fovy: float = 45 * DEGREES,
fovy: float = 45 * DEG,
euler_axes: str = "zxz",
# This keeps it ordered first in a scene
z_index=-1,
Expand Down Expand Up @@ -181,7 +181,7 @@ def reorient(
Shortcut for set_euler_angles, defaulting to taking
in angles in degrees
"""
self.set_euler_angles(theta_degrees, phi_degrees, gamma_degrees, units=DEGREES)
self.set_euler_angles(theta_degrees, phi_degrees, gamma_degrees, units=DEG)
if center is not None:
self.move_to(np.array(center))
if height is not None:
Expand Down Expand Up @@ -209,7 +209,7 @@ def increment_gamma(self, dgamma: float, units=RADIANS):
self.increment_euler_angles(dgamma=dgamma, units=units)
return self

def add_ambient_rotation(self, angular_speed=1 * DEGREES):
def add_ambient_rotation(self, angular_speed=1 * DEG):
self.add_updater(lambda m, dt: m.increment_theta(angular_speed * dt))
return self

Expand Down
13 changes: 7 additions & 6 deletions manimlib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,6 @@ def parse_cli():
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",
help="Save each frame as a png",
)
parser.add_argument(
"-i", "--gif",
action="store_true",
Expand Down Expand Up @@ -148,6 +143,12 @@ def parse_cli():
action="store_true",
help="Show the output file in finder",
)
parser.add_argument(
"--subdivide",
action="store_true",
help="Divide the output animation into individual movie files " +
"for each animation",
)
parser.add_argument(
"--file_name",
help="Name for the movie or image file",
Expand Down Expand Up @@ -261,8 +262,8 @@ def update_file_writer_config(config: dict, args: Namespace):
file_writer_config = config["file_writer"]
file_writer_config.update(
write_to_movie=(not args.skip_animations and args.write_file),
subdivide_output=args.subdivide,
save_last_frame=(args.skip_animations and args.write_file),
save_pngs=args.save_pngs,
png_mode=("RGBA" if args.transparent else "RGB"),
movie_file_extension=(get_file_ext(args)),
output_directory=get_output_directory(args, config),
Expand Down
5 changes: 3 additions & 2 deletions manimlib/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@
# Angles
PI: float = np.pi
TAU: float = 2 * PI
DEGREES: float = TAU / 360
DEG: float = TAU / 360
DEGREES = DEG # Many older animations use teh full name
# Nice to have a constant for readability
# when juxtaposed with expressions like 30 * DEGREES
# when juxtaposed with expressions like 30 * DEG
RADIANS: float = 1

# Related to Text
Expand Down
24 changes: 17 additions & 7 deletions manimlib/default_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,6 @@ camera:
fps: 30
background_opacity: 1.0
file_writer:
# If break_into_partial_movies is set to True, then many small
# files will be written corresponding to each Scene.play and
# Scene.wait call, and these files will then be combined
# to form the full scene. Sometimes video-editing is made
# easier when working with the broken up scene, which
# effectively has cuts at all the places you might want.
break_into_partial_movies: False
# What command to use for ffmpeg
ffmpeg_bin: "ffmpeg"
# Parameters to pass into ffmpeg
Expand All @@ -71,6 +64,9 @@ file_writer:
scene:
show_animation_progress: False
leave_progress_bars: False
# When skipping animations, should a single frame be rendered
# at the end of each play call?
preview_while_skipping: True
# How long does a scene pause on Scene.wait calls
default_wait_time: 1.0
vmobject:
Expand Down Expand Up @@ -104,6 +100,20 @@ sizes:
# Default buffers used in Mobject.next_to or Mobject.to_edge
default_mobject_to_edge_buff: 0.5
default_mobject_to_mobject_buff: 0.25
key_bindings:
pan_3d: 'd'
pan: 'f'
reset: 'r'
quit: 'q' # Together with command
select: 's'
unselect: 'u'
grab: 'g'
x_grab: 'h'
y_grab: 'v'
resize: 't'
color: 'c'
information: 'i'
cursor: 'k'
colors:
blue_e: "#1C758A"
blue_d: "#29ABCA"
Expand Down
56 changes: 24 additions & 32 deletions manimlib/extract_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
if TYPE_CHECKING:
Module = importlib.util.types.ModuleType
from typing import Optional
from addict import Dict


class BlankScene(InteractiveScene):
Expand Down Expand Up @@ -43,11 +44,7 @@ def prompt_user_for_choice(scene_classes):
print(f"{str(idx).zfill(max_digits)}: {name}")
name_to_class[name] = scene_class
try:
user_input = input(
"\nThat module has multiple scenes, " + \
"which ones would you like to render?" + \
"\nScene Name or Number: "
)
user_input = input("\nSelect which scene to render (by name or number): ")
return [
name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str) - 1]
for split_str in user_input.replace(" ", "").split(",")
Expand Down Expand Up @@ -80,29 +77,29 @@ def compute_total_frames(scene_class, scene_config):
return int(total_time * manim_config.camera.fps)


def scene_from_class(scene_class, scene_config, run_config):
def scene_from_class(scene_class, scene_config: Dict, run_config: Dict):
fw_config = manim_config.file_writer
if fw_config.write_to_movie and run_config.prerun:
scene_config.file_writer_config.total_frames = compute_total_frames(scene_class, scene_config)
return scene_class(**scene_config)


def get_scenes_to_render(all_scene_classes, scene_config, run_config):
if run_config["write_all"]:
return [sc(**scene_config) for sc in all_scene_classes]
def note_missing_scenes(arg_names, module_names):
for name in arg_names:
if name not in module_names:
log.error(f"No scene named {name} found")

names_to_classes = {sc.__name__: sc for sc in all_scene_classes}
scene_names = run_config["scene_names"]

for name in set.difference(set(scene_names), names_to_classes):
log.error(f"No scene named {name} found")
scene_names.remove(name)

if scene_names:
classes_to_run = [names_to_classes[name] for name in scene_names]
elif len(all_scene_classes) == 1:
classes_to_run = [all_scene_classes[0]]
def get_scenes_to_render(all_scene_classes: list, scene_config: Dict, run_config: Dict):
if run_config["write_all"] or len(all_scene_classes) == 1:
classes_to_run = all_scene_classes
else:
name_to_class = {sc.__name__: sc for sc in all_scene_classes}
classes_to_run = [name_to_class.get(name) for name in run_config.scene_names]
classes_to_run = list(filter(lambda x: x, classes_to_run)) # Remove Nones
note_missing_scenes(run_config.scene_names, name_to_class.keys())

if len(classes_to_run) == 0:
classes_to_run = prompt_user_for_choice(all_scene_classes)

return [
Expand All @@ -111,7 +108,10 @@ def get_scenes_to_render(all_scene_classes, scene_config, run_config):
]


def get_scene_classes_from_module(module):
def get_scene_classes(module: Optional[Module]):
if module is None:
# If no module was passed in, just play the blank scene
return [BlankScene(**scene_config)]
if hasattr(module, "SCENES_IN_ORDER"):
return module.SCENES_IN_ORDER
else:
Expand Down Expand Up @@ -162,24 +162,16 @@ def insert_embed_line_to_module(module: Module, line_number: int):
exec(code_object, module.__dict__)


def get_scene_module(file_name: Optional[str], embed_line: Optional[int], is_reload: bool = False) -> Module:
def get_module(file_name: Optional[str], embed_line: Optional[int], is_reload: bool = False) -> Module:
module = ModuleLoader.get_module(file_name, is_reload)
if embed_line:
insert_embed_line_to_module(module, embed_line)
return module


def main(scene_config, run_config):
module = get_scene_module(
run_config["file_name"],
run_config["embed_line"],
run_config["is_reload"]
)
if module is None:
# If no module was passed in, just play the blank scene
return [BlankScene(**scene_config)]

all_scene_classes = get_scene_classes_from_module(module)
def main(scene_config: Dict, run_config: Dict):
module = get_module(run_config.file_name, run_config.embed_line, run_config.is_reload)
all_scene_classes = get_scene_classes(module)
scenes = get_scenes_to_render(all_scene_classes, scene_config, run_config)
if len(scenes) == 0:
print("No scenes found to run")
Expand Down
6 changes: 3 additions & 3 deletions manimlib/mobject/coordinate_systems.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import itertools as it

from manimlib.constants import BLACK, BLUE, BLUE_D, BLUE_E, GREEN, GREY_A, WHITE, RED
from manimlib.constants import DEGREES, PI
from manimlib.constants import DEG, PI
from manimlib.constants import DL, UL, DOWN, DR, LEFT, ORIGIN, OUT, RIGHT, UP
from manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS
from manimlib.constants import MED_SMALL_BUFF, SMALL_BUFF
Expand Down Expand Up @@ -307,7 +307,7 @@ def get_graph_label(

point = self.input_to_graph_point(x, graph)
angle = self.angle_of_tangent(x, graph)
normal = rotate_vector(RIGHT, angle + 90 * DEGREES)
normal = rotate_vector(RIGHT, angle + 90 * DEG)
if normal[1] < 0:
normal *= -1
label.next_to(point, normal, buff=buff)
Expand Down Expand Up @@ -474,7 +474,7 @@ def __init__(
),
length=height,
)
self.y_axis.rotate(90 * DEGREES, about_point=ORIGIN)
self.y_axis.rotate(90 * DEG, about_point=ORIGIN)
# Add as a separate group in case various other
# mobjects are added to self, as for example in
# NumberPlane below
Expand Down
Loading
Loading