Merge branch 'master' into refactor

This commit is contained in:
YishiMichael 2022-04-24 08:26:22 +08:00 committed by GitHub
commit 97edc2d6cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 1193 additions and 708 deletions

View file

@ -4,6 +4,8 @@ __version__ = pkg_resources.get_distribution("manimgl").version
from manimlib.constants import * from manimlib.constants import *
from manimlib.window import *
from manimlib.animation.animation import * from manimlib.animation.animation import *
from manimlib.animation.composition import * from manimlib.animation.composition import *
from manimlib.animation.creation import * from manimlib.animation.creation import *
@ -20,17 +22,16 @@ from manimlib.animation.update import *
from manimlib.camera.camera import * from manimlib.camera.camera import *
from manimlib.window import *
from manimlib.mobject.boolean_ops import * from manimlib.mobject.boolean_ops import *
from manimlib.mobject.coordinate_systems import *
from manimlib.mobject.changing import * from manimlib.mobject.changing import *
from manimlib.mobject.coordinate_systems import *
from manimlib.mobject.frame import * from manimlib.mobject.frame import *
from manimlib.mobject.functions import * from manimlib.mobject.functions import *
from manimlib.mobject.geometry import * from manimlib.mobject.geometry import *
from manimlib.mobject.interactive import * from manimlib.mobject.interactive import *
from manimlib.mobject.matrix import * from manimlib.mobject.matrix import *
from manimlib.mobject.mobject import * from manimlib.mobject.mobject import *
from manimlib.mobject.mobject_update_utils import *
from manimlib.mobject.number_line import * from manimlib.mobject.number_line import *
from manimlib.mobject.numbers import * from manimlib.mobject.numbers import *
from manimlib.mobject.probability import * from manimlib.mobject.probability import *
@ -43,17 +44,16 @@ from manimlib.mobject.svg.svg_mobject import *
from manimlib.mobject.svg.tex_mobject import * from manimlib.mobject.svg.tex_mobject import *
from manimlib.mobject.svg.text_mobject import * from manimlib.mobject.svg.text_mobject import *
from manimlib.mobject.three_dimensions import * from manimlib.mobject.three_dimensions import *
from manimlib.mobject.types.dot_cloud import *
from manimlib.mobject.types.image_mobject import * from manimlib.mobject.types.image_mobject import *
from manimlib.mobject.types.point_cloud_mobject import * from manimlib.mobject.types.point_cloud_mobject import *
from manimlib.mobject.types.surface import * from manimlib.mobject.types.surface import *
from manimlib.mobject.types.vectorized_mobject import * from manimlib.mobject.types.vectorized_mobject import *
from manimlib.mobject.types.dot_cloud import *
from manimlib.mobject.mobject_update_utils import *
from manimlib.mobject.value_tracker import * from manimlib.mobject.value_tracker import *
from manimlib.mobject.vector_field import * from manimlib.mobject.vector_field import *
from manimlib.scene.scene import *
from manimlib.scene.interactive_scene import * from manimlib.scene.interactive_scene import *
from manimlib.scene.scene import *
from manimlib.scene.three_d_scene import * from manimlib.scene.three_d_scene import *
from manimlib.utils.bezier import * from manimlib.utils.bezier import *
@ -62,9 +62,9 @@ from manimlib.utils.config_ops import *
from manimlib.utils.customization import * from manimlib.utils.customization import *
from manimlib.utils.debug import * from manimlib.utils.debug import *
from manimlib.utils.directories import * from manimlib.utils.directories import *
from manimlib.utils.file_ops import *
from manimlib.utils.images import * from manimlib.utils.images import *
from manimlib.utils.iterables import * from manimlib.utils.iterables import *
from manimlib.utils.file_ops import *
from manimlib.utils.paths import * from manimlib.utils.paths import *
from manimlib.utils.rate_functions import * from manimlib.utils.rate_functions import *
from manimlib.utils.simple_functions import * from manimlib.utils.simple_functions import *

View file

@ -1,9 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
import manimlib.config
import manimlib.logger
import manimlib.extract_scene
import manimlib.utils.init_config
from manimlib import __version__ from manimlib import __version__
import manimlib.config
import manimlib.extract_scene
import manimlib.logger
import manimlib.utils.init_config
def main(): def main():

View file

@ -1,7 +1,6 @@
from __future__ import annotations from __future__ import annotations
from copy import deepcopy from copy import deepcopy
from typing import Callable
from manimlib.mobject.mobject import _AnimationBuilder from manimlib.mobject.mobject import _AnimationBuilder
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
@ -12,6 +11,8 @@ from manimlib.utils.simple_functions import clip
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Callable
from manimlib.scene.scene import Scene from manimlib.scene.scene import Scene

View file

@ -1,9 +1,9 @@
from __future__ import annotations from __future__ import annotations
import numpy as np import numpy as np
from typing import Callable
from manimlib.animation.animation import Animation, prepare_animation from manimlib.animation.animation import Animation
from manimlib.animation.animation import prepare_animation
from manimlib.mobject.mobject import Group from manimlib.mobject.mobject import Group
from manimlib.utils.bezier import integer_interpolate from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import interpolate
@ -15,8 +15,10 @@ from manimlib.utils.simple_functions import clip
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from manimlib.scene.scene import Scene from typing import Callable
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
from manimlib.scene.scene import Scene
DEFAULT_LAGGED_START_LAG_RATIO = 0.05 DEFAULT_LAGGED_START_LAG_RATIO = 0.05

View file

@ -4,17 +4,17 @@ import numpy as np
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation
from manimlib.animation.transform import Transform from manimlib.animation.transform import Transform
from manimlib.mobject.mobject import Group
from manimlib.constants import ORIGIN from manimlib.constants import ORIGIN
from manimlib.mobject.mobject import Group
from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import interpolate
from manimlib.utils.rate_functions import there_and_back from manimlib.utils.rate_functions import there_and_back
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from manimlib.scene.scene import Scene
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.scene.scene import Scene
DEFAULT_FADE_LAG_RATIO = 0 DEFAULT_FADE_LAG_RATIO = 0

View file

@ -1,14 +1,15 @@
from __future__ import annotations from __future__ import annotations
from manimlib.constants import PI
from manimlib.animation.transform import Transform from manimlib.animation.transform import Transform
from manimlib.constants import PI
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
import numpy as np import numpy as np
from manimlib.mobject.mobject import Mobject
from manimlib.mobject.geometry import Arrow from manimlib.mobject.geometry import Arrow
from manimlib.mobject.mobject import Mobject
class GrowFromPoint(Transform): class GrowFromPoint(Transform):

View file

@ -1,40 +1,44 @@
from __future__ import annotations from __future__ import annotations
import math import math
from typing import Union, Sequence
import numpy as np import numpy as np
from manimlib.constants import *
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation
from manimlib.animation.movement import Homotopy
from manimlib.animation.composition import AnimationGroup from manimlib.animation.composition import AnimationGroup
from manimlib.animation.composition import Succession from manimlib.animation.composition import Succession
from manimlib.animation.creation import ShowCreation from manimlib.animation.creation import ShowCreation
from manimlib.animation.creation import ShowPartial from manimlib.animation.creation import ShowPartial
from manimlib.animation.fading import FadeOut from manimlib.animation.fading import FadeOut
from manimlib.animation.fading import FadeIn from manimlib.animation.fading import FadeIn
from manimlib.animation.movement import Homotopy
from manimlib.animation.transform import Transform from manimlib.animation.transform import Transform
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.constants import ORIGIN, RIGHT, UP
from manimlib.constants import SMALL_BUFF
from manimlib.constants import TAU
from manimlib.constants import GREY, YELLOW
from manimlib.mobject.geometry import Circle from manimlib.mobject.geometry import Circle
from manimlib.mobject.geometry import Dot from manimlib.mobject.geometry import Dot
from manimlib.mobject.geometry import Line
from manimlib.mobject.shape_matchers import SurroundingRectangle from manimlib.mobject.shape_matchers import SurroundingRectangle
from manimlib.mobject.shape_matchers import Underline from manimlib.mobject.shape_matchers import Underline
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.geometry import Line
from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import interpolate
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.rate_functions import there_and_back
from manimlib.utils.rate_functions import wiggle
from manimlib.utils.rate_functions import smooth from manimlib.utils.rate_functions import smooth
from manimlib.utils.rate_functions import squish_rate_func from manimlib.utils.rate_functions import squish_rate_func
from manimlib.utils.rate_functions import there_and_back
from manimlib.utils.rate_functions import wiggle
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
import colour from colour import Color
from typing import Union
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
ManimColor = Union[str, colour.Color, Sequence[float]]
ManimColor = Union[str, Color]
class FocusOn(Transform): class FocusOn(Transform):

View file

@ -1,14 +1,15 @@
from __future__ import annotations from __future__ import annotations
from typing import Callable, Sequence
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation
from manimlib.utils.rate_functions import linear from manimlib.utils.rate_functions import linear
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Callable, Sequence
import numpy as np import numpy as np
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject

View file

@ -1,11 +1,14 @@
from __future__ import annotations from __future__ import annotations
from typing import Callable
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation
from manimlib.mobject.numbers import DecimalNumber from manimlib.mobject.numbers import DecimalNumber
from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import interpolate
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable
class ChangingDecimal(Animation): class ChangingDecimal(Animation):
CONFIG = { CONFIG = {

View file

@ -1,10 +1,8 @@
from __future__ import annotations from __future__ import annotations
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation
from manimlib.constants import OUT from manimlib.constants import ORIGIN, OUT
from manimlib.constants import PI from manimlib.constants import PI, TAU
from manimlib.constants import TAU
from manimlib.constants import ORIGIN
from manimlib.utils.rate_functions import linear from manimlib.utils.rate_functions import linear
from manimlib.utils.rate_functions import smooth from manimlib.utils.rate_functions import smooth
@ -12,6 +10,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
import numpy as np import numpy as np
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject

View file

@ -1,15 +1,17 @@
from __future__ import annotations from __future__ import annotations
import numpy as np
from manimlib.animation.composition import LaggedStart from manimlib.animation.composition import LaggedStart
from manimlib.animation.transform import Restore from manimlib.animation.transform import Restore
from manimlib.constants import WHITE from manimlib.constants import BLACK, WHITE
from manimlib.constants import BLACK
from manimlib.mobject.geometry import Circle from manimlib.mobject.geometry import Circle
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import numpy as np
class Broadcast(LaggedStart): class Broadcast(LaggedStart):
CONFIG = { CONFIG = {

View file

@ -1,15 +1,13 @@
from __future__ import annotations from __future__ import annotations
import inspect import inspect
from typing import Callable, Union, Sequence
import numpy as np import numpy as np
import numpy.typing as npt
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation
from manimlib.constants import DEFAULT_POINTWISE_FUNCTION_RUN_TIME from manimlib.constants import DEFAULT_POINTWISE_FUNCTION_RUN_TIME
from manimlib.constants import OUT
from manimlib.constants import DEGREES from manimlib.constants import DEGREES
from manimlib.constants import OUT
from manimlib.mobject.mobject import Group from manimlib.mobject.mobject import Group
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
@ -21,9 +19,14 @@ from manimlib.utils.rate_functions import squish_rate_func
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
import colour from colour import Color
from typing import Callable, Union
import numpy.typing as npt
from manimlib.scene.scene import Scene from manimlib.scene.scene import Scene
ManimColor = Union[str, colour.Color, Sequence[float]]
ManimColor = Union[str, Color]
class Transform(Animation): class Transform(Animation):

View file

@ -1,13 +1,12 @@
from __future__ import annotations from __future__ import annotations
import operator as op
from typing import Callable
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Callable
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
@ -47,10 +46,7 @@ class MaintainPositionRelativeTo(Animation):
**kwargs **kwargs
): ):
self.tracked_mobject = tracked_mobject self.tracked_mobject = tracked_mobject
self.diff = op.sub( self.diff = mobject.get_center() - tracked_mobject.get_center()
mobject.get_center(),
tracked_mobject.get_center(),
)
super().__init__(mobject, **kwargs) super().__init__(mobject, **kwargs)
def interpolate_mobject(self, alpha: float) -> None: def interpolate_mobject(self, alpha: float) -> None:

View file

@ -1,19 +1,23 @@
from __future__ import annotations from __future__ import annotations
import moderngl import itertools as it
from colour import Color
import OpenGL.GL as gl
import math import math
import itertools as it import moderngl
import numpy as np import numpy as np
from scipy.spatial.transform import Rotation import OpenGL.GL as gl
from PIL import Image from PIL import Image
from scipy.spatial.transform import Rotation
from manimlib.constants import * from manimlib.constants import BLACK
from manimlib.constants import DEGREES, RADIANS
from manimlib.constants import DEFAULT_FRAME_RATE
from manimlib.constants import DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH
from manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH
from manimlib.constants import DOWN, LEFT, ORIGIN, OUT, RIGHT, UP
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
from manimlib.mobject.mobject import Point from manimlib.mobject.mobject import Point
from manimlib.utils.color import color_to_rgba
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.simple_functions import fdiv from manimlib.utils.simple_functions import fdiv
from manimlib.utils.space_ops import normalize from manimlib.utils.space_ops import normalize
@ -189,10 +193,9 @@ class Camera(object):
def __init__(self, ctx: moderngl.Context | None = None, **kwargs): def __init__(self, ctx: moderngl.Context | None = None, **kwargs):
digest_config(self, kwargs, locals()) digest_config(self, kwargs, locals())
self.rgb_max_val: float = np.iinfo(self.pixel_array_dtype).max self.rgb_max_val: float = np.iinfo(self.pixel_array_dtype).max
self.background_rgba: list[float] = [ self.background_rgba: list[float] = list(color_to_rgba(
*Color(self.background_color).get_rgb(), self.background_color, self.background_opacity
self.background_opacity ))
]
self.init_frame() self.init_frame()
self.init_context(ctx) self.init_context(ctx)
self.init_shaders() self.init_shaders()

View file

@ -1,16 +1,16 @@
import argparse import argparse
import colour import colour
import inspect from contextlib import contextmanager
import importlib import importlib
import inspect
import os import os
from screeninfo import get_monitors
import sys import sys
import yaml import yaml
from contextlib import contextmanager
from screeninfo import get_monitors
from manimlib.logger import log
from manimlib.utils.config_ops import merge_dicts_recursively from manimlib.utils.config_ops import merge_dicts_recursively
from manimlib.utils.init_config import init_customization from manimlib.utils.init_config import init_customization
from manimlib.logger import log
__config_file__ = "custom_config.yml" __config_file__ = "custom_config.yml"
@ -123,10 +123,13 @@ def parse_cli():
"the rendering at the second value", "the rendering at the second value",
) )
parser.add_argument( parser.add_argument(
"-e", "--embed", metavar="LINENO", "-e", "--embed",
help="Takes a line number as an argument, and results" nargs="?",
"in the scene being called as if the line `self.embed()`" const="",
"was inserted into the scene code at that line number." help="Creates a new file where the line `self.embed` is inserted "
"into the Scenes construct method. "
"If a string is passed in, the line will be inserted below the "
"last line of code including that string."
) )
parser.add_argument( parser.add_argument(
"-r", "--resolution", "-r", "--resolution",
@ -185,22 +188,70 @@ def get_module(file_name):
return module return module
def get_indent(line: str):
return len(line) - len(line.lstrip())
@contextmanager @contextmanager
def insert_embed_line(file_name, lineno): def insert_embed_line(file_name: str, scene_name: str, line_marker: str):
"""
This is hacky, but convenient. When user includes the argument "-e", it will try
to recreate a file that inserts the line `self.embed()` into the end of the scene's
construct method. If there is an argument passed in, it will insert the line after
the last line in the sourcefile which includes that string.
"""
with open(file_name, 'r') as fp: with open(file_name, 'r') as fp:
lines = fp.readlines() lines = fp.readlines()
line = lines[lineno - 1]
n_spaces = len(line) - len(line.lstrip())
lines.insert(lineno, " " * n_spaces + "self.embed()\n")
alt_file = file_name.replace(".py", "_inserted_embed.py")
with open(alt_file, 'w') as fp:
fp.writelines(lines)
try: try:
yield alt_file scene_line_number = next(
i for i, line in enumerate(lines)
if line.startswith(f"class {scene_name}")
)
except StopIteration:
log.error(f"No scene {scene_name}")
prev_line_num = None
n_spaces = None
if len(line_marker) == 0:
# Find the end of the construct method
in_construct = False
for index in range(scene_line_number, len(lines) - 1):
line = lines[index]
if line.lstrip().startswith("def construct"):
in_construct = True
n_spaces = get_indent(line) + 4
elif in_construct:
if len(line.strip()) > 0 and get_indent(line) < n_spaces:
prev_line_num = index - 2
break
elif line_marker.isdigit():
# Treat the argument as a line number
prev_line_num = int(line_marker) - 1
elif len(line_marker) > 0:
# Treat the argument as a string
try:
prev_line_num = next(
i
for i in range(len(lines) - 1, scene_line_number, -1)
if line_marker in lines[i]
)
except StopIteration:
log.error(f"No lines matching {line_marker}")
sys.exit(2)
# Insert and write new file
if n_spaces is None:
n_spaces = get_indent(lines[prev_line_num])
new_lines = list(lines)
new_lines.insert(prev_line_num + 1, " " * n_spaces + "self.embed()\n")
with open(file_name, 'w') as fp:
fp.writelines(new_lines)
try:
yield file_name
finally: finally:
os.remove(alt_file) with open(file_name, 'w') as fp:
fp.writelines(lines)
def get_custom_config(): def get_custom_config():
@ -296,10 +347,10 @@ def get_configuration(args):
"quiet": args.quiet, "quiet": args.quiet,
} }
if args.embed is None:
module = get_module(args.file) module = get_module(args.file)
else:
with insert_embed_line(args.file, int(args.embed)) as alt_file: if args.embed is not None:
with insert_embed_line(args.file, args.scene_names[0], args.embed) as alt_file:
module = get_module(alt_file) module = get_module(alt_file)
config = { config = {

View file

@ -1,5 +1,6 @@
import numpy as np import numpy as np
# Sizes relevant to default camera frame # Sizes relevant to default camera frame
ASPECT_RATIO = 16.0 / 9.0 ASPECT_RATIO = 16.0 / 9.0
FRAME_HEIGHT = 8.0 FRAME_HEIGHT = 8.0
@ -74,6 +75,7 @@ DEFAULT_STROKE_WIDTH = 4
# For keyboard interactions # For keyboard interactions
CTRL_SYMBOL = 65508 CTRL_SYMBOL = 65508
SHIFT_SYMBOL = 65505 SHIFT_SYMBOL = 65505
COMMAND_SYMBOL = 65517
DELETE_SYMBOL = 65288 DELETE_SYMBOL = 65288
ARROW_SYMBOLS = list(range(65361, 65365)) ARROW_SYMBOLS = list(range(65361, 65365))

View file

@ -2,8 +2,8 @@ from __future__ import annotations
import numpy as np import numpy as np
from manimlib.event_handler.event_type import EventType
from manimlib.event_handler.event_listner import EventListner from manimlib.event_handler.event_listner import EventListner
from manimlib.event_handler.event_type import EventType
class EventDispatcher(object): class EventDispatcher(object):

View file

@ -1,10 +1,13 @@
from __future__ import annotations from __future__ import annotations
from typing import Callable, TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from manimlib.mobject.mobject import Mobject from typing import Callable
from manimlib.event_handler.event_type import EventType from manimlib.event_handler.event_type import EventType
from manimlib.mobject.mobject import Mobject
class EventListner(object): class EventListner(object):
def __init__( def __init__(

View file

@ -1,13 +1,14 @@
import copy
import inspect import inspect
import sys import sys
import copy
from manimlib.scene.scene import Scene
from manimlib.config import get_custom_config from manimlib.config import get_custom_config
from manimlib.logger import log from manimlib.logger import log
from manimlib.scene.interactive_scene import InteractiveScene
from manimlib.scene.scene import Scene
class BlankScene(Scene): class BlankScene(InteractiveScene):
def construct(self): def construct(self):
exec(get_custom_config()["universal_import_line"]) exec(get_custom_config()["universal_import_line"])
self.embed() self.embed()

View file

@ -1,4 +1,5 @@
import logging import logging
from rich.logging import RichHandler from rich.logging import RichHandler
__all__ = ["log"] __all__ = ["log"]

View file

@ -1,19 +1,18 @@
from __future__ import annotations from __future__ import annotations
from typing import Callable
import numpy as np import numpy as np
from manimlib.constants import BLUE_D from manimlib.constants import BLUE_B, BLUE_D, BLUE_E, GREY_BROWN, WHITE
from manimlib.constants import BLUE_B
from manimlib.constants import BLUE_E
from manimlib.constants import GREY_BROWN
from manimlib.constants import WHITE
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.rate_functions import smooth from manimlib.utils.rate_functions import smooth
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable
class AnimatedBoundary(VGroup): class AnimatedBoundary(VGroup):
CONFIG = { CONFIG = {

View file

@ -1,16 +1,20 @@
from __future__ import annotations from __future__ import annotations
from abc import ABC, abstractmethod
import numbers import numbers
from abc import abstractmethod
from typing import Type, TypeVar, Union, Callable, Iterable, Sequence
import numpy as np import numpy as np
from manimlib.constants import * from manimlib.constants import BLACK, BLUE, BLUE_D, GREEN, GREY_A, WHITE
from manimlib.constants import DEGREES, PI
from manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, OUT, RIGHT, UP
from manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH
from manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS
from manimlib.constants import MED_SMALL_BUFF, SMALL_BUFF
from manimlib.mobject.functions import ParametricCurve from manimlib.mobject.functions import ParametricCurve
from manimlib.mobject.geometry import Arrow from manimlib.mobject.geometry import Arrow
from manimlib.mobject.geometry import Line
from manimlib.mobject.geometry import DashedLine from manimlib.mobject.geometry import DashedLine
from manimlib.mobject.geometry import Line
from manimlib.mobject.geometry import Rectangle from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.number_line import NumberLine from manimlib.mobject.number_line import NumberLine
from manimlib.mobject.svg.tex_mobject import Tex from manimlib.mobject.svg.tex_mobject import Tex
@ -25,16 +29,19 @@ from manimlib.utils.space_ops import rotate_vector
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
import colour from colour import Color
from typing import Callable, Iterable, Sequence, Type, TypeVar, Union
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
T = TypeVar("T", bound=Mobject) T = TypeVar("T", bound=Mobject)
ManimColor = Union[str, colour.Color, Sequence[float]] ManimColor = Union[str, Color]
EPSILON = 1e-8 EPSILON = 1e-8
class CoordinateSystem(): class CoordinateSystem(ABC):
""" """
Abstract class for Axes and NumberPlane Abstract class for Axes and NumberPlane
""" """

View file

@ -1,4 +1,5 @@
from manimlib.constants import * from manimlib.constants import BLACK, GREY_E
from manimlib.constants import FRAME_HEIGHT
from manimlib.mobject.geometry import Rectangle from manimlib.mobject.geometry import Rectangle
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config

View file

@ -1,13 +1,18 @@
from __future__ import annotations from __future__ import annotations
from typing import Callable, Sequence
from isosurfaces import plot_isoline from isosurfaces import plot_isoline
import numpy as np
from manimlib.constants import * from manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS
from manimlib.constants import YELLOW
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 typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable, Sequence
class ParametricCurve(VMobject): class ParametricCurve(VMobject):
CONFIG = { CONFIG = {

View file

@ -2,23 +2,24 @@ from __future__ import annotations
import math import math
import numbers import numbers
from typing import Sequence, Union
import colour
import numpy as np import numpy as np
from manimlib.constants import * from manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, OUT, RIGHT, UL, UP, UR
from manimlib.constants import GREY_A, RED, WHITE
from manimlib.constants import MED_SMALL_BUFF
from manimlib.constants import PI, TAU
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
from manimlib.mobject.types.vectorized_mobject import DashedVMobject
from manimlib.mobject.types.vectorized_mobject import VGroup 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.mobject.types.vectorized_mobject import DashedVMobject
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.iterables import adjacent_n_tuples from manimlib.utils.iterables import adjacent_n_tuples
from manimlib.utils.iterables import adjacent_pairs from manimlib.utils.iterables import adjacent_pairs
from manimlib.utils.simple_functions import fdiv
from manimlib.utils.simple_functions import clip from manimlib.utils.simple_functions import clip
from manimlib.utils.space_ops import angle_of_vector from manimlib.utils.simple_functions import fdiv
from manimlib.utils.space_ops import angle_between_vectors from manimlib.utils.space_ops import angle_between_vectors
from manimlib.utils.space_ops import angle_of_vector
from manimlib.utils.space_ops import compass_directions from manimlib.utils.space_ops import compass_directions
from manimlib.utils.space_ops import find_intersection from manimlib.utils.space_ops import find_intersection
from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import get_norm
@ -26,7 +27,13 @@ from manimlib.utils.space_ops import normalize
from manimlib.utils.space_ops import rotate_vector from manimlib.utils.space_ops import rotate_vector
from manimlib.utils.space_ops import rotation_matrix_transpose from manimlib.utils.space_ops import rotation_matrix_transpose
ManimColor = Union[str, colour.Color, Sequence[float]] from typing import TYPE_CHECKING
if TYPE_CHECKING:
from colour import Color
from typing import Union
ManimColor = Union[str, Color]
DEFAULT_DOT_RADIUS = 0.08 DEFAULT_DOT_RADIUS = 0.08
@ -716,8 +723,8 @@ class Arrow(Line):
def set_stroke( def set_stroke(
self, self,
color: ManimColor | None = None, color: ManimColor | Iterable[ManimColor] | None = None,
width: float | None = None, width: float | Iterable[float] | None = None,
*args, **kwargs *args, **kwargs
): ):
super().set_stroke(color=color, width=width, *args, **kwargs) super().set_stroke(color=color, width=width, *args, **kwargs)

View file

@ -1,22 +1,32 @@
from __future__ import annotations from __future__ import annotations
from typing import Callable
import numpy as np import numpy as np
from pyglet.window import key as PygletWindowKeys from pyglet.window import key as PygletWindowKeys
from manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH from manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH
from manimlib.constants import LEFT, RIGHT, UP, DOWN, ORIGIN from manimlib.constants import DOWN, LEFT, ORIGIN, RIGHT, UP
from manimlib.constants import SMALL_BUFF, MED_SMALL_BUFF, MED_LARGE_BUFF from manimlib.constants import MED_LARGE_BUFF, MED_SMALL_BUFF, SMALL_BUFF
from manimlib.constants import BLACK, GREY_A, GREY_C, RED, GREEN, BLUE, WHITE from manimlib.constants import BLACK, BLUE, GREEN, GREY_A, GREY_C, RED, WHITE
from manimlib.mobject.mobject import Mobject, Group from manimlib.mobject.mobject import Group
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.mobject import Mobject
from manimlib.mobject.geometry import Dot, Line, Square, Rectangle, RoundedRectangle, Circle from manimlib.mobject.geometry import Circle
from manimlib.mobject.geometry import Dot
from manimlib.mobject.geometry import Line
from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.geometry import RoundedRectangle
from manimlib.mobject.geometry import Square
from manimlib.mobject.svg.text_mobject import Text from manimlib.mobject.svg.text_mobject import Text
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.value_tracker import ValueTracker from manimlib.mobject.value_tracker import ValueTracker
from manimlib.utils.color import rgb_to_hex
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.space_ops import get_norm, get_closest_point_on_line from manimlib.utils.space_ops import get_closest_point_on_line
from manimlib.utils.color import rgb_to_color, color_to_rgba, rgb_to_hex from manimlib.utils.space_ops import get_norm
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable
# Interactive Mobjects # Interactive Mobjects
@ -336,7 +346,7 @@ class ColorSliders(Group):
g = self.g_slider.get_value() / 255 g = self.g_slider.get_value() / 255
b = self.b_slider.get_value() / 255 b = self.b_slider.get_value() / 255
alpha = self.a_slider.get_value() alpha = self.a_slider.get_value()
return color_to_rgba(rgb_to_color((r, g, b)), alpha=alpha) return np.array((r, g, b, alpha))
def get_picked_color(self) -> str: def get_picked_color(self) -> str:
rgba = self.get_value() rgba = self.get_value()

View file

@ -1,12 +1,12 @@
from __future__ import annotations from __future__ import annotations
import itertools as it import itertools as it
from typing import Union, Sequence
import numpy as np import numpy as np
import numpy.typing as npt
from manimlib.constants import * from manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFFER
from manimlib.constants import DOWN, LEFT, RIGHT, UP
from manimlib.constants import WHITE
from manimlib.mobject.numbers import DecimalNumber from manimlib.mobject.numbers import DecimalNumber
from manimlib.mobject.numbers import Integer from manimlib.mobject.numbers import Integer
from manimlib.mobject.shape_matchers import BackgroundRectangle from manimlib.mobject.shape_matchers import BackgroundRectangle
@ -18,9 +18,14 @@ from manimlib.mobject.types.vectorized_mobject import VMobject
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
import colour from colour import Color
from typing import Union
import numpy.typing as npt
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
ManimColor = Union[str, colour.Color, Sequence[float]]
ManimColor = Union[str, Color]
VECTOR_LABEL_SCALE_FACTOR = 0.8 VECTOR_LABEL_SCALE_FACTOR = 0.8

View file

@ -1,50 +1,61 @@
from __future__ import annotations from __future__ import annotations
import copy import copy
import sys
import random
import itertools as it
from functools import wraps from functools import wraps
from typing import Iterable, Callable, Union, Sequence import itertools as it
import pickle
import os import os
import pickle
import random
import sys
import colour
import moderngl import moderngl
import numbers
import numpy as np import numpy as np
import numpy.typing as npt
from manimlib.constants import * from manimlib.constants import DEFAULT_MOBJECT_TO_EDGE_BUFFER
from manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFFER
from manimlib.constants import DOWN, IN, LEFT, ORIGIN, OUT, RIGHT, UP
from manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS
from manimlib.constants import MED_SMALL_BUFF
from manimlib.constants import TAU
from manimlib.constants import WHITE
from manimlib.event_handler import EVENT_DISPATCHER
from manimlib.event_handler.event_listner import EventListner
from manimlib.event_handler.event_type import EventType
from manimlib.logger import log
from manimlib.shader_wrapper import get_colormap_code
from manimlib.shader_wrapper import ShaderWrapper
from manimlib.utils.color import color_gradient from manimlib.utils.color import color_gradient
from manimlib.utils.color import color_to_rgb
from manimlib.utils.color import get_colormap_list from manimlib.utils.color import get_colormap_list
from manimlib.utils.color import rgb_to_hex from manimlib.utils.color import rgb_to_hex
from manimlib.utils.color import color_to_rgb
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.iterables import batch_by_property from manimlib.utils.iterables import batch_by_property
from manimlib.utils.iterables import list_update from manimlib.utils.iterables import list_update
from manimlib.utils.iterables import listify
from manimlib.utils.iterables import resize_array from manimlib.utils.iterables import resize_array
from manimlib.utils.iterables import resize_preserving_order from manimlib.utils.iterables import resize_preserving_order
from manimlib.utils.iterables import resize_with_interpolation from manimlib.utils.iterables import resize_with_interpolation
from manimlib.utils.iterables import listify
from manimlib.utils.bezier import interpolate
from manimlib.utils.bezier import integer_interpolate from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.bezier import interpolate
from manimlib.utils.paths import straight_path from manimlib.utils.paths import straight_path
from manimlib.utils.simple_functions import get_parameters from manimlib.utils.simple_functions import get_parameters
from manimlib.utils.space_ops import angle_of_vector from manimlib.utils.space_ops import angle_of_vector
from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import get_norm
from manimlib.utils.space_ops import rotation_matrix_transpose from manimlib.utils.space_ops import rotation_matrix_transpose
from manimlib.shader_wrapper import ShaderWrapper
from manimlib.shader_wrapper import get_colormap_code
from manimlib.event_handler import EVENT_DISPATCHER
from manimlib.event_handler.event_listner import EventListner
from manimlib.event_handler.event_type import EventType
from manimlib.logger import log
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from colour import Color
from typing import Callable, Iterable, Sequence, Union
import numpy.typing as npt
TimeBasedUpdater = Callable[["Mobject", float], None] TimeBasedUpdater = Callable[["Mobject", float], None]
NonTimeUpdater = Callable[["Mobject"], None] NonTimeUpdater = Callable[["Mobject"], None]
Updater = Union[TimeBasedUpdater, NonTimeUpdater] Updater = Union[TimeBasedUpdater, NonTimeUpdater]
ManimColor = Union[str, colour.Color, Sequence[float]] ManimColor = Union[str, Color]
class Mobject(object): class Mobject(object):
@ -84,7 +95,8 @@ class Mobject(object):
self.locked_data_keys: set[str] = set() self.locked_data_keys: set[str] = set()
self.needs_new_bounding_box: bool = True self.needs_new_bounding_box: bool = True
self._is_animating: bool = False self._is_animating: bool = False
self._is_movable: bool = False self.saved_state = None
self.target = None
self.init_data() self.init_data()
self.init_uniforms() self.init_uniforms()
@ -136,8 +148,10 @@ class Mobject(object):
return self return self
def set_uniforms(self, uniforms: dict): def set_uniforms(self, uniforms: dict):
for key in uniforms: for key, value in uniforms.items():
self.uniforms[key] = uniforms[key] # Copy? if isinstance(value, np.ndarray):
value = value.copy()
self.uniforms[key] = value
return self return self
@property @property
@ -460,66 +474,94 @@ class Mobject(object):
self.assemble_family() self.assemble_family()
return self return self
# Creating new Mobjects from this one # Copying and serialization
def replicate(self, n: int) -> Group: def stash_mobject_pointers(func):
return self.get_group_class()( @wraps(func)
*(self.copy() for x in range(n)) def wrapper(self, *args, **kwargs):
) uncopied_attrs = ["parents", "target", "saved_state"]
stash = dict()
for attr in uncopied_attrs:
if hasattr(self, attr):
value = getattr(self, attr)
stash[attr] = value
null_value = [] if isinstance(value, list) else None
setattr(self, attr, null_value)
result = func(self, *args, **kwargs)
self.__dict__.update(stash)
return result
return wrapper
def get_grid(self, n_rows: int, n_cols: int, height: float | None = None, **kwargs): @stash_mobject_pointers
""" def serialize(self):
Returns a new mobject containing multiple copies of this one return pickle.dumps(self)
arranged in a grid
"""
grid = self.replicate(n_rows * n_cols)
grid.arrange_in_grid(n_rows, n_cols, **kwargs)
if height is not None:
grid.set_height(height)
return grid
# Copying def deserialize(self, data: bytes):
self.become(pickle.loads(data))
return self
def copy(self): def deepcopy(self):
self.parents = []
try: try:
# Often faster than deepcopy
return pickle.loads(pickle.dumps(self)) return pickle.loads(pickle.dumps(self))
except AttributeError: except AttributeError:
return copy.deepcopy(self) return copy.deepcopy(self)
def deepcopy(self): @stash_mobject_pointers
# This used to be different from copy, so is now just here for backward compatibility def copy(self, deep: bool = False):
return self.copy() if deep:
return self.deepcopy()
result = copy.copy(self)
# The line above is only a shallow copy, so the internal
# data which are numpyu arrays or other mobjects still
# need to be further copied.
result.data = {
key: np.array(value)
for key, value in self.data.items()
}
result.uniforms = {
key: np.array(value)
for key, value in self.uniforms.items()
}
result.submobjects = []
result.add(*(sm.copy() for sm in self.submobjects))
result.match_updaters(self)
family = self.get_family()
for attr, value in list(self.__dict__.items()):
if isinstance(value, Mobject) and value is not self:
if value in family:
setattr(result, attr, result.family[self.family.index(value)])
else:
setattr(result, attr, value.copy())
if isinstance(value, np.ndarray):
setattr(result, attr, value.copy())
if isinstance(value, ShaderWrapper):
setattr(result, attr, value.copy())
return result
def generate_target(self, use_deepcopy: bool = False): def generate_target(self, use_deepcopy: bool = False):
# TODO, remove now pointless use_deepcopy arg self.target = self.copy(deep=use_deepcopy)
self.target = None # Prevent exponential explosion self.target.saved_state = self.saved_state
self.target = self.copy()
return self.target return self.target
def save_state(self, use_deepcopy: bool = False): def save_state(self, use_deepcopy: bool = False):
# TODO, remove now pointless use_deepcopy arg self.saved_state = self.copy(deep=use_deepcopy)
if hasattr(self, "saved_state"): self.saved_state.target = self.target
# Prevent exponential growth of data
self.saved_state = None
self.saved_state = self.copy()
return self return self
def restore(self): def restore(self):
if not hasattr(self, "saved_state") or self.save_state is None: if not hasattr(self, "saved_state") or self.saved_state is None:
raise Exception("Trying to restore without having saved") raise Exception("Trying to restore without having saved")
self.become(self.saved_state) self.become(self.saved_state)
return self return self
def save_to_file(self, file_path): def save_to_file(self, file_path: str, supress_overwrite_warning: bool = False):
if not file_path.endswith(".mob"):
file_path += ".mob"
if os.path.exists(file_path):
cont = input(f"{file_path} already exists. Overwrite (y/n)? ")
if cont != "y":
return
with open(file_path, "wb") as fp: with open(file_path, "wb") as fp:
pickle.dump(self, fp) fp.write(self.serialize())
log.info(f"Saved mobject to {file_path}") log.info(f"Saved mobject to {file_path}")
return self return self
@ -532,6 +574,39 @@ class Mobject(object):
mobject = pickle.load(fp) mobject = pickle.load(fp)
return mobject return mobject
def become(self, mobject: Mobject):
"""
Edit all data and submobjects to be idential
to another mobject
"""
self.align_family(mobject)
for sm1, sm2 in zip(self.get_family(), mobject.get_family()):
sm1.set_data(sm2.data)
sm1.set_uniforms(sm2.uniforms)
sm1.shader_folder = sm2.shader_folder
sm1.texture_paths = sm2.texture_paths
sm1.depth_test = sm2.depth_test
sm1.render_primitive = sm2.render_primitive
self.refresh_bounding_box(recurse_down=True)
return self
# Creating new Mobjects from this one
def replicate(self, n: int) -> Group:
group_class = self.get_group_class()
return group_class(*(self.copy() for _ in range(n)))
def get_grid(self, n_rows: int, n_cols: int, height: float | None = None, **kwargs) -> Group:
"""
Returns a new mobject containing multiple copies of this one
arranged in a grid
"""
grid = self.replicate(n_rows * n_cols)
grid.arrange_in_grid(n_rows, n_cols, **kwargs)
if height is not None:
grid.set_height(height)
return grid
# Updating # Updating
def init_updaters(self): def init_updaters(self):
@ -634,21 +709,13 @@ class Mobject(object):
# Check if mark as static or not for camera # Check if mark as static or not for camera
def is_changing(self) -> bool: def is_changing(self) -> bool:
return self._is_animating or self.has_updaters or self._is_movable return self._is_animating or self.has_updaters
def set_animating_status(self, is_animating: bool, recurse: bool = True) -> None: def set_animating_status(self, is_animating: bool, recurse: bool = True) -> None:
for mob in self.get_family(recurse): for mob in self.get_family(recurse):
mob._is_animating = is_animating mob._is_animating = is_animating
return self return self
def make_movable(self, value: bool = True, recurse: bool = True) -> None:
for mob in self.get_family(recurse):
mob._is_movable = value
return self
def is_movable(self) -> bool:
return self._is_movable
# Transforming operations # Transforming operations
def shift(self, vector: np.ndarray): def shift(self, vector: np.ndarray):
@ -675,10 +742,10 @@ class Mobject(object):
Otherwise, if about_point is given a value, scaling is done with Otherwise, if about_point is given a value, scaling is done with
respect to that point. respect to that point.
""" """
if isinstance(scale_factor, Iterable): if isinstance(scale_factor, numbers.Number):
scale_factor = np.array(scale_factor).clip(min=min_scale_factor)
else:
scale_factor = max(scale_factor, min_scale_factor) scale_factor = max(scale_factor, min_scale_factor)
else:
scale_factor = np.array(scale_factor).clip(min=min_scale_factor)
self.apply_points_function( self.apply_points_function(
lambda points: scale_factor * points, lambda points: scale_factor * points,
about_point=about_point, about_point=about_point,
@ -1064,8 +1131,8 @@ class Mobject(object):
def set_rgba_array_by_color( def set_rgba_array_by_color(
self, self,
color: ManimColor | None = None, color: ManimColor | Iterable[ManimColor] | None = None,
opacity: float | None = None, opacity: float | Iterable[float] | None = None,
name: str = "rgbas", name: str = "rgbas",
recurse: bool = True recurse: bool = True
): ):
@ -1087,7 +1154,12 @@ class Mobject(object):
mob.data[name][:, 3] = resize_array(opacities, size) mob.data[name][:, 3] = resize_array(opacities, size)
return self return self
def set_color(self, color: ManimColor, opacity: float | None = None, recurse: bool = True): def set_color(
self,
color: ManimColor | Iterable[ManimColor] | None,
opacity: float | Iterable[float] | None = None,
recurse: bool = True
):
self.set_rgba_array_by_color(color, opacity, recurse=False) self.set_rgba_array_by_color(color, opacity, recurse=False)
# Recurse to submobjects differently from how set_rgba_array_by_color # Recurse to submobjects differently from how set_rgba_array_by_color
# in case they implement set_color differently # in case they implement set_color differently
@ -1096,7 +1168,11 @@ class Mobject(object):
submob.set_color(color, recurse=True) submob.set_color(color, recurse=True)
return self return self
def set_opacity(self, opacity: float, recurse: bool = True): def set_opacity(
self,
opacity: float | Iterable[float] | None,
recurse: bool = True
):
self.set_rgba_array_by_color(color=None, opacity=opacity, recurse=False) self.set_rgba_array_by_color(color=None, opacity=opacity, recurse=False)
if recurse: if recurse:
for submob in self.submobjects: for submob in self.submobjects:
@ -1203,7 +1279,7 @@ class Mobject(object):
bb = self.get_bounding_box() bb = self.get_bounding_box()
return np.array([ return np.array([
[bb[indices[-i + 1]][i] for i in range(3)] [bb[indices[-i + 1]][i] for i in range(3)]
for indices in it.product(*3 * [[0, 2]]) for indices in it.product([0, 2], repeat=3)
]) ])
def get_center(self) -> np.ndarray: def get_center(self) -> np.ndarray:
@ -1519,18 +1595,6 @@ class Mobject(object):
""" """
pass # To implement in subclass pass # To implement in subclass
def become(self, mobject: Mobject):
"""
Edit all data and submobjects to be idential
to another mobject
"""
self.align_family(mobject)
for sm1, sm2 in zip(self.get_family(), mobject.get_family()):
sm1.set_data(sm2.data)
sm1.set_uniforms(sm2.uniforms)
self.refresh_bounding_box(recurse_down=True)
return self
# Locking data # Locking data
def lock_data(self, keys: Iterable[str]): def lock_data(self, keys: Iterable[str]):

View file

@ -1,7 +1,6 @@
from __future__ import annotations from __future__ import annotations
import inspect import inspect
from typing import Callable
from manimlib.constants import DEGREES from manimlib.constants import DEGREES
from manimlib.constants import RIGHT from manimlib.constants import RIGHT
@ -11,7 +10,10 @@ from manimlib.utils.simple_functions import clip
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Callable
import numpy as np import numpy as np
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation

View file

@ -1,8 +1,10 @@
from __future__ import annotations from __future__ import annotations
from typing import Iterable, Sequence import numpy as np
from manimlib.constants import * from manimlib.constants import DOWN, LEFT, RIGHT, UP
from manimlib.constants import GREY_B
from manimlib.constants import MED_SMALL_BUFF
from manimlib.mobject.geometry import Line from manimlib.mobject.geometry import Line
from manimlib.mobject.numbers import DecimalNumber from manimlib.mobject.numbers import DecimalNumber
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
@ -12,6 +14,11 @@ from manimlib.utils.config_ops import digest_config
from manimlib.utils.config_ops import merge_dicts_recursively from manimlib.utils.config_ops import merge_dicts_recursively
from manimlib.utils.simple_functions import fdiv from manimlib.utils.simple_functions import fdiv
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Iterable, Sequence
class NumberLine(Line): class NumberLine(Line):
CONFIG = { CONFIG = {

View file

@ -1,12 +1,17 @@
from __future__ import annotations from __future__ import annotations
from typing import TypeVar, Type import numpy as np
from manimlib.constants import * from manimlib.constants import DOWN, LEFT, RIGHT, UP
from manimlib.mobject.svg.tex_mobject import SingleStringTex from manimlib.mobject.svg.tex_mobject import SingleStringTex
from manimlib.mobject.svg.text_mobject import Text from manimlib.mobject.svg.text_mobject import Text
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VMobject
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Type, TypeVar
T = TypeVar("T", bound=VMobject) T = TypeVar("T", bound=VMobject)

View file

@ -1,9 +1,8 @@
from __future__ import annotations from __future__ import annotations
from typing import Iterable, Union, Sequence from manimlib.constants import BLUE, BLUE_E, GREEN_E, GREY_B, GREY_D, MAROON_B, YELLOW
import colour from manimlib.constants import DOWN, LEFT, RIGHT, UP
from manimlib.constants import MED_LARGE_BUFF, MED_SMALL_BUFF, SMALL_BUFF
from manimlib.constants import *
from manimlib.mobject.geometry import Line from manimlib.mobject.geometry import Line
from manimlib.mobject.geometry import Rectangle from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
@ -14,7 +13,14 @@ from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.color import color_gradient from manimlib.utils.color import color_gradient
from manimlib.utils.iterables import listify from manimlib.utils.iterables import listify
ManimColor = Union[str, colour.Color, Sequence[float]] from typing import TYPE_CHECKING
if TYPE_CHECKING:
from colour import Color
from typing import Iterable, Union
ManimColor = Union[str, Color]
EPSILON = 0.0001 EPSILON = 0.0001

View file

@ -1,20 +1,25 @@
from __future__ import annotations from __future__ import annotations
from manimlib.constants import * from colour import Color
from manimlib.constants import BLACK, RED, YELLOW
from manimlib.constants import DL, DOWN, DR, LEFT, RIGHT, UL, UR
from manimlib.constants import SMALL_BUFF
from manimlib.mobject.geometry import Line from manimlib.mobject.geometry import Line
from manimlib.mobject.geometry import Rectangle from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.types.vectorized_mobject import VGroup 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.color import Color
from manimlib.utils.customization import get_customization
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.customization import get_customization
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Union, Sequence from typing import Union
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
ManimColor = Union[str, Color, Sequence[float]]
ManimColor = Union[str, Color]
class SurroundingRectangle(Rectangle): class SurroundingRectangle(Rectangle):

View file

@ -2,27 +2,32 @@ from __future__ import annotations
import math import math
import copy import copy
from typing import Iterable
import numpy as np import numpy as np
from manimlib.constants import * from manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFFER, SMALL_BUFF
from manimlib.constants import DOWN, LEFT, ORIGIN, RIGHT, UP
from manimlib.constants import PI
from manimlib.animation.composition import AnimationGroup
from manimlib.animation.fading import FadeIn from manimlib.animation.fading import FadeIn
from manimlib.animation.growing import GrowFromCenter from manimlib.animation.growing import GrowFromCenter
from manimlib.animation.composition import AnimationGroup
from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.svg.tex_mobject import SingleStringTex from manimlib.mobject.svg.tex_mobject import SingleStringTex
from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.svg.tex_mobject import TexText from manimlib.mobject.svg.tex_mobject import TexText
from manimlib.mobject.svg.text_mobject import Text from manimlib.mobject.svg.text_mobject import Text
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.iterables import listify
from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import get_norm
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from manimlib.mobject.mobject import Mobject from typing import Iterable
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation
from manimlib.mobject.mobject import Mobject
class Brace(SingleStringTex): class Brace(SingleStringTex):
CONFIG = { CONFIG = {
@ -113,8 +118,8 @@ class BraceLabel(VMobject):
def __init__( def __init__(
self, self,
obj: list[VMobject] | Mobject, obj: VMobject | list[VMobject],
text: Iterable[str] | str, text: str | Iterable[str],
brace_direction: np.ndarray = DOWN, brace_direction: np.ndarray = DOWN,
**kwargs **kwargs
) -> None: ) -> None:
@ -124,11 +129,7 @@ class BraceLabel(VMobject):
obj = VMobject(*obj) obj = VMobject(*obj)
self.brace = Brace(obj, brace_direction, **kwargs) self.brace = Brace(obj, brace_direction, **kwargs)
if isinstance(text, Iterable): self.label = self.label_constructor(*listify(text), **kwargs)
self.label = self.label_constructor(*text, **kwargs)
else:
self.label = self.label_constructor(str(text))
if self.label_scale != 1:
self.label.scale(self.label_scale) self.label.scale(self.label_scale)
self.brace.put_at_tip(self.label, buff=self.label_buff) self.brace.put_at_tip(self.label, buff=self.label_buff)
@ -141,7 +142,7 @@ class BraceLabel(VMobject):
) -> AnimationGroup: ) -> AnimationGroup:
return AnimationGroup(brace_anim(self.brace), label_anim(self.label)) return AnimationGroup(brace_anim(self.brace), label_anim(self.label))
def shift_brace(self, obj: list[VMobject] | Mobject, **kwargs): def shift_brace(self, obj: VMobject | list[VMobject], **kwargs):
if isinstance(obj, list): if isinstance(obj, list):
obj = VMobject(*obj) obj = VMobject(*obj)
self.brace = Brace(obj, self.brace_direction, **kwargs) self.brace = Brace(obj, self.brace_direction, **kwargs)
@ -158,7 +159,7 @@ class BraceLabel(VMobject):
self.submobjects[1] = self.label self.submobjects[1] = self.label
return self return self
def change_brace_label(self, obj: list[VMobject] | Mobject, *text: str): def change_brace_label(self, obj: VMobject | list[VMobject], *text: str):
self.shift_brace(obj) self.shift_brace(obj)
self.change_label(*text) self.change_label(*text)
return self return self

View file

@ -20,8 +20,8 @@ from manimlib.utils.config_ops import digest_config
from manimlib.utils.rate_functions import linear from manimlib.utils.rate_functions import linear
from manimlib.utils.space_ops import angle_of_vector from manimlib.utils.space_ops import angle_of_vector
from manimlib.utils.space_ops import complex_to_R3 from manimlib.utils.space_ops import complex_to_R3
from manimlib.utils.space_ops import rotate_vector
from manimlib.utils.space_ops import midpoint from manimlib.utils.space_ops import midpoint
from manimlib.utils.space_ops import rotate_vector
class Checkmark(TexText): class Checkmark(TexText):

View file

@ -1,27 +1,24 @@
from __future__ import annotations from __future__ import annotations
import os
import hashlib import hashlib
import itertools as it import os
from typing import Callable
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
import svgelements as se
import numpy as np import numpy as np
import svgelements as se
from manimlib.constants import RIGHT from manimlib.constants import RIGHT
from manimlib.mobject.geometry import Line from manimlib.logger import log
from manimlib.mobject.geometry import Circle from manimlib.mobject.geometry import Circle
from manimlib.mobject.geometry import Line
from manimlib.mobject.geometry import Polygon 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 VMobject 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 from manimlib.utils.directories import get_mobject_data_dir
from manimlib.utils.images import get_full_vector_image_path from manimlib.utils.images import get_full_vector_image_path
from manimlib.utils.iterables import hash_obj from manimlib.utils.iterables import hash_obj
from manimlib.logger import log
SVG_HASH_TO_MOB_MAP: dict[int, VMobject] = {} SVG_HASH_TO_MOB_MAP: dict[int, VMobject] = {}
@ -199,9 +196,9 @@ class SVGMobject(VMobject):
) -> VMobject: ) -> VMobject:
mob.set_style( mob.set_style(
stroke_width=shape.stroke_width, stroke_width=shape.stroke_width,
stroke_color=shape.stroke.hex, stroke_color=shape.stroke.hexrgb,
stroke_opacity=shape.stroke.opacity, stroke_opacity=shape.stroke.opacity,
fill_color=shape.fill.hex, fill_color=shape.fill.hexrgb,
fill_opacity=shape.fill.opacity fill_opacity=shape.fill.opacity
) )
return mob return mob

View file

@ -1,21 +1,28 @@
from __future__ import annotations from __future__ import annotations
from typing import Iterable, Sequence, Union
from functools import reduce from functools import reduce
import operator as op import operator as op
import colour
import re import re
from manimlib.constants import * from manimlib.constants import BLACK, WHITE
from manimlib.constants import DOWN, LEFT, RIGHT, UP
from manimlib.constants import FRAME_WIDTH
from manimlib.constants import MED_LARGE_BUFF, MED_SMALL_BUFF, SMALL_BUFF
from manimlib.mobject.geometry import Line from manimlib.mobject.geometry import Line
from manimlib.mobject.svg.svg_mobject import SVGMobject from manimlib.mobject.svg.svg_mobject import SVGMobject
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.tex_file_writing import tex_to_svg_file
from manimlib.utils.tex_file_writing import get_tex_config
from manimlib.utils.tex_file_writing import display_during_execution from manimlib.utils.tex_file_writing import display_during_execution
from manimlib.utils.tex_file_writing import get_tex_config
from manimlib.utils.tex_file_writing import tex_to_svg_file
ManimColor = Union[str, colour.Color, Sequence[float]] from typing import TYPE_CHECKING
if TYPE_CHECKING:
from colour import Color
from typing import Iterable, Union
ManimColor = Union[str, Color]
SCALE_FACTOR_PER_FONT_POINT = 0.001 SCALE_FACTOR_PER_FONT_POINT = 0.001

View file

@ -2,19 +2,23 @@ from __future__ import annotations
import math import math
from manimlib.constants import * import numpy as np
from manimlib.mobject.types.surface import Surface
from manimlib.constants import BLUE, BLUE_D, BLUE_E
from manimlib.constants import IN, ORIGIN, OUT, RIGHT
from manimlib.constants import PI, TAU
from manimlib.mobject.types.surface import SGroup from manimlib.mobject.types.surface import SGroup
from manimlib.mobject.types.surface import Surface
from manimlib.mobject.types.vectorized_mobject import VGroup 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.mobject.geometry import Square
from manimlib.mobject.geometry import Polygon from manimlib.mobject.geometry import Polygon
from manimlib.mobject.geometry import Square
from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import interpolate
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.iterables import adjacent_pairs from manimlib.utils.iterables import adjacent_pairs
from manimlib.utils.space_ops import compass_directions
from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import get_norm
from manimlib.utils.space_ops import z_to_vector from manimlib.utils.space_ops import z_to_vector
from manimlib.utils.space_ops import compass_directions
class SurfaceMesh(VGroup): class SurfaceMesh(VGroup):

View file

@ -1,15 +1,18 @@
from __future__ import annotations from __future__ import annotations
import numpy as np
import numpy.typing as npt
import moderngl import moderngl
import numpy as np
from manimlib.constants import GREY_C from manimlib.constants import GREY_C, YELLOW
from manimlib.constants import YELLOW
from manimlib.constants import ORIGIN from manimlib.constants import ORIGIN
from manimlib.mobject.types.point_cloud_mobject import PMobject from manimlib.mobject.types.point_cloud_mobject import PMobject
from manimlib.utils.iterables import resize_preserving_order from manimlib.utils.iterables import resize_preserving_order
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import numpy.typing as npt
DEFAULT_DOT_RADIUS = 0.05 DEFAULT_DOT_RADIUS = 0.05
DEFAULT_GLOW_DOT_RADIUS = 0.2 DEFAULT_GLOW_DOT_RADIUS = 0.2

View file

@ -3,7 +3,7 @@ from __future__ import annotations
import numpy as np import numpy as np
from PIL import Image from PIL import Image
from manimlib.constants import * from manimlib.constants import DL, DR, UL, UR
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
from manimlib.utils.bezier import inverse_interpolate from manimlib.utils.bezier import inverse_interpolate
from manimlib.utils.images import get_full_raster_image_path from manimlib.utils.images import get_full_raster_image_path

View file

@ -1,19 +1,22 @@
from __future__ import annotations from __future__ import annotations
from typing import Callable, Sequence, Union from manimlib.constants import BLACK
from manimlib.constants import ORIGIN
import colour
import numpy.typing as npt
from manimlib.constants import *
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
from manimlib.utils.color import color_gradient from manimlib.utils.color import color_gradient
from manimlib.utils.color import color_to_rgba from manimlib.utils.color import color_to_rgba
from manimlib.utils.iterables import resize_with_interpolation
from manimlib.utils.iterables import resize_array from manimlib.utils.iterables import resize_array
from manimlib.utils.iterables import resize_with_interpolation
from typing import TYPE_CHECKING
ManimColor = Union[str, colour.Color, Sequence[float]] if TYPE_CHECKING:
from colour import Color
from typing import Callable, Union
import numpy.typing as npt
ManimColor = Union[str, Color]
class PMobject(Mobject): class PMobject(Mobject):

View file

@ -1,12 +1,10 @@
from __future__ import annotations from __future__ import annotations
from typing import Iterable, Callable
import moderngl import moderngl
import numpy as np import numpy as np
import numpy.typing as npt
from manimlib.constants import * from manimlib.constants import GREY
from manimlib.constants import OUT
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
from manimlib.utils.bezier import integer_interpolate from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import interpolate
@ -17,6 +15,10 @@ from manimlib.utils.space_ops import normalize_along_axis
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Callable, Iterable
import numpy.typing as npt
from manimlib.camera.camera import Camera from manimlib.camera.camera import Camera

View file

@ -1,30 +1,36 @@
from __future__ import annotations from __future__ import annotations
import operator as op from functools import reduce
from functools import wraps
import itertools as it import itertools as it
from functools import reduce, wraps import operator as op
from typing import Iterable, Sequence, Callable, Union
import colour
import moderngl import moderngl
import numpy.typing as npt import numpy as np
from manimlib.constants import * from manimlib.constants import GREY_C
from manimlib.constants import GREY_E
from manimlib.constants import BLACK, WHITE
from manimlib.constants import DEFAULT_STROKE_WIDTH
from manimlib.constants import DEGREES
from manimlib.constants import JOINT_TYPE_MAP
from manimlib.constants import ORIGIN, OUT, UP
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
from manimlib.mobject.mobject import Point from manimlib.mobject.mobject import Point
from manimlib.utils.bezier import bezier from manimlib.utils.bezier import bezier
from manimlib.utils.bezier import get_smooth_quadratic_bezier_handle_points
from manimlib.utils.bezier import get_smooth_cubic_bezier_handle_points
from manimlib.utils.bezier import get_quadratic_approximation_of_cubic from manimlib.utils.bezier import get_quadratic_approximation_of_cubic
from manimlib.utils.bezier import get_smooth_cubic_bezier_handle_points
from manimlib.utils.bezier import get_smooth_quadratic_bezier_handle_points
from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import interpolate
from manimlib.utils.bezier import inverse_interpolate from manimlib.utils.bezier import inverse_interpolate
from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.bezier import partial_quadratic_bezier_points from manimlib.utils.bezier import partial_quadratic_bezier_points
from manimlib.utils.color import color_gradient
from manimlib.utils.color import rgb_to_hex from manimlib.utils.color import rgb_to_hex
from manimlib.utils.iterables import listify
from manimlib.utils.iterables import make_even from manimlib.utils.iterables import make_even
from manimlib.utils.iterables import resize_array from manimlib.utils.iterables import resize_array
from manimlib.utils.iterables import resize_with_interpolation from manimlib.utils.iterables import resize_with_interpolation
from manimlib.utils.iterables import listify
from manimlib.utils.space_ops import angle_between_vectors from manimlib.utils.space_ops import angle_between_vectors
from manimlib.utils.space_ops import cross2d from manimlib.utils.space_ops import cross2d
from manimlib.utils.space_ops import earclip_triangulation from manimlib.utils.space_ops import earclip_triangulation
@ -33,8 +39,15 @@ from manimlib.utils.space_ops import get_unit_normal
from manimlib.utils.space_ops import z_to_vector from manimlib.utils.space_ops import z_to_vector
from manimlib.shader_wrapper import ShaderWrapper from manimlib.shader_wrapper import ShaderWrapper
from typing import TYPE_CHECKING
ManimColor = Union[str, colour.Color, Sequence[float]] if TYPE_CHECKING:
from colour import Color
from typing import Callable, Iterable, Sequence, Union
import numpy.typing as npt
ManimColor = Union[str, Color]
class VMobject(Mobject): class VMobject(Mobject):
@ -130,8 +143,8 @@ class VMobject(Mobject):
def set_fill( def set_fill(
self, self,
color: ManimColor | None = None, color: ManimColor | Iterable[ManimColor] | None = None,
opacity: float | None = None, opacity: float | Iterable[float] | None = None,
recurse: bool = True recurse: bool = True
): ):
self.set_rgba_array_by_color(color, opacity, 'fill_rgba', recurse) self.set_rgba_array_by_color(color, opacity, 'fill_rgba', recurse)
@ -139,9 +152,9 @@ class VMobject(Mobject):
def set_stroke( def set_stroke(
self, self,
color: ManimColor | None = None, color: ManimColor | Iterable[ManimColor] | None = None,
width: float | npt.ArrayLike | None = None, width: float | Iterable[float] | None = None,
opacity: float | None = None, opacity: float | Iterable[float] | None = None,
background: bool | None = None, background: bool | None = None,
recurse: bool = True recurse: bool = True
): ):
@ -162,8 +175,8 @@ class VMobject(Mobject):
def set_backstroke( def set_backstroke(
self, self,
color: ManimColor = BLACK, color: ManimColor | Iterable[ManimColor] = BLACK,
width: float | npt.ArrayLike = 3, width: float | Iterable[float] = 3,
background: bool = True background: bool = True
): ):
self.set_stroke(color, width, background=background) self.set_stroke(color, width, background=background)
@ -177,13 +190,13 @@ class VMobject(Mobject):
def set_style( def set_style(
self, self,
fill_color: ManimColor | None = None, fill_color: ManimColor | Iterable[ManimColor] | None = None,
fill_opacity: float | None = None, fill_opacity: float | Iterable[float] | None = None,
fill_rgba: npt.ArrayLike | None = None, fill_rgba: npt.ArrayLike | None = None,
stroke_color: ManimColor | None = None, stroke_color: ManimColor | Iterable[ManimColor] | None = None,
stroke_opacity: float | None = None, stroke_opacity: float | Iterable[float] | None = None,
stroke_rgba: npt.ArrayLike | None = None, stroke_rgba: npt.ArrayLike | None = None,
stroke_width: float | npt.ArrayLike | None = None, stroke_width: float | Iterable[float] | None = None,
stroke_background: bool = True, stroke_background: bool = True,
reflectiveness: float | None = None, reflectiveness: float | None = None,
gloss: float | None = None, gloss: float | None = None,
@ -247,12 +260,21 @@ class VMobject(Mobject):
sm1.match_style(sm2) sm1.match_style(sm2)
return self return self
def set_color(self, color: ManimColor, recurse: bool = True): def set_color(
self.set_fill(color, recurse=recurse) self,
self.set_stroke(color, recurse=recurse) color: ManimColor | Iterable[ManimColor] | None,
opacity: float | Iterable[float] | None = None,
recurse: bool = True
):
self.set_fill(color, opacity=opacity, recurse=recurse)
self.set_stroke(color, opacity=opacity, recurse=recurse)
return self return self
def set_opacity(self, opacity: float, recurse: bool = True): def set_opacity(
self,
opacity: float | Iterable[float] | None,
recurse: bool = True
):
self.set_fill(opacity=opacity, recurse=recurse) self.set_fill(opacity=opacity, recurse=recurse)
self.set_stroke(opacity=opacity, recurse=recurse) self.set_stroke(opacity=opacity, recurse=recurse)
return self return self
@ -1174,3 +1196,24 @@ class DashedVMobject(VMobject):
# Family is already taken care of by get_subcurve # Family is already taken care of by get_subcurve
# implementation # implementation
self.match_style(vmobject, recurse=False) self.match_style(vmobject, recurse=False)
class VHighlight(VGroup):
def __init__(
self,
vmobject: VMobject,
n_layers: int = 3,
color_bounds: tuple[ManimColor] = (GREY_C, GREY_E),
max_stroke_width: float = 10.0,
):
outline = vmobject.replicate(n_layers)
outline.set_fill(opacity=0)
added_widths = np.linspace(0, max_stroke_width, n_layers + 1)[1:]
colors = color_gradient(color_bounds, n_layers)
for part, added_width, color in zip(reversed(outline), added_widths, colors):
for sm in part.family_members_with_points():
part.set_stroke(
width=sm.get_stroke_width() + added_width,
color=color,
)
super().__init__(*outline)

View file

@ -1,23 +1,21 @@
from __future__ import annotations from __future__ import annotations
import itertools as it import itertools as it
import random
from typing import Sequence, TypeVar, Callable, Iterable
import numpy as np import numpy as np
import numpy.typing as npt
from manimlib.constants import * from manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH
from manimlib.constants import WHITE
from manimlib.animation.composition import AnimationGroup from manimlib.animation.composition import AnimationGroup
from manimlib.animation.indication import VShowPassingFlash from manimlib.animation.indication import VShowPassingFlash
from manimlib.mobject.geometry import Arrow from manimlib.mobject.geometry import Arrow
from manimlib.mobject.types.vectorized_mobject import VGroup 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.bezier import inverse_interpolate
from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import interpolate
from manimlib.utils.bezier import inverse_interpolate
from manimlib.utils.color import get_colormap_list from manimlib.utils.color import get_colormap_list
from manimlib.utils.config_ops import merge_dicts_recursively
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.config_ops import merge_dicts_recursively
from manimlib.utils.rate_functions import linear from manimlib.utils.rate_functions import linear
from manimlib.utils.simple_functions import sigmoid from manimlib.utils.simple_functions import sigmoid
from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import get_norm
@ -25,8 +23,13 @@ from manimlib.utils.space_ops import get_norm
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from manimlib.mobject.mobject import Mobject from typing import Callable, Iterable, Sequence, TypeVar
import numpy.typing as npt
from manimlib.mobject.coordinate_systems import CoordinateSystem from manimlib.mobject.coordinate_systems import CoordinateSystem
from manimlib.mobject.mobject import Mobject
T = TypeVar("T") T = TypeVar("T")
@ -299,7 +302,7 @@ class AnimatedStreamLines(VGroup):
**self.line_anim_config, **self.line_anim_config,
) )
line.anim.begin() line.anim.begin()
line.time = -self.lag_range * random.random() line.time = -self.lag_range * np.random.random()
self.add(line.anim.mobject) self.add(line.anim.mobject)
self.add_updater(lambda m, dt: m.update(dt)) self.add_updater(lambda m, dt: m.update(dt))

View file

@ -1,4 +1,5 @@
from functools import reduce from functools import reduce
import random
from manimlib.constants import * from manimlib.constants import *
# from manimlib.for_3b1b_videos.pi_creature import PiCreature # from manimlib.for_3b1b_videos.pi_creature import PiCreature

View file

@ -1,41 +1,43 @@
import numpy as np
import itertools as it import itertools as it
import numpy as np
import pyperclip import pyperclip
import os
import platform
from manimlib.animation.fading import FadeIn from manimlib.animation.fading import FadeIn
from manimlib.constants import MANIM_COLORS, WHITE, YELLOW from manimlib.constants import ARROW_SYMBOLS, CTRL_SYMBOL, DELETE_SYMBOL, SHIFT_SYMBOL
from manimlib.constants import ORIGIN, UP, DOWN, LEFT, RIGHT, DL, UL, UR, DR from manimlib.constants import COMMAND_MODIFIER, SHIFT_MODIFIER
from manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, RIGHT, UL, UP, UR
from manimlib.constants import FRAME_WIDTH, SMALL_BUFF from manimlib.constants import FRAME_WIDTH, SMALL_BUFF
from manimlib.constants import SHIFT_SYMBOL, DELETE_SYMBOL, ARROW_SYMBOLS from manimlib.constants import MANIM_COLORS, WHITE, GREY_C
from manimlib.constants import SHIFT_MODIFIER, COMMAND_MODIFIER
from manimlib.mobject.mobject import Mobject
from manimlib.mobject.geometry import Rectangle from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.geometry import Square from manimlib.mobject.geometry import Square
from manimlib.mobject.mobject import Group from manimlib.mobject.mobject import Group
from manimlib.mobject.mobject import Mobject
from manimlib.mobject.numbers import DecimalNumber
from manimlib.mobject.svg.tex_mobject import Tex from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.svg.text_mobject import Text from manimlib.mobject.svg.text_mobject import Text
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.dot_cloud import DotCloud from manimlib.mobject.types.dot_cloud import DotCloud
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VHighlight
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.scene.scene import Scene from manimlib.scene.scene import Scene
from manimlib.utils.tex_file_writing import LatexError
from manimlib.utils.family_ops import extract_mobject_family_members from manimlib.utils.family_ops import extract_mobject_family_members
from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import get_norm
from manimlib.logger import log from manimlib.utils.tex_file_writing import LatexError
SELECT_KEY = 's' SELECT_KEY = 's'
GRAB_KEY = 'g' GRAB_KEY = 'g'
HORIZONTAL_GRAB_KEY = 'h' X_GRAB_KEY = 'h'
VERTICAL_GRAB_KEY = 'v' Y_GRAB_KEY = 'v'
GRAB_KEYS = [GRAB_KEY, X_GRAB_KEY, Y_GRAB_KEY]
RESIZE_KEY = 't' RESIZE_KEY = 't'
COLOR_KEY = 'c' COLOR_KEY = 'c'
CURSOR_LOCATION_KEY = 'l'
# Note, a lot of the functionality here is still buggy and very much a work in progress. # Note, a lot of the functionality here is still buggy and very much a work in progress.
class InteractiveScene(Scene): class InteractiveScene(Scene):
""" """
To select mobjects on screen, hold ctrl and move the mouse to highlight a region, To select mobjects on screen, hold ctrl and move the mouse to highlight a region,
@ -66,36 +68,102 @@ class InteractiveScene(Scene):
selection_rectangle_stroke_width = 1.0 selection_rectangle_stroke_width = 1.0
colors = MANIM_COLORS colors = MANIM_COLORS
selection_nudge_size = 0.05 selection_nudge_size = 0.05
cursor_location_config = dict(
font_size=14,
fill_color=GREY_C,
num_decimal_places=3,
)
def setup(self): def setup(self):
self.selection = Group() self.selection = Group()
self.selection_highlight = Group() self.selection_highlight = Group()
self.selection_rectangle = self.get_selection_rectangle() self.selection_rectangle = self.get_selection_rectangle()
self.color_palette = self.get_color_palette() self.color_palette = self.get_color_palette()
self.cursor_location_label = self.get_cursor_location_label()
self.unselectables = [ self.unselectables = [
self.selection, self.selection,
self.selection_highlight, self.selection_highlight,
self.selection_rectangle, self.selection_rectangle,
self.cursor_location_label,
self.camera.frame self.camera.frame
] ]
self.saved_selection_state = []
self.select_top_level_mobs = True self.select_top_level_mobs = True
self.regenerate_selection_search_set()
self.is_selecting = False self.is_selecting = False
self.is_grabbing = False
self.add(self.selection_highlight) self.add(self.selection_highlight)
def get_selection_rectangle(self):
rect = Rectangle(
stroke_color=self.selection_rectangle_stroke_color,
stroke_width=self.selection_rectangle_stroke_width,
)
rect.fix_in_frame()
rect.fixed_corner = ORIGIN
rect.add_updater(self.update_selection_rectangle)
return rect
def update_selection_rectangle(self, rect):
p1 = rect.fixed_corner
p2 = self.mouse_point.get_center()
rect.set_points_as_corners([
p1, [p2[0], p1[1], 0],
p2, [p1[0], p2[1], 0],
p1,
])
return rect
def get_color_palette(self):
palette = VGroup(*(
Square(fill_color=color, fill_opacity=1, side_length=1)
for color in self.colors
))
palette.set_stroke(width=0)
palette.arrange(RIGHT, buff=0.5)
palette.set_width(FRAME_WIDTH - 0.5)
palette.to_edge(DOWN, buff=SMALL_BUFF)
palette.fix_in_frame()
return palette
def get_cursor_location_label(self):
decimals = VGroup(*(
DecimalNumber(**self.cursor_location_config)
for n in range(3)
))
def update_coords(decimals):
for mob, coord in zip(decimals, self.mouse_point.get_location()):
mob.set_value(coord)
decimals.arrange(RIGHT, buff=decimals.get_height())
decimals.to_corner(DR, buff=SMALL_BUFF)
decimals.fix_in_frame()
return decimals
decimals.add_updater(update_coords)
return decimals
# Related to selection
def toggle_selection_mode(self): def toggle_selection_mode(self):
self.select_top_level_mobs = not self.select_top_level_mobs self.select_top_level_mobs = not self.select_top_level_mobs
self.refresh_selection_scope() self.refresh_selection_scope()
self.regenerate_selection_search_set()
def get_selection_search_set(self): def get_selection_search_set(self) -> list[Mobject]:
mobs = [m for m in self.mobjects if m not in self.unselectables] return self.selection_search_set
def regenerate_selection_search_set(self):
selectable = list(filter(
lambda m: m not in self.unselectables,
self.mobjects
))
if self.select_top_level_mobs: if self.select_top_level_mobs:
return mobs self.selection_search_set = selectable
else: else:
return [ self.selection_search_set = [
submob submob
for mob in mobs for mob in selectable
for submob in mob.family_members_with_points() for submob in mob.family_members_with_points()
] ]
@ -116,37 +184,7 @@ class InteractiveScene(Scene):
) )
self.refresh_selection_highlight() self.refresh_selection_highlight()
def get_selection_rectangle(self): def get_corner_dots(self, mobject: Mobject) -> Mobject:
rect = Rectangle(
stroke_color=self.selection_rectangle_stroke_color,
stroke_width=self.selection_rectangle_stroke_width,
)
rect.fix_in_frame()
rect.fixed_corner = ORIGIN
rect.add_updater(self.update_selection_rectangle)
return rect
def get_color_palette(self):
palette = VGroup(*(
Square(fill_color=color, fill_opacity=1, side_length=1)
for color in self.colors
))
palette.set_stroke(width=0)
palette.arrange(RIGHT, buff=0.5)
palette.set_width(FRAME_WIDTH - 0.5)
palette.to_edge(DOWN, buff=SMALL_BUFF)
palette.fix_in_frame()
return palette
def get_stroke_highlight(self, vmobject):
outline = vmobject.copy()
for sm, osm in zip(vmobject.get_family(), outline.get_family()):
osm.set_fill(opacity=0)
osm.set_stroke(YELLOW, width=sm.get_stroke_width() + 1.5)
outline.add_updater(lambda o: o.replace(vmobject))
return outline
def get_corner_dots(self, mobject):
dots = DotCloud(**self.corner_dot_config) dots = DotCloud(**self.corner_dot_config)
radius = self.corner_dot_config["radius"] radius = self.corner_dot_config["radius"]
if mobject.get_depth() < 1e-2: if mobject.get_depth() < 1e-2:
@ -159,9 +197,11 @@ class InteractiveScene(Scene):
])) ]))
return dots return dots
def get_highlight(self, mobject): def get_highlight(self, mobject: Mobject) -> Mobject:
if isinstance(mobject, VMobject) and mobject.has_points(): if isinstance(mobject, VMobject) and mobject.has_points() and not self.select_top_level_mobs:
return self.get_stroke_highlight(mobject) result = VHighlight(mobject)
result.add_updater(lambda m: m.replace(mobject))
return result
else: else:
return self.get_corner_dots(mobject) return self.get_corner_dots(mobject)
@ -171,40 +211,54 @@ class InteractiveScene(Scene):
for mob in self.selection for mob in self.selection
]) ])
def update_selection_rectangle(self, rect):
p1 = rect.fixed_corner
p2 = self.mouse_point.get_center()
rect.set_points_as_corners([
p1, [p2[0], p1[1], 0],
p2, [p1[0], p2[1], 0],
p1,
])
return rect
def add_to_selection(self, *mobjects): def add_to_selection(self, *mobjects):
mobs = list(filter(lambda m: m not in self.unselectables, mobjects)) mobs = list(filter(
self.selection.add(*mobjects) lambda m: m not in self.unselectables and m not in self.selection,
mobjects
))
if len(mobs) == 0:
return
self.selection.add(*mobs)
self.selection_highlight.add(*map(self.get_highlight, mobs)) self.selection_highlight.add(*map(self.get_highlight, mobs))
self.saved_selection_state = [(mob, mob.copy()) for mob in self.selection] for mob in mobs:
mob.set_animating_status(True)
self.refresh_static_mobjects()
def toggle_from_selection(self, *mobjects): def toggle_from_selection(self, *mobjects):
for mob in mobjects: for mob in mobjects:
if mob in self.selection: if mob in self.selection:
self.selection.remove(mob) self.selection.remove(mob)
mob.set_animating_status(False)
else: else:
self.add_to_selection(mob) self.add_to_selection(mob)
self.refresh_selection_highlight() self.refresh_selection_highlight()
def clear_selection(self): def clear_selection(self):
for mob in self.selection:
mob.set_animating_status(False)
self.selection.set_submobjects([]) self.selection.set_submobjects([])
self.selection_highlight.set_submobjects([]) self.selection_highlight.set_submobjects([])
self.refresh_static_mobjects()
def add(self, *new_mobjects: Mobject): def add(self, *new_mobjects: Mobject):
for mob in new_mobjects:
mob.make_movable()
super().add(*new_mobjects) super().add(*new_mobjects)
self.regenerate_selection_search_set()
# Selection operations def remove(self, *mobjects: Mobject):
super().remove(*mobjects)
self.regenerate_selection_search_set()
def disable_interaction(self, *mobjects: Mobject):
for mob in mobjects:
self.unselectables.append(mob)
self.regenerate_selection_search_set()
def enable_interaction(self, *mobjects: Mobject):
for mob in mobjects:
if mob in self.unselectables:
self.unselectables.remove(mob)
# Functions for keyboard actions
def copy_selection(self): def copy_selection(self):
ids = map(id, self.selection) ids = map(id, self.selection)
@ -218,11 +272,11 @@ class InteractiveScene(Scene):
mobs = map(self.id_to_mobject, ids) mobs = map(self.id_to_mobject, ids)
mob_copies = [m.copy() for m in mobs if m is not None] mob_copies = [m.copy() for m in mobs if m is not None]
self.clear_selection() self.clear_selection()
self.add_to_selection(*mob_copies)
self.play(*( self.play(*(
FadeIn(mc, run_time=0.5, scale=1.5) FadeIn(mc, run_time=0.5, scale=1.5)
for mc in mob_copies for mc in mob_copies
)) ))
self.add_to_selection(*mob_copies)
return return
except ValueError: except ValueError:
pass pass
@ -242,41 +296,27 @@ class InteractiveScene(Scene):
self.remove(*self.selection) self.remove(*self.selection)
self.clear_selection() self.clear_selection()
def saved_selection_to_file(self): def restore_state(self, mobject_states: list[tuple[Mobject, Mobject]]):
directory = self.file_writer.get_saved_mobject_directory() super().restore_state(mobject_states)
files = os.listdir(directory)
for mob in self.selection:
file_name = str(mob) + "_0.mob"
index = 0
while file_name in files:
file_name = file_name.replace(str(index), str(index + 1))
index += 1
if platform.system() == 'Darwin':
user_name = os.popen(f"""
osascript -e '
set chosenfile to (choose file name default name "{file_name}" default location "{directory}")
POSIX path of chosenfile'
""").read()
user_name = user_name.replace("\n", "")
else:
user_name = input(
f"Enter mobject file name (default is {file_name}): "
)
if user_name:
file_name = user_name
files.append(file_name)
self.save_mobect(mob, file_name)
def undo(self):
mobs = []
for mob, state in self.saved_selection_state:
mob.become(state)
mobs.append(mob)
if mob not in self.mobjects:
self.add(mob)
self.selection.set_submobjects(mobs)
self.refresh_selection_highlight() self.refresh_selection_highlight()
def enable_selection(self):
self.is_selecting = True
self.add(self.selection_rectangle)
self.selection_rectangle.fixed_corner = self.mouse_point.get_center().copy()
def gather_new_selection(self):
self.is_selecting = False
self.remove(self.selection_rectangle)
for mob in reversed(self.get_selection_search_set()):
if self.selection_rectangle.is_touching(mob):
self.add_to_selection(mob)
def prepare_grab(self):
mp = self.mouse_point.get_center()
self.mouse_to_selection = mp - self.selection.get_center()
self.is_grabbing = True
def prepare_resizing(self, about_corner=False): def prepare_resizing(self, about_corner=False):
center = self.selection.get_center() center = self.selection.get_center()
mp = self.mouse_point.get_center() mp = self.mouse_point.get_center()
@ -286,62 +326,24 @@ class InteractiveScene(Scene):
self.scale_about_point = center self.scale_about_point = center
self.scale_ref_vect = mp - self.scale_about_point self.scale_ref_vect = mp - self.scale_about_point
self.scale_ref_width = self.selection.get_width() self.scale_ref_width = self.selection.get_width()
self.scale_ref_height = self.selection.get_height()
# Event handlers def toggle_color_palette(self):
def on_key_press(self, symbol: int, modifiers: int) -> None:
super().on_key_press(symbol, modifiers)
char = chr(symbol)
# Enable selection
if char == SELECT_KEY and modifiers == 0:
self.is_selecting = True
self.add(self.selection_rectangle)
self.selection_rectangle.fixed_corner = self.mouse_point.get_center().copy()
# Prepare for move
elif char in [GRAB_KEY, HORIZONTAL_GRAB_KEY, VERTICAL_GRAB_KEY] and modifiers == 0:
mp = self.mouse_point.get_center()
self.mouse_to_selection = mp - self.selection.get_center()
# Prepare for resizing
elif char == RESIZE_KEY and modifiers in [0, SHIFT_MODIFIER]:
self.prepare_resizing(about_corner=(modifiers == SHIFT_MODIFIER))
elif symbol == SHIFT_SYMBOL:
if self.window.is_key_pressed(ord("t")):
self.prepare_resizing(about_corner=True)
# Show color palette
elif char == COLOR_KEY and modifiers == 0:
if len(self.selection) == 0: if len(self.selection) == 0:
return return
if self.color_palette not in self.mobjects: if self.color_palette not in self.mobjects:
self.save_state()
self.add(self.color_palette) self.add(self.color_palette)
else: else:
self.remove(self.color_palette) self.remove(self.color_palette)
# Command + c -> Copy mobject ids to clipboard
elif char == "c" and modifiers == COMMAND_MODIFIER: def group_selection(self):
self.copy_selection()
# Command + v -> Paste
elif char == "v" and modifiers == COMMAND_MODIFIER:
self.paste_selection()
# Command + x -> Cut
elif char == "x" and modifiers == COMMAND_MODIFIER:
# TODO, this copy won't work, because once the objects are removed,
# they're not searched for in the pasting.
self.copy_selection()
self.delete_selection()
# Delete
elif symbol == DELETE_SYMBOL:
self.delete_selection()
# Command + a -> Select all
elif char == "a" and modifiers == COMMAND_MODIFIER:
self.clear_selection()
self.add_to_selection(*self.mobjects)
# Command + g -> Group selection
elif char == "g" and modifiers == COMMAND_MODIFIER:
group = self.get_group(*self.selection) group = self.get_group(*self.selection)
self.add(group) self.add(group)
self.clear_selection() self.clear_selection()
self.add_to_selection(group) self.add_to_selection(group)
# Command + shift + g -> Ungroup the selection
elif char == "g" and modifiers == COMMAND_MODIFIER | SHIFT_MODIFIER: def ungroup_selection(self):
pieces = [] pieces = []
for mob in list(self.selection): for mob in list(self.selection):
self.remove(mob) self.remove(mob)
@ -349,73 +351,148 @@ class InteractiveScene(Scene):
self.clear_selection() self.clear_selection()
self.add(*pieces) self.add(*pieces)
self.add_to_selection(*pieces) self.add_to_selection(*pieces)
# Command + t -> Toggle selection mode
def nudge_selection(self, vect: np.ndarray, large: bool = False):
nudge = self.selection_nudge_size
if large:
nudge *= 10
self.selection.shift(nudge * vect)
def save_selection_to_file(self):
if len(self.selection) == 1:
self.save_mobject_to_file(self.selection[0])
else:
self.save_mobject_to_file(self.selection)
def on_key_press(self, symbol: int, modifiers: int) -> None:
super().on_key_press(symbol, modifiers)
char = chr(symbol)
if char == SELECT_KEY and modifiers == 0:
self.enable_selection()
elif char in GRAB_KEYS and modifiers == 0:
self.prepare_grab()
elif char == RESIZE_KEY and modifiers in [0, SHIFT_MODIFIER]:
self.prepare_resizing(about_corner=(modifiers == SHIFT_MODIFIER))
elif symbol == SHIFT_SYMBOL:
if self.window.is_key_pressed(ord("t")):
self.prepare_resizing(about_corner=True)
elif char == COLOR_KEY and modifiers == 0:
self.toggle_color_palette()
elif char == CURSOR_LOCATION_KEY and modifiers == 0:
self.add(self.cursor_location_label)
elif char == "c" and modifiers == COMMAND_MODIFIER:
self.copy_selection()
elif char == "v" and modifiers == COMMAND_MODIFIER:
self.paste_selection()
elif char == "x" and modifiers == COMMAND_MODIFIER:
self.copy_selection()
self.delete_selection()
elif symbol == DELETE_SYMBOL:
self.delete_selection()
elif char == "a" and modifiers == COMMAND_MODIFIER:
self.clear_selection()
self.add_to_selection(*self.mobjects)
elif char == "g" and modifiers == COMMAND_MODIFIER:
self.group_selection()
elif char == "g" and modifiers == COMMAND_MODIFIER | SHIFT_MODIFIER:
self.ungroup_selection()
elif char == "t" and modifiers == COMMAND_MODIFIER: elif char == "t" and modifiers == COMMAND_MODIFIER:
self.toggle_selection_mode() self.toggle_selection_mode()
# Command + z -> Restore selection to original state
elif char == "z" and modifiers == COMMAND_MODIFIER:
self.undo()
# Command + s -> Save selections to file
elif char == "s" and modifiers == COMMAND_MODIFIER: elif char == "s" and modifiers == COMMAND_MODIFIER:
self.saved_selection_to_file() self.save_selection_to_file()
# Keyboard movements
elif symbol in ARROW_SYMBOLS: elif symbol in ARROW_SYMBOLS:
nudge = self.selection_nudge_size self.nudge_selection(
if (modifiers & SHIFT_MODIFIER): vect=[LEFT, UP, RIGHT, DOWN][ARROW_SYMBOLS.index(symbol)],
nudge *= 10 large=(modifiers & SHIFT_MODIFIER),
vect = [LEFT, UP, RIGHT, DOWN][ARROW_SYMBOLS.index(symbol)] )
self.selection.shift(nudge * vect)
# Conditions for saving state
if char in [GRAB_KEY, X_GRAB_KEY, Y_GRAB_KEY, RESIZE_KEY]:
self.save_state()
def on_key_release(self, symbol: int, modifiers: int) -> None: def on_key_release(self, symbol: int, modifiers: int) -> None:
super().on_key_release(symbol, modifiers) super().on_key_release(symbol, modifiers)
if chr(symbol) == SELECT_KEY: if chr(symbol) == SELECT_KEY:
self.is_selecting = False self.gather_new_selection()
self.remove(self.selection_rectangle) if chr(symbol) in GRAB_KEYS:
for mob in reversed(self.get_selection_search_set()): self.is_grabbing = False
if mob.is_movable() and self.selection_rectangle.is_touching(mob): elif chr(symbol) == CURSOR_LOCATION_KEY:
self.add_to_selection(mob) self.remove(self.cursor_location_label)
elif symbol == SHIFT_SYMBOL and self.window.is_key_pressed(ord(RESIZE_KEY)):
elif symbol == SHIFT_SYMBOL:
if self.window.is_key_pressed(ord(RESIZE_KEY)):
self.prepare_resizing(about_corner=False) self.prepare_resizing(about_corner=False)
def on_mouse_motion(self, point: np.ndarray, d_point: np.ndarray) -> None: # Mouse actions
super().on_mouse_motion(point, d_point) def handle_grabbing(self, point: np.ndarray):
# Move selection diff = point - self.mouse_to_selection
if self.window.is_key_pressed(ord("g")): if self.window.is_key_pressed(ord(GRAB_KEY)):
self.selection.move_to(point - self.mouse_to_selection) self.selection.move_to(diff)
# Move selection restricted to horizontal elif self.window.is_key_pressed(ord(X_GRAB_KEY)):
elif self.window.is_key_pressed(ord("h")): self.selection.set_x(diff[0])
self.selection.set_x((point - self.mouse_to_selection)[0]) elif self.window.is_key_pressed(ord(Y_GRAB_KEY)):
# Move selection restricted to vertical self.selection.set_y(diff[1])
elif self.window.is_key_pressed(ord("v")):
self.selection.set_y((point - self.mouse_to_selection)[1]) def handle_resizing(self, point: np.ndarray):
# Scale selection
elif self.window.is_key_pressed(ord("t")):
# TODO, allow for scaling about the opposite corner
vect = point - self.scale_about_point vect = point - self.scale_about_point
if self.window.is_key_pressed(CTRL_SYMBOL):
for i in (0, 1):
scalar = vect[i] / self.scale_ref_vect[i]
self.selection.rescale_to_fit(
scalar * [self.scale_ref_width, self.scale_ref_height][i],
dim=i,
about_point=self.scale_about_point,
stretch=True,
)
else:
scalar = get_norm(vect) / get_norm(self.scale_ref_vect) scalar = get_norm(vect) / get_norm(self.scale_ref_vect)
self.selection.set_width( self.selection.set_width(
scalar * self.scale_ref_width, scalar * self.scale_ref_width,
about_point=self.scale_about_point about_point=self.scale_about_point
) )
def handle_sweeping_selection(self, point: np.ndarray):
mob = self.point_to_mobject(
point, search_set=self.get_selection_search_set(),
buff=SMALL_BUFF
)
if mob is not None:
self.add_to_selection(mob)
def choose_color(self, point: np.ndarray):
# Search through all mobject on the screen, not just the palette
to_search = [
sm
for mobject in self.mobjects
for sm in mobject.family_members_with_points()
if mobject not in self.unselectables
]
mob = self.point_to_mobject(point, to_search)
if mob is not None:
self.selection.set_color(mob.get_color())
self.remove(self.color_palette)
def toggle_clicked_mobject_from_selection(self, point: np.ndarray):
mob = self.point_to_mobject(
point,
search_set=self.get_selection_search_set(),
buff=SMALL_BUFF
)
if mob is not None:
self.toggle_from_selection(mob)
def on_mouse_motion(self, point: np.ndarray, d_point: np.ndarray) -> None:
super().on_mouse_motion(point, d_point)
if self.is_grabbing:
self.handle_grabbing(point)
elif self.window.is_key_pressed(ord(RESIZE_KEY)):
self.handle_resizing(point)
elif self.window.is_key_pressed(ord(SELECT_KEY)) and self.window.is_key_pressed(SHIFT_SYMBOL):
self.handle_sweeping_selection(point)
def on_mouse_release(self, point: np.ndarray, button: int, mods: int) -> None: def on_mouse_release(self, point: np.ndarray, button: int, mods: int) -> None:
super().on_mouse_release(point, button, mods) super().on_mouse_release(point, button, mods)
if self.color_palette in self.mobjects: if self.color_palette in self.mobjects:
# Search through all mobject on the screne, not just the palette self.choose_color(point)
to_search = list(it.chain(*(
mobject.family_members_with_points()
for mobject in self.mobjects
if mobject not in self.unselectables
)))
mob = self.point_to_mobject(point, to_search)
if mob is not None:
self.selection.set_color(mob.get_fill_color())
self.remove(self.color_palette)
elif self.window.is_key_pressed(SHIFT_SYMBOL): elif self.window.is_key_pressed(SHIFT_SYMBOL):
mob = self.point_to_mobject(point) self.toggle_clicked_mobject_from_selection(point)
if mob is not None:
self.toggle_from_selection(mob)
else: else:
self.clear_selection() self.clear_selection()

View file

@ -2,10 +2,11 @@ from manimlib.animation.animation import Animation
from manimlib.animation.transform import MoveToTarget from manimlib.animation.transform import MoveToTarget
from manimlib.animation.transform import Transform from manimlib.animation.transform import Transform
from manimlib.animation.update import UpdateFromFunc from manimlib.animation.update import UpdateFromFunc
from manimlib.constants import * from manimlib.constants import DOWN, RIGHT
from manimlib.scene.scene import Scene from manimlib.constants import MED_LARGE_BUFF, SMALL_BUFF
from manimlib.mobject.probability import SampleSpace from manimlib.mobject.probability import SampleSpace
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.scene.scene import Scene
class SampleSpaceScene(Scene): class SampleSpaceScene(Scene):

View file

@ -1,40 +1,42 @@
from __future__ import annotations from __future__ import annotations
import time
import random
import inspect
import platform
from functools import wraps from functools import wraps
from typing import Iterable, Callable import inspect
import os import os
import platform
import random
import time
from tqdm import tqdm as ProgressDisplay
import numpy as np import numpy as np
from tqdm import tqdm as ProgressDisplay
from manimlib.animation.animation import prepare_animation from manimlib.animation.animation import prepare_animation
from manimlib.animation.transform import MoveToTarget from manimlib.animation.transform import MoveToTarget
from manimlib.camera.camera import Camera from manimlib.camera.camera import Camera
from manimlib.config import get_custom_config
from manimlib.constants import DEFAULT_WAIT_TIME
from manimlib.constants import ARROW_SYMBOLS from manimlib.constants import ARROW_SYMBOLS
from manimlib.constants import SHIFT_MODIFIER, CTRL_MODIFIER, COMMAND_MODIFIER from manimlib.constants import DEFAULT_WAIT_TIME
from manimlib.constants import COMMAND_MODIFIER
from manimlib.constants import SHIFT_MODIFIER
from manimlib.event_handler import EVENT_DISPATCHER
from manimlib.event_handler.event_type import EventType
from manimlib.logger import log
from manimlib.mobject.mobject import Group
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject
from manimlib.mobject.mobject import Point from manimlib.mobject.mobject import Point
from manimlib.mobject.mobject import Group
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.scene.scene_file_writer import SceneFileWriter from manimlib.scene.scene_file_writer import SceneFileWriter
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.family_ops import extract_mobject_family_members from manimlib.utils.family_ops import extract_mobject_family_members
from manimlib.utils.family_ops import restructure_list_to_exclude_certain_family_members from manimlib.utils.family_ops import restructure_list_to_exclude_certain_family_members
from manimlib.event_handler.event_type import EventType
from manimlib.event_handler import EVENT_DISPATCHER
from manimlib.logger import log
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Callable, Iterable
from PIL.Image import Image from PIL.Image import Image
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation
@ -43,7 +45,6 @@ FRAME_SHIFT_KEY = 'f'
ZOOM_KEY = 'z' ZOOM_KEY = 'z'
RESET_FRAME_KEY = 'r' RESET_FRAME_KEY = 'r'
QUIT_KEY = 'q' QUIT_KEY = 'q'
EMBED_KEY = 'e'
class Scene(object): class Scene(object):
@ -62,6 +63,7 @@ class Scene(object):
"presenter_mode": False, "presenter_mode": False,
"linger_after_completion": True, "linger_after_completion": True,
"pan_sensitivity": 3, "pan_sensitivity": 3,
"max_num_saved_states": 20,
} }
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -71,12 +73,15 @@ class Scene(object):
self.window = Window(scene=self, **self.window_config) self.window = Window(scene=self, **self.window_config)
self.camera_config["ctx"] = self.window.ctx self.camera_config["ctx"] = self.window.ctx
self.camera_config["frame_rate"] = 30 # Where's that 30 from? self.camera_config["frame_rate"] = 30 # Where's that 30 from?
self.undo_stack = []
self.redo_stack = []
else: else:
self.window = None self.window = None
self.camera: Camera = self.camera_class(**self.camera_config) self.camera: Camera = self.camera_class(**self.camera_config)
self.file_writer = SceneFileWriter(self, **self.file_writer_config) self.file_writer = SceneFileWriter(self, **self.file_writer_config)
self.mobjects: list[Mobject] = [self.camera.frame] self.mobjects: list[Mobject] = [self.camera.frame]
self.id_to_mobject_map: dict[int, Mobject] = dict()
self.num_plays: int = 0 self.num_plays: int = 0
self.time: float = 0 self.time: float = 0
self.skip_time: float = 0 self.skip_time: float = 0
@ -88,12 +93,16 @@ class Scene(object):
self.mouse_point = Point() self.mouse_point = Point()
self.mouse_drag_point = Point() self.mouse_drag_point = Point()
self.hold_on_wait = self.presenter_mode self.hold_on_wait = self.presenter_mode
self.inside_embed = False
# 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:
random.seed(self.random_seed) random.seed(self.random_seed)
np.random.seed(self.random_seed) np.random.seed(self.random_seed)
def __str__(self) -> str:
return self.__class__.__name__
def run(self) -> None: def run(self) -> None:
self.virtual_animation_start_time: float = 0 self.virtual_animation_start_time: float = 0
self.real_animation_start_time: float = time.time() self.real_animation_start_time: float = time.time()
@ -143,40 +152,57 @@ class Scene(object):
def embed(self, close_scene_on_exit: bool = True) -> None: def embed(self, close_scene_on_exit: bool = True) -> None:
if not self.preview: if not self.preview:
# If the scene is just being # Ignore embed calls when there is no preview
# written, ignore embed calls
return return
self.inside_embed = True
self.stop_skipping() self.stop_skipping()
self.linger_after_completion = False self.linger_after_completion = False
self.update_frame() self.update_frame()
# Save scene state at the point of embedding
self.save_state() self.save_state()
from IPython.terminal.embed import InteractiveShellEmbed # Configure and launch embedded IPython terminal
shell = InteractiveShellEmbed() from IPython.terminal import embed, pt_inputhooks
# Have the frame update after each command shell = embed.InteractiveShellEmbed.instance()
shell.events.register('post_run_cell', lambda *a, **kw: self.refresh_static_mobjects())
shell.events.register('post_run_cell', lambda *a, **kw: self.update_frame()) # Use the locals namespace of the caller
# Use the locals of the caller as the local namespace
# once embedded, and add a few custom shortcuts
local_ns = inspect.currentframe().f_back.f_locals local_ns = inspect.currentframe().f_back.f_locals
local_ns["touch"] = self.interact # Add a few custom shortcuts
local_ns["i2g"] = self.ids_to_group local_ns.update({
for term in ("play", "wait", "add", "remove", "clear", "save_state", "restore"): name: getattr(self, name)
local_ns[term] = getattr(self, term) for name in [
log.info("Tips: Now the embed iPython terminal is open. But you can't interact with" "play", "wait", "add", "remove", "clear",
" the window directly. To do so, you need to type `touch()` or `self.interact()`") "save_state", "undo", "redo", "i2g", "i2m"
exec(get_custom_config()["universal_import_line"]) ]
})
# Enables gui interactions during the embed
def inputhook(context):
while not context.input_is_ready():
if self.window.is_closing:
pass
# self.window.destroy()
else:
self.update_frame(dt=0)
pt_inputhooks.register("manim", inputhook)
shell.enable_gui("manim")
# Operation to run after each ipython command
def post_cell_func(*args, **kwargs):
self.refresh_static_mobjects()
shell.events.register("post_run_cell", post_cell_func)
# Launch shell, with stack_depth=2 indicating we should use caller globals/locals
shell(local_ns=local_ns, stack_depth=2) shell(local_ns=local_ns, stack_depth=2)
self.inside_embed = False
# End scene when exiting an embed # End scene when exiting an embed
if close_scene_on_exit: if close_scene_on_exit:
raise EndSceneEarlyException() raise EndSceneEarlyException()
def __str__(self) -> str:
return self.__class__.__name__
# Only these methods should touch the camera # Only these methods should touch the camera
def get_image(self) -> Image: def get_image(self) -> Image:
return self.camera.get_image() return self.camera.get_image()
@ -207,6 +233,7 @@ class Scene(object):
self.file_writer.write_frame(self.camera) self.file_writer.write_frame(self.camera)
# Related to updating # Related to updating
def update_mobjects(self, dt: float) -> None: def update_mobjects(self, dt: float) -> None:
for mobject in self.mobjects: for mobject in self.mobjects:
mobject.update(dt) mobject.update(dt)
@ -225,6 +252,7 @@ class Scene(object):
]) ])
# Related to time # Related to time
def get_time(self) -> float: def get_time(self) -> float:
return self.time return self.time
@ -232,6 +260,7 @@ class Scene(object):
self.time += dt self.time += dt
# Related to internal mobject organization # Related to internal mobject organization
def get_top_level_mobjects(self) -> list[Mobject]: def get_top_level_mobjects(self) -> list[Mobject]:
# Return only those which are not in the family # Return only those which are not in the family
# of another mobject from the scene # of another mobject from the scene
@ -256,6 +285,11 @@ class Scene(object):
""" """
self.remove(*new_mobjects) self.remove(*new_mobjects)
self.mobjects += new_mobjects self.mobjects += new_mobjects
self.id_to_mobject_map.update({
id(sm): sm
for m in new_mobjects
for sm in m.get_family()
})
return self return self
def add_mobjects_among(self, values: Iterable): def add_mobjects_among(self, values: Iterable):
@ -319,11 +353,7 @@ class Scene(object):
return Group(*mobjects) return Group(*mobjects)
def id_to_mobject(self, id_value): def id_to_mobject(self, id_value):
for mob in self.mobjects: return self.id_to_mobject_map[id_value]
for sm in mob.get_family():
if id(sm) == id_value:
return sm
return None
def ids_to_group(self, *id_values): def ids_to_group(self, *id_values):
return self.get_group(*filter( return self.get_group(*filter(
@ -331,7 +361,14 @@ class Scene(object):
map(self.id_to_mobject, id_values) map(self.id_to_mobject, id_values)
)) ))
def i2g(self, *id_values):
return self.ids_to_group(*id_values)
def i2m(self, id_value):
return self.id_to_mobject(id_value)
# Related to skipping # Related to skipping
def update_skipping_status(self) -> None: def update_skipping_status(self) -> None:
if self.start_at_animation_number is not None: if self.start_at_animation_number is not None:
if self.num_plays == self.start_at_animation_number: if self.num_plays == self.start_at_animation_number:
@ -347,6 +384,7 @@ class Scene(object):
self.skip_animations = False self.skip_animations = False
# Methods associated with running animations # Methods associated with running animations
def get_time_progression( def get_time_progression(
self, self,
run_time: float, run_time: float,
@ -470,6 +508,8 @@ class Scene(object):
def handle_play_like_call(func): def handle_play_like_call(func):
@wraps(func) @wraps(func)
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
if self.inside_embed:
self.save_state()
self.update_skipping_status() self.update_skipping_status()
should_write = not self.skip_animations should_write = not self.skip_animations
if should_write: if should_write:
@ -591,24 +631,39 @@ class Scene(object):
self.file_writer.add_sound(sound_file, time, gain, gain_to_background) self.file_writer.add_sound(sound_file, time, gain, gain_to_background)
# Helpers for interactive development # Helpers for interactive development
def get_state(self) -> list[tuple[Mobject, Mobject]]:
return [(mob, mob.copy()) for mob in self.mobjects]
def restore_state(self, mobject_states: list[tuple[Mobject, Mobject]]):
self.mobjects = [mob.become(mob_copy) for mob, mob_copy in mobject_states]
def save_state(self) -> None: def save_state(self) -> None:
self.saved_state = [ if not self.preview:
(mob, mob.copy()) return
for mob in self.mobjects self.redo_stack = []
] self.undo_stack.append(self.get_state())
if len(self.undo_stack) > self.max_num_saved_states:
self.undo_stack.pop(0)
def restore(self) -> None: def undo(self):
if not hasattr(self, "saved_state"): if self.undo_stack:
raise Exception("Trying to restore scene without having saved") self.redo_stack.append(self.get_state())
self.mobjects = [] self.restore_state(self.undo_stack.pop())
for mob, mob_state in self.saved_state: self.refresh_static_mobjects()
mob.become(mob_state)
self.mobjects.append(mob)
def save_mobect(self, mobject: Mobject, file_name: str): def redo(self):
directory = self.file_writer.get_saved_mobject_directory() if self.redo_stack:
path = os.path.join(directory, file_name) self.undo_stack.append(self.get_state())
mobject.save_to_file(path) self.restore_state(self.redo_stack.pop())
self.refresh_static_mobjects()
def save_mobject_to_file(self, mobject: Mobject, file_path: str | None = None) -> None:
if file_path is None:
file_path = self.file_writer.get_saved_mobject_path(mobject)
if file_path is None:
return
mobject.save_to_file(file_path)
def load_mobject(self, file_name): def load_mobject(self, file_name):
if os.path.exists(file_name): if os.path.exists(file_name):
@ -730,15 +785,16 @@ class Scene(object):
if char == RESET_FRAME_KEY: if char == RESET_FRAME_KEY:
self.camera.frame.to_default_state() self.camera.frame.to_default_state()
elif char == "z" and modifiers == COMMAND_MODIFIER:
self.undo()
elif char == "z" and modifiers == COMMAND_MODIFIER | SHIFT_MODIFIER:
self.redo()
# command + q # command + q
elif char == QUIT_KEY and modifiers == COMMAND_MODIFIER: elif char == QUIT_KEY and modifiers == COMMAND_MODIFIER:
self.quit_interaction = True self.quit_interaction = True
# Space or right arrow # Space or right arrow
elif char == " " or symbol == ARROW_SYMBOLS[2]: elif char == " " or symbol == ARROW_SYMBOLS[2]:
self.hold_on_wait = False self.hold_on_wait = False
# ctrl + shift + e
elif char == EMBED_KEY and modifiers == CTRL_MODIFIER | SHIFT_MODIFIER:
self.embed(close_scene_on_exit=False)
def on_resize(self, width: int, height: int) -> None: def on_resize(self, width: int, height: int) -> None:
self.camera.reset_pixel_shape(width, height) self.camera.reset_pixel_shape(width, height)

View file

@ -1,30 +1,32 @@
from __future__ import annotations from __future__ import annotations
import os import os
import sys
import shutil
import platform import platform
import shutil
import subprocess as sp import subprocess as sp
import sys
import numpy as np import numpy as np
from pydub import AudioSegment from pydub import AudioSegment
from tqdm import tqdm as ProgressDisplay from tqdm import tqdm as ProgressDisplay
from manimlib.constants import FFMPEG_BIN from manimlib.constants import FFMPEG_BIN
from manimlib.logger import log
from manimlib.mobject.mobject import Mobject
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.file_ops import guarantee_existence
from manimlib.utils.file_ops import add_extension_if_not_present from manimlib.utils.file_ops import add_extension_if_not_present
from manimlib.utils.file_ops import get_sorted_integer_files from manimlib.utils.file_ops import get_sorted_integer_files
from manimlib.utils.file_ops import guarantee_existence
from manimlib.utils.sounds import get_full_sound_file_path from manimlib.utils.sounds import get_full_sound_file_path
from manimlib.logger import log
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from manimlib.scene.scene import Scene
from manimlib.camera.camera import Camera
from PIL.Image import Image from PIL.Image import Image
from manimlib.camera.camera import Camera
from manimlib.scene.scene import Scene
class SceneFileWriter(object): class SceneFileWriter(object):
CONFIG = { CONFIG = {
@ -60,7 +62,7 @@ class SceneFileWriter(object):
# Output directories and files # Output directories and files
def init_output_directories(self) -> None: def init_output_directories(self) -> None:
out_dir = self.output_directory out_dir = self.output_directory or ""
if self.mirror_module_path: if self.mirror_module_path:
module_dir = self.get_default_module_directory() module_dir = self.get_default_module_directory()
out_dir = os.path.join(out_dir, module_dir) out_dir = os.path.join(out_dir, module_dir)
@ -127,6 +129,36 @@ class SceneFileWriter(object):
str(self.scene), str(self.scene),
)) ))
def get_saved_mobject_path(self, mobject: Mobject) -> str | None:
directory = self.get_saved_mobject_directory()
files = os.listdir(directory)
default_name = str(mobject) + "_0.mob"
index = 0
while default_name in files:
default_name = default_name.replace(str(index), str(index + 1))
index += 1
if platform.system() == 'Darwin':
cmds = [
"osascript", "-e",
f"""
set chosenfile to (choose file name default name "{default_name}" default location "{directory}")
POSIX path of chosenfile
""",
]
process = sp.Popen(cmds, stdout=sp.PIPE)
file_path = process.stdout.read().decode("utf-8").split("\n")[0]
if not file_path:
return
else:
user_name = input(f"Enter mobject file name (default is {default_name}): ")
file_path = os.path.join(directory, user_name or default_name)
if os.path.exists(file_path) or os.path.exists(file_path + ".mob"):
if input(f"{file_path} already exists. Overwrite (y/n)? ") != "y":
return
if not file_path.endswith(".mob"):
file_path = file_path + ".mob"
return file_path
# Sound # Sound
def init_audio(self) -> None: def init_audio(self) -> None:
self.includes_sound: bool = False self.includes_sound: bool = False

View file

@ -8,7 +8,10 @@ from manimlib.animation.growing import GrowArrow
from manimlib.animation.transform import ApplyFunction from manimlib.animation.transform import ApplyFunction
from manimlib.animation.transform import ApplyPointwiseFunction from manimlib.animation.transform import ApplyPointwiseFunction
from manimlib.animation.transform import Transform from manimlib.animation.transform import Transform
from manimlib.constants import * from manimlib.constants import BLACK, BLUE_D, GREEN_C, RED_C, GREY, WHITE, YELLOW
from manimlib.constants import DL, DOWN, ORIGIN, RIGHT, UP
from manimlib.constants import FRAME_WIDTH, FRAME_X_RADIUS, FRAME_Y_RADIUS
from manimlib.constants import SMALL_BUFF
from manimlib.mobject.coordinate_systems import Axes from manimlib.mobject.coordinate_systems import Axes
from manimlib.mobject.coordinate_systems import NumberPlane from manimlib.mobject.coordinate_systems import NumberPlane
from manimlib.mobject.geometry import Arrow from manimlib.mobject.geometry import Arrow
@ -30,6 +33,7 @@ from manimlib.utils.rate_functions import rush_into
from manimlib.utils.space_ops import angle_of_vector from manimlib.utils.space_ops import angle_of_vector
from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import get_norm
X_COLOR = GREEN_C X_COLOR = GREEN_C
Y_COLOR = RED_C Y_COLOR = RED_C
Z_COLOR = BLUE_D Z_COLOR = BLUE_D

View file

@ -1,9 +1,8 @@
from __future__ import annotations from __future__ import annotations
import copy
import os import os
import re import re
import copy
from typing import Iterable
import moderngl import moderngl
import numpy as np import numpy as np
@ -11,6 +10,12 @@ import numpy as np
from manimlib.utils.directories import get_shader_dir from manimlib.utils.directories import get_shader_dir
from manimlib.utils.file_ops import find_file from manimlib.utils.file_ops import find_file
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Iterable
# Mobjects that should be rendered with # Mobjects that should be rendered with
# the same shader will be organized and # the same shader will be organized and
# clumped together based on keeping track # clumped together based on keeping track

View file

@ -1,19 +1,26 @@
from __future__ import annotations from __future__ import annotations
from typing import Iterable, Callable, TypeVar, Sequence
from scipy import linalg
import numpy as np import numpy as np
from scipy import linalg
from manimlib.logger import log
from manimlib.utils.simple_functions import choose
from manimlib.utils.space_ops import cross2d
from manimlib.utils.space_ops import find_intersection
from manimlib.utils.space_ops import midpoint
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable, Iterable, Sequence, TypeVar
import numpy.typing as npt import numpy.typing as npt
from manimlib.utils.simple_functions import choose T = TypeVar("T")
from manimlib.utils.space_ops import find_intersection
from manimlib.utils.space_ops import cross2d
from manimlib.utils.space_ops import midpoint
from manimlib.logger import log
CLOSED_THRESHOLD = 0.001 CLOSED_THRESHOLD = 0.001
T = TypeVar("T")
def bezier( def bezier(
points: Iterable[float | np.ndarray] points: Iterable[float | np.ndarray]

View file

@ -1,15 +1,24 @@
import random from __future__ import annotations
from colour import Color from colour import Color
from colour import hex2rgb
from colour import rgb2hex
import numpy as np import numpy as np
from manimlib.constants import WHITE
from manimlib.constants import COLORMAP_3B1B from manimlib.constants import COLORMAP_3B1B
from manimlib.constants import WHITE
from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import interpolate
from manimlib.utils.iterables import resize_with_interpolation from manimlib.utils.iterables import resize_with_interpolation
from typing import TYPE_CHECKING
def color_to_rgb(color): if TYPE_CHECKING:
from typing import Iterable, Union
ManimColor = Union[str, Color]
def color_to_rgb(color: ManimColor) -> np.ndarray:
if isinstance(color, str): if isinstance(color, str):
return hex_to_rgb(color) return hex_to_rgb(color)
elif isinstance(color, Color): elif isinstance(color, Color):
@ -18,55 +27,48 @@ def color_to_rgb(color):
raise Exception("Invalid color type") raise Exception("Invalid color type")
def color_to_rgba(color, alpha=1): def color_to_rgba(color: ManimColor, alpha: float = 1.0) -> np.ndarray:
return np.array([*color_to_rgb(color), alpha]) return np.array([*color_to_rgb(color), alpha])
def rgb_to_color(rgb): def rgb_to_color(rgb: Iterable[float]) -> Color:
try: try:
return Color(rgb=rgb) return Color(rgb=tuple(rgb))
except ValueError: except ValueError:
return Color(WHITE) return Color(WHITE)
def rgba_to_color(rgba): def rgba_to_color(rgba: Iterable[float]) -> Color:
return rgb_to_color(rgba[:3]) return rgb_to_color(tuple(rgba)[:3])
def rgb_to_hex(rgb): def rgb_to_hex(rgb: Iterable[float]) -> str:
return "#" + "".join( return rgb2hex(rgb, force_long=True).upper()
hex(int_x // 16)[2] + hex(int_x % 16)[2]
for x in rgb
for int_x in [int(255 * x)]
)
def hex_to_rgb(hex_code): def hex_to_rgb(hex_code: str) -> np.ndarray:
hex_part = hex_code[1:] return np.array(hex2rgb(hex_code))
if len(hex_part) == 3:
hex_part = "".join([2 * c for c in hex_part])
return np.array([
int(hex_part[i:i + 2], 16) / 255
for i in range(0, 6, 2)
])
def invert_color(color): def invert_color(color: ManimColor) -> Color:
return rgb_to_color(1.0 - color_to_rgb(color)) return rgb_to_color(1.0 - color_to_rgb(color))
def color_to_int_rgb(color): def color_to_int_rgb(color: ManimColor) -> np.ndarray:
return (255 * color_to_rgb(color)).astype('uint8') return (255 * color_to_rgb(color)).astype('uint8')
def color_to_int_rgba(color, opacity=1.0): def color_to_int_rgba(color: ManimColor, opacity: float = 1.0) -> np.ndarray:
alpha = int(255 * opacity) alpha = int(255 * opacity)
return np.array([*color_to_int_rgb(color), alpha]) return np.array([*color_to_int_rgb(color), alpha])
def color_gradient(reference_colors, length_of_output): def color_gradient(
reference_colors: Iterable[ManimColor],
length_of_output: int
) -> list[Color]:
if length_of_output == 0: if length_of_output == 0:
return reference_colors[0] return []
rgbs = list(map(color_to_rgb, reference_colors)) rgbs = list(map(color_to_rgb, reference_colors))
alphas = np.linspace(0, (len(rgbs) - 1), length_of_output) alphas = np.linspace(0, (len(rgbs) - 1), length_of_output)
floors = alphas.astype('int') floors = alphas.astype('int')
@ -80,30 +82,33 @@ def color_gradient(reference_colors, length_of_output):
] ]
def interpolate_color(color1, color2, alpha): def interpolate_color(
color1: ManimColor,
color2: ManimColor,
alpha: float
) -> Color:
rgb = interpolate(color_to_rgb(color1), color_to_rgb(color2), alpha) rgb = interpolate(color_to_rgb(color1), color_to_rgb(color2), alpha)
return rgb_to_color(rgb) return rgb_to_color(rgb)
def average_color(*colors): def average_color(*colors: ManimColor) -> Color:
rgbs = np.array(list(map(color_to_rgb, colors))) rgbs = np.array(list(map(color_to_rgb, colors)))
return rgb_to_color(rgbs.mean(0)) return rgb_to_color(rgbs.mean(0))
def random_bright_color(): def random_color() -> Color:
return Color(rgb=tuple(np.random.random(3)))
def random_bright_color() -> Color:
color = random_color() color = random_color()
curr_rgb = color_to_rgb(color) return average_color(color, Color(WHITE))
new_rgb = interpolate(
curr_rgb, np.ones(len(curr_rgb)), 0.5
)
return Color(rgb=new_rgb)
def random_color(): def get_colormap_list(
return Color(rgb=(random.random() for i in range(3))) map_name: str = "viridis",
n_colors: int = 9
) -> np.ndarray:
def get_colormap_list(map_name="viridis", n_colors=9):
""" """
Options for map_name: Options for map_name:
3b1b_colormap 3b1b_colormap

View file

@ -4,6 +4,7 @@ import tempfile
from manimlib.config import get_custom_config from manimlib.config import get_custom_config
from manimlib.config import get_manim_dir from manimlib.config import get_manim_dir
CUSTOMIZATION = {} CUSTOMIZATION = {}

View file

@ -1,17 +1,18 @@
from __future__ import annotations from __future__ import annotations
import time
import numpy as np import numpy as np
from typing import Callable import time
from manimlib.constants import BLACK from manimlib.constants import BLACK
from manimlib.logger import log
from manimlib.mobject.numbers import Integer from manimlib.mobject.numbers import Integer
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.logger import log
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Callable
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject

View file

@ -2,8 +2,8 @@ from __future__ import annotations
import os import os
from manimlib.utils.file_ops import guarantee_existence
from manimlib.utils.customization import get_customization from manimlib.utils.customization import get_customization
from manimlib.utils.file_ops import guarantee_existence
def get_directories() -> dict[str, str]: def get_directories() -> dict[str, str]:

View file

@ -1,10 +1,10 @@
from __future__ import annotations from __future__ import annotations
from typing import Iterable
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Iterable
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject

View file

@ -1,11 +1,15 @@
from __future__ import annotations from __future__ import annotations
import os import os
from typing import Iterable
import numpy as np import numpy as np
import validators import validators
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Iterable
def add_extension_if_not_present(file_name: str, extension: str) -> str: def add_extension_if_not_present(file_name: str, extension: str) -> str:
# This could conceivably be smarter about handling existing differing extensions # This could conceivably be smarter about handling existing differing extensions

View file

@ -1,10 +1,16 @@
from __future__ import annotations
import numpy as np import numpy as np
from PIL import Image from PIL import Image
from typing import Iterable
from manimlib.utils.file_ops import find_file
from manimlib.utils.directories import get_raster_image_dir from manimlib.utils.directories import get_raster_image_dir
from manimlib.utils.directories import get_vector_image_dir from manimlib.utils.directories import get_vector_image_dir
from manimlib.utils.file_ops import find_file
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Iterable
def get_full_raster_image_path(image_file_name: str) -> str: def get_full_raster_image_path(image_file_name: str) -> str:

View file

@ -1,16 +1,21 @@
from __future__ import annotations from __future__ import annotations
import importlib
import inspect
import os import os
import yaml import yaml
import inspect
import importlib
from typing import Any
from rich import box from rich import box
from rich.console import Console
from rich.prompt import Confirm
from rich.prompt import Prompt
from rich.rule import Rule from rich.rule import Rule
from rich.table import Table from rich.table import Table
from rich.console import Console
from rich.prompt import Prompt, Confirm from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any
def get_manim_dir() -> str: def get_manim_dir() -> str:

View file

@ -1,5 +1,6 @@
from __future__ import annotations
import math import math
from typing import Callable
import numpy as np import numpy as np
@ -8,6 +9,12 @@ from manimlib.utils.bezier import interpolate
from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import get_norm
from manimlib.utils.space_ops import rotation_matrix_transpose from manimlib.utils.space_ops import rotation_matrix_transpose
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable
STRAIGHT_PATH_THRESHOLD = 0.01 STRAIGHT_PATH_THRESHOLD = 0.01

View file

@ -1,9 +1,14 @@
from typing import Callable from __future__ import annotations
import numpy as np import numpy as np
from manimlib.utils.bezier import bezier from manimlib.utils.bezier import bezier
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable
def linear(t: float) -> float: def linear(t: float) -> float:
return t return t

View file

@ -1,7 +1,8 @@
import inspect
import numpy as np
import math
from functools import lru_cache from functools import lru_cache
import inspect
import math
import numpy as np
def sigmoid(x): def sigmoid(x):

View file

@ -1,8 +1,10 @@
from manimlib.utils.file_ops import find_file from __future__ import annotations
from manimlib.utils.directories import get_sound_dir from manimlib.utils.directories import get_sound_dir
from manimlib.utils.file_ops import find_file
def get_full_sound_file_path(sound_file_name) -> str: def get_full_sound_file_path(sound_file_name: str) -> str:
return find_file( return find_file(
sound_file_name, sound_file_name,
directories=[get_sound_dir()], directories=[get_sound_dir()],

View file

@ -1,25 +1,27 @@
from __future__ import annotations from __future__ import annotations
from functools import reduce
import math import math
import operator as op import operator as op
from functools import reduce
from typing import Callable, Iterable, Sequence
import platform import platform
import numpy as np
import numpy.typing as npt
from mapbox_earcut import triangulate_float32 as earcut from mapbox_earcut import triangulate_float32 as earcut
import numpy as np
from scipy.spatial.transform import Rotation from scipy.spatial.transform import Rotation
from tqdm import tqdm as ProgressDisplay from tqdm import tqdm as ProgressDisplay
from manimlib.constants import RIGHT from manimlib.constants import DOWN, OUT, RIGHT
from manimlib.constants import DOWN from manimlib.constants import PI, TAU
from manimlib.constants import OUT
from manimlib.constants import PI
from manimlib.constants import TAU
from manimlib.utils.iterables import adjacent_pairs from manimlib.utils.iterables import adjacent_pairs
from manimlib.utils.simple_functions import clip from manimlib.utils.simple_functions import clip
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable, Iterable, Sequence
import numpy.typing as npt
def cross(v1: np.ndarray, v2: np.ndarray) -> list[np.ndarray]: def cross(v1: np.ndarray, v2: np.ndarray) -> list[np.ndarray]:
return [ return [

View file

@ -1,18 +1,20 @@
import sys from __future__ import annotations
import os
import hashlib from contextlib import contextmanager
from contextlib import contextmanager import hashlib
import os
import sys
from manimlib.utils.directories import get_tex_dir
from manimlib.config import get_manim_dir
from manimlib.config import get_custom_config from manimlib.config import get_custom_config
from manimlib.config import get_manim_dir
from manimlib.logger import log from manimlib.logger import log
from manimlib.utils.directories import get_tex_dir
SAVED_TEX_CONFIG = {} SAVED_TEX_CONFIG = {}
def get_tex_config(): def get_tex_config() -> dict[str, str]:
""" """
Returns a dict which should look something like this: Returns a dict which should look something like this:
{ {
@ -37,13 +39,13 @@ def get_tex_config():
return SAVED_TEX_CONFIG return SAVED_TEX_CONFIG
def tex_hash(tex_file_content): def tex_hash(tex_file_content: str) -> int:
# Truncating at 16 bytes for cleanliness # Truncating at 16 bytes for cleanliness
hasher = hashlib.sha256(tex_file_content.encode()) hasher = hashlib.sha256(tex_file_content.encode())
return hasher.hexdigest()[:16] return hasher.hexdigest()[:16]
def tex_to_svg_file(tex_file_content): def tex_to_svg_file(tex_file_content: str) -> str:
svg_file = os.path.join( svg_file = os.path.join(
get_tex_dir(), tex_hash(tex_file_content) + ".svg" get_tex_dir(), tex_hash(tex_file_content) + ".svg"
) )
@ -53,7 +55,7 @@ def tex_to_svg_file(tex_file_content):
return svg_file return svg_file
def tex_to_svg(tex_file_content, svg_file): def tex_to_svg(tex_file_content: str, svg_file: str) -> str:
tex_file = svg_file.replace(".svg", ".tex") tex_file = svg_file.replace(".svg", ".tex")
with open(tex_file, "w", encoding="utf-8") as outfile: with open(tex_file, "w", encoding="utf-8") as outfile:
outfile.write(tex_file_content) outfile.write(tex_file_content)
@ -69,7 +71,7 @@ def tex_to_svg(tex_file_content, svg_file):
return svg_file return svg_file
def tex_to_dvi(tex_file): def tex_to_dvi(tex_file: str) -> str:
tex_config = get_tex_config() tex_config = get_tex_config()
program = tex_config["executable"] program = tex_config["executable"]
file_type = tex_config["intermediate_filetype"] file_type = tex_config["intermediate_filetype"]
@ -96,7 +98,7 @@ def tex_to_dvi(tex_file):
return result return result
def dvi_to_svg(dvi_file, regen_if_exists=False): def dvi_to_svg(dvi_file: str) -> str:
""" """
Converts a dvi, which potentially has multiple slides, into a Converts a dvi, which potentially has multiple slides, into a
directory full of enumerated pngs corresponding with these slides. directory full of enumerated pngs corresponding with these slides.
@ -123,7 +125,7 @@ def dvi_to_svg(dvi_file, regen_if_exists=False):
# TODO, perhaps this should live elsewhere # TODO, perhaps this should live elsewhere
@contextmanager @contextmanager
def display_during_execution(message): def display_during_execution(message: str) -> None:
# Only show top line # Only show top line
to_print = message.split("\n")[0] to_print = message.split("\n")[0]
max_characters = os.get_terminal_size().columns - 1 max_characters = os.get_terminal_size().columns - 1

View file

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import numpy as np import numpy as np
import moderngl_window as mglw import moderngl_window as mglw
from moderngl_window.context.pyglet.window import Window as PygletWindow from moderngl_window.context.pyglet.window import Window as PygletWindow
from moderngl_window.timers.clock import Timer from moderngl_window.timers.clock import Timer

View file

@ -1,23 +1,23 @@
colour colour
numpy ipython
Pillow isosurfaces
scipy manimpango>=0.4.0.post0,<0.5.0
sympy
tqdm
mapbox-earcut mapbox-earcut
matplotlib matplotlib
moderngl moderngl
moderngl_window moderngl_window
skia-pathops numpy
Pillow
pydub pydub
pygments pygments
PyOpenGL
pyperclip pyperclip
pyyaml pyyaml
rich rich
scipy
screeninfo screeninfo
validators skia-pathops
ipython
PyOpenGL
manimpango>=0.4.0.post0,<0.5.0
isosurfaces
svgelements svgelements
sympy
tqdm
validators

View file

@ -30,27 +30,28 @@ packages = find:
include_package_data = True include_package_data = True
install_requires = install_requires =
colour colour
numpy ipython
Pillow isosurfaces
scipy manimpango>=0.4.0.post0,<0.5.0
sympy
tqdm
mapbox-earcut mapbox-earcut
matplotlib matplotlib
moderngl moderngl
moderngl_window moderngl_window
skia-pathops numpy
Pillow
pydub pydub
pygments pygments
PyOpenGL
pyperclip
pyyaml pyyaml
rich rich
scipy
screeninfo screeninfo
validators skia-pathops
ipython
PyOpenGL
manimpango>=0.4.0.post0,<0.5.0
isosurfaces
svgelements svgelements
sympy
tqdm
validators
[options.entry_points] [options.entry_points]
console_scripts = console_scripts =