Skip to content

Commit 65240b8

Browse files
naveen521kkfriedkeenanleotrs
authored
Automatically Import Plugins (#967)
* Automatically import Plugins Specify plugins using CLI and config file Signed-off-by: Naveen M K <naveen@syrusdark.website> * Fix Doc tests * clean manim.cfg * Add more explanatory docs * add info about plugins site * use property method Co-authored-by: friedkeenan <friedkeenan@protonmail.com> * use types module and f-strings * Add tests * lint * remove --plugins it seems it is not possible currently to implement that * fix doc tests * don't define unnecessary variables * Apply suggestions from code review Co-authored-by: Leo Torres <dleonardotn@gmail.com> Co-authored-by: friedkeenan <friedkeenan@protonmail.com> Co-authored-by: Leo Torres <dleonardotn@gmail.com>
1 parent b2abe69 commit 65240b8

File tree

14 files changed

+497
-90
lines changed

14 files changed

+497
-90
lines changed

docs/source/installation/plugins.rst

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ install, use, and create your own plugins.
2020
for the best practices on installing, using, and creating plugins; as
2121
well as new subcommands/flags for ``manim plugins``
2222

23+
.. tip::
24+
25+
See https://plugins.manim.community/ for the list of plugins available.
26+
2327
Installing Plugins
2428
******************
2529
Plugins can be easily installed via the ``pip``
@@ -55,27 +59,37 @@ You can list plugins as such:
5559
5660
Using Plugins in Projects
5761
*************************
58-
Plugins specified in ``plugins/__init__.py`` are imported automatically by
59-
manim's ``__init__.py``. As such, writing:
62+
For enabling a plugin ``manim.cfg`` or command line parameters should be used.
63+
64+
.. important::
65+
66+
The plugins should be module name of the plugin and not PyPi name.
6067

61-
.. code-block:: python
68+
Enabling plugins through ``manim.cfg``
6269

63-
from manim import *
70+
.. code-block:: ini
6471
65-
in your projects will import any of the plugins imported in
66-
``plugins/__init__.py``.
72+
[CLI]
73+
plugins = manim_rubikscube
6774
68-
By default, ``plugins/__init__.py`` is not provided; although, there are
69-
plans to support subcommands that would manage this file. It is especially
70-
useful to create this file for projects that involve usage of the same
71-
plugins. Alternatively, you may manually specify the plugins in your project
72-
scripts.
75+
For specifing multiple plugins, command separated values must be used.
7376

74-
.. code-block:: python
77+
.. code-block:: ini
7578
76-
import manim_cool_plugin
77-
# or
78-
from manim_cool_plugin import feature_x, feature_y, ...
79+
[CLI]
80+
plugins = manim_rubikscube, manim_plugintemplate
81+
82+
Enabling Plugins through CLI
83+
84+
.. code-block:: bash
85+
86+
manim basic.py --plugins=manim_plugintemplate
87+
88+
For multiple plugins
89+
90+
.. code-block:: bash
91+
92+
manim basic.py --plugins=manim_rubikscube,manim_plugintemplate
7993
8094
Creating Plugins
8195
****************
@@ -101,4 +115,33 @@ specified in poetry as:
101115
.. code-block:: toml
102116
103117
[tool.poetry.plugins."manim.plugins"]
104-
"name" = "object_reference"
118+
"name" = "object_reference"
119+
120+
Here ``name`` is the name of the module of the plugin.
121+
122+
Here ``object_reference`` can point to either a function in a module or a module
123+
itself. For example,
124+
125+
.. code-block:: toml
126+
127+
[tool.poetry.plugins."manim.plugins"]
128+
"manim_plugintemplate" = "manim_plugintemplate"
129+
130+
Here a module is used as ``object_reference``, and when this plugin is enabled,
131+
Manim will look for ``__all__`` keyword defined in ``manim_plugintemplate`` and
132+
everything as a global variable one by one.
133+
134+
If ``object_reference`` is a function, Manim calls the function and expects the
135+
function returns a list of modules or functions that needs to defined globally and
136+
it defined it.
137+
138+
For example,
139+
140+
.. code-block:: toml
141+
142+
[tool.poetry.plugins."manim.plugins"]
143+
"manim_plugintemplate" = "manim_awesomeplugin.imports:setup_things"
144+
145+
Here, Manim will call the function ``setup_things`` defined in
146+
``manim_awesomeplugin.imports`` and calls that. It returns a list of function or
147+
modules which will be imported globally.

docs/source/tutorials/configuration.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -353,12 +353,12 @@ A list of all config options
353353
'frame_y_radius', 'from_animation_number', 'images_dir', 'input_file',
354354
'js_renderer_path', 'leave_progress_bars', 'left_side', 'log_dir', 'log_to_file',
355355
'max_files_cached', 'media_dir', 'movie_file_extension', 'output_file',
356-
'partial_movie_dir', 'pixel_height', 'pixel_width', 'png_mode', 'preview',
357-
'progress_bar', 'quality', 'right_side', 'save_as_gif', 'save_last_frame',
356+
'partial_movie_dir', 'pixel_height', 'pixel_width', 'plugins', 'png_mode',
357+
'preview', 'progress_bar', 'quality', 'right_side', 'save_as_gif', 'save_last_frame',
358358
'save_pngs', 'scene_names', 'show_in_file_browser', 'sound', 'tex_dir',
359359
'tex_template', 'tex_template_file', 'text_dir', 'top', 'transparent',
360-
'upto_animation_number', 'use_js_renderer', 'verbosity', 'video_dir', 'write_all',
361-
'write_to_movie']
360+
'upto_animation_number', 'use_js_renderer', 'verbosity', 'video_dir',
361+
'write_all', 'write_to_movie']
362362

363363

364364
A list of all CLI flags

manim/_config/default.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ disable_caching = False
112112
# --tex_template
113113
tex_template =
114114

115+
# specify the plugins as comma seperated values
116+
# manim will load that plugin if it specified here.
117+
plugins =
115118

116119
# Overrides the default output folders, NOT the output file names. Note that
117120
# if the custom_folders flag is present, the Tex and text files will not be put

manim/_config/utils.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ class MyScene(Scene):
272272
"partial_movie_dir",
273273
"pixel_height",
274274
"pixel_width",
275+
"plugins",
275276
"png_mode",
276277
"preview",
277278
"progress_bar",
@@ -551,7 +552,8 @@ def digest_parser(self, parser: configparser.ConfigParser) -> "ManimConfig":
551552
# "frame_height",
552553
]:
553554
setattr(self, key, parser["CLI"].getfloat(key))
554-
555+
# plugins
556+
self.plugins = parser["CLI"].get("plugins", fallback="", raw=True).split(",")
555557
# the next two must be set AFTER digesting pixel_width and pixel_height
556558
self["frame_height"] = parser["CLI"].getfloat("frame_height", 8.0)
557559
width = parser["CLI"].getfloat("frame_width", None)
@@ -1304,6 +1306,15 @@ def tex_template_file(self, val: str) -> None:
13041306
self._d["tex_template_file"] = val # actually set the falsy value
13051307
self._tex_template = TexTemplate() # but don't use it
13061308

1309+
@property
1310+
def plugins(self):
1311+
"""List of plugins to enable."""
1312+
return self._d["plugins"]
1313+
1314+
@plugins.setter
1315+
def plugins(self, value):
1316+
self._d["plugins"] = value
1317+
13071318

13081319
class ManimFrame(Mapping):
13091320
_OPTS: typing.Set[str] = {

manim/plugins/.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

manim/plugins/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .import_plugins import *

manim/plugins/import_plugins.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import types
2+
from importlib import import_module
3+
4+
import pkg_resources
5+
6+
from .. import config, logger
7+
8+
__all__ = []
9+
10+
11+
plugins_requested: list = config["plugins"]
12+
if "" in plugins_requested:
13+
plugins_requested.remove("")
14+
for plugin in pkg_resources.iter_entry_points("manim.plugins"):
15+
if plugin.name not in plugins_requested:
16+
continue
17+
loaded_plugin = plugin.load()
18+
if isinstance(loaded_plugin, types.ModuleType):
19+
# it is a module so it can't be called
20+
# see if __all__ is defined
21+
# if it is defined use that to load all the modules necessary
22+
# essentially this would be similar to `from plugin import *``
23+
# if not just import the module with the plugin name
24+
if hasattr(loaded_plugin, "__all__"):
25+
for thing in loaded_plugin.__all__:
26+
exec(f"{thing}=loaded_plugin.{thing}")
27+
__all__.append(thing)
28+
else:
29+
exec(f"{plugin.name}=loaded_plugin")
30+
__all__.append(plugin.name)
31+
elif isinstance(loaded_plugin, types.FunctionType):
32+
# call the function first
33+
# it will return a list of modules to add globally
34+
# finally add it
35+
lists = loaded_plugin()
36+
for l in lists:
37+
exec(f"{l.__name__}=l")
38+
__all__.append(l.__name__)
39+
plugins_requested.remove(plugin.name)
40+
else:
41+
if plugins_requested != []:
42+
logger.warning("Missing Plugins: %s", plugins_requested)

0 commit comments

Comments
 (0)