Merge branch 'main' into guide

This commit is contained in:
adeshpande 2024-03-31 14:00:26 -04:00 committed by GitHub
commit 978b3e71b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 859 additions and 950 deletions

View file

@ -34,7 +34,7 @@ jobs:
poetry config virtualenvs.prefer-active-python true
- name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
cache: "poetry"
@ -143,7 +143,7 @@ jobs:
$tinyTexPackages = $(python -c "import json;print(' '.join(json.load(open('.github/manimdependency.json'))['windows']['tinytex']))") -Split ' '
$OriPath = $env:PATH
echo "Install Tinytex"
Invoke-WebRequest "https://github.com/yihui/tinytex-releases/releases/download/daily/TinyTeX-1.zip" -O "$($env:TMP)\TinyTex.zip"
Invoke-WebRequest "https://github.com/yihui/tinytex-releases/releases/download/daily/TinyTeX-1.zip" -OutFile "$($env:TMP)\TinyTex.zip"
Expand-Archive -LiteralPath "$($env:TMP)\TinyTex.zip" -DestinationPath "$($PWD)\ManimCache\LatexWindows"
$env:Path = "$($PWD)\ManimCache\LatexWindows\TinyTeX\bin\windows;$($env:PATH)"
tlmgr update --self

View file

@ -11,7 +11,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.11
@ -30,7 +30,7 @@ jobs:
poetry build
- name: Store artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
path: dist/*.tar.gz
name: manim.tar.gz

View file

@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.11
@ -40,7 +40,7 @@ jobs:
tar -czvf ../html-docs.tar.gz *
- name: Store artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
path: ${{ github.workspace }}/docs/build/html-docs.tar.gz
name: html-docs.tar.gz

View file

@ -115,8 +115,8 @@ Typing guidelines
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from manim.typing import Vector3
# type stuff with Vector3
from manim.typing import Vector3D
# type stuff with Vector3D
Missing Sections for typehints are:
-----------------------------------

View file

@ -94,7 +94,7 @@ Basic Concepts
self.add(image, image.background_rectangle)
.. manim:: BooleanOperations
:ref_classes: Union Intersection Exclusion
:ref_classes: Union Intersection Exclusion Difference
class BooleanOperations(Scene):
def construct(self):

View file

@ -45,7 +45,7 @@ modify to your liking. First, run
.. code-block:: sh
docker run -it --name my-manim-container -v "/full/path/to/your/directory:/manim" manimcommunity/manim /bin/bash
docker run -it --name my-manim-container -v "/full/path/to/your/directory:/manim" manimcommunity/manim bash
to obtain an interactive shell inside your container allowing you

View file

@ -71,7 +71,7 @@ then execute it.
texlive-latex-recommended texlive-science \
tipa libpango1.0-dev
!pip install manim
!pip install IPython --upgrade
!pip install IPython==8.21.0
You should start to see Colab installing all the dependencies specified
in these commands. After the execution has completed, you will be prompted

View file

@ -99,40 +99,18 @@ directory structure, build system, and naming are completely up to your
discretion as an author. The aforementioned template plugin is only a model
using Poetry since this is the build system Manim uses. The plugin's `entry
point <https://packaging.python.org/specifications/entry-points/>`_ can be
specified in poetry as:
specified in Poetry as:
.. code-block:: toml
[tool.poetry.plugins."manim.plugins"]
"name" = "object_reference"
Here ``name`` is the name of the module of the plugin.
.. versionremoved:: 0.19.0
Here ``object_reference`` can point to either a function in a module or a module
itself. For example,
.. code-block:: toml
[tool.poetry.plugins."manim.plugins"]
"manim_plugintemplate" = "manim_plugintemplate"
Here a module is used as ``object_reference``, and when this plugin is enabled,
Manim will look for ``__all__`` keyword defined in ``manim_plugintemplate`` and
everything as a global variable one by one.
If ``object_reference`` is a function, Manim calls the function and expects the
function to return a list of modules or functions that need to be defined globally.
For example,
.. code-block:: toml
[tool.poetry.plugins."manim.plugins"]
"manim_plugintemplate" = "manim_awesomeplugin.imports:setup_things"
Here, Manim will call the function ``setup_things`` defined in
``manim_awesomeplugin.imports`` and calls that. It returns a list of function or
modules which will be imported globally.
Plugins should be imported explicitly to be usable in user code. The plugin
system will probably be refactored in the future to provide a more structured
interface.
A note on Renderer Compatibility
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -148,7 +148,7 @@ class Indicate(Transform):
def __init__(
self,
mobject: "Mobject",
mobject: Mobject,
scale_factor: float = 1.2,
color: str = YELLOW,
rate_func: Callable[[float, Optional[float]], np.ndarray] = there_and_back,
@ -158,7 +158,7 @@ class Indicate(Transform):
self.scale_factor = scale_factor
super().__init__(mobject, rate_func=rate_func, **kwargs)
def create_target(self) -> "Mobject":
def create_target(self) -> Mobject:
target = self.mobject.copy()
target.scale(self.scale_factor)
target.set_color(self.color)
@ -348,7 +348,7 @@ class ShowPassingFlashWithThinningStrokeWidth(AnimationGroup):
message="Use Create then FadeOut to achieve this effect.",
)
class ShowCreationThenFadeOut(Succession):
def __init__(self, mobject: "Mobject", remover: bool = True, **kwargs) -> None:
def __init__(self, mobject: Mobject, remover: bool = True, **kwargs) -> None:
super().__init__(Create(mobject), FadeOut(mobject), remover=remover, **kwargs)
@ -397,7 +397,7 @@ class ApplyWave(Homotopy):
def __init__(
self,
mobject: "Mobject",
mobject: Mobject,
direction: np.ndarray = UP,
amplitude: float = 0.2,
wave_func: Callable[[float], float] = smooth,
@ -516,7 +516,7 @@ class Wiggle(Animation):
def __init__(
self,
mobject: "Mobject",
mobject: Mobject,
scale_value: float = 1.1,
rotation_angle: float = 0.01 * TAU,
n_wiggles: int = 6,
@ -544,8 +544,8 @@ class Wiggle(Animation):
def interpolate_submobject(
self,
submobject: "Mobject",
starting_submobject: "Mobject",
submobject: Mobject,
starting_submobject: Mobject,
alpha: float,
) -> None:
submobject.points[:, :] = starting_submobject.points

View file

@ -59,7 +59,13 @@ render_options = option_group(
type=Choice(["png", "gif", "mp4", "webm", "mov"], case_sensitive=False),
default=None,
),
option("-s", "--save_last_frame", is_flag=True, default=None),
option(
"-s",
"--save_last_frame",
default=None,
is_flag=True,
help="Render and save only the last frame of a scene as a PNG image.",
),
option(
"-q",
"--quality",
@ -123,13 +129,6 @@ render_options = option_group(
is_flag=True,
help="Save section videos in addition to movie file.",
),
option(
"-s",
"--save_last_frame",
default=None,
is_flag=True,
help="Save last frame as png (Deprecated).",
),
option(
"-t",
"--transparent",

View file

@ -389,7 +389,7 @@ class Arc(TipableVMobject):
# For a1 and a2 to lie at the same point arc radius
# must be zero. Thus arc_center will also lie at
# that point.
return a1
return np.copy(a1)
# Tangent vectors
t1 = h1 - a1
t2 = h2 - a2

View file

@ -444,8 +444,21 @@ class Text(SVGMobject):
**kwargs,
) -> None:
self.line_spacing = line_spacing
if font and warn_missing_font and font not in Text.font_list():
logger.warning(f"Font {font} not in {Text.font_list()}.")
if font and warn_missing_font:
fonts_list = Text.font_list()
# handle special case of sans/sans-serif
if font.lower() == "sans-serif":
font = "sans"
if font not in fonts_list:
# check if the capitalized version is in the supported fonts
if font.capitalize() in fonts_list:
font = font.capitalize()
elif font.lower() in fonts_list:
font = font.lower()
elif font.title() in fonts_list:
font = font.title()
else:
logger.warning(f"Font {font} not in {fonts_list}.")
self.font = font
self._font_size = float(font_size)
# needs to be a float or else size is inflated when font_size = 24
@ -1169,8 +1182,21 @@ class MarkupText(SVGMobject):
) -> None:
self.text = text
self.line_spacing = line_spacing
if font and warn_missing_font and font not in Text.font_list():
logger.warning(f"Font {font} not in {Text.font_list()}.")
if font and warn_missing_font:
fonts_list = Text.font_list()
# handle special case of sans/sans-serif
if font.lower() == "sans-serif":
font = "sans"
if font not in fonts_list:
# check if the capitalized version is in the supported fonts
if font.capitalize() in fonts_list:
font = font.capitalize()
elif font.lower() in fonts_list:
font = font.lower()
elif font.title() in fonts_list:
font = font.title()
else:
logger.warning(f"Font {font} not in {fonts_list}.")
self.font = font
self._font_size = float(font_size)
self.slant = slant

View file

@ -45,7 +45,7 @@ class ValueTracker(Mobject, metaclass=ConvertToOpenGL):
self.wait(1)
tracker -= 4
self.wait(0.5)
self.play(tracker.animate.set_value(5)),
self.play(tracker.animate.set_value(5))
self.wait(0.5)
self.play(tracker.animate.set_value(3))
self.play(tracker.animate.increment_value(-2))

View file

@ -1,3 +1,17 @@
from __future__ import annotations
from .import_plugins import *
from manim import config, logger
from .plugins_flags import get_plugins, list_plugins
__all__ = [
"get_plugins",
"list_plugins",
]
requested_plugins: set[str] = set(config["plugins"])
missing_plugins = requested_plugins - set(get_plugins().keys())
if missing_plugins:
logger.warning("Missing Plugins: %s", missing_plugins)

View file

@ -1,43 +0,0 @@
from __future__ import annotations
import types
import pkg_resources
from .. import config, logger
__all__ = []
plugins_requested: list[str] = config["plugins"]
if "" in plugins_requested:
plugins_requested.remove("")
for plugin in pkg_resources.iter_entry_points("manim.plugins"):
if plugin.name not in plugins_requested:
continue
loaded_plugin = plugin.load()
if isinstance(loaded_plugin, types.ModuleType):
# it is a module so it can't be called
# see if __all__ is defined
# if it is defined use that to load all the modules necessary
# essentially this would be similar to `from plugin import *``
# if not just import the module with the plugin name
if hasattr(loaded_plugin, "__all__"):
for thing in loaded_plugin.__all__: # type: ignore
exec(f"{thing}=loaded_plugin.{thing}")
__all__.append(thing)
else:
exec(f"{plugin.name}=loaded_plugin")
__all__.append(plugin.name)
elif isinstance(loaded_plugin, types.FunctionType):
# call the function first
# it will return a list of modules to add globally
# finally add it
lists = loaded_plugin()
for lst in lists:
exec(f"{lst.__name__}=lst")
__all__.append(lst.__name__)
plugins_requested.remove(plugin.name)
if plugins_requested != []:
logger.warning("Missing Plugins: %s", plugins_requested)

View file

@ -2,22 +2,28 @@
from __future__ import annotations
import pkg_resources
import sys
from typing import Any
if sys.version_info < (3, 10):
from importlib_metadata import entry_points
else:
from importlib.metadata import entry_points
from manim import console
__all__ = ["list_plugins"]
def get_plugins():
plugins = {
def get_plugins() -> dict[str, Any]:
plugins: dict[str, Any] = {
entry_point.name: entry_point.load()
for entry_point in pkg_resources.iter_entry_points("manim.plugins")
for entry_point in entry_points(group="manim.plugins")
}
return plugins
def list_plugins():
def list_plugins() -> None:
console.print("[green bold]Plugins:[/green bold]", justify="left")
plugins = get_plugins()

View file

@ -725,6 +725,8 @@ class SceneFileWriter:
def write_subcaption_file(self):
"""Writes the subcaption file."""
if config.output_file is None:
return
subcaption_file = Path(config.output_file).with_suffix(".srt")
subcaption_file.write_text(srt.compose(self.subcaptions), encoding="utf-8")
logger.info(f"Subcaption file has been written as {subcaption_file}")

View file

@ -21,7 +21,7 @@
from __future__ import annotations
from os import PathLike
from typing import Callable, Literal, Union
from typing import Callable, Union
import numpy as np
import numpy.typing as npt
@ -257,9 +257,9 @@ parameter can handle being passed a `Point3D` instead.
"""
InternalPoint2D_Array: TypeAlias = npt.NDArray[PointDType]
"""``shape: (N, 3)``
"""``shape: (N, 2)``
An array of `Point2D` objects: ``[[float, float], ...]``.
An array of `InternalPoint2D` objects: ``[[float, float], ...]``.
.. note::
This type alias is mostly made available for internal use, and
@ -319,7 +319,7 @@ further type information.
Vector types
"""
Vector2D: TypeAlias = Point2D
Vector2D: TypeAlias = npt.NDArray[PointDType]
"""``shape: (2,)``
A 2-dimensional vector: ``[float, float]``.
@ -332,7 +332,7 @@ parameter can handle being passed a `Vector3D` instead.
VMobjects!
"""
Vector2D_Array: TypeAlias = Point2D_Array
Vector2D_Array: TypeAlias = npt.NDArray[PointDType]
"""``shape: (M, 2)``
An array of `Vector2D` objects: ``[[float, float], ...]``.
@ -341,7 +341,7 @@ Normally, a function or method which expects a `Vector2D_Array` as a
parameter can handle being passed a `Vector3D_Array` instead.
"""
Vector3D: TypeAlias = Point3D
Vector3D: TypeAlias = npt.NDArray[PointDType]
"""``shape: (3,)``
A 3-dimensional vector: ``[float, float, float]``.
@ -351,13 +351,13 @@ A 3-dimensional vector: ``[float, float, float]``.
VMobjects!
"""
Vector3D_Array: TypeAlias = Point3D_Array
Vector3D_Array: TypeAlias = npt.NDArray[PointDType]
"""``shape: (M, 3)``
An array of `Vector3D` objects: ``[[float, float, float], ...]``.
"""
VectorND: TypeAlias = Union[npt.NDArray[PointDType], tuple[float, ...]]
VectorND: TypeAlias = npt.NDArray[PointDType]
"""``shape (N,)``
An :math:`N`-dimensional vector: ``[float, ...]``.
@ -368,19 +368,19 @@ An :math:`N`-dimensional vector: ``[float, ...]``.
collisions.
"""
VectorND_Array: TypeAlias = Union[npt.NDArray[PointDType], tuple[VectorND, ...]]
VectorND_Array: TypeAlias = npt.NDArray[PointDType]
"""``shape (M, N)``
An array of `VectorND` objects: ``[[float, ...], ...]``.
"""
RowVector: TypeAlias = Union[npt.NDArray[PointDType], tuple[tuple[float, ...]]]
RowVector: TypeAlias = npt.NDArray[PointDType]
"""``shape: (1, N)``
A row vector: ``[[float, ...]]``.
"""
ColVector: TypeAlias = Union[npt.NDArray[PointDType], tuple[tuple[float], ...]]
ColVector: TypeAlias = npt.NDArray[PointDType]
"""``shape: (N, 1)``
A column vector: ``[[float], [float], ...]``.
@ -392,13 +392,13 @@ A column vector: ``[[float], [float], ...]``.
Matrix types
"""
MatrixMN: TypeAlias = Union[npt.NDArray[PointDType], tuple[tuple[float, ...], ...]]
MatrixMN: TypeAlias = npt.NDArray[PointDType]
"""``shape: (M, N)``
A matrix: ``[[float, ...], [float, ...], ...]``.
"""
Zeros: TypeAlias = Union[npt.NDArray[PointDType], tuple[tuple[Literal[0], ...], ...]]
Zeros: TypeAlias = MatrixMN
"""``shape: (M, N)``
A `MatrixMN` filled with zeros, typically created with

1436
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -32,6 +32,7 @@ click = ">=8.0"
cloup = ">=2.0.0"
dearpygui = { version = ">=1.0.0", optional = true }
decorator = ">=4.3.2"
importlib-metadata = {version = ">=3.6", python = "<=3.9"} # Required to discover plugins
isosurfaces = ">=0.1.0"
jupyterlab = { version = ">=3.0.0", optional = true }
manimpango = ">=0.5.0,<1.0.0" # Complete API change in 1.0.0

View file

@ -1,5 +1,8 @@
from __future__ import annotations
from contextlib import redirect_stdout
from io import StringIO
from manim.mobject.text.text_mobject import MarkupText, Text
@ -11,3 +14,20 @@ def test_font_size():
assert round(text_string.font_size, 5) == 14.4
assert round(markuptext_string.font_size, 5) == 14.4
def test_font_warnings():
def warning_printed(font: str, **kwargs) -> bool:
io = StringIO()
with redirect_stdout(io):
Text("hi!", font=font, **kwargs)
txt = io.getvalue()
return "Font" in txt and "not in" in txt
# check for normal fonts (no warning)
assert not warning_printed("System-ui", warn_missing_font=True)
# should be converted to sans before checking
assert not warning_printed("Sans-serif", warn_missing_font=True)
# check random string (should be warning)
assert warning_printed("Manim!" * 3, warn_missing_font=True)

View file

@ -8,17 +8,3 @@ class SquareToCircle(Scene):
square = Square()
circle = Circle()
self.play(Transform(square, circle))
class FunctionLikeTest(Scene):
def construct(self):
assert "FunctionLike" in globals()
a = FunctionLike()
self.play(FadeIn(a))
class WithAllTest(Scene):
def construct(self):
assert "WithAll" in globals()
a = WithAll()
self.play(FadeIn(a))

View file

@ -2,7 +2,6 @@ from __future__ import annotations
import random
import string
import tempfile
import textwrap
from pathlib import Path
@ -142,116 +141,3 @@ def create_plugin(tmp_path, python_version, random_string):
out, err, exit_code = capture(command)
print(out)
assert exit_code == 0, err
@pytest.mark.slow
def test_plugin_function_like(
tmp_path,
create_plugin,
python_version,
simple_scenes_path,
):
function_like_plugin = create_plugin(
"{plugin_name}.__init__:import_all",
"FunctionLike",
"import_all",
)
cfg_file = cfg_file_create(
cfg_file_contents.format(plugin_name=function_like_plugin["plugin_name"]),
tmp_path,
)
scene_name = "FunctionLikeTest"
command = [
python_version,
"-m",
"manim",
"-ql",
"--media_dir",
str(cfg_file.parent),
"--config_file",
str(cfg_file),
str(simple_scenes_path),
scene_name,
]
out, err, exit_code = capture(command, cwd=str(cfg_file.parent))
print(out)
print(err)
assert exit_code == 0, err
@pytest.mark.slow
def test_plugin_no_all(tmp_path, create_plugin, python_version):
create_plugin = create_plugin("{plugin_name}", "NoAll", "import_all")
plugin_name = create_plugin["plugin_name"]
cfg_file = cfg_file_create(
cfg_file_contents.format(plugin_name=plugin_name),
tmp_path,
)
test_class = textwrap.dedent(
f"""\
from manim import *
class NoAllTest(Scene):
def construct(self):
assert "{plugin_name}" in globals()
a = {plugin_name}.NoAll()
self.play(FadeIn(a))
""",
)
with tempfile.NamedTemporaryFile(
mode="w",
encoding="utf-8",
suffix=".py",
delete=False,
) as tmpfile:
tmpfile.write(test_class)
scene_name = "NoAllTest"
command = [
python_version,
"-m",
"manim",
"-ql",
"--media_dir",
str(cfg_file.parent),
"--config_file",
str(cfg_file),
tmpfile.name,
scene_name,
]
out, err, exit_code = capture(command, cwd=str(cfg_file.parent))
print(out)
print(err)
assert exit_code == 0, err
Path(tmpfile.name).unlink()
@pytest.mark.slow
def test_plugin_with_all(tmp_path, create_plugin, python_version, simple_scenes_path):
create_plugin = create_plugin(
"{plugin_name}",
"WithAll",
"import_all",
all_dec="__all__=['WithAll']",
)
plugin_name = create_plugin["plugin_name"]
cfg_file = cfg_file_create(
cfg_file_contents.format(plugin_name=plugin_name),
tmp_path,
)
scene_name = "WithAllTest"
command = [
python_version,
"-m",
"manim",
"-ql",
"--media_dir",
str(cfg_file.parent),
"--config_file",
str(cfg_file),
str(simple_scenes_path),
scene_name,
]
out, err, exit_code = capture(command, cwd=str(cfg_file.parent))
print(out)
print(err)
assert exit_code == 0, err