mirror of
https://github.com/3b1b/manim.git
synced 2025-11-14 03:07:45 +00:00
commit
eeb4fdf270
28 changed files with 696 additions and 826 deletions
|
|
@ -8,38 +8,35 @@ they are only used inside manim.
|
|||
Frame and pixel shape
|
||||
---------------------
|
||||
|
||||
These values will be determined based on the ``camera`` configuration in default_config.yml or custom_config.yml
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ASPECT_RATIO = 16.0 / 9.0
|
||||
FRAME_HEIGHT = 8.0
|
||||
FRAME_WIDTH = FRAME_HEIGHT * ASPECT_RATIO
|
||||
FRAME_Y_RADIUS = FRAME_HEIGHT / 2
|
||||
FRAME_X_RADIUS = FRAME_WIDTH / 2
|
||||
ASPECT_RATIO
|
||||
FRAME_HEIGHT
|
||||
FRAME_WIDTH
|
||||
FRAME_Y_RADIUS
|
||||
FRAME_X_RADIUS
|
||||
|
||||
DEFAULT_PIXEL_HEIGHT = 1080
|
||||
DEFAULT_PIXEL_WIDTH = 1920
|
||||
DEFAULT_FPS = 30
|
||||
DEFAULT_PIXEL_HEIGHT
|
||||
DEFAULT_PIXEL_WIDTH
|
||||
DEFAULT_FPS
|
||||
|
||||
Buffs
|
||||
-----
|
||||
|
||||
.. code-block:: python
|
||||
These values will be determined based on the ``size`` configuration in default_config.yml or custom_config.yml
|
||||
|
||||
SMALL_BUFF = 0.1
|
||||
MED_SMALL_BUFF = 0.25
|
||||
MED_LARGE_BUFF = 0.5
|
||||
LARGE_BUFF = 1
|
||||
|
||||
DEFAULT_MOBJECT_TO_EDGE_BUFFER = MED_LARGE_BUFF # Distance between object and edge
|
||||
DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = MED_SMALL_BUFF # Distance between objects
|
||||
|
||||
Run times
|
||||
---------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
DEFAULT_POINTWISE_FUNCTION_RUN_TIME = 3.0
|
||||
DEFAULT_WAIT_TIME = 1.0
|
||||
SMALL_BUFF
|
||||
MED_SMALL_BUFF
|
||||
MED_LARGE_BUFF
|
||||
LARGE_BUFF
|
||||
|
||||
DEFAULT_MOBJECT_TO_EDGE_BUFF
|
||||
DEFAULT_MOBJECT_TO_MOBJECT_BUFF
|
||||
|
||||
Coordinates
|
||||
-----------
|
||||
|
|
@ -89,16 +86,11 @@ Text
|
|||
OBLIQUE = "OBLIQUE"
|
||||
BOLD = "BOLD"
|
||||
|
||||
Stroke width
|
||||
------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
DEFAULT_STROKE_WIDTH = 4
|
||||
|
||||
Colours
|
||||
-------
|
||||
|
||||
Color constants are determined based on the ``color`` configuration in default_config.yml or custom_config.yml
|
||||
|
||||
Here are the preview of default colours. (Modified from
|
||||
`elteoremadebeethoven <https://elteoremadebeethoven.github.io/manim_3feb_docs.github.io/html/_static/colors/colors.html>`_)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ custom_config
|
|||
running file under the ``output`` path, and save the output (``images/``
|
||||
or ``videos/``) in it.
|
||||
|
||||
- ``base``
|
||||
The root directory that will hold files, such as video files manim renders,
|
||||
or image resources that it pulls from
|
||||
|
||||
- ``output``
|
||||
Output file path, the videos will be saved in the ``videos/`` folder under it,
|
||||
and the pictures will be saved in the ``images/`` folder under it.
|
||||
|
|
@ -66,34 +70,62 @@ custom_config
|
|||
The directory for storing sound files to be used in ``Scene.add_sound()`` (
|
||||
including ``.wav`` and ``.mp3``).
|
||||
|
||||
- ``temporary_storage``
|
||||
- ``cache``
|
||||
The directory for storing temporarily generated cache files, including
|
||||
``Tex`` cache, ``Text`` cache and storage of object points.
|
||||
|
||||
``tex``
|
||||
|
||||
``window``
|
||||
----------
|
||||
|
||||
- ``position_string``
|
||||
The relative position of the playback window on the display (two characters,
|
||||
the first character means upper(U) / middle(O) / lower(D), the second character
|
||||
means left(L) / middle(O) / right(R)).
|
||||
|
||||
- ``monitor_index``
|
||||
If using multiple monitors, which one should the window show up in?
|
||||
|
||||
- ``full_screen``
|
||||
Should the preview window be full screen. If not, it defaults to half the screen
|
||||
|
||||
- ``position``
|
||||
This is an option to more manually set the default window position, in pixel
|
||||
coordinates, e.g. (500, 300)
|
||||
|
||||
- ``size``
|
||||
Option to more manually set the default window size, in pixel coordinates,
|
||||
e.g. (1920, 1080)
|
||||
|
||||
|
||||
``camera``
|
||||
----------
|
||||
|
||||
- ``resolution``
|
||||
Resolution to render at, e.g. (1920, 1080)
|
||||
|
||||
- ``background_color``
|
||||
Default background color of scenes
|
||||
|
||||
- ``fps``
|
||||
Framerate
|
||||
|
||||
- ``background_opacity``
|
||||
Opacity of the background
|
||||
|
||||
|
||||
``file_writer``
|
||||
---------------
|
||||
Configuration specifying how files are written, e.g. what ffmpeg parameters to use
|
||||
|
||||
|
||||
``scene``
|
||||
-------
|
||||
Some default configuration for the Scene class
|
||||
|
||||
- ``executable``
|
||||
The executable program used to compile LaTeX (``latex`` or ``xelatex -no-pdf``
|
||||
is recommended)
|
||||
|
||||
- ``template_file``
|
||||
LaTeX template used, in ``manimlib/tex_templates``
|
||||
|
||||
- ``intermediate_filetype``
|
||||
The type of intermediate vector file generated after compilation (``dvi`` if
|
||||
``latex`` is used, ``xdv`` if ``xelatex`` is used)
|
||||
|
||||
- ``text_to_replace``
|
||||
The text to be replaced in the template (needn't to change)
|
||||
|
||||
``universal_import_line``
|
||||
-------------------------
|
||||
|
||||
Import line that need to execute when entering interactive mode directly.
|
||||
|
||||
``style``
|
||||
---------
|
||||
``text``
|
||||
-------
|
||||
|
||||
- ``font``
|
||||
Default font of Text
|
||||
|
|
@ -101,57 +133,44 @@ Import line that need to execute when entering interactive mode directly.
|
|||
- ``text_alignment``
|
||||
Default text alignment for LaTeX
|
||||
|
||||
- ``background_color``
|
||||
Default background color
|
||||
|
||||
``window_position``
|
||||
-------------------
|
||||
|
||||
The relative position of the playback window on the display (two characters,
|
||||
the first character means upper(U) / middle(O) / lower(D), the second character
|
||||
means left(L) / middle(O) / right(R)).
|
||||
|
||||
``window_monitor``
|
||||
------------------
|
||||
|
||||
The number of the monitor you want the preview window to pop up on. (default is 0)
|
||||
|
||||
``full_screen``
|
||||
---------------
|
||||
|
||||
Whether open the window in full screen. (default is false)
|
||||
|
||||
``break_into_partial_movies``
|
||||
-----------------------------
|
||||
|
||||
If this is set to ``True``, then many small files will be written corresponding
|
||||
to each ``Scene.play`` and ``Scene.wait`` call, and these files will then be combined
|
||||
to form the full scene.
|
||||
|
||||
Sometimes video-editing is made easier when working with the broken up scene, which
|
||||
effectively has cuts at all the places you might want.
|
||||
|
||||
``camera_resolutions``
|
||||
----------------------
|
||||
|
||||
Export resolutions
|
||||
|
||||
- ``low``
|
||||
Low resolutions (default is 480p)
|
||||
|
||||
- ``medium``
|
||||
Medium resolutions (default is 720p)
|
||||
|
||||
- ``high``
|
||||
High resolutions (default is 1080p)
|
||||
|
||||
- ``ultra_high``
|
||||
Ultra high resolutions (default is 4K)
|
||||
|
||||
- ``default_resolutions``
|
||||
Default resolutions (one of the above four, default is high)
|
||||
|
||||
``fps``
|
||||
``tex``
|
||||
-------
|
||||
|
||||
Export frame rate. (default is 30)
|
||||
- ``template``
|
||||
Which configuration from the manimlib/tex_template.yml file should be used
|
||||
to determine the latex compiler to use, and what preamble to include for
|
||||
rendering tex.
|
||||
|
||||
|
||||
``sizes``
|
||||
---------
|
||||
|
||||
Valuess for various constants used in manimm to specify distances, like the height
|
||||
of the frame, the value of SMALL_BUFF, LARGE_BUFF, etc.
|
||||
|
||||
|
||||
``colors``
|
||||
----------
|
||||
|
||||
Color pallete to use, determining values of color constants like RED, BLUE_E, TEAL, etc.
|
||||
|
||||
``loglevel``
|
||||
------------
|
||||
|
||||
Can be DEBUG / INFO / WARNING / ERROR / CRITICAL
|
||||
|
||||
|
||||
``universal_import_line``
|
||||
-------------------------
|
||||
|
||||
Import line that need to execute when entering interactive mode directly.
|
||||
|
||||
|
||||
``ignore_manimlib_modules_on_reload``
|
||||
-------------------------------------
|
||||
|
||||
When calling ``reload`` during the interactive mode, imported modules are
|
||||
by default reloaded, in case the user writing a scene which pulls from various
|
||||
other files they have written. By default, modules withinn the manim library will
|
||||
be ignored, but one developing manim may want to set this to be False so that
|
||||
edits to the library are reloaded as well.
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@ Below is the directory structure of manim:
|
|||
├── family_ops.py # Process family members
|
||||
├── file_ops.py # Process files and directories
|
||||
├── images.py # Read image
|
||||
├── init_config.py # Configuration guide
|
||||
├── iterables.py # Functions related to list/dictionary processing
|
||||
├── paths.py # Curve path
|
||||
├── rate_functions.py # Some defined rate_functions
|
||||
|
|
|
|||
|
|
@ -1,9 +1,48 @@
|
|||
#!/usr/bin/env python
|
||||
from addict import Dict
|
||||
|
||||
from manimlib import __version__
|
||||
import manimlib.config
|
||||
import manimlib.logger
|
||||
import manimlib.utils.init_config
|
||||
from manimlib.reload_manager import ReloadManager
|
||||
from manimlib.config import manim_config
|
||||
from manimlib.config import parse_cli
|
||||
import manimlib.extract_scene
|
||||
from manimlib.window import Window
|
||||
|
||||
|
||||
from IPython.terminal.embed import KillEmbedded
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from argparse import Namespace
|
||||
|
||||
|
||||
def run_scenes():
|
||||
"""
|
||||
Runs the scenes in a loop and detects when a scene reload is requested.
|
||||
"""
|
||||
# Create a new dict to be able to upate without
|
||||
# altering global configuration
|
||||
scene_config = Dict(manim_config.scene)
|
||||
run_config = manim_config.run
|
||||
|
||||
if run_config.show_in_window:
|
||||
# Create a reusable window
|
||||
window = Window(**manim_config.window)
|
||||
scene_config.update(window=window)
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Blocking call since a scene may init an IPython shell()
|
||||
scenes = manimlib.extract_scene.main(scene_config, run_config)
|
||||
for scene in scenes:
|
||||
scene.run()
|
||||
return
|
||||
except KillEmbedded:
|
||||
# Requested via the `exit_raise` IPython runline magic
|
||||
# by means of the reload_scene() command
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
|
||||
def main():
|
||||
|
|
@ -12,18 +51,11 @@ def main():
|
|||
"""
|
||||
print(f"ManimGL \033[32mv{__version__}\033[0m")
|
||||
|
||||
args = manimlib.config.parse_cli()
|
||||
args = parse_cli()
|
||||
if args.version and args.file is None:
|
||||
return
|
||||
if args.log_level:
|
||||
manimlib.logger.log.setLevel(args.log_level)
|
||||
|
||||
if args.config:
|
||||
manimlib.utils.init_config.init_customization()
|
||||
return
|
||||
|
||||
reload_manager = ReloadManager(args)
|
||||
reload_manager.run()
|
||||
run_scenes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ from PIL import Image
|
|||
|
||||
from manimlib.camera.camera_frame import CameraFrame
|
||||
from manimlib.constants import BLACK
|
||||
from manimlib.constants import DEFAULT_FPS
|
||||
from manimlib.constants import DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH
|
||||
from manimlib.constants import DEFAULT_RESOLUTION
|
||||
from manimlib.constants import FRAME_HEIGHT
|
||||
from manimlib.constants import FRAME_WIDTH
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
|
|
@ -29,10 +28,9 @@ class Camera(object):
|
|||
window: Optional[Window] = None,
|
||||
background_image: Optional[str] = None,
|
||||
frame_config: dict = dict(),
|
||||
pixel_width: int = DEFAULT_PIXEL_WIDTH,
|
||||
pixel_height: int = DEFAULT_PIXEL_HEIGHT,
|
||||
fps: int = DEFAULT_FPS,
|
||||
# Note: frame height and width will be resized to match the pixel aspect ratio
|
||||
# Note: frame height and width will be resized to match this resolution aspect ratio
|
||||
resolution=DEFAULT_RESOLUTION,
|
||||
fps: int = 30,
|
||||
background_color: ManimColor = BLACK,
|
||||
background_opacity: float = 1.0,
|
||||
# Points in vectorized mobjects with norm greater
|
||||
|
|
@ -47,9 +45,9 @@ class Camera(object):
|
|||
# to set samples to be greater than 0.
|
||||
samples: int = 0,
|
||||
):
|
||||
self.background_image = background_image
|
||||
self.window = window
|
||||
self.default_pixel_shape = (pixel_width, pixel_height)
|
||||
self.background_image = background_image
|
||||
self.default_pixel_shape = resolution # Rename?
|
||||
self.fps = fps
|
||||
self.max_allowable_norm = max_allowable_norm
|
||||
self.image_mode = image_mode
|
||||
|
|
|
|||
|
|
@ -5,15 +5,13 @@ import colour
|
|||
import importlib
|
||||
import inspect
|
||||
import os
|
||||
import screeninfo
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
from functools import lru_cache
|
||||
from ast import literal_eval
|
||||
from addict import Dict
|
||||
|
||||
from manimlib.logger import log
|
||||
from manimlib.utils.dict_ops import merge_dicts_recursively
|
||||
from manimlib.utils.init_config import init_customization
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -21,6 +19,36 @@ if TYPE_CHECKING:
|
|||
from typing import Optional
|
||||
|
||||
|
||||
def initialize_manim_config() -> Dict:
|
||||
"""
|
||||
Return default configuration for various classes in manim, such as
|
||||
Scene, Window, Camera, and SceneFileWriter, as well as configuration
|
||||
determining how the scene is run (e.g. written to file or previewed in window).
|
||||
|
||||
The result is initially on the contents of default_config.yml in the manimlib directory,
|
||||
which can be further updated by a custom configuration file custom_config.yml.
|
||||
It is further updated based on command line argument.
|
||||
"""
|
||||
args = parse_cli()
|
||||
global_defaults_file = os.path.join(get_manim_dir(), "manimlib", "default_config.yml")
|
||||
config = merge_dicts_recursively(
|
||||
load_yaml(global_defaults_file),
|
||||
load_yaml("custom_config.yml"), # From current working directory
|
||||
load_yaml(args.config_file) if args.config_file else dict(),
|
||||
)
|
||||
|
||||
log.setLevel(args.log_level or config["log_level"])
|
||||
|
||||
update_directory_config(config)
|
||||
update_window_config(config, args)
|
||||
update_camera_config(config, args)
|
||||
update_file_writer_config(config, args)
|
||||
update_scene_config(config, args)
|
||||
update_run_config(config, args)
|
||||
|
||||
return Dict(config)
|
||||
|
||||
|
||||
def parse_cli():
|
||||
try:
|
||||
parser = argparse.ArgumentParser()
|
||||
|
|
@ -48,12 +76,12 @@ def parse_cli():
|
|||
parser.add_argument(
|
||||
"-l", "--low_quality",
|
||||
action="store_true",
|
||||
help="Render at a low quality (for faster rendering)",
|
||||
help="Render at 480p",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m", "--medium_quality",
|
||||
action="store_true",
|
||||
help="Render at a medium quality",
|
||||
help="Render at 720p",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--hd",
|
||||
|
|
@ -119,11 +147,6 @@ def parse_cli():
|
|||
action="store_true",
|
||||
help="Show the output file in finder",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--config",
|
||||
action="store_true",
|
||||
help="Guide for automatic configuration",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--file_name",
|
||||
help="Name for the movie or image file",
|
||||
|
|
@ -137,8 +160,9 @@ def parse_cli():
|
|||
)
|
||||
parser.add_argument(
|
||||
"-e", "--embed",
|
||||
help="Creates a new file where the line `self.embed` is inserted " + \
|
||||
"at the corresponding line number"
|
||||
metavar="LINE_NUMBER",
|
||||
help="Adds a breakpoint at the inputted file dropping into an " + \
|
||||
"interactive iPython session at that point of the code."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-r", "--resolution",
|
||||
|
|
@ -193,10 +217,101 @@ def parse_cli():
|
|||
sys.exit(2)
|
||||
|
||||
|
||||
def get_manim_dir():
|
||||
manimlib_module = importlib.import_module("manimlib")
|
||||
manimlib_dir = os.path.dirname(inspect.getabsfile(manimlib_module))
|
||||
return os.path.abspath(os.path.join(manimlib_dir, ".."))
|
||||
def update_directory_config(config: dict):
|
||||
dir_config = config["directories"]
|
||||
base = dir_config['base']
|
||||
for key, subdir in dir_config['subdirs'].items():
|
||||
dir_config[key] = os.path.join(base, subdir)
|
||||
|
||||
|
||||
def update_window_config(config: dict, args: Namespace):
|
||||
window_config = config["window"]
|
||||
for key in "position", "size":
|
||||
if window_config.get(key):
|
||||
window_config[key] = literal_eval(window_config[key])
|
||||
if args.full_screen:
|
||||
window_config["full_screen"] = True
|
||||
|
||||
|
||||
def update_camera_config(config: dict, args: Namespace):
|
||||
camera_config = config["camera"]
|
||||
arg_resolution = get_resolution_from_args(args, config["resolution_options"])
|
||||
camera_config["resolution"] = arg_resolution or literal_eval(camera_config["resolution"])
|
||||
if args.fps:
|
||||
camera_config["fps"] = args.fps
|
||||
if args.color:
|
||||
try:
|
||||
camera_config["background_color"] = colour.Color(args.color)
|
||||
except Exception:
|
||||
log.error("Please use a valid color")
|
||||
log.error(err)
|
||||
sys.exit(2)
|
||||
if args.transparent:
|
||||
camera_config["background_opacity"] = 0.0
|
||||
|
||||
|
||||
def update_file_writer_config(config: dict, args: Namespace):
|
||||
file_writer_config = config["file_writer"]
|
||||
file_writer_config.update(
|
||||
write_to_movie=(not args.skip_animations and args.write_file),
|
||||
save_last_frame=(args.skip_animations and args.write_file),
|
||||
save_pngs=args.save_pngs,
|
||||
png_mode=("RGBA" if args.transparent else "RGB"),
|
||||
movie_file_extension=(get_file_ext(args)),
|
||||
output_directory=get_output_directory(args, config),
|
||||
file_name=args.file_name,
|
||||
input_file_path=args.file or "",
|
||||
open_file_upon_completion=args.open,
|
||||
show_file_location_upon_completion=args.finder,
|
||||
quiet=args.quiet,
|
||||
)
|
||||
|
||||
if args.vcodec:
|
||||
file_writer_config["video_codec"] = args.vcodec
|
||||
elif args.transparent:
|
||||
file_writer_config["video_codec"] = 'prores_ks'
|
||||
file_writer_config["pixel_format"] = ''
|
||||
elif args.gif:
|
||||
file_writer_config["video_codec"] = ''
|
||||
|
||||
if args.pix_fmt:
|
||||
file_writer_config["pixel_format"] = args.pix_fmt
|
||||
|
||||
|
||||
def update_scene_config(config: dict, args: Namespace):
|
||||
scene_config = config["scene"]
|
||||
start, end = get_animations_numbers(args)
|
||||
scene_config.update(
|
||||
# Note, Scene.__init__ makes use of both manimlib.camera and
|
||||
# manimlib.file_writer below, so the arguments here are just for
|
||||
# any future specifications beyond what the global configuration holds
|
||||
camera_config=dict(),
|
||||
file_writer_config=dict(),
|
||||
skip_animations=args.skip_animations,
|
||||
start_at_animation_number=start,
|
||||
end_at_animation_number=end,
|
||||
presenter_mode=args.presenter_mode,
|
||||
)
|
||||
if args.leave_progress_bars:
|
||||
scene_config["leave_progress_bars"] = True
|
||||
if args.show_animation_progress:
|
||||
scene_config["show_animation_progress"] = True
|
||||
|
||||
|
||||
def update_run_config(config: dict, args: Namespace):
|
||||
config["run"] = dict(
|
||||
file_name=args.file,
|
||||
embed_line=(int(args.embed) if args.embed is not None else None),
|
||||
is_reload=False,
|
||||
prerun=args.prerun,
|
||||
scene_names=args.scene_names,
|
||||
quiet=args.quiet or args.write_all,
|
||||
write_all=args.write_all,
|
||||
show_in_window=not args.write_file
|
||||
)
|
||||
|
||||
|
||||
# Helpers for the functions above
|
||||
|
||||
|
||||
def load_yaml(file_path: str):
|
||||
|
|
@ -207,22 +322,24 @@ def load_yaml(file_path: str):
|
|||
return {}
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_global_config():
|
||||
args = parse_cli()
|
||||
global_defaults_file = os.path.join(get_manim_dir(), "manimlib", "default_config.yml")
|
||||
config = merge_dicts_recursively(
|
||||
load_yaml(global_defaults_file),
|
||||
load_yaml("custom_config.yml"), # From current working directory
|
||||
load_yaml(args.config_file) if args.config_file else {},
|
||||
)
|
||||
def get_manim_dir():
|
||||
manimlib_module = importlib.import_module("manimlib")
|
||||
manimlib_dir = os.path.dirname(inspect.getabsfile(manimlib_module))
|
||||
return os.path.abspath(os.path.join(manimlib_dir, ".."))
|
||||
|
||||
# Set the subdirectories
|
||||
base = config['directories']['base']
|
||||
for key, subdir in config['directories']['subdirs'].items():
|
||||
config['directories'][key] = os.path.join(base, subdir)
|
||||
|
||||
return config
|
||||
def get_resolution_from_args(args: Optional[Namespace], resolution_options: dict) -> Optional[tuple[int, int]]:
|
||||
if args.resolution:
|
||||
return tuple(map(int, args.resolution.split("x")))
|
||||
if args.low_quality:
|
||||
return literal_eval(resolution_options["low"])
|
||||
if args.medium_quality:
|
||||
return literal_eval(resolution_options["med"])
|
||||
if args.hd:
|
||||
return literal_eval(resolution_options["high"])
|
||||
if args.uhd:
|
||||
return literal_eval(resolution_options["4k"])
|
||||
return None
|
||||
|
||||
|
||||
def get_file_ext(args: Namespace) -> str:
|
||||
|
|
@ -245,8 +362,8 @@ def get_animations_numbers(args: Namespace) -> tuple[int | None, int | None]:
|
|||
return int(stan), None
|
||||
|
||||
|
||||
def get_output_directory(args: Namespace, global_config: dict) -> str:
|
||||
dir_config = global_config["directories"]
|
||||
def get_output_directory(args: Namespace, config: dict) -> str:
|
||||
dir_config = config["directories"]
|
||||
output_directory = args.video_dir or dir_config["output"]
|
||||
if dir_config["mirror_module_path"] and args.file:
|
||||
to_cut = dir_config["removed_mirror_prefix"]
|
||||
|
|
@ -258,142 +375,5 @@ def get_output_directory(args: Namespace, global_config: dict) -> str:
|
|||
return output_directory
|
||||
|
||||
|
||||
def get_file_writer_config(args: Namespace, global_config: dict) -> dict:
|
||||
result = {
|
||||
"write_to_movie": not args.skip_animations and args.write_file,
|
||||
"save_last_frame": args.skip_animations and args.write_file,
|
||||
"save_pngs": args.save_pngs,
|
||||
# If -t is passed in (for transparent), this will be RGBA
|
||||
"png_mode": "RGBA" if args.transparent else "RGB",
|
||||
"movie_file_extension": get_file_ext(args),
|
||||
"output_directory": get_output_directory(args, global_config),
|
||||
"file_name": args.file_name,
|
||||
"input_file_path": args.file or "",
|
||||
"open_file_upon_completion": args.open,
|
||||
"show_file_location_upon_completion": args.finder,
|
||||
"quiet": args.quiet,
|
||||
**global_config["file_writer_config"],
|
||||
}
|
||||
|
||||
if args.vcodec:
|
||||
result["video_codec"] = args.vcodec
|
||||
elif args.transparent:
|
||||
result["video_codec"] = 'prores_ks'
|
||||
result["pixel_format"] = ''
|
||||
elif args.gif:
|
||||
result["video_codec"] = ''
|
||||
|
||||
if args.pix_fmt:
|
||||
result["pixel_format"] = args.pix_fmt
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_resolution(args: Optional[Namespace] = None, global_config: Optional[dict] = None):
|
||||
args = args or parse_cli()
|
||||
global_config = global_config or get_global_config()
|
||||
|
||||
camera_resolutions = global_config["camera_resolutions"]
|
||||
if args.resolution:
|
||||
resolution = args.resolution
|
||||
elif args.low_quality:
|
||||
resolution = camera_resolutions["low"]
|
||||
elif args.medium_quality:
|
||||
resolution = camera_resolutions["med"]
|
||||
elif args.hd:
|
||||
resolution = camera_resolutions["high"]
|
||||
elif args.uhd:
|
||||
resolution = camera_resolutions["4k"]
|
||||
else:
|
||||
resolution = camera_resolutions[camera_resolutions["default_resolution"]]
|
||||
|
||||
width_str, height_str = resolution.split("x")
|
||||
return int(width_str), int(height_str)
|
||||
|
||||
|
||||
def get_window_config(args: Namespace, global_config: dict) -> dict:
|
||||
# Default to making window half the screen size
|
||||
# but make it full screen if -f is passed in
|
||||
try:
|
||||
monitors = screeninfo.get_monitors()
|
||||
except screeninfo.ScreenInfoError:
|
||||
# Default fallback
|
||||
monitors = [screeninfo.Monitor(width=1920, height=1080)]
|
||||
mon_index = global_config["window_monitor"]
|
||||
monitor = monitors[min(mon_index, len(monitors) - 1)]
|
||||
|
||||
width, height = get_resolution(args, global_config)
|
||||
|
||||
aspect_ratio = width / height
|
||||
window_width = monitor.width
|
||||
if not (args.full_screen or global_config["full_screen"]):
|
||||
window_width //= 2
|
||||
window_height = int(window_width / aspect_ratio)
|
||||
return dict(size=(window_width, window_height))
|
||||
|
||||
|
||||
def get_camera_config(args: Optional[Namespace] = None, global_config: Optional[dict] = None) -> dict:
|
||||
args = args or parse_cli()
|
||||
global_config = global_config or get_global_config()
|
||||
|
||||
width, height = get_resolution(args, global_config)
|
||||
fps = int(args.fps or global_config["fps"])
|
||||
|
||||
camera_config = {
|
||||
"pixel_width": width,
|
||||
"pixel_height": height,
|
||||
"fps": fps,
|
||||
}
|
||||
|
||||
try:
|
||||
bg_color = args.color or global_config["style"]["background_color"]
|
||||
camera_config["background_color"] = colour.Color(bg_color)
|
||||
except ValueError as err:
|
||||
log.error("Please use a valid color")
|
||||
log.error(err)
|
||||
sys.exit(2)
|
||||
|
||||
# If rendering a transparent image/movie, make sure the
|
||||
# scene has a background opacity of 0
|
||||
if args.transparent:
|
||||
camera_config["background_opacity"] = 0
|
||||
|
||||
return camera_config
|
||||
|
||||
|
||||
def get_scene_config(args: Namespace) -> dict:
|
||||
"""
|
||||
Returns a dictionary to be used as key word arguments for Scene
|
||||
"""
|
||||
global_config = get_global_config()
|
||||
camera_config = get_camera_config(args, global_config)
|
||||
file_writer_config = get_file_writer_config(args, global_config)
|
||||
start, end = get_animations_numbers(args)
|
||||
|
||||
return {
|
||||
"file_writer_config": file_writer_config,
|
||||
"camera_config": camera_config,
|
||||
"skip_animations": args.skip_animations,
|
||||
"start_at_animation_number": start,
|
||||
"end_at_animation_number": end,
|
||||
"presenter_mode": args.presenter_mode,
|
||||
"leave_progress_bars": args.leave_progress_bars,
|
||||
"show_animation_progress": args.show_animation_progress,
|
||||
"embed_exception_mode": global_config["embed_exception_mode"],
|
||||
"embed_error_sound": global_config["embed_error_sound"],
|
||||
}
|
||||
|
||||
|
||||
def get_run_config(args: Namespace):
|
||||
window_config = get_window_config(args, get_global_config())
|
||||
return {
|
||||
"file_name": args.file,
|
||||
"embed_line": int(args.embed) if args.embed is not None else None,
|
||||
"is_reload": False,
|
||||
"prerun": args.prerun,
|
||||
"scene_names": args.scene_names,
|
||||
"quiet": args.quiet or args.write_all,
|
||||
"write_all": args.write_all,
|
||||
"window_config": window_config,
|
||||
"show_in_window": not args.write_file
|
||||
}
|
||||
# Create global configuration
|
||||
manim_config: Dict = initialize_manim_config()
|
||||
|
|
|
|||
|
|
@ -1,44 +1,39 @@
|
|||
from __future__ import annotations
|
||||
import numpy as np
|
||||
|
||||
from manimlib.config import get_resolution
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from typing import List
|
||||
from manimlib.typing import ManimColor, Vect3
|
||||
|
||||
# See manimlib/default_config.yml
|
||||
from manimlib.config import manim_config
|
||||
|
||||
# TODO, it feels a bit unprincipled to have some global constants
|
||||
# depend on the output of this function, and for all that configuration
|
||||
# code to be run merely upon importing from this file.
|
||||
DEFAULT_RESOLUTION: tuple[int, int] = get_resolution()
|
||||
DEFAULT_PIXEL_WIDTH = DEFAULT_RESOLUTION[0]
|
||||
DEFAULT_PIXEL_HEIGHT = DEFAULT_RESOLUTION[1]
|
||||
DEFAULT_FPS: int = 30
|
||||
|
||||
DEFAULT_RESOLUTION: tuple[int, int] = manim_config.camera.resolution
|
||||
DEFAULT_PIXEL_WIDTH: int = DEFAULT_RESOLUTION[0]
|
||||
DEFAULT_PIXEL_HEIGHT: int = DEFAULT_RESOLUTION[1]
|
||||
|
||||
# Sizes relevant to default camera frame
|
||||
ASPECT_RATIO: float = DEFAULT_PIXEL_WIDTH / DEFAULT_PIXEL_HEIGHT
|
||||
FRAME_HEIGHT: float = 8.0
|
||||
FRAME_HEIGHT: float = manim_config.sizes.frame_height
|
||||
FRAME_WIDTH: float = FRAME_HEIGHT * ASPECT_RATIO
|
||||
FRAME_SHAPE: tuple[float, float] = (FRAME_WIDTH, FRAME_HEIGHT)
|
||||
FRAME_Y_RADIUS: float = FRAME_HEIGHT / 2
|
||||
FRAME_X_RADIUS: float = FRAME_WIDTH / 2
|
||||
|
||||
|
||||
SMALL_BUFF: float = 0.1
|
||||
MED_SMALL_BUFF: float = 0.25
|
||||
MED_LARGE_BUFF: float = 0.5
|
||||
LARGE_BUFF: float = 1
|
||||
# Helpful values for positioning mobjects
|
||||
SMALL_BUFF: float = manim_config.sizes.small_buff
|
||||
MED_SMALL_BUFF: float = manim_config.sizes.med_small_buff
|
||||
MED_LARGE_BUFF: float = manim_config.sizes.med_large_buff
|
||||
LARGE_BUFF: float = manim_config.sizes.large_buff
|
||||
|
||||
DEFAULT_MOBJECT_TO_EDGE_BUFFER: float = MED_LARGE_BUFF
|
||||
DEFAULT_MOBJECT_TO_MOBJECT_BUFFER: float = MED_SMALL_BUFF
|
||||
|
||||
|
||||
# In seconds
|
||||
DEFAULT_WAIT_TIME: float = 1.0
|
||||
DEFAULT_MOBJECT_TO_EDGE_BUFF: float = manim_config.sizes.default_mobject_to_edge_buff
|
||||
DEFAULT_MOBJECT_TO_MOBJECT_BUFF: float = manim_config.sizes.default_mobject_to_mobject_buff
|
||||
|
||||
|
||||
# Standard vectors
|
||||
ORIGIN: Vect3 = np.array([0., 0., 0.])
|
||||
UP: Vect3 = np.array([0., 1., 0.])
|
||||
DOWN: Vect3 = np.array([0., -1., 0.])
|
||||
|
|
@ -63,6 +58,7 @@ BOTTOM: Vect3 = FRAME_Y_RADIUS * DOWN
|
|||
LEFT_SIDE: Vect3 = FRAME_X_RADIUS * LEFT
|
||||
RIGHT_SIDE: Vect3 = FRAME_X_RADIUS * RIGHT
|
||||
|
||||
# Angles
|
||||
PI: float = np.pi
|
||||
TAU: float = 2 * PI
|
||||
DEGREES: float = TAU / 360
|
||||
|
|
@ -70,100 +66,71 @@ DEGREES: float = TAU / 360
|
|||
# when juxtaposed with expressions like 30 * DEGREES
|
||||
RADIANS: float = 1
|
||||
|
||||
FFMPEG_BIN: str = "ffmpeg"
|
||||
|
||||
JOINT_TYPE_MAP: dict = {
|
||||
"no_joint": 0,
|
||||
"auto": 1,
|
||||
"bevel": 2,
|
||||
"miter": 3,
|
||||
}
|
||||
|
||||
# Related to Text
|
||||
NORMAL: str = "NORMAL"
|
||||
ITALIC: str = "ITALIC"
|
||||
OBLIQUE: str = "OBLIQUE"
|
||||
BOLD: str = "BOLD"
|
||||
|
||||
DEFAULT_STROKE_WIDTH: float = 4
|
||||
|
||||
# For keyboard interactions
|
||||
CTRL_SYMBOL: int = 65508
|
||||
SHIFT_SYMBOL: int = 65505
|
||||
COMMAND_SYMBOL: int = 65517
|
||||
DELETE_SYMBOL: int = 65288
|
||||
ARROW_SYMBOLS: list[int] = list(range(65361, 65365))
|
||||
DEFAULT_STROKE_WIDTH: float = manim_config.vmobject.default_stroke_width
|
||||
|
||||
# Colors
|
||||
BLUE_E: ManimColor = manim_config.colors.blue_e
|
||||
BLUE_D: ManimColor = manim_config.colors.blue_d
|
||||
BLUE_C: ManimColor = manim_config.colors.blue_c
|
||||
BLUE_B: ManimColor = manim_config.colors.blue_b
|
||||
BLUE_A: ManimColor = manim_config.colors.blue_a
|
||||
TEAL_E: ManimColor = manim_config.colors.teal_e
|
||||
TEAL_D: ManimColor = manim_config.colors.teal_d
|
||||
TEAL_C: ManimColor = manim_config.colors.teal_c
|
||||
TEAL_B: ManimColor = manim_config.colors.teal_b
|
||||
TEAL_A: ManimColor = manim_config.colors.teal_a
|
||||
GREEN_E: ManimColor = manim_config.colors.green_e
|
||||
GREEN_D: ManimColor = manim_config.colors.green_d
|
||||
GREEN_C: ManimColor = manim_config.colors.green_c
|
||||
GREEN_B: ManimColor = manim_config.colors.green_b
|
||||
GREEN_A: ManimColor = manim_config.colors.green_a
|
||||
YELLOW_E: ManimColor = manim_config.colors.yellow_e
|
||||
YELLOW_D: ManimColor = manim_config.colors.yellow_d
|
||||
YELLOW_C: ManimColor = manim_config.colors.yellow_c
|
||||
YELLOW_B: ManimColor = manim_config.colors.yellow_b
|
||||
YELLOW_A: ManimColor = manim_config.colors.yellow_a
|
||||
GOLD_E: ManimColor = manim_config.colors.gold_e
|
||||
GOLD_D: ManimColor = manim_config.colors.gold_d
|
||||
GOLD_C: ManimColor = manim_config.colors.gold_c
|
||||
GOLD_B: ManimColor = manim_config.colors.gold_b
|
||||
GOLD_A: ManimColor = manim_config.colors.gold_a
|
||||
RED_E: ManimColor = manim_config.colors.red_e
|
||||
RED_D: ManimColor = manim_config.colors.red_d
|
||||
RED_C: ManimColor = manim_config.colors.red_c
|
||||
RED_B: ManimColor = manim_config.colors.red_b
|
||||
RED_A: ManimColor = manim_config.colors.red_a
|
||||
MAROON_E: ManimColor = manim_config.colors.maroon_e
|
||||
MAROON_D: ManimColor = manim_config.colors.maroon_d
|
||||
MAROON_C: ManimColor = manim_config.colors.maroon_c
|
||||
MAROON_B: ManimColor = manim_config.colors.maroon_b
|
||||
MAROON_A: ManimColor = manim_config.colors.maroon_a
|
||||
PURPLE_E: ManimColor = manim_config.colors.purple_e
|
||||
PURPLE_D: ManimColor = manim_config.colors.purple_d
|
||||
PURPLE_C: ManimColor = manim_config.colors.purple_c
|
||||
PURPLE_B: ManimColor = manim_config.colors.purple_b
|
||||
PURPLE_A: ManimColor = manim_config.colors.purple_a
|
||||
GREY_E: ManimColor = manim_config.colors.grey_e
|
||||
GREY_D: ManimColor = manim_config.colors.grey_d
|
||||
GREY_C: ManimColor = manim_config.colors.grey_c
|
||||
GREY_B: ManimColor = manim_config.colors.grey_b
|
||||
GREY_A: ManimColor = manim_config.colors.grey_a
|
||||
WHITE: ManimColor = manim_config.colors.white
|
||||
BLACK: ManimColor = manim_config.colors.black
|
||||
GREY_BROWN: ManimColor = manim_config.colors.grey_brown
|
||||
DARK_BROWN: ManimColor = manim_config.colors.dark_brown
|
||||
LIGHT_BROWN: ManimColor = manim_config.colors.light_brown
|
||||
PINK: ManimColor = manim_config.colors.pink
|
||||
LIGHT_PINK: ManimColor = manim_config.colors.light_pink
|
||||
GREEN_SCREEN: ManimColor = manim_config.colors.green_screen
|
||||
ORANGE: ManimColor = manim_config.colors.orange
|
||||
|
||||
BLUE_E: ManimColor = "#1C758A"
|
||||
BLUE_D: ManimColor = "#29ABCA"
|
||||
BLUE_C: ManimColor = "#58C4DD"
|
||||
BLUE_B: ManimColor = "#9CDCEB"
|
||||
BLUE_A: ManimColor = "#C7E9F1"
|
||||
TEAL_E: ManimColor = "#49A88F"
|
||||
TEAL_D: ManimColor = "#55C1A7"
|
||||
TEAL_C: ManimColor = "#5CD0B3"
|
||||
TEAL_B: ManimColor = "#76DDC0"
|
||||
TEAL_A: ManimColor = "#ACEAD7"
|
||||
GREEN_E: ManimColor = "#699C52"
|
||||
GREEN_D: ManimColor = "#77B05D"
|
||||
GREEN_C: ManimColor = "#83C167"
|
||||
GREEN_B: ManimColor = "#A6CF8C"
|
||||
GREEN_A: ManimColor = "#C9E2AE"
|
||||
YELLOW_E: ManimColor = "#E8C11C"
|
||||
YELLOW_D: ManimColor = "#F4D345"
|
||||
YELLOW_C: ManimColor = "#FFFF00"
|
||||
YELLOW_B: ManimColor = "#FFEA94"
|
||||
YELLOW_A: ManimColor = "#FFF1B6"
|
||||
GOLD_E: ManimColor = "#C78D46"
|
||||
GOLD_D: ManimColor = "#E1A158"
|
||||
GOLD_C: ManimColor = "#F0AC5F"
|
||||
GOLD_B: ManimColor = "#F9B775"
|
||||
GOLD_A: ManimColor = "#F7C797"
|
||||
RED_E: ManimColor = "#CF5044"
|
||||
RED_D: ManimColor = "#E65A4C"
|
||||
RED_C: ManimColor = "#FC6255"
|
||||
RED_B: ManimColor = "#FF8080"
|
||||
RED_A: ManimColor = "#F7A1A3"
|
||||
MAROON_E: ManimColor = "#94424F"
|
||||
MAROON_D: ManimColor = "#A24D61"
|
||||
MAROON_C: ManimColor = "#C55F73"
|
||||
MAROON_B: ManimColor = "#EC92AB"
|
||||
MAROON_A: ManimColor = "#ECABC1"
|
||||
PURPLE_E: ManimColor = "#644172"
|
||||
PURPLE_D: ManimColor = "#715582"
|
||||
PURPLE_C: ManimColor = "#9A72AC"
|
||||
PURPLE_B: ManimColor = "#B189C6"
|
||||
PURPLE_A: ManimColor = "#CAA3E8"
|
||||
GREY_E: ManimColor = "#222222"
|
||||
GREY_D: ManimColor = "#444444"
|
||||
GREY_C: ManimColor = "#888888"
|
||||
GREY_B: ManimColor = "#BBBBBB"
|
||||
GREY_A: ManimColor = "#DDDDDD"
|
||||
WHITE: ManimColor = "#FFFFFF"
|
||||
BLACK: ManimColor = "#000000"
|
||||
GREY_BROWN: ManimColor = "#736357"
|
||||
DARK_BROWN: ManimColor = "#8B4513"
|
||||
LIGHT_BROWN: ManimColor = "#CD853F"
|
||||
PINK: ManimColor = "#D147BD"
|
||||
LIGHT_PINK: ManimColor = "#DC75CD"
|
||||
GREEN_SCREEN: ManimColor = "#00FF00"
|
||||
ORANGE: ManimColor = "#FF862F"
|
||||
|
||||
MANIM_COLORS: List[ManimColor] = [
|
||||
BLACK, GREY_E, GREY_D, GREY_C, GREY_B, GREY_A, WHITE,
|
||||
BLUE_E, BLUE_D, BLUE_C, BLUE_B, BLUE_A,
|
||||
TEAL_E, TEAL_D, TEAL_C, TEAL_B, TEAL_A,
|
||||
GREEN_E, GREEN_D, GREEN_C, GREEN_B, GREEN_A,
|
||||
YELLOW_E, YELLOW_D, YELLOW_C, YELLOW_B, YELLOW_A,
|
||||
GOLD_E, GOLD_D, GOLD_C, GOLD_B, GOLD_A,
|
||||
RED_E, RED_D, RED_C, RED_B, RED_A,
|
||||
MAROON_E, MAROON_D, MAROON_C, MAROON_B, MAROON_A,
|
||||
PURPLE_E, PURPLE_D, PURPLE_C, PURPLE_B, PURPLE_A,
|
||||
GREY_BROWN, DARK_BROWN, LIGHT_BROWN,
|
||||
PINK, LIGHT_PINK,
|
||||
]
|
||||
MANIM_COLORS: List[ManimColor] = list(manim_config.colors.values())
|
||||
|
||||
# Abbreviated names for the "median" colors
|
||||
BLUE: ManimColor = BLUE_C
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
# This file determines the default configuration for how manim is
|
||||
# run, including names for directories it will write to, default
|
||||
# parameters for various classes, style choices, etc. To customize
|
||||
# your own, create a custom_config.yml file in whatever directory
|
||||
# you are running manim. For 3blue1brown, for instance, mind is
|
||||
# here: https://github.com/3b1b/videos/blob/master/custom_config.yml
|
||||
|
||||
# Alternatively, you can create it whereever you like, and on running
|
||||
# manim, pass in `--config_file /path/to/custom/config/file.yml`
|
||||
|
||||
directories:
|
||||
# Set this to true if you want the path to video files
|
||||
# to match the directory structure of the path to the
|
||||
|
|
@ -25,19 +35,23 @@ directories:
|
|||
# it stores this saved data to whatever directory appdirs.user_cache_dir("manim") returns,
|
||||
# but here a user can specify a different cache location
|
||||
cache: ""
|
||||
universal_import_line: "from manimlib import *"
|
||||
style:
|
||||
tex_template: "default"
|
||||
font: "Consolas"
|
||||
text_alignment: "LEFT"
|
||||
window:
|
||||
# The position of window on screen. UR -> Upper Right, and likewise DL -> Down and Left,
|
||||
# UO would be upper middle, etc.
|
||||
position_string: UR
|
||||
# If using multiple monitors, which one should show the window
|
||||
monitor_index: 0
|
||||
# If not full screen, the default to give it half the screen width
|
||||
full_screen: False
|
||||
# Other optional specifications that override the above include:
|
||||
# position: (500, 500) # Specific position, in pixel coordiantes, for upper right corner
|
||||
# size: (1920, 1080) # Specific size, in pixels
|
||||
camera:
|
||||
resolution: (1920, 1080)
|
||||
background_color: "#333333"
|
||||
# Set the position of preview window, you can use directions, e.g. UL/DR/OL/OO/...
|
||||
# also, you can also specify the position(pixel) of the upper left corner of
|
||||
# the window on the monitor, e.g. "960,540"
|
||||
window_position: UR
|
||||
window_monitor: 0
|
||||
full_screen: False
|
||||
file_writer_config:
|
||||
fps: 30
|
||||
background_opacity: 1.0
|
||||
file_writer:
|
||||
# If break_into_partial_movies is set to True, then many small
|
||||
# files will be written corresponding to each Scene.play and
|
||||
# Scene.wait call, and these files will then be combined
|
||||
|
|
@ -45,17 +59,106 @@ file_writer_config:
|
|||
# easier when working with the broken up scene, which
|
||||
# effectively has cuts at all the places you might want.
|
||||
break_into_partial_movies: False
|
||||
# What command to use for ffmpeg
|
||||
ffmpeg_bin: "ffmpeg"
|
||||
# Parameters to pass into ffmpeg
|
||||
video_codec: "libx264"
|
||||
pixel_format: "yuv420p"
|
||||
saturation: 1.0
|
||||
gamma: 1.0
|
||||
camera_resolutions:
|
||||
low: "854x480"
|
||||
med: "1280x720"
|
||||
high: "1920x1080"
|
||||
4k: "3840x2160"
|
||||
default_resolution: "high"
|
||||
fps: 30
|
||||
embed_exception_mode: "Verbose"
|
||||
embed_error_sound: False
|
||||
# Most of the scene configuration will come from CLI arguments,
|
||||
# but defaults can be set here
|
||||
scene:
|
||||
show_animation_progress: False
|
||||
leave_progress_bars: False
|
||||
# How long does a scene pause on Scene.wait calls
|
||||
default_wait_time: 1.0
|
||||
vmobject:
|
||||
default_stroke_width: 4.0
|
||||
tex:
|
||||
# See tex_templates.yml
|
||||
template: "default"
|
||||
text:
|
||||
font: "Consolas"
|
||||
alignment: "LEFT"
|
||||
embed:
|
||||
exception_mode: "Verbose"
|
||||
resolution_options:
|
||||
# When the user passes in -l, -m, --hd or --uhd, these are the corresponding
|
||||
# resolutions
|
||||
low: (854, 480)
|
||||
med: (1280, 720)
|
||||
high: (1920, 1080)
|
||||
4k: (3840, 2160)
|
||||
sizes:
|
||||
# This determines the scale of the manim coordinate system with respect to
|
||||
# the viewing frame
|
||||
frame_height: 8.0
|
||||
# These determine the constants SMALL_BUFF, MED_SMALL_BUFF, etc., useful
|
||||
# for nudging things around and having default spacing values
|
||||
small_buff: 0.1
|
||||
med_small_buff: 0.25
|
||||
med_large_buff: 0.5
|
||||
large_buff: 1.0
|
||||
# Default buffers used in Mobject.next_to or Mobject.to_edge
|
||||
default_mobject_to_edge_buff: 0.5
|
||||
default_mobject_to_mobject_buff: 0.25
|
||||
colors:
|
||||
blue_e: "#1C758A"
|
||||
blue_d: "#29ABCA"
|
||||
blue_c: "#58C4DD"
|
||||
blue_b: "#9CDCEB"
|
||||
blue_a: "#C7E9F1"
|
||||
teal_e: "#49A88F"
|
||||
teal_d: "#55C1A7"
|
||||
teal_c: "#5CD0B3"
|
||||
teal_b: "#76DDC0"
|
||||
teal_a: "#ACEAD7"
|
||||
green_e: "#699C52"
|
||||
green_d: "#77B05D"
|
||||
green_c: "#83C167"
|
||||
green_b: "#A6CF8C"
|
||||
green_a: "#C9E2AE"
|
||||
yellow_e: "#E8C11C"
|
||||
yellow_d: "#F4D345"
|
||||
yellow_c: "#FFFF00"
|
||||
yellow_b: "#FFEA94"
|
||||
yellow_a: "#FFF1B6"
|
||||
gold_e: "#C78D46"
|
||||
gold_d: "#E1A158"
|
||||
gold_c: "#F0AC5F"
|
||||
gold_b: "#F9B775"
|
||||
gold_a: "#F7C797"
|
||||
red_e: "#CF5044"
|
||||
red_d: "#E65A4C"
|
||||
red_c: "#FC6255"
|
||||
red_b: "#FF8080"
|
||||
red_a: "#F7A1A3"
|
||||
maroon_e: "#94424F"
|
||||
maroon_d: "#A24D61"
|
||||
maroon_c: "#C55F73"
|
||||
maroon_b: "#EC92AB"
|
||||
maroon_a: "#ECABC1"
|
||||
purple_e: "#644172"
|
||||
purple_d: "#715582"
|
||||
purple_c: "#9A72AC"
|
||||
purple_b: "#B189C6"
|
||||
purple_a: "#CAA3E8"
|
||||
grey_e: "#222222"
|
||||
grey_d: "#444444"
|
||||
grey_c: "#888888"
|
||||
grey_b: "#BBBBBB"
|
||||
grey_a: "#DDDDDD"
|
||||
white: "#FFFFFF"
|
||||
black: "#000000"
|
||||
grey_brown: "#736357"
|
||||
dark_brown: "#8B4513"
|
||||
light_brown: "#CD853F"
|
||||
pink: "#D147BD"
|
||||
light_pink: "#DC75CD"
|
||||
green_screen: "#00FF00"
|
||||
orange: "#FF862F"
|
||||
# Can be DEBUG / INFO / WARNING / ERROR / CRITICAL
|
||||
log_level: "INFO"
|
||||
universal_import_line: "from manimlib import *"
|
||||
ignore_manimlib_modules_on_reload: True
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import sys
|
|||
|
||||
from manimlib.module_loader import ModuleLoader
|
||||
|
||||
from manimlib.config import get_global_config
|
||||
from manimlib.config import manim_config
|
||||
from manimlib.logger import log
|
||||
from manimlib.scene.interactive_scene import InteractiveScene
|
||||
from manimlib.scene.scene import Scene
|
||||
|
|
@ -19,7 +19,7 @@ if TYPE_CHECKING:
|
|||
|
||||
class BlankScene(InteractiveScene):
|
||||
def construct(self):
|
||||
exec(get_global_config()["universal_import_line"])
|
||||
exec(manim_config.universal_import_line)
|
||||
self.embed()
|
||||
|
||||
|
||||
|
|
@ -77,13 +77,13 @@ def compute_total_frames(scene_class, scene_config):
|
|||
pre_scene = scene_class(**pre_config)
|
||||
pre_scene.run()
|
||||
total_time = pre_scene.time - pre_scene.skip_time
|
||||
return int(total_time * scene_config["camera_config"]["fps"])
|
||||
return int(total_time * manim_config.camera.fps)
|
||||
|
||||
|
||||
def scene_from_class(scene_class, scene_config, run_config):
|
||||
fw_config = scene_config["file_writer_config"]
|
||||
if fw_config["write_to_movie"] and run_config["prerun"]:
|
||||
fw_config["total_frames"] = compute_total_frames(scene_class, scene_config)
|
||||
fw_config = manim_config.file_writer
|
||||
if fw_config.write_to_movie and run_config.prerun:
|
||||
scene_config.file_writer_config.total_frames = compute_total_frames(scene_class, scene_config)
|
||||
return scene_class(**scene_config)
|
||||
|
||||
|
||||
|
|
@ -180,4 +180,7 @@ def main(scene_config, run_config):
|
|||
return [BlankScene(**scene_config)]
|
||||
|
||||
all_scene_classes = get_scene_classes_from_module(module)
|
||||
return get_scenes_to_render(all_scene_classes, scene_config, run_config)
|
||||
scenes = get_scenes_to_render(all_scene_classes, scene_config, run_config)
|
||||
if len(scenes) == 0:
|
||||
print("No scenes found to run")
|
||||
return scenes
|
||||
|
|
|
|||
|
|
@ -11,4 +11,3 @@ logging.basicConfig(
|
|||
)
|
||||
|
||||
log = logging.getLogger("manimgl")
|
||||
log.setLevel("WARNING")
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import moderngl
|
|||
import numbers
|
||||
import numpy as np
|
||||
|
||||
from manimlib.constants import DEFAULT_MOBJECT_TO_EDGE_BUFFER
|
||||
from manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFFER
|
||||
from manimlib.constants import DEFAULT_MOBJECT_TO_EDGE_BUFF
|
||||
from manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFF
|
||||
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
|
||||
|
|
@ -1055,7 +1055,7 @@ class Mobject(object):
|
|||
def align_on_border(
|
||||
self,
|
||||
direction: Vect3,
|
||||
buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
|
||||
buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFF
|
||||
) -> Self:
|
||||
"""
|
||||
Direction just needs to be a vector pointing towards side or
|
||||
|
|
@ -1071,14 +1071,14 @@ class Mobject(object):
|
|||
def to_corner(
|
||||
self,
|
||||
corner: Vect3 = LEFT + DOWN,
|
||||
buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
|
||||
buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFF
|
||||
) -> Self:
|
||||
return self.align_on_border(corner, buff)
|
||||
|
||||
def to_edge(
|
||||
self,
|
||||
edge: Vect3 = LEFT,
|
||||
buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
|
||||
buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFF
|
||||
) -> Self:
|
||||
return self.align_on_border(edge, buff)
|
||||
|
||||
|
|
@ -1086,7 +1086,7 @@ class Mobject(object):
|
|||
self,
|
||||
mobject_or_point: Mobject | Vect3,
|
||||
direction: Vect3 = RIGHT,
|
||||
buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
|
||||
buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFF,
|
||||
aligned_edge: Vect3 = ORIGIN,
|
||||
submobject_to_align: Mobject | None = None,
|
||||
index_of_submobject_to_align: int | slice | None = None,
|
||||
|
|
@ -1117,7 +1117,7 @@ class Mobject(object):
|
|||
space_lengths = [FRAME_X_RADIUS, FRAME_Y_RADIUS]
|
||||
for vect in UP, DOWN, LEFT, RIGHT:
|
||||
dim = np.argmax(np.abs(vect))
|
||||
buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_EDGE_BUFFER)
|
||||
buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_EDGE_BUFF)
|
||||
max_val = space_lengths[dim] - buff
|
||||
edge_center = self.get_edge_center(vect)
|
||||
if np.dot(edge_center, vect) > max_val:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||
|
||||
from colour import Color
|
||||
|
||||
from manimlib.config import get_global_config
|
||||
from manimlib.config import manim_config
|
||||
from manimlib.constants import BLACK, RED, YELLOW, WHITE
|
||||
from manimlib.constants import DL, DOWN, DR, LEFT, RIGHT, UL, UR
|
||||
from manimlib.constants import SMALL_BUFF
|
||||
|
|
@ -57,7 +57,7 @@ class BackgroundRectangle(SurroundingRectangle):
|
|||
**kwargs
|
||||
):
|
||||
if color is None:
|
||||
color = get_global_config()['style']['background_color']
|
||||
color = manim_config.camera.background_color
|
||||
super().__init__(
|
||||
mobject,
|
||||
color=color,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import copy
|
|||
|
||||
import numpy as np
|
||||
|
||||
from manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFFER, SMALL_BUFF
|
||||
from manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFF, SMALL_BUFF
|
||||
from manimlib.constants import DOWN, LEFT, ORIGIN, RIGHT, DL, DR, UL
|
||||
from manimlib.constants import PI
|
||||
from manimlib.animation.composition import AnimationGroup
|
||||
|
|
@ -79,7 +79,7 @@ class Brace(Tex):
|
|||
)
|
||||
else:
|
||||
mob.move_to(self.get_tip())
|
||||
buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_MOBJECT_BUFFER)
|
||||
buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_MOBJECT_BUFF)
|
||||
shift_distance = mob.get_width() / 2.0 + buff
|
||||
mob.shift(self.get_direction() * shift_distance)
|
||||
return self
|
||||
|
|
@ -116,7 +116,7 @@ class BraceLabel(VMobject):
|
|||
text: str | Iterable[str],
|
||||
brace_direction: np.ndarray = DOWN,
|
||||
label_scale: float = 1.0,
|
||||
label_buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
|
||||
label_buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFF,
|
||||
**kwargs
|
||||
) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import pygments
|
|||
import pygments.formatters
|
||||
import pygments.lexers
|
||||
|
||||
from manimlib.config import get_global_config
|
||||
from manimlib.config import manim_config
|
||||
from manimlib.constants import DEFAULT_PIXEL_WIDTH, FRAME_WIDTH
|
||||
from manimlib.constants import NORMAL
|
||||
from manimlib.logger import log
|
||||
|
|
@ -157,14 +157,14 @@ class MarkupText(StringMobject):
|
|||
isolate: Selector = re.compile(r"\w+", re.U),
|
||||
**kwargs
|
||||
):
|
||||
default_style = get_global_config()["style"]
|
||||
text_config = manim_config.text
|
||||
self.text = text
|
||||
self.font_size = font_size
|
||||
self.justify = justify
|
||||
self.indent = indent
|
||||
self.alignment = alignment or default_style["text_alignment"]
|
||||
self.alignment = alignment or text_config.alignment
|
||||
self.line_width = line_width
|
||||
self.font = font or default_style["font"]
|
||||
self.font = font or text_config.font
|
||||
self.slant = slant
|
||||
self.weight = weight
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from manimlib.constants import GREY_A, GREY_C, GREY_E
|
|||
from manimlib.constants import BLACK
|
||||
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
|
||||
from manimlib.constants import PI
|
||||
from manimlib.constants import TAU
|
||||
|
|
@ -72,6 +71,12 @@ class VMobject(Mobject):
|
|||
make_smooth_after_applying_functions: bool = False
|
||||
# TODO, do we care about accounting for varying zoom levels?
|
||||
tolerance_for_point_equality: float = 1e-8
|
||||
joint_type_map: dict = {
|
||||
"no_joint": 0,
|
||||
"auto": 1,
|
||||
"bevel": 2,
|
||||
"miter": 3,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -123,7 +128,7 @@ class VMobject(Mobject):
|
|||
super().init_uniforms()
|
||||
self.uniforms.update(
|
||||
anti_alias_width=self.anti_alias_width,
|
||||
joint_type=JOINT_TYPE_MAP[self.joint_type],
|
||||
joint_type=self.joint_type_map[self.joint_type],
|
||||
flat_stroke=float(self.flat_stroke),
|
||||
scale_stroke_with_zoom=float(self.scale_stroke_with_zoom)
|
||||
)
|
||||
|
|
@ -406,7 +411,7 @@ class VMobject(Mobject):
|
|||
|
||||
def set_joint_type(self, joint_type: str, recurse: bool = True) -> Self:
|
||||
for mob in self.get_family(recurse):
|
||||
mob.uniforms["joint_type"] = JOINT_TYPE_MAP[joint_type]
|
||||
mob.uniforms["joint_type"] = self.joint_type_map[joint_type]
|
||||
return self
|
||||
|
||||
def get_joint_type(self) -> float:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import os
|
|||
import sys
|
||||
import sysconfig
|
||||
|
||||
from manimlib.config import get_global_config
|
||||
from manimlib.config import manim_config
|
||||
from manimlib.logger import log
|
||||
|
||||
Module = importlib.util.types.ModuleType
|
||||
|
|
@ -142,9 +142,12 @@ class ModuleLoader:
|
|||
|
||||
Only user-defined modules are reloaded, see `is_user_defined_module()`.
|
||||
"""
|
||||
ignore_manimlib_modules = get_global_config()["ignore_manimlib_modules_on_reload"]
|
||||
ignore_manimlib_modules = manim_config.ignore_manimlib_modules_on_reload
|
||||
if ignore_manimlib_modules and module.__name__.startswith("manimlib"):
|
||||
return
|
||||
if module.__name__.startswith("manimlib.config"):
|
||||
# We don't want to reload global config
|
||||
return
|
||||
|
||||
if not hasattr(module, "__dict__"):
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from IPython.terminal.embed import KillEmbedded
|
||||
|
||||
|
||||
import manimlib.config
|
||||
import manimlib.extract_scene
|
||||
|
||||
from manimlib.window import Window
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from argparse import Namespace
|
||||
|
||||
|
||||
class ReloadManager:
|
||||
"""
|
||||
Manages the loading and running of scenes and is called directly from the
|
||||
main entry point of ManimGL.
|
||||
|
||||
The name "reload" comes from the fact that this class handles the
|
||||
reinitialization of scenes when requested by the user via the `reload()`
|
||||
command in the IPython shell.
|
||||
"""
|
||||
|
||||
window = None
|
||||
is_reload = False
|
||||
|
||||
def __init__(self, cli_args: Namespace):
|
||||
self.args = cli_args
|
||||
|
||||
def set_new_start_at_line(self, start_at_line):
|
||||
"""
|
||||
Sets/Updates the line number to load the scene from when reloading.
|
||||
"""
|
||||
self.args.embed = str(start_at_line)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Runs the scenes in a loop and detects when a scene reload is requested.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
# blocking call since a scene will init an IPython shell()
|
||||
self.retrieve_scenes_and_run()
|
||||
return
|
||||
except KillEmbedded:
|
||||
# Requested via the `exit_raise` IPython runline magic
|
||||
# by means of our scene.reload() command
|
||||
self.note_reload()
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
def note_reload(self):
|
||||
self.is_reload = True
|
||||
print(" ".join([
|
||||
"Reloading interactive session for",
|
||||
f"\033[96m{self.args.scene_names[0]}\033[0m",
|
||||
f"at line \033[96m{self.args.embed}\033[0m"
|
||||
]))
|
||||
|
||||
def retrieve_scenes_and_run(self):
|
||||
"""
|
||||
Creates a new configuration based on the CLI args and runs the scenes.
|
||||
"""
|
||||
# Args to Config
|
||||
scene_config = manimlib.config.get_scene_config(self.args)
|
||||
scene_config.update(reload_manager=self)
|
||||
|
||||
run_config = manimlib.config.get_run_config(self.args)
|
||||
run_config.update(is_reload=self.is_reload)
|
||||
|
||||
# Create or reuse window
|
||||
if run_config["show_in_window"] and not self.window:
|
||||
self.window = Window(**run_config["window_config"])
|
||||
scene_config.update(window=self.window)
|
||||
|
||||
# Scenes
|
||||
scenes = manimlib.extract_scene.main(scene_config, run_config)
|
||||
if len(scenes) == 0:
|
||||
print("No scenes found to run")
|
||||
|
||||
for scene in scenes:
|
||||
scene.run()
|
||||
|
|
@ -7,7 +7,6 @@ from IPython.core.getipython import get_ipython
|
|||
from pyglet.window import key as PygletWindowKeys
|
||||
|
||||
from manimlib.animation.fading import FadeIn
|
||||
from manimlib.constants import ARROW_SYMBOLS, CTRL_SYMBOL, DELETE_SYMBOL, SHIFT_SYMBOL
|
||||
from manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, RIGHT, UL, UP, UR
|
||||
from manimlib.constants import FRAME_WIDTH, FRAME_HEIGHT, SMALL_BUFF
|
||||
from manimlib.constants import PI
|
||||
|
|
@ -49,6 +48,15 @@ INFORMATION_KEY = 'i'
|
|||
CURSOR_KEY = 'k'
|
||||
COPY_FRAME_POSITION_KEY = 'p'
|
||||
|
||||
# For keyboard interactions
|
||||
|
||||
ARROW_SYMBOLS: list[int] = [
|
||||
PygletWindowKeys.LEFT,
|
||||
PygletWindowKeys.UP,
|
||||
PygletWindowKeys.RIGHT,
|
||||
PygletWindowKeys.DOWN,
|
||||
]
|
||||
|
||||
ALL_MODIFIERS = PygletWindowKeys.MOD_CTRL | PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_SHIFT
|
||||
|
||||
# Note, a lot of the functionality here is still buggy and very much a work in progress.
|
||||
|
|
@ -472,7 +480,7 @@ class InteractiveScene(Scene):
|
|||
self.prepare_grab()
|
||||
elif char == RESIZE_KEY and (modifiers & PygletWindowKeys.MOD_SHIFT):
|
||||
self.prepare_resizing(about_corner=((modifiers & PygletWindowKeys.MOD_SHIFT) > 0))
|
||||
elif symbol == SHIFT_SYMBOL:
|
||||
elif symbol == PygletWindowKeys.LSHIFT:
|
||||
if self.window.is_key_pressed(ord("t")):
|
||||
self.prepare_resizing(about_corner=True)
|
||||
elif char == COLOR_KEY and (modifiers & ALL_MODIFIERS) == 0:
|
||||
|
|
@ -486,7 +494,7 @@ class InteractiveScene(Scene):
|
|||
elif char == "x" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):
|
||||
self.copy_selection()
|
||||
self.delete_selection()
|
||||
elif symbol == DELETE_SYMBOL:
|
||||
elif symbol == PygletWindowKeys.BACKSPACE:
|
||||
self.delete_selection()
|
||||
elif char == "a" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):
|
||||
self.clear_selection()
|
||||
|
|
@ -527,7 +535,7 @@ class InteractiveScene(Scene):
|
|||
self.is_grabbing = False
|
||||
elif chr(symbol) == INFORMATION_KEY:
|
||||
self.display_information(False)
|
||||
elif symbol == SHIFT_SYMBOL and self.window.is_key_pressed(ord(RESIZE_KEY)):
|
||||
elif symbol == PygletWindowKeys.LSHIFT and self.window.is_key_pressed(ord(RESIZE_KEY)):
|
||||
self.prepare_resizing(about_corner=False)
|
||||
|
||||
# Mouse actions
|
||||
|
|
@ -544,7 +552,7 @@ class InteractiveScene(Scene):
|
|||
if not hasattr(self, "scale_about_point"):
|
||||
return
|
||||
vect = point - self.scale_about_point
|
||||
if self.window.is_key_pressed(CTRL_SYMBOL):
|
||||
if self.window.is_key_pressed(PygletWindowKeys.LCTRL):
|
||||
for i in (0, 1):
|
||||
scalar = vect[i] / self.scale_ref_vect[i]
|
||||
self.selection.rescale_to_fit(
|
||||
|
|
@ -589,7 +597,7 @@ class InteractiveScene(Scene):
|
|||
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):
|
||||
elif self.window.is_key_pressed(ord(SELECT_KEY)) and self.window.is_key_pressed(PygletWindowKeys.LSHIFT):
|
||||
self.handle_sweeping_selection(point)
|
||||
|
||||
def on_mouse_drag(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import random
|
|||
import time
|
||||
from functools import wraps
|
||||
|
||||
from IPython.core.getipython import get_ipython
|
||||
from pyglet.window import key as PygletWindowKeys
|
||||
|
||||
import numpy as np
|
||||
|
|
@ -15,8 +14,7 @@ from tqdm.auto import tqdm as ProgressDisplay
|
|||
from manimlib.animation.animation import prepare_animation
|
||||
from manimlib.camera.camera import Camera
|
||||
from manimlib.camera.camera_frame import CameraFrame
|
||||
from manimlib.constants import ARROW_SYMBOLS
|
||||
from manimlib.constants import DEFAULT_WAIT_TIME
|
||||
from manimlib.config import manim_config
|
||||
from manimlib.event_handler import EVENT_DISPATCHER
|
||||
from manimlib.event_handler.event_type import EventType
|
||||
from manimlib.logger import log
|
||||
|
|
@ -29,6 +27,7 @@ from manimlib.mobject.types.vectorized_mobject import VMobject
|
|||
from manimlib.scene.scene_embed import interactive_scene_embed
|
||||
from manimlib.scene.scene_embed import CheckpointManager
|
||||
from manimlib.scene.scene_file_writer import SceneFileWriter
|
||||
from manimlib.utils.dict_ops import merge_dicts_recursively
|
||||
from manimlib.utils.family_ops import extract_mobject_family_members
|
||||
from manimlib.utils.family_ops import recursive_mobject_remove
|
||||
from manimlib.utils.iterables import batch_by_property
|
||||
|
|
@ -44,7 +43,6 @@ if TYPE_CHECKING:
|
|||
|
||||
from PIL.Image import Image
|
||||
|
||||
from manimlib.reload_manager import ReloadManager
|
||||
from manimlib.animation.animation import Animation
|
||||
|
||||
|
||||
|
|
@ -68,33 +66,37 @@ class Scene(object):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
window: Optional[Window] = None,
|
||||
camera_config: dict = dict(),
|
||||
file_writer_config: dict = dict(),
|
||||
skip_animations: bool = False,
|
||||
always_update_mobjects: bool = False,
|
||||
start_at_animation_number: int | None = None,
|
||||
end_at_animation_number: int | None = None,
|
||||
leave_progress_bars: bool = False,
|
||||
window: Optional[Window] = None,
|
||||
reload_manager: Optional[ReloadManager] = None,
|
||||
presenter_mode: bool = False,
|
||||
show_animation_progress: bool = False,
|
||||
embed_exception_mode: str = "",
|
||||
embed_error_sound: bool = False,
|
||||
leave_progress_bars: bool = False,
|
||||
presenter_mode: bool = False,
|
||||
default_wait_time: float = 1.0,
|
||||
):
|
||||
self.skip_animations = skip_animations
|
||||
self.always_update_mobjects = always_update_mobjects
|
||||
self.start_at_animation_number = start_at_animation_number
|
||||
self.end_at_animation_number = end_at_animation_number
|
||||
self.show_animation_progress = show_animation_progress
|
||||
self.leave_progress_bars = leave_progress_bars
|
||||
self.presenter_mode = presenter_mode
|
||||
self.show_animation_progress = show_animation_progress
|
||||
self.embed_exception_mode = embed_exception_mode
|
||||
self.embed_error_sound = embed_error_sound
|
||||
self.reload_manager = reload_manager
|
||||
self.default_wait_time = default_wait_time
|
||||
|
||||
self.camera_config = {**self.default_camera_config, **camera_config}
|
||||
self.file_writer_config = {**self.default_file_writer_config, **file_writer_config}
|
||||
self.camera_config = merge_dicts_recursively(
|
||||
manim_config.camera, # Global default
|
||||
self.default_camera_config, # Updated configuration that subclasses may specify
|
||||
camera_config, # Updated configuration from instantiation
|
||||
)
|
||||
self.file_writer_config = merge_dicts_recursively(
|
||||
manim_config.file_writer,
|
||||
self.default_file_writer_config,
|
||||
file_writer_config,
|
||||
)
|
||||
|
||||
self.window = window
|
||||
if self.window:
|
||||
|
|
@ -589,11 +591,13 @@ class Scene(object):
|
|||
|
||||
def wait(
|
||||
self,
|
||||
duration: float = DEFAULT_WAIT_TIME,
|
||||
duration: Optional[float] = None,
|
||||
stop_condition: Callable[[], bool] = None,
|
||||
note: str = None,
|
||||
ignore_presenter_mode: bool = False
|
||||
):
|
||||
if duration is None:
|
||||
duration = self.default_wait_time
|
||||
self.pre_play()
|
||||
self.update_mobjects(dt=0) # Any problems with this?
|
||||
if self.presenter_mode and not self.skip_animations and not ignore_presenter_mode:
|
||||
|
|
@ -839,7 +843,7 @@ class Scene(object):
|
|||
elif char == QUIT_KEY and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):
|
||||
self.quit_interaction = True
|
||||
# Space or right arrow
|
||||
elif char == " " or symbol == ARROW_SYMBOLS[2]:
|
||||
elif char == " " or symbol == PygletWindowKeys.RIGHT:
|
||||
self.hold_on_wait = False
|
||||
|
||||
def on_resize(self, width: int, height: int) -> None:
|
||||
|
|
@ -854,34 +858,6 @@ class Scene(object):
|
|||
def on_close(self) -> None:
|
||||
pass
|
||||
|
||||
def reload(self, start_at_line: int | None = None) -> None:
|
||||
"""
|
||||
Reloads the scene just like the `manimgl` command would do with the
|
||||
same arguments that were provided for the initial startup. This allows
|
||||
for quick iteration during scene development since we don't have to exit
|
||||
the IPython kernel and re-run the `manimgl` command again. The GUI stays
|
||||
open during the reload.
|
||||
|
||||
If `start_at_line` is provided, the scene will be reloaded at that line
|
||||
number. This corresponds to the `linemarker` param of the
|
||||
`extract_scene.insert_embed_line_to_module()` method.
|
||||
|
||||
Before reload, the scene is cleared and the entire state is reset, such
|
||||
that we can start from a clean slate. This is taken care of by the
|
||||
ReloadManager, which will catch the error raised by the `exit_raise`
|
||||
magic command that we invoke here.
|
||||
|
||||
Note that we cannot define a custom exception class for this error,
|
||||
since the IPython kernel will swallow any exception. While we can catch
|
||||
such an exception in our custom exception handler registered with the
|
||||
`set_custom_exc` method, we cannot break out of the IPython shell by
|
||||
this means.
|
||||
"""
|
||||
self.reload_manager.set_new_start_at_line(start_at_line)
|
||||
shell = get_ipython()
|
||||
if shell:
|
||||
shell.run_line_magic("exit_raise", "")
|
||||
|
||||
def focus(self) -> None:
|
||||
"""
|
||||
Puts focus on the ManimGL window.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import inspect
|
||||
import pyperclip
|
||||
import os
|
||||
|
||||
from IPython.core.getipython import get_ipython
|
||||
from IPython.terminal import pt_inputhooks
|
||||
from IPython.terminal.embed import InteractiveShellEmbed
|
||||
|
||||
from manimlib.animation.fading import VFadeInThenOut
|
||||
from manimlib.config import manim_config
|
||||
from manimlib.constants import RED
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.mobject.frame import FullScreenRectangle
|
||||
|
|
@ -39,11 +39,12 @@ def get_ipython_shell_for_embedded_scene(scene):
|
|||
module = ModuleLoader.get_module(caller_frame.f_globals["__file__"])
|
||||
module.__dict__.update(caller_frame.f_locals)
|
||||
module.__dict__.update(get_shortcuts(scene))
|
||||
exception_mode = manim_config.embed.exception_mode
|
||||
|
||||
return InteractiveShellEmbed(
|
||||
user_module=module,
|
||||
display_banner=False,
|
||||
xmode=scene.embed_exception_mode
|
||||
xmode=exception_mode
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -59,12 +60,12 @@ def get_shortcuts(scene):
|
|||
clear=scene.clear,
|
||||
focus=scene.focus,
|
||||
save_state=scene.save_state,
|
||||
reload=scene.reload,
|
||||
undo=scene.undo,
|
||||
redo=scene.redo,
|
||||
i2g=scene.i2g,
|
||||
i2m=scene.i2m,
|
||||
checkpoint_paste=scene.checkpoint_paste,
|
||||
reload=reload_scene # Defined below
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -95,8 +96,6 @@ def ensure_flash_on_error(shell, scene):
|
|||
def custom_exc(shell, etype, evalue, tb, tb_offset=None):
|
||||
# Show the error don't just swallow it
|
||||
shell.showtraceback((etype, evalue, tb), tb_offset=tb_offset)
|
||||
if scene.embed_error_sound:
|
||||
os.system("printf '\a'")
|
||||
rect = FullScreenRectangle().set_stroke(RED, 30).set_fill(opacity=0)
|
||||
rect.fix_in_frame()
|
||||
scene.play(VFadeInThenOut(rect, run_time=0.5))
|
||||
|
|
@ -104,6 +103,43 @@ def ensure_flash_on_error(shell, scene):
|
|||
shell.set_custom_exc((Exception,), custom_exc)
|
||||
|
||||
|
||||
def reload_scene(embed_line: int | None = None) -> None:
|
||||
"""
|
||||
Reloads the scene just like the `manimgl` command would do with the
|
||||
same arguments that were provided for the initial startup. This allows
|
||||
for quick iteration during scene development since we don't have to exit
|
||||
the IPython kernel and re-run the `manimgl` command again. The GUI stays
|
||||
open during the reload.
|
||||
|
||||
If `embed_line` is provided, the scene will be reloaded at that line
|
||||
number. This corresponds to the `linemarker` param of the
|
||||
`extract_scene.insert_embed_line_to_module()` method.
|
||||
|
||||
Before reload, the scene is cleared and the entire state is reset, such
|
||||
that we can start from a clean slate. This is taken care of by the
|
||||
run_scenes function in __main__.py, which will catch the error raised by the
|
||||
`exit_raise` magic command that we invoke here.
|
||||
|
||||
Note that we cannot define a custom exception class for this error,
|
||||
since the IPython kernel will swallow any exception. While we can catch
|
||||
such an exception in our custom exception handler registered with the
|
||||
`set_custom_exc` method, we cannot break out of the IPython shell by
|
||||
this means.
|
||||
"""
|
||||
shell = get_ipython()
|
||||
if not shell:
|
||||
return
|
||||
|
||||
# Update the global run configuration.
|
||||
run_config = manim_config.run
|
||||
run_config.is_reload = True
|
||||
if embed_line:
|
||||
run_config.embed_line = embed_line
|
||||
|
||||
print("Reloading...")
|
||||
shell.run_line_magic("exit_raise", "")
|
||||
|
||||
|
||||
class CheckpointManager:
|
||||
checkpoint_states: dict[str, list[tuple[Mobject, Mobject]]] = dict()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ from pydub import AudioSegment
|
|||
from tqdm.auto import tqdm as ProgressDisplay
|
||||
from pathlib import Path
|
||||
|
||||
from manimlib.constants import FFMPEG_BIN
|
||||
from manimlib.logger import log
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.utils.file_ops import add_extension_if_not_present
|
||||
|
|
@ -49,6 +48,8 @@ class SceneFileWriter(object):
|
|||
quiet: bool = False,
|
||||
total_frames: int = 0,
|
||||
progress_description_len: int = 40,
|
||||
# Name of the binary used for ffmpeg
|
||||
ffmpeg_bin: str = "ffmpeg",
|
||||
video_codec: str = "libx264",
|
||||
pixel_format: str = "yuv420p",
|
||||
saturation: float = 1.0,
|
||||
|
|
@ -70,6 +71,7 @@ class SceneFileWriter(object):
|
|||
self.quiet = quiet
|
||||
self.total_frames = total_frames
|
||||
self.progress_description_len = progress_description_len
|
||||
self.ffmpeg_bin = ffmpeg_bin
|
||||
self.video_codec = video_codec
|
||||
self.pixel_format = pixel_format
|
||||
self.saturation = saturation
|
||||
|
|
@ -236,7 +238,7 @@ class SceneFileWriter(object):
|
|||
vf_arg += f',eq=saturation={self.saturation}:gamma={self.gamma}'
|
||||
|
||||
command = [
|
||||
FFMPEG_BIN,
|
||||
self.ffmpeg_bin,
|
||||
'-y', # overwrite output file if it exists
|
||||
'-f', 'rawvideo',
|
||||
'-s', f'{width}x{height}', # size of one frame
|
||||
|
|
@ -358,7 +360,7 @@ class SceneFileWriter(object):
|
|||
|
||||
movie_file_path = self.get_movie_file_path()
|
||||
commands = [
|
||||
FFMPEG_BIN,
|
||||
self.ffmpeg_bin,
|
||||
'-y', # overwrite output file if it exists
|
||||
'-f', 'concat',
|
||||
'-safe', '0',
|
||||
|
|
@ -385,7 +387,7 @@ class SceneFileWriter(object):
|
|||
)
|
||||
temp_file_path = stem + "_temp" + ext
|
||||
commands = [
|
||||
FFMPEG_BIN,
|
||||
self.ffmpeg_bin,
|
||||
"-i", movie_file_path,
|
||||
"-i", sound_file_path,
|
||||
'-y', # overwrite output file if it exists
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import numpy as np
|
|||
from functools import lru_cache
|
||||
|
||||
from manimlib.config import parse_cli
|
||||
from manimlib.config import get_camera_config
|
||||
from manimlib.config import manim_config
|
||||
from manimlib.utils.shaders import get_shader_code_from_file
|
||||
from manimlib.utils.shaders import get_shader_program
|
||||
from manimlib.utils.shaders import image_path_to_texture
|
||||
|
|
@ -410,8 +410,7 @@ class VShaderWrapper(ShaderWrapper):
|
|||
which can display that texture as a simple quad onto a screen,
|
||||
along with the rgb value which is meant to be discarded.
|
||||
"""
|
||||
cam_config = get_camera_config()
|
||||
size = (cam_config['pixel_width'], cam_config['pixel_height'])
|
||||
size = manim_config.camera.resolution
|
||||
double_size = (2 * size[0], 2 * size[1])
|
||||
|
||||
# Important to make sure dtype is floating point (not fixed point)
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import tempfile
|
|||
import appdirs
|
||||
|
||||
|
||||
from manimlib.config import get_global_config
|
||||
from manimlib.config import manim_config
|
||||
from manimlib.config import get_manim_dir
|
||||
from manimlib.utils.file_ops import guarantee_existence
|
||||
|
||||
|
||||
def get_directories() -> dict[str, str]:
|
||||
return get_global_config()["directories"]
|
||||
return manim_config.directories
|
||||
|
||||
|
||||
def get_cache_dir() -> str:
|
||||
|
|
|
|||
|
|
@ -1,164 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import inspect
|
||||
import os
|
||||
import yaml
|
||||
|
||||
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 typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
def get_manim_dir() -> str:
|
||||
manimlib_module = importlib.import_module("manimlib")
|
||||
manimlib_dir = os.path.dirname(inspect.getabsfile(manimlib_module))
|
||||
return os.path.abspath(os.path.join(manimlib_dir, ".."))
|
||||
|
||||
|
||||
def remove_empty_value(dictionary: dict[str, Any]) -> None:
|
||||
for key in list(dictionary.keys()):
|
||||
if dictionary[key] == "":
|
||||
dictionary.pop(key)
|
||||
elif isinstance(dictionary[key], dict):
|
||||
remove_empty_value(dictionary[key])
|
||||
|
||||
|
||||
def init_customization() -> None:
|
||||
configuration = {
|
||||
"directories": {
|
||||
"mirror_module_path": False,
|
||||
"base": "",
|
||||
"subdirs": {
|
||||
"output": "videos",
|
||||
"raster_images": "raster_images",
|
||||
"vector_images": "vector_images",
|
||||
"sounds": "sounds",
|
||||
"data": "data",
|
||||
"downloads": "downloads",
|
||||
}
|
||||
},
|
||||
"universal_import_line": "from manimlib import *",
|
||||
"style": {
|
||||
"tex_template": "",
|
||||
"font": "Consolas",
|
||||
"background_color": "",
|
||||
},
|
||||
"window_position": "UR",
|
||||
"window_monitor": 0,
|
||||
"full_screen": False,
|
||||
"break_into_partial_movies": False,
|
||||
"camera_resolutions": {
|
||||
"low": "854x480",
|
||||
"medium": "1280x720",
|
||||
"high": "1920x1080",
|
||||
"4k": "3840x2160",
|
||||
"default_resolution": "",
|
||||
},
|
||||
"fps": 30,
|
||||
}
|
||||
|
||||
console = Console()
|
||||
console.print(Rule("[bold]Configuration Guide[/bold]"))
|
||||
# print("Initialize configuration")
|
||||
try:
|
||||
scope = Prompt.ask(
|
||||
" Select the scope of the configuration",
|
||||
choices=["global", "local"],
|
||||
default="local"
|
||||
)
|
||||
|
||||
console.print("[bold]Directories:[/bold]")
|
||||
dir_config = configuration["directories"]
|
||||
dir_config["base"] = Prompt.ask(
|
||||
" What base directory should manim use for reading/writing video and images? [prompt.default](optional, default is none)",
|
||||
default="",
|
||||
show_default=False
|
||||
)
|
||||
dir_config["subdirs"]["output"] = Prompt.ask(
|
||||
" Within that base directory, which subdirectory should manim [bold]output[/bold] video and image files to?" + \
|
||||
" [prompt.default](optional, default is \"videos\")",
|
||||
default="videos",
|
||||
show_default=False
|
||||
)
|
||||
dir_config["subdirs"]["raster_images"] = Prompt.ask(
|
||||
" Within that base directory, which subdirectory should manim look for raster images (.png, .jpg)" + \
|
||||
" [prompt.default](optional, default is \"raster_images\")",
|
||||
default="raster_images",
|
||||
show_default=False
|
||||
)
|
||||
dir_config["subdirs"]["vector_images"] = Prompt.ask(
|
||||
" Within that base directory, which subdirectory should manim look for raster images (.svg, .xdv)" + \
|
||||
" [prompt.default](optional, default is \"vector_images\")",
|
||||
default="vector_images",
|
||||
show_default=False
|
||||
)
|
||||
dir_config["subdirs"]["sounds"] = Prompt.ask(
|
||||
" Within that base directory, which subdirectory should manim look for sound files (.mp3, .wav)" + \
|
||||
" [prompt.default](optional, default is \"sounds\")",
|
||||
default="sounds",
|
||||
show_default=False
|
||||
)
|
||||
dir_config["subdirs"]["downloads"] = Prompt.ask(
|
||||
" Within that base directory, which subdirectory should manim output downloaded files" + \
|
||||
" [prompt.default](optional, default is \"downloads\")",
|
||||
default="downloads",
|
||||
show_default=False
|
||||
)
|
||||
|
||||
console.print("[bold]Styles:[/bold]")
|
||||
style_config = configuration["style"]
|
||||
tex_template = Prompt.ask(
|
||||
" Select a TeX template to compile a LaTeX source file",
|
||||
default="default"
|
||||
)
|
||||
style_config["tex_template"] = tex_template
|
||||
style_config["background_color"] = Prompt.ask(
|
||||
" Which [bold]background color[/bold] do you want [italic](hex code)",
|
||||
default="#333333"
|
||||
)
|
||||
|
||||
console.print("[bold]Camera qualities:[/bold]")
|
||||
table = Table(
|
||||
"low", "medium", "high", "ultra_high",
|
||||
title="Four defined qualities",
|
||||
box=box.ROUNDED
|
||||
)
|
||||
table.add_row("480p15", "720p30", "1080p60", "2160p60")
|
||||
console.print(table)
|
||||
configuration["camera_resolutions"]["default_resolution"] = Prompt.ask(
|
||||
" Which one to choose as the default rendering quality",
|
||||
choices=["low", "medium", "high", "ultra_high"],
|
||||
default="high"
|
||||
)
|
||||
|
||||
write_to_file = Confirm.ask(
|
||||
"\n[bold]Are you sure to write these configs to file?[/bold]",
|
||||
default=True
|
||||
)
|
||||
if not write_to_file:
|
||||
raise KeyboardInterrupt
|
||||
|
||||
global_file_name = os.path.join(get_manim_dir(), "manimlib", "default_config.yml")
|
||||
if scope == "global":
|
||||
file_name = global_file_name
|
||||
else:
|
||||
if os.path.exists(global_file_name):
|
||||
remove_empty_value(configuration)
|
||||
file_name = os.path.join(os.getcwd(), "custom_config.yml")
|
||||
with open(file_name, "w", encoding="utf-8") as f:
|
||||
yaml.dump(configuration, f)
|
||||
|
||||
console.print(f"\n:rocket: You have successfully set up a {scope} configuration file!")
|
||||
console.print(f"You can manually modify it in: [cyan]`{file_name}`[/cyan]")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n[green]Exit configuration guide[/green]")
|
||||
|
|
@ -10,15 +10,12 @@ from pathlib import Path
|
|||
import tempfile
|
||||
|
||||
from manimlib.utils.cache import cache_on_disk
|
||||
from manimlib.config import get_global_config
|
||||
from manimlib.config import manim_config
|
||||
from manimlib.config import get_manim_dir
|
||||
from manimlib.logger import log
|
||||
from manimlib.utils.simple_functions import hash_string
|
||||
|
||||
|
||||
SAVED_TEX_CONFIG = {}
|
||||
|
||||
|
||||
def get_tex_template_config(template_name: str) -> dict[str, str]:
|
||||
name = template_name.replace(" ", "_").lower()
|
||||
template_path = os.path.join(get_manim_dir(), "manimlib", "tex_templates.yml")
|
||||
|
|
@ -33,7 +30,8 @@ def get_tex_template_config(template_name: str) -> dict[str, str]:
|
|||
return templates_dict[name]
|
||||
|
||||
|
||||
def get_tex_config() -> dict[str, str]:
|
||||
@lru_cache
|
||||
def get_tex_config(template: str = "") -> dict[str, str]:
|
||||
"""
|
||||
Returns a dict which should look something like this:
|
||||
{
|
||||
|
|
@ -42,16 +40,13 @@ def get_tex_config() -> dict[str, str]:
|
|||
"preamble": "..."
|
||||
}
|
||||
"""
|
||||
# Only load once, then save thereafter
|
||||
if not SAVED_TEX_CONFIG:
|
||||
template_name = get_global_config()["style"]["tex_template"]
|
||||
template_config = get_tex_template_config(template_name)
|
||||
SAVED_TEX_CONFIG.update({
|
||||
"template": template_name,
|
||||
"compiler": template_config["compiler"],
|
||||
"preamble": template_config["preamble"]
|
||||
})
|
||||
return SAVED_TEX_CONFIG
|
||||
template = template or manim_config.tex.template
|
||||
template_config = get_tex_template_config(template)
|
||||
return {
|
||||
"template": template,
|
||||
"compiler": template_config["compiler"],
|
||||
"preamble": template_config["preamble"]
|
||||
}
|
||||
|
||||
|
||||
def get_full_tex(content: str, preamble: str = ""):
|
||||
|
|
@ -94,17 +89,12 @@ def latex_to_svg(
|
|||
message = message[:max_message_len - 3] + "..."
|
||||
print(message, end="\r")
|
||||
|
||||
tex_config = get_tex_config()
|
||||
if template and template != tex_config["template"]:
|
||||
tex_config = get_tex_template_config(template)
|
||||
|
||||
tex_config = get_tex_config(template)
|
||||
compiler = tex_config["compiler"]
|
||||
|
||||
if compiler == "latex":
|
||||
program = "latex"
|
||||
dvi_ext = ".dvi"
|
||||
elif compiler == "xelatex":
|
||||
program = "xelatex -no-pdf"
|
||||
dvi_ext = ".xdv"
|
||||
else:
|
||||
raise NotImplementedError(f"Compiler '{compiler}' is not implemented")
|
||||
|
|
@ -119,18 +109,18 @@ def latex_to_svg(
|
|||
dvi_path = base_path + dvi_ext
|
||||
|
||||
# Write tex file
|
||||
with open(tex_path, "w", encoding="utf-8") as tex_file:
|
||||
tex_file.write(full_tex)
|
||||
Path(tex_path).write_text(full_tex)
|
||||
|
||||
# Run latex compiler
|
||||
process = subprocess.run(
|
||||
[
|
||||
program.split()[0], # Split for xelatex case
|
||||
compiler,
|
||||
"-no-pdf",
|
||||
"-interaction=batchmode",
|
||||
"-halt-on-error",
|
||||
"-output-directory=" + temp_dir,
|
||||
f"-output-directory={temp_dir}",
|
||||
tex_path
|
||||
] + (["--no-pdf"] if compiler == "xelatex" else []),
|
||||
],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ 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
|
||||
from screeninfo import get_monitors
|
||||
from functools import wraps
|
||||
import screeninfo
|
||||
|
||||
from manimlib.config import get_global_config
|
||||
from manimlib.constants import ASPECT_RATIO
|
||||
from manimlib.constants import FRAME_SHAPE
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
@ -30,16 +30,21 @@ class Window(PygletWindow):
|
|||
def __init__(
|
||||
self,
|
||||
scene: Optional[Scene] = None,
|
||||
size: tuple[int, int] = (1280, 720),
|
||||
position_string: str = "UR",
|
||||
monitor_index: int = 1,
|
||||
full_screen: bool = False,
|
||||
size: Optional[tuple[int, int]] = None,
|
||||
position: Optional[tuple[int, int]] = None,
|
||||
samples: int = 0
|
||||
):
|
||||
super().__init__(size=size, samples=samples)
|
||||
|
||||
self.scene = scene
|
||||
self.default_size = size
|
||||
self.default_position = self.find_initial_position(size)
|
||||
self.monitor = self.get_monitor(monitor_index)
|
||||
self.default_size = size or self.get_default_size(full_screen)
|
||||
self.default_position = position or self.position_from_string(position_string)
|
||||
self.pressed_keys = set()
|
||||
self.size = size
|
||||
|
||||
super().__init__(samples=samples)
|
||||
self.to_default_position()
|
||||
|
||||
if self.scene:
|
||||
self.init_for_scene(scene)
|
||||
|
|
@ -64,7 +69,31 @@ class Window(PygletWindow):
|
|||
mglw.activate_context(window=self, ctx=self.ctx)
|
||||
self.timer.start()
|
||||
|
||||
self.to_default_position()
|
||||
self.focus()
|
||||
|
||||
def get_monitor(self, index):
|
||||
try:
|
||||
monitors = screeninfo.get_monitors()
|
||||
return monitors[min(index, len(monitors) - 1)]
|
||||
except screeninfo.ScreenInfoError:
|
||||
# Default fallback
|
||||
return screeninfo.Monitor(width=1920, height=1080)
|
||||
|
||||
def get_default_size(self, full_screen=False):
|
||||
width = self.monitor.width // (1 if full_screen else 2)
|
||||
height = int(width // ASPECT_RATIO)
|
||||
return (width, height)
|
||||
|
||||
def position_from_string(self, position_string):
|
||||
# Alternatively, it might be specified with a string like
|
||||
# UR, OO, DL, etc. specifying what corner it should go to
|
||||
char_to_n = {"L": 0, "U": 0, "O": 1, "R": 2, "D": 2}
|
||||
size = self.default_size
|
||||
width_diff = self.monitor.width - size[0]
|
||||
height_diff = self.monitor.height - size[1]
|
||||
x_step = char_to_n[position_string[1]] * width_diff // 2
|
||||
y_step = char_to_n[position_string[0]] * height_diff // 2
|
||||
return (self.monitor.x + x_step, -self.monitor.y + y_step)
|
||||
|
||||
def focus(self):
|
||||
"""
|
||||
|
|
@ -77,6 +106,8 @@ class Window(PygletWindow):
|
|||
"""
|
||||
self._window.set_visible(False)
|
||||
self._window.set_visible(True)
|
||||
# This line seems to resync the viewport
|
||||
self.on_resize(*self.size)
|
||||
|
||||
def to_default_position(self):
|
||||
self.position = self.default_position
|
||||
|
|
@ -86,28 +117,6 @@ class Window(PygletWindow):
|
|||
self.size = (w - 1, h - 1)
|
||||
self.size = (w, h)
|
||||
|
||||
def find_initial_position(self, size: tuple[int, int]) -> tuple[int, int]:
|
||||
global_config = get_global_config()
|
||||
custom_position = global_config["window_position"]
|
||||
mon_index = global_config["window_monitor"]
|
||||
monitors = get_monitors()
|
||||
monitor = monitors[min(mon_index, len(monitors) - 1)]
|
||||
window_width, window_height = size
|
||||
# Position might be specified with a string of the form
|
||||
# x,y for integers x and y
|
||||
if "," in custom_position:
|
||||
return tuple(map(int, custom_position.split(",")))
|
||||
|
||||
# Alternatively, it might be specified with a string like
|
||||
# UR, OO, DL, etc. specifying what corner it should go to
|
||||
char_to_n = {"L": 0, "U": 0, "O": 1, "R": 2, "D": 2}
|
||||
width_diff = monitor.width - window_width
|
||||
height_diff = monitor.height - window_height
|
||||
return (
|
||||
monitor.x + char_to_n[custom_position[1]] * width_diff // 2,
|
||||
-monitor.y + char_to_n[custom_position[0]] * height_diff // 2,
|
||||
)
|
||||
|
||||
# Delegate event handling to scene
|
||||
def pixel_coords_to_space_coords(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
addict
|
||||
appdirs
|
||||
colour
|
||||
diskcache
|
||||
|
|
@ -22,7 +23,6 @@ screeninfo
|
|||
skia-pathops
|
||||
svgelements>=1.8.1
|
||||
sympy
|
||||
tempfile
|
||||
tqdm
|
||||
typing-extensions; python_version < "3.11"
|
||||
validators
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ classifiers =
|
|||
packages = find:
|
||||
include_package_data = True
|
||||
install_requires =
|
||||
addict
|
||||
appdirs
|
||||
colour
|
||||
diskcache
|
||||
|
|
@ -53,7 +54,6 @@ install_requires =
|
|||
skia-pathops
|
||||
svgelements>=1.8.1
|
||||
sympy
|
||||
tempfile
|
||||
tqdm
|
||||
typing-extensions; python_version < "3.11"
|
||||
validators
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue