103 Commits

Author SHA1 Message Date
TonyCrane
b6c23a09e9 update version to v1.2.0 2021-10-16 13:07:26 +08:00
Michael W
0e574882b3 Refactor #1637 (#1650)
* Refactor #1637

* Refactor #1637

* Refactor #1637

* Refactor #1637

* Refactor #1637

* Refactor #1637

* Update config.py

Co-authored-by: 鹤翔万里 <tonycrane@foxmail.com>
2021-10-16 13:04:52 +08:00
TonyCrane
bee3470856 update changelog 2021-10-16 11:12:13 +08:00
Grant Sanderson
ed3d44120c Merge pull request #1637 from 3b1b/add_warnings
Add warnings and use rich to display log
2021-10-15 12:13:02 -07:00
Grant Sanderson
4466cfe727 Merge branch 'master' into add_warnings 2021-10-15 12:12:36 -07:00
Grant Sanderson
e9aba0b92c Merge pull request #1649 from 3b1b/quintic
Small tweaks
2021-10-15 12:10:18 -07:00
Grant Sanderson
6cdbe0d67a Have image mobject remember the filepath to the Image 2021-10-15 12:08:30 -07:00
Grant Sanderson
7732d2f0ee Fix ComplexPlane -i display bug 2021-10-15 12:07:47 -07:00
Grant Sanderson
f77482c864 Merge pull request #1648 from 3b1b/quintic
Newton fractals and Mandelbrot Fractals
2021-10-15 12:07:05 -07:00
Grant Sanderson
23ebbb2af1 Merge branch 'master' of github.com:3b1b/manim into quintic 2021-10-15 12:05:09 -07:00
Grant Sanderson
14fbed76da Consolidate and rename newton_fractal shader 2021-10-15 12:00:29 -07:00
Grant Sanderson
e10a752c00 Allow releasing of Textures 2021-10-15 08:52:37 -07:00
鹤翔万里
fde82e09c0 Merge pull request #1643 from BillyLikesHacking/patch-1
Fixed Simple typo
2021-10-12 12:21:06 +08:00
BillyLikesHacking
cfd362aa56 Update matrix.py 2021-10-12 09:04:30 +08:00
Grant Sanderson
329d2c6eae Make sure stroke width is always a float 2021-10-11 06:23:03 -07:00
Grant Sanderson
f22a341e84 Save state before each embed 2021-10-11 06:22:41 -07:00
Grant Sanderson
2d115a2c90 Small cleanup 2021-10-11 06:22:26 -07:00
Grant Sanderson
c726eb7a18 buf fix with get_lines_parallel_to_axis 2021-10-11 06:22:05 -07:00
Grant Sanderson
33fa76dfac Add more fractals and decompose slightly 2021-10-11 06:21:47 -07:00
TonyCrane
0021880fba update changelog 2021-10-07 23:37:33 +08:00
TonyCrane
ed99427a3b print version when start 2021-10-07 17:42:23 +08:00
TonyCrane
7425057d9f use rich to log 2021-10-07 17:37:10 +08:00
TonyCrane
ef5253f1bc add cli flag -v to show version info 2021-10-06 17:54:28 +08:00
TonyCrane
fbccb1ebf3 add tips for embed mode 2021-10-06 17:34:46 +08:00
TonyCrane
f626a1a1e2 add tips for interactive mode 2021-10-06 17:28:18 +08:00
TonyCrane
c1242d2dd5 add warning for empty 2021-10-06 17:21:56 +08:00
Grant Sanderson
719c81d72b Fix negative path_arc case 2021-10-05 14:17:41 -07:00
Grant Sanderson
ed1fc4d5f9 Add bounding box as exceptional key to point_cloud mobject 2021-10-05 14:17:24 -07:00
Grant Sanderson
3822b00bec Change how Julia set highlight works 2021-10-05 14:16:50 -07:00
Grant Sanderson
2753beb7bb Merge branch 'master' of github.com:3b1b/manim into quintic 2021-10-03 10:28:14 -07:00
Grant Sanderson
a4afbfd739 Small fixes to PMobject 2021-10-03 10:27:34 -07:00
Grant Sanderson
5f878a2c1a Fix match_style 2021-10-03 10:27:06 -07:00
Grant Sanderson
9483f26a3b Copy uniform arrays in Mobject.copy 2021-10-03 10:26:47 -07:00
Grant Sanderson
b4132e3d5e Bug fix for 0 arrow length case 2021-10-03 10:26:27 -07:00
Grant Sanderson
e9b404406d Updates to poly_fractal shaders 2021-10-01 12:34:06 -07:00
Grant Sanderson
b543cc0e32 Temporary fix for PMobject array resizing 2021-10-01 12:33:52 -07:00
Grant Sanderson
d45ea28dc1 Fix DotCloud.set_radii 2021-10-01 12:33:20 -07:00
Grant Sanderson
788775e419 Larger spacing between Text lines 2021-10-01 12:32:38 -07:00
Grant Sanderson
1bca0e63e9 Remove unnecessary import 2021-10-01 12:32:20 -07:00
Grant Sanderson
54ad3550ef Fix bug with SVG rectangles 2021-10-01 12:32:04 -07:00
Grant Sanderson
d19b386415 Fix dots 2021-10-01 12:31:16 -07:00
Grant Sanderson
e359f520bc Merge pull request #1625 from YishiMichael/master
Add Code mobject with syntax highlighting
2021-09-15 09:31:34 -07:00
TonyCrane
696fc85ff7 change CRLF to LF 2021-09-15 20:29:56 +08:00
TonyCrane
add1daf500 change CRLF to LF 2021-09-15 20:02:57 +08:00
Michael W
242e4a3471 Add pygments to support Code 2021-09-15 16:04:27 +08:00
Michael W
9e563ae3b4 Add Code mobject and rewrite Text.text2settings() 2021-09-15 15:55:19 +08:00
鹤翔万里
da909c0df8 Merge pull request #1624 from BillyLikesHacking/patch-1
Update comments in example.py
2021-09-11 23:17:41 -05:00
BillyLikesHacking
0239e12d8a Update comments in example.py
updated the cmd command line to "manimgl example_scenes.py OpeningManimExample" instead of "python -m manim", which the latter one was used for cairo-backend manim version instead of the current manimgl
2021-09-12 11:06:09 +08:00
鹤翔万里
0fd8fdc3ca Merge pull request #1620 from meadlai/patch-1
Fix the import
2021-09-04 08:50:55 -05:00
meadlai
762f1abef7 Fix the import
Fix the import statement
2021-09-04 21:31:57 +08:00
Grant Sanderson
17c2772b84 Add Mobject.replicate 2021-08-28 06:21:51 -07:00
Grant Sanderson
0d2d1b5c03 Add MAX_DEGREE to poly_fractal 2021-08-28 06:21:32 -07:00
Grant Sanderson
4ce123be44 (Maybe temporary?) upates to Tracers 2021-08-26 11:44:45 -07:00
Grant Sanderson
d5a88d0fa4 Pass tuples and not arrays to uniforms 2021-08-26 11:44:24 -07:00
Grant Sanderson
3b146636b4 Use isclose in place of of == 0 2021-08-26 11:43:44 -07:00
Grant Sanderson
b24ba19dec Add kwargs to set_length 2021-08-26 11:43:19 -07:00
Grant Sanderson
0dc096bf57 Fix bug for single-valued ValueTracker 2021-08-26 11:42:58 -07:00
Grant Sanderson
e40a2935b1 Remove redundant relic 2021-08-26 11:42:32 -07:00
Grant Sanderson
f84b8a66fe poly_fractal shader 2021-08-26 11:40:55 -07:00
鹤翔万里
952a598e3b Merge pull request #1613 from k1499/fix-example-scenes-doc-typo
Typo fix in UpdatersExample
2021-08-24 23:27:08 -05:00
Grant Sanderson
c635f19f2a Push pixel_coords_to_space_coords to Window 2021-08-24 11:26:43 -07:00
Grant Sanderson
8645894255 Fix CoordinateSystem init bug 2021-08-24 11:26:22 -07:00
Kausik SS
e712951f2d Typo fix in UpdatersExample 2021-08-24 10:44:24 +05:30
TonyCrane
1b24074369 update packaging method 2021-08-24 09:00:42 +08:00
Grant Sanderson
acba13f499 Add Scene.point_to_mobject 2021-08-22 14:57:32 -07:00
Grant Sanderson
61aec6051a Use generator in place of list 2021-08-21 17:08:28 -07:00
Grant Sanderson
9a78d13212 get_smooth_quadratic_bezier_handle_points edge case for 2 points 2021-08-21 17:07:49 -07:00
Grant Sanderson
0787c4f362 Make sure framerate is 30 for previewed scenes 2021-08-21 17:07:20 -07:00
Grant Sanderson
0b7b3f4f31 Improve NumberLine.p2n speed 2021-08-21 17:07:05 -07:00
Grant Sanderson
7356a36fa7 Only call self.throw_error_if_no_points once for get_start_and_end 2021-08-21 17:06:37 -07:00
Grant Sanderson
f3e3a7c56f Improve TracingTaill 2021-08-21 17:05:40 -07:00
Grant Sanderson
a35dd5a3cb Add TracingTail 2021-08-21 10:36:59 -07:00
Grant Sanderson
7b4199c674 Call _handle_scale_side_effects after scaling takes place 2021-08-21 10:36:18 -07:00
Grant Sanderson
d8378d8157 Use approximately_smooth by default 2021-08-21 10:35:50 -07:00
Grant Sanderson
8647a6429d Reimplement arrow to be stroke, not fill (which will break some past scenes) 2021-08-21 10:35:29 -07:00
Grant Sanderson
3bb8f3f042 Add set_max_width, set_max_height, etc. 2021-08-19 14:47:30 -07:00
Grant Sanderson
56df15453f Fix bug with common range array used for all coordinate systems 2021-08-19 14:47:10 -07:00
Grant Sanderson
d50717a3fc Merge pull request #1607 from 3b1b/perf
Perf
2021-08-19 10:33:47 -07:00
Grant Sanderson
ca9b70e218 Make sure triangulation is remembered correctly 2021-08-19 09:19:02 -07:00
Grant Sanderson
25c5aa2c65 Small stylistic cleanup 2021-08-19 09:18:48 -07:00
Grant Sanderson
c08ea4e645 Add FlashyFadeIn 2021-08-19 09:00:30 -07:00
Grant Sanderson
573d630e5b Merge branch 'master' of github.com:3b1b/manim 2021-08-19 08:38:03 -07:00
Grant Sanderson
6d72893382 Let ValueTracker track vectors 2021-08-19 08:37:57 -07:00
Grant Sanderson
40290ada83 Remove unused parameter 2021-08-19 08:37:40 -07:00
Grant Sanderson
bd356daa99 Add VCube 2021-08-19 08:37:01 -07:00
Grant Sanderson
b667db2d31 Simplify Square 2021-08-19 08:34:53 -07:00
Grant Sanderson
f92211b352 Merge pull request #1598 from YishiMichael/master
Support the elliptical arc command for SVGMobject
2021-08-09 16:19:15 -07:00
Grant Sanderson
1c2b52a128 Merge branch 'master' of github.com:3b1b/manim 2021-08-09 16:09:01 -07:00
Grant Sanderson
eb315daeda Merge pull request #1601 from 3b1b/revert-scale-changes
Revert scale changes
2021-08-09 16:07:33 -07:00
Grant Sanderson
e151334675 Alternate fix to Decimal scaling issue 2021-08-09 16:06:19 -07:00
Grant Sanderson
bbeba108bc Merge branch 'master' of github.com:3b1b/manim into revert-scale-changes 2021-08-09 15:42:41 -07:00
Grant Sanderson
77ce17679c Change back to simpler Mobject.scale implementation 2021-08-09 15:42:32 -07:00
Grant Sanderson
7fa2654d8a Revert changes to Mobject.scale 2021-08-09 15:37:03 -07:00
Michael W
ec620fa849 Support the elliptical arc command for SVGMobject 2021-08-08 20:53:50 +08:00
鹤翔万里
da53a6f808 Merge pull request #1594 from slowy07/minor-fixing
fix: fix typo spelling grammar
2021-08-07 10:57:08 -05:00
slowy07
1e621e8278 fix: fix typo spelling grammar 2021-08-07 22:25:26 +07:00
Grant Sanderson
31119b630e Merge pull request #1592 from AStarySky/patch-1
fix put_start_and_end_on
2021-08-06 10:25:46 -07:00
AStarySky
b0fd520382 Update mobject.py
emmm i forgot to swap those
2021-08-04 23:38:21 +08:00
AStarySky
c1e14ef5b6 Update mobject.py 2021-08-04 23:32:59 +08:00
AStarySky
e9470b6bde Update mobject.py 2021-08-04 23:04:35 +08:00
AStarySky
5c0a1e4b76 fix put_start_and_end_on 2021-08-04 22:52:13 +08:00
Grant Sanderson
121e6215f8 Merge pull request #1591 from TonyCrane/improve-scale
Improve `Mobject.scale`
2021-08-03 13:37:18 -07:00
TonyCrane
3d5642f3d7 little improvement to Mobject.scale 2021-08-03 15:25:13 +08:00
52 changed files with 1125 additions and 295 deletions

View File

@@ -1,4 +1,4 @@
from manimlib.imports import *
from manimlib import *
class SquareToCircle(Scene):
def construct(self):

View File

@@ -1,10 +1,64 @@
Changelog
=========
Unreleased
----------
v1.2.0
------
Fixed bugs
^^^^^^^^^^
- `#1592 <https://github.com/3b1b/manim/pull/1592>`__: Fixed ``put_start_and_end_on`` in 3D
- `#1601 <https://github.com/3b1b/manim/pull/1601>`__: Fixed ``DecimalNumber``'s scaling issue
- `56df154 <https://github.com/3b1b/manim/commit/56df15453f3e3837ed731581e52a1d76d5692077>`__: Fixed bug with common range array used for all coordinate systems
- `8645894 <https://github.com/3b1b/manim/commit/86458942550c639a241267d04d57d0e909fcf252>`__: Fixed ``CoordinateSystem`` init bug
- `0dc096b <https://github.com/3b1b/manim/commit/0dc096bf576ea900b351e6f4a80c13a77676f89b>`__: Fixed bug for single-valued ``ValueTracker``
- `54ad355 <https://github.com/3b1b/manim/commit/54ad3550ef0c0e2fda46b26700a43fa8cde0973f>`__: Fixed bug with SVG rectangles
- `d45ea28 <https://github.com/3b1b/manim/commit/d45ea28dc1d92ab9c639a047c00c151382eb0131>`__: Fixed ``DotCloud.set_radii``
- `b543cc0 <https://github.com/3b1b/manim/commit/b543cc0e32d45399ee81638b6d4fb631437664cd>`__: Temporarily fixed bug for ``PMobject`` array resizing
- `5f878a2 <https://github.com/3b1b/manim/commit/5f878a2c1aa531b7682bd048468c72d2835c7fe5>`__: Fixed ``match_style``
- `719c81d <https://github.com/3b1b/manim/commit/719c81d72b00dcf49f148d7c146774b22e0fe348>`__: Fixed negative ``path_arc`` case
- `c726eb7 <https://github.com/3b1b/manim/commit/c726eb7a180b669ee81a18555112de26a8aff6d6>`__: Fixed bug with ``CoordinateSystem.get_lines_parallel_to_axis``
- `7732d2f <https://github.com/3b1b/manim/commit/7732d2f0ee10449c5731499396d4911c03e89648>`__: Fixed ``ComplexPlane`` -i display bug
New Features
^^^^^^^^^^^^
- `#1598 <https://github.com/3b1b/manim/pull/1598>`__: Supported the elliptical arc command ``A`` for ``SVGMobject``
- `#1607 <https://github.com/3b1b/manim/pull/1607>`__: Added ``FlashyFadeIn``
- `#1607 <https://github.com/3b1b/manim/pull/1607>`__: Save triangulation
- `#1625 <https://github.com/3b1b/manim/pull/1625>`__: Added new ``Code`` mobject
- `#1637 <https://github.com/3b1b/manim/pull/1637>`__: Add warnings and use rich to display log
- `bd356da <https://github.com/3b1b/manim/commit/bd356daa99bfe3134fcb192a5f72e0d76d853801>`__: Added ``VCube``
- `6d72893 <https://github.com/3b1b/manim/commit/6d7289338234acc6658b9377c0f0084aa1fa7119>`__: Supported ``ValueTracker`` to track vectors
- `3bb8f3f <https://github.com/3b1b/manim/commit/3bb8f3f0422a5dfba0da6ef122dc0c01f31aff03>`__: Added ``set_max_width``, ``set_max_height``, ``set_max_depth`` to ``Mobject``
- `a35dd5a <https://github.com/3b1b/manim/commit/a35dd5a3cbdeffa3891d5aa5f80287c18dba2f7f>`__: Added ``TracgTail``
- `acba13f <https://github.com/3b1b/manim/commit/acba13f4991b78d54c0bf93cce7ca3b351c25476>`__: Added ``Scene.point_to_mobject``
- `f84b8a6 <https://github.com/3b1b/manim/commit/f84b8a66fe9e8b3872e5c716c5c240c14bb555ee>`__: Added poly_fractal shader
- `b24ba19 <https://github.com/3b1b/manim/commit/b24ba19dec48ba4e38acbde8eec6d3a308b6ab83>`__: Added kwargs to ``TipableVMobject.set_length``
- `17c2772 <https://github.com/3b1b/manim/commit/17c2772b84abf6392a4170030e36e981de4737d0>`__: Added ``Mobject.replicate``
- `33fa76d <https://github.com/3b1b/manim/commit/33fa76dfac36e70bb5fad69dc6a336800c6dacce>`__: Added mandelbrot_fractal shader
- `f22a341 <https://github.com/3b1b/manim/commit/f22a341e8411eae9331d4dd976b5e15bc6db08d9>`__: Saved state before each embed
- `e10a752 <https://github.com/3b1b/manim/commit/e10a752c0001e8981038faa03be4de2603d3565f>`__: Allowed releasing of Textures
- `14fbed7 <https://github.com/3b1b/manim/commit/14fbed76da4b493191136caebb8a955e2d41265b>`__: Consolidated and renamed newton_fractal shader
- `6cdbe0d <https://github.com/3b1b/manim/commit/6cdbe0d67a11ab14a6d84840a114ae6d3af10168>`__: Hade ``ImageMoject`` remember the filepath to the Image
Refactor
^^^^^^^^
- `#1601 <https://github.com/3b1b/manim/pull/1601>`__: Changed back to simpler ``Mobject.scale`` implementation
- `b667db2 <https://github.com/3b1b/manim/commit/b667db2d311a11cbbca2a6ff511d2c3cf1675486>`__: Simplified ``Square``
- `40290ad <https://github.com/3b1b/manim/commit/40290ada8343f10901fa9151cbdf84689667786d>`__: Removed unused parameter ``triangulation_locked``
- `8647a64 <https://github.com/3b1b/manim/commit/8647a6429dd0c52cba14e971b8c09194a93cfd87>`__: Reimplemented ``Arrow``
- `d8378d8 <https://github.com/3b1b/manim/commit/d8378d8157040cd797cc47ef9576beffd8607863>`__: Used ``make_approximately_smooth`` for ``set_points_smoothly`` by default
- `7b4199c <https://github.com/3b1b/manim/commit/7b4199c674e291f1b84678828b63b6bd4fcc6b17>`__: Refactored to call ``_handle_scale_side_effects`` after scaling takes place
- `7356a36 <https://github.com/3b1b/manim/commit/7356a36fa70a8279b43ae74e247cbd43b2bfd411>`__: Refactored to only call ``throw_error_if_no_points`` once for ``get_start_and_end``
- `0787c4f <https://github.com/3b1b/manim/commit/0787c4f36270a6560b50ce3e07b30b0ec5f2ba3e>`__: Made sure framerate is 30 for previewed scenes
- `c635f19 <https://github.com/3b1b/manim/commit/c635f19f2a33e916509e53ded46f55e2afa8f5f2>`__: Pushed ``pixel_coords_to_space_coords`` to ``Window``
- `d5a88d0 <https://github.com/3b1b/manim/commit/d5a88d0fa457cfcf4cb9db417a098c37c95c7051>`__: Refactored to pass tuples and not arrays to uniforms
- `9483f26 <https://github.com/3b1b/manim/commit/9483f26a3b056de0e34f27acabd1a946f1adbdf9>`__: Refactored to copy uniform arrays in ``Mobject.copy``
- `ed1fc4d <https://github.com/3b1b/manim/commit/ed1fc4d5f94467d602a568466281ca2d0368b506>`__: Added ``bounding_box`` as exceptional key to point_cloud mobject
- `329d2c6 <https://github.com/3b1b/manim/commit/329d2c6eaec3d88bfb754b555575a3ea7c97a7e0>`__: Made sure stroke width is always a float
No changes
v1.1.0
-------

View File

@@ -52,7 +52,7 @@ flag abbr function
``--finder`` Show the output file in finder
``--config`` Guide for automatic configuration
``--file_name FILE_NAME`` Name for the movie or image file
``--start_at_animation_number START_AT_ANIMATION_NUMBER`` ``-n`` Start rendering not from the first animation, but from another, specified by its index. If you passin two comma separated values, e.g. "3,6", it will end the rendering at the second value.
``--start_at_animation_number START_AT_ANIMATION_NUMBER`` ``-n`` Start rendering not from the first animation, but from another, specified by its index. If you passing two comma separated values, e.g. "3,6", it will end the rendering at the second value.
``--resolution RESOLUTION`` ``-r`` Resolution, passed as "WxH", e.g. "1920x1080"
``--frame_rate FRAME_RATE`` Frame rate, as an integer
``--color COLOR`` ``-c`` Background color

View File

@@ -187,7 +187,7 @@ TexTransformExample
# Passing in muliple arguments to Tex will result
# in the same expression as if those arguments had
# been joined together, except that the submobject
# heirarchy of the resulting mobject ensure that the
# hierarchy of the resulting mobject ensure that the
# Tex mobject has a subject corresponding to
# each of these strings. For example, the Tex mobject
# below will have 5 subjects, corresponding to the
@@ -335,7 +335,7 @@ UpdatersExample
# If the argument itself might change, you can use f_always,
# for which the arguments following the initial Mobject method
# should be functions returning arguments to that method.
# The following line ensures thst decimal.set_value(square.get_y())
# The following line ensures that decimal.set_value(square.get_y())
# is called every frame
f_always(number.set_value, square.get_width)
# You could also write the following equivalent line

View File

@@ -2,7 +2,7 @@ from manimlib import *
import numpy as np
# To watch one of these scenes, run the following:
# python -m manim example_scenes.py SquareToCircle
# manimgl example_scenes.py OpeningManimExample
# Use -s to skip to the end and just save the final frame
# Use -w to write the animation to a file
# Use -o to write it to a file and open it once done
@@ -164,7 +164,7 @@ class TexTransformExample(Scene):
# Passing in muliple arguments to Tex will result
# in the same expression as if those arguments had
# been joined together, except that the submobject
# heirarchy of the resulting mobject ensure that the
# hierarchy of the resulting mobject ensure that the
# Tex mobject has a subject corresponding to
# each of these strings. For example, the Tex mobject
# below will have 5 subjects, corresponding to the

View File

@@ -1,3 +1,7 @@
import pkg_resources
__version__ = pkg_resources.get_distribution("manimgl").version
from manimlib.constants import *
from manimlib.animation.animation import *

View File

@@ -2,10 +2,15 @@
import manimlib.config
import manimlib.extract_scene
import manimlib.utils.init_config
from manimlib import __version__
def main():
print(f"ManimGL \033[32mv{__version__}\033[0m")
args = manimlib.config.parse_cli()
if args.version and args.file == None:
return
if args.config:
manimlib.utils.init_config.init_customization()
@@ -16,5 +21,5 @@ def main():
for scene in scenes:
scene.run()
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -9,6 +9,7 @@ from manimlib.animation.composition import Succession
from manimlib.animation.creation import ShowCreation
from manimlib.animation.creation import ShowPartial
from manimlib.animation.fading import FadeOut
from manimlib.animation.fading import FadeIn
from manimlib.animation.transform import Transform
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.geometry import Circle
@@ -21,6 +22,8 @@ from manimlib.utils.bezier import interpolate
from manimlib.utils.config_ops import digest_config
from manimlib.utils.rate_functions import there_and_back
from manimlib.utils.rate_functions import wiggle
from manimlib.utils.rate_functions import smooth
from manimlib.utils.rate_functions import squish_rate_func
class FocusOn(Transform):
@@ -361,3 +364,22 @@ class TurnInsideOut(Transform):
def create_target(self):
return self.mobject.copy().reverse_points()
class FlashyFadeIn(AnimationGroup):
CONFIG = {
"fade_lag": 0,
}
def __init__(self, vmobject, stroke_width=2, **kwargs):
digest_config(self, kwargs)
outline = vmobject.copy()
outline.set_fill(opacity=0)
outline.set_stroke(width=stroke_width, opacity=1)
rate_func = kwargs.get("rate_func", smooth)
super().__init__(
FadeIn(vmobject, rate_func=squish_rate_func(rate_func, self.fade_lag, 1)),
VShowPassingFlash(outline, time_width=1),
**kwargs
)

View File

@@ -44,7 +44,7 @@ class Transform(Animation):
self.check_target_mobject_validity()
# Use a copy of target_mobject for the align_data_and_family
# call so that the actual target_mobject stays
# preserved, since calling allign_data will potentailly
# preserved, since calling allign_data will potentially
# change the structure of both arguments
self.target_copy = self.target_mobject.copy()
self.mobject.align_data_and_family(self.target_copy)
@@ -161,7 +161,7 @@ class ApplyMethod(Transform):
method is a method of Mobject, *args are arguments for
that method. Key word arguments should be passed in
as the last arg, as a dict, since **kwargs is for
configuration of the transform itslef
configuration of the transform itself
Relies on the fact that mobject methods return the mobject
"""

View File

@@ -316,17 +316,6 @@ class Camera(object):
self.frame.set_height(frame_height)
self.frame.set_width(frame_width)
def pixel_coords_to_space_coords(self, px, py, relative=False):
pw, ph = self.fbo.size
fw, fh = self.get_frame_shape()
fc = self.get_frame_center()
if relative:
return 2 * np.array([px / pw, py / ph, 0])
else:
# Only scale wrt one axis
scale = fh / ph
return fc + scale * np.array([(px - pw / 2), (py - ph / 2), 0])
# Rendering
def capture(self, *mobjects, **kwargs):
self.refresh_perspective_uniforms()
@@ -423,6 +412,8 @@ class Camera(object):
shader[name].value = tid
for name, value in it.chain(shader_wrapper.uniforms.items(), self.perspective_uniforms.items()):
try:
if isinstance(value, np.ndarray):
value = tuple(value)
shader[name].value = value
except KeyError:
pass
@@ -449,12 +440,13 @@ class Camera(object):
}
def init_textures(self):
self.path_to_texture_id = {}
self.n_textures = 0
self.path_to_texture = {}
def get_texture_id(self, path):
if path not in self.path_to_texture_id:
# A way to increase tid's sequentially
tid = len(self.path_to_texture_id)
if path not in self.path_to_texture:
tid = self.n_textures
self.n_textures += 1
im = Image.open(path).convert("RGBA")
texture = self.ctx.texture(
size=im.size,
@@ -462,8 +454,14 @@ class Camera(object):
data=im.tobytes(),
)
texture.use(location=tid)
self.path_to_texture_id[path] = tid
return self.path_to_texture_id[path]
self.path_to_texture[path] = (tid, texture)
return self.path_to_texture[path][0]
def release_texture(self, path):
tid_and_texture = self.path_to_texture.pop(path, None)
if tid_and_texture:
tid_and_texture[1].release()
return self
# Mostly just defined so old scenes don't break

View File

@@ -9,6 +9,7 @@ from screeninfo import get_monitors
from manimlib.utils.config_ops import merge_dicts_recursively
from manimlib.utils.init_config import init_customization
from manimlib.logger import log
def parse_cli():
@@ -136,10 +137,15 @@ def parse_cli():
"--config_file",
help="Path to the custom configuration file",
)
parser.add_argument(
"-v", "--version",
action="store_true",
help="Display the version of manimgl"
)
args = parser.parse_args()
return args
except argparse.ArgumentError as err:
print(str(err))
log.error(str(err))
sys.exit(2)
@@ -185,36 +191,45 @@ def get_custom_config():
return config
def check_temporary_storage(config):
if config["directories"]["temporary_storage"] == "" and sys.platform == "win32":
log.warning("You may be using Windows platform and have not specified the path of"
" `temporary_storage`, which may cause OSError. So it is recommended"
" to specify the `temporary_storage` in the config file (.yml)")
def get_configuration(args):
global __config_file__
# ensure __config_file__ always exists
if args.config_file is not None:
if not os.path.exists(args.config_file):
print(f"Can't find {args.config_file}.")
log.error(f"Can't find {args.config_file}.")
if sys.platform == 'win32':
print(f"Copying default configuration file to {args.config_file}...")
log.info(f"Copying default configuration file to {args.config_file}...")
os.system(f"copy default_config.yml {args.config_file}")
elif sys.platform in ["linux2", "darwin"]:
print(f"Copying default configuration file to {args.config_file}...")
log.info(f"Copying default configuration file to {args.config_file}...")
os.system(f"cp default_config.yml {args.config_file}")
else:
print("Please create the configuration file manually.")
print("Read configuration from default_config.yml.")
log.info("Please create the configuration file manually.")
log.info("Read configuration from default_config.yml.")
else:
__config_file__ = args.config_file
global_defaults_file = os.path.join(get_manim_dir(), "manimlib", "default_config.yml")
if not (os.path.exists(global_defaults_file) or os.path.exists(__config_file__)):
print("There is no configuration file detected. Initial configuration:\n")
log.info("There is no configuration file detected. Switch to the config file initializer:")
init_customization()
elif not os.path.exists(__config_file__):
print(f"Warning: Using the default configuration file, which you can modify in {global_defaults_file}")
print(f"If you want to create a local configuration file, you can create a file named {__config_file__}, or run manimgl --config")
log.info(f"Using the default configuration file, which you can modify in `{global_defaults_file}`")
log.info("If you want to create a local configuration file, you can create a file named"
f" `{__config_file__}`, or run `manimgl --config`")
custom_config = get_custom_config()
check_temporary_storage(custom_config)
write_file = any([args.write_file, args.open, args.finder])
if args.transparent:
@@ -319,9 +334,9 @@ def get_camera_configuration(args, custom_config):
try:
bg_color = args.color or custom_config["style"]["background_color"]
camera_config["background_color"] = colour.Color(bg_color)
except AttributeError as err:
print("Please use a valid color")
print(err)
except ValueError as err:
log.error("Please use a valid color")
log.error(err)
sys.exit(2)
# If rendering a transparent image/move, make sure the

View File

@@ -1,9 +1,9 @@
import inspect
import sys
import logging
from manimlib.scene.scene import Scene
from manimlib.config import get_custom_config
from manimlib.logger import log
class BlankScene(Scene):
@@ -41,8 +41,11 @@ def prompt_user_for_choice(scene_classes):
name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str)-1]
for split_str in user_input.replace(" ", "").split(",")
]
except IndexError:
log.error("Invalid scene number")
sys.exit(2)
except KeyError:
logging.log(logging.ERROR, "Invalid scene")
log.error("Invalid scene name")
sys.exit(2)
except EOFError:
sys.exit(1)
@@ -78,10 +81,7 @@ def get_scenes_to_render(scene_classes, scene_config, config):
found = True
break
if not found and (scene_name != ""):
logging.log(
logging.ERROR,
f"No scene named {scene_name} found",
)
log.error(f"No scene named {scene_name} found")
if result:
return result
if len(scene_classes) == 1:

12
manimlib/logger.py Normal file
View File

@@ -0,0 +1,12 @@
import logging
from rich.logging import RichHandler
__all__ = ["log"]
FORMAT = "%(message)s"
logging.basicConfig(
level="NOTSET", format=FORMAT, datefmt="[%X]", handlers=[RichHandler()]
)
log = logging.getLogger("rich")

View File

@@ -1,8 +1,13 @@
from manimlib.constants import *
import numpy as np
from manimlib.constants import BLUE_D
from manimlib.constants import BLUE_B
from manimlib.constants import BLUE_E
from manimlib.constants import GREY_BROWN
from manimlib.constants import WHITE
from manimlib.mobject.mobject import Mobject
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.rate_functions import smooth
from manimlib.utils.space_ops import get_norm
class AnimatedBoundary(VGroup):
@@ -74,25 +79,63 @@ class TracedPath(VMobject):
CONFIG = {
"stroke_width": 2,
"stroke_color": WHITE,
"min_distance_to_new_point": 0.1,
"time_traced": np.inf,
"fill_opacity": 0,
"time_per_anchor": 1 / 15,
}
def __init__(self, traced_point_func, **kwargs):
super().__init__(**kwargs)
self.traced_point_func = traced_point_func
self.add_updater(lambda m: m.update_path())
self.time = 0
self.traced_points = []
self.add_updater(lambda m, dt: m.update_path(dt))
def update_path(self):
new_point = self.traced_point_func()
if not self.has_points():
self.start_new_path(new_point)
self.add_line_to(new_point)
def update_path(self, dt):
if dt == 0:
return self
point = self.traced_point_func().copy()
self.traced_points.append(point)
if self.time_traced < np.inf:
n_relevant_points = int(self.time_traced / dt + 0.5)
# n_anchors = int(self.time_traced / self.time_per_anchor)
n_tps = len(self.traced_points)
if n_tps < n_relevant_points:
points = self.traced_points + [point] * (n_relevant_points - n_tps)
else:
points = self.traced_points[n_tps - n_relevant_points:]
# points = [
# self.traced_points[max(n_tps - int(alpha * n_relevant_points) - 1, 0)]
# for alpha in np.linspace(1, 0, n_anchors)
# ]
# Every now and then refresh the list
if n_tps > 10 * n_relevant_points:
self.traced_points = self.traced_points[-n_relevant_points:]
else:
# Set the end to be the new point
self.get_points()[-1] = new_point
# sparseness = max(int(self.time_per_anchor / dt), 1)
# points = self.traced_points[::sparseness]
# points[-1] = self.traced_points[-1]
points = self.traced_points
# Second to last point
nppcc = self.n_points_per_curve
dist = get_norm(new_point - self.get_points()[-nppcc])
if dist >= self.min_distance_to_new_point:
self.add_line_to(new_point)
if points:
self.set_points_smoothly(points)
self.time += dt
return self
class TracingTail(TracedPath):
CONFIG = {
"stroke_width": (0, 3),
"stroke_opacity": (0, 1),
"stroke_color": WHITE,
"time_traced": 1.0,
}
def __init__(self, mobject_or_func, **kwargs):
if isinstance(mobject_or_func, Mobject):
func = mobject_or_func.get_center
else:
func = mobject_or_func
super().__init__(func, **kwargs)

View File

@@ -10,6 +10,7 @@ from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.number_line import NumberLine
from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.config_ops import digest_config
from manimlib.utils.config_ops import merge_dicts_recursively
from manimlib.utils.simple_functions import binary_search
from manimlib.utils.space_ops import angle_of_vector
@@ -25,13 +26,18 @@ class CoordinateSystem():
"""
CONFIG = {
"dimension": 2,
"x_range": np.array([-8.0, 8.0, 1.0]),
"y_range": np.array([-4.0, 4.0, 1.0]),
"width": None,
"height": None,
"default_x_range": [-8.0, 8.0, 1.0],
"default_y_range": [-4.0, 4.0, 1.0],
"width": FRAME_WIDTH,
"height": FRAME_HEIGHT,
"num_sampled_graph_points_per_tick": 20,
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
self.x_range = np.array(self.default_x_range)
self.y_range = np.array(self.default_y_range)
def coords_to_point(self, *coords):
raise Exception("Not implemented")
@@ -127,6 +133,7 @@ class CoordinateSystem():
**kwargs
)
graph.underlying_function = function
graph.x_range = x_range
return graph
def get_parametric_curve(self, function, **kwargs):
@@ -282,7 +289,9 @@ class Axes(VGroup, CoordinateSystem):
x_range=None,
y_range=None,
**kwargs):
super().__init__(**kwargs)
CoordinateSystem.__init__(self, **kwargs)
VGroup.__init__(self, **kwargs)
if x_range is not None:
self.x_range[:len(x_range)] = x_range
if y_range is not None:
@@ -441,7 +450,7 @@ class NumberPlane(Axes):
return lines1, lines2
def get_lines_parallel_to_axis(self, axis1, axis2):
freq = axis1.x_step
freq = axis2.x_step
ratio = self.faded_line_ratio
line = Line(axis1.get_start(), axis1.get_end())
dense_freq = (1 + ratio)
@@ -501,15 +510,15 @@ class ComplexPlane(NumberPlane):
def p2n(self, point):
return self.point_to_number(point)
def get_default_coordinate_values(self):
def get_default_coordinate_values(self, skip_first=True):
x_numbers = self.get_x_axis().get_tick_range()[1:]
y_numbers = self.get_y_axis().get_tick_range()[1:]
y_numbers = [complex(0, y) for y in y_numbers if y != 0]
return [*x_numbers, *y_numbers]
def add_coordinate_labels(self, numbers=None, **kwargs):
def add_coordinate_labels(self, numbers=None, skip_first=True, **kwargs):
if numbers is None:
numbers = self.get_default_coordinate_values()
numbers = self.get_default_coordinate_values(skip_first)
self.coordinate_labels = VGroup()
for number in numbers:
@@ -522,6 +531,15 @@ class ComplexPlane(NumberPlane):
axis = self.get_x_axis()
value = z.real
number_mob = axis.get_number_mobject(value, **kwargs)
# For i and -i, remove the "1"
if z.imag == 1:
number_mob.remove(number_mob[0])
if z.imag == -1:
number_mob.remove(number_mob[1])
number_mob[0].next_to(
number_mob[1], LEFT,
buff=number_mob[0].get_width() / 4
)
self.coordinate_labels.add(number_mob)
self.add(self.coordinate_labels)
return self

View File

@@ -1,4 +1,6 @@
import numpy as np
import math
import numbers
from manimlib.constants import *
from manimlib.mobject.mobject import Mobject
@@ -27,6 +29,7 @@ DEFAULT_ARROW_TIP_LENGTH = 0.35
DEFAULT_ARROW_TIP_WIDTH = 0.35
# Deprecate?
class TipableVMobject(VMobject):
"""
Meant for shared functionality between Arc and Line.
@@ -404,32 +407,38 @@ class Line(TipableVMobject):
self.set_points_by_ends(self.start, self.end, self.buff, self.path_arc)
def set_points_by_ends(self, start, end, buff=0, path_arc=0):
if path_arc:
self.set_points(Arc.create_quadratic_bezier_points(path_arc))
self.put_start_and_end_on(start, end)
else:
vect = end - start
dist = get_norm(vect)
if np.isclose(dist, 0):
self.set_points_as_corners([start, end])
self.account_for_buff(self.buff)
return self
if path_arc:
neg = path_arc < 0
if neg:
path_arc = -path_arc
start, end = end, start
radius = (dist / 2) / math.sin(path_arc / 2)
alpha = (PI - path_arc) / 2
center = start + radius * normalize(rotate_vector(end - start, alpha))
raw_arc_points = Arc.create_quadratic_bezier_points(
angle=path_arc - 2 * buff / radius,
start_angle=angle_of_vector(start - center) + buff / radius,
)
if neg:
raw_arc_points = raw_arc_points[::-1]
self.set_points(center + radius * raw_arc_points)
else:
if buff > 0 and dist > 0:
start = start + vect * (buff / dist)
end = end - vect * (buff / dist)
self.set_points_as_corners([start, end])
return self
def set_path_arc(self, new_value):
self.path_arc = new_value
self.init_points()
def account_for_buff(self, buff):
if buff == 0:
return
#
if self.path_arc == 0:
length = self.get_length()
else:
length = self.get_arc_length()
#
if length < 2 * buff:
return
buff_prop = buff / length
self.pointwise_become_partial(self, buff_prop, 1 - buff_prop)
return self
def set_start_and_end_attrs(self, start, end):
# If either start or end are Mobjects, this
# gives their centers
@@ -439,8 +448,8 @@ class Line(TipableVMobject):
# Now that we know the direction between them,
# we can find the appropriate boundary point from
# start and end, if they're mobjects
self.start = self.pointify(start, vect) + self.buff * vect
self.end = self.pointify(end, -vect) - self.buff * vect
self.start = self.pointify(start, vect)
self.end = self.pointify(end, -vect)
def pointify(self, mob_or_point, direction=None):
"""
@@ -461,8 +470,10 @@ class Line(TipableVMobject):
def put_start_and_end_on(self, start, end):
curr_start, curr_end = self.get_start_and_end()
if (curr_start == curr_end).all():
self.set_points_by_ends(start, end, self.path_arc)
if np.isclose(curr_start, curr_end).all():
# Handle null lines more gracefully
self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc)
return self
return super().put_start_and_end_on(start, end)
def get_vector(self):
@@ -494,8 +505,8 @@ class Line(TipableVMobject):
)
return self
def set_length(self, length):
self.scale(length / self.get_length())
def set_length(self, length, **kwargs):
self.scale(length / self.get_length(), **kwargs)
class DashedLine(Line):
@@ -578,6 +589,80 @@ class Elbow(VMobject):
class Arrow(Line):
CONFIG = {
"stroke_color": GREY_A,
"stroke_width": 5,
"tip_width_ratio": 4,
"width_to_tip_len": 0.0075,
"max_tip_length_to_length_ratio": 0.3,
"max_width_to_length_ratio": 10,
"buff": 0.25,
}
def set_points_by_ends(self, start, end, buff=0, path_arc=0):
super().set_points_by_ends(start, end, buff, path_arc)
self.insert_tip_anchor()
return self
def init_colors(self):
super().init_colors()
self.create_tip_with_stroke_width()
def get_arc_length(self):
# Push up into Line?
arc_len = get_norm(self.get_vector())
if self.path_arc > 0:
arc_len *= self.path_arc / (2 * math.sin(self.path_arc / 2))
return arc_len
def insert_tip_anchor(self):
prev_end = self.get_end()
arc_len = self.get_arc_length()
tip_len = self.get_stroke_width() * self.width_to_tip_len * self.tip_width_ratio
if tip_len >= self.max_tip_length_to_length_ratio * arc_len:
alpha = self.max_tip_length_to_length_ratio
else:
alpha = tip_len / arc_len
self.pointwise_become_partial(self, 0, 1 - alpha)
self.add_line_to(prev_end)
return self
def create_tip_with_stroke_width(self):
width = min(
self.max_stroke_width,
self.max_width_to_length_ratio * self.get_length(),
)
widths_array = np.full(self.get_num_points(), width)
nppc = self.n_points_per_curve
if len(widths_array) > nppc:
widths_array[-nppc:] = [
a * self.tip_width_ratio * width
for a in np.linspace(1, 0, nppc)
]
self.set_stroke(width=widths_array)
return self
def reset_tip(self):
self.set_points_by_ends(
self.get_start(),
self.get_end(),
path_arc=self.path_arc,
)
self.create_tip_with_stroke_width()
return self
def set_stroke(self, color=None, width=None, *args, **kwargs):
super().set_stroke(color=color, width=width, *args, **kwargs)
if isinstance(width, numbers.Number):
self.max_stroke_width = width
self.reset_tip()
return self
def _handle_scale_side_effects(self, scale_factor):
return self.reset_tip()
class FillArrow(Line):
CONFIG = {
"fill_color": GREY_A,
"fill_opacity": 1,
@@ -842,16 +927,8 @@ class Rectangle(Polygon):
class Square(Rectangle):
CONFIG = {
"side_length": 2.0,
}
def __init__(self, side_length=None, **kwargs):
digest_config(self, kwargs)
if side_length is None:
side_length = self.side_length
def __init__(self, side_length=2.0, **kwargs):
self.side_length = side_length
super().__init__(side_length, side_length, **kwargs)

View File

@@ -68,7 +68,7 @@ class Matrix(VMobject):
def __init__(self, matrix, **kwargs):
"""
Matrix can either either include numbres, tex_strings,
Matrix can either include numbers, tex_strings,
or mobjects
"""
VMobject.__init__(self, **kwargs)

View File

@@ -365,14 +365,17 @@ class Mobject(object):
self.center()
return self
def replicate(self, n):
return self.get_group_class()(
*(self.copy() for x in range(n))
)
def get_grid(self, n_rows, n_cols, height=None, **kwargs):
"""
Returns a new mobject containing multiple copies of this one
arranged in a grid
"""
grid = self.get_group_class()(
*(self.copy() for n in range(n_rows * n_cols))
)
grid = self.replicate(n_rows * n_cols)
grid.arrange_in_grid(n_rows, n_cols, **kwargs)
if height is not None:
grid.set_height(height)
@@ -408,8 +411,10 @@ class Mobject(object):
for key in self.data:
copy_mobject.data[key] = self.data[key].copy()
# TODO, are uniforms ever numpy arrays?
copy_mobject.uniforms = dict(self.uniforms)
for key in self.uniforms:
if isinstance(self.uniforms[key], np.ndarray):
copy_mobject.uniforms[key] = self.uniforms[key].copy()
copy_mobject.submobjects = []
copy_mobject.add(*[sm.copy() for sm in self.submobjects])
@@ -561,7 +566,7 @@ class Mobject(object):
)
return self
def scale(self, scale_factor, min_scale_factor=1e-8, **kwargs):
def scale(self, scale_factor, min_scale_factor=1e-8, about_point=None, about_edge=ORIGIN):
"""
Default behavior is to scale about the center of the mobject.
The argument about_edge can be a vector, indicating which side of
@@ -574,11 +579,19 @@ class Mobject(object):
scale_factor = max(scale_factor, min_scale_factor)
self.apply_points_function(
lambda points: scale_factor * points,
about_point=about_point,
about_edge=about_edge,
works_on_bounding_box=True,
**kwargs
)
for mob in self.get_family():
mob._handle_scale_side_effects(scale_factor)
return self
def _handle_scale_side_effects(self, scale_factor):
# In case subclasses, such as DecimalNumber, need to make
# any other changes when the size gets altered
pass
def stretch(self, factor, dim, **kwargs):
def func(points):
points[:, dim] *= factor
@@ -765,6 +778,21 @@ class Mobject(object):
def set_depth(self, depth, stretch=False, **kwargs):
return self.rescale_to_fit(depth, 2, stretch=stretch, **kwargs)
def set_max_width(self, max_width, **kwargs):
if self.get_width() > max_width:
self.set_width(max_width, **kwargs)
return self
def set_max_height(self, max_height, **kwargs):
if self.get_height() > max_height:
self.set_height(max_height, **kwargs)
return self
def set_max_depth(self, max_depth, **kwargs):
if self.get_depth() > max_depth:
self.set_depth(max_depth, **kwargs)
return self
def set_coord(self, value, dim, direction=ORIGIN):
curr = self.get_coord(dim, direction)
shift_vect = np.zeros(self.dim)
@@ -823,7 +851,6 @@ class Mobject(object):
return self
def put_start_and_end_on(self, start, end):
# TODO, this doesn't currently work in 3d
curr_start, curr_end = self.get_start_and_end()
curr_vect = curr_end - curr_start
if np.all(curr_vect == 0):
@@ -835,9 +862,12 @@ class Mobject(object):
)
self.rotate(
angle_of_vector(target_vect) - angle_of_vector(curr_vect),
about_point=curr_start
)
self.shift(start - curr_start)
self.rotate(
np.arctan2(curr_vect[2], get_norm(curr_vect[:2])) - np.arctan2(target_vect[2], get_norm(target_vect[:2])),
axis=np.array([-target_vect[1], target_vect[0], 0]),
)
self.shift(start - self.get_start())
return self
# Color functions
@@ -1063,14 +1093,16 @@ class Mobject(object):
def get_start(self):
self.throw_error_if_no_points()
return np.array(self.get_points()[0])
return self.get_points()[0].copy()
def get_end(self):
self.throw_error_if_no_points()
return np.array(self.get_points()[-1])
return self.get_points()[-1].copy()
def get_start_and_end(self):
return self.get_start(), self.get_end()
self.throw_error_if_no_points()
points = self.get_points()
return (points[0].copy(), points[-1].copy())
def point_from_proportion(self, alpha):
points = self.get_points()
@@ -1436,7 +1468,7 @@ class Mobject(object):
return result
def check_data_alignment(self, array, data_key):
# Makes sure that self.data[key] can be brodcast into
# Makes sure that self.data[key] can be broadcast into
# the given array, meaning its length has to be either 1
# or the length of the array
d_len = len(self.data[data_key])

View File

@@ -23,7 +23,7 @@ def always(method, *args, **kwargs):
def f_always(method, *arg_generators, **kwargs):
"""
More functional version of always, where instead
of taking in args, it takes in functions which ouput
of taking in args, it takes in functions which output
the relevant arguments.
"""
assert_is_mobject_method(method)

View File

@@ -6,7 +6,6 @@ from manimlib.utils.bezier import interpolate
from manimlib.utils.config_ops import digest_config
from manimlib.utils.config_ops import merge_dicts_recursively
from manimlib.utils.simple_functions import fdiv
from manimlib.utils.space_ops import normalize
class NumberLine(Line):
@@ -83,7 +82,7 @@ class NumberLine(Line):
ticks = VGroup()
for x in self.get_tick_range():
size = self.tick_size
if x in self.numbers_with_elongated_ticks:
if np.isclose(self.numbers_with_elongated_ticks, x).any():
size *= self.longer_tick_multiple
ticks.add(self.get_tick(x, size))
self.add(ticks)
@@ -106,11 +105,13 @@ class NumberLine(Line):
return interpolate(self.get_start(), self.get_end(), alpha)
def point_to_number(self, point):
start, end = self.get_start_and_end()
unit_vect = normalize(end - start)
points = self.get_points()
start = points[0]
end = points[-1]
vect = end - start
proportion = fdiv(
np.dot(point - start, unit_vect),
np.dot(end - start, unit_vect),
np.dot(point - start, vect),
np.dot(end - start, vect),
)
return interpolate(self.x_min, self.x_max, proportion)

View File

@@ -36,7 +36,9 @@ class DecimalNumber(VMobject):
# Add non-numerical bits
if self.show_ellipsis:
self.add(self.string_to_mob("..."))
dots = self.string_to_mob("...")
dots.arrange(RIGHT, buff=2 * dots[0].get_width())
self.add(dots)
if self.unit is not None:
self.unit_sign = self.string_to_mob(self.unit, SingleStringTex)
self.add(self.unit_sign)
@@ -128,17 +130,15 @@ class DecimalNumber(VMobject):
def set_value(self, number):
move_to_point = self.get_edge_center(self.edge_to_fix)
old_submobjects = self.submobjects
old_submobjects = list(self.submobjects)
self.set_submobjects_from_number(number)
self.move_to(move_to_point, self.edge_to_fix)
for sm1, sm2 in zip(self.submobjects, old_submobjects):
sm1.match_style(sm2)
return self
def scale(self, scale_factor, **kwargs):
super().scale(scale_factor, **kwargs)
def _handle_scale_side_effects(self, scale_factor):
self.data["font_size"] *= scale_factor
return self
def get_value(self):
return self.number

View File

@@ -47,7 +47,7 @@ class BackgroundRectangle(SurroundingRectangle):
fill_opacity=None,
family=True
):
# Unchangable style, except for fill_opacity
# Unchangeable style, except for fill_opacity
VMobject.set_style_data(
self,
stroke_color=BLACK,

View File

@@ -11,6 +11,7 @@ from manimlib.constants import DEFAULT_STROKE_WIDTH
from manimlib.constants import ORIGIN, UP, DOWN, LEFT, RIGHT
from manimlib.constants import BLACK
from manimlib.constants import WHITE
from manimlib.constants import DEGREES, PI
from manimlib.mobject.geometry import Circle
from manimlib.mobject.geometry import Rectangle
@@ -21,6 +22,7 @@ from manimlib.utils.color import *
from manimlib.utils.config_ops import digest_config
from manimlib.utils.directories import get_mobject_data_dir
from manimlib.utils.images import get_full_vector_image_path
from manimlib.utils.simple_functions import clip
def string_to_numbers(num_string):
@@ -86,10 +88,10 @@ class SVGMobject(VMobject):
elif element.tagName == 'style':
pass # TODO, handle style
elif element.tagName in ['g', 'svg', 'symbol']:
result += it.chain(*[
result += it.chain(*(
self.get_mobjects_from(child)
for child in element.childNodes
])
))
elif element.tagName == 'path':
result.append(self.path_string_to_mobject(
element.getAttribute('d')
@@ -182,8 +184,9 @@ class SVGMobject(VMobject):
corner_radius = rect_element.getAttribute("rx")
# input preprocessing
fill_opacity = 1
if fill_color in ["", "none", "#FFF", "#FFFFFF"] or Color(fill_color) == Color(WHITE):
opacity = 0
fill_opacity = 0
fill_color = BLACK # shdn't be necessary but avoids error msgs
if fill_color in ["#000", "#000000"]:
fill_color = WHITE
@@ -211,7 +214,7 @@ class SVGMobject(VMobject):
stroke_width=stroke_width,
stroke_color=stroke_color,
fill_color=fill_color,
fill_opacity=opacity
fill_opacity=fill_opacity
)
else:
mob = RoundedRectangle(
@@ -319,7 +322,7 @@ class SVGMobject(VMobject):
class VMobjectFromSVGPathstring(VMobject):
CONFIG = {
"long_lines": True,
"long_lines": False,
"should_subdivide_sharp_curves": False,
"should_remove_null_curves": False,
}
@@ -339,6 +342,8 @@ class VMobjectFromSVGPathstring(VMobject):
if os.path.exists(points_filepath) and os.path.exists(tris_filepath):
self.set_points(np.load(points_filepath))
self.triangulation = np.load(tris_filepath)
self.needs_new_triangulation = False
else:
self.relative_point = np.array(ORIGIN)
for command, coord_string in self.get_commands_and_coord_strings():
@@ -354,6 +359,7 @@ class VMobjectFromSVGPathstring(VMobject):
self.stretch(-1, 1, about_point=ORIGIN)
# Save to a file for future use
np.save(points_filepath, self.get_points())
np.save(tris_filepath, self.get_triangulation())
def get_commands_and_coord_strings(self):
all_commands = list(self.get_command_to_function_map().keys())
@@ -367,10 +373,18 @@ class VMobjectFromSVGPathstring(VMobject):
def handle_command(self, command, new_points):
if command.islower():
# Treat it as a relative command
new_points += self.relative_point
if command == "a":
# Only the last `self.dim` columns refer to points
new_points[:, -self.dim:] += self.relative_point
else:
new_points += self.relative_point
func, n_points = self.command_to_function(command)
func(*new_points[:n_points])
command_points = new_points[:n_points]
if command.upper() == "A":
func(*command_points[0][:-self.dim], np.array(command_points[0][-self.dim:]))
else:
func(*command_points)
leftover_points = new_points[n_points:]
# Recursively handle the rest of the points
@@ -379,7 +393,10 @@ class VMobjectFromSVGPathstring(VMobject):
# Treat following points as relative line coordinates
command = "l"
if command.islower():
leftover_points -= self.relative_point
if command == "a":
leftover_points[:, -self.dim:] -= self.relative_point
else:
leftover_points -= self.relative_point
self.relative_point = self.get_last_point()
self.handle_command(command, leftover_points)
else:
@@ -388,20 +405,131 @@ class VMobjectFromSVGPathstring(VMobject):
def string_to_points(self, command, coord_string):
numbers = string_to_numbers(coord_string)
if command.upper() == "A":
# Only the last `self.dim` columns refer to points
# Each "point" returned here has a size of `(5 + self.dim)`
params = np.array(numbers).reshape((-1, 7))
result = np.zeros((params.shape[0], 5 + self.dim))
result[:, :7] = params
return result
if command.upper() in ["H", "V"]:
i = {"H": 0, "V": 1}[command.upper()]
xy = np.zeros((len(numbers), 2))
xy[:, i] = numbers
if command.isupper():
xy[:, 1 - i] = self.relative_point[1 - i]
elif command.upper() == "A":
raise Exception("Not implemented")
else:
xy = np.array(numbers).reshape((len(numbers) // 2, 2))
xy = np.array(numbers).reshape((-1, 2))
result = np.zeros((xy.shape[0], self.dim))
result[:, :2] = xy
return result
def add_elliptical_arc_to(self, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, point):
"""
In fact, this method only suits 2d VMobjects.
"""
def close_to_zero(a, threshold=1e-5):
return abs(a) < threshold
def solve_2d_linear_equation(a, b, c):
"""
Using Crammer's rule to solve the linear equation `[a b]x = c`
where `a`, `b` and `c` are all 2d vectors.
"""
def det(a, b):
return a[0] * b[1] - a[1] * b[0]
d = det(a, b)
if close_to_zero(d):
raise Exception("Cannot handle 0 determinant.")
return [det(c, b) / d, det(a, c) / d]
def get_arc_center_and_angles(x0, y0, rx, ry, phi, large_arc_flag, sweep_flag, x1, y1):
"""
The parameter functions of an ellipse rotated `phi` radians counterclockwise is (on `alpha`):
x = cx + rx * cos(alpha) * cos(phi) + ry * sin(alpha) * sin(phi),
y = cy + rx * cos(alpha) * sin(phi) - ry * sin(alpha) * cos(phi).
Now we have two points sitting on the ellipse: `(x0, y0)`, `(x1, y1)`, corresponding to 4 equations,
and we want to hunt for 4 variables: `cx`, `cy`, `alpha0` and `alpha_1`.
Let `d_alpha = alpha1 - alpha0`, then:
if `sweep_flag = 0` and `large_arc_flag = 1`, then `PI <= d_alpha < 2 * PI`;
if `sweep_flag = 0` and `large_arc_flag = 0`, then `0 < d_alpha <= PI`;
if `sweep_flag = 1` and `large_arc_flag = 0`, then `-PI <= d_alpha < 0`;
if `sweep_flag = 1` and `large_arc_flag = 1`, then `-2 * PI < d_alpha <= -PI`.
"""
xd = x1 - x0
yd = y1 - y0
if close_to_zero(xd) and close_to_zero(yd):
raise Exception("Cannot find arc center since the start point and the end point meet.")
# Find `p = cos(alpha1) - cos(alpha0)`, `q = sin(alpha1) - sin(alpha0)`
eq0 = [rx * np.cos(phi), ry * np.sin(phi), xd]
eq1 = [rx * np.sin(phi), -ry * np.cos(phi), yd]
p, q = solve_2d_linear_equation(*zip(eq0, eq1))
# Find `s = (alpha1 - alpha0) / 2`, `t = (alpha1 + alpha0) / 2`
# If `sin(s) = 0`, this requires `p = q = 0`,
# implying `xd = yd = 0`, which is impossible.
sin_s = (p ** 2 + q ** 2) ** 0.5 / 2
if sweep_flag:
sin_s = -sin_s
sin_s = clip(sin_s, -1, 1)
s = np.arcsin(sin_s)
if large_arc_flag:
if not sweep_flag:
s = PI - s
else:
s = -PI - s
sin_t = -p / (2 * sin_s)
cos_t = q / (2 * sin_s)
cos_t = clip(cos_t, -1, 1)
t = np.arccos(cos_t)
if sin_t <= 0:
t = -t
# We can make sure `0 < abs(s) < PI`, `-PI <= t < PI`.
alpha0 = t - s
alpha_1 = t + s
cx = x0 - rx * np.cos(alpha0) * np.cos(phi) - ry * np.sin(alpha0) * np.sin(phi)
cy = y0 - rx * np.cos(alpha0) * np.sin(phi) + ry * np.sin(alpha0) * np.cos(phi)
return cx, cy, alpha0, alpha_1
def get_point_on_ellipse(cx, cy, rx, ry, phi, angle):
return np.array([
cx + rx * np.cos(angle) * np.cos(phi) + ry * np.sin(angle) * np.sin(phi),
cy + rx * np.cos(angle) * np.sin(phi) - ry * np.sin(angle) * np.cos(phi),
0
])
def convert_elliptical_arc_to_quadratic_bezier_curve(
cx, cy, rx, ry, phi, start_angle, end_angle, n_components=8
):
theta = (end_angle - start_angle) / n_components / 2
handles = np.array([
get_point_on_ellipse(cx, cy, rx / np.cos(theta), ry / np.cos(theta), phi, a)
for a in np.linspace(
start_angle + theta,
end_angle - theta,
n_components,
)
])
anchors = np.array([
get_point_on_ellipse(cx, cy, rx, ry, phi, a)
for a in np.linspace(
start_angle + theta * 2,
end_angle,
n_components,
)
])
return handles, anchors
phi = x_axis_rotation * DEGREES
x0, y0 = self.get_last_point()[:2]
cx, cy, start_angle, end_angle = get_arc_center_and_angles(
x0, y0, rx, ry, phi, large_arc_flag, sweep_flag, point[0], point[1]
)
handles, anchors = convert_elliptical_arc_to_quadratic_bezier_curve(
cx, cy, rx, ry, phi, start_angle, end_angle
)
for handle, anchor in zip(handles, anchors):
self.add_quadratic_bezier_curve_to(handle, anchor)
def command_to_function(self, command):
return self.get_command_to_function_map()[command.upper()]
@@ -419,7 +547,7 @@ class VMobjectFromSVGPathstring(VMobject):
"S": (self.add_smooth_cubic_curve_to, 2),
"Q": (self.add_quadratic_bezier_curve_to, 2),
"T": (self.add_smooth_curve_to, 1),
"A": (self.add_quadratic_bezier_curve_to, 2), # TODO
"A": (self.add_elliptical_arc_to, 1),
"Z": (self.close_path, 0),
}

View File

@@ -1,7 +1,6 @@
from functools import reduce
import operator as op
import re
import itertools as it
from manimlib.constants import *
from manimlib.mobject.geometry import Line

View File

@@ -7,6 +7,9 @@ import typing
import warnings
import xml.etree.ElementTree as ET
import functools
import pygments
import pygments.lexers
import pygments.styles
from contextlib import contextmanager
from pathlib import Path
@@ -22,7 +25,8 @@ from manimlib.utils.directories import get_downloads_dir, get_text_dir
from manimpango import PangoUtils, TextSetting, MarkupUtils
TEXT_MOB_SCALE_FACTOR = 0.0076
DEFAULT_LINE_SPACING_SCALE = 0.3
DEFAULT_LINE_SPACING_SCALE = 0.6
class Text(SVGMobject):
CONFIG = {
@@ -47,9 +51,9 @@ class Text(SVGMobject):
"disable_ligatures": True,
}
def __init__(self, text, **config):
self.full2short(config)
digest_config(self, config)
def __init__(self, text, **kwargs):
self.full2short(kwargs)
digest_config(self, kwargs)
if self.size:
warnings.warn(
"self.size has been deprecated and will "
@@ -68,7 +72,7 @@ class Text(SVGMobject):
file_name = self.text2svg()
PangoUtils.remove_last_M(file_name)
self.remove_empty_path(file_name)
SVGMobject.__init__(self, file_name, **config)
SVGMobject.__init__(self, file_name, **kwargs)
self.text = text
if self.disable_ligatures:
self.apply_space_chars()
@@ -145,13 +149,13 @@ class Text(SVGMobject):
def set_color_by_t2c(self, t2c=None):
t2c = t2c if t2c else self.t2c
for word, color in list(t2c.items()):
for word, color in t2c.items():
for start, end in self.find_indexes(word):
self[start:end].set_color(color)
def set_color_by_t2g(self, t2g=None):
t2g = t2g if t2g else self.t2g
for word, gradient in list(t2g.items()):
for word, gradient in t2g.items():
for start, end in self.find_indexes(word):
self[start:end].set_color_by_gradient(*gradient)
@@ -165,49 +169,62 @@ class Text(SVGMobject):
return hasher.hexdigest()[:16]
def text2settings(self):
"""
Substrings specified in t2f, t2s, t2w can occupy each other.
For each category of style, a stack following first-in-last-out is constructed,
and the last value in each stack takes effect.
"""
settings = []
t2x = [self.t2f, self.t2s, self.t2w]
for i in range(len(t2x)):
fsw = [self.font, self.slant, self.weight]
if t2x[i]:
for word, x in list(t2x[i].items()):
for start, end in self.find_indexes(word):
fsw[i] = x
settings.append(TextSetting(start, end, *fsw))
self.line_num = 0
def add_text_settings(start, end, style_stacks):
if start == end:
return
breakdown_indices = [start, *[
i + start + 1 for i, char in enumerate(self.text[start:end]) if char == "\n"
], end]
style = [stack[-1] for stack in style_stacks]
for atom_start, atom_end in zip(breakdown_indices[:-1], breakdown_indices[1:]):
if atom_start < atom_end:
settings.append(TextSetting(atom_start, atom_end, *style, self.line_num))
self.line_num += 1
self.line_num -= 1
# Set All text settings(default font slant weight)
fsw = [self.font, self.slant, self.weight]
settings.sort(key=lambda setting: setting.start)
temp_settings = settings.copy()
start = 0
for setting in settings:
if setting.start != start:
temp_settings.append(TextSetting(start, setting.start, *fsw))
start = setting.end
if start != len(self.text):
temp_settings.append(TextSetting(start, len(self.text), *fsw))
settings = sorted(temp_settings, key=lambda setting: setting.start)
# Set all the default and specified values.
len_text = len(self.text)
t2x_items = sorted([
*[
(0, len_text, t2x_index, value)
for t2x_index, value in enumerate([self.font, self.slant, self.weight])
],
*[
(start, end, t2x_index, value)
for t2x_index, t2x in enumerate([self.t2f, self.t2s, self.t2w])
for word, value in t2x.items()
for start, end in self.find_indexes(word)
]
], key=lambda item: item[0])
if re.search(r'\n', self.text):
line_num = 0
for start, end in self.find_indexes('\n'):
for setting in settings:
if setting.line_num == -1:
setting.line_num = line_num
if start < setting.end:
line_num += 1
new_setting = copy.copy(setting)
setting.end = end
new_setting.start = end
new_setting.line_num = line_num
settings.append(new_setting)
settings.sort(key=lambda setting: setting.start)
break
for setting in settings:
if setting.line_num == -1:
setting.line_num = 0
# Break down ranges and construct settings separately.
active_items = []
style_stacks = [[] for _ in range(3)]
for item, next_start in zip(t2x_items, [*[item[0] for item in t2x_items[1:]], len_text]):
active_items.append(item)
start, end, t2x_index, value = item
style_stacks[t2x_index].append(value)
halting_items = sorted(filter(
lambda item: item[1] <= next_start,
active_items
), key=lambda item: item[1])
atom_start = start
for halting_item in halting_items:
active_items.remove(halting_item)
_, atom_end, t2x_index, _ = halting_item
add_text_settings(atom_start, atom_end, style_stacks)
style_stacks[t2x_index].pop()
atom_start = atom_end
add_text_settings(atom_start, next_start, style_stacks)
del self.line_num
return settings
def text2svg(self):
@@ -257,6 +274,7 @@ class MarkupText(SVGMobject):
"gradient": None,
"disable_ligatures": True,
}
def __init__(self, text, **config):
digest_config(self, config)
self.text = f'<span>{text}</span>'
@@ -303,6 +321,7 @@ class MarkupText(SVGMobject):
# anti-aliasing
if self.height is None:
self.scale(TEXT_MOB_SCALE_FACTOR)
def text2hash(self):
"""Generates ``sha256`` hash for file name."""
settings = (
@@ -349,7 +368,6 @@ class MarkupText(SVGMobject):
**extra_kwargs
)
def _parse_color(self, col):
"""Parse color given in ``<color>`` or ``<gradient>`` tags."""
if re.match("#[0-9a-f]{6}", col):
@@ -472,6 +490,63 @@ class MarkupText(SVGMobject):
def __repr__(self):
return f"MarkupText({repr(self.original_text)})"
class Code(Text):
CONFIG = {
"font": "Consolas",
"font_size": 24,
"lsh": 1.0,
"language": "python",
# Visit https://pygments.org/demo/ to have a preview of more styles.
"code_style": "monokai",
# If not None, then each character will cover a space of equal width.
"char_width": None
}
def __init__(self, code, **kwargs):
self.full2short(kwargs)
digest_config(self, kwargs)
code = code.lstrip("\n") # avoid mismatches of character indices
lexer = pygments.lexers.get_lexer_by_name(self.language)
tokens_generator = pygments.lex(code, lexer)
styles_dict = dict(pygments.styles.get_style_by_name(self.code_style))
default_color_hex = styles_dict[pygments.token.Text]["color"]
if not default_color_hex:
default_color_hex = self.color[1:]
start_index = 0
t2c = {}
t2s = {}
t2w = {}
for pair in tokens_generator:
ttype, token = pair
end_index = start_index + len(token)
range_str = f"[{start_index}:{end_index}]"
style_dict = styles_dict[ttype]
t2c[range_str] = "#" + (style_dict["color"] or default_color_hex)
t2s[range_str] = ITALIC if style_dict["italic"] else NORMAL
t2w[range_str] = BOLD if style_dict["bold"] else NORMAL
start_index = end_index
t2c.update(self.t2c)
t2s.update(self.t2s)
t2w.update(self.t2w)
kwargs["t2c"] = t2c
kwargs["t2s"] = t2s
kwargs["t2w"] = t2w
Text.__init__(self, code, **kwargs)
if self.char_width is not None:
self.set_monospace(self.char_width)
def set_monospace(self, char_width):
current_char_index = 0
for i, char in enumerate(self.text):
if char == "\n":
current_char_index = 0
continue
self[i].set_x(current_char_index * char_width)
current_char_index += 1
self.center()
@contextmanager
def register_font(font_file: typing.Union[str, Path]):
"""Temporarily add a font file to Pango's search path.

View File

@@ -5,9 +5,11 @@ from manimlib.mobject.types.surface import Surface
from manimlib.mobject.types.surface import SGroup
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.geometry import Square
from manimlib.utils.config_ops import digest_config
from manimlib.utils.space_ops import get_norm
from manimlib.utils.space_ops import z_to_vector
from manimlib.utils.space_ops import compass_directions
class SurfaceMesh(VGroup):
@@ -161,15 +163,49 @@ class Cube(SGroup):
"gloss": 0.5,
"square_resolution": (2, 2),
"side_length": 2,
"square_class": Square3D,
}
def init_points(self):
for vect in [OUT, RIGHT, UP, LEFT, DOWN, IN]:
face = Square3D(resolution=self.square_resolution)
face.shift(OUT)
face.apply_matrix(z_to_vector(vect))
self.add(face)
self.set_height(self.side_length)
face = Square3D(
resolution=self.square_resolution,
side_length=self.side_length,
)
self.add(*self.square_to_cube_faces(face))
@staticmethod
def square_to_cube_faces(square):
radius = square.get_height() / 2
square.move_to(radius * OUT)
result = [square]
result.extend([
square.copy().rotate(PI / 2, axis=vect, about_point=ORIGIN)
for vect in compass_directions(4)
])
result.append(square.copy().rotate(PI, RIGHT, about_point=ORIGIN))
return result
def _get_face(self):
return Square3D(resolution=self.square_resolution)
class VCube(VGroup):
CONFIG = {
"fill_color": BLUE_D,
"fill_opacity": 1,
"stroke_width": 0,
"gloss": 0.5,
"shadow": 0.5,
}
def __init__(self, side_length=2, **kwargs):
super().__init__(**kwargs)
face = Square(side_length=side_length)
face.get_triangulation()
self.add(*Cube.square_to_cube_faces(face))
self.init_colors()
self.apply_depth_test()
self.refresh_unit_normal()
class Prism(Cube):

View File

@@ -68,7 +68,9 @@ class DotCloud(PMobject):
return self
def set_radii(self, radii):
self.data["radii"][:] = resize_preserving_order(radii, len(self.data["radii"]))
n_points = len(self.get_points())
radii = np.array(radii).reshape((len(radii), 1))
self.data["radii"] = resize_preserving_order(radii, n_points)
self.refresh_bounding_box()
return self

View File

@@ -22,10 +22,13 @@ class ImageMobject(Mobject):
}
def __init__(self, filename, **kwargs):
path = get_full_raster_image_path(filename)
self.set_image_path(get_full_raster_image_path(filename))
super().__init__(**kwargs)
def set_image_path(self, path):
self.path = path
self.image = Image.open(path)
self.texture_paths = {"Texture": path}
super().__init__(**kwargs)
def init_data(self):
self.data = {

View File

@@ -14,10 +14,17 @@ class PMobject(Mobject):
def resize_points(self, size, resize_func=resize_array):
# TODO
for key in self.data:
if key == "bounding_box":
continue
if len(self.data[key]) != size:
self.data[key] = resize_array(self.data[key], size)
return self
def set_points(self, points):
super().set_points(points)
self.resize_points(len(points))
return self
def add_points(self, points, rgbas=None, color=None, opacity=None):
"""
points must be a Nx3 numpy array, as must rgbas if it is not None
@@ -54,6 +61,8 @@ class PMobject(Mobject):
for mob in self.family_members_with_points():
to_keep = ~np.apply_along_axis(condition, 1, mob.get_points())
for key in mob.data:
if key == "bounding_box":
continue
mob.data[key] = mob.data[key][to_keep]
return self
@@ -85,7 +94,9 @@ class PMobject(Mobject):
lower_index = int(a * pmobject.get_num_points())
upper_index = int(b * pmobject.get_num_points())
for key in self.data:
self.data[key] = pmobject.data[key][lower_index:upper_index]
if key == "bounding_box":
continue
self.data[key] = pmobject.data[key][lower_index:upper_index].copy()
return self

View File

@@ -54,7 +54,6 @@ class VMobject(Mobject):
"joint_type": "auto",
"flat_stroke": False,
"render_primitive": moderngl.TRIANGLES,
"triangulation_locked": False,
"fill_dtype": [
('point', np.float32, (3,)),
('unit_normal', np.float32, (3,)),
@@ -128,7 +127,7 @@ class VMobject(Mobject):
if isinstance(width, np.ndarray):
arr = width.reshape((len(width), 1))
else:
arr = np.array([[w] for w in listify(width)])
arr = np.array([[w] for w in listify(width)], dtype=float)
mob.data['stroke_width'] = arr
if background is not None:
@@ -150,6 +149,7 @@ class VMobject(Mobject):
stroke_opacity=None,
stroke_rgba=None,
stroke_width=None,
stroke_background=True,
gloss=None,
shadow=None,
recurse=True):
@@ -164,13 +164,17 @@ class VMobject(Mobject):
if stroke_rgba is not None:
self.data['stroke_rgba'] = resize_with_interpolation(stroke_rgba, len(fill_rgba))
self.set_stroke(width=stroke_width)
self.set_stroke(
width=stroke_width,
background=stroke_background,
)
else:
self.set_stroke(
color=stroke_color,
width=stroke_width,
opacity=stroke_opacity,
recurse=recurse,
background=stroke_background,
)
if gloss is not None:
@@ -184,6 +188,7 @@ class VMobject(Mobject):
"fill_rgba": self.data['fill_rgba'],
"stroke_rgba": self.data['stroke_rgba'],
"stroke_width": self.data['stroke_width'],
"stroke_background": self.draw_stroke_behind_fill,
"gloss": self.get_gloss(),
"shadow": self.get_shadow(),
}
@@ -424,7 +429,10 @@ class VMobject(Mobject):
def set_points_smoothly(self, points, true_smooth=False):
self.set_points_as_corners(points)
self.make_smooth()
if true_smooth:
self.make_smooth()
else:
self.make_approximately_smooth()
return self
def change_anchor_mode(self, mode):

View File

@@ -1,6 +1,7 @@
import numpy as np
from manimlib.mobject.mobject import Mobject
from manimlib.utils.iterables import listify
class ValueTracker(Mobject):
@@ -15,18 +16,25 @@ class ValueTracker(Mobject):
}
def __init__(self, value=0, **kwargs):
self.value = value
super().__init__(**kwargs)
self.set_value(value)
def init_data(self):
super().init_data()
self.data["value"] = np.zeros((1, 1), dtype=self.value_type)
self.data["value"] = np.array(
listify(self.value),
ndmin=2,
dtype=self.value_type,
)
def get_value(self):
return self.data["value"][0, 0]
result = self.data["value"][0, :]
if len(result) == 1:
return result[0]
return result
def set_value(self, value):
self.data["value"][0, 0] = value
self.data["value"][0, :] = value
return self
def increment_value(self, d_value):

View File

@@ -333,7 +333,7 @@ class DiscreteGraphScene(Scene):
x_coord_of = {root: 0}
y_coord_of = {root: bottom}
# width to allocate to a given node, computed as
# the maxium number of decendents in a single generation,
# the maximum number of decendents in a single generation,
# minus 1, multiplied by x_sep
width_of = {}
for index in indices:

View File

@@ -5,7 +5,7 @@ from manimlib.constants import *
from manimlib.mobject.mobject import Mobject
from manimlib.utils.iterables import adjacent_pairs
# Warning: This is all now pretty depricated, and should not be expected to work
# Warning: This is all now pretty deprecated, and should not be expected to work
class Region(Mobject):

View File

@@ -2,7 +2,6 @@ import inspect
import random
import platform
import itertools as it
import logging
from functools import wraps
from tqdm import tqdm as ProgressDisplay
@@ -11,16 +10,17 @@ import time
from manimlib.animation.animation import prepare_animation
from manimlib.animation.transform import MoveToTarget
from manimlib.mobject.mobject import Point
from manimlib.camera.camera import Camera
from manimlib.constants import DEFAULT_WAIT_TIME
from manimlib.mobject.mobject import Mobject
from manimlib.mobject.mobject import Point
from manimlib.scene.scene_file_writer import SceneFileWriter
from manimlib.utils.config_ops import digest_config
from manimlib.utils.family_ops import extract_mobject_family_members
from manimlib.utils.family_ops import restructure_list_to_exclude_certain_family_members
from manimlib.event_handler.event_type import EventType
from manimlib.event_handler import EVENT_DISPATCHER
from manimlib.logger import log
class Scene(object):
@@ -45,6 +45,7 @@ class Scene(object):
from manimlib.window import Window
self.window = Window(scene=self, **self.window_config)
self.camera_config["ctx"] = self.window.ctx
self.camera_config["frame_rate"] = 30 # Where's that 30 from?
else:
self.window = None
@@ -100,10 +101,12 @@ class Scene(object):
# If there is a window, enter a loop
# which updates the frame while under
# the hood calling the pyglet event loop
log.info("Tips: You are now in the interactive mode. Now you can use the keyboard"
" and the mouse to interact with the scene. Just press `q` if you want to quit.")
self.quit_interaction = False
self.lock_static_mobject_data()
while not (self.window.is_closing or self.quit_interaction):
self.update_frame()
self.update_frame(1 / self.camera.frame_rate)
if self.window.is_closing:
self.window.destroy()
if self.quit_interaction:
@@ -118,16 +121,21 @@ class Scene(object):
self.linger_after_completion = False
self.update_frame()
# Save scene state at the point of embedding
self.save_state()
from IPython.terminal.embed import InteractiveShellEmbed
shell = InteractiveShellEmbed()
# Have the frame update after each command
shell.events.register('post_run_cell', lambda *a, **kw: self.update_frame())
# Use the locals of the caller as the local namespace
# once embeded, and add a few custom shortcuts
# once embedded, and add a few custom shortcuts
local_ns = inspect.currentframe().f_back.f_locals
local_ns["touch"] = self.interact
for term in ("play", "wait", "add", "remove", "clear", "save_state", "restore"):
local_ns[term] = getattr(self, term)
log.info("Tips: Now the embed iPython terminal is open. But you can't interact with"
" the window directly. To do so, you need to type `touch()` or `self.interact()`")
shell(local_ns=local_ns, stack_depth=2)
# End scene when exiting an embed.
raise EndSceneEarlyException()
@@ -247,6 +255,18 @@ class Scene(object):
def get_mobject_copies(self):
return [m.copy() for m in self.mobjects]
def point_to_mobject(self, point, search_set=None, buff=0):
"""
E.g. if clicking on the scene, this returns the top layer mobject
under a given point
"""
if search_set is None:
search_set = self.mobjects
for mobject in reversed(search_set):
if mobject.is_point_touching(point, buff=buff):
return mobject
return None
# Related to skipping
def update_skipping_status(self):
if self.start_at_animation_number is not None:
@@ -443,10 +463,7 @@ class Scene(object):
@handle_play_like_call
def play(self, *args, **kwargs):
if len(args) == 0:
logging.log(
logging.WARNING,
"Called Scene.play with no animations"
)
log.warning("Called Scene.play with no animations")
return
animations = self.anims_from_play_args(*args, **kwargs)
self.lock_static_mobject_data(*animations)
@@ -586,7 +603,7 @@ class Scene(object):
try:
char = chr(symbol)
except OverflowError:
print(" Warning: The value of the pressed key is too large.")
log.warning("The value of the pressed key is too large.")
return
event_data = {"symbol": symbol, "modifiers": modifiers}

View File

@@ -12,6 +12,7 @@ from manimlib.utils.file_ops import guarantee_existence
from manimlib.utils.file_ops import add_extension_if_not_present
from manimlib.utils.file_ops import get_sorted_integer_files
from manimlib.utils.sounds import get_full_sound_file_path
from manimlib.logger import log
class SceneFileWriter(object):
@@ -183,7 +184,7 @@ class SceneFileWriter(object):
'-s', f'{width}x{height}', # size of one frame
'-pix_fmt', 'rgba',
'-r', str(fps), # frames per second
'-i', '-', # The imput comes from a pipe
'-i', '-', # The input comes from a pipe
'-vf', 'vflip',
'-an', # Tells FFMPEG not to expect any audio
'-loglevel', 'error',
@@ -231,7 +232,7 @@ class SceneFileWriter(object):
**kwargs
)
if len(partial_movie_files) == 0:
print("No animations in this scene")
log.warning("No animations in this scene")
return
# Write a file partial_file_list.txt containing all
@@ -300,7 +301,7 @@ class SceneFileWriter(object):
self.print_file_ready_message(file_path)
def print_file_ready_message(self, file_path):
print(f"\nFile ready at {file_path}\n")
log.info(f"File ready at {file_path}")
def should_open_file(self):
return any([

View File

@@ -1,43 +0,0 @@
///// INSERT COLOR_MAP FUNCTION HERE /////
vec4 add_light(vec4 color,
vec3 point,
vec3 unit_normal,
vec3 light_coords,
float gloss,
float shadow){
///// INSERT COLOR FUNCTION HERE /////
// The line above may be replaced by arbitrary code snippets, as per
// the method Mobject.set_color_by_code
if(gloss == 0.0 && shadow == 0.0) return color;
// TODO, do we actually want this? It effectively treats surfaces as two-sided
if(unit_normal.z < 0){
unit_normal *= -1;
}
// TODO, read this in as a uniform?
float camera_distance = 6;
// Assume everything has already been rotated such that camera is in the z-direction
vec3 to_camera = vec3(0, 0, camera_distance) - point;
vec3 to_light = light_coords - point;
vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal);
float dot_prod = dot(normalize(light_reflection), normalize(to_camera));
float shine = gloss * exp(-3 * pow(1 - dot_prod, 2));
float dp2 = dot(normalize(to_light), unit_normal);
float darkening = mix(1, max(dp2, 0), shadow);
return vec4(
darkening * mix(color.rgb, vec3(1.0), shine),
color.a
);
}
vec4 finalize_color(vec4 color,
vec3 point,
vec3 unit_normal,
vec3 light_coords,
float gloss,
float shadow){
// Put insertion here instead
return add_light(color, point, unit_normal, light_coords, gloss, shadow);
}

View File

@@ -0,0 +1,15 @@
vec2 complex_mult(vec2 z, vec2 w){
return vec2(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x);
}
vec2 complex_div(vec2 z, vec2 w){
return complex_mult(z, vec2(w.x, -w.y)) / (w.x * w.x + w.y * w.y);
}
vec2 complex_pow(vec2 z, int n){
vec2 result = vec2(1.0, 0.0);
for(int i = 0; i < n; i++){
result = complex_mult(result, z);
}
return result;
}

View File

@@ -0,0 +1,77 @@
#version 330
uniform vec3 light_source_position;
uniform float gloss;
uniform float shadow;
uniform float focal_distance;
uniform vec2 parameter;
uniform float opacity;
uniform float n_steps;
uniform float mandelbrot;
uniform vec3 color0;
uniform vec3 color1;
uniform vec3 color2;
uniform vec3 color3;
uniform vec3 color4;
uniform vec3 color5;
uniform vec3 color6;
uniform vec3 color7;
uniform vec3 color8;
uniform vec2 frame_shape;
in vec3 xyz_coords;
out vec4 frag_color;
#INSERT finalize_color.glsl
#INSERT complex_functions.glsl
const int MAX_DEGREE = 5;
void main() {
vec3 color_map[9] = vec3[9](
color0, color1, color2, color3,
color4, color5, color6, color7, color8
);
vec3 color;
vec2 z;
vec2 c;
if(bool(mandelbrot)){
c = xyz_coords.xy;
z = vec2(0.0, 0.0);
}else{
c = parameter;
z = xyz_coords.xy;
}
float outer_bound = 2.0;
bool stable = true;
for(int n = 0; n < int(n_steps); n++){
z = complex_mult(z, z) + c;
if(length(z) > outer_bound){
float float_n = float(n);
float_n += log(outer_bound) / log(length(z));
float_n += 0.5 * length(c);
color = float_to_color(sqrt(float_n), 1.5, 8.0, color_map);
stable = false;
break;
}
}
if(stable){
color = vec3(0.0, 0.0, 0.0);
}
frag_color = finalize_color(
vec4(color, opacity),
xyz_coords,
vec3(0.0, 0.0, 1.0),
light_source_position,
gloss,
shadow
);
}

View File

@@ -0,0 +1,17 @@
#version 330
#INSERT camera_uniform_declarations.glsl
in vec3 point;
out vec3 xyz_coords;
uniform float scale_factor;
uniform vec3 offset;
#INSERT position_point_into_frame.glsl
#INSERT get_gl_Position.glsl
void main(){
xyz_coords = (point - offset) / scale_factor;
gl_Position = get_gl_Position(position_point_into_frame(point));
}

View File

@@ -0,0 +1,157 @@
#version 330
uniform vec3 light_source_position;
uniform float gloss;
uniform float shadow;
uniform float focal_distance;
uniform vec4 color0;
uniform vec4 color1;
uniform vec4 color2;
uniform vec4 color3;
uniform vec4 color4;
uniform vec2 coef0;
uniform vec2 coef1;
uniform vec2 coef2;
uniform vec2 coef3;
uniform vec2 coef4;
uniform vec2 coef5;
uniform vec2 root0;
uniform vec2 root1;
uniform vec2 root2;
uniform vec2 root3;
uniform vec2 root4;
uniform float n_roots;
uniform float n_steps;
uniform float julia_highlight;
uniform float saturation_factor;
uniform float black_for_cycles;
uniform float is_parameter_space;
uniform vec2 frame_shape;
in vec3 xyz_coords;
out vec4 frag_color;
#INSERT finalize_color.glsl
#INSERT complex_functions.glsl
const int MAX_DEGREE = 5;
const float CLOSE_ENOUGH = 1e-3;
vec2 poly(vec2 z, vec2[MAX_DEGREE + 1] coefs){
vec2 result = vec2(0.0);
for(int n = 0; n < int(n_roots) + 1; n++){
result += complex_mult(coefs[n], complex_pow(z, n));
}
return result;
}
vec2 dpoly(vec2 z, vec2[MAX_DEGREE + 1] coefs){
vec2 result = vec2(0.0);
for(int n = 1; n < int(n_roots) + 1; n++){
result += n * complex_mult(coefs[n], complex_pow(z, n - 1));
}
return result;
}
vec2 seek_root(vec2 z, vec2[MAX_DEGREE + 1] coefs, int max_steps, out float n_iters){
float last_len;
float curr_len;
float threshold = CLOSE_ENOUGH;
for(int i = 0; i < max_steps; i++){
last_len = curr_len;
n_iters = float(i);
vec2 step = complex_div(poly(z, coefs), dpoly(z, coefs));
curr_len = length(step);
if(curr_len < threshold){
break;
}
z = z - step;
}
n_iters -= clamp((threshold - curr_len) / (last_len - curr_len), 0.0, 1.0);
return z;
}
void main() {
vec2[MAX_DEGREE + 1] coefs = vec2[MAX_DEGREE + 1](coef0, coef1, coef2, coef3, coef4, coef5);
vec2[MAX_DEGREE] roots = vec2[MAX_DEGREE](root0, root1, root2, root3, root4);
vec4[MAX_DEGREE] colors = vec4[MAX_DEGREE](color0, color1, color2, color3, color4);
vec2 z = xyz_coords.xy;
if(is_parameter_space > 0){
// In this case, pixel should correspond to one of the roots
roots[2] = xyz_coords.xy;
vec2 r0 = roots[0];
vec2 r1 = roots[1];
vec2 r2 = roots[2];
// It is assumed that the polynomial is cubid...
coefs[0] = -complex_mult(complex_mult(r0, r1), r2);
coefs[1] = complex_mult(r0, r1) + complex_mult(r0, r2) + complex_mult(r1, r2);
coefs[2] = -(r0 + r1 + r2);
coefs[3] = vec2(1.0, 0.0);
// Seed value is always center of the roots
z = -coefs[2] / 3.0;
}
float n_iters;
vec2 found_root = seek_root(z, coefs, int(n_steps), n_iters);
vec4 color = vec4(0.0);
float min_dist = 1e10;
float dist;
for(int i = 0; i < int(n_roots); i++){
dist = distance(roots[i], found_root);
if(dist < min_dist){
min_dist = dist;
color = colors[i];
}
}
color *= 1.0 + (0.01 * saturation_factor) * (n_iters - 5 * saturation_factor);
if(black_for_cycles > 0 && min_dist > CLOSE_ENOUGH){
color = vec4(0.0, 0.0, 0.0, 1.0);
}
if(julia_highlight > 0.0){
float radius = julia_highlight;
vec2[4] samples = vec2[4](
z + vec2(radius, 0.0),
z + vec2(-radius, 0.0),
z + vec2(0.0, radius),
z + vec2(0.0, -radius)
);
for(int i = 0; i < 4; i++){
for(int j = 0; j < n_steps; j++){
vec2 z = samples[i];
z = z - complex_div(poly(z, coefs), dpoly(z, coefs));
samples[i] = z;
}
}
float max_dist = 0.0;
for(int i = 0; i < 4; i++){
max_dist = max(max_dist, distance(samples[i], samples[(i + 1) % 4]));
}
color *= 1.0 * smoothstep(0, 0.1, max_dist);
}
frag_color = finalize_color(
color,
xyz_coords,
vec3(0.0, 0.0, 1.0),
light_source_position,
gloss,
shadow
);
}

View File

@@ -0,0 +1,17 @@
#version 330
#INSERT camera_uniform_declarations.glsl
in vec3 point;
out vec3 xyz_coords;
uniform float scale_factor;
uniform vec3 offset;
#INSERT position_point_into_frame.glsl
#INSERT get_gl_Position.glsl
void main(){
xyz_coords = (point - offset) / scale_factor;
gl_Position = get_gl_Position(position_point_into_frame(point));
}

View File

@@ -4,6 +4,8 @@ import numpy as np
from manimlib.utils.simple_functions import choose
from manimlib.utils.space_ops import find_intersection
from manimlib.utils.space_ops import cross2d
from manimlib.utils.space_ops import midpoint
from manimlib.logger import log
CLOSED_THRESHOLD = 0.001
@@ -67,9 +69,9 @@ def interpolate(start, end, alpha):
try:
return (1 - alpha) * start + alpha * end
except TypeError:
print(type(start), start.dtype)
print(type(end), start.dtype)
print(alpha)
log.debug(f"`start` parameter with type `{type(start)}` and dtype `{start.dtype}`")
log.debug(f"`end` parameter with type `{type(end)}` and dtype `{end.dtype}`")
log.debug(f"`alpha` parameter with value `{alpha}`")
import sys
sys.exit(2)
@@ -130,6 +132,8 @@ def get_smooth_quadratic_bezier_handle_points(points):
another that would produce a parabola passing through P0, call it smooth_to_left,
and use the midpoint between the two.
"""
if len(points) == 2:
return midpoint(*points)
smooth_to_right, smooth_to_left = [
0.25 * ps[0:-2] + ps[1:-1] - 0.25 * ps[2:]
for ps in (points, points[::-1])
@@ -157,7 +161,7 @@ def get_smooth_cubic_bezier_handle_points(points):
l, u = 2, 1
# diag is a representation of the matrix in diagonal form
# See https://www.particleincell.com/2012/bezier-splines/
# for how to arive at these equations
# for how to arrive at these equations
diag = np.zeros((l + u + 1, 2 * num_handles))
diag[0, 1::2] = -1
diag[0, 2::2] = 1

View File

@@ -3,11 +3,12 @@ import time
from manimlib.constants import BLACK
from manimlib.mobject.numbers import Integer
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.logger import log
def print_family(mobject, n_tabs=0):
"""For debugging purposes"""
print("\t" * n_tabs, mobject, id(mobject))
log.debug("\t" * n_tabs + str(mobject) + " " + str(id(mobject)))
for submob in mobject.submobjects:
print_family(submob, n_tabs + 1)

View File

@@ -46,6 +46,7 @@ def init_customization():
}
}
print("Initialize configuration")
scope = input(" Please select the scope of the configuration [global/local]: ")
if scope == "global":
from manimlib.config import get_manim_dir

View File

@@ -5,7 +5,7 @@ import numpy as np
def remove_list_redundancies(l):
"""
Used instead of list(set(l)) to maintain order
Keeps the last occurance of each element
Keeps the last occurrence of each element
"""
reversed_result = []
used = set()

View File

@@ -1,5 +1,6 @@
import numpy as np
import itertools as it
import operator as op
from functools import reduce
import math
from mapbox_earcut import triangulate_float32 as earcut
@@ -12,7 +13,7 @@ from manimlib.utils.iterables import adjacent_pairs
def get_norm(vect):
return sum([x**2 for x in vect])**0.5
return sum((x**2 for x in vect))**0.5
# Quaternions
@@ -376,8 +377,8 @@ def earclip_triangulation(verts, ring_ends):
# Points at the same position may cause problems
for i in rings:
verts[i[0]] += (verts[i[1]]-verts[i[0]]) * 1e-6
verts[i[-1]] += (verts[i[-2]]-verts[i[-1]]) * 1e-6
verts[i[0]] += (verts[i[1]] - verts[i[0]]) * 1e-6
verts[i[-1]] += (verts[i[-2]] - verts[i[-1]]) * 1e-6
# First, we should know which rings are directly contained in it for each ring
@@ -393,9 +394,11 @@ def earclip_triangulation(verts, ring_ends):
def is_in_fast(ring_a, ring_b):
# Whether a is in b
return (left[ring_b] <= left[ring_a] <= right[ring_a] <= right[ring_b] and
bottom[ring_b] <= bottom[ring_a] <= top[ring_a] <= top[ring_b] and
is_in(verts[rings[ring_a][0]], ring_b))
return reduce(op.and_, (
left[ring_b] <= left[ring_a] <= right[ring_a] <= right[ring_b],
bottom[ring_b] <= bottom[ring_a] <= top[ring_a] <= top[ring_b],
is_in(verts[rings[ring_a][0]], ring_b)
))
chilren = [[] for i in rings]
for idx, i in enumerate(rings_sorted):

View File

@@ -1,4 +1,3 @@
import logging
import sys
import os
import hashlib
@@ -7,6 +6,7 @@ from contextlib import contextmanager
from manimlib.utils.directories import get_tex_dir
from manimlib.config import get_manim_dir
from manimlib.config import get_custom_config
from manimlib.logger import log
SAVED_TEX_CONFIG = {}
@@ -87,15 +87,11 @@ def tex_to_dvi(tex_file):
exit_code = os.system(" ".join(commands))
if exit_code != 0:
log_file = tex_file.replace(".tex", ".log")
logging.log(
logging.ERROR,
"\n\n LaTeX Error! Not a worry, it happens to the best of us.\n"
)
log.error("LaTeX Error! Not a worry, it happens to the best of us.")
with open(log_file, "r") as file:
for line in file.readlines():
if line.startswith("!"):
print(line[1:])
logging.log(logging.INFO, line)
log.debug(f"The error could be: `{line[2:-1]}`")
sys.exit(2)
return result

View File

@@ -1,3 +1,4 @@
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
@@ -48,7 +49,7 @@ class Window(PygletWindow):
return tuple(map(int, custom_position.split(",")))
# Alternatively, it might be specified with a string like
# UR, OO, DL, etc. specifiying what corner it should go to
# 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
@@ -59,7 +60,17 @@ class Window(PygletWindow):
# Delegate event handling to scene
def pixel_coords_to_space_coords(self, px, py, relative=False):
return self.scene.camera.pixel_coords_to_space_coords(px, py, relative)
pw, ph = self.size
fw, fh = self.scene.camera.get_frame_shape()
fc = self.scene.camera.get_frame_center()
if relative:
return np.array([px / pw, py / ph, 0])
else:
return np.array([
fc[0] + px * fw / pw - fw / 2,
fc[1] + py * fh / ph - fh / 2,
0
])
def on_mouse_motion(self, x, y, dx, dy):
super().on_mouse_motion(x, y, dx, dy)

View File

@@ -1,3 +1,2 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
requires = ["setuptools", "wheel"]

View File

@@ -10,7 +10,9 @@ matplotlib
moderngl
moderngl_window
pydub
pygments
pyyaml
rich
screeninfo
pyreadline; sys_platform == 'win32'
validators

View File

@@ -1,6 +1,6 @@
[metadata]
name = manimgl
version = 1.1.0
version = 1.2.0
author = Grant Sanderson
author_email= grant@3blue1brown.com
description = Animation engine for explanatory math videos
@@ -29,7 +29,9 @@ install_requires =
moderngl
moderngl_window
pydub
pygments
pyyaml
rich
screeninfo
pyreadline; sys_platform == 'win32'
validators

2
setup.py Normal file
View File

@@ -0,0 +1,2 @@
import setuptools
setuptools.setup()