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.window import *
from manimlib.animation.animation import *
from manimlib.animation.composition import *
from manimlib.animation.creation import *
@ -20,17 +22,16 @@ from manimlib.animation.update import *
from manimlib.camera.camera import *
from manimlib.window import *
from manimlib.mobject.boolean_ops import *
from manimlib.mobject.coordinate_systems import *
from manimlib.mobject.changing import *
from manimlib.mobject.coordinate_systems import *
from manimlib.mobject.frame import *
from manimlib.mobject.functions import *
from manimlib.mobject.geometry import *
from manimlib.mobject.interactive import *
from manimlib.mobject.matrix import *
from manimlib.mobject.mobject import *
from manimlib.mobject.mobject_update_utils import *
from manimlib.mobject.number_line import *
from manimlib.mobject.numbers 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.text_mobject 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.point_cloud_mobject import *
from manimlib.mobject.types.surface 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.vector_field import *
from manimlib.scene.scene import *
from manimlib.scene.interactive_scene import *
from manimlib.scene.scene import *
from manimlib.scene.three_d_scene import *
from manimlib.utils.bezier import *
@ -62,9 +62,9 @@ from manimlib.utils.config_ops import *
from manimlib.utils.customization import *
from manimlib.utils.debug import *
from manimlib.utils.directories import *
from manimlib.utils.file_ops import *
from manimlib.utils.images import *
from manimlib.utils.iterables import *
from manimlib.utils.file_ops import *
from manimlib.utils.paths import *
from manimlib.utils.rate_functions import *
from manimlib.utils.simple_functions import *

View file

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

View file

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

View file

@ -1,9 +1,9 @@
from __future__ import annotations
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.utils.bezier import integer_interpolate
from manimlib.utils.bezier import interpolate
@ -15,8 +15,10 @@ from manimlib.utils.simple_functions import clip
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from manimlib.scene.scene import Scene
from typing import Callable
from manimlib.mobject.mobject import Mobject
from manimlib.scene.scene import Scene
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.transform import Transform
from manimlib.mobject.mobject import Group
from manimlib.constants import ORIGIN
from manimlib.mobject.mobject import Group
from manimlib.utils.bezier import interpolate
from manimlib.utils.rate_functions import there_and_back
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from manimlib.scene.scene import Scene
from manimlib.mobject.mobject import Mobject
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.scene.scene import Scene
DEFAULT_FADE_LAG_RATIO = 0

View file

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

View file

@ -1,40 +1,44 @@
from __future__ import annotations
import math
from typing import Union, Sequence
import numpy as np
from manimlib.constants import *
from manimlib.animation.animation import Animation
from manimlib.animation.movement import Homotopy
from manimlib.animation.composition import AnimationGroup
from manimlib.animation.composition import Succession
from manimlib.animation.creation import ShowCreation
from manimlib.animation.creation import ShowPartial
from manimlib.animation.fading import FadeOut
from manimlib.animation.fading import FadeIn
from manimlib.animation.movement import Homotopy
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 Dot
from manimlib.mobject.geometry import Line
from manimlib.mobject.shape_matchers import SurroundingRectangle
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.geometry import Line
from manimlib.utils.bezier import interpolate
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 squish_rate_func
from manimlib.utils.rate_functions import there_and_back
from manimlib.utils.rate_functions import wiggle
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import colour
from colour import Color
from typing import Union
from manimlib.mobject.mobject import Mobject
ManimColor = Union[str, colour.Color, Sequence[float]]
ManimColor = Union[str, Color]
class FocusOn(Transform):

View file

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

View file

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

View file

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

View file

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

View file

@ -1,15 +1,13 @@
from __future__ import annotations
import inspect
from typing import Callable, Union, Sequence
import numpy as np
import numpy.typing as npt
from manimlib.animation.animation import Animation
from manimlib.constants import DEFAULT_POINTWISE_FUNCTION_RUN_TIME
from manimlib.constants import OUT
from manimlib.constants import DEGREES
from manimlib.constants import OUT
from manimlib.mobject.mobject import Group
from manimlib.mobject.mobject import Mobject
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
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
ManimColor = Union[str, colour.Color, Sequence[float]]
ManimColor = Union[str, Color]
class Transform(Animation):

View file

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

View file

@ -1,19 +1,23 @@
from __future__ import annotations
import moderngl
from colour import Color
import OpenGL.GL as gl
import itertools as it
import math
import itertools as it
import moderngl
import numpy as np
from scipy.spatial.transform import Rotation
import OpenGL.GL as gl
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 Point
from manimlib.utils.color import color_to_rgba
from manimlib.utils.config_ops import digest_config
from manimlib.utils.simple_functions import fdiv
from manimlib.utils.space_ops import normalize
@ -189,10 +193,9 @@ class Camera(object):
def __init__(self, ctx: moderngl.Context | None = None, **kwargs):
digest_config(self, kwargs, locals())
self.rgb_max_val: float = np.iinfo(self.pixel_array_dtype).max
self.background_rgba: list[float] = [
*Color(self.background_color).get_rgb(),
self.background_opacity
]
self.background_rgba: list[float] = list(color_to_rgba(
self.background_color, self.background_opacity
))
self.init_frame()
self.init_context(ctx)
self.init_shaders()

View file

@ -1,16 +1,16 @@
import argparse
import colour
import inspect
from contextlib import contextmanager
import importlib
import inspect
import os
from screeninfo import get_monitors
import sys
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.init_config import init_customization
from manimlib.logger import log
__config_file__ = "custom_config.yml"
@ -117,16 +117,19 @@ def parse_cli():
)
parser.add_argument(
"-n", "--start_at_animation_number",
help="Start rendering not from the first animation, but"
"from another, specified by its index. If you pass"
"in two comma separated values, e.g. \"3,6\", it will end"
help="Start rendering not from the first animation, but "
"from another, specified by its index. If you pass "
"in two comma separated values, e.g. \"3,6\", it will end "
"the rendering at the second value",
)
parser.add_argument(
"-e", "--embed", metavar="LINENO",
help="Takes a line number as an argument, and results"
"in the scene being called as if the line `self.embed()`"
"was inserted into the scene code at that line number."
"-e", "--embed",
nargs="?",
const="",
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(
"-r", "--resolution",
@ -185,22 +188,70 @@ def get_module(file_name):
return module
def get_indent(line: str):
return len(line) - len(line.lstrip())
@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:
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:
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:
os.remove(alt_file)
with open(file_name, 'w') as fp:
fp.writelines(lines)
def get_custom_config():
@ -296,10 +347,10 @@ def get_configuration(args):
"quiet": args.quiet,
}
if args.embed is None:
module = get_module(args.file)
else:
with insert_embed_line(args.file, int(args.embed)) as alt_file:
module = get_module(args.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)
config = {

View file

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

View file

@ -2,8 +2,8 @@ from __future__ import annotations
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_type import EventType
class EventDispatcher(object):

View file

@ -1,10 +1,13 @@
from __future__ import annotations
from typing import Callable, TYPE_CHECKING
from typing import 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.mobject.mobject import Mobject
class EventListner(object):
def __init__(

View file

@ -1,13 +1,14 @@
import copy
import inspect
import sys
import copy
from manimlib.scene.scene import Scene
from manimlib.config import get_custom_config
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):
exec(get_custom_config()["universal_import_line"])
self.embed()

View file

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

View file

@ -1,19 +1,18 @@
from __future__ import annotations
from typing import Callable
import numpy as np
from manimlib.constants import BLUE_D
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.constants import BLUE_B, BLUE_D, BLUE_E, GREY_BROWN, WHITE
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 VMobject
from manimlib.utils.rate_functions import smooth
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable
class AnimatedBoundary(VGroup):
CONFIG = {

View file

@ -1,16 +1,20 @@
from __future__ import annotations
from abc import ABC, abstractmethod
import numbers
from abc import abstractmethod
from typing import Type, TypeVar, Union, Callable, Iterable, Sequence
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.geometry import Arrow
from manimlib.mobject.geometry import Line
from manimlib.mobject.geometry import DashedLine
from manimlib.mobject.geometry import Line
from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.number_line import NumberLine
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
if TYPE_CHECKING:
import colour
from colour import Color
from typing import Callable, Iterable, Sequence, Type, TypeVar, Union
from manimlib.mobject.mobject import Mobject
T = TypeVar("T", bound=Mobject)
ManimColor = Union[str, colour.Color, Sequence[float]]
ManimColor = Union[str, Color]
EPSILON = 1e-8
class CoordinateSystem():
class CoordinateSystem(ABC):
"""
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.utils.config_ops import digest_config

View file

@ -1,13 +1,18 @@
from __future__ import annotations
from typing import Callable, Sequence
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.utils.config_ops import digest_config
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable, Sequence
class ParametricCurve(VMobject):
CONFIG = {

View file

@ -2,23 +2,24 @@ from __future__ import annotations
import math
import numbers
from typing import Sequence, Union
import colour
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.types.vectorized_mobject import DashedVMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
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.iterables import adjacent_n_tuples
from manimlib.utils.iterables import adjacent_pairs
from manimlib.utils.simple_functions import fdiv
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_of_vector
from manimlib.utils.space_ops import compass_directions
from manimlib.utils.space_ops import find_intersection
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 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
@ -716,8 +723,8 @@ class Arrow(Line):
def set_stroke(
self,
color: ManimColor | None = None,
width: float | None = None,
color: ManimColor | Iterable[ManimColor] | None = None,
width: float | Iterable[float] | None = None,
*args, **kwargs
):
super().set_stroke(color=color, width=width, *args, **kwargs)

View file

@ -1,22 +1,32 @@
from __future__ import annotations
from typing import Callable
import numpy as np
from pyglet.window import key as PygletWindowKeys
from manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH
from manimlib.constants import LEFT, RIGHT, UP, DOWN, ORIGIN
from manimlib.constants import SMALL_BUFF, MED_SMALL_BUFF, MED_LARGE_BUFF
from manimlib.constants import BLACK, GREY_A, GREY_C, RED, GREEN, BLUE, WHITE
from manimlib.mobject.mobject import Mobject, Group
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.geometry import Dot, Line, Square, Rectangle, RoundedRectangle, Circle
from manimlib.constants import DOWN, LEFT, ORIGIN, RIGHT, UP
from manimlib.constants import MED_LARGE_BUFF, MED_SMALL_BUFF, SMALL_BUFF
from manimlib.constants import BLACK, BLUE, GREEN, GREY_A, GREY_C, RED, WHITE
from manimlib.mobject.mobject import Group
from manimlib.mobject.mobject import Mobject
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.types.vectorized_mobject import VGroup
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.space_ops import get_norm, 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_closest_point_on_line
from manimlib.utils.space_ops import get_norm
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable
# Interactive Mobjects
@ -336,7 +346,7 @@ class ColorSliders(Group):
g = self.g_slider.get_value() / 255
b = self.b_slider.get_value() / 255
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:
rgba = self.get_value()

View file

@ -1,12 +1,12 @@
from __future__ import annotations
import itertools as it
from typing import Union, Sequence
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 Integer
from manimlib.mobject.shape_matchers import BackgroundRectangle
@ -18,9 +18,14 @@ from manimlib.mobject.types.vectorized_mobject import VMobject
from typing import 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
ManimColor = Union[str, colour.Color, Sequence[float]]
ManimColor = Union[str, Color]
VECTOR_LABEL_SCALE_FACTOR = 0.8

View file

@ -1,50 +1,61 @@
from __future__ import annotations
import copy
import sys
import random
import itertools as it
from functools import wraps
from typing import Iterable, Callable, Union, Sequence
import pickle
import itertools as it
import os
import pickle
import random
import sys
import colour
import moderngl
import numbers
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_to_rgb
from manimlib.utils.color import get_colormap_list
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.iterables import batch_by_property
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_preserving_order
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 interpolate
from manimlib.utils.paths import straight_path
from manimlib.utils.simple_functions import get_parameters
from manimlib.utils.space_ops import angle_of_vector
from manimlib.utils.space_ops import get_norm
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
TimeBasedUpdater = Callable[["Mobject", float], None]
NonTimeUpdater = Callable[["Mobject"], None]
Updater = Union[TimeBasedUpdater, NonTimeUpdater]
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
TimeBasedUpdater = Callable[["Mobject", float], None]
NonTimeUpdater = Callable[["Mobject"], None]
Updater = Union[TimeBasedUpdater, NonTimeUpdater]
ManimColor = Union[str, Color]
class Mobject(object):
@ -84,7 +95,8 @@ class Mobject(object):
self.locked_data_keys: set[str] = set()
self.needs_new_bounding_box: bool = True
self._is_animating: bool = False
self._is_movable: bool = False
self.saved_state = None
self.target = None
self.init_data()
self.init_uniforms()
@ -136,8 +148,10 @@ class Mobject(object):
return self
def set_uniforms(self, uniforms: dict):
for key in uniforms:
self.uniforms[key] = uniforms[key] # Copy?
for key, value in uniforms.items():
if isinstance(value, np.ndarray):
value = value.copy()
self.uniforms[key] = value
return self
@property
@ -460,66 +474,94 @@ class Mobject(object):
self.assemble_family()
return self
# Creating new Mobjects from this one
# Copying and serialization
def replicate(self, n: int) -> Group:
return self.get_group_class()(
*(self.copy() for x in range(n))
)
def stash_mobject_pointers(func):
@wraps(func)
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):
"""
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
@stash_mobject_pointers
def serialize(self):
return pickle.dumps(self)
# Copying
def deserialize(self, data: bytes):
self.become(pickle.loads(data))
return self
def copy(self):
self.parents = []
def deepcopy(self):
try:
# Often faster than deepcopy
return pickle.loads(pickle.dumps(self))
except AttributeError:
return copy.deepcopy(self)
def deepcopy(self):
# This used to be different from copy, so is now just here for backward compatibility
return self.copy()
@stash_mobject_pointers
def copy(self, deep: bool = False):
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):
# TODO, remove now pointless use_deepcopy arg
self.target = None # Prevent exponential explosion
self.target = self.copy()
self.target = self.copy(deep=use_deepcopy)
self.target.saved_state = self.saved_state
return self.target
def save_state(self, use_deepcopy: bool = False):
# TODO, remove now pointless use_deepcopy arg
if hasattr(self, "saved_state"):
# Prevent exponential growth of data
self.saved_state = None
self.saved_state = self.copy()
self.saved_state = self.copy(deep=use_deepcopy)
self.saved_state.target = self.target
return 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")
self.become(self.saved_state)
return self
def save_to_file(self, file_path):
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
def save_to_file(self, file_path: str, supress_overwrite_warning: bool = False):
with open(file_path, "wb") as fp:
pickle.dump(self, fp)
fp.write(self.serialize())
log.info(f"Saved mobject to {file_path}")
return self
@ -532,6 +574,39 @@ class Mobject(object):
mobject = pickle.load(fp)
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
def init_updaters(self):
@ -634,21 +709,13 @@ class Mobject(object):
# Check if mark as static or not for camera
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:
for mob in self.get_family(recurse):
mob._is_animating = is_animating
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
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
respect to that point.
"""
if isinstance(scale_factor, Iterable):
scale_factor = np.array(scale_factor).clip(min=min_scale_factor)
else:
if isinstance(scale_factor, numbers.Number):
scale_factor = max(scale_factor, min_scale_factor)
else:
scale_factor = np.array(scale_factor).clip(min=min_scale_factor)
self.apply_points_function(
lambda points: scale_factor * points,
about_point=about_point,
@ -1064,8 +1131,8 @@ class Mobject(object):
def set_rgba_array_by_color(
self,
color: ManimColor | None = None,
opacity: float | None = None,
color: ManimColor | Iterable[ManimColor] | None = None,
opacity: float | Iterable[float] | None = None,
name: str = "rgbas",
recurse: bool = True
):
@ -1087,7 +1154,12 @@ class Mobject(object):
mob.data[name][:, 3] = resize_array(opacities, size)
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)
# Recurse to submobjects differently from how set_rgba_array_by_color
# in case they implement set_color differently
@ -1096,7 +1168,11 @@ class Mobject(object):
submob.set_color(color, recurse=True)
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)
if recurse:
for submob in self.submobjects:
@ -1203,7 +1279,7 @@ class Mobject(object):
bb = self.get_bounding_box()
return np.array([
[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:
@ -1519,18 +1595,6 @@ class Mobject(object):
"""
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
def lock_data(self, keys: Iterable[str]):

View file

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

View file

@ -1,8 +1,10 @@
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.numbers import DecimalNumber
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.simple_functions import fdiv
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Iterable, Sequence
class NumberLine(Line):
CONFIG = {

View file

@ -1,13 +1,18 @@
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.text_mobject import Text
from manimlib.mobject.types.vectorized_mobject import VMobject
T = TypeVar("T", bound=VMobject)
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Type, TypeVar
T = TypeVar("T", bound=VMobject)
class DecimalNumber(VMobject):

View file

@ -1,9 +1,8 @@
from __future__ import annotations
from typing import Iterable, Union, Sequence
import colour
from manimlib.constants import *
from manimlib.constants import BLUE, BLUE_E, GREEN_E, GREY_B, GREY_D, MAROON_B, YELLOW
from manimlib.constants import DOWN, LEFT, RIGHT, UP
from manimlib.constants import MED_LARGE_BUFF, MED_SMALL_BUFF, SMALL_BUFF
from manimlib.mobject.geometry import Line
from manimlib.mobject.geometry import Rectangle
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.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

View file

@ -1,20 +1,25 @@
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 Rectangle
from manimlib.mobject.types.vectorized_mobject import VGroup
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.customization import get_customization
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Union, Sequence
from typing import Union
from manimlib.mobject.mobject import Mobject
ManimColor = Union[str, Color, Sequence[float]]
ManimColor = Union[str, Color]
class SurroundingRectangle(Rectangle):

View file

@ -2,27 +2,32 @@ from __future__ import annotations
import math
import copy
from typing import Iterable
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.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 Tex
from manimlib.mobject.svg.tex_mobject import TexText
from manimlib.mobject.svg.text_mobject import Text
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.config_ops import digest_config
from manimlib.utils.iterables import listify
from manimlib.utils.space_ops import get_norm
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from manimlib.mobject.mobject import Mobject
from typing import Iterable
from manimlib.animation.animation import Animation
from manimlib.mobject.mobject import Mobject
class Brace(SingleStringTex):
CONFIG = {
@ -113,8 +118,8 @@ class BraceLabel(VMobject):
def __init__(
self,
obj: list[VMobject] | Mobject,
text: Iterable[str] | str,
obj: VMobject | list[VMobject],
text: str | Iterable[str],
brace_direction: np.ndarray = DOWN,
**kwargs
) -> None:
@ -124,12 +129,8 @@ class BraceLabel(VMobject):
obj = VMobject(*obj)
self.brace = Brace(obj, brace_direction, **kwargs)
if isinstance(text, Iterable):
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 = self.label_constructor(*listify(text), **kwargs)
self.label.scale(self.label_scale)
self.brace.put_at_tip(self.label, buff=self.label_buff)
self.set_submobjects([self.brace, self.label])
@ -137,11 +138,11 @@ class BraceLabel(VMobject):
def creation_anim(
self,
label_anim: Animation = FadeIn,
brace_anim: Animation=GrowFromCenter
brace_anim: Animation = GrowFromCenter
) -> AnimationGroup:
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):
obj = VMobject(*obj)
self.brace = Brace(obj, self.brace_direction, **kwargs)
@ -158,7 +159,7 @@ class BraceLabel(VMobject):
self.submobjects[1] = self.label
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.change_label(*text)
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.space_ops import angle_of_vector
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 rotate_vector
class Checkmark(TexText):

View file

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

View file

@ -1,21 +1,28 @@
from __future__ import annotations
from typing import Iterable, Sequence, Union
from functools import reduce
import operator as op
import colour
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.svg.svg_mobject import SVGMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
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 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

View file

@ -2,19 +2,23 @@ from __future__ import annotations
import math
from manimlib.constants import *
from manimlib.mobject.types.surface import Surface
import numpy as np
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 Surface
from manimlib.mobject.types.vectorized_mobject import VGroup
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 Square
from manimlib.utils.bezier import interpolate
from manimlib.utils.config_ops import digest_config
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 z_to_vector
from manimlib.utils.space_ops import compass_directions
class SurfaceMesh(VGroup):

View file

@ -1,15 +1,18 @@
from __future__ import annotations
import numpy as np
import numpy.typing as npt
import moderngl
import numpy as np
from manimlib.constants import GREY_C
from manimlib.constants import YELLOW
from manimlib.constants import GREY_C, YELLOW
from manimlib.constants import ORIGIN
from manimlib.mobject.types.point_cloud_mobject import PMobject
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_GLOW_DOT_RADIUS = 0.2

View file

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

View file

@ -1,19 +1,22 @@
from __future__ import annotations
from typing import Callable, Sequence, Union
import colour
import numpy.typing as npt
from manimlib.constants import *
from manimlib.constants import BLACK
from manimlib.constants import ORIGIN
from manimlib.mobject.mobject import Mobject
from manimlib.utils.color import color_gradient
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_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):

View file

@ -1,12 +1,10 @@
from __future__ import annotations
from typing import Iterable, Callable
import moderngl
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.utils.bezier import integer_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
if TYPE_CHECKING:
from typing import Callable, Iterable
import numpy.typing as npt
from manimlib.camera.camera import Camera

View file

@ -1,30 +1,36 @@
from __future__ import annotations
import operator as op
from functools import reduce
from functools import wraps
import itertools as it
from functools import reduce, wraps
from typing import Iterable, Sequence, Callable, Union
import operator as op
import colour
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 Point
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_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 inverse_interpolate
from manimlib.utils.bezier import integer_interpolate
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.iterables import listify
from manimlib.utils.iterables import make_even
from manimlib.utils.iterables import resize_array
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 cross2d
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.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):
@ -130,8 +143,8 @@ class VMobject(Mobject):
def set_fill(
self,
color: ManimColor | None = None,
opacity: float | None = None,
color: ManimColor | Iterable[ManimColor] | None = None,
opacity: float | Iterable[float] | None = None,
recurse: bool = True
):
self.set_rgba_array_by_color(color, opacity, 'fill_rgba', recurse)
@ -139,9 +152,9 @@ class VMobject(Mobject):
def set_stroke(
self,
color: ManimColor | None = None,
width: float | npt.ArrayLike | None = None,
opacity: float | None = None,
color: ManimColor | Iterable[ManimColor] | None = None,
width: float | Iterable[float] | None = None,
opacity: float | Iterable[float] | None = None,
background: bool | None = None,
recurse: bool = True
):
@ -162,8 +175,8 @@ class VMobject(Mobject):
def set_backstroke(
self,
color: ManimColor = BLACK,
width: float | npt.ArrayLike = 3,
color: ManimColor | Iterable[ManimColor] = BLACK,
width: float | Iterable[float] = 3,
background: bool = True
):
self.set_stroke(color, width, background=background)
@ -177,13 +190,13 @@ class VMobject(Mobject):
def set_style(
self,
fill_color: ManimColor | None = None,
fill_opacity: float | None = None,
fill_color: ManimColor | Iterable[ManimColor] | None = None,
fill_opacity: float | Iterable[float] | None = None,
fill_rgba: npt.ArrayLike | None = None,
stroke_color: ManimColor | None = None,
stroke_opacity: float | None = None,
stroke_color: ManimColor | Iterable[ManimColor] | None = None,
stroke_opacity: float | Iterable[float] | 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,
reflectiveness: float | None = None,
gloss: float | None = None,
@ -247,12 +260,21 @@ class VMobject(Mobject):
sm1.match_style(sm2)
return self
def set_color(self, color: ManimColor, recurse: bool = True):
self.set_fill(color, recurse=recurse)
self.set_stroke(color, recurse=recurse)
def set_color(
self,
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
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_stroke(opacity=opacity, recurse=recurse)
return self
@ -1174,3 +1196,24 @@ class DashedVMobject(VMobject):
# Family is already taken care of by get_subcurve
# implementation
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
import itertools as it
import random
from typing import Sequence, TypeVar, Callable, Iterable
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.indication import VShowPassingFlash
from manimlib.mobject.geometry import Arrow
from manimlib.mobject.types.vectorized_mobject import VGroup
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 inverse_interpolate
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 merge_dicts_recursively
from manimlib.utils.rate_functions import linear
from manimlib.utils.simple_functions import sigmoid
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
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.mobject import Mobject
T = TypeVar("T")
@ -299,7 +302,7 @@ class AnimatedStreamLines(VGroup):
**self.line_anim_config,
)
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_updater(lambda m, dt: m.update(dt))

View file

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

View file

@ -1,41 +1,43 @@
import numpy as np
import itertools as it
import numpy as np
import pyperclip
import os
import platform
from manimlib.animation.fading import FadeIn
from manimlib.constants import MANIM_COLORS, WHITE, YELLOW
from manimlib.constants import ORIGIN, UP, DOWN, LEFT, RIGHT, DL, UL, UR, DR
from manimlib.constants import ARROW_SYMBOLS, CTRL_SYMBOL, DELETE_SYMBOL, SHIFT_SYMBOL
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 SHIFT_SYMBOL, DELETE_SYMBOL, ARROW_SYMBOLS
from manimlib.constants import SHIFT_MODIFIER, COMMAND_MODIFIER
from manimlib.mobject.mobject import Mobject
from manimlib.constants import MANIM_COLORS, WHITE, GREY_C
from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.geometry import Square
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.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.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.utils.tex_file_writing import LatexError
from manimlib.utils.family_ops import extract_mobject_family_members
from manimlib.utils.space_ops import get_norm
from manimlib.logger import log
from manimlib.utils.tex_file_writing import LatexError
SELECT_KEY = 's'
GRAB_KEY = 'g'
HORIZONTAL_GRAB_KEY = 'h'
VERTICAL_GRAB_KEY = 'v'
X_GRAB_KEY = 'h'
Y_GRAB_KEY = 'v'
GRAB_KEYS = [GRAB_KEY, X_GRAB_KEY, Y_GRAB_KEY]
RESIZE_KEY = 't'
COLOR_KEY = 'c'
CURSOR_LOCATION_KEY = 'l'
# Note, a lot of the functionality here is still buggy and very much a work in progress.
class InteractiveScene(Scene):
"""
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
colors = MANIM_COLORS
selection_nudge_size = 0.05
cursor_location_config = dict(
font_size=14,
fill_color=GREY_C,
num_decimal_places=3,
)
def setup(self):
self.selection = Group()
self.selection_highlight = Group()
self.selection_rectangle = self.get_selection_rectangle()
self.color_palette = self.get_color_palette()
self.cursor_location_label = self.get_cursor_location_label()
self.unselectables = [
self.selection,
self.selection_highlight,
self.selection_rectangle,
self.cursor_location_label,
self.camera.frame
]
self.saved_selection_state = []
self.select_top_level_mobs = True
self.regenerate_selection_search_set()
self.is_selecting = False
self.is_grabbing = False
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):
self.select_top_level_mobs = not self.select_top_level_mobs
self.refresh_selection_scope()
self.regenerate_selection_search_set()
def get_selection_search_set(self):
mobs = [m for m in self.mobjects if m not in self.unselectables]
def get_selection_search_set(self) -> list[Mobject]:
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:
return mobs
self.selection_search_set = selectable
else:
return [
self.selection_search_set = [
submob
for mob in mobs
for mob in selectable
for submob in mob.family_members_with_points()
]
@ -116,37 +184,7 @@ class InteractiveScene(Scene):
)
self.refresh_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 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):
def get_corner_dots(self, mobject: Mobject) -> Mobject:
dots = DotCloud(**self.corner_dot_config)
radius = self.corner_dot_config["radius"]
if mobject.get_depth() < 1e-2:
@ -159,9 +197,11 @@ class InteractiveScene(Scene):
]))
return dots
def get_highlight(self, mobject):
if isinstance(mobject, VMobject) and mobject.has_points():
return self.get_stroke_highlight(mobject)
def get_highlight(self, mobject: Mobject) -> Mobject:
if isinstance(mobject, VMobject) and mobject.has_points() and not self.select_top_level_mobs:
result = VHighlight(mobject)
result.add_updater(lambda m: m.replace(mobject))
return result
else:
return self.get_corner_dots(mobject)
@ -171,40 +211,54 @@ class InteractiveScene(Scene):
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):
mobs = list(filter(lambda m: m not in self.unselectables, mobjects))
self.selection.add(*mobjects)
mobs = list(filter(
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.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):
for mob in mobjects:
if mob in self.selection:
self.selection.remove(mob)
mob.set_animating_status(False)
else:
self.add_to_selection(mob)
self.refresh_selection_highlight()
def clear_selection(self):
for mob in self.selection:
mob.set_animating_status(False)
self.selection.set_submobjects([])
self.selection_highlight.set_submobjects([])
self.refresh_static_mobjects()
def add(self, *new_mobjects: Mobject):
for mob in new_mobjects:
mob.make_movable()
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):
ids = map(id, self.selection)
@ -218,11 +272,11 @@ class InteractiveScene(Scene):
mobs = map(self.id_to_mobject, ids)
mob_copies = [m.copy() for m in mobs if m is not None]
self.clear_selection()
self.add_to_selection(*mob_copies)
self.play(*(
FadeIn(mc, run_time=0.5, scale=1.5)
for mc in mob_copies
))
self.add_to_selection(*mob_copies)
return
except ValueError:
pass
@ -242,41 +296,27 @@ class InteractiveScene(Scene):
self.remove(*self.selection)
self.clear_selection()
def saved_selection_to_file(self):
directory = self.file_writer.get_saved_mobject_directory()
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)
def restore_state(self, mobject_states: list[tuple[Mobject, Mobject]]):
super().restore_state(mobject_states)
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):
center = self.selection.get_center()
mp = self.mouse_point.get_center()
@ -286,136 +326,173 @@ class InteractiveScene(Scene):
self.scale_about_point = center
self.scale_ref_vect = mp - self.scale_about_point
self.scale_ref_width = self.selection.get_width()
self.scale_ref_height = self.selection.get_height()
# Event handlers
def toggle_color_palette(self):
if len(self.selection) == 0:
return
if self.color_palette not in self.mobjects:
self.save_state()
self.add(self.color_palette)
else:
self.remove(self.color_palette)
def group_selection(self):
group = self.get_group(*self.selection)
self.add(group)
self.clear_selection()
self.add_to_selection(group)
def ungroup_selection(self):
pieces = []
for mob in list(self.selection):
self.remove(mob)
pieces.extend(list(mob))
self.clear_selection()
self.add(*pieces)
self.add_to_selection(*pieces)
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)
# 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
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)
# Show color palette
elif char == COLOR_KEY and modifiers == 0:
if len(self.selection) == 0:
return
if self.color_palette not in self.mobjects:
self.add(self.color_palette)
else:
self.remove(self.color_palette)
# Command + c -> Copy mobject ids to clipboard
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()
# 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)
self.add(group)
self.clear_selection()
self.add_to_selection(group)
# Command + shift + g -> Ungroup the selection
self.group_selection()
elif char == "g" and modifiers == COMMAND_MODIFIER | SHIFT_MODIFIER:
pieces = []
for mob in list(self.selection):
self.remove(mob)
pieces.extend(list(mob))
self.clear_selection()
self.add(*pieces)
self.add_to_selection(*pieces)
# Command + t -> Toggle selection mode
self.ungroup_selection()
elif char == "t" and modifiers == COMMAND_MODIFIER:
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:
self.saved_selection_to_file()
# Keyboard movements
self.save_selection_to_file()
elif symbol in ARROW_SYMBOLS:
nudge = self.selection_nudge_size
if (modifiers & SHIFT_MODIFIER):
nudge *= 10
vect = [LEFT, UP, RIGHT, DOWN][ARROW_SYMBOLS.index(symbol)]
self.selection.shift(nudge * vect)
self.nudge_selection(
vect=[LEFT, UP, RIGHT, DOWN][ARROW_SYMBOLS.index(symbol)],
large=(modifiers & SHIFT_MODIFIER),
)
# 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:
super().on_key_release(symbol, modifiers)
if chr(symbol) == SELECT_KEY:
self.is_selecting = False
self.remove(self.selection_rectangle)
for mob in reversed(self.get_selection_search_set()):
if mob.is_movable() and self.selection_rectangle.is_touching(mob):
self.add_to_selection(mob)
self.gather_new_selection()
if chr(symbol) in GRAB_KEYS:
self.is_grabbing = False
elif chr(symbol) == CURSOR_LOCATION_KEY:
self.remove(self.cursor_location_label)
elif symbol == SHIFT_SYMBOL and self.window.is_key_pressed(ord(RESIZE_KEY)):
self.prepare_resizing(about_corner=False)
elif symbol == SHIFT_SYMBOL:
if self.window.is_key_pressed(ord(RESIZE_KEY)):
self.prepare_resizing(about_corner=False)
# Mouse actions
def handle_grabbing(self, point: np.ndarray):
diff = point - self.mouse_to_selection
if self.window.is_key_pressed(ord(GRAB_KEY)):
self.selection.move_to(diff)
elif self.window.is_key_pressed(ord(X_GRAB_KEY)):
self.selection.set_x(diff[0])
elif self.window.is_key_pressed(ord(Y_GRAB_KEY)):
self.selection.set_y(diff[1])
def on_mouse_motion(self, point: np.ndarray, d_point: np.ndarray) -> None:
super().on_mouse_motion(point, d_point)
# Move selection
if self.window.is_key_pressed(ord("g")):
self.selection.move_to(point - self.mouse_to_selection)
# Move selection restricted to horizontal
elif self.window.is_key_pressed(ord("h")):
self.selection.set_x((point - self.mouse_to_selection)[0])
# Move selection restricted to vertical
elif self.window.is_key_pressed(ord("v")):
self.selection.set_y((point - self.mouse_to_selection)[1])
# Scale selection
elif self.window.is_key_pressed(ord("t")):
# TODO, allow for scaling about the opposite corner
vect = point - self.scale_about_point
def handle_resizing(self, point: np.ndarray):
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)
self.selection.set_width(
scalar * self.scale_ref_width,
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:
super().on_mouse_release(point, button, mods)
if self.color_palette in self.mobjects:
# Search through all mobject on the screne, not just the palette
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)
self.choose_color(point)
elif self.window.is_key_pressed(SHIFT_SYMBOL):
mob = self.point_to_mobject(point)
if mob is not None:
self.toggle_from_selection(mob)
self.toggle_clicked_mobject_from_selection(point)
else:
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 Transform
from manimlib.animation.update import UpdateFromFunc
from manimlib.constants import *
from manimlib.scene.scene import Scene
from manimlib.constants import DOWN, RIGHT
from manimlib.constants import MED_LARGE_BUFF, SMALL_BUFF
from manimlib.mobject.probability import SampleSpace
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.scene.scene import Scene
class SampleSpaceScene(Scene):

View file

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

View file

@ -1,30 +1,32 @@
from __future__ import annotations
import os
import sys
import shutil
import platform
import shutil
import subprocess as sp
import sys
import numpy as np
from pydub import AudioSegment
from tqdm import tqdm as ProgressDisplay
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.file_ops import guarantee_existence
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 guarantee_existence
from manimlib.utils.sounds import get_full_sound_file_path
from manimlib.logger import log
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from manimlib.scene.scene import Scene
from manimlib.camera.camera import Camera
from PIL.Image import Image
from manimlib.camera.camera import Camera
from manimlib.scene.scene import Scene
class SceneFileWriter(object):
CONFIG = {
@ -60,7 +62,7 @@ class SceneFileWriter(object):
# Output directories and files
def init_output_directories(self) -> None:
out_dir = self.output_directory
out_dir = self.output_directory or ""
if self.mirror_module_path:
module_dir = self.get_default_module_directory()
out_dir = os.path.join(out_dir, module_dir)
@ -127,6 +129,36 @@ class SceneFileWriter(object):
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
def init_audio(self) -> None:
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 ApplyPointwiseFunction
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 NumberPlane
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 get_norm
X_COLOR = GREEN_C
Y_COLOR = RED_C
Z_COLOR = BLUE_D

View file

@ -1,9 +1,8 @@
from __future__ import annotations
import copy
import os
import re
import copy
from typing import Iterable
import moderngl
import numpy as np
@ -11,6 +10,12 @@ import numpy as np
from manimlib.utils.directories import get_shader_dir
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
# the same shader will be organized and
# clumped together based on keeping track

View file

@ -1,19 +1,26 @@
from __future__ import annotations
from typing import Iterable, Callable, TypeVar, Sequence
from scipy import linalg
import numpy as np
import numpy.typing as npt
from scipy import linalg
from manimlib.utils.simple_functions import choose
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
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
T = TypeVar("T")
CLOSED_THRESHOLD = 0.001
T = TypeVar("T")
def bezier(
points: Iterable[float | np.ndarray]

View file

@ -1,15 +1,24 @@
import random
from __future__ import annotations
from colour import Color
from colour import hex2rgb
from colour import rgb2hex
import numpy as np
from manimlib.constants import WHITE
from manimlib.constants import COLORMAP_3B1B
from manimlib.constants import WHITE
from manimlib.utils.bezier import interpolate
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):
return hex_to_rgb(color)
elif isinstance(color, Color):
@ -18,55 +27,48 @@ def color_to_rgb(color):
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])
def rgb_to_color(rgb):
def rgb_to_color(rgb: Iterable[float]) -> Color:
try:
return Color(rgb=rgb)
return Color(rgb=tuple(rgb))
except ValueError:
return Color(WHITE)
def rgba_to_color(rgba):
return rgb_to_color(rgba[:3])
def rgba_to_color(rgba: Iterable[float]) -> Color:
return rgb_to_color(tuple(rgba)[:3])
def rgb_to_hex(rgb):
return "#" + "".join(
hex(int_x // 16)[2] + hex(int_x % 16)[2]
for x in rgb
for int_x in [int(255 * x)]
)
def rgb_to_hex(rgb: Iterable[float]) -> str:
return rgb2hex(rgb, force_long=True).upper()
def hex_to_rgb(hex_code):
hex_part = hex_code[1:]
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 hex_to_rgb(hex_code: str) -> np.ndarray:
return np.array(hex2rgb(hex_code))
def invert_color(color):
def invert_color(color: ManimColor) -> 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')
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)
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:
return reference_colors[0]
return []
rgbs = list(map(color_to_rgb, reference_colors))
alphas = np.linspace(0, (len(rgbs) - 1), length_of_output)
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)
return rgb_to_color(rgb)
def average_color(*colors):
def average_color(*colors: ManimColor) -> Color:
rgbs = np.array(list(map(color_to_rgb, colors)))
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()
curr_rgb = color_to_rgb(color)
new_rgb = interpolate(
curr_rgb, np.ones(len(curr_rgb)), 0.5
)
return Color(rgb=new_rgb)
return average_color(color, Color(WHITE))
def random_color():
return Color(rgb=(random.random() for i in range(3)))
def get_colormap_list(map_name="viridis", n_colors=9):
def get_colormap_list(
map_name: str = "viridis",
n_colors: int = 9
) -> np.ndarray:
"""
Options for map_name:
3b1b_colormap

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,15 @@
from __future__ import annotations
import os
from typing import Iterable
import numpy as np
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:
# This could conceivably be smarter about handling existing differing extensions

View file

@ -1,10 +1,16 @@
from __future__ import annotations
import numpy as np
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_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:

View file

@ -1,16 +1,21 @@
from __future__ import annotations
import importlib
import inspect
import os
import yaml
import inspect
import importlib
from typing import Any
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.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:

View file

@ -1,5 +1,6 @@
from __future__ import annotations
import math
from typing import Callable
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 rotation_matrix_transpose
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Callable
STRAIGHT_PATH_THRESHOLD = 0.01

View file

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

View file

@ -1,7 +1,8 @@
import inspect
import numpy as np
import math
from functools import lru_cache
import inspect
import math
import numpy as np
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.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(
sound_file_name,
directories=[get_sound_dir()],

View file

@ -1,25 +1,27 @@
from __future__ import annotations
from functools import reduce
import math
import operator as op
from functools import reduce
from typing import Callable, Iterable, Sequence
import platform
import numpy as np
import numpy.typing as npt
from mapbox_earcut import triangulate_float32 as earcut
import numpy as np
from scipy.spatial.transform import Rotation
from tqdm import tqdm as ProgressDisplay
from manimlib.constants import RIGHT
from manimlib.constants import DOWN
from manimlib.constants import OUT
from manimlib.constants import PI
from manimlib.constants import TAU
from manimlib.constants import DOWN, OUT, RIGHT
from manimlib.constants import PI, TAU
from manimlib.utils.iterables import adjacent_pairs
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]:
return [

View file

@ -1,18 +1,20 @@
import sys
import os
import hashlib
from contextlib import contextmanager
from __future__ import annotations
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_manim_dir
from manimlib.logger import log
from manimlib.utils.directories import get_tex_dir
SAVED_TEX_CONFIG = {}
def get_tex_config():
def get_tex_config() -> dict[str, str]:
"""
Returns a dict which should look something like this:
{
@ -37,13 +39,13 @@ def get_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
hasher = hashlib.sha256(tex_file_content.encode())
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(
get_tex_dir(), tex_hash(tex_file_content) + ".svg"
)
@ -53,7 +55,7 @@ def tex_to_svg_file(tex_file_content):
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")
with open(tex_file, "w", encoding="utf-8") as outfile:
outfile.write(tex_file_content)
@ -69,7 +71,7 @@ def tex_to_svg(tex_file_content, svg_file):
return svg_file
def tex_to_dvi(tex_file):
def tex_to_dvi(tex_file: str) -> str:
tex_config = get_tex_config()
program = tex_config["executable"]
file_type = tex_config["intermediate_filetype"]
@ -96,7 +98,7 @@ def tex_to_dvi(tex_file):
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
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
@contextmanager
def display_during_execution(message):
def display_during_execution(message: str) -> None:
# Only show top line
to_print = message.split("\n")[0]
max_characters = os.get_terminal_size().columns - 1

View file

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

View file

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

View file

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