179 Commits

Author SHA1 Message Date
TonyCrane
9df58e4ddf change version 2021-07-28 23:18:24 +08:00
TonyCrane
f09092024f Merge branch 'master' of https://github.com/3b1b/manim 2021-07-28 23:15:03 +08:00
TonyCrane
4d65c97965 allow sound_file_name to be taken in without extensions 2021-07-28 23:13:15 +08:00
TonyCrane
76966064ce update docs 2021-07-28 23:12:45 +08:00
Grant Sanderson
152d03ed27 Merge pull request #1587 from 3b1b/some1-video-changes
Some1 video changes
2021-07-28 07:54:27 -07:00
Grant Sanderson
8624168ed9 Merge branch 'master' into some1-video-changes 2021-07-28 07:53:04 -07:00
Grant Sanderson
354db4423f Merge pull request #1578 from nutanstrek/patch-1
Minor fix for zooming
2021-07-28 07:50:42 -07:00
Grant Sanderson
27344249de Merge pull request #1566 from pdcxs/patch-2
Add frame to the scene when initialization
2021-07-28 07:48:02 -07:00
Grant Sanderson
0b3a1b271c Merge pull request #1565 from 3b1b/fix-package-versioning
Transition build method to PEP 517
2021-07-28 07:47:18 -07:00
Grant Sanderson
fd8904ec83 Merge pull request #1557 from naveen521kk/add-markuptext
Add MarkupText
2021-07-28 07:45:50 -07:00
Grant Sanderson
7da6179493 Merge pull request #1586 from Wallbreaker5th/master
Fix triangulation
2021-07-28 07:43:42 -07:00
Grant Sanderson
17452dcd10 Changing plane defaults 2021-07-28 07:32:45 -07:00
Grant Sanderson
2f5acc6a87 Small refactor 2021-07-28 07:32:16 -07:00
Grant Sanderson
71f018dfff Add TrueDot 2021-07-28 07:31:31 -07:00
Grant Sanderson
b3ae517a05 Take in u_range and v_range as arguments to ParametricSurface 2021-07-28 07:31:10 -07:00
Grant Sanderson
f7bb5c1b8c If there is multisampling, don't have an antialias width 2021-07-28 07:30:13 -07:00
Grant Sanderson
a3227dda67 Small formatting fix 2021-07-28 07:29:43 -07:00
Wallbreaker5th
2ed78c6e0f Rewrite earclip_triangulation 2021-07-28 17:06:43 +08:00
TonyCrane
8aa004b0b1 use jsdelivr cdn for assets in docs 2021-07-18 22:22:52 +08:00
鹤翔万里
45938dd76f Merge pull request #944 from Lalourche/fix-counting
Fixed execution of counting.py
2021-07-17 22:19:23 -05:00
Darylgolden
3fe4d6d2d4 Add clarification on versions (#1580)
* Clarification on versions

* Grammar

* add clarification on package names

Co-authored-by: 鹤翔万里 <tonycrane@foxmail.com>
2021-07-18 10:58:48 +08:00
鹤翔万里
a18600e8a4 Merge pull request #1579 from Darylgolden/patch-1
Update Discord link
2021-07-17 21:51:33 -05:00
Darylgolden
700418a79c Update Discord link 2021-07-17 13:50:54 +08:00
Paras Sharma
4940ccac7d Minor fix for zooming
It's weird that when you Scroll Up, the interactive shell zooms out.

So to fix this replace factor -> 1/factor .
2021-07-14 13:56:20 +05:30
Grant Sanderson
17d7f0b6f0 Update README.md 2021-07-04 09:22:25 -07:00
Eric Brown
275cf94b06 Add frame to the scene when initialization
If we add an updater to the frame of the camera, and have not added the frame into the scene before, the updater will not work. So, I suggest to add the frame to the objects of the scene at the initilization stage.
2021-06-29 23:03:08 +08:00
TonyCrane
54fff5523b change default font size of ControlsExample 2021-06-29 20:55:25 +08:00
TonyCrane
5707585d17 fix typo 2021-06-26 17:45:21 +08:00
TonyCrane
0305582e64 update GitHub workflow 2021-06-26 12:37:57 +08:00
TonyCrane
a4c3bb03d1 update packaging method 2021-06-26 12:23:14 +08:00
TonyCrane
b00d718431 Merge branch 'master' of https://github.com/3b1b/manim 2021-06-21 13:38:41 +08:00
TonyCrane
6da5d4c8f6 update manim-kindergarten's link 2021-06-21 13:34:10 +08:00
sahilmakhijani
01670cf823 Fix ControlsExample Scene (#1551) 2021-06-20 15:37:06 +08:00
Naveen M K
5986d0e7d2 Add MarkupText
This would use a Pango specific markup which looks like html.

There are some specific implementation here about `<color>`
and `<gradient>`
Pango doesn't support `<gradient>`  or `<color> ` instead it works
with `color` attribute and gradient isn't supported.
Since, `SVGMobject` doesn't know about parsing colors from SVG image
and implmentation of `<color>` and `<gradient>` is added.

Co-authored-by: Philipp Imhof <52650214+PhilippImhof@users.noreply.github.com>
Signed-off-by: Naveen M K <naveen@syrusdark.website>
2021-06-20 01:13:13 +05:30
Grant Sanderson
d384fc1e27 Merge pull request #1552 from Wallbreaker5th/master
Temporary hack for showing text correctly
2021-06-19 09:53:24 -07:00
Grant Sanderson
8aedb8f33e Merge branch 'master' into master 2021-06-19 09:48:53 -07:00
Grant Sanderson
bccc17a3ac Merge pull request #1555 from 3b1b/revert-1543-master
Scale `Text` correctly | Revert "Fixed the default size of Text"
2021-06-19 09:30:40 -07:00
TonyCrane
892df54c9f remove lsh in OpeningManimExample 2021-06-19 20:13:41 +08:00
TonyCrane
663c57ba74 a little fix to TEXT_MOB_SCALE_FACTOR 2021-06-19 19:33:53 +08:00
TonyCrane
26e9b9cd7c added the missing import line in mobject.py 2021-06-19 18:30:23 +08:00
Wallbreaker5th
a99ccea02c Add some comments about the temporary hack 2021-06-19 16:00:39 +08:00
TonyCrane
892ce2db09 update docs 2021-06-19 13:21:25 +08:00
鹤翔万里
d14f22c5ba Revert "Fixed the default size of Text" 2021-06-19 13:11:36 +08:00
Grant Sanderson
846c10a0ff Merge pull request #1543 from TonyCrane/master
Fixed the default size of Text
2021-06-18 12:28:33 -07:00
Grant Sanderson
128178b46e Merge branch 'master' into master 2021-06-18 12:25:00 -07:00
Grant Sanderson
b4f23e8d8e Merge pull request #1553 from naveen521kk/fix-text
Scale Text Correctly
2021-06-18 12:14:32 -07:00
Naveen M K
5765ab9055 Move 0.3 constant to DEFAULT_LINE_SPACING_SCALE 2021-06-19 00:43:01 +05:30
Naveen M K
6eb7edc664 Scale Text Correctly
Change TEXT_MOB_SCALE_FACTOR value
Also deprecate `size` parameter

Also, now text isn't scaled when increasing font size
instead it is passed to the underlying enging
for handling. Though always a Text object is scaled
with a default value so that it fits the screen.

Signed-off-by: Naveen M K <naveen@syrusdark.website>
2021-06-18 17:59:15 +05:30
Wallbreaker5th
e836c3bb42 Temporary hack for showing text correctly 2021-06-18 14:43:09 +08:00
Grant Sanderson
5ff8e28ba5 Change suspend_mobject_updating default 2021-06-16 10:33:06 -07:00
Grant Sanderson
6dc1ecb00a Merge branch 'master' of github.com:3b1b/manim 2021-06-15 21:45:25 -07:00
Grant Sanderson
be78f5257a Ensure images used for textures are RGBA 2021-06-15 21:45:13 -07:00
Grant Sanderson
798479536d Merge pull request #1545 from TonyCrane/fix-example-scenes
Fix example scenes
2021-06-15 11:16:37 -07:00
TonyCrane
226df63d0b update doc videos 2021-06-15 19:54:53 +08:00
TonyCrane
b8fb69773e fix example scenes 2021-06-15 19:20:19 +08:00
TonyCrane
fec2306f9a update docs 2021-06-15 19:16:30 +08:00
TonyCrane
17d75bd336 Fixed the size of Text on different machines 2021-06-15 18:14:06 +08:00
鹤翔万里
23662d093f Merge branch '3b1b:master' into master 2021-06-15 17:48:25 +08:00
Grant Sanderson
34d4689672 Merge pull request #1541 from 3b1b/quick-eigen-video
Quick eigen video
2021-06-14 09:57:57 -07:00
Grant Sanderson
607ef334e9 Fix Lightbulb 2021-06-14 09:56:35 -07:00
Grant Sanderson
b4b4d39ec5 Fixes issues #1436 2021-06-14 09:55:40 -07:00
Grant Sanderson
1c2942798e Merge branch 'master' of github.com:3b1b/manim into quick-eigen-video 2021-06-14 09:54:20 -07:00
Grant Sanderson
e9ea5fbea0 Merge pull request #1481 from libinglong/lbl-fix-tex
fix issue #1480
2021-06-14 09:50:20 -07:00
Grant Sanderson
7ecfc041b3 Merge pull request #1538 from manim-kindergarten/shaders
Added some features including --config_file CLI flag and tip_style
2021-06-14 09:47:50 -07:00
Grant Sanderson
44a9c6337e Merge pull request #1530 from Wallbreaker5th/master
Modified the shaders slightly
2021-06-14 09:44:01 -07:00
Grant Sanderson
d1a5089acc Merge pull request #1529 from calvinpelletier/window_size_bug_fix
fix issue #1509
2021-06-14 09:42:41 -07:00
TonyCrane
9e5f39a4a9 update docs 2021-06-14 16:36:16 +08:00
TonyCrane
6da93cde7b clean .gitignore 2021-06-14 16:25:30 +08:00
TonyCrane
6340db1076 small improvement to config_file 2021-06-14 16:21:38 +08:00
TonyCrane
f45d81be11 some improvement to tip
rename tip_look to tip_style
set the default of tip_style to 0
2021-06-14 16:13:53 +08:00
鹤翔万里
baa2adc128 Merge branch '3b1b:master' into shaders 2021-06-14 13:49:49 +08:00
TonyCrane
f16277f100 some fixes of doc 2021-06-14 13:30:37 +08:00
TonyCrane
876f06cc37 add window_position to config guide 2021-06-14 08:29:38 +08:00
TonyCrane
f682bf97e3 Merge branch 'master' of https://github.com/3b1b/manim 2021-06-14 07:55:24 +08:00
TonyCrane
025639f026 update doc 2021-06-14 07:55:17 +08:00
widcardw
e885ec6ecd Fix the color of tip 2021-06-13 21:07:25 +08:00
GZTime
3b3150c3c5 Merge branch 'shaders' of https://github.com/manim-kindergarten/manim into shaders 2021-06-12 22:05:26 +08:00
GZTime
33aa4e979a Merge remote-tracking branch 'upstream/master' into shaders 2021-06-12 22:04:28 +08:00
鹤翔万里
9de7a6477d Merge branch '3b1b:master' into shaders 2021-06-12 22:00:08 +08:00
鹤翔万里
19b8057136 Merge pull request #1521 from naveen521kk/patch-1
requirements.txt: manimpango v0.3.0
2021-06-12 20:29:52 +08:00
鹤翔万里
b6dd6fe16d Merge pull request #1499 from jamilgafur/patch-1
Update quickstart.rst
2021-06-12 19:28:41 +08:00
鹤翔万里
51b2984ee3 Merge pull request #1488 from libinglong/lbl-fix-document
Fix document. Fix the mismatch between code and video
2021-06-12 18:31:43 +08:00
鹤翔万里
957eedc32c Merge pull request #1477 from giacomo-b/readme-fix
Update README.md
2021-06-12 18:21:08 +08:00
鹤翔万里
2614b34d11 Merge branch 'master' into readme-fix 2021-06-12 18:20:41 +08:00
鹤翔万里
4ea1d6d64f Merge pull request #1475 from CodingPower472/master
Fix typo in README
2021-06-12 18:18:18 +08:00
鹤翔万里
fd67858bb0 Merge pull request #1467 from alessandro-gentilini/patch-1
Typo
2021-06-12 18:06:03 +08:00
Wallbreaker5th
442e7fc14d Use focal_distance as camera_distance in shaders. 2021-06-05 17:43:32 +08:00
Wallbreaker5th
bb27a78449 fix: checking whether normal is facing the camera. 2021-06-05 17:33:54 +08:00
Calvin Pelletier
a06d5613f4 fix issue #1509 2021-06-04 17:07:41 -07:00
Naveen M K
6605ab75e8 requirements.txt: manimpango v0.3.0 is supported
it should work as there are no breaking changes for the API used here
I should bring in MarkupText here...
2021-05-24 22:25:08 +05:30
GZTime
ed9a4bd9eb Merge remote-tracking branch 'upstream/master' into shaders 2021-05-14 16:42:07 +08:00
Grant Sanderson
d54b796154 Change default to non-flat stroke 2021-05-07 16:07:49 -07:00
Grant Sanderson
bb72718c3b Merge branch 'master' of github.com:3b1b/manim 2021-04-28 08:51:09 -07:00
Grant Sanderson
d279272762 Small cleanup for TexMobject, and remove {{ }} separation convention 2021-04-28 08:50:50 -07:00
jamilgafur
f2f652f85d Update quickstart.rst
Removed extra left side curly bracket in embed section
2021-04-25 09:58:04 -05:00
libinglong
bf9d797d84 fix the mismatch between code and video 2021-04-20 15:59:07 +08:00
libinglong
29e5a8bc90 fix issue #1480 2021-04-17 22:30:08 +08:00
giacomo-b
02bad81fc3 Update README.md 2021-04-17 10:37:20 +02:00
CodingPower472
2bfe7e08ff Fix typo in README 2021-04-16 09:21:28 -06:00
widcardw
e727faaccb Add a custom style to ArrowTip. 2021-04-13 17:40:48 +08:00
GZTime
6b911f5721 Merge remote-tracking branch 'upstream/master' into shaders 2021-04-12 00:35:54 +08:00
鹤翔万里
565ff09d58 Update chinese docs' link 2021-04-11 19:24:40 +08:00
鹤翔万里
4b4a973464 Re-run Action 2021-04-11 19:21:43 +08:00
鹤翔万里
8e2799a499 Merge pull request #1469 from naveen521kk/patch-1
CI: install pango so that manimpango install
2021-04-11 19:18:46 +08:00
Naveen M K
a44e230a07 CI: install pango so that manimpango install
https://github.com/ManimCommunity/ManimPango/issues/53
2021-04-11 16:45:03 +05:30
Alessandro Gentilini
f9fb68c011 Typo 2021-04-10 14:18:47 +02:00
GZTime
c1ad893030 Fix a wrong function name. 2021-04-09 22:06:20 +08:00
GZTime
65d0826b91 Update config.py to load config file manually. 2021-04-09 21:32:52 +08:00
GZTime
41120b096e Add support for debugger launch 2021-04-09 20:17:21 +08:00
GZTime
1f6e911d60 Merge remote-tracking branch 'upstream/master' into shaders 2021-04-09 20:09:43 +08:00
Grant Sanderson
c45ff910f0 Remove double brace convention, since it causes errors with a number of tex strings 2021-04-08 14:46:03 -07:00
Grant Sanderson
15760cf253 Merge branch 'master' of github.com:3b1b/manim 2021-04-08 14:32:07 -07:00
Grant Sanderson
f6291d7e82 Merge pull request #1421 from naveen521kk/patch-6
update docs for linux installation
2021-04-08 14:23:28 -07:00
Grant Sanderson
b5e6177afd Merge pull request #1408 from Eisenwave/patch-1
Fix typo in README.md
2021-04-08 14:23:02 -07:00
Grant Sanderson
22d9c57f60 Merge pull request #1419 from JWro/patch-1
Fix init of Elbow super class
2021-04-08 14:22:47 -07:00
Grant Sanderson
3a992e136d Merge pull request #1437 from williamclavier/patch-1
Fix typo in example_scenes.py
2021-04-08 14:22:25 -07:00
Grant Sanderson
12ef0a26d7 Fix chaining animation in example scenes 2021-04-08 14:20:37 -07:00
Grant Sanderson
7a11e3d20f Merge pull request #1464 from 3b1b/matrix-exp-development
Matrix exp development
2021-04-08 14:18:25 -07:00
Grant Sanderson
cf63dfddf9 Fix Lighthouse 2021-04-08 14:14:32 -07:00
Grant Sanderson
42d8888f8e Allow any VMobject to be passed into TransformMatchingTex, so that slicing into Tex works 2021-03-31 23:28:49 -07:00
Grant Sanderson
322f138490 Add CameraFrame.reorient for quicker changes to frame angle 2021-03-31 23:27:12 -07:00
Grant Sanderson
df657c06c2 Add (admitedly silly) RADIANS constant 2021-03-31 23:26:35 -07:00
Grant Sanderson
0c61c908b2 Small fixes to Axes defaults 2021-03-31 23:26:10 -07:00
Grant Sanderson
82658e1db3 Change default element alignment for integer matrices 2021-03-31 23:25:43 -07:00
Grant Sanderson
de9ecbd766 Remove unnecessary import 2021-03-31 23:24:55 -07:00
Grant Sanderson
ca9f4357fa Allow configuration in Brace.get_text 2021-03-31 23:23:34 -07:00
Grant Sanderson
e95aa69c4c Change arg_separator default 2021-03-31 23:22:54 -07:00
Grant Sanderson
6997cc9501 Have Mobject.match_points return self 2021-03-27 11:57:50 -07:00
Grant Sanderson
7f47815230 Change some defaults and add Matrix.get_rows method 2021-03-27 11:56:58 -07:00
Grant Sanderson
d3e61b962b Have DecimalNumber match full family style when setting a new value 2021-03-27 11:56:36 -07:00
Grant Sanderson
8999ebb556 Also look for jpegs 2021-03-27 11:55:58 -07:00
Grant Sanderson
88f0c24c69 Decompose ellipse manipulations 2021-03-24 14:00:46 -07:00
Grant Sanderson
09579fcd3e Not a great long-term fix, but flipping should always refresh the triangulation 2021-03-24 13:58:52 -07:00
Grant Sanderson
01d989ba23 Fix a bug for off-center vector fields 2021-03-24 13:58:18 -07:00
Grant Sanderson
6c3e4b94ea Add min_scale_factor to keep Mobjects from shrinking to 0, and hence losing all shape information, unless its purposeful 2021-03-23 08:50:10 -07:00
Grant Sanderson
52baf5b7c2 Change matrix entry alignment default 2021-03-23 08:46:13 -07:00
Grant Sanderson
fd18e4a21f Fixed missing arg in self.get_parts_by_text 2021-03-19 10:55:04 -07:00
Grant Sanderson
2a1b023442 Merge branch 'master' of github.com:3b1b/manim into matrix-exp-development 2021-03-18 17:44:21 -07:00
Grant Sanderson
288983e7b9 Make sure mobject data gets unlocked after animations 2021-03-18 17:43:15 -07:00
Grant Sanderson
f6ff070a8e Add FlashAround and FlashUnder 2021-03-18 17:42:47 -07:00
Grant Sanderson
5126dd1f52 New defaults for FullScreenRectangle 2021-03-18 17:42:19 -07:00
Grant Sanderson
8345ca6160 Small fixes to NumberLine 2021-03-18 17:37:12 -07:00
Grant Sanderson
611ac7f448 Update to Cross to make it default to variable stroke width 2021-03-18 17:36:46 -07:00
Grant Sanderson
933b7fd3da Use Text not TexText for Brace 2021-03-18 17:35:23 -07:00
Grant Sanderson
15f3b359ae Added Text.get_parts_by_text 2021-03-18 17:34:57 -07:00
Grant Sanderson
0e326c7ac5 Return stroke_width as 1d array 2021-03-18 17:34:36 -07:00
Grant Sanderson
ed2e3e80d9 Updates to VectorField 2021-03-18 17:34:16 -07:00
Grant Sanderson
3c240478b8 Tiny format change 2021-03-18 17:33:20 -07:00
Grant Sanderson
120d26defa If chosen monitor is not available, choose one that does exist 2021-03-18 17:32:45 -07:00
William Clavier
5c427ea287 Fixed typo in example_scenes.py 2021-03-07 22:19:01 -05:00
Naveen M K
503bd116a6 link to manimpango 2021-03-01 12:11:44 +05:30
Naveen M K
f5d5565af1 Update installation.rst 2021-03-01 12:08:36 +05:30
Naveen M K
aedf5633aa update docs for linux installation 2021-02-26 22:31:22 +05:30
JWro
63b497c352 Fix init of Elbow super class 2021-02-26 10:46:19 +01:00
Grant Sanderson
531a031b50 Merge pull request #1415 from 3b1b/matrix-exp-development
Matrix exp development
2021-02-25 08:52:29 -08:00
Grant Sanderson
b48ce3f1de Remove whitespace 2021-02-25 08:48:50 -08:00
Grant Sanderson
5636b41dfd bug fix for resize_with_interpolation in the case of length=0 2021-02-25 08:48:41 -08:00
Grant Sanderson
402c06c99a Allow 3b1b_colormap as an option for get_colormap_list 2021-02-25 08:48:22 -08:00
Grant Sanderson
eec396681c Updated VectorField and StreamLines 2021-02-25 08:47:29 -08:00
Grant Sanderson
d06b3769b8 Added Mobject.set_color_by_rgba_func 2021-02-25 08:46:56 -08:00
Grant Sanderson
8fcb069808 Added some methods to coordinate system to access all axes ranges 2021-02-25 08:45:49 -08:00
Grant Sanderson
9fb6280f1d Added colormap 2021-02-25 08:45:15 -08:00
Grant Sanderson
e35f8466be Added VShowPassingFlash 2021-02-25 08:45:03 -08:00
Eisenwave
44df81fd70 fix typo 2021-02-24 18:35:22 +01:00
Grant Sanderson
d1fc6c8ed7 Merge branch 'master' of github.com:3b1b/manim 2021-02-23 12:01:00 -08:00
Grant Sanderson
9d1c8df095 Merge pull request #1398 from casperdcl/tqdm-fixes
progress fixes
2021-02-23 12:00:31 -08:00
Grant Sanderson
1d0b864001 Merge pull request #1395 from naveen521kk/patch-6
register_font is available for macOS
2021-02-23 11:59:42 -08:00
Grant Sanderson
5008e20b8e Tiny PEP fix 2021-02-23 11:59:08 -08:00
Casper da Costa-Luis
c92b6dbd0b correct ascii fallback 2021-02-19 17:04:29 +00:00
Casper da Costa-Luis
9c23a5feef remove unused requirement 2021-02-19 17:04:00 +00:00
Naveen M K
ba3bb64bce register_font is available for macOS
https://github.com/ManimCommunity/ManimPango/pull/26
2021-02-19 00:49:50 +05:30
Tony031218
448d792473 unspecify the version in setup.cfg 2021-02-15 12:56:58 +08:00
Tony031218
2f202e26b2 update docs for pip install 2021-02-15 12:23:53 +08:00
鹤翔万里
0c25b56afe Update 2021-02-14 21:30:08 +08:00
鹤翔万里
0f998615ad Merge pull request #6 from 3b1b/shaders
update shaders branch
2020-06-05 08:20:26 +08:00
鹤翔万里
abc018e0d8 Merge pull request #2 from chenxijun/patch-1
Make "shaders" branch work
2020-04-06 11:48:40 +08:00
尘息
abe1ea78d0 It's too old 2020-04-03 14:32:08 +08:00
尘息
229c809a4b Fix a bug in extract_scene.py 2020-04-01 13:45:24 +08:00
Lalourche
1b125df572 Fixed execution of counting.py 2020-03-28 22:02:13 +01:00
80 changed files with 1096 additions and 874 deletions

View File

@@ -20,7 +20,7 @@ jobs:
- name: Install sphinx and manim env
run: |
pip3 install --upgrade pip
sudo apt install python3-setuptools
sudo apt install python3-setuptools libpango1.0-dev
pip3 install -r docs/requirements.txt
pip3 install -r requirements.txt
@@ -37,4 +37,4 @@ jobs:
with:
ACCESS_TOKEN: ${{ secrets.DOC_DEPLOY_TOKEN }}
BRANCH: gh-pages
FOLDER: docs/build/html
FOLDER: docs/build/html

View File

@@ -19,12 +19,12 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
pip install setuptools wheel twine build
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
python -m build
twine upload dist/*

4
.gitignore vendored
View File

@@ -15,6 +15,8 @@ __pycache__/
build/
develop-eggs/
dist/
manimlib.egg-info/
downloads/
eggs/
.eggs/
@@ -147,4 +149,4 @@ dmypy.json
# For manim
/videos
/custom_config.yml
/custom_config.yml

2
MANIFEST.in Normal file
View File

@@ -0,0 +1,2 @@
graft manimlib
recursive-exclude manimlib *.pyc *.DS_Store

View File

@@ -4,26 +4,39 @@
</a>
</p>
[![pypi version](https://img.shields.io/pypi/v/manimgl?logo=pypi)](https://pypi.org/project/manimgl/)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://choosealicense.com/licenses/mit/)
[![Manim Subreddit](https://img.shields.io/reddit/subreddit-subscribers/manim.svg?color=ff4301&label=reddit)](https://www.reddit.com/r/manim/)
[![Manim Discord](https://img.shields.io/discord/581738731934056449.svg?label=discord)](https://discord.gg/mMRrZQW)
[![Manim Subreddit](https://img.shields.io/reddit/subreddit-subscribers/manim.svg?color=ff4301&label=reddit&logo=reddit)](https://www.reddit.com/r/manim/)
[![Manim Discord](https://img.shields.io/discord/581738731934056449.svg?label=discord&logo=discord)](https://discord.com/invite/bYCyhM9Kz2)
[![docs](https://github.com/3b1b/manim/workflows/docs/badge.svg)](https://3b1b.github.io/manim/)
Manim is an engine for precise programatic animations, designed for creating explanatory math videos.
Manim is an engine for precise programmatic animations, designed for creating explanatory math videos.
Note, there are two versions of manim. This repository began as a personal project by the author of [3Blue1Brown](https://www.3blue1brown.com/) for the purpose of animating those videos, with video-specific code available [here](https://github.com/3b1b/videos). In 2020 a group of devlopers forked it into what is now the [community edition](https://github.com/ManimCommunity/manim/), with a goal of being more stable, better tested, quicker to respond to community contributions, and all around friendlier to get started with. You can engage with that community by joining the discord.
Since the fork, this version has evolved to work on top of OpenGL, and allows real-time rendering to an interactive window before scenes are finalized and written to a file.
Note, there are two versions of manim. This repository began as a personal project by the author of [3Blue1Brown](https://www.3blue1brown.com/) for the purpose of animating those videos, with video-specific code available [here](https://github.com/3b1b/videos). In 2020 a group of developers forked it into what is now the [community edition](https://github.com/ManimCommunity/manim/), with a goal of being more stable, better tested, quicker to respond to community contributions, and all around friendlier to get started with. See [this page](https://docs.manim.community/en/stable/installation/versions.html?highlight=OpenGL#which-version-to-use) for more details.
## Installation
> **WARNING:** These instructions are for ManimGL _only_. Trying to use these instructions to install [ManimCommunity/manim](https://github.com/ManimCommunity/manim) or instructions there to install this version will cause problems. You should first decide which version you wish to install, then only follow the instructions for your desired version.
>
> **Note**: To install manim directly through pip, please pay attention to the name of the installed package. This repository is ManimGL of 3b1b. The package name is `manimgl` instead of `manim` or `manimlib`. Please use `pip install manimgl` to install the version in this repository.
Manim runs on Python 3.6 or higher (Python 3.8 is recommended).
System requirements are [FFmpeg](https://ffmpeg.org/), [OpenGL](https://www.opengl.org//) and [LaTeX](https://www.latex-project.org) (optional, if you want to use LaTeX).
System requirements are [FFmpeg](https://ffmpeg.org/), [OpenGL](https://www.opengl.org/) and [LaTeX](https://www.latex-project.org) (optional, if you want to use LaTeX).
For Linux, [Pango](https://pango.gnome.org) along with its development headers are required. See instruction [here](https://github.com/ManimCommunity/ManimPango#building).
For more options, take a look at the [Using manim](#using-manim) sections further below.
### Directly
```sh
# Install manimgl
pip install manimgl
# Try it out
manimgl
```
For more options, take a look at the [Using manim](#using-manim) sections further below.
If you want to hack on manimlib itself, clone this repository and in that directory execute:
```sh
@@ -35,7 +48,21 @@ manimgl example_scenes.py OpeningManimExample
# or
manim-render example_scenes.py OpeningManimExample
```
### Directly (Windows)
1. [Install FFmpeg](https://www.wikihow.com/Install-FFmpeg-on-Windows).
2. Install a LaTeX distribution. [MiKTeX](https://miktex.org/download) is recommended.
3. Install the remaining Python packages.
```sh
git clone https://github.com/3b1b/manim.git
cd manim
pip install -e .
manimgl example_scenes.py OpeningManimExample
```
### Mac OSX
1. Install FFmpeg, LaTeX in terminal using homebrew.
```sh
brew install ffmpeg mactex
@@ -49,23 +76,12 @@ manim-render example_scenes.py OpeningManimExample
manimgl example_scenes.py OpeningManimExample
```
### Directly (Windows)
1. [Install FFmpeg](https://www.wikihow.com/Install-FFmpeg-on-Windows).
2. Install a LaTeX distribution. [MiKTeX](https://miktex.org/download) is recommended.
3. Install the remaining Python packages.
```sh
git clone https://github.com/3b1b/manim.git
cd manim
pip install -e .
manimgl example_scenes.py OpeningManimExample
```
## Anaconda Install
* Install LaTeX as above.
* Create a conda environment using `conda create -n manim python=3.8`.
* Activate the environment using `conda activate manim`.
* Install manimgl using `pip install -e .`.
1. Install LaTeX as above.
2. Create a conda environment using `conda create -n manim python=3.8`.
3. Activate the environment using `conda activate manim`.
4. Install manimgl using `pip install -e .`.
## Using manim
@@ -88,7 +104,7 @@ Take a look at custom_config.yml for further configuration. To add your customi
Look through the [example scenes](https://3b1b.github.io/manim/getting_started/example_scenes.html) to get a sense of how it is used, and feel free to look through the code behind [3blue1brown videos](https://github.com/3b1b/videos) for a much larger set of example. Note, however, that developments are often made to the library without considering backwards compatibility with those old videos. To run an old project with a guarantee that it will work, you will have to go back to the commit which completed that project.
### Documentation
Documentation is in progress at [3b1b.github.io/manim](https://3b1b.github.io/manim/). And there is also a Chinese version maintained by **@manim-kindergarten**: [manim.ml](https://manim.ml/) (in Chinese).
Documentation is in progress at [3b1b.github.io/manim](https://3b1b.github.io/manim/). And there is also a Chinese version maintained by [**@manim-kindergarten**](https://manim.org.cn): [docs.manim.org.cn](https://docs.manim.org.cn/) (in Chinese).
[manim-kindergarten](https://github.com/manim-kindergarten/) wrote and collected some useful extra classes and some codes of videos in [manim_sandbox repo](https://github.com/manim-kindergarten/manim_sandbox).

View File

@@ -1,293 +0,0 @@
p.color-text {
font-size: inherit;
font-family: var(--font-stack--monospace);
margin-top: 25px;
color: WHITE;
}
p.color-text-small {
font-size: small;
font-family: var(--font-stack--monospace);
margin-top: 28px;
color: WHITE;
}
.colors {
float: left;
padding: 10px;
border: 10px;
margin: 0;
width: 80px;
height: 80px;
text-align: center;
}
.BLUE_A {
background: #C7E9F1;
color:#C7E9F1;
}
.BLUE_B {
background: #9CDCEB;
color:#9CDCEB;
}
.BLUE_C {
background: #58C4DD;
color:#58C4DD;
}
.BLUE_D {
background: #29ABCA;
color:#29ABCA;
}
.BLUE_E {
background: #1C758A;
color:#1C758A;
}
.TEAL_A {
background: #ACEAD7;
color:#ACEAD7 ;
}
.TEAL_B {
background: #76DDC0;
color: #76DDC0;
}
.TEAL_C {
background: #5CD0B3;
color: #5CD0B3;
}
.TEAL_D {
background: #55C1A7;
color: #55C1A7;
}
.TEAL_E {
background: #49A88F;
color: #49A88F;
}
.GREEN_A {
background: #C9E2AE;
color: #C9E2AE;
}
.GREEN_B {
background: #A6CF8C;
color: #A6CF8C;
}
.GREEN_C {
background: #83C167;
color: #83C167;
}
.GREEN_D {
background: #77B05D;
color: #77B05D;
}
.GREEN_E {
background: #699C52;
color: #699C52;
}
.YELLOW_A {
background: #FFF1B6;
color: #FFF1B6;
}
.YELLOW_B {
background: #FFEA94;
color:#FFEA94 ;
}
.YELLOW_C {
background: #FFFF00;
color: #FFFF00;
}
.YELLOW_D {
background: #F4D345;
color: #F4D345;
}
.YELLOW_E {
background: #E8C11C;
color: #E8C11C;
}
.GOLD_A {
background: #F7C797;
color:#F7C797;
}
.GOLD_B {
background: #F9B775;
color:#F9B775;
}
.GOLD_C {
background: #F0AC5F;
color:#F0AC5F;
}
.GOLD_D {
background: #E1A158;
color:#E1A158;
}
.GOLD_E {
background: #C78D46;
color:#C78D46;
}
.RED_A {
background: #F7A1A3;
color:#F7A1A3;
}
.RED_B {
background: #FF8080;
color:#FF8080;
}
.RED_C {
background: #FC6255;
color:#FC6255;
}
.RED_D {
background: #E65A4C;
color:#E65A4C;
}
.RED_E {
background: #CF5044;
color:#CF5044;
}
.MAROON_A {
background: #ECABC1;
color: #ECABC1;
}
.MAROON_B {
background: #EC92AB;
color: #EC92AB;
}
.MAROON_C {
background: #C55F73;
color: #C55F73;
}
.MAROON_D {
background: #A24D61;
color: #A24D61;
}
.MAROON_E {
background: #94424F;
color: #94424F;
}
.PURPLE_A {
background: #CAA3E8;
color: #CAA3E8;
}
.PURPLE_B {
background: #B189C6;
color: #B189C6;
}
.PURPLE_C {
background: #9A72AC;
color: #9A72AC;
}
.PURPLE_D {
background: #715582;
color: #715582;
}
.PURPLE_E {
background: #644172;
color: #644172;
}
.GREY_A {
background: #DDDDDD;
color: #DDDDDD;
}
.GREY_B {
background: #BBBBBB;
color: #BBBBBB;
}
.GREY_C {
background: #888888;
color: #888888;
}
.GREY_D {
background: #444444;
color: #444444;
}
.GREY_E {
background: #222222;
color: #222222;
}
.WHITE {
background: #FFFFFF;
color: #FFFFFF;
}
.BLACK {
background: #000000;
color: #000000;
}
.GREY_BROWN {
background: #736357;
color: #736357;
}
.DARK_BROWN {
background: #8B4513;
color: #8B4513;
}
.LIGHT_BROWN {
background: #CD853F;
color: #CD853F;
}
.PINK {
background: #D147BD;
color: #D147BD;
}
.LIGHT_PINK {
background: #DC75CD;
color: #DC75CD;
}
.GREEN_SCREEN {
background: #00FF00;
color: #00FF00;
}
.ORANGE {
background: #FF862F;
color: #FF862F;
}

View File

@@ -1,62 +0,0 @@
p {
font-size: initial;
}
span.caption-text {
font-size: larger;
}
span.pre {
font-size: initial;
}
.highlight-python.notranslate {
margin-top: 0em;
}
.manim-video {
width: 99.9%;
padding: 8px 0;
outline: 0;
}
.manim-example {
background-color: #333333;
margin-bottom: 10px;
box-shadow: 2px 2px 4px #ddd;
}
.manim-example .manim-video {
padding: 0;
}
.manim-example img {
margin-bottom: 0;
}
h5.example-header {
font-size: 18px;
font-weight: bold;
padding: 8px 16px;
color: white;
margin: 0;
font-family: inherit;
text-transform: none;
margin-top: -0.4em;
margin-bottom: -0.2em;
}
.manim-example .highlight {
background-color: #fafafa;
border: 2px solid #333333;
padding: 8px 8px 10px 8px;
font-size: large;
margin: 0;
}
.manim-example .highlight pre {
background-color: inherit;
border-left: none;
margin: 0;
padding: 0 6px 0 6px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -31,7 +31,10 @@ master_doc = 'index'
pygments_style = 'default'
html_static_path = ["_static"]
html_css_files = ["custom.css", "colors.css"]
html_css_files = [
"https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/custom.css",
"https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/colors.css"
]
html_theme = 'furo' # pip install furo==2020.10.5b9
html_favicon = '_static/icon.png'
html_logo = '../../logo/transparent_graph.png'

View File

@@ -1,4 +1,59 @@
Changelog
=========
No changes now.
Unreleased
----------
No changes
v1.1.0
-------
Fixed bugs
^^^^^^^^^^
- Fixed the bug of :func:`~manimlib.utils.iterables.resize_with_interpolation` in the case of ``length=0``
- Fixed the bug of ``__init__`` in :class:`~manimlib.mobject.geometry.Elbow`
- If chosen monitor is not available, choose one that does exist
- Make sure mobject data gets unlocked after animations
- Fixed a bug for off-center vector fields
- Had ``Mobject.match_points`` return self
- Fixed chaining animation in example scenes
- Fixed the default color of tip
- Fixed a typo in ``ShowPassingFlashWithThinningStrokeWidth``
- Fixed the default size of ``Text``
- Fixed a missing import line in ``mobject.py``
- Fixed the bug in ControlsExample
- Make sure frame is added to the scene when initialization
- Fixed zooming directions
- Rewrote ``earclip_triangulation`` to fix triangulation
- Allowed sound_file_name to be taken in without extensions
New Features
^^^^^^^^^^^^
- Added :class:`~manimlib.animation.indication.VShowPassingFlash`
- Added ``COLORMAP_3B1B``
- Added some methods to coordinate system to access all axes ranges
- :meth:`~manimlib.mobject.coordinate_systems.CoordinateSystem.get_origin`
- :meth:`~manimlib.mobject.coordinate_systems.CoordinateSystem.get_all_ranges`
- Added :meth:`~manimlib.mobject.mobject.Mobject.set_color_by_rgba_func`
- Updated :class:`~manimlib.mobject.vector_field.VectorField` and :class:`~manimlib.mobject.vector_field.StreamLines`
- Allow ``3b1b_colormap`` as an option for :func:`~manimlib.utils.color.get_colormap_list`
- Return ``stroke_width`` as 1d array
- Added :meth:`~manimlib.mobject.svg.text_mobject.Text.get_parts_by_text`
- Use Text not TexText for Brace
- Update to Cross to make it default to variable stroke width
- Added :class:`~manimlib.animation.indication.FlashAround` and :class:`~manimlib.animation.indication.FlashUnder`
- Allowed configuration in ``Brace.get_text``
- Added :meth:`~manimlib.camera.camera.CameraFrame.reorient` for quicker changes to frame angle
- Added ``units`` to :meth:`~manimlib.camera.camera.CameraFrame.set_euler_angles`
- Allowed any ``VMobject`` to be passed into ``TransformMatchingTex``
- Removed double brace convention in ``Tex`` and ``TexText``
- Added support for debugger launch
- Added CLI flag ``--config_file`` to load configuration file manually
- Added ``tip_style`` to ``tip_config``
- Added ``MarkupText``
- Take in ``u_range`` and ``v_range`` as arguments to ``ParametricSurface``
- Added ``TrueDot``

View File

@@ -56,7 +56,7 @@ custom_config
- ``raster_images``
The directory for storing raster images to be used in the code (including
``.jpg``, ``.png`` and ``.gif``), which will be read by ``ImageMobject``.
``.jpg``, ``.jpeg``, ``.png`` and ``.gif``), which will be read by ``ImageMobject``.
- ``vector_images``
The directory for storing vector images to be used in the code (including
@@ -108,6 +108,11 @@ The relative position of the playback window on the display (two characters,
the first character means upper(U) / middle(O) / lower(D), the second character
means left(L) / middle(O) / right(R)).
``window_monitor``
------------------
The number of the monitor you want the preview window to pop up on. (default is 0)
``break_into_partial_movies``
-----------------------------

View File

@@ -58,6 +58,7 @@ flag abbr function
``--color COLOR`` ``-c`` Background color
``--leave_progress_bars`` Leave progress bars displayed in terminal
``--video_dir VIDEO_DIR`` directory to write video
``--config_file CONFIG_FILE`` Path to the custom configuration file
========================================================== ====== =================================================================================================================================================================================================
custom_config
@@ -85,5 +86,11 @@ following the directory structure:
└── custom_config.yml
When you enter the ``project/`` folder and run ``manimgl code.py <Scene>``,
it will overwrite ``manim/custom_config.yml`` with ``custom_config.yml``
in the ``project`` folder.
it will overwrite ``manim/default_config.yml`` with ``custom_config.yml``
in the ``project`` folder.
Alternatively, you can use ``--config_file`` flag in CLI to specify configuration file manually.
.. code-block:: sh
manimgl project/code.py --config_file /path/to/custom_config.yml

View File

@@ -8,12 +8,12 @@ the simplest and one by one.
InteractiveDevlopment
---------------------
.. manim-example:: InteractiveDevlopment
:media: ../_static/example_scenes/InteractiveDevlopment.mp4
.. manim-example:: InteractiveDevelopment
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/InteractiveDevelopment.mp4
from manimlib import *
class InteractiveDevlopment(Scene):
class InteractiveDevelopment(Scene):
def construct(self):
circle = Circle()
circle.set_fill(BLUE, opacity=0.5)
@@ -66,7 +66,7 @@ AnimatingMethods
----------------
.. manim-example:: AnimatingMethods
:media: ../_static/example_scenes/AnimatingMethods.mp4
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/AnimatingMethods.mp4
class AnimatingMethods(Scene):
def construct(self):
@@ -124,10 +124,12 @@ TextExample
-----------
.. manim-example:: TextExample
:media: ../_static/example_scenes/TextExample.mp4
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/TextExample.mp4
class TextExample(Scene):
def construct(self):
# To run this scene properly, you should have "Consolas" font in your computer
# for full usage, you can see https://github.com/3b1b/manim/pull/680
text = Text("Here is a text", font="Consolas", font_size=90)
difference = Text(
"""
@@ -135,6 +137,7 @@ TextExample
you can change the font more easily, but can't use the LaTeX grammar
""",
font="Arial", font_size=24,
# t2c is a dict that you can choose color for different text
t2c={"Text": BLUE, "TexText": BLUE, "LaTeX": ORANGE}
)
VGroup(text, difference).arrange(DOWN, buff=1)
@@ -148,6 +151,7 @@ TextExample
t2f={"font": "Consolas", "words": "Consolas"},
t2c={"font": BLUE, "words": GREEN}
)
fonts.set_width(FRAME_WIDTH - 1)
slant = Text(
"And the same as slant and weight",
font="Consolas",
@@ -174,26 +178,30 @@ TexTransformExample
-------------------
.. manim-example:: TexTransformExample
:media: ../_static/example_scenes/TexTransformExample.mp4
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/TexTransformExample.mp4
class TexTransformExample(Scene):
def construct(self):
to_isolate = ["B", "C", "=", "(", ")"]
lines = VGroup(
# Surrounding substrings with double braces
# will ensure that those parts are separated
# out in the Tex. For example, here the
# Tex will have 5 submobjects, corresponding
# to the strings [A^2, +, B^2, =, C^2]
Tex("{{A^2}} + {{B^2}} = {{C^2}}"),
Tex("{{A^2}} = {{C^2}} - {{B^2}}"),
# 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
# Tex mobject has a subject corresponding to
# each of these strings. For example, the Tex mobject
# below will have 5 subjects, corresponding to the
# expressions [A^2, +, B^2, =, C^2]
Tex("A^2", "+", "B^2", "=", "C^2"),
# Likewise here
Tex("A^2", "=", "C^2", "-", "B^2"),
# Alternatively, you can pass in the keyword argument
# "isolate" with a list of strings that should be out as
# their own submobject. So both lines below are equivalent
# to what you'd get by wrapping every instance of "B", "C"
# "=", "(" and ")" with double braces
Tex("{{A^2}} = (C + B)(C - B)", isolate=to_isolate),
Tex("A = \\sqrt{(C + B)(C - B)}", isolate=to_isolate)
# their own submobject. So the line below is equivalent
# to the commented out line below it.
Tex("A^2 = (C + B)(C - B)", isolate=["A^2", *to_isolate]),
# Tex("A^2", "=", "(", "C", "+", "B", ")", "(", "C", "-", "B", ")"),
Tex("A = \\sqrt{(C + B)(C - B)}", isolate=["A", *to_isolate])
)
lines.arrange(DOWN, buff=LARGE_BUFF)
for line in lines:
@@ -252,7 +260,7 @@ TexTransformExample
# new_line2 and the "\sqrt" from the final line. By passing in,
# transform_mismatches=True, it will transform this "^2" part into
# the "\sqrt" part.
new_line2 = Tex("{{A}}^2 = (C + B)(C - B)", isolate=to_isolate)
new_line2 = Tex("A^2 = (C + B)(C - B)", isolate=["A", *to_isolate])
new_line2.replace(lines[2])
new_line2.match_style(lines[2])
@@ -295,7 +303,7 @@ UpdatersExample
---------------
.. manim-example:: UpdatersExample
:media: ../_static/example_scenes/UpdatersExample.mp4
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/UpdatersExample.mp4
class UpdatersExample(Scene):
def construct(self):
@@ -343,7 +351,7 @@ UpdatersExample
)
self.wait()
self.play(
square.set_width(5, stretch=True),
square.animate.set_width(5, stretch=True),
run_time=3,
)
self.wait()
@@ -380,14 +388,14 @@ CoordinateSystemExample
-----------------------
.. manim-example:: CoordinateSystemExample
:media: ../_static/example_scenes/CoordinateSystemExample.mp4
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/CoordinateSystemExample.mp4
class CoordinateSystemExample(Scene):
def construct(self):
axes = Axes(
# x-axis ranges from -1 to 10, with a default step size of 1
x_range=(-1, 10),
# y-axis ranges from -2 to 10 with a step size of 0.5
# y-axis ranges from -2 to 2 with a step size of 0.5
y_range=(-2, 2, 0.5),
# The axes will be stretched so as to match the specified
# height and width
@@ -450,8 +458,7 @@ CoordinateSystemExample
# system defined by them.
f_always(dot.move_to, lambda: axes.c2p(1, 1))
self.play(
axes.animate.scale(0.75),
axes.animate.to_corner(UL),
axes.animate.scale(0.75).to_corner(UL),
run_time=2,
)
self.wait()
@@ -465,7 +472,7 @@ GraphExample
------------
.. manim-example:: GraphExample
:media: ../_static/example_scenes/GraphExample.mp4
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/GraphExample.mp4
class GraphExample(Scene):
def construct(self):
@@ -551,7 +558,7 @@ SurfaceExample
--------------
.. manim-example:: SurfaceExample
:media: ../_static/example_scenes/SurfaceExample.mp4
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/SurfaceExample.mp4
class SurfaceExample(Scene):
CONFIG = {
@@ -652,7 +659,7 @@ OpeningManimExample
-------------------
.. manim-example:: OpeningManimExample
:media: ../_static/example_scenes/OpeningManimExample.mp4
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/OpeningManimExample.mp4
class OpeningManimExample(Scene):

View File

@@ -1,17 +1,27 @@
Installation
============
Manim runs on Python 3.8.
Manim runs on Python 3.6 or higher (Python 3.8 is recommended).
System requirements are
- `FFmpeg <https://ffmpeg.org/>`__
- `OpenGL <https://www.opengl.org//>`__ (included in python package ``PyOpenGL``)
- `LaTeX <https://www.latex-project.org>`__ (optional, if you want to use LaTeX)
- `Pango <https://pango.org>`__ (only for Linux)
Directly
--------
.. code-block:: sh
# Install manimgl
pip install manimgl
# Try it out
manimgl
If you want to hack on manimlib itself, clone this repository and in
that directory execute:
@@ -56,4 +66,4 @@ For Anaconda
cd manim
conda create -n manim python=3.8
conda activate manim
pip install -e .
pip install -e .

View File

@@ -59,7 +59,7 @@ At this time, no window will pop up. When the program is finished, this rendered
image will be automatically opened (saved in the subdirectory ``images/`` of the same
level directory of ``start.py`` by default):
.. image:: ../_static/quickstart/SquareToCircle.png
.. image:: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/quickstart/SquareToCircle.png
:align: center
Make an image
@@ -162,7 +162,7 @@ opened after the operation is over:
.. raw:: html
<video class="manim-video" controls loop autoplay src="../_static/quickstart/SquareToCircle.mp4"></video>
<video class="manim-video" controls loop autoplay src="https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/quickstart/SquareToCircle.mp4"></video>
Let's take a look at the code this time. The first 7 lines are the same as the previous
ones, and the 8th line is similar to the 5th line, which creates an instance of the
@@ -221,7 +221,7 @@ For example: input the following lines (without comment lines) into it respectiv
.. code-block:: python
# Stretched 4 times in the vertical direction
play(circle.animate.stretch(4, dim=0}))
play(circle.animate.stretch(4, dim=0))
# Rotate the ellipse 90°
play(Rotate(circle, TAU / 4))
# Move 2 units to the right and shrink to 1/4 of the original
@@ -237,7 +237,7 @@ You will get an animation similar to the following:
.. raw:: html
<video class="manim-video" controls loop autoplay src="../_static/quickstart/SquareToCircleEmbed.mp4"></video>
<video class="manim-video" controls loop autoplay src="https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/quickstart/SquareToCircleEmbed.mp4"></video>
If you want to enter the interactive mode directly, you don't have to write an
empty scene containing only ``self.embed()``, you can directly run the following command

View File

@@ -99,6 +99,7 @@ Below is the directory structure of manim:
├── config_ops.py # Process CONFIG
├── customization.py # Read from custom_config.yml
├── debug.py # Utilities for debugging in program
├── directories.py # Read directories from config file
├── family_ops.py # Process family members
├── file_ops.py # Process files and directories
├── images.py # Read image
@@ -119,9 +120,9 @@ Inheritance structure of manim's classes
is a pdf showed inheritance structure of manim's classes, large,
but basically all classes have included:
.. image:: ../_static/manim_shaders_structure.png
.. image:: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/manim_shaders_structure.png
Manim execution process
-----------------------
.. image:: ../_static/manim_shaders_process_en.png
.. image:: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/manim_shaders_process_en.png

View File

@@ -1,12 +1,12 @@
Manim's documentation
=====================
.. image:: ../../logo/white_with_name.png
.. image:: https://cdn.jsdelivr.net/gh/3b1b/manim@master/logo/white_with_name.png
Manim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos
at `3Blue1Brown <https://www.3blue1brown.com/>`_.
And here is a Chinese version of this documentation: https://manim.ml/shaders
And here is a Chinese version of this documentation: https://docs.manim.org.cn/shaders
.. toctree::
:maxdepth: 2

View File

@@ -161,20 +161,24 @@ class TexTransformExample(Scene):
def construct(self):
to_isolate = ["B", "C", "=", "(", ")"]
lines = VGroup(
# Surrounding substrings with double braces
# will ensure that those parts are separated
# out in the Tex. For example, here the
# Tex will have 5 submobjects, corresponding
# to the strings [A^2, +, B^2, =, C^2]
Tex("{{A^2}} + {{B^2}} = {{C^2}}"),
Tex("{{A^2}} = {{C^2}} - {{B^2}}"),
# 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
# Tex mobject has a subject corresponding to
# each of these strings. For example, the Tex mobject
# below will have 5 subjects, corresponding to the
# expressions [A^2, +, B^2, =, C^2]
Tex("A^2", "+", "B^2", "=", "C^2"),
# Likewise here
Tex("A^2", "=", "C^2", "-", "B^2"),
# Alternatively, you can pass in the keyword argument
# "isolate" with a list of strings that should be out as
# their own submobject. So both lines below are equivalent
# to what you'd get by wrapping every instance of "B", "C"
# "=", "(" and ")" with double braces
Tex("{{A^2}} = (C + B)(C - B)", isolate=to_isolate),
Tex("A = \\sqrt{(C + B)(C - B)}", isolate=to_isolate)
# their own submobject. So the line below is equivalent
# to the commented out line below it.
Tex("A^2 = (C + B)(C - B)", isolate=["A^2", *to_isolate]),
# Tex("A^2", "=", "(", "C", "+", "B", ")", "(", "C", "-", "B", ")"),
Tex("A = \\sqrt{(C + B)(C - B)}", isolate=["A", *to_isolate])
)
lines.arrange(DOWN, buff=LARGE_BUFF)
for line in lines:
@@ -233,7 +237,7 @@ class TexTransformExample(Scene):
# new_line2 and the "\sqrt" from the final line. By passing in,
# transform_mismatches=True, it will transform this "^2" part into
# the "\sqrt" part.
new_line2 = Tex("{{A}}^2 = (C + B)(C - B)", isolate=to_isolate)
new_line2 = Tex("A^2 = (C + B)(C - B)", isolate=["A", *to_isolate])
new_line2.replace(lines[2])
new_line2.match_style(lines[2])
@@ -328,7 +332,7 @@ class UpdatersExample(Scene):
now = self.time
w0 = square.get_width()
square.add_updater(
lambda m: m.set_width(w0 * math.cos(self.time - now))
lambda m: m.set_width(w0 * math.sin(self.time - now) + w0)
)
self.wait(4 * PI)
@@ -338,7 +342,7 @@ class CoordinateSystemExample(Scene):
axes = Axes(
# x-axis ranges from -1 to 10, with a default step size of 1
x_range=(-1, 10),
# y-axis ranges from -2 to 10 with a step size of 0.5
# y-axis ranges from -2 to 2 with a step size of 0.5
y_range=(-2, 2, 0.5),
# The axes will be stretched so as to match the specified
# height and width
@@ -401,8 +405,7 @@ class CoordinateSystemExample(Scene):
# system defined by them.
f_always(dot.move_to, lambda: axes.c2p(1, 1))
self.play(
axes.animate.scale(0.75),
axes.animate.to_corner(UL),
axes.animate.scale(0.75).to_corner(UL),
run_time=2,
)
self.wait()
@@ -584,7 +587,7 @@ class SurfaceExample(Scene):
self.wait()
class InteractiveDevlopment(Scene):
class InteractiveDevelopment(Scene):
def construct(self):
circle = Circle()
circle.set_fill(BLUE, opacity=0.5)
@@ -636,14 +639,14 @@ class ControlsExample(Scene):
self.checkbox = Checkbox()
self.color_picker = ColorSliders()
self.panel = ControlPanel(
Text("Text", size=0.5), self.textbox, Line(),
Text("Show/Hide Text", size=0.5), self.checkbox, Line(),
Text("Color of Text", size=0.5), self.color_picker
Text("Text", font_size=24), self.textbox, Line(),
Text("Show/Hide Text", font_size=24), self.checkbox, Line(),
Text("Color of Text", font_size=24), self.color_picker
)
self.add(self.panel)
def construct(self):
text = Text("", size=2)
text = Text("text", font_size=96)
def text_updater(old_text):
assert(isinstance(old_text, Text))

View File

@@ -6,12 +6,15 @@ import manimlib.utils.init_config
def main():
args = manimlib.config.parse_cli()
if args.config:
manimlib.utils.init_config.init_customization()
else:
config = manimlib.config.get_configuration(args)
scenes = manimlib.extract_scene.main(config)
for scene in scenes:
scene.run()
if __name__ == '__main__':
main()

View File

@@ -26,7 +26,7 @@ class Animation(object):
# If 0 < lag_ratio < 1, its applied to each
# with lagged start times
"lag_ratio": DEFAULT_ANIMATION_LAG_RATIO,
"suspend_mobject_updating": False,
"suspend_mobject_updating": True,
}
def __init__(self, mobject, **kwargs):

View File

@@ -25,6 +25,10 @@ class ShowPartial(Animation):
if not self.should_match_start:
self.mobject.lock_matching_data(self.mobject, self.starting_mobject)
def finish(self):
super().finish()
self.mobject.unlock_data()
def interpolate_submobject(self, submob, start_submob, alpha):
submob.pointwise_become_partial(
start_submob, *self.get_bounds(alpha)

View File

@@ -1,4 +1,5 @@
import numpy as np
import math
from manimlib.constants import *
from manimlib.animation.animation import Animation
@@ -13,6 +14,7 @@ from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.geometry import Circle
from manimlib.mobject.geometry import Dot
from manimlib.mobject.shape_matchers import SurroundingRectangle
from manimlib.mobject.shape_matchers import Underline
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.geometry import Line
from manimlib.utils.bezier import interpolate
@@ -152,6 +154,87 @@ class ShowPassingFlash(ShowPartial):
submob.pointwise_become_partial(start, 0, 1)
class VShowPassingFlash(Animation):
CONFIG = {
"time_width": 0.3,
"taper_width": 0.02,
"remover": True,
}
def begin(self):
self.mobject.align_stroke_width_data_to_points()
# Compute an array of stroke widths for each submobject
# which tapers out at either end
self.submob_to_anchor_widths = dict()
for sm in self.mobject.get_family():
original_widths = sm.get_stroke_widths()
anchor_widths = np.array([*original_widths[0::3], original_widths[-1]])
def taper_kernel(x):
if x < self.taper_width:
return x
elif x > 1 - self.taper_width:
return 1.0 - x
return 1.0
taper_array = list(map(taper_kernel, np.linspace(0, 1, len(anchor_widths))))
self.submob_to_anchor_widths[hash(sm)] = anchor_widths * taper_array
super().begin()
def interpolate_submobject(self, submobject, starting_sumobject, alpha):
anchor_widths = self.submob_to_anchor_widths[hash(submobject)]
# Create a gaussian such that 3 sigmas out on either side
# will equals time_width
tw = self.time_width
sigma = tw / 6
mu = interpolate(-tw / 2, 1 + tw / 2, alpha)
def gauss_kernel(x):
if abs(x - mu) > 3 * sigma:
return 0
z = (x - mu) / sigma
return math.exp(-0.5 * z * z)
kernel_array = list(map(gauss_kernel, np.linspace(0, 1, len(anchor_widths))))
scaled_widths = anchor_widths * kernel_array
new_widths = np.zeros(submobject.get_num_points())
new_widths[0::3] = scaled_widths[:-1]
new_widths[2::3] = scaled_widths[1:]
new_widths[1::3] = (new_widths[0::3] + new_widths[2::3]) / 2
submobject.set_stroke(width=new_widths)
def finish(self):
super().finish()
for submob, start in self.get_all_families_zipped():
submob.match_style(start)
class FlashAround(VShowPassingFlash):
CONFIG = {
"stroke_width": 4.0,
"color": YELLOW,
"buff": SMALL_BUFF,
"time_width": 1.0,
"n_inserted_curves": 20,
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs)
path = self.get_path(mobject)
path.insert_n_curves(self.n_inserted_curves)
path.set_points(path.get_points_without_null_curves())
path.set_stroke(self.color, self.stroke_width)
super().__init__(path, **kwargs)
def get_path(self, mobject):
return SurroundingRectangle(mobject, buff=self.buff)
class FlashUnder(FlashAround):
def get_path(self, mobject):
return Underline(mobject, buff=self.buff)
class ShowCreationThenDestruction(ShowPassingFlash):
CONFIG = {
"time_width": 2.0,

View File

@@ -10,7 +10,6 @@ from manimlib.mobject.mobject import Mobject
from manimlib.mobject.mobject import Group
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.utils.config_ops import digest_config
@@ -129,7 +128,7 @@ class TransformMatchingShapes(TransformMatchingParts):
class TransformMatchingTex(TransformMatchingParts):
CONFIG = {
"mobject_type": Tex,
"mobject_type": VMobject,
"group_type": VGroup,
}

View File

@@ -77,16 +77,24 @@ class CameraFrame(Mobject):
self.set_euler_angles(theta, phi, gamma)
return self
def set_euler_angles(self, theta=None, phi=None, gamma=None):
def set_euler_angles(self, theta=None, phi=None, gamma=None, units=RADIANS):
if theta is not None:
self.data["euler_angles"][0] = theta
self.data["euler_angles"][0] = theta * units
if phi is not None:
self.data["euler_angles"][1] = phi
self.data["euler_angles"][1] = phi * units
if gamma is not None:
self.data["euler_angles"][2] = gamma
self.data["euler_angles"][2] = gamma * units
self.refresh_rotation_matrix()
return self
def reorient(self, theta_degrees=None, phi_degrees=None, gamma_degrees=None):
"""
Shortcut for set_euler_angles, defaulting to taking
in angles in degrees
"""
self.set_euler_angles(theta_degrees, phi_degrees, gamma_degrees, units=DEGREES)
return self
def set_theta(self, theta):
return self.set_euler_angles(theta=theta)
@@ -447,7 +455,7 @@ class Camera(object):
if path not in self.path_to_texture_id:
# A way to increase tid's sequentially
tid = len(self.path_to_texture_id)
im = Image.open(path)
im = Image.open(path).convert("RGBA")
texture = self.ctx.texture(
size=im.size,
components=len(im.getbands()),

View File

@@ -130,7 +130,11 @@ def parse_cli():
)
parser.add_argument(
"--video_dir",
help="directory to write video",
help="Directory to write video",
)
parser.add_argument(
"--config_file",
help="Path to the custom configuration file",
)
args = parser.parse_args()
return args
@@ -155,17 +159,19 @@ def get_module(file_name):
spec.loader.exec_module(module)
return module
__config_file__ = "custom_config.yml"
def get_custom_config():
filename = "custom_config.yml"
global __config_file__
global_defaults_file = os.path.join(get_manim_dir(), "manimlib", "default_config.yml")
if os.path.exists(global_defaults_file):
with open(global_defaults_file, "r") as file:
config = yaml.safe_load(file)
if os.path.exists(filename):
with open(filename, "r") as file:
if os.path.exists(__config_file__):
with open(__config_file__, "r") as file:
local_defaults = yaml.safe_load(file)
if local_defaults:
config = merge_dicts_recursively(
@@ -173,22 +179,41 @@ def get_custom_config():
local_defaults,
)
else:
with open(filename, "r") as file:
with open(__config_file__, "r") as file:
config = yaml.safe_load(file)
return config
def get_configuration(args):
local_config_file = "custom_config.yml"
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}.")
if sys.platform == 'win32':
print(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}...")
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.")
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(local_config_file)):
if not (os.path.exists(global_defaults_file) or os.path.exists(__config_file__)):
print("There is no configuration file detected. Initial configuration:\n")
init_customization()
elif not os.path.exists(local_config_file):
print(f"""Warning: Using the default configuration file, which you can modify in {global_defaults_file}
If you want to create a local configuration file, you can create a file named {local_config_file}, or run manimgl --config
""")
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")
custom_config = get_custom_config()
write_file = any([args.write_file, args.open, args.finder])
@@ -234,7 +259,9 @@ def get_configuration(args):
# Default to making window half the screen size
# but make it full screen if -f is passed in
monitor = get_monitors()[custom_config["window_monitor"]]
monitors = get_monitors()
mon_index = custom_config["window_monitor"]
monitor = monitors[min(mon_index, len(monitors) - 1)]
window_width = monitor.width
if not args.full_screen:
window_width //= 2

View File

@@ -50,6 +50,9 @@ RIGHT_SIDE = FRAME_X_RADIUS * RIGHT
PI = np.pi
TAU = 2 * PI
DEGREES = TAU / 360
# Nice to have a constant for readability
# when juxtaposed with expressions like 30 * DEGREES
RADIANS = 1
FFMPEG_BIN = "ffmpeg"
@@ -136,3 +139,5 @@ RED = RED_C
MAROON = MAROON_C
PURPLE = PURPLE_C
GREY = GREY_C
COLORMAP_3B1B = [BLUE_E, GREEN, YELLOW, RED]

View File

@@ -25,11 +25,11 @@ class CoordinateSystem():
"""
CONFIG = {
"dimension": 2,
"x_range": np.array([-8, 8, 1.0]),
"y_range": np.array([-4, 4, 1.0]),
"x_range": np.array([-8.0, 8.0, 1.0]),
"y_range": np.array([-4.0, 4.0, 1.0]),
"width": None,
"height": None,
"num_sampled_graph_points_per_tick": 5,
"num_sampled_graph_points_per_tick": 20,
}
def coords_to_point(self, *coords):
@@ -46,9 +46,15 @@ class CoordinateSystem():
"""Abbreviation for point_to_coords"""
return self.point_to_coords(point)
def get_origin(self):
return self.c2p(*[0] * self.dimension)
def get_axes(self):
raise Exception("Not implemented")
def get_all_ranges(self):
raise Exception("Not implemented")
def get_axis(self, index):
return self.get_axes()[index]
@@ -319,6 +325,9 @@ class Axes(VGroup, CoordinateSystem):
def get_axes(self):
return self.axes
def get_all_ranges(self):
return [self.x_range, self.y_range]
def add_coordinate_labels(self,
x_values=None,
y_values=None,
@@ -334,11 +343,13 @@ class Axes(VGroup, CoordinateSystem):
class ThreeDAxes(Axes):
CONFIG = {
"dimension": 3,
"x_range": np.array([-6, 6, 1]),
"y_range": np.array([-5, 5, 1]),
"z_range": np.array([-4, 4, 1]),
"x_range": np.array([-6.0, 6.0, 1.0]),
"y_range": np.array([-5.0, 5.0, 1.0]),
"z_range": np.array([-4.0, 4.0, 1.0]),
"z_axis_config": {},
"z_normal": DOWN,
"height": None,
"width": None,
"depth": None,
"num_axis_pieces": 20,
"gloss": 0.5,
@@ -346,9 +357,11 @@ class ThreeDAxes(Axes):
def __init__(self, x_range=None, y_range=None, z_range=None, **kwargs):
Axes.__init__(self, x_range, y_range, **kwargs)
if z_range is not None:
self.z_range[:len(z_range)] = z_range
z_axis = self.create_axis(
z_range or self.z_range,
self.z_range,
self.z_axis_config,
self.depth,
)
@@ -365,6 +378,9 @@ class ThreeDAxes(Axes):
for axis in self.axes:
axis.insert_n_curves(self.num_axis_pieces - 1)
def get_all_ranges(self):
return [self.x_range, self.y_range, self.z_range]
class NumberPlane(Axes):
CONFIG = {
@@ -388,7 +404,7 @@ class NumberPlane(Axes):
"width": None,
# Defaults to a faded version of line_config
"faded_line_style": None,
"faded_line_ratio": 1,
"faded_line_ratio": 4,
"make_smooth_after_applying_functions": True,
}

View File

@@ -20,6 +20,9 @@ class ScreenRectangle(Rectangle):
class FullScreenRectangle(ScreenRectangle):
CONFIG = {
"height": FRAME_HEIGHT,
"fill_color": GREY_E,
"fill_opacity": 1,
"stroke_width": 0,
}

View File

@@ -49,6 +49,7 @@ class TipableVMobject(VMobject):
"tip_config": {
"fill_opacity": 1,
"stroke_width": 0,
"tip_style": 0, # triangle=0, inner_smooth=1, dot=2
},
"normal_vector": OUT,
}
@@ -63,6 +64,7 @@ class TipableVMobject(VMobject):
tip = self.create_tip(at_start, **kwargs)
self.reset_endpoints_based_on_tip(tip, at_start)
self.asign_tip_attr(tip, at_start)
tip.set_color(self.get_stroke_color())
self.add(tip)
return self
@@ -569,7 +571,7 @@ class Elbow(VMobject):
}
def __init__(self, **kwargs):
super().__init__(self, **kwargs)
super().__init__(**kwargs)
self.set_points_as_corners([UP, UP + RIGHT, RIGHT])
self.set_width(self.width, about_point=ORIGIN)
self.rotate(self.angle, about_point=ORIGIN)
@@ -786,12 +788,20 @@ class ArrowTip(Triangle):
"width": DEFAULT_ARROW_TIP_WIDTH,
"length": DEFAULT_ARROW_TIP_LENGTH,
"angle": 0,
"tip_style": 0, # triangle=0, inner_smooth=1, dot=2
}
def __init__(self, **kwargs):
Triangle.__init__(self, start_angle=0, **kwargs)
self.set_height(self.width)
self.set_width(self.length, stretch=True)
if self.tip_style == 1:
self.set_height(self.length * 0.9, stretch=True)
self.data["points"][4] += np.array([0.6 * self.length, 0, 0])
elif self.tip_style == 2:
h = self.length / 2
self.clear_points()
self.data["points"] = Dot().set_width(h).get_points()
self.rotate(self.angle)
def get_base(self):

View File

@@ -439,7 +439,7 @@ class ControlPanel(Group):
},
"opener_text_kwargs": {
"text": "Control Panel",
"size": 0.4
"font_size": 20
}
}

View File

@@ -57,13 +57,13 @@ class Matrix(VMobject):
CONFIG = {
"v_buff": 0.8,
"h_buff": 1.3,
"bracket_h_buff": MED_SMALL_BUFF,
"bracket_v_buff": MED_SMALL_BUFF,
"bracket_h_buff": 0.2,
"bracket_v_buff": 0.25,
"add_background_rectangles_to_entries": False,
"include_background_rectangle": False,
"element_to_mobject": Tex,
"element_to_mobject_config": {},
"element_alignment_corner": DR,
"element_alignment_corner": DOWN,
}
def __init__(self, matrix, **kwargs):
@@ -132,6 +132,12 @@ class Matrix(VMobject):
for i in range(len(self.mob_matrix[0]))
])
def get_rows(self):
return VGroup(*[
VGroup(*row)
for row in self.mob_matrix
])
def set_column_colors(self, *colors):
columns = self.get_columns()
for color, column in zip(colors, columns):
@@ -163,6 +169,7 @@ class DecimalMatrix(Matrix):
class IntegerMatrix(Matrix):
CONFIG = {
"element_to_mobject": Integer,
"element_alignment_corner": UP,
}

View File

@@ -21,6 +21,7 @@ from manimlib.utils.iterables import resize_with_interpolation
from manimlib.utils.iterables import make_even
from manimlib.utils.iterables import listify
from manimlib.utils.bezier import interpolate
from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.paths import straight_path
from manimlib.utils.simple_functions import get_parameters
from manimlib.utils.space_ops import angle_of_vector
@@ -56,9 +57,7 @@ class Mobject(object):
# Must match in attributes of vert shader
"shader_dtype": [
('point', np.float32, (3,)),
],
# Event listener
"listen_to_events": False
]
}
def __init__(self, **kwargs):
@@ -176,6 +175,7 @@ class Mobject(object):
def match_points(self, mobject):
self.set_points(mobject.get_points())
return self
def get_points(self):
return self.data["points"]
@@ -504,7 +504,7 @@ class Mobject(object):
self.refresh_has_updater_status()
if call_updater:
self.update()
self.update(dt=0)
return self
def remove_updater(self, update_function):
@@ -561,7 +561,7 @@ class Mobject(object):
)
return self
def scale(self, scale_factor, **kwargs):
def scale(self, scale_factor, min_scale_factor=1e-8, **kwargs):
"""
Default behavior is to scale about the center of the mobject.
The argument about_edge can be a vector, indicating which side of
@@ -571,6 +571,7 @@ class Mobject(object):
Otherwise, if about_point is given a value, scaling is done with
respect to that point.
"""
scale_factor = max(scale_factor, min_scale_factor)
self.apply_points_function(
lambda points: scale_factor * points,
works_on_bounding_box=True,
@@ -841,7 +842,30 @@ class Mobject(object):
# Color functions
def set_rgba_array(self, color=None, opacity=None, name="rgbas", recurse=True):
def set_rgba_array(self, rgba_array, name="rgbas", recurse=False):
for mob in self.get_family(recurse):
mob.data[name] = np.array(rgba_array)
return self
def set_color_by_rgba_func(self, func, recurse=True):
"""
Func should take in a point in R3 and output an rgba value
"""
for mob in self.get_family(recurse):
rgba_array = [func(point) for point in mob.get_points()]
mob.set_rgba_array(rgba_array)
return self
def set_color_by_rgb_func(self, func, opacity=1, recurse=True):
"""
Func should take in a point in R3 and output an rgb value
"""
for mob in self.get_family(recurse):
rgba_array = [[*func(point), opacity] for point in mob.get_points()]
mob.set_rgba_array(rgba_array)
return self
def set_rgba_array_by_color(self, color=None, opacity=None, name="rgbas", recurse=True):
if color is not None:
rgbs = np.array([color_to_rgb(c) for c in listify(color)])
if opacity is not None:
@@ -870,8 +894,8 @@ class Mobject(object):
return self
def set_color(self, color, opacity=None, recurse=True):
self.set_rgba_array(color, opacity, recurse=False)
# Recurse to submobjects differently from how set_rgba_array
self.set_rgba_array_by_color(color, opacity, recurse=False)
# Recurse to submobjects differently from how set_rgba_array_by_color
# in case they implement set_color differently
if recurse:
for submob in self.submobjects:
@@ -879,7 +903,7 @@ class Mobject(object):
return self
def set_opacity(self, opacity, recurse=True):
self.set_rgba_array(color=None, opacity=opacity, recurse=False)
self.set_rgba_array_by_color(color=None, opacity=opacity, recurse=False)
if recurse:
for submob in self.submobjects:
submob.set_opacity(opacity, recurse=True)

View File

@@ -5,7 +5,6 @@ from manimlib.mobject.types.vectorized_mobject import VGroup
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.iterables import list_difference_update
from manimlib.utils.simple_functions import fdiv
from manimlib.utils.space_ops import normalize
@@ -144,7 +143,7 @@ class NumberLine(Line):
direction=direction,
buff=buff
)
if x < 0 and self.line_to_number_direction[0] == 0:
if x < 0 and direction[0] == 0:
# Align without the minus sign
num_mob.shift(num_mob[0].get_width() * LEFT / 2)
return num_mob
@@ -155,10 +154,11 @@ class NumberLine(Line):
kwargs["font_size"] = font_size
if excluding is None:
excluding = self.numbers_to_exclude
numbers = VGroup()
for x in x_values:
if x in self.numbers_to_exclude:
continue
if excluding is not None and x in excluding:
continue
numbers.add(self.get_number_mobject(x, **kwargs))

View File

@@ -128,10 +128,11 @@ class DecimalNumber(VMobject):
def set_value(self, number):
move_to_point = self.get_edge_center(self.edge_to_fix)
style = self.get_style()
old_submobjects = self.submobjects
self.set_submobjects_from_number(number)
self.move_to(move_to_point, self.edge_to_fix)
self.set_style(**style)
for sm1, sm2 in zip(self.submobjects, old_submobjects):
sm1.match_style(sm2)
return self
def scale(self, scale_factor, **kwargs):

View File

@@ -64,16 +64,17 @@ class BackgroundRectangle(SurroundingRectangle):
class Cross(VGroup):
CONFIG = {
"stroke_color": RED,
"stroke_width": 6,
"stroke_width": [0, 6, 0],
}
def __init__(self, mobject, **kwargs):
VGroup.__init__(self,
Line(UP + LEFT, DOWN + RIGHT),
Line(UP + RIGHT, DOWN + LEFT),
)
super().__init__(
Line(UL, DR),
Line(UR, DL),
)
self.insert_n_curves(2)
self.replace(mobject, stretch=True)
self.set_stroke(self.stroke_color, self.stroke_width)
self.set_stroke(self.stroke_color, width=self.stroke_width)
class Underline(Line):

View File

@@ -8,6 +8,7 @@ from manimlib.animation.growing import GrowFromCenter
from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.svg.tex_mobject import SingleStringTex
from manimlib.mobject.svg.tex_mobject import TexText
from manimlib.mobject.svg.text_mobject import Text
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.config_ops import digest_config
from manimlib.utils.space_ops import get_norm
@@ -61,9 +62,10 @@ class Brace(SingleStringTex):
mob.shift(self.get_direction() * shift_distance)
return self
def get_text(self, *text, **kwargs):
text_mob = TexText(*text)
self.put_at_tip(text_mob, **kwargs)
def get_text(self, text, **kwargs):
buff = kwargs.pop("buff", SMALL_BUFF)
text_mob = Text(text, **kwargs)
self.put_at_tip(text_mob, buff=buff)
return text_mob
def get_tex(self, *tex, **kwargs):

View File

@@ -41,7 +41,6 @@ class Exmark(TexText):
class Lightbulb(SVGMobject):
CONFIG = {
"file_name": "lightbulb",
"height": 1,
"stroke_color": YELLOW,
"stroke_width": 3,
@@ -49,6 +48,9 @@ class Lightbulb(SVGMobject):
"fill_opacity": 0,
}
def __init__(self, **kwargs):
super().__init__("lightbulb", **kwargs)
class Speedometer(VMobject):
CONFIG = {

View File

@@ -169,7 +169,11 @@ class SVGMobject(VMobject):
else 0.0
for key in ("cx", "cy", "rx", "ry")
]
return Circle().scale(rx * RIGHT + ry * UP).shift(x * RIGHT + y * DOWN)
result = Circle()
result.stretch(rx, 0)
result.stretch(ry, 1)
result.shift(x * RIGHT + y * DOWN)
return result
def rect_to_mobject(self, rect_element):
fill_color = rect_element.getAttribute("fill")

View File

@@ -65,6 +65,8 @@ class SingleStringTex(VMobject):
if self.math_mode:
new_tex = "\\begin{align*}\n" + new_tex + "\n\\end{align*}"
new_tex = self.alignment + "\n" + new_tex
tex_config = get_tex_config()
return tex_config["tex_body"].replace(
tex_config["text_to_replace"],
@@ -72,10 +74,7 @@ class SingleStringTex(VMobject):
)
def get_modified_expression(self, tex_string):
result = self.alignment + " " + tex_string
result = result.strip()
result = self.modify_special_strings(result)
return result
return self.modify_special_strings(tex_string.strip())
def modify_special_strings(self, tex):
tex = tex.strip()
@@ -152,10 +151,7 @@ class SingleStringTex(VMobject):
class Tex(SingleStringTex):
CONFIG = {
"arg_separator": " ",
# Note, use of isolate is largely rendered
# moot by the fact that you can surround such strings in
# {{ and }} as needed.
"arg_separator": "",
"isolate": [],
"tex_to_color_map": {},
}
@@ -172,18 +168,22 @@ class Tex(SingleStringTex):
self.organize_submobjects_left_to_right()
def break_up_tex_strings(self, tex_strings):
# Separate out anything surrounded in double braces
patterns = ["{{", "}}"]
# Separate out any strings specified in the isolate
# or tex_to_color_map lists.
patterns.extend([
substrings_to_isolate = [*self.isolate, *self.tex_to_color_map.keys()]
if len(substrings_to_isolate) == 0:
return tex_strings
patterns = (
"({})".format(re.escape(ss))
for ss in it.chain(self.isolate, self.tex_to_color_map.keys())
])
for ss in substrings_to_isolate
)
pattern = "|".join(patterns)
pieces = []
for s in tex_strings:
pieces.extend(re.split(pattern, s))
if pattern:
pieces.extend(re.split(pattern, s))
else:
pieces.append(s)
return list(filter(lambda s: s, pieces))
def break_up_by_substrings(self):

View File

@@ -2,7 +2,12 @@ import copy
import hashlib
import os
import re
import io
import typing
import warnings
import xml.etree.ElementTree as ET
import functools
from contextlib import contextmanager
from pathlib import Path
@@ -10,14 +15,14 @@ import manimpango
from manimlib.constants import *
from manimlib.mobject.geometry import Dot
from manimlib.mobject.svg.svg_mobject import SVGMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.config_ops import digest_config
from manimlib.utils.customization import get_customization
from manimlib.utils.directories import get_downloads_dir, get_text_dir
from manimpango import PangoUtils
from manimpango import TextSetting
TEXT_MOB_SCALE_FACTOR = 0.001048
from manimpango import PangoUtils, TextSetting, MarkupUtils
TEXT_MOB_SCALE_FACTOR = 0.0076
DEFAULT_LINE_SPACING_SCALE = 0.3
class Text(SVGMobject):
CONFIG = {
@@ -29,7 +34,7 @@ class Text(SVGMobject):
"font": '',
"gradient": None,
"lsh": -1,
"size": 1,
"size": None,
"font_size": 48,
"tab_width": 4,
"slant": NORMAL,
@@ -45,7 +50,17 @@ class Text(SVGMobject):
def __init__(self, text, **config):
self.full2short(config)
digest_config(self, config)
self.lsh = self.size if self.lsh == -1 else self.lsh
if self.size:
warnings.warn(
"self.size has been deprecated and will "
"be removed in future.",
DeprecationWarning
)
self.font_size = self.size
if self.lsh == -1:
self.lsh = self.font_size + self.font_size * DEFAULT_LINE_SPACING_SCALE
else:
self.lsh = self.font_size + self.font_size * self.lsh
text_without_tabs = text
if text.find('\t') != -1:
text_without_tabs = text.replace('\t', ' ' * self.tab_width)
@@ -66,7 +81,7 @@ class Text(SVGMobject):
# anti-aliasing
if self.height is None:
self.scale(TEXT_MOB_SCALE_FACTOR * self.font_size)
self.scale(TEXT_MOB_SCALE_FACTOR)
def remove_empty_path(self, file_name):
with open(file_name, 'r') as fpr:
@@ -100,6 +115,19 @@ class Text(SVGMobject):
index = self.text.find(word, index + len(word))
return indexes
def get_parts_by_text(self, word):
return VGroup(*(
self[i:j]
for i, j in self.find_indexes(word)
))
def get_part_by_text(self, word):
parts = self.get_parts_by_text(word)
if len(parts) > 0:
return parts[0]
else:
return None
def full2short(self, config):
for kwargs in [config, self.CONFIG]:
if kwargs.__contains__('line_spacing_height'):
@@ -130,7 +158,7 @@ class Text(SVGMobject):
def text2hash(self):
settings = self.font + self.slant + self.weight
settings += str(self.t2f) + str(self.t2s) + str(self.t2w)
settings += str(self.lsh) + str(self.size)
settings += str(self.lsh) + str(self.font_size)
id_str = self.text + settings
hasher = hashlib.sha256()
hasher.update(id_str.encode())
@@ -184,8 +212,8 @@ class Text(SVGMobject):
def text2svg(self):
# anti-aliasing
size = self.size * 10
lsh = self.lsh * 10
size = self.font_size
lsh = self.lsh
if self.font == '':
self.font = get_customization()['style']['font']
@@ -196,8 +224,8 @@ class Text(SVGMobject):
if os.path.exists(file_name):
return file_name
settings = self.text2settings()
width = 600
height = 400
width = DEFAULT_PIXEL_WIDTH
height = DEFAULT_PIXEL_HEIGHT
disable_liga = self.disable_ligatures
return manimpango.text2svg(
settings,
@@ -212,6 +240,238 @@ class Text(SVGMobject):
self.text,
)
class MarkupText(SVGMobject):
CONFIG = {
# Mobject
"color": WHITE,
"height": None,
# Text
"font": '',
"font_size": 48,
"lsh": None,
"justify": False,
"slant": NORMAL,
"weight": NORMAL,
"tab_width": 4,
"gradient": None,
"disable_ligatures": True,
}
def __init__(self, text, **config):
digest_config(self, config)
self.text = f'<span>{text}</span>'
self.original_text = self.text
self.text_for_parsing = self.text
text_without_tabs = text
if "\t" in text:
text_without_tabs = text.replace("\t", " " * self.tab_width)
try:
colormap = self.extract_color_tags()
gradientmap = self.extract_gradient_tags()
except ET.ParseError:
# let pango handle that error
pass
validate_error = MarkupUtils.validate(self.text)
if validate_error:
raise ValueError(validate_error)
file_name = self.text2svg()
PangoUtils.remove_last_M(file_name)
super().__init__(
file_name,
**config,
)
self.chars = self.get_group_class()(*self.submobjects)
self.text = text_without_tabs.replace(" ", "").replace("\n", "")
if self.gradient:
self.set_color_by_gradient(*self.gradient)
for col in colormap:
self.chars[
col["start"]
- col["start_offset"] : col["end"]
- col["start_offset"]
- col["end_offset"]
].set_color(self._parse_color(col["color"]))
for grad in gradientmap:
self.chars[
grad["start"]
- grad["start_offset"] : grad["end"]
- grad["start_offset"]
- grad["end_offset"]
].set_color_by_gradient(
*(self._parse_color(grad["from"]), self._parse_color(grad["to"]))
)
# anti-aliasing
if self.height is None:
self.scale(TEXT_MOB_SCALE_FACTOR)
def text2hash(self):
"""Generates ``sha256`` hash for file name."""
settings = (
"MARKUPPANGO" + self.font + self.slant + self.weight + self.color
) # to differentiate from classical Pango Text
settings += str(self.lsh) + str(self.font_size)
settings += str(self.disable_ligatures)
settings += str(self.justify)
id_str = self.text + settings
hasher = hashlib.sha256()
hasher.update(id_str.encode())
return hasher.hexdigest()[:16]
def text2svg(self):
"""Convert the text to SVG using Pango."""
size = self.font_size
dir_name = get_text_dir()
disable_liga = self.disable_ligatures
if not os.path.exists(dir_name):
os.makedirs(dir_name)
hash_name = self.text2hash()
file_name = os.path.join(dir_name, hash_name) + ".svg"
if os.path.exists(file_name):
return file_name
extra_kwargs = {}
extra_kwargs['justify'] = self.justify
extra_kwargs['pango_width'] = DEFAULT_PIXEL_WIDTH - 100
if self.lsh:
extra_kwargs['line_spacing']=self.lsh
return MarkupUtils.text2svg(
f'<span foreground="{self.color}">{self.text}</span>',
self.font,
self.slant,
self.weight,
size,
0, # empty parameter
disable_liga,
file_name,
START_X,
START_Y,
DEFAULT_PIXEL_WIDTH, # width
DEFAULT_PIXEL_HEIGHT, # height
**extra_kwargs
)
def _parse_color(self, col):
"""Parse color given in ``<color>`` or ``<gradient>`` tags."""
if re.match("#[0-9a-f]{6}", col):
return col
else:
return globals()[col.upper()] # this is hacky
@functools.lru_cache(10)
def get_text_from_markup(self, element=None):
if not element:
element = ET.fromstring(self.text_for_parsing)
final_text = ''
for i in element.itertext():
final_text += i
return final_text
def extract_color_tags(self, text=None, colormap = None):
"""Used to determine which parts (if any) of the string should be formatted
with a custom color.
Removes the ``<color>`` tag, as it is not part of Pango's markup and would cause an error.
Note: Using the ``<color>`` tags is deprecated. As soon as the legacy syntax is gone, this function
will be removed.
"""
if not text:
text = self.text_for_parsing
if not colormap:
colormap = list()
elements = ET.fromstring(text)
text_from_markup = self.get_text_from_markup()
final_xml = ET.fromstring(f'<span>{elements.text if elements.text else ""}</span>')
def get_color_map(elements):
for element in elements:
if element.tag == 'color':
element_text = self.get_text_from_markup(element)
start = text_from_markup.find(element_text)
end = start + len(element_text)
offsets = element.get('offset').split(",") if element.get('offset') else [0]
start_offset = int(offsets[0]) if offsets[0] else 0
end_offset = int(offsets[1]) if len(offsets) == 2 and offsets[1] else 0
colormap.append(
{
"start": start,
"end": end,
"color": element.get('col'),
"start_offset": start_offset,
"end_offset": end_offset,
}
)
_elements_list = list(element.iter())
if len(_elements_list) <= 1:
final_xml.append(ET.fromstring(f'<span>{element.text if element.text else ""}</span>'))
else:
final_xml.append(_elements_list[-1])
else:
if len(list(element.iter())) == 1:
final_xml.append(element)
else:
get_color_map(element)
get_color_map(elements)
with io.BytesIO() as f:
tree = ET.ElementTree()
tree._setroot(final_xml)
tree.write(f)
self.text = f.getvalue().decode()
self.text_for_parsing = self.text # gradients will use it
return colormap
def extract_gradient_tags(self, text=None,gradientmap=None):
"""Used to determine which parts (if any) of the string should be formatted
with a gradient.
Removes the ``<gradient>`` tag, as it is not part of Pango's markup and would cause an error.
"""
if not text:
text = self.text_for_parsing
if not gradientmap:
gradientmap = list()
elements = ET.fromstring(text)
text_from_markup = self.get_text_from_markup()
final_xml = ET.fromstring(f'<span>{elements.text if elements.text else ""}</span>')
def get_gradient_map(elements):
for element in elements:
if element.tag == 'gradient':
element_text = self.get_text_from_markup(element)
start = text_from_markup.find(element_text)
end = start + len(element_text)
offsets = element.get('offset').split(",") if element.get('offset') else [0]
start_offset = int(offsets[0]) if offsets[0] else 0
end_offset = int(offsets[1]) if len(offsets) == 2 and offsets[1] else 0
gradientmap.append(
{
"start": start,
"end": end,
"from": element.get('from'),
"to": element.get('to'),
"start_offset": start_offset,
"end_offset": end_offset,
}
)
_elements_list = list(element.iter())
if len(_elements_list) == 1:
final_xml.append(ET.fromstring(f'<span>{element.text if element.text else ""}</span>'))
else:
final_xml.append(_elements_list[-1])
else:
if len(list(element.iter())) == 1:
final_xml.append(element)
else:
get_gradient_map(element)
get_gradient_map(elements)
with io.BytesIO() as f:
tree = ET.ElementTree()
tree._setroot(final_xml)
tree.write(f)
self.text = f.getvalue().decode()
return gradientmap
def __repr__(self):
return f"MarkupText({repr(self.original_text)})"
@contextmanager
def register_font(font_file: typing.Union[str, Path]):
"""Temporarily add a font file to Pango's search path.
@@ -240,8 +500,8 @@ def register_font(font_file: typing.Union[str, Path]):
-----
This method of adding font files also works with :class:`CairoText`.
.. important ::
This method isn't available for macOS. Using this
method on macOS will raise an :class:`AttributeError`.
This method is available for macOS for ``ManimPango>=v0.2.3``. Using this
method with previous releases will raise an :class:`AttributeError` on macOS.
"""
input_folder = Path(get_downloads_dir()).parent.resolve()

View File

@@ -14,7 +14,7 @@ class SurfaceMesh(VGroup):
CONFIG = {
"resolution": (21, 21),
"stroke_width": 1,
"normal_nudge": 1e-2,
"normal_nudge": 1e-3,
"depth_test": True,
"flat_stroke": False,
}
@@ -35,7 +35,7 @@ class SurfaceMesh(VGroup):
points, du_points, dv_points = uv_surface.get_surface_points_and_nudged_points()
normals = uv_surface.get_unit_normals()
nudge = 1e-2
nudge = self.normal_nudge
nudged_points = points + nudge * normals
for ui in u_indices:

View File

@@ -2,11 +2,12 @@ import numpy as np
import moderngl
from manimlib.constants import GREY_C
from manimlib.constants import ORIGIN
from manimlib.mobject.types.point_cloud_mobject import PMobject
from manimlib.utils.iterables import resize_preserving_order
DEFAULT_DOT_CLOUD_RADIUS = 0.05
DEFAULT_DOT_RADIUS = 0.05
DEFAULT_GRID_HEIGHT = 6
DEFAULT_BUFF_RATIO = 0.5
@@ -15,7 +16,7 @@ class DotCloud(PMobject):
CONFIG = {
"color": GREY_C,
"opacity": 1,
"radius": DEFAULT_DOT_CLOUD_RADIUS,
"radius": DEFAULT_DOT_RADIUS,
"shader_folder": "true_dot",
"render_primitive": moderngl.POINTS,
"shader_dtype": [
@@ -106,3 +107,8 @@ class DotCloud(PMobject):
self.read_data_to_shader(shader_data, "radius", "radii")
self.read_data_to_shader(shader_data, "color", "rgbas")
return shader_data
class TrueDot(DotCloud):
def __init__(self, center=ORIGIN, radius=DEFAULT_DOT_RADIUS, **kwargs):
super().__init__(points=[center], radius=radius, **kwargs)

View File

@@ -181,9 +181,9 @@ class Surface(Mobject):
class ParametricSurface(Surface):
def __init__(self, uv_func, **kwargs):
def __init__(self, uv_func, u_range=(0, 1), v_range=(0, 1), **kwargs):
self.passed_uv_func = uv_func
super().__init__(**kwargs)
super().__init__(u_range=u_range, v_range=v_range, **kwargs)
def uv_func(self, u, v):
return self.passed_uv_func(u, v)

View File

@@ -52,7 +52,7 @@ class VMobject(Mobject):
"fill_shader_folder": "quadratic_bezier_fill",
# Could also be "bevel", "miter", "round"
"joint_type": "auto",
"flat_stroke": True,
"flat_stroke": False,
"render_primitive": moderngl.TRIANGLES,
"triangulation_locked": False,
"fill_dtype": [
@@ -106,24 +106,42 @@ class VMobject(Mobject):
self.set_flat_stroke(self.flat_stroke)
return self
def set_rgba_array(self, rgba_array, name=None, recurse=False):
if name is None:
names = ["fill_rgba", "stroke_rgba"]
else:
names = [name]
for name in names:
super().set_rgba_array(rgba_array, name, recurse)
return self
def set_fill(self, color=None, opacity=None, recurse=True):
self.set_rgba_array(color, opacity, 'fill_rgba', recurse)
self.set_rgba_array_by_color(color, opacity, 'fill_rgba', recurse)
return self
def set_stroke(self, color=None, width=None, opacity=None, background=None, recurse=True):
self.set_rgba_array(color, opacity, 'stroke_rgba', recurse)
self.set_rgba_array_by_color(color, opacity, 'stroke_rgba', recurse)
if width is not None:
for mob in self.get_family(recurse):
mob.data['stroke_width'] = np.array([
[width] for width in listify(width)
])
if isinstance(width, np.ndarray):
arr = width.reshape((len(width), 1))
else:
arr = np.array([[w] for w in listify(width)])
mob.data['stroke_width'] = arr
if background is not None:
for mob in self.get_family(recurse):
mob.draw_stroke_behind_fill = background
return self
def align_stroke_width_data_to_points(self, recurse=True):
for mob in self.get_family(recurse):
mob.data["stroke_width"] = resize_with_interpolation(
mob.data["stroke_width"], len(mob.get_points())
)
def set_style(self,
fill_color=None,
fill_opacity=None,
@@ -226,7 +244,7 @@ class VMobject(Mobject):
return self.data['stroke_rgba'][:, 3]
def get_stroke_widths(self):
return self.data['stroke_width']
return self.data['stroke_width'][:, 0]
# TODO, it's weird for these to return the first of various lists
# rather than the full information
@@ -259,7 +277,7 @@ class VMobject(Mobject):
return self.get_fill_color()
def has_stroke(self):
return any(self.get_stroke_widths()) and any(self.get_stroke_opacities())
return self.get_stroke_widths().any() and self.get_stroke_opacities().any()
def has_fill(self):
return any(self.get_fill_opacities())
@@ -830,8 +848,8 @@ class VMobject(Mobject):
old_points = self.get_points()
func(self, *args, **kwargs)
if not np.all(self.get_points() == old_points):
self.refresh_triangulation()
self.refresh_unit_normal()
self.refresh_triangulation()
return wrapper
@triggers_refreshed_triangulation
@@ -852,9 +870,10 @@ class VMobject(Mobject):
self.make_approximately_smooth()
return self
@triggers_refreshed_triangulation
def flip(self, *args, **kwargs):
super().flip(*args, **kwargs)
self.refresh_unit_normal()
self.refresh_triangulation()
return self
# For shaders

View File

@@ -1,66 +1,32 @@
import numpy as np
import os
import itertools as it
from PIL import Image
import random
from manimlib.constants import *
from manimlib.animation.composition import AnimationGroup
from manimlib.animation.indication import ShowPassingFlash
from manimlib.mobject.geometry import Vector
from manimlib.animation.indication import VShowPassingFlash
from manimlib.mobject.geometry import Arrow
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.bezier import inverse_interpolate
from manimlib.utils.bezier import interpolate
from manimlib.utils.color import color_to_rgb
from manimlib.utils.color import rgb_to_color
from manimlib.utils.color import get_colormap_list
from manimlib.utils.config_ops import merge_dicts_recursively
from manimlib.utils.config_ops import digest_config
from manimlib.utils.rate_functions import linear
from manimlib.utils.simple_functions import sigmoid
from manimlib.utils.space_ops import get_norm
# from manimlib.utils.space_ops import normalize
DEFAULT_SCALAR_FIELD_COLORS = [BLUE_E, GREEN, YELLOW, RED]
def get_colored_background_image(scalar_field_func,
number_to_rgb_func,
pixel_height=DEFAULT_PIXEL_HEIGHT,
pixel_width=DEFAULT_PIXEL_WIDTH):
ph = pixel_height
pw = pixel_width
fw = FRAME_WIDTH
fh = FRAME_HEIGHT
points_array = np.zeros((ph, pw, 3))
x_array = np.linspace(-fw / 2, fw / 2, pw)
x_array = x_array.reshape((1, len(x_array)))
x_array = x_array.repeat(ph, axis=0)
y_array = np.linspace(fh / 2, -fh / 2, ph)
y_array = y_array.reshape((len(y_array), 1))
y_array.repeat(pw, axis=1)
points_array[:, :, 0] = x_array
points_array[:, :, 1] = y_array
scalars = np.apply_along_axis(scalar_field_func, 2, points_array)
rgb_array = number_to_rgb_func(scalars.flatten()).reshape((ph, pw, 3))
return Image.fromarray((rgb_array * 255).astype('uint8'))
def get_rgb_gradient_function(min_value=0, max_value=1,
colors=[BLUE, RED],
flip_alphas=True, # Why?
):
rgbs = np.array(list(map(color_to_rgb, colors)))
def get_vectorized_rgb_gradient_function(min_value, max_value, color_map):
rgbs = np.array(get_colormap_list(color_map))
def func(values):
alphas = inverse_interpolate(
min_value, max_value, np.array(values)
)
alphas = np.clip(alphas, 0, 1)
# if flip_alphas:
# alphas = 1 - alphas
scaled_alphas = alphas * (len(rgbs) - 1)
indices = scaled_alphas.astype(int)
next_indices = np.clip(indices + 1, 0, len(rgbs) - 1)
@@ -71,29 +37,9 @@ def get_rgb_gradient_function(min_value=0, max_value=1,
return func
def get_color_field_image_file(scalar_func,
min_value=0, max_value=2,
colors=DEFAULT_SCALAR_FIELD_COLORS
):
# try_hash
np.random.seed(0)
sample_inputs = 5 * np.random.random(size=(10, 3)) - 10
sample_outputs = np.apply_along_axis(scalar_func, 1, sample_inputs)
func_hash = hash(
str(min_value) + str(max_value) + str(colors) + str(sample_outputs)
)
file_name = "%d.png" % func_hash
full_path = os.path.join(RASTER_IMAGE_DIR, file_name)
if not os.path.exists(full_path):
print("Rendering color field image " + str(func_hash))
rgb_gradient_func = get_rgb_gradient_function(
min_value=min_value,
max_value=max_value,
colors=colors
)
image = get_colored_background_image(scalar_func, rgb_gradient_func)
image.save(full_path)
return full_path
def get_rgb_gradient_function(min_value, max_value, color_map):
vectorized_func = get_vectorized_rgb_gradient_function(min_value, max_value, color_map)
return lambda value: vectorized_func([value])[0]
def move_along_vector_field(mobject, func):
@@ -116,217 +62,200 @@ def move_submobjects_along_vector_field(mobject, func):
return mobject
def move_points_along_vector_field(mobject, func):
def move_points_along_vector_field(mobject, func, coordinate_system):
cs = coordinate_system
origin = cs.get_origin()
def apply_nudge(self, dt):
self.mobject.apply_function(
lambda p: p + func(p) * dt
mobject.apply_function(
lambda p: p + (cs.c2p(*func(*cs.p2c(p))) - origin) * dt
)
mobject.add_updater(apply_nudge)
return mobject
def get_sample_points_from_coordinate_system(coordinate_system, step_multiple):
ranges = []
for range_args in coordinate_system.get_all_ranges():
_min, _max, step = range_args
step *= step_multiple
ranges.append(np.arange(_min, _max + step, step))
return it.product(*ranges)
# Mobjects
class VectorField(VGroup):
CONFIG = {
"delta_x": 0.5,
"delta_y": 0.5,
"x_min": int(np.floor(-FRAME_WIDTH / 2)),
"x_max": int(np.ceil(FRAME_WIDTH / 2)),
"y_min": int(np.floor(-FRAME_HEIGHT / 2)),
"y_max": int(np.ceil(FRAME_HEIGHT / 2)),
"min_magnitude": 0,
"max_magnitude": 2,
"colors": DEFAULT_SCALAR_FIELD_COLORS,
"step_multiple": 0.5,
"magnitude_range": (0, 2),
"color_map": "3b1b_colormap",
# Takes in actual norm, spits out displayed norm
"length_func": lambda norm: 0.45 * sigmoid(norm),
"opacity": 1.0,
"vector_config": {},
}
def __init__(self, func, **kwargs):
def __init__(self, func, coordinate_system, **kwargs):
super().__init__(**kwargs)
self.func = func
self.rgb_gradient_function = get_rgb_gradient_function(
self.min_magnitude,
self.max_magnitude,
self.colors,
flip_alphas=False
self.coordinate_system = coordinate_system
self.value_to_rgb = get_rgb_gradient_function(
*self.magnitude_range, self.color_map,
)
x_range = np.arange(
self.x_min,
self.x_max + self.delta_x,
self.delta_x
)
y_range = np.arange(
self.y_min,
self.y_max + self.delta_y,
self.delta_y
)
for x, y in it.product(x_range, y_range):
point = x * RIGHT + y * UP
self.add(self.get_vector(point))
self.set_opacity(self.opacity)
def get_vector(self, point, **kwargs):
output = np.array(self.func(point))
norm = get_norm(output)
if norm == 0:
output *= 0
else:
output *= self.length_func(norm) / norm
vector_config = dict(self.vector_config)
vector_config.update(kwargs)
vect = Vector(output, **vector_config)
vect.shift(point)
fill_color = rgb_to_color(
self.rgb_gradient_function(np.array([norm]))[0]
samples = get_sample_points_from_coordinate_system(
coordinate_system, self.step_multiple
)
vect.set_color(fill_color)
self.add(*(
self.get_vector(coords)
for coords in samples
))
def get_vector(self, coords, **kwargs):
vector_config = merge_dicts_recursively(
self.vector_config,
kwargs
)
output = np.array(self.func(*coords))
norm = get_norm(output)
if norm > 0:
output *= self.length_func(norm) / norm
origin = self.coordinate_system.get_origin()
_input = self.coordinate_system.c2p(*coords)
_output = self.coordinate_system.c2p(*output)
vect = Arrow(
origin, _output, buff=0,
**vector_config
)
vect.shift(_input - origin)
vect.set_rgba_array([[*self.value_to_rgb(norm), self.opacity]])
return vect
class StreamLines(VGroup):
CONFIG = {
# TODO, this is an awkward way to inherit
# defaults to a method.
"start_points_generator_config": {},
# Config for choosing start points
"x_min": -8,
"x_max": 8,
"y_min": -5,
"y_max": 5,
"delta_x": 0.5,
"delta_y": 0.5,
"step_multiple": 0.5,
"n_repeats": 1,
"noise_factor": None,
# Config for drawing lines
"dt": 0.05,
"virtual_time": 3,
"n_anchors_per_line": 100,
"arc_len": 3,
"max_time_steps": 200,
"n_samples_per_line": 10,
"cutoff_norm": 15,
# Style info
"stroke_width": 1,
"stroke_color": WHITE,
"color_by_arc_length": True,
# Min and max arc lengths meant to define
# the color range, should color_by_arc_length be True
"min_arc_length": 0,
"max_arc_length": 12,
"color_by_magnitude": False,
# Min and max magnitudes meant to define
# the color range, should color_by_magnitude be True
"min_magnitude": 0.5,
"max_magnitude": 1.5,
"colors": DEFAULT_SCALAR_FIELD_COLORS,
"cutoff_norm": 15,
"stroke_opacity": 1,
"color_by_magnitude": True,
"magnitude_range": (0, 2.0),
"taper_stroke_width": False,
"color_map": "3b1b_colormap",
}
def __init__(self, func, **kwargs):
VGroup.__init__(self, **kwargs)
def __init__(self, func, coordinate_system, **kwargs):
super().__init__(**kwargs)
self.func = func
dt = self.dt
self.coordinate_system = coordinate_system
self.draw_lines()
self.init_style()
start_points = self.get_start_points(
**self.start_points_generator_config
)
for point in start_points:
def point_func(self, point):
in_coords = self.coordinate_system.p2c(point)
out_coords = self.func(*in_coords)
return self.coordinate_system.c2p(*out_coords)
def draw_lines(self):
lines = []
origin = self.coordinate_system.get_origin()
for point in self.get_start_points():
points = [point]
for t in np.arange(0, self.virtual_time, dt):
total_arc_len = 0
time = 0
for x in range(self.max_time_steps):
time += self.dt
last_point = points[-1]
points.append(last_point + dt * func(last_point))
new_point = last_point + self.dt * (self.point_func(last_point) - origin)
points.append(new_point)
total_arc_len += get_norm(new_point - last_point)
if get_norm(last_point) > self.cutoff_norm:
break
if total_arc_len > self.arc_len:
break
line = VMobject()
step = max(1, int(len(points) / self.n_anchors_per_line))
line.set_points_smoothly(points[::step])
self.add(line)
self.set_stroke(self.stroke_color, self.stroke_width)
if self.color_by_arc_length:
len_to_rgb = get_rgb_gradient_function(
self.min_arc_length,
self.max_arc_length,
colors=self.colors,
)
for line in self:
arc_length = line.get_arc_length()
rgb = len_to_rgb([arc_length])[0]
color = rgb_to_color(rgb)
line.set_color(color)
elif self.color_by_magnitude:
image_file = get_color_field_image_file(
lambda p: get_norm(func(p)),
min_value=self.min_magnitude,
max_value=self.max_magnitude,
colors=self.colors,
)
self.color_using_background_image(image_file)
line.virtual_time = time
step = max(1, int(len(points) / self.n_samples_per_line))
line.set_points_as_corners(points[::step])
line.make_approximately_smooth()
lines.append(line)
self.set_submobjects(lines)
def get_start_points(self):
x_min = self.x_min
x_max = self.x_max
y_min = self.y_min
y_max = self.y_max
delta_x = self.delta_x
delta_y = self.delta_y
n_repeats = self.n_repeats
cs = self.coordinate_system
sample_coords = get_sample_points_from_coordinate_system(
cs, self.step_multiple,
)
noise_factor = self.noise_factor
if noise_factor is None:
noise_factor = delta_y / 2
noise_factor = cs.x_range[2] * self.step_multiple * 0.5
return np.array([
x * RIGHT + y * UP + noise_factor * np.random.random(3)
for n in range(n_repeats)
for x in np.arange(x_min, x_max + delta_x, delta_x)
for y in np.arange(y_min, y_max + delta_y, delta_y)
cs.c2p(*coords) + noise_factor * np.random.random(3)
for n in range(self.n_repeats)
for coords in sample_coords
])
# TODO: Make it so that you can have a group of stream_lines
# varying in response to a changing vector field, and still
# animate the resulting flow
class ShowPassingFlashWithThinningStrokeWidth(AnimationGroup):
CONFIG = {
"n_segments": 10,
"time_width": 0.1,
"remover": True
}
def __init__(self, vmobject, **kwargs):
digest_config(self, kwargs)
max_stroke_width = vmobject.get_stroke_width()
max_time_width = kwargs.pop("time_width", self.time_width)
AnimationGroup.__init__(self, *[
ShowPassingFlash(
vmobject.deepcopy().set_stroke(width=stroke_width),
time_width=time_width,
**kwargs
def init_style(self):
if self.color_by_magnitude:
values_to_rgbs = get_vectorized_rgb_gradient_function(
*self.magnitude_range, self.color_map,
)
for stroke_width, time_width in zip(
np.linspace(0, max_stroke_width, self.n_segments),
np.linspace(max_time_width, 0, self.n_segments)
)
])
cs = self.coordinate_system
for line in self.submobjects:
norms = [
get_norm(self.func(*cs.p2c(point)))
for point in line.get_points()
]
rgbs = values_to_rgbs(norms)
rgbas = np.zeros((len(rgbs), 4))
rgbas[:, :3] = rgbs
rgbas[:, 3] = self.stroke_opacity
line.set_rgba_array(rgbas, "stroke_rgba")
else:
self.set_stroke(self.stroke_color, opacity=self.stroke_opacity)
if self.taper_stroke_width:
width = [0, self.stroke_width, 0]
else:
width = self.stroke_width
self.set_stroke(width=width)
# TODO, this is untested after turning it from a
# ContinualAnimation into a VGroup
class AnimatedStreamLines(VGroup):
CONFIG = {
"lag_range": 4,
"line_anim_class": ShowPassingFlash,
"line_anim_class": VShowPassingFlash,
"line_anim_config": {
"run_time": 4,
# "run_time": 4,
"rate_func": linear,
"time_width": 0.3,
"time_width": 0.5,
},
}
def __init__(self, stream_lines, **kwargs):
VGroup.__init__(self, **kwargs)
super().__init__(**kwargs)
self.stream_lines = stream_lines
for line in stream_lines:
line.anim = self.line_anim_class(line, **self.line_anim_config)
line.anim = self.line_anim_class(
line,
run_time=line.virtual_time,
**self.line_anim_config,
)
line.anim.begin()
line.time = -self.lag_range * random.random()
self.add(line.anim.mobject)
@@ -339,3 +268,28 @@ class AnimatedStreamLines(VGroup):
line.time += dt
adjusted_time = max(line.time, 0) % line.anim.run_time
line.anim.update(adjusted_time / line.anim.run_time)
# TODO: This class should be deleted
class ShowPassingFlashWithThinningStrokeWidth(AnimationGroup):
CONFIG = {
"n_segments": 10,
"time_width": 0.1,
"remover": True
}
def __init__(self, vmobject, **kwargs):
digest_config(self, kwargs)
max_stroke_width = vmobject.get_stroke_width()
max_time_width = kwargs.pop("time_width", self.time_width)
AnimationGroup.__init__(self, *[
VShowPassingFlash(
vmobject.deepcopy().set_stroke(width=stroke_width),
time_width=time_width,
**kwargs
)
for stroke_width, time_width in zip(
np.linspace(0, max_stroke_width, self.n_segments),
np.linspace(max_time_width, 0, self.n_segments)
)
])

View File

@@ -10,6 +10,7 @@ from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.scene.scene import Scene
import itertools as it
class CountingScene(Scene):
CONFIG = {
@@ -219,7 +220,7 @@ class CountInTernary(PowerCounter):
def construct(self):
self.count(27)
# def get_template_configuration(self):
# def get_template_configuration(self, place):
# return [ORIGIN, UP]
@@ -233,7 +234,7 @@ class CountInBinaryTo256(PowerCounter):
def construct(self):
self.count(128, 0.3)
def get_template_configuration(self):
def get_template_configuration(self, place):
return [ORIGIN, UP]

View File

@@ -74,12 +74,14 @@ class SwitchOff(LaggedStartMap):
class Lighthouse(SVGMobject):
CONFIG = {
"file_name": "lighthouse",
"height": LIGHTHOUSE_HEIGHT,
"fill_color": WHITE,
"fill_opacity": 1.0,
}
def __init__(self, **kwargs):
super().__init__("lighthouse", **kwargs)
def move_to(self, point):
self.next_to(point, DOWN, buff=0)

View File

@@ -50,7 +50,7 @@ class Scene(object):
self.camera = self.camera_class(**self.camera_config)
self.file_writer = SceneFileWriter(self, **self.file_writer_config)
self.mobjects = []
self.mobjects = [self.camera.frame]
self.num_plays = 0
self.time = 0
self.skip_time = 0
@@ -117,7 +117,7 @@ class Scene(object):
self.stop_skipping()
self.linger_after_completion = False
self.update_frame()
from IPython.terminal.embed import InteractiveShellEmbed
shell = InteractiveShellEmbed()
# Have the frame update after each command
@@ -272,7 +272,7 @@ class Scene(object):
times,
total=n_iterations,
leave=self.leave_progress_bars,
ascii=False if platform.system() != 'Windows' else True
ascii=True if platform.system() == 'Windows' else None
)
return time_progression
@@ -570,7 +570,7 @@ class Scene(object):
frame = self.camera.frame
if self.window.is_key_pressed(ord("z")):
factor = 1 + np.arctan(10 * offset[1])
frame.scale(factor, about_point=point)
frame.scale(1/factor, about_point=point)
else:
transform = frame.get_inverse_camera_rotation_matrix()
shift = np.dot(np.transpose(transform), offset)

View File

@@ -5,6 +5,7 @@ class ThreeDScene(Scene):
CONFIG = {
"camera_config": {
"samples": 4,
"anti_alias_width": 0,
}
}

View File

@@ -9,5 +9,5 @@ out vec4 frag_color;
void main() {
frag_color = texture(Texture, v_im_coords);
frag_color.a = v_opacity;
frag_color.a *= v_opacity;
}

View File

@@ -17,16 +17,16 @@ vec4 add_light(vec4 color,
float shadow){
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;
float camera_distance = focal_distance;
// 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;
// TODO, do we actually want this? It effectively treats surfaces as two-sided
if(dot(to_camera,unit_normal) < 0){
unit_normal *= -1;
}
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));

View File

@@ -3,6 +3,7 @@
uniform vec3 light_source_position;
uniform float gloss;
uniform float shadow;
uniform float focal_distance;
in vec3 xyz_coords;
in vec3 v_normal;

View File

@@ -6,6 +6,7 @@ uniform float num_textures;
uniform vec3 light_source_position;
uniform float gloss;
uniform float shadow;
uniform float focal_distance;
in vec3 xyz_coords;
in vec3 v_normal;

View File

@@ -4,6 +4,7 @@ uniform vec3 light_source_position;
uniform float gloss;
uniform float shadow;
uniform float anti_alias_width;
uniform float focal_distance;
in vec4 color;
in float radius;

View File

@@ -4,7 +4,9 @@ from colour import Color
import numpy as np
from manimlib.constants import WHITE
from manimlib.constants import COLORMAP_3B1B
from manimlib.utils.bezier import interpolate
from manimlib.utils.iterables import resize_with_interpolation
from manimlib.utils.simple_functions import clip_in_place
from manimlib.utils.space_ops import normalize
@@ -114,10 +116,22 @@ def get_shaded_rgb(rgb, point, unit_normal_vect, light_source):
def get_colormap_list(map_name="viridis", n_colors=9):
"""
Options for map_name:
3b1b_colormap
magma
inferno
plasma
viridis
cividis
twilight
twilight_shifted
turbo
"""
from matplotlib.cm import get_cmap
rgbs = get_cmap(map_name).colors # Make more general?
return [
rgbs[int(n)]
for n in np.linspace(0, len(rgbs) - 1, n_colors)
]
if map_name == "3b1b_colormap":
rgbs = [color_to_rgb(color) for color in COLORMAP_3B1B]
else:
rgbs = get_cmap(map_name).colors # Make more general?
return resize_with_interpolation(np.array(rgbs), n_colors)

View File

@@ -23,6 +23,7 @@ def get_text_dir():
def get_mobject_data_dir():
return guarantee_existence(os.path.join(get_temp_dir(), "mobject_data"))
def get_downloads_dir():
return guarantee_existence(os.path.join(get_temp_dir(), "manim_downloads"))

View File

@@ -10,7 +10,7 @@ def get_full_raster_image_path(image_file_name):
return find_file(
image_file_name,
directories=[get_raster_image_dir()],
extensions=[".jpg", ".png", ".gif", ""]
extensions=[".jpg", ".jpeg", ".png", ".gif", ""]
)

View File

@@ -23,6 +23,7 @@ def init_customization():
"background_color": "",
},
"window_position": "UR",
"window_monitor": 0,
"break_into_partial_movies": False,
"camera_qualities": {
"low": {

View File

@@ -98,6 +98,8 @@ def resize_preserving_order(nparray, length):
def resize_with_interpolation(nparray, length):
if len(nparray) == length:
return nparray
if length == 0:
return np.zeros((0, *nparray.shape[1:]))
cont_indices = np.linspace(0, len(nparray) - 1, length)
return np.array([
(1 - a) * nparray[lh] + a * nparray[rh]

View File

@@ -6,5 +6,5 @@ def get_full_sound_file_path(sound_file_name):
return find_file(
sound_file_name,
directories=[get_sound_dir()],
extensions=[".wav", ".mp3"]
extensions=[".wav", ".mp3", ""]
)

View File

@@ -359,77 +359,64 @@ def earclip_triangulation(verts, ring_ends):
the ends of new paths are
"""
# First, connect all the rings so that the polygon
# with holes is instead treated as a (very convex)
# polygon with one edge. Do this by drawing connections
# between rings close to each other
rings = [
list(range(e0, e1))
for e0, e1 in zip([0, *ring_ends], ring_ends)
]
attached_rings = rings[:1]
detached_rings = rings[1:]
loop_connections = dict()
while detached_rings:
i_range, j_range = [
list(filter(
# Ignore indices that are already being
# used to draw some connection
lambda i: i not in loop_connections,
it.chain(*ring_group)
))
for ring_group in (attached_rings, detached_rings)
]
def is_in(point, ring_id):
return abs(abs(get_winding_number([i - point for i in verts[rings[ring_id]]])) - 1) < 1e-5
# Closet point on the atttached rings to an estimated midpoint
# of the detached rings
tmp_j_vert = midpoint(
verts[j_range[0]],
verts[j_range[len(j_range) // 2]]
)
i = min(i_range, key=lambda i: norm_squared(verts[i] - tmp_j_vert))
# Closet point of the detached rings to the aforementioned
# point of the attached rings
j = min(j_range, key=lambda j: norm_squared(verts[i] - verts[j]))
# Recalculate i based on new j
i = min(i_range, key=lambda i: norm_squared(verts[i] - verts[j]))
def ring_area(ring_id):
ring = rings[ring_id]
s = 0
for i, j in zip(ring[1:], ring):
s += cross2d(verts[i], verts[j])
return abs(s) / 2
# Remember to connect the polygon at these points
loop_connections[i] = j
loop_connections[j] = i
# 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
# Move the ring which j belongs to from the
# attached list to the detached list
new_ring = next(filter(
lambda ring: ring[0] <= j < ring[-1],
detached_rings
))
detached_rings.remove(new_ring)
attached_rings.append(new_ring)
# First, we should know which rings are directly contained in it for each ring
# Setup linked list
after = []
end0 = 0
for end1 in ring_ends:
after.extend(range(end0 + 1, end1))
after.append(end0)
end0 = end1
right = [max(verts[rings[i], 0]) for i in range(len(rings))]
left = [min(verts[rings[i], 0]) for i in range(len(rings))]
top = [max(verts[rings[i], 1]) for i in range(len(rings))]
bottom = [min(verts[rings[i], 1]) for i in range(len(rings))]
area = [ring_area(i) for i in range(len(rings))]
# Find an ordering of indices walking around the polygon
indices = []
i = 0
for x in range(len(verts) + len(ring_ends) - 1):
# starting = False
if i in loop_connections:
j = loop_connections[i]
indices.extend([i, j])
i = after[j]
else:
indices.append(i)
i = after[i]
if i == 0:
break
# The larger ring must be outside
rings_sorted = list(range(len(rings)))
rings_sorted.sort(key=lambda x: area[x], reverse=True)
meta_indices = earcut(verts[indices, :2], [len(indices)])
return [indices[mi] for mi in meta_indices]
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))
chilren = [[] for i in rings]
for idx, i in enumerate(rings_sorted):
for j in rings_sorted[:idx][::-1]:
if is_in_fast(i, j):
chilren[j].append(i)
break
res = []
# Then, we can use earcut for each part
used = [False] * len(rings)
for i in rings_sorted:
if used[i]:
continue
v = rings[i]
ring_ends = [len(v)]
for j in chilren[i]:
used[j] = True
v += rings[j]
ring_ends.append(len(v))
res += [v[i] for i in earcut(verts[v, :2], ring_ends)]
return res

View File

@@ -15,7 +15,7 @@ class Window(PygletWindow):
cursor = True
def __init__(self, scene, size=(1280, 720), **kwargs):
super().__init__()
super().__init__(size=size)
digest_config(self, kwargs)
self.scene = scene
@@ -38,7 +38,9 @@ class Window(PygletWindow):
def find_initial_position(self, size):
custom_position = get_customization()["window_position"]
monitor = get_monitors()[get_customization()["window_monitor"]]
monitors = get_monitors()
mon_index = get_customization()["window_monitor"]
monitor = monitors[min(mon_index, len(monitors) - 1)]
window_width, window_height = size
# Position might be specified with a string of the form
# x,y for integers x and y

3
pyproject.toml Normal file
View File

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

View File

@@ -2,7 +2,6 @@ argparse
colour
numpy
Pillow
progressbar
scipy
sympy
tqdm
@@ -17,4 +16,4 @@ pyreadline; sys_platform == 'win32'
validators
ipython
PyOpenGL
manimpango>=0.2.0,<0.3.0'
manimpango>=0.2.0,<0.4.0

View File

@@ -1,23 +1,43 @@
[metadata]
name = manimgl
version = 1.0.0
version = 1.1.0
author = Grant Sanderson
author-email= grant@3blue1brown.com
summary = Animation engine for explanatory math videos
description-file = README.md
description-content-type = text/markdown; charset=UTF-8
home-page = https://github.com/3b1b/manim
author_email= grant@3blue1brown.com
description = Animation engine for explanatory math videos
long_description = file: README.md
long_description_content_type = text/markdown; charset=UTF-8
home_page = https://github.com/3b1b/manim
project_urls =
Bug Tracker = https://github.com/3b1b/manim/issues
Documentation = https://3b1b.github.io/manim/
Source Code = https://github.com/3b1b/manim
license = MIT
[files]
packages = manimlib
extra_files = requirements.txt
[options]
packages = find:
include_package_data=True
install_requires =
argparse
colour
numpy
Pillow
scipy
sympy
tqdm
mapbox-earcut
matplotlib
moderngl
moderngl_window
pydub
pyyaml
screeninfo
pyreadline; sys_platform == 'win32'
validators
ipython
PyOpenGL
manimpango>=0.2.0,<0.4.0
[entry_points]
[options.entry_points]
console_scripts =
manimgl = manimlib.__main__:main
manim-render = manimlib.__main__:main

View File

@@ -1,8 +0,0 @@
#!/usr/bin/env python
from setuptools import setup
setup(
setup_requires=['pbr'],
pbr=True,
)