mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
Merge branch 'main' into guide
This commit is contained in:
commit
7866e007d3
32 changed files with 499 additions and 336 deletions
0
docs/skip-manim
Normal file
0
docs/skip-manim
Normal file
|
|
@ -5,6 +5,24 @@ If you are adding new features to manim, you should add appropriate tests for th
|
|||
manim from breaking at each change by checking that no other
|
||||
feature has been broken and/or been unintentionally modified.
|
||||
|
||||
.. warning::
|
||||
|
||||
The full tests suite requires Cairo 1.18 in order to run all tests.
|
||||
However, Cairo 1.18 may not be available from your package manager,
|
||||
like ``apt``, and it is very likely that you have an older version installed,
|
||||
e.g., 1.16. If you run tests with a version prior to 1.18,
|
||||
many tests will be skipped. Those tests are not skipped in the online CI.
|
||||
|
||||
If you want to run all tests locally, you need to install Cairo 1.18 or above.
|
||||
You can do so by compiling Cairo from source:
|
||||
|
||||
1. download ``cairo-1.18.0.tar.xz`` from
|
||||
`here <https://www.cairographics.org/releases/>`_.
|
||||
and uncompress it;
|
||||
2. open the INSTALL file and follow the instructions (you might need to install
|
||||
``meson`` and ``ninja``);
|
||||
3. run the tests suite and verify that the Cairo version is correct.
|
||||
|
||||
How Manim tests
|
||||
---------------
|
||||
|
||||
|
|
|
|||
|
|
@ -376,13 +376,13 @@ and :meth:`~.Mobject.get_start`. Here is an example of some important coordinate
|
|||
|
||||
class MobjectExample(Scene):
|
||||
def construct(self):
|
||||
p1= [-1,-1,0]
|
||||
p2= [1,-1,0]
|
||||
p3= [1,1,0]
|
||||
p4= [-1,1,0]
|
||||
a = Line(p1,p2).append_points(Line(p2,p3).points).append_points(Line(p3,p4).points)
|
||||
point_start= a.get_start()
|
||||
point_end = a.get_end()
|
||||
p1 = [-1,-1, 0]
|
||||
p2 = [ 1,-1, 0]
|
||||
p3 = [ 1, 1, 0]
|
||||
p4 = [-1, 1, 0]
|
||||
a = Line(p1,p2).append_points(Line(p2,p3).points).append_points(Line(p3,p4).points)
|
||||
point_start = a.get_start()
|
||||
point_end = a.get_end()
|
||||
point_center = a.get_center()
|
||||
self.add(Text(f"a.get_start() = {np.round(point_start,2).tolist()}", font_size=24).to_edge(UR).set_color(YELLOW))
|
||||
self.add(Text(f"a.get_end() = {np.round(point_end,2).tolist()}", font_size=24).next_to(self.mobjects[-1],DOWN).set_color(RED))
|
||||
|
|
@ -425,8 +425,8 @@ function of numpy:
|
|||
self.camera.background_color = WHITE
|
||||
m1a = Square().set_color(RED).shift(LEFT)
|
||||
m1b = Circle().set_color(RED).shift(LEFT)
|
||||
m2a= Square().set_color(BLUE).shift(RIGHT)
|
||||
m2b= Circle().set_color(BLUE).shift(RIGHT)
|
||||
m2a = Square().set_color(BLUE).shift(RIGHT)
|
||||
m2b = Circle().set_color(BLUE).shift(RIGHT)
|
||||
|
||||
points = m2a.points
|
||||
points = np.roll(points, int(len(points)/4), axis=0)
|
||||
|
|
|
|||
|
|
@ -268,11 +268,9 @@ and animating those method calls with ``.animate``.
|
|||
|
||||
self.play(Create(square)) # show the square on screen
|
||||
self.play(square.animate.rotate(PI / 4)) # rotate the square
|
||||
self.play(Transform(square, circle)) # transform the square into a circle
|
||||
self.play(
|
||||
ReplacementTransform(square, circle)
|
||||
) # transform the square into a circle
|
||||
self.play(
|
||||
circle.animate.set_fill(PINK, opacity=0.5)
|
||||
square.animate.set_fill(PINK, opacity=0.5)
|
||||
) # color the circle on screen
|
||||
|
||||
2. Render ``AnimatedSquareToCircle`` by running the following command in the command line:
|
||||
|
|
@ -293,8 +291,8 @@ The following animation will render:
|
|||
|
||||
self.play(Create(square)) # show the square on screen
|
||||
self.play(square.animate.rotate(PI / 4)) # rotate the square
|
||||
self.play(ReplacementTransform(square, circle)) # transform the square into a circle
|
||||
self.play(circle.animate.set_fill(PINK, opacity=0.5)) # color the circle on screen
|
||||
self.play(Transform(square, circle)) # transform the square into a circle
|
||||
self.play(square.animate.set_fill(PINK, opacity=0.5)) # color the circle on screen
|
||||
|
||||
The first ``self.play`` creates the square. The second animates rotating it 45 degrees.
|
||||
The third transforms the square into a circle, and the last colors the circle pink.
|
||||
|
|
@ -348,6 +346,55 @@ If you find that your own usage of ``.animate`` is causing similar unwanted beha
|
|||
using conventional animation methods like the right square, which uses ``Rotate``.
|
||||
|
||||
|
||||
``Transform`` vs ``ReplacementTransform``
|
||||
*****************************************
|
||||
The difference between ``Transform`` and ``ReplacementTransform`` is that ``Transform(mob1, mob2)`` transforms the points
|
||||
(as well as other attributes like color) of ``mob1`` into the points/attributes of ``mob2``.
|
||||
|
||||
``ReplacementTransform(mob1, mob2)`` on the other hand literally replaces ``mob1`` on the scene with ``mob2``.
|
||||
|
||||
The use of ``ReplacementTransform`` or ``Transform`` is mostly up to personal preference. They can be used to accomplish the same effect, as shown below.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class TwoTransforms(Scene):
|
||||
def transform(self):
|
||||
a = Circle()
|
||||
b = Square()
|
||||
c = Triangle()
|
||||
self.play(Transform(a, b))
|
||||
self.play(Transform(a, c))
|
||||
self.play(FadeOut(a))
|
||||
|
||||
def replacement_transform(self):
|
||||
a = Circle()
|
||||
b = Square()
|
||||
c = Triangle()
|
||||
self.play(ReplacementTransform(a, b))
|
||||
self.play(ReplacementTransform(b, c))
|
||||
self.play(FadeOut(c))
|
||||
|
||||
def construct(self):
|
||||
self.transform()
|
||||
self.wait(0.5) # wait for 0.5 seconds
|
||||
self.replacement_transform()
|
||||
|
||||
|
||||
However, in some cases it is more beneficial to use ``Transform``, like when you are transforming several mobjects one after the other.
|
||||
The code below avoids having to keep a reference to the last mobject that was transformed.
|
||||
|
||||
.. manim:: TransformCycle
|
||||
|
||||
class TransformCycle(Scene):
|
||||
def construct(self):
|
||||
a = Circle()
|
||||
t1 = Square()
|
||||
t2 = Triangle()
|
||||
self.add(a)
|
||||
self.wait()
|
||||
for t in [t1,t2]:
|
||||
self.play(Transform(a,t))
|
||||
|
||||
************
|
||||
You're done!
|
||||
************
|
||||
|
|
|
|||
|
|
@ -167,8 +167,8 @@ Triangle.set_default(stroke_width=20)
|
|||
class LineJoints(Scene):
|
||||
def construct(self):
|
||||
t1 = Triangle()
|
||||
t2 = Triangle(line_join=LineJointType.ROUND)
|
||||
t3 = Triangle(line_join=LineJointType.BEVEL)
|
||||
t2 = Triangle(joint_type=LineJointType.ROUND)
|
||||
t3 = Triangle(joint_type=LineJointType.BEVEL)
|
||||
|
||||
grp = VGroup(t1, t2, t3).arrange(RIGHT)
|
||||
grp.set(width=config.frame_width - 1)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import sys
|
||||
|
||||
import click
|
||||
import cloup
|
||||
|
||||
from . import __version__, cli_ctx_settings, console
|
||||
|
|
@ -9,18 +10,20 @@ from .cli.cfg.group import cfg
|
|||
from .cli.checkhealth.commands import checkhealth
|
||||
from .cli.default_group import DefaultGroup
|
||||
from .cli.init.commands import init
|
||||
from .cli.new.group import new
|
||||
from .cli.plugins.commands import plugins
|
||||
from .cli.render.commands import render
|
||||
from .constants import EPILOG
|
||||
|
||||
|
||||
def exit_early(ctx, param, value):
|
||||
def show_splash(ctx, param, value):
|
||||
if value:
|
||||
sys.exit()
|
||||
console.print(f"Manim Community [green]v{__version__}[/green]\n")
|
||||
|
||||
|
||||
console.print(f"Manim Community [green]v{__version__}[/green]\n")
|
||||
def print_version_and_exit(ctx, param, value):
|
||||
show_splash(ctx, param, value)
|
||||
if value:
|
||||
ctx.exit()
|
||||
|
||||
|
||||
@cloup.group(
|
||||
|
|
@ -38,7 +41,16 @@ console.print(f"Manim Community [green]v{__version__}[/green]\n")
|
|||
"--version",
|
||||
is_flag=True,
|
||||
help="Show version and exit.",
|
||||
callback=exit_early,
|
||||
callback=print_version_and_exit,
|
||||
is_eager=True,
|
||||
expose_value=False,
|
||||
)
|
||||
@click.option(
|
||||
"--show-splash/--hide-splash",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Print splash message with version information.",
|
||||
callback=show_splash,
|
||||
is_eager=True,
|
||||
expose_value=False,
|
||||
)
|
||||
|
|
@ -52,7 +64,6 @@ main.add_command(checkhealth)
|
|||
main.add_command(cfg)
|
||||
main.add_command(plugins)
|
||||
main.add_command(init)
|
||||
main.add_command(new)
|
||||
main.add_command(render)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -191,6 +191,11 @@ class Animation:
|
|||
method.
|
||||
|
||||
"""
|
||||
if self.run_time <= 0:
|
||||
raise ValueError(
|
||||
f"{self} has a run_time of <= 0 seconds, this cannot be rendered correctly. "
|
||||
"Please set the run_time to be positive"
|
||||
)
|
||||
self.starting_mobject = self.create_starting_mobject()
|
||||
if self.suspend_mobject_updating:
|
||||
# All calls to self.mobject's internal updaters
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Callable, Sequence
|
||||
import types
|
||||
from typing import TYPE_CHECKING, Callable, Iterable, Sequence
|
||||
|
||||
import numpy as np
|
||||
|
||||
from manim.mobject.opengl.opengl_mobject import OpenGLGroup
|
||||
from manim.utils.parameter_parsing import flatten_iterable_parameters
|
||||
|
||||
from .._config import config
|
||||
from ..animation.animation import Animation, prepare_animation
|
||||
|
|
@ -54,14 +56,15 @@ class AnimationGroup(Animation):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
*animations: Animation,
|
||||
*animations: Animation | Iterable[Animation] | types.GeneratorType[Animation],
|
||||
group: Group | VGroup | OpenGLGroup | OpenGLVGroup = None,
|
||||
run_time: float | None = None,
|
||||
rate_func: Callable[[float], float] = linear,
|
||||
lag_ratio: float = 0,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
self.animations = [prepare_animation(anim) for anim in animations]
|
||||
arg_anim = flatten_iterable_parameters(animations)
|
||||
self.animations = [prepare_animation(anim) for anim in arg_anim]
|
||||
self.rate_func = rate_func
|
||||
self.group = group
|
||||
if self.group is None:
|
||||
|
|
@ -81,6 +84,16 @@ class AnimationGroup(Animation):
|
|||
return list(self.group)
|
||||
|
||||
def begin(self) -> None:
|
||||
if self.run_time <= 0:
|
||||
tmp = (
|
||||
"Please set the run_time to be positive"
|
||||
if len(self.animations) != 0
|
||||
else "Please add at least one Animation with positive run_time"
|
||||
)
|
||||
raise ValueError(
|
||||
f"{self} has a run_time of 0 seconds, this cannot be "
|
||||
f"rendered correctly. {tmp}."
|
||||
)
|
||||
if self.suspend_mobject_updating:
|
||||
self.group.suspend_updating()
|
||||
for anim in self.animations:
|
||||
|
|
|
|||
|
|
@ -37,6 +37,14 @@ LINE_JOIN_MAP = {
|
|||
}
|
||||
|
||||
|
||||
CAP_STYLE_MAP = {
|
||||
CapStyleType.AUTO: None, # TODO: this could be improved
|
||||
CapStyleType.ROUND: cairo.LineCap.ROUND,
|
||||
CapStyleType.BUTT: cairo.LineCap.BUTT,
|
||||
CapStyleType.SQUARE: cairo.LineCap.SQUARE,
|
||||
}
|
||||
|
||||
|
||||
class Camera:
|
||||
"""Base camera class.
|
||||
|
||||
|
|
@ -778,11 +786,13 @@ class Camera:
|
|||
ctx.set_line_width(
|
||||
width
|
||||
* self.cairo_line_width_multiple
|
||||
# This ensures lines have constant width as you zoom in on them.
|
||||
* (self.frame_width / self.frame_width),
|
||||
# This ensures lines have constant width as you zoom in on them.
|
||||
)
|
||||
if vmobject.joint_type != LineJointType.AUTO:
|
||||
ctx.set_line_join(LINE_JOIN_MAP[vmobject.joint_type])
|
||||
if vmobject.cap_style != CapStyleType.AUTO:
|
||||
ctx.set_line_cap(CAP_STYLE_MAP[vmobject.cap_style])
|
||||
ctx.stroke_preserve()
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -1,189 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
import cloup
|
||||
|
||||
from ... import console
|
||||
from ...constants import CONTEXT_SETTINGS, EPILOG, QUALITIES
|
||||
from ...utils.file_ops import (
|
||||
add_import_statement,
|
||||
copy_template_files,
|
||||
get_template_names,
|
||||
get_template_path,
|
||||
)
|
||||
|
||||
CFG_DEFAULTS = {
|
||||
"frame_rate": 30,
|
||||
"background_color": "BLACK",
|
||||
"background_opacity": 1,
|
||||
"scene_names": "Default",
|
||||
"resolution": (854, 480),
|
||||
}
|
||||
|
||||
|
||||
def select_resolution():
|
||||
"""Prompts input of type click.Choice from user. Presents options from QUALITIES constant.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`tuple`
|
||||
Tuple containing height and width.
|
||||
"""
|
||||
resolution_options = []
|
||||
for quality in QUALITIES.items():
|
||||
resolution_options.append(
|
||||
(quality[1]["pixel_height"], quality[1]["pixel_width"]),
|
||||
)
|
||||
resolution_options.pop()
|
||||
choice = click.prompt(
|
||||
"\nSelect resolution:\n",
|
||||
type=cloup.Choice([f"{i[0]}p" for i in resolution_options]),
|
||||
show_default=False,
|
||||
default="480p",
|
||||
)
|
||||
return [res for res in resolution_options if f"{res[0]}p" == choice][0]
|
||||
|
||||
|
||||
def update_cfg(cfg_dict: dict, project_cfg_path: Path):
|
||||
"""Updates the manim.cfg file after reading it from the project_cfg_path.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cfg_dict
|
||||
values used to update manim.cfg found project_cfg_path.
|
||||
project_cfg_path
|
||||
Path of manim.cfg file.
|
||||
"""
|
||||
config = configparser.ConfigParser()
|
||||
config.read(project_cfg_path)
|
||||
cli_config = config["CLI"]
|
||||
for key, value in cfg_dict.items():
|
||||
if key == "resolution":
|
||||
cli_config["pixel_height"] = str(value[0])
|
||||
cli_config["pixel_width"] = str(value[1])
|
||||
else:
|
||||
cli_config[key] = str(value)
|
||||
|
||||
with project_cfg_path.open("w") as conf:
|
||||
config.write(conf)
|
||||
|
||||
|
||||
@cloup.command(
|
||||
context_settings=CONTEXT_SETTINGS,
|
||||
epilog=EPILOG,
|
||||
)
|
||||
@cloup.argument("project_name", type=Path, required=False)
|
||||
@cloup.option(
|
||||
"-d",
|
||||
"--default",
|
||||
"default_settings",
|
||||
is_flag=True,
|
||||
help="Default settings for project creation.",
|
||||
nargs=1,
|
||||
)
|
||||
def project(default_settings, **args):
|
||||
"""Creates a new project.
|
||||
|
||||
PROJECT_NAME is the name of the folder in which the new project will be initialized.
|
||||
"""
|
||||
if args["project_name"]:
|
||||
project_name = args["project_name"]
|
||||
else:
|
||||
project_name = click.prompt("Project Name", type=Path)
|
||||
|
||||
# in the future when implementing a full template system. Choices are going to be saved in some sort of config file for templates
|
||||
template_name = click.prompt(
|
||||
"Template",
|
||||
type=click.Choice(get_template_names(), False),
|
||||
default="Default",
|
||||
)
|
||||
|
||||
if project_name.is_dir():
|
||||
console.print(
|
||||
f"\nFolder [red]{project_name}[/red] exists. Please type another name\n",
|
||||
)
|
||||
else:
|
||||
project_name.mkdir()
|
||||
new_cfg = {}
|
||||
new_cfg_path = Path.resolve(project_name / "manim.cfg")
|
||||
|
||||
if not default_settings:
|
||||
for key, value in CFG_DEFAULTS.items():
|
||||
if key == "scene_names":
|
||||
new_cfg[key] = template_name + "Template"
|
||||
elif key == "resolution":
|
||||
new_cfg[key] = select_resolution()
|
||||
else:
|
||||
new_cfg[key] = click.prompt(f"\n{key}", default=value)
|
||||
|
||||
console.print("\n", new_cfg)
|
||||
if click.confirm("Do you want to continue?", default=True, abort=True):
|
||||
copy_template_files(project_name, template_name)
|
||||
update_cfg(new_cfg, new_cfg_path)
|
||||
else:
|
||||
copy_template_files(project_name, template_name)
|
||||
update_cfg(CFG_DEFAULTS, new_cfg_path)
|
||||
|
||||
|
||||
@cloup.command(
|
||||
context_settings=CONTEXT_SETTINGS,
|
||||
no_args_is_help=True,
|
||||
epilog=EPILOG,
|
||||
)
|
||||
@cloup.argument("scene_name", type=str, required=True)
|
||||
@cloup.argument("file_name", type=str, required=False)
|
||||
def scene(**args):
|
||||
"""Inserts a SCENE to an existing FILE or creates a new FILE.
|
||||
|
||||
SCENE is the name of the scene that will be inserted.
|
||||
|
||||
FILE is the name of file in which the SCENE will be inserted.
|
||||
"""
|
||||
if not Path("main.py").exists():
|
||||
raise FileNotFoundError(f"{Path('main.py')} : Not a valid project directory.")
|
||||
|
||||
template_name = click.prompt(
|
||||
"template",
|
||||
type=click.Choice(get_template_names(), False),
|
||||
default="Default",
|
||||
)
|
||||
scene = (get_template_path() / f"{template_name}.mtp").resolve().read_text()
|
||||
scene = scene.replace(template_name + "Template", args["scene_name"], 1)
|
||||
|
||||
if args["file_name"]:
|
||||
file_name = Path(args["file_name"] + ".py")
|
||||
|
||||
if file_name.is_file():
|
||||
# file exists so we are going to append new scene to that file
|
||||
with file_name.open("a") as f:
|
||||
f.write("\n\n\n" + scene)
|
||||
else:
|
||||
# file does not exist so we create a new file, append the scene and prepend the import statement
|
||||
file_name.write_text("\n\n\n" + scene)
|
||||
|
||||
add_import_statement(file_name)
|
||||
else:
|
||||
# file name is not provided so we assume it is main.py
|
||||
# if main.py does not exist we do not continue
|
||||
with Path("main.py").open("a") as f:
|
||||
f.write("\n\n\n" + scene)
|
||||
|
||||
|
||||
@cloup.group(
|
||||
context_settings=CONTEXT_SETTINGS,
|
||||
invoke_without_command=True,
|
||||
no_args_is_help=True,
|
||||
epilog=EPILOG,
|
||||
help="Create a new project or insert a new scene.",
|
||||
deprecated=True,
|
||||
)
|
||||
@cloup.pass_context
|
||||
def new(ctx):
|
||||
pass
|
||||
|
||||
|
||||
new.add_command(project)
|
||||
new.add_command(scene)
|
||||
|
|
@ -76,6 +76,7 @@ __all__ = [
|
|||
"CTRL_VALUE",
|
||||
"RendererType",
|
||||
"LineJointType",
|
||||
"CapStyleType",
|
||||
]
|
||||
# Messages
|
||||
|
||||
|
|
@ -305,3 +306,41 @@ class LineJointType(Enum):
|
|||
ROUND = 1
|
||||
BEVEL = 2
|
||||
MITER = 3
|
||||
|
||||
|
||||
class CapStyleType(Enum):
|
||||
"""Collection of available cap styles.
|
||||
|
||||
See the example below for a visual illustration of the different
|
||||
cap styles.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. manim:: CapStyleVariants
|
||||
:save_last_frame:
|
||||
|
||||
class CapStyleVariants(Scene):
|
||||
def construct(self):
|
||||
arcs = VGroup(*[
|
||||
Arc(
|
||||
radius=1,
|
||||
start_angle=0,
|
||||
angle=TAU / 4,
|
||||
stroke_width=20,
|
||||
color=GREEN,
|
||||
cap_style=cap_style,
|
||||
)
|
||||
for cap_style in CapStyleType
|
||||
])
|
||||
arcs.arrange(RIGHT, buff=1)
|
||||
self.add(arcs)
|
||||
for arc in arcs:
|
||||
label = Text(arc.cap_style.name, font_size=24).next_to(arc, DOWN)
|
||||
self.add(label)
|
||||
"""
|
||||
|
||||
AUTO = 0
|
||||
ROUND = 1
|
||||
BUTT = 2
|
||||
SQUARE = 3
|
||||
|
|
|
|||
|
|
@ -598,8 +598,10 @@ class Rectangle(Polygon):
|
|||
def construct(self):
|
||||
rect1 = Rectangle(width=4.0, height=2.0, grid_xstep=1.0, grid_ystep=0.5)
|
||||
rect2 = Rectangle(width=1.0, height=4.0)
|
||||
rect3 = Rectangle(width=2.0, height=2.0, grid_xstep=1.0, grid_ystep=1.0)
|
||||
rect3.grid_lines.set_stroke(width=1)
|
||||
|
||||
rects = Group(rect1,rect2).arrange(buff=1)
|
||||
rects = Group(rect1, rect2, rect3).arrange(buff=1)
|
||||
self.add(rects)
|
||||
"""
|
||||
|
||||
|
|
@ -617,10 +619,16 @@ class Rectangle(Polygon):
|
|||
super().__init__(UR, UL, DL, DR, color=color, **kwargs)
|
||||
self.stretch_to_fit_width(width)
|
||||
self.stretch_to_fit_height(height)
|
||||
|
||||
v = self.get_vertices()
|
||||
if grid_xstep is not None:
|
||||
self.grid_lines = VGroup()
|
||||
|
||||
if grid_xstep or grid_ystep:
|
||||
from manim.mobject.geometry.line import Line
|
||||
|
||||
v = self.get_vertices()
|
||||
|
||||
if grid_xstep:
|
||||
grid_xstep = abs(grid_xstep)
|
||||
count = int(width / grid_xstep)
|
||||
grid = VGroup(
|
||||
|
|
@ -633,8 +641,9 @@ class Rectangle(Polygon):
|
|||
for i in range(1, count)
|
||||
)
|
||||
)
|
||||
self.add(grid)
|
||||
if grid_ystep is not None:
|
||||
self.grid_lines.add(grid)
|
||||
|
||||
if grid_ystep:
|
||||
grid_ystep = abs(grid_ystep)
|
||||
count = int(height / grid_ystep)
|
||||
grid = VGroup(
|
||||
|
|
@ -647,7 +656,10 @@ class Rectangle(Polygon):
|
|||
for i in range(1, count)
|
||||
)
|
||||
)
|
||||
self.add(grid)
|
||||
self.grid_lines.add(grid)
|
||||
|
||||
if self.grid_lines:
|
||||
self.add(self.grid_lines)
|
||||
|
||||
|
||||
class Square(Rectangle):
|
||||
|
|
|
|||
|
|
@ -968,11 +968,11 @@ class Mobject:
|
|||
|
||||
class DtUpdater(Scene):
|
||||
def construct(self):
|
||||
line = Square()
|
||||
square = Square()
|
||||
|
||||
#Let the line rotate 90° per second
|
||||
line.add_updater(lambda mobject, dt: mobject.rotate(dt*90*DEGREES))
|
||||
self.add(line)
|
||||
#Let the square rotate 90° per second
|
||||
square.add_updater(lambda mobject, dt: mobject.rotate(dt*90*DEGREES))
|
||||
self.add(square)
|
||||
self.wait(2)
|
||||
|
||||
See also
|
||||
|
|
@ -1423,11 +1423,59 @@ class Mobject:
|
|||
def to_corner(
|
||||
self, corner: Vector3 = DL, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
|
||||
) -> Self:
|
||||
"""Moves this :class:`~.Mobject` to the given corner of the screen.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`.Mobject`
|
||||
The newly positioned mobject.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. manim:: ToCornerExample
|
||||
:save_last_frame:
|
||||
|
||||
class ToCornerExample(Scene):
|
||||
def construct(self):
|
||||
c = Circle()
|
||||
c.to_corner(UR)
|
||||
t = Tex("To the corner!")
|
||||
t2 = MathTex("x^3").shift(DOWN)
|
||||
self.add(c,t,t2)
|
||||
t.to_corner(DL, buff=0)
|
||||
t2.to_corner(UL, buff=1.5)
|
||||
"""
|
||||
return self.align_on_border(corner, buff)
|
||||
|
||||
def to_edge(
|
||||
self, edge: Vector3 = LEFT, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
|
||||
) -> Self:
|
||||
"""Moves this :class:`~.Mobject` to the given edge of the screen,
|
||||
without affecting its position in the other dimension.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`.Mobject`
|
||||
The newly positioned mobject.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. manim:: ToEdgeExample
|
||||
:save_last_frame:
|
||||
|
||||
class ToEdgeExample(Scene):
|
||||
def construct(self):
|
||||
tex_top = Tex("I am at the top!")
|
||||
tex_top.to_edge(UP)
|
||||
tex_side = Tex("I am moving to the side!")
|
||||
c = Circle().shift(2*DOWN)
|
||||
self.add(tex_top, tex_side)
|
||||
tex_side.to_edge(LEFT)
|
||||
c.to_edge(RIGHT, buff=0)
|
||||
|
||||
"""
|
||||
return self.align_on_border(edge, buff)
|
||||
|
||||
def next_to(
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ Examples
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
|
||||
__all__ = ["Text", "Paragraph", "MarkupText", "register_font"]
|
||||
|
||||
|
||||
|
|
@ -407,6 +409,11 @@ class Text(SVGMobject):
|
|||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def font_list() -> list[str]:
|
||||
return manimpango.list_fonts()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text: str,
|
||||
|
|
@ -431,13 +438,12 @@ class Text(SVGMobject):
|
|||
width: float = None,
|
||||
should_center: bool = True,
|
||||
disable_ligatures: bool = False,
|
||||
use_svg_cache: bool = False,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
self.line_spacing = line_spacing
|
||||
if font and warn_missing_font:
|
||||
fonts_list = manimpango.list_fonts()
|
||||
if font not in fonts_list:
|
||||
logger.warning(f"Font {font} not in {fonts_list}.")
|
||||
if font and warn_missing_font and font not in Text.font_list():
|
||||
logger.warning(f"Font {font} not in {Text.font_list()}.")
|
||||
self.font = font
|
||||
self._font_size = float(font_size)
|
||||
# needs to be a float or else size is inflated when font_size = 24
|
||||
|
|
@ -491,7 +497,7 @@ class Text(SVGMobject):
|
|||
height=height,
|
||||
width=width,
|
||||
should_center=should_center,
|
||||
use_svg_cache=False,
|
||||
use_svg_cache=use_svg_cache,
|
||||
**kwargs,
|
||||
)
|
||||
self.text = text
|
||||
|
|
@ -1133,6 +1139,11 @@ class MarkupText(SVGMobject):
|
|||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def font_list() -> list[str]:
|
||||
return manimpango.list_fonts()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text: str,
|
||||
|
|
@ -1156,10 +1167,8 @@ class MarkupText(SVGMobject):
|
|||
) -> None:
|
||||
self.text = text
|
||||
self.line_spacing = line_spacing
|
||||
if font and warn_missing_font:
|
||||
fonts_list = manimpango.list_fonts()
|
||||
if font not in fonts_list:
|
||||
logger.warning(f"Font {font} not in {fonts_list}.")
|
||||
if font and warn_missing_font and font not in Text.font_list():
|
||||
logger.warning(f"Font {font} not in {Text.font_list()}.")
|
||||
self.font = font
|
||||
self._font_size = float(font_size)
|
||||
self.slant = slant
|
||||
|
|
|
|||
|
|
@ -191,7 +191,9 @@ class ImageMobject(AbstractImageMobject):
|
|||
self.pixel_array, self.pixel_array_dtype
|
||||
)
|
||||
if self.invert:
|
||||
self.pixel_array[:, :, :3] = 255 - self.pixel_array[:, :, :3]
|
||||
self.pixel_array[:, :, :3] = (
|
||||
np.iinfo(self.pixel_array_dtype).max - self.pixel_array[:, :, :3]
|
||||
)
|
||||
super().__init__(scale_to_resolution, **kwargs)
|
||||
|
||||
def get_pixel_array(self):
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ class VMobject(Mobject):
|
|||
# TODO, do we care about accounting for varying zoom levels?
|
||||
tolerance_for_point_equality: float = 1e-6,
|
||||
n_points_per_cubic_curve: int = 4,
|
||||
cap_style: CapStyleType = CapStyleType.AUTO,
|
||||
**kwargs,
|
||||
):
|
||||
self.fill_opacity = fill_opacity
|
||||
|
|
@ -150,6 +151,7 @@ class VMobject(Mobject):
|
|||
self.shade_in_3d: bool = shade_in_3d
|
||||
self.tolerance_for_point_equality: float = tolerance_for_point_equality
|
||||
self.n_points_per_cubic_curve: int = n_points_per_cubic_curve
|
||||
self.cap_style: CapStyleType = cap_style
|
||||
super().__init__(**kwargs)
|
||||
self.submobjects: list[VMobject]
|
||||
|
||||
|
|
@ -340,6 +342,34 @@ class VMobject(Mobject):
|
|||
self.background_stroke_color = ManimColor(color)
|
||||
return self
|
||||
|
||||
def set_cap_style(self, cap_style: CapStyleType) -> Self:
|
||||
"""
|
||||
Sets the cap style of the :class:`VMobject`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cap_style
|
||||
The cap style to be set. See :class:`.CapStyleType` for options.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`VMobject`
|
||||
``self``
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. manim:: CapStyleExample
|
||||
:save_last_frame:
|
||||
|
||||
class CapStyleExample(Scene):
|
||||
def construct(self):
|
||||
line = Line(LEFT, RIGHT, color=YELLOW, stroke_width=20)
|
||||
line.set_cap_style(CapStyleType.ROUND)
|
||||
self.add(line)
|
||||
"""
|
||||
self.cap_style = cap_style
|
||||
return self
|
||||
|
||||
def set_background_stroke(self, **kwargs) -> Self:
|
||||
kwargs["background"] = True
|
||||
self.set_stroke(**kwargs)
|
||||
|
|
@ -2458,7 +2488,7 @@ class CurvesAsSubmobjects(VGroup):
|
|||
if len(self.submobjects) == 0:
|
||||
caller_name = sys._getframe(1).f_code.co_name
|
||||
raise Exception(
|
||||
f"Cannot call CurvesAsSubmobjects.{caller_name} for a CurvesAsSubmobject with no submobjects"
|
||||
f"Cannot call CurvesAsSubmobjects. {caller_name} for a CurvesAsSubmobject with no submobjects"
|
||||
)
|
||||
|
||||
def _get_submobjects_with_points(self):
|
||||
|
|
@ -2468,7 +2498,7 @@ class CurvesAsSubmobjects(VGroup):
|
|||
if len(submobjs_with_pts) == 0:
|
||||
caller_name = sys._getframe(1).f_code.co_name
|
||||
raise Exception(
|
||||
f"Cannot call CurvesAsSubmobjects.{caller_name} for a CurvesAsSubmobject whose submobjects have no points"
|
||||
f"Cannot call CurvesAsSubmobjects. {caller_name} for a CurvesAsSubmobject whose submobjects have no points"
|
||||
)
|
||||
return submobjs_with_pts
|
||||
|
||||
|
|
|
|||
|
|
@ -829,7 +829,9 @@ class StreamLines(VectorField):
|
|||
step = max(1, int(len(points) / self.max_anchors_per_line))
|
||||
line.set_points_smoothly(points[::step])
|
||||
if self.single_color:
|
||||
line.set_stroke(self.color)
|
||||
line.set_stroke(
|
||||
color=self.color, width=self.stroke_width, opacity=opacity
|
||||
)
|
||||
else:
|
||||
if config.renderer == RendererType.OPENGL:
|
||||
# scaled for compatibility with cairo
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
|
@ -15,6 +14,10 @@ from ..utils.exceptions import EndSceneEarlyException
|
|||
from ..utils.iterables import list_update
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import types
|
||||
from typing import Any, Iterable
|
||||
|
||||
from manim.animation.animation import Animation
|
||||
from manim.scene.scene import Scene
|
||||
|
||||
|
||||
|
|
@ -51,7 +54,12 @@ class CairoRenderer:
|
|||
scene.__class__.__name__,
|
||||
)
|
||||
|
||||
def play(self, scene, *args, **kwargs):
|
||||
def play(
|
||||
self,
|
||||
scene: Scene,
|
||||
*args: Animation | Iterable[Animation] | types.GeneratorType[Animation],
|
||||
**kwargs,
|
||||
):
|
||||
# Reset skip_animations to the original state.
|
||||
# Needed when rendering only some animations, and skipping others.
|
||||
self.skip_animations = self._original_skipping_status
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from manim.utils.parameter_parsing import flatten_iterable_parameters
|
||||
|
||||
__all__ = ["Scene"]
|
||||
|
||||
import copy
|
||||
|
|
@ -13,7 +15,6 @@ import threading
|
|||
import time
|
||||
import types
|
||||
from queue import Queue
|
||||
from typing import Callable
|
||||
|
||||
import srt
|
||||
|
||||
|
|
@ -25,6 +26,8 @@ try:
|
|||
dearpygui_imported = True
|
||||
except ImportError:
|
||||
dearpygui_imported = False
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
|
@ -48,6 +51,9 @@ from ..utils.family_ops import restructure_list_to_exclude_certain_family_member
|
|||
from ..utils.file_ops import open_media_file
|
||||
from ..utils.iterables import list_difference_update, list_update
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Callable, Iterable
|
||||
|
||||
|
||||
class RerunSceneHandler(FileSystemEventHandler):
|
||||
"""A class to handle rerunning a Scene after the input file is modified."""
|
||||
|
|
@ -865,7 +871,11 @@ class Scene:
|
|||
)
|
||||
return all_moving_mobject_families, static_mobjects
|
||||
|
||||
def compile_animations(self, *args: Animation, **kwargs):
|
||||
def compile_animations(
|
||||
self,
|
||||
*args: Animation | Iterable[Animation] | types.GeneratorType[Animation],
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Creates _MethodAnimations from any _AnimationBuilders and updates animation
|
||||
kwargs with kwargs passed to play().
|
||||
|
|
@ -883,7 +893,9 @@ class Scene:
|
|||
Animations to be played.
|
||||
"""
|
||||
animations = []
|
||||
for arg in args:
|
||||
arg_anims = flatten_iterable_parameters(args)
|
||||
# Allow passing a generator to self.play instead of comma separated arguments
|
||||
for arg in arg_anims:
|
||||
try:
|
||||
animations.append(prepare_animation(arg))
|
||||
except TypeError:
|
||||
|
|
@ -1027,7 +1039,7 @@ class Scene:
|
|||
|
||||
def play(
|
||||
self,
|
||||
*args,
|
||||
*args: Animation | Iterable[Animation] | types.GeneratorType[Animation],
|
||||
subcaption=None,
|
||||
subcaption_duration=None,
|
||||
subcaption_offset=0,
|
||||
|
|
@ -1157,7 +1169,11 @@ class Scene:
|
|||
"""
|
||||
self.wait(max_time, stop_condition=stop_condition)
|
||||
|
||||
def compile_animation_data(self, *animations: Animation, **play_kwargs):
|
||||
def compile_animation_data(
|
||||
self,
|
||||
*animations: Animation | Iterable[Animation] | types.GeneratorType[Animation],
|
||||
**play_kwargs,
|
||||
):
|
||||
"""Given a list of animations, compile the corresponding
|
||||
static and moving mobjects, and gather the animation durations.
|
||||
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ class SceneFileWriter:
|
|||
and not skip_animations
|
||||
):
|
||||
# relative to index file
|
||||
section_video = f"{self.output_name}_{len(self.sections):04}{config.movie_file_extension}"
|
||||
section_video = f"{self.output_name}_{len(self.sections):04}_{name}{config.movie_file_extension}"
|
||||
|
||||
self.sections.append(
|
||||
Section(
|
||||
|
|
|
|||
31
manim/utils/parameter_parsing.py
Normal file
31
manim/utils/parameter_parsing.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from types import GeneratorType
|
||||
from typing import Iterable, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def flatten_iterable_parameters(
|
||||
args: Iterable[T | Iterable[T] | GeneratorType],
|
||||
) -> list[T]:
|
||||
"""Flattens an iterable of parameters into a list of parameters.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args
|
||||
The iterable of parameters to flatten.
|
||||
[(generator), [], (), ...]
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`list`
|
||||
The flattened list of parameters.
|
||||
"""
|
||||
flattened_parameters = []
|
||||
for arg in args:
|
||||
if isinstance(arg, (Iterable, GeneratorType)):
|
||||
flattened_parameters.extend(arg)
|
||||
else:
|
||||
flattened_parameters.append(arg)
|
||||
return flattened_parameters
|
||||
104
poetry.lock
generated
104
poetry.lock
generated
|
|
@ -15,7 +15,7 @@ files = [
|
|||
name = "anyio"
|
||||
version = "4.1.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "anyio-4.1.0-py3-none-any.whl", hash = "sha256:56a415fbc462291813a94528a779597226619c8e78af7de0507333f700011e5f"},
|
||||
|
|
@ -47,7 +47,7 @@ files = [
|
|||
name = "argon2-cffi"
|
||||
version = "23.1.0"
|
||||
description = "Argon2 for Python"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"},
|
||||
|
|
@ -67,7 +67,7 @@ typing = ["mypy"]
|
|||
name = "argon2-cffi-bindings"
|
||||
version = "21.2.0"
|
||||
description = "Low-level CFFI bindings for Argon2"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"},
|
||||
|
|
@ -104,7 +104,7 @@ tests = ["pytest"]
|
|||
name = "arrow"
|
||||
version = "1.3.0"
|
||||
description = "Better dates & times for Python"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"},
|
||||
|
|
@ -261,7 +261,7 @@ uvloop = ["uvloop (>=0.15.2)"]
|
|||
name = "bleach"
|
||||
version = "6.1.0"
|
||||
description = "An easy safelist-based HTML-sanitizing tool."
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "bleach-6.1.0-py3-none-any.whl", hash = "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6"},
|
||||
|
|
@ -842,7 +842,7 @@ files = [
|
|||
name = "defusedxml"
|
||||
version = "0.7.1"
|
||||
description = "XML bomb protection for Python stdlib modules"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
files = [
|
||||
{file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
|
||||
|
|
@ -934,7 +934,7 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth
|
|||
name = "fastjsonschema"
|
||||
version = "2.19.0"
|
||||
description = "Fastest Python implementation of JSON schema"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "fastjsonschema-2.19.0-py3-none-any.whl", hash = "sha256:b9fd1a2dd6971dbc7fee280a95bd199ae0dd9ce22beb91cc75e9c1c528a5170e"},
|
||||
|
|
@ -1168,7 +1168,7 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
|
|||
name = "fqdn"
|
||||
version = "1.5.1"
|
||||
description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4"
|
||||
files = [
|
||||
{file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"},
|
||||
|
|
@ -1456,7 +1456,7 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pa
|
|||
name = "isoduration"
|
||||
version = "20.11.0"
|
||||
description = "Operations with ISO 8601 durations"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"},
|
||||
|
|
@ -1551,7 +1551,7 @@ dev = ["hypothesis"]
|
|||
name = "jsonpointer"
|
||||
version = "2.4"
|
||||
description = "Identify specific nodes in a JSON document (RFC 6901)"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*"
|
||||
files = [
|
||||
{file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"},
|
||||
|
|
@ -1562,7 +1562,7 @@ files = [
|
|||
name = "jsonschema"
|
||||
version = "4.20.0"
|
||||
description = "An implementation of JSON Schema validation for Python"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "jsonschema-4.20.0-py3-none-any.whl", hash = "sha256:ed6231f0429ecf966f5bc8dfef245998220549cbbcf140f913b7464c52c3b6b3"},
|
||||
|
|
@ -1591,7 +1591,7 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-
|
|||
name = "jsonschema-specifications"
|
||||
version = "2023.11.2"
|
||||
description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "jsonschema_specifications-2023.11.2-py3-none-any.whl", hash = "sha256:e74ba7c0a65e8cb49dc26837d6cfe576557084a8b423ed16a420984228104f93"},
|
||||
|
|
@ -1605,7 +1605,7 @@ referencing = ">=0.31.0"
|
|||
name = "jupyter-client"
|
||||
version = "8.6.0"
|
||||
description = "Jupyter protocol implementation and client libraries"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "jupyter_client-8.6.0-py3-none-any.whl", hash = "sha256:909c474dbe62582ae62b758bca86d6518c85234bdee2d908c778db6d72f39d99"},
|
||||
|
|
@ -1628,7 +1628,7 @@ test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pyt
|
|||
name = "jupyter-core"
|
||||
version = "5.5.0"
|
||||
description = "Jupyter core package. A base package on which Jupyter projects rely."
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "jupyter_core-5.5.0-py3-none-any.whl", hash = "sha256:e11e02cd8ae0a9de5c6c44abf5727df9f2581055afe00b22183f621ba3585805"},
|
||||
|
|
@ -1648,7 +1648,7 @@ test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"]
|
|||
name = "jupyter-events"
|
||||
version = "0.9.0"
|
||||
description = "Jupyter Event System library"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "jupyter_events-0.9.0-py3-none-any.whl", hash = "sha256:d853b3c10273ff9bc8bb8b30076d65e2c9685579db736873de6c2232dde148bf"},
|
||||
|
|
@ -1686,13 +1686,13 @@ jupyter-server = ">=1.1.2"
|
|||
|
||||
[[package]]
|
||||
name = "jupyter-server"
|
||||
version = "2.11.1"
|
||||
version = "2.11.2"
|
||||
description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications."
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "jupyter_server-2.11.1-py3-none-any.whl", hash = "sha256:4b3a16e3ed16fd202588890f10b8ca589bd3e29405d128beb95935f059441373"},
|
||||
{file = "jupyter_server-2.11.1.tar.gz", hash = "sha256:fe80bab96493acf5f7d6cd9a1575af8fbd253dc2591aa4d015131a1e03b5799a"},
|
||||
{file = "jupyter_server-2.11.2-py3-none-any.whl", hash = "sha256:0c548151b54bcb516ca466ec628f7f021545be137d01b5467877e87f6fff4374"},
|
||||
{file = "jupyter_server-2.11.2.tar.gz", hash = "sha256:0c99f9367b0f24141e527544522430176613f9249849be80504c6d2b955004bb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -1724,7 +1724,7 @@ test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-sc
|
|||
name = "jupyter-server-terminals"
|
||||
version = "0.4.4"
|
||||
description = "A Jupyter Server Extension Providing Terminals."
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "jupyter_server_terminals-0.4.4-py3-none-any.whl", hash = "sha256:75779164661cec02a8758a5311e18bb8eb70c4e86c6b699403100f1585a12a36"},
|
||||
|
|
@ -1775,7 +1775,7 @@ test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-cons
|
|||
name = "jupyterlab-pygments"
|
||||
version = "0.3.0"
|
||||
description = "Pygments theme using JupyterLab CSS variables"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"},
|
||||
|
|
@ -1969,6 +1969,14 @@ files = [
|
|||
{file = "mapbox_earcut-1.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9af9369266bf0ca32f4d401152217c46c699392513f22639c6b1be32bde9c1cc"},
|
||||
{file = "mapbox_earcut-1.0.1-cp311-cp311-win32.whl", hash = "sha256:ff9a13be4364625697b0e0e04ba6a0f77300148b871bba0a85bfa67e972e85c4"},
|
||||
{file = "mapbox_earcut-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e736557539c74fa969e866889c2b0149fc12668f35e3ae33667d837ff2880d3"},
|
||||
{file = "mapbox_earcut-1.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4fe92174410e4120022393013705d77cb856ead5bdf6c81bec614a70df4feb5d"},
|
||||
{file = "mapbox_earcut-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:082f70a865c6164a60af039aa1c377073901cf1f94fd37b1c5610dfbae2a7369"},
|
||||
{file = "mapbox_earcut-1.0.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43d268ece49d0c9e22cb4f92cd54c2cc64f71bf1c5e10800c189880d923e1292"},
|
||||
{file = "mapbox_earcut-1.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7748f1730fd36dd1fcf0809d8f872d7e1ddaa945f66a6a466ad37ef3c552ae93"},
|
||||
{file = "mapbox_earcut-1.0.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5a82d10c8dec2a0bd9a6a6c90aca7044017c8dad79f7e209fd0667826f842325"},
|
||||
{file = "mapbox_earcut-1.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:01b292588cd3f6bad7d76ee31c004ed1b557a92bbd9602a72d2be15513b755be"},
|
||||
{file = "mapbox_earcut-1.0.1-cp312-cp312-win32.whl", hash = "sha256:fce236ddc3a56ea7260acc94601a832c260e6ac5619374bb2cec2e73e7414ff0"},
|
||||
{file = "mapbox_earcut-1.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:1ce86407353b4f09f5778c436518bbbc6f258f46c5736446f25074fe3d3a3bd8"},
|
||||
{file = "mapbox_earcut-1.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:aa6111a18efacb79c081f3d3cdd7d25d0585bb0e9f28896b207ebe1d56efa40e"},
|
||||
{file = "mapbox_earcut-1.0.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2911829d1e6e5e1282fbe2840fadf578f606580f02ed436346c2d51c92f810b"},
|
||||
{file = "mapbox_earcut-1.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ff909a7b8405a923abedd701b53633c997cc2b5dc9d5b78462f51c25ec2c33"},
|
||||
|
|
@ -2221,7 +2229,7 @@ files = [
|
|||
name = "mistune"
|
||||
version = "3.0.2"
|
||||
description = "A sane and fast Markdown parser with useful plugins and renderers"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"},
|
||||
|
|
@ -2383,7 +2391,7 @@ testing = ["beautifulsoup4", "coverage", "docutils (>=0.17.0,<0.18.0)", "pytest
|
|||
name = "nbclient"
|
||||
version = "0.9.0"
|
||||
description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor."
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "nbclient-0.9.0-py3-none-any.whl", hash = "sha256:a3a1ddfb34d4a9d17fc744d655962714a866639acd30130e9be84191cd97cd15"},
|
||||
|
|
@ -2405,7 +2413,7 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=
|
|||
name = "nbconvert"
|
||||
version = "7.11.0"
|
||||
description = "Converting Jupyter Notebooks"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "nbconvert-7.11.0-py3-none-any.whl", hash = "sha256:d1d417b7f34a4e38887f8da5bdfd12372adf3b80f995d57556cb0972c68909fe"},
|
||||
|
|
@ -2443,7 +2451,7 @@ webpdf = ["playwright"]
|
|||
name = "nbformat"
|
||||
version = "5.9.2"
|
||||
description = "The Jupyter Notebook format"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "nbformat-5.9.2-py3-none-any.whl", hash = "sha256:1c5172d786a41b82bcfd0c23f9e6b6f072e8fb49c39250219e4acfff1efe89e9"},
|
||||
|
|
@ -2592,7 +2600,7 @@ files = [
|
|||
name = "overrides"
|
||||
version = "7.4.0"
|
||||
description = "A decorator to automatically detect mismatch when overriding a method."
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "overrides-7.4.0-py3-none-any.whl", hash = "sha256:3ad24583f86d6d7a49049695efe9933e67ba62f0c7625d53c59fa832ce4b8b7d"},
|
||||
|
|
@ -2614,7 +2622,7 @@ files = [
|
|||
name = "pandocfilters"
|
||||
version = "1.5.0"
|
||||
description = "Utilities for writing pandoc filters in python"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
{file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"},
|
||||
|
|
@ -2780,7 +2788,7 @@ virtualenv = ">=20.10.0"
|
|||
name = "prometheus-client"
|
||||
version = "0.19.0"
|
||||
description = "Python client for the Prometheus monitoring system."
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "prometheus_client-0.19.0-py3-none-any.whl", hash = "sha256:c88b1e6ecf6b41cd8fb5731c7ae919bf66df6ec6fafa555cd6c0e16ca169ae92"},
|
||||
|
|
@ -2861,7 +2869,7 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "unittest2", "wmi"]
|
|||
name = "ptyprocess"
|
||||
version = "0.7.0"
|
||||
description = "Run a subprocess in a pseudo terminal"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
|
||||
|
|
@ -3228,7 +3236,7 @@ six = ">=1.5"
|
|||
name = "python-json-logger"
|
||||
version = "2.0.7"
|
||||
description = "A python library adding a json log formatter"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c"},
|
||||
|
|
@ -3239,7 +3247,7 @@ files = [
|
|||
name = "pywin32"
|
||||
version = "306"
|
||||
description = "Python for Window Extensions"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"},
|
||||
|
|
@ -3262,7 +3270,7 @@ files = [
|
|||
name = "pywinpty"
|
||||
version = "2.0.12"
|
||||
description = "Pseudo terminal support for Windows from Python."
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pywinpty-2.0.12-cp310-none-win_amd64.whl", hash = "sha256:21319cd1d7c8844fb2c970fb3a55a3db5543f112ff9cfcd623746b9c47501575"},
|
||||
|
|
@ -3336,7 +3344,7 @@ files = [
|
|||
name = "pyzmq"
|
||||
version = "25.1.1"
|
||||
description = "Python bindings for 0MQ"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pyzmq-25.1.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76"},
|
||||
|
|
@ -3441,7 +3449,7 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""}
|
|||
name = "referencing"
|
||||
version = "0.31.1"
|
||||
description = "JSON Referencing + Python"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "referencing-0.31.1-py3-none-any.whl", hash = "sha256:c19c4d006f1757e3dd75c4f784d38f8698d87b649c54f9ace14e5e8c9667c01d"},
|
||||
|
|
@ -3490,7 +3498,7 @@ docutils = ">=0.11,<1.0"
|
|||
name = "rfc3339-validator"
|
||||
version = "0.1.4"
|
||||
description = "A pure python RFC3339 validator"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
files = [
|
||||
{file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"},
|
||||
|
|
@ -3504,7 +3512,7 @@ six = "*"
|
|||
name = "rfc3986-validator"
|
||||
version = "0.1.1"
|
||||
description = "Pure python rfc3986 validator"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
files = [
|
||||
{file = "rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9"},
|
||||
|
|
@ -3533,7 +3541,7 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
|||
name = "rpds-py"
|
||||
version = "0.13.2"
|
||||
description = "Python bindings to Rust's persistent data structures (rpds)"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "rpds_py-0.13.2-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:1ceebd0ae4f3e9b2b6b553b51971921853ae4eebf3f54086be0565d59291e53d"},
|
||||
|
|
@ -3698,7 +3706,7 @@ pyobjc-framework-Cocoa = {version = "*", markers = "sys_platform == \"darwin\""}
|
|||
name = "send2trash"
|
||||
version = "1.8.2"
|
||||
description = "Send file to trash natively under Mac OS X, Windows and Linux"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
files = [
|
||||
{file = "Send2Trash-1.8.2-py3-none-any.whl", hash = "sha256:a384719d99c07ce1eefd6905d2decb6f8b7ed054025bb0e618919f945de4f679"},
|
||||
|
|
@ -3803,7 +3811,7 @@ files = [
|
|||
name = "sniffio"
|
||||
version = "1.3.0"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
|
||||
|
|
@ -4063,7 +4071,7 @@ files = [
|
|||
name = "terminado"
|
||||
version = "0.18.0"
|
||||
description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library."
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "terminado-0.18.0-py3-none-any.whl", hash = "sha256:87b0d96642d0fe5f5abd7783857b9cab167f221a39ff98e3b9619a788a3c0f2e"},
|
||||
|
|
@ -4084,7 +4092,7 @@ typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"]
|
|||
name = "tinycss2"
|
||||
version = "1.2.1"
|
||||
description = "A tiny CSS parser"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"},
|
||||
|
|
@ -4113,7 +4121,7 @@ files = [
|
|||
name = "tornado"
|
||||
version = "6.4"
|
||||
description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">= 3.8"
|
||||
files = [
|
||||
{file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"},
|
||||
|
|
@ -4153,7 +4161,7 @@ telegram = ["requests"]
|
|||
name = "traitlets"
|
||||
version = "5.14.0"
|
||||
description = "Traitlets Python configuration system"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "traitlets-5.14.0-py3-none-any.whl", hash = "sha256:f14949d23829023013c47df20b4a76ccd1a85effb786dc060f34de7948361b33"},
|
||||
|
|
@ -4216,7 +4224,7 @@ types-setuptools = "*"
|
|||
name = "types-python-dateutil"
|
||||
version = "2.8.19.14"
|
||||
description = "Typing stubs for python-dateutil"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"},
|
||||
|
|
@ -4249,7 +4257,7 @@ files = [
|
|||
name = "uri-template"
|
||||
version = "1.3.0"
|
||||
description = "RFC 6570 URI Template Processor"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7"},
|
||||
|
|
@ -4349,7 +4357,7 @@ files = [
|
|||
name = "webcolors"
|
||||
version = "1.13"
|
||||
description = "A library for working with the color formats defined by HTML and CSS."
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "webcolors-1.13-py3-none-any.whl", hash = "sha256:29bc7e8752c0a1bd4a1f03c14d6e6a72e93d82193738fa860cbff59d0fcc11bf"},
|
||||
|
|
@ -4364,7 +4372,7 @@ tests = ["pytest", "pytest-cov"]
|
|||
name = "webencodings"
|
||||
version = "0.5.1"
|
||||
description = "Character encoding aliases for legacy web content"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
|
||||
|
|
@ -4375,7 +4383,7 @@ files = [
|
|||
name = "websocket-client"
|
||||
version = "1.6.4"
|
||||
description = "WebSocket client for Python with low level API options"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"},
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@
|
|||
},
|
||||
"section_dir_layout": [
|
||||
"SquareToCircle.json",
|
||||
"SquareToCircle_0000.mp4",
|
||||
"SquareToCircle_0000_autocreated.mp4",
|
||||
"."
|
||||
],
|
||||
"section_index": [
|
||||
{
|
||||
"name": "autocreated",
|
||||
"type": "default.normal",
|
||||
"video": "SquareToCircle_0000.mp4",
|
||||
"video": "SquareToCircle_0000_autocreated.mp4",
|
||||
"codec_name": "h264",
|
||||
"width": 854,
|
||||
"height": 480,
|
||||
|
|
|
|||
|
|
@ -10,18 +10,18 @@
|
|||
},
|
||||
"section_dir_layout": [
|
||||
"SceneWithSections.json",
|
||||
"SceneWithSections_0004.mp4",
|
||||
"SceneWithSections_0003.mp4",
|
||||
"SceneWithSections_0002.mp4",
|
||||
"SceneWithSections_0001.mp4",
|
||||
"SceneWithSections_0000.mp4",
|
||||
"SceneWithSections_0004_unnamed.mp4",
|
||||
"SceneWithSections_0003_Prepare For Unforeseen Consequences..mp4",
|
||||
"SceneWithSections_0002_test.mp4",
|
||||
"SceneWithSections_0001_unnamed.mp4",
|
||||
"SceneWithSections_0000_autocreated.mp4",
|
||||
"."
|
||||
],
|
||||
"section_index": [
|
||||
{
|
||||
"name": "autocreated",
|
||||
"type": "default.normal",
|
||||
"video": "SceneWithSections_0000.mp4",
|
||||
"video": "SceneWithSections_0000_autocreated.mp4",
|
||||
"codec_name": "h264",
|
||||
"width": 854,
|
||||
"height": 480,
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
{
|
||||
"name": "unnamed",
|
||||
"type": "default.normal",
|
||||
"video": "SceneWithSections_0001.mp4",
|
||||
"video": "SceneWithSections_0001_unnamed.mp4",
|
||||
"codec_name": "h264",
|
||||
"width": 854,
|
||||
"height": 480,
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
{
|
||||
"name": "test",
|
||||
"type": "default.normal",
|
||||
"video": "SceneWithSections_0002.mp4",
|
||||
"video": "SceneWithSections_0002_test.mp4",
|
||||
"codec_name": "h264",
|
||||
"width": 854,
|
||||
"height": 480,
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
{
|
||||
"name": "Prepare For Unforeseen Consequences.",
|
||||
"type": "default.normal",
|
||||
"video": "SceneWithSections_0003.mp4",
|
||||
"video": "SceneWithSections_0003_Prepare For Unforeseen Consequences..mp4",
|
||||
"codec_name": "h264",
|
||||
"width": 854,
|
||||
"height": 480,
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
{
|
||||
"name": "unnamed",
|
||||
"type": "presentation.skip",
|
||||
"video": "SceneWithSections_0004.mp4",
|
||||
"video": "SceneWithSections_0004_unnamed.mp4",
|
||||
"codec_name": "h264",
|
||||
"width": 854,
|
||||
"height": 480,
|
||||
|
|
|
|||
|
|
@ -10,16 +10,16 @@
|
|||
},
|
||||
"section_dir_layout": [
|
||||
"ElaborateSceneWithSections.json",
|
||||
"ElaborateSceneWithSections_0003.mp4",
|
||||
"ElaborateSceneWithSections_0001.mp4",
|
||||
"ElaborateSceneWithSections_0000.mp4",
|
||||
"ElaborateSceneWithSections_0003_fade out.mp4",
|
||||
"ElaborateSceneWithSections_0001_transform to circle.mp4",
|
||||
"ElaborateSceneWithSections_0000_create square.mp4",
|
||||
"."
|
||||
],
|
||||
"section_index": [
|
||||
{
|
||||
"name": "create square",
|
||||
"type": "default.normal",
|
||||
"video": "ElaborateSceneWithSections_0000.mp4",
|
||||
"video": "ElaborateSceneWithSections_0000_create square.mp4",
|
||||
"codec_name": "h264",
|
||||
"width": 854,
|
||||
"height": 480,
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
{
|
||||
"name": "transform to circle",
|
||||
"type": "default.normal",
|
||||
"video": "ElaborateSceneWithSections_0001.mp4",
|
||||
"video": "ElaborateSceneWithSections_0001_transform to circle.mp4",
|
||||
"codec_name": "h264",
|
||||
"width": 854,
|
||||
"height": 480,
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
{
|
||||
"name": "fade out",
|
||||
"type": "default.normal",
|
||||
"video": "ElaborateSceneWithSections_0003.mp4",
|
||||
"video": "ElaborateSceneWithSections_0003_fade out.mp4",
|
||||
"codec_name": "h264",
|
||||
"width": 854,
|
||||
"height": 480,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,9 @@ def test_manim_cfg_subcommand():
|
|||
command = ["cfg"]
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, command, prog_name="manim")
|
||||
expected_output = """\
|
||||
expected_output = f"""\
|
||||
Manim Community v{__version__}
|
||||
|
||||
Usage: manim cfg [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
Manages Manim configuration files.
|
||||
|
|
@ -50,7 +52,9 @@ def test_manim_plugins_subcommand():
|
|||
command = ["plugins"]
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, command, prog_name="manim")
|
||||
expected_output = """\
|
||||
expected_output = f"""\
|
||||
Manim Community v{__version__}
|
||||
|
||||
Usage: manim plugins [OPTIONS]
|
||||
|
||||
Manages Manim plugins.
|
||||
|
|
@ -90,7 +94,9 @@ def test_manim_init_subcommand():
|
|||
command = ["init"]
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, command, prog_name="manim")
|
||||
expected_output = """\
|
||||
expected_output = f"""\
|
||||
Manim Community v{__version__}
|
||||
|
||||
Usage: manim init [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
Create a new project or insert a new scene.
|
||||
|
|
@ -135,24 +141,3 @@ def test_manim_init_scene(tmp_path):
|
|||
assert (Path(tmp_dir) / "main.py").exists()
|
||||
file_content = (Path(tmp_dir) / "main.py").read_text()
|
||||
assert "DefaultFileTestScene(Scene):" in file_content
|
||||
|
||||
|
||||
def test_manim_new_command():
|
||||
command = ["new"]
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, command, prog_name="manim")
|
||||
expected_output = """\
|
||||
Usage: manim new [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
(Deprecated) Create a new project or insert a new scene.
|
||||
|
||||
Options:
|
||||
--help Show this message and exit.
|
||||
|
||||
Commands:
|
||||
project Creates a new project.
|
||||
scene Inserts a SCENE to an existing FILE or creates a new FILE.
|
||||
|
||||
Made with <3 by Manim Community developers.
|
||||
"""
|
||||
assert dedent(expected_output) == result.output
|
||||
|
|
|
|||
|
|
@ -169,3 +169,13 @@ def test_animationgroup_is_passing_remover_to_nested_animationgroups():
|
|||
assert sqr_animation.remover
|
||||
assert circ_animation.remover
|
||||
assert polygon_animation.remover
|
||||
|
||||
|
||||
def test_empty_animation_group_fails():
|
||||
with pytest.raises(ValueError, match="Please add at least one Animation"):
|
||||
AnimationGroup().begin()
|
||||
|
||||
|
||||
def test_empty_animation_fails():
|
||||
with pytest.raises(ValueError, match="Please set the run_time to be positive"):
|
||||
FadeIn(None, run_time=0).begin()
|
||||
|
|
|
|||
14
tests/module/mobject/test_image.py
Normal file
14
tests/module/mobject/test_image.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from manim import ImageMobject
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype", [np.uint8, np.uint16])
|
||||
def test_invert_image(dtype):
|
||||
array = (255 * np.random.rand(10, 10, 4)).astype(dtype)
|
||||
image = ImageMobject(array, pixel_array_dtype=dtype, invert=True)
|
||||
assert image.pixel_array.dtype == dtype
|
||||
|
||||
array[:, :, :3] = np.iinfo(dtype).max - array[:, :, :3]
|
||||
assert np.allclose(array, image.pixel_array)
|
||||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import manim.utils.color as C
|
||||
from manim import VMobject
|
||||
from manim.mobject.vector_field import StreamLines
|
||||
|
||||
|
||||
def test_stroke_props_in_ctor():
|
||||
|
|
@ -24,3 +25,17 @@ def test_set_background_stroke():
|
|||
assert m.background_stroke_width == 2
|
||||
assert m.background_stroke_opacity == 0.8
|
||||
assert m.background_stroke_color.to_hex() == C.ORANGE.to_hex()
|
||||
|
||||
|
||||
def test_streamline_attributes_for_single_color():
|
||||
vector_field = StreamLines(
|
||||
lambda x: x, # It is not important what this function is.
|
||||
x_range=[-1, 1, 0.1],
|
||||
y_range=[-1, 1, 0.1],
|
||||
padding=0.1,
|
||||
stroke_width=1.0,
|
||||
opacity=0.2,
|
||||
color=C.BLUE_D,
|
||||
)
|
||||
assert vector_field[0].stroke_width == 1.0
|
||||
assert vector_field[0].stroke_opacity == 0.2
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -53,3 +53,22 @@ def test_vmobject_joint_types(scene):
|
|||
|
||||
lines.arrange(RIGHT, buff=1)
|
||||
scene.add(lines)
|
||||
|
||||
|
||||
@frames_comparison
|
||||
def test_vmobject_cap_styles(scene):
|
||||
arcs = VGroup(
|
||||
*[
|
||||
Arc(
|
||||
radius=1,
|
||||
start_angle=0,
|
||||
angle=TAU / 4,
|
||||
stroke_width=20,
|
||||
color=GREEN,
|
||||
cap_style=cap_style,
|
||||
)
|
||||
for cap_style in CapStyleType
|
||||
]
|
||||
)
|
||||
arcs.arrange(RIGHT, buff=1)
|
||||
scene.add(arcs)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue