489 Commits

Author SHA1 Message Date
TonyCrane
3883f57bf8 release: ready to release v1.4.1 2022-02-04 11:03:37 +08:00
TonyCrane
d2e0811285 import Iterable from collections.abc instead of collections 2022-02-04 10:55:59 +08:00
Grant Sanderson
1e2a6ffb8a Merge pull request #1724 from TurkeyBilly/patch-2
Temporarily fix boolean operation bug
2022-01-31 08:06:27 -08:00
Bill Xi
56e5696163 Update boolean_ops.py 2022-01-31 23:29:36 +08:00
TonyCrane
1ec00629a5 release: ready to release v1.4.0 2022-01-30 13:06:22 +08:00
TonyCrane
aa6335cd90 docs: update changelog for #1719 #1720 #1721 and #1723 2022-01-30 13:00:57 +08:00
YishiMichael
7093f7d02d Some small refactors to MTex (#1723) 2022-01-30 12:42:35 +08:00
Grant Sanderson
fad9ed2df7 Merge pull request #1720 from YishiMichael/master
Handle explicit color-related commands
2022-01-29 08:01:48 -08:00
YishiMichael
725155409b Some small refactors 2022-01-29 21:06:54 +08:00
YishiMichael
a6675eb043 Some small refactors 2022-01-29 14:35:27 +08:00
YishiMichael
5d2dcec307 Fix color-related bugs 2022-01-29 14:05:52 +08:00
Grant Sanderson
f60dc7cd07 Merge pull request #1719 from 3b1b/parse-style
Parse style from <style> tag and Add support to <line> tag
2022-01-27 13:33:14 -08:00
YishiMichael
6c39cac62b Remove redundant attribute 2022-01-28 01:19:02 +08:00
鹤翔万里
2bd25a55fa add back override parameter to init_colors 2022-01-28 00:20:13 +08:00
鹤翔万里
0e4edfdd79 improve config helper (#1721) 2022-01-28 00:16:19 +08:00
YishiMichael
277256a407 Merge branch '3b1b:master' into master 2022-01-27 23:11:19 +08:00
YishiMichael
831b7d455c Handle explicit color-related commands 2022-01-27 23:09:05 +08:00
TonyCrane
1d14a23af9 docs: update changelog for #1712 #1717 and #1716 2022-01-27 22:21:40 +08:00
TonyCrane
dffa70ea15 docs: update changelog for #1704 and #1709 2022-01-27 22:09:57 +08:00
TonyCrane
31976063df add dependency cssselect2 2022-01-27 17:31:14 +08:00
TonyCrane
aa135280ac support <line> tag in SVG 2022-01-27 17:23:58 +08:00
TonyCrane
f0160822ba fix bug of ref map 2022-01-27 17:17:19 +08:00
TonyCrane
48e07d1817 parse style attribute using tinycss 2022-01-27 17:16:52 +08:00
TonyCrane
3ef5899a24 some cleanups 2022-01-27 16:43:45 +08:00
TonyCrane
f895455264 add parser for <style> tag of SVG 2022-01-27 16:37:51 +08:00
Grant Sanderson
3baa14103e Merge pull request #1716 from YishiMichael/master
Refactor MTex and some clean-ups
2022-01-26 08:56:44 -08:00
Grant Sanderson
c315300ff1 Merge branch 'master' into master 2022-01-26 08:54:18 -08:00
Grant Sanderson
3b17d6d0eb Merge pull request #1718 from 3b1b/text-fix
Text fix
2022-01-26 08:21:36 -08:00
Grant Sanderson
8a29de5ef0 Override style for Text 2022-01-26 08:21:00 -08:00
Grant Sanderson
ecb729850a Small style fixes 2022-01-26 08:20:45 -08:00
Grant Sanderson
a770291053 Include style in MTex.get_mobjects_from 2022-01-26 08:20:38 -08:00
Grant Sanderson
27c666fab5 Merge pull request #1717 from 3b1b/svg-style
Parse and generate style for SVG
2022-01-26 07:56:03 -08:00
YishiMichael
942a7e71b8 Update MTex 2022-01-26 23:46:13 +08:00
TonyCrane
ebb75d1235 cached SVGMobject in SingleStringTex with default color 2022-01-26 20:37:44 +08:00
TonyCrane
9af23415a2 synchronize SingleStringTex's color to SVGMobject 2022-01-26 20:20:48 +08:00
TonyCrane
19778e405a some cleanups 2022-01-26 19:55:47 +08:00
TonyCrane
833e40c2d4 fix default style 2022-01-26 19:50:27 +08:00
TonyCrane
9df53b8a18 fix the bug of M command with more than 2 args 2022-01-26 14:05:01 +08:00
TonyCrane
ff86b0e378 fix the bug of outdated relative_point after command Z 2022-01-26 13:56:42 +08:00
TonyCrane
92adcd75d4 add style support to svg 2022-01-26 13:53:53 +08:00
YishiMichael
240f5020b4 Add back default_config.yml 2022-01-26 13:21:27 +08:00
YishiMichael
e8205a5049 Some refactors for MTex 2022-01-26 13:03:14 +08:00
TonyCrane
6c8dd14adc some clean 2022-01-26 11:00:57 +08:00
Grant Sanderson
07f84e2676 Merge pull request #1712 from 3b1b/fix-svg
Improve handling of SVG transform and Some refactors
2022-01-25 13:26:40 -08:00
TonyCrane
8db1164ece some refactors 2022-01-25 21:48:04 +08:00
TonyCrane
790bf0a104 fix typo 2022-01-25 20:25:30 +08:00
TonyCrane
8205edcc4c fix a small bug 2022-01-25 20:13:20 +08:00
TonyCrane
05b3c9852e fix add_smooth_cubic_curve_to when have only one point 2022-01-25 20:06:00 +08:00
TonyCrane
925f2e123f add comments 2022-01-25 19:54:19 +08:00
TonyCrane
565763a2ff reconstruct path parser 2022-01-25 19:44:42 +08:00
TonyCrane
6a74c241b8 fix bug of node which is not an element 2022-01-25 16:28:23 +08:00
TonyCrane
416cc8e6d5 add warning for unsupported element type 2022-01-25 14:41:11 +08:00
TonyCrane
d694aed452 add support for skewX and skewY transform 2022-01-25 14:40:02 +08:00
TonyCrane
11379283aa add support for rotate transform 2022-01-25 14:29:47 +08:00
TonyCrane
dd13559b11 replace warnings.warn with log.warning 2022-01-25 14:09:05 +08:00
TonyCrane
1658438fef allow Mobject.scale receive iterable scale_factor 2022-01-25 14:05:32 +08:00
TonyCrane
f4eb2724c5 refactor SVGMobject.handle_transforms 2022-01-25 14:04:35 +08:00
TonyCrane
33f720c73a fix typo 2022-01-25 13:15:53 +08:00
TonyCrane
bbb4fa155c fix the depth of svg tag 2022-01-25 13:14:19 +08:00
Grant Sanderson
2318c9e716 Merge pull request #1709 from TurkeyBilly/patch-4
Fix "Code is unreachable Pylance" warning for NumberPlane
2022-01-17 08:56:08 -08:00
Bill Xi
e80dd243f1 Added abstract method decorator and override 2022-01-17 20:27:34 +08:00
Grant Sanderson
3ffe300f96 Merge pull request #1704 from TurkeyBilly/patch-2
Adding "label_buff" config parameter for Brace
2022-01-03 08:53:34 -08:00
Bill Xi
24e3caa072 fix no "import copy" bug
added import copy
2022-01-03 16:49:00 +08:00
Bill Xi
9efd02c500 Remove spelling mistake
I misspelled "label"
2022-01-03 16:37:26 +08:00
Bill Xi
0a318486c5 Adding "lable_buff" config parameter for Brace 2022-01-03 14:57:16 +08:00
鹤翔万里
919133c6bf Merge pull request #1702 from Suji04/patch-2
removed extra 'all' from comments
2021-12-31 18:18:50 +08:00
Sujan Dutta
066a2ed5dc removed extra 'all' from comments 2021-12-31 00:10:57 -05:00
TonyCrane
09ced7ce9a docs: update changelog for #1694 and #1697 2021-12-23 10:34:15 +08:00
Grant Sanderson
505b229117 Merge pull request #1697 from 3b1b/video-work
Video work
2021-12-21 10:59:50 -08:00
Grant Sanderson
5aa8d15d85 Use FFMPEG_BIN instead of "ffmpeg" for sound incorporation 2021-12-21 10:58:58 -08:00
Grant Sanderson
7aa05572ab Remove unnecessary import 2021-12-21 10:58:41 -08:00
Grant Sanderson
f1996f8479 Small hack for the lightbulb, needs to be fixed properly later 2021-12-21 10:58:33 -08:00
Grant Sanderson
37b63ca956 Merge pull request #1694 from DangGiaChi/BarChart_modified
Add option to add ticks on x-axis in BarChart()
2021-12-17 09:30:53 -08:00
DangGiaChi
84fd657d9b Change variables names: x_tick, x_ticks, y_tick, y_ticks 2021-12-17 15:02:10 +07:00
DangGiaChi
b489490f41 Fixed things as suggestions 2021-12-17 07:14:37 +07:00
DangGiaChi
bbf45f95c6 Add option to add ticks on x-axis in BarChart() 2021-12-16 22:03:29 +07:00
TonyCrane
b61f1473a5 release: ready to release v1.3.0 2021-12-14 13:41:44 +08:00
TonyCrane
e3d5b49a55 docs: remove deprecated config usage example 2021-12-14 13:35:50 +08:00
TonyCrane
4d6a0db1e1 docs: update changelog for #1691 and #1678 2021-12-14 13:31:44 +08:00
TonyCrane
0af46e149d add metavar LINENO for --embed option 2021-12-14 12:14:04 +08:00
TonyCrane
896b011d76 docs: update changelog for #1688 2021-12-14 12:11:25 +08:00
Grant Sanderson
3adaf8e325 Merge pull request #1678 from YishiMichael/master
Construct `MTex`
2021-12-13 16:09:55 -08:00
Grant Sanderson
8762177df5 Merge pull request #1691 from 3b1b/video-work
Video work
2021-12-13 16:07:42 -08:00
Grant Sanderson
a1d51474ea Add GlowDot 2021-12-13 16:03:57 -08:00
Grant Sanderson
83841ae415 Add Dodecahedron 2021-12-13 16:03:46 -08:00
Grant Sanderson
b81f244c3c Inserted "self.embed" line should match passed in line number 2021-12-13 16:03:36 -08:00
Grant Sanderson
7023548ec6 Fix TransformMatchingParts bug 2021-12-13 16:03:12 -08:00
Grant Sanderson
758f329a06 Use array copy when checking need for refreshing triangulation 2021-12-13 16:02:47 -08:00
Grant Sanderson
8f1dfabff0 VectorizedPoint should call __init__ for both super classes 2021-12-13 16:02:10 -08:00
Grant Sanderson
7fa01d5de8 Small formatting change 2021-12-13 16:01:54 -08:00
Michael W
0de303d5e0 Some refactors
- Split out `_TexParser` class
- Replace `math_mode` parameter with `tex_environment`
- Fix the bug that braces following even number of backslashes aren't matched
2021-12-13 21:01:27 +08:00
Michael W
155839bde9 Add unbreakable_commands parameter 2021-12-13 12:46:29 +08:00
Grant Sanderson
3a1e5e1bcf Remove old implementation for SurfaceMesh 2021-12-07 10:07:49 -08:00
Grant Sanderson
264f7b1172 Add Circle.get_radius 2021-12-07 10:07:25 -08:00
Grant Sanderson
85e90a1488 Don't print info for pre-run scene 2021-12-07 10:07:15 -08:00
Grant Sanderson
f8e6e7df3c Update progress display for full scene render 2021-12-07 10:06:48 -08:00
Grant Sanderson
5dd7cce67f Have Scene.wait only go through full progression during skipping when there are time-based updaters 2021-12-07 10:05:33 -08:00
Grant Sanderson
f21a4a4696 Only stop skipping if the scene wasn't originally meant to be 2021-12-07 10:04:28 -08:00
Grant Sanderson
98b0d266d2 Make sure skip_animations and start_at_animation_number play well together 2021-12-07 10:03:10 -08:00
Michael W
6821a7c20e Handle empty strings 2021-12-07 14:12:08 +08:00
Michael W
00f72da493 Some small refactor 2021-12-07 13:17:48 +08:00
Michael W
744916507c Add a debugging method 2021-12-07 12:55:52 +08:00
Michael W
88d863c1d7 Support get_tex() for submobjects of MTex 2021-12-07 00:34:07 +08:00
Michael W
d7dcc9d76f Recover file 2021-12-07 00:32:12 +08:00
Michael W
4631508b7d Add get_tex() method 2021-12-06 13:48:17 +08:00
Michael W
8803088121 Fix bugs concerned with child environments 2021-12-06 09:44:59 +08:00
Michael W
1d466cb299 Add Exception for indices_of_part() 2021-12-05 22:17:09 +08:00
Michael W
5a1f00b1cb Add TransformMatchingMTex 2021-12-05 11:46:15 +08:00
Michael W
17d31045b2 Add TransformMatchingMTex 2021-12-05 11:45:42 +08:00
Michael W
950466c1da Some refactors 2021-12-05 10:21:55 +08:00
Michael W
62151e52f1 Merge branch '3b1b:master' into master 2021-12-01 08:42:17 +08:00
Grant Sanderson
b4ce0b910c Merge pull request #1688 from 3b1b/video-work
Video work
2021-11-30 11:45:57 -08:00
Grant Sanderson
9dd1f47dab Create single progress display for full scene render
When a scene is written to file, it will now do a preliminary run of a copy of the scene with skip_animations turned on to count the total frames, which has the added benefit of catching runtime errors early, and allowing an quicker preview of the last frame to be sure everything will render as expected.

The Progress display bars for individual animations are replaced with a more global progress display bar showing the full render time for the scene.

This has the downside that all the non-rendering computations in a scene are run twice, so any scene with slow computations unrelated to rendering will take longer. But those are rarer, so the benefits seem worth it.
2021-11-30 11:41:33 -08:00
Grant Sanderson
49743daf32 Add Mobject.insert_submobject method 2021-11-30 11:30:50 -08:00
Grant Sanderson
ba23fbe71e Make sure Mobject.is_fixed_in_frame stays updated with uniforms 2021-11-30 11:30:34 -08:00
Grant Sanderson
ee1594a3cb Match fix_in_frame status for FlashAround mobject 2021-11-30 11:29:12 -08:00
Grant Sanderson
e9afb0ee33 Fix tiny PEP errors 2021-11-30 11:28:26 -08:00
Michael W
8b1715379d Some small refactors 2021-11-29 09:48:00 +08:00
Michael W
2501fac32f Some small refactors 2021-11-29 09:38:48 +08:00
Michael W
1aec0462ec Some small refactors 2021-11-29 01:43:48 +08:00
Michael W
83c70a59d8 Sort superscripts and subscripts in submobjects 2021-11-29 01:15:38 +08:00
Michael W
9b8a6e7ff8 Merge branch '3b1b:master' into master 2021-11-28 23:38:23 +08:00
Michael W
758f2ec236 Some small refactor 2021-11-28 23:38:12 +08:00
TonyCrane
d9cac38618 update changelog 2021-11-28 18:50:57 +08:00
Michael W
e8ebfa312b Prevent infinite loops from unexpected inputs 2021-11-28 13:26:54 +08:00
Michael W
dae24891fa Add get_all_isolated_substrings method 2021-11-28 13:03:33 +08:00
Michael W
a4f9de1ca1 Fix bugs concerned with coloring 2021-11-28 12:14:29 +08:00
Michael W
697028cd4c Add slicing and indexing methods 2021-11-27 23:07:46 +08:00
Michael W
c84acc0023 Remove disabled methods 2021-11-27 19:53:52 +08:00
Michael W
b1d869cd11 Update __init__.py to include mtex_mobject 2021-11-27 16:21:06 +08:00
Michael W
13a5f6d6ff Add MTex 2021-11-27 16:19:01 +08:00
Michael W
e3f87d835b Recover files 2021-11-27 16:17:22 +08:00
Michael W
7ffab788b7 Recover numbers.py 2021-11-27 16:16:18 +08:00
Grant Sanderson
bcd09906be Fix bug in ShowSubmobjectsOneByOne 2021-11-22 08:05:59 -08:00
Grant Sanderson
407c53f97c Have rotation_between_vectors handle identical/similar vectors 2021-11-18 17:52:48 -08:00
Grant Sanderson
eea3c6b294 Better align SurfaceMesh to the corresponding surface polygons 2021-11-18 17:52:17 -08:00
Grant Sanderson
d2182b9112 Make sure set_length returns self 2021-11-18 17:51:56 -08:00
Grant Sanderson
fbc329d7ce Small bug fix for angle_between_vectors 2021-11-17 12:49:53 -08:00
Grant Sanderson
25045143a1 Have mobject uniforms supercede camera uniforms 2021-11-17 12:49:08 -08:00
Grant Sanderson
e899604a2d Add getter methods for specific euler angles 2021-11-17 12:48:17 -08:00
Grant Sanderson
0b898a5594 Add always_sort_to_camera for surfaces 2021-11-16 17:38:43 -08:00
Grant Sanderson
ee2f68cd49 Exchange gloss for reflectiveness 2021-11-16 17:38:30 -08:00
Grant Sanderson
2cce4ccdd7 Exchange gloss for reflectiveness 2021-11-16 17:38:08 -08:00
Grant Sanderson
f3ecebee43 Remove unnecessary import 2021-11-16 17:37:45 -08:00
Grant Sanderson
e764da3c3a use quick_point_from_proportion for graph points 2021-11-16 17:37:27 -08:00
Grant Sanderson
fbbea47d11 Change temp embed file name 2021-11-16 17:37:01 -08:00
Grant Sanderson
781a9934fd Add shortcut for setting black background stroke 2021-11-16 17:29:24 -08:00
Grant Sanderson
a7173142bf Fix VMobject.fade 2021-11-16 17:29:10 -08:00
Grant Sanderson
0e78027186 Improve point_from_proportion to account for arc length 2021-11-16 17:28:48 -08:00
Grant Sanderson
82bd02d21f Fix angle_between_vectors, add rotation_between_vectors 2021-11-16 17:08:35 -08:00
Grant Sanderson
d065e1973d Add option to insert embed line from the command line (mildly hacky) 2021-11-14 12:31:56 -08:00
Grant Sanderson
7070777408 Tiny formatting change 2021-11-12 15:47:23 -08:00
TonyCrane
5c2a9f2129 style: change CRLF to LF
Change the line ending characters from CRLF to LF
2021-11-12 21:49:56 +08:00
Michael W
1b695e1c19 Refactor Tex 2021-11-12 21:22:42 +08:00
Michael W
da1cc44d90 Remove SingleStringTex 2021-11-12 21:21:44 +08:00
Grant Sanderson
3bbb759112 Merge branch 'master' of github.com:3b1b/manim into video-work 2021-11-09 09:18:56 -08:00
Grant Sanderson
41c6cbcb59 Merge pull request #1675 from YishiMichael/master
Add boolean operations for mobjects
2021-11-09 09:18:38 -08:00
Grant Sanderson
5930e6a176 Refresh unit normal when reversing points 2021-11-09 09:15:15 -08:00
Grant Sanderson
8f3ff91165 Add reflectiveness to style and default to fill for VMobject.get_color 2021-11-09 09:15:00 -08:00
Michael W
b12677bc1a Add files via upload 2021-11-10 00:35:09 +08:00
Michael W
cdec64e3f1 Add boolean operations for mobjects 2021-11-10 00:23:40 +08:00
Michael W
2dc8bc9b9c Add boolean operations for mobjects 2021-11-10 00:23:04 +08:00
Michael W
94f0bf557a Add skia-pathops package 2021-11-10 00:21:04 +08:00
Grant Sanderson
e20690b7c1 Don't necessarily remove anti_alias on ThreeDScene 2021-11-08 21:48:42 -08:00
Grant Sanderson
2c7689ed9e Enable glow_factor on dots 2021-11-08 21:47:48 -08:00
Grant Sanderson
c73d507c76 Fix SurfaceMesh to be evenly spaced 2021-11-08 21:47:26 -08:00
Grant Sanderson
317a5d6226 Make it possible to set full screen preview as a default 2021-11-08 21:47:02 -08:00
Grant Sanderson
4339f97c56 Small refactor and added functionality 2021-11-08 21:46:35 -08:00
Grant Sanderson
81c3ae3037 Have separate notions of gloss and reflectiveness 2021-11-08 21:46:09 -08:00
Grant Sanderson
61b04079f5 Merge branch 'master' of github.com:3b1b/manim into video-work 2021-11-01 13:18:09 -07:00
Grant Sanderson
5a0e5a16ea Merge pull request #1667 from TurkeyBilly/master
Overridden add operations for mobjects
2021-11-01 13:17:16 -07:00
Grant Sanderson
f0b5181694 Update manimlib/mobject/mobject.py
Small bug fix to Mobject.__add__
2021-11-01 13:16:50 -07:00
Grant Sanderson
185782a2e7 Remove stray brace 2021-11-01 13:05:13 -07:00
Grant Sanderson
8ab95ebe9d Change where unit_normal data gets updated 2021-11-01 13:04:53 -07:00
Bill Xi
77159eea2e Update mobject.py 2021-11-01 10:19:06 +08:00
Bill Xi
6766e459f2 Update vectorized_mobject.py 2021-10-31 20:03:04 +08:00
Bill Xi
01f4ef3e5d Create mobject.py 2021-10-31 20:02:30 +08:00
Bill Xi
b531c82bc4 Update mobject.py 2021-10-31 20:01:16 +08:00
Bill Xi
5d942d5ac0 Update vectorized_mobject.py 2021-10-31 18:42:14 +08:00
Bill Xi
b285ca7c22 Update vectorized_mobject.py 2021-10-31 18:38:23 +08:00
Bill Xi
82540edae9 Update mobject.py 2021-10-31 18:37:12 +08:00
Bill Xi
f9a6fa7036 Update mobject.py 2021-10-31 18:35:28 +08:00
Bill Xi
4eabaecfc8 Update mobject.py 2021-10-31 18:34:23 +08:00
Grant Sanderson
b881e55fca Merge branch 'master' of github.com:3b1b/manim into video-work 2021-10-24 09:48:56 -07:00
Grant Sanderson
f1c50640a3 Merge pull request #1662 from YishiMichael/master
Refactor command handling in `svg_mobject.py`
2021-10-24 09:47:54 -07:00
Grant Sanderson
deb1311e48 Fix VideoIcon 2021-10-24 09:28:52 -07:00
Grant Sanderson
82fa6ab125 Temporary hack to fix a bug I don't understand 2021-10-24 09:28:39 -07:00
Michael W
4d91ff3f2f Update balance_braces method 2021-10-24 23:21:49 +08:00
Michael W
b6f9da87d0 Refactor command handling in svg_mobject.py 2021-10-24 22:30:18 +08:00
BillyLikesHacking
c60e97ebf9 Update vectorized_mobject.py 2021-10-22 20:58:19 +08:00
BillyLikesHacking
b1ed16e81a Update mobject.py 2021-10-22 20:46:47 +08:00
BillyLikesHacking
c94ebaa260 Update vectorized_mobject.py 2021-10-22 20:03:58 +08:00
BillyLikesHacking
030fb52018 Update vectorized_mobject.py 2021-10-22 20:03:05 +08:00
BillyLikesHacking
487f582302 Update mobject.py 2021-10-22 20:02:05 +08:00
BillyLikesHacking
6d0c55d2ba Update mobject.py 2021-10-22 20:00:27 +08:00
鹤翔万里
c82f60e29e Merge pull request #1658 from 050644zf/master
Update the link of the Chinese Ver. Docs
2021-10-20 17:25:28 +08:00
Nightsky
c03279d626 Update the link of the Chinese Ver. Docs 2021-10-20 17:20:52 +08:00
Grant Sanderson
7b72fa8ca1 Merge branch 'master' of github.com:3b1b/manim into video-work 2021-10-18 07:12:10 -07:00
Grant Sanderson
8b454fbe93 Slight tweaks to how saturation_factor works on newton-fractal 2021-10-18 07:12:05 -07:00
Grant Sanderson
f77e25ff86 Merge pull request #1655 from widcardw/master
Fix the bug of rotating camera
2021-10-18 07:01:58 -07:00
widcardw
872ef67cf7 Fix bug of rotating camera 2021-10-18 21:00:25 +08:00
widcardw
305ca72ebe Fix the bug of rotating camera 2021-10-18 19:05:05 +08:00
TonyCrane
4d81d3678b update changelog 2021-10-16 21:07:53 +08:00
鹤翔万里
55e968e174 Merge pull request #1653 from YishiMichael/master
Fix parameter typo
2021-10-16 21:06:32 +08:00
TonyCrane
97d1609849 update changelog 2021-10-16 21:03:36 +08:00
TonyCrane
e10f850d0d add cli flag to specify log level 2021-10-16 21:01:39 +08:00
Michael W
b8584fe5ab Fix parameter typo 2021-10-16 20:59:31 +08:00
TonyCrane
b6c23a09e9 update version to v1.2.0 2021-10-16 13:07:26 +08:00
Michael W
0e574882b3 Refactor #1637 (#1650)
* Refactor #1637

* Refactor #1637

* Refactor #1637

* Refactor #1637

* Refactor #1637

* Refactor #1637

* Update config.py

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

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,219 @@
Changelog
=========
No changes now.
v1.4.1
------
Fixed bugs
^^^^^^^^^^
- `#1724 <https://github.com/3b1b/manim/pull/1724>`__: Temporarily fixed boolean operations' bug
- `d2e0811 <https://github.com/3b1b/manim/commit/d2e0811285f7908e71a65e664fec88b1af1c6144>`__: Import ``Iterable`` from ``collections.abc`` instead of ``collections`` which is deprecated since python 3.9
v1.4.0
------
Fixed bugs
^^^^^^^^^^
- `f1996f8 <https://github.com/3b1b/manim/pull/1697/commits/f1996f8479f9e33d626b3b66e9eb6995ce231d86>`__: Temporarily fixed ``Lightbulb``
- `#1712 <https://github.com/3b1b/manim/pull/1712>`__: Fixed some bugs of ``SVGMobject``
- `#1717 <https://github.com/3b1b/manim/pull/1717>`__: Fixed some bugs of SVG path string parser
- `#1720 <https://github.com/3b1b/manim/pull/1720>`__: Fixed some bugs of ``MTex``
New Features
^^^^^^^^^^^^
- `#1694 <https://github.com/3b1b/manim/pull/1694>`__: Added option to add ticks on x-axis in ``BarChart``
- `#1704 <https://github.com/3b1b/manim/pull/1704>`__: Added ``lable_buff`` config parameter for ``Brace``
- `#1712 <https://github.com/3b1b/manim/pull/1712>`__: Added support for ``rotate skewX skewY`` transform in SVG
- `#1717 <https://github.com/3b1b/manim/pull/1717>`__: Added style support to ``SVGMobject``
- `#1719 <https://github.com/3b1b/manim/pull/1719>`__: Added parser to <style> element of SVG
- `#1719 <https://github.com/3b1b/manim/pull/1719>`__: Added support for <line> element in ``SVGMobject``
Refactor
^^^^^^^^
- `5aa8d15 <https://github.com/3b1b/manim/pull/1697/commits/5aa8d15d85797f68a8f169ca69fd90d441a3abbe>`__: Used ``FFMPEG_BIN`` instead of ``"ffmpeg"`` for sound incorporation
- `#1709 <https://github.com/3b1b/manim/pull/1709>`__: Decorated ``CoordinateSystem.get_axes`` and ``.get_all_ranges`` as abstract method
- `#1712 <https://github.com/3b1b/manim/pull/1712>`__: Refactored SVG path string parser
- `#1712 <https://github.com/3b1b/manim/pull/1712>`__: Allowed ``Mobject.scale`` to receive iterable ``scale_factor``
- `#1716 <https://github.com/3b1b/manim/pull/1716>`__: Refactored ``MTex``
- `#1721 <https://github.com/3b1b/manim/pull/1721>`__: Improved config helper (``manimgl --config``)
- `#1723 <https://github.com/3b1b/manim/pull/1723>`__: Refactored ``MTex``
Dependencies
^^^^^^^^^^^^
- `#1719 <https://github.com/3b1b/manim/pull/1719>`__: Added dependency on python package `cssselect2 <https://github.com/Kozea/cssselect2>`__
v1.3.0
------
Fixed bugs
^^^^^^^^^^
- `#1653 <https://github.com/3b1b/manim/pull/1653>`__: Fixed ``Mobject.stretch_to_fit_depth``
- `#1655 <https://github.com/3b1b/manim/pull/1655>`__: Fixed the bug of rotating camera
- `c73d507 <https://github.com/3b1b/manim/pull/1688/commits/c73d507c76af5c8602d4118bc7538ba04c03ebae>`__: Fixed ``SurfaceMesh`` to be evenly spaced
- `82bd02d <https://github.com/3b1b/manim/pull/1688/commits/82bd02d21fbd89b71baa21e077e143f440df9014>`__: Fixed ``angle_between_vectors`` add ``rotation_between_vectors``
- `a717314 <https://github.com/3b1b/manim/pull/1688/commits/a7173142bf93fd309def0cc10f3c56f5e6972332>`__: Fixed ``VMobject.fade``
- `fbc329d <https://github.com/3b1b/manim/pull/1688/commits/fbc329d7ce3b11821d47adf6052d932f7eff724a>`__: Fixed ``angle_between_vectors``
- `bcd0990 <https://github.com/3b1b/manim/pull/1688/commits/bcd09906bea5eaaa5352e7bee8f3153f434cf606>`__: Fixed bug in ``ShowSubmobjectsOneByOne``
- `7023548 <https://github.com/3b1b/manim/pull/1691/commits/7023548ec62c4adb2f371aab6a8c7f62deb7c33c>`__: Fixed bug in ``TransformMatchingParts``
New Features
^^^^^^^^^^^^
- `e10f850 <https://github.com/3b1b/manim/commit/e10f850d0d9f971931cc85d44befe67dc842af6d>`__: Added CLI flag ``--log-level`` to specify log level
- `#1667 <https://github.com/3b1b/manim/pull/1667>`__: Added operations (``+`` and ``*``) for ``Mobject``
- `#1675 <https://github.com/3b1b/manim/pull/1675>`__: Added 4 boolean operations for ``VMobject`` in ``manimlib/mobject/boolean_ops.py``
- ``Union(*vmobjects, **kwargs)``
- ``Difference(subject, clip, **kwargs)``
- ``Intersection(*vmobjects, **kwargs)``
- ``Exclusion(*vmobjects, **kwargs)``
- `81c3ae3 <https://github.com/3b1b/manim/pull/1688/commits/81c3ae30372e288dc772633dbd17def6e603753e>`__: Added reflectiveness
- `2c7689e <https://github.com/3b1b/manim/pull/1688/commits/2c7689ed9e81229ce87c648f97f26267956c0bc9>`__: Enabled ``glow_factor`` on ``DotCloud``
- `d065e19 <https://github.com/3b1b/manim/pull/1688/commits/d065e1973d1d6ebd2bece81ce4bdf0c2fff7c772>`__: Added option ``-e`` to insert embed line from the command line
- `0e78027 <https://github.com/3b1b/manim/pull/1688/commits/0e78027186a976f7e5fa8d586f586bf6e6baab8d>`__: Improved ``point_from_proportion`` to account for arc length
- `781a993 <https://github.com/3b1b/manim/pull/1688/commits/781a9934fda6ba11f22ba32e8ccddcb3ba78592e>`__: Added shortcut ``set_backstroke`` for setting black background stroke
- `0b898a5 <https://github.com/3b1b/manim/pull/1688/commits/0b898a5594203668ed9cad38b490ab49ba233bd4>`__: Added ``Suface.always_sort_to_camera``
- `e899604 <https://github.com/3b1b/manim/pull/1688/commits/e899604a2d05f78202fcb3b9824ec34647237eae>`__: Added getter methods for specific euler angles
- `407c53f <https://github.com/3b1b/manim/pull/1688/commits/407c53f97c061bfd8a53beacd88af4c786f9e9ee>`__: Hade ``rotation_between_vectors`` handle identical/similar vectors
- `49743da <https://github.com/3b1b/manim/pull/1688/commits/49743daf3244bfa11a427040bdde8e2bb79589e8>`__: Added ``Mobject.insert_submobject`` method
- `9dd1f47 <https://github.com/3b1b/manim/pull/1688/commits/9dd1f47dabca1580d6102e34e44574b0cba556e7>`__: Created single progress display for full scene render
- `264f7b1 <https://github.com/3b1b/manim/pull/1691/commits/264f7b11726e9e736f0fe472f66e38539f74e848>`__: Added ``Circle.get_radius``
- `83841ae <https://github.com/3b1b/manim/pull/1691/commits/83841ae41568a9c9dff44cd163106c19a74ac281>`__: Added ``Dodecahedron``
- `a1d5147 <https://github.com/3b1b/manim/pull/1691/commits/a1d51474ea1ce3b7aa3efbe4c5e221be70ee2f5b>`__: Added ``GlowDot``
- `#1678 <https://github.com/3b1b/manim/pull/1678>`__: Added ``MTex`` , see `#1678 <https://github.com/3b1b/manim/pull/1678>`__ for details
Refactor
^^^^^^^^
- `#1662 <https://github.com/3b1b/manim/pull/1662>`__: Refactored support for command ``A`` in path of SVG
- `#1662 <https://github.com/3b1b/manim/pull/1662>`__: Refactored ``SingleStringTex.balance_braces``
- `8b454fb <https://github.com/3b1b/manim/pull/1688/commits/8b454fbe9335a7011e947093230b07a74ba9c653>`__: Slight tweaks to how saturation_factor works on newton-fractal
- `317a5d6 <https://github.com/3b1b/manim/pull/1688/commits/317a5d6226475b6b54a78db7116c373ef84ea923>`__: Made it possible to set full screen preview as a default
- `e764da3 <https://github.com/3b1b/manim/pull/1688/commits/e764da3c3adc5ae2a4ce877b340d2b6abcddc2fc>`__: Used ``quick_point_from_proportion`` for graph points
- `d2182b9 <https://github.com/3b1b/manim/pull/1688/commits/d2182b9112300558b6c074cefd685f97c10b3898>`__: Made sure ``Line.set_length`` returns self
- `eea3c6b <https://github.com/3b1b/manim/pull/1688/commits/eea3c6b29438f9e9325329c4355e76b9f635e97a>`__: Better align ``SurfaceMesh`` to the corresponding surface polygons
- `ee1594a <https://github.com/3b1b/manim/pull/1688/commits/ee1594a3cb7a79b8fc361e4c4397a88c7d20c7e3>`__: Match ``fix_in_frame`` status for ``FlashAround`` mobject
- `ba23fbe <https://github.com/3b1b/manim/pull/1688/commits/ba23fbe71e4a038201cd7df1d200514ed1c13bc2>`__: Made sure ``Mobject.is_fixed_in_frame`` stays updated with uniforms
- `98b0d26 <https://github.com/3b1b/manim/pull/1691/commits/98b0d266d2475926a606331923cca3dc1dea97ad>`__: Made sure ``skip_animations`` and ``start_at_animation_number`` play well together
- `f8e6e7d <https://github.com/3b1b/manim/pull/1691/commits/f8e6e7df3ceb6f3d845ced4b690a85b35e0b8d00>`__: Updated progress display for full scene render
- `8f1dfab <https://github.com/3b1b/manim/pull/1691/commits/8f1dfabff04a8456f5c4df75b0f97d50b2755003>`__: ``VectorizedPoint`` should call ``__init__`` for both super classes
- `758f329 <https://github.com/3b1b/manim/pull/1691/commits/758f329a06a0c198b27a48c577575d94554305bf>`__: Used array copy when checking need for refreshing triangulation
Dependencies
^^^^^^^^^^^^
- `#1675 <https://github.com/3b1b/manim/pull/1675>`__: Added dependency on python package `skia-pathops <https://github.com/fonttools/skia-pathops>`__
v1.2.0
------
Fixed bugs
^^^^^^^^^^
- `#1592 <https://github.com/3b1b/manim/pull/1592>`__: Fixed ``put_start_and_end_on`` in 3D
- `#1601 <https://github.com/3b1b/manim/pull/1601>`__: Fixed ``DecimalNumber``'s scaling issue
- `56df154 <https://github.com/3b1b/manim/commit/56df15453f3e3837ed731581e52a1d76d5692077>`__: Fixed bug with common range array used for all coordinate systems
- `8645894 <https://github.com/3b1b/manim/commit/86458942550c639a241267d04d57d0e909fcf252>`__: Fixed ``CoordinateSystem`` init bug
- `0dc096b <https://github.com/3b1b/manim/commit/0dc096bf576ea900b351e6f4a80c13a77676f89b>`__: Fixed bug for single-valued ``ValueTracker``
- `54ad355 <https://github.com/3b1b/manim/commit/54ad3550ef0c0e2fda46b26700a43fa8cde0973f>`__: Fixed bug with SVG rectangles
- `d45ea28 <https://github.com/3b1b/manim/commit/d45ea28dc1d92ab9c639a047c00c151382eb0131>`__: Fixed ``DotCloud.set_radii``
- `b543cc0 <https://github.com/3b1b/manim/commit/b543cc0e32d45399ee81638b6d4fb631437664cd>`__: Temporarily fixed bug for ``PMobject`` array resizing
- `5f878a2 <https://github.com/3b1b/manim/commit/5f878a2c1aa531b7682bd048468c72d2835c7fe5>`__: Fixed ``match_style``
- `719c81d <https://github.com/3b1b/manim/commit/719c81d72b00dcf49f148d7c146774b22e0fe348>`__: Fixed negative ``path_arc`` case
- `c726eb7 <https://github.com/3b1b/manim/commit/c726eb7a180b669ee81a18555112de26a8aff6d6>`__: Fixed bug with ``CoordinateSystem.get_lines_parallel_to_axis``
- `7732d2f <https://github.com/3b1b/manim/commit/7732d2f0ee10449c5731499396d4911c03e89648>`__: Fixed ``ComplexPlane`` -i display bug
New Features
^^^^^^^^^^^^
- `#1598 <https://github.com/3b1b/manim/pull/1598>`__: Supported the elliptical arc command ``A`` for ``SVGMobject``
- `#1607 <https://github.com/3b1b/manim/pull/1607>`__: Added ``FlashyFadeIn``
- `#1607 <https://github.com/3b1b/manim/pull/1607>`__: Save triangulation
- `#1625 <https://github.com/3b1b/manim/pull/1625>`__: Added new ``Code`` mobject
- `#1637 <https://github.com/3b1b/manim/pull/1637>`__: Add warnings and use rich to display log
- `bd356da <https://github.com/3b1b/manim/commit/bd356daa99bfe3134fcb192a5f72e0d76d853801>`__: Added ``VCube``
- `6d72893 <https://github.com/3b1b/manim/commit/6d7289338234acc6658b9377c0f0084aa1fa7119>`__: Supported ``ValueTracker`` to track vectors
- `3bb8f3f <https://github.com/3b1b/manim/commit/3bb8f3f0422a5dfba0da6ef122dc0c01f31aff03>`__: Added ``set_max_width``, ``set_max_height``, ``set_max_depth`` to ``Mobject``
- `a35dd5a <https://github.com/3b1b/manim/commit/a35dd5a3cbdeffa3891d5aa5f80287c18dba2f7f>`__: Added ``TracgTail``
- `acba13f <https://github.com/3b1b/manim/commit/acba13f4991b78d54c0bf93cce7ca3b351c25476>`__: Added ``Scene.point_to_mobject``
- `f84b8a6 <https://github.com/3b1b/manim/commit/f84b8a66fe9e8b3872e5c716c5c240c14bb555ee>`__: Added poly_fractal shader
- `b24ba19 <https://github.com/3b1b/manim/commit/b24ba19dec48ba4e38acbde8eec6d3a308b6ab83>`__: Added kwargs to ``TipableVMobject.set_length``
- `17c2772 <https://github.com/3b1b/manim/commit/17c2772b84abf6392a4170030e36e981de4737d0>`__: Added ``Mobject.replicate``
- `33fa76d <https://github.com/3b1b/manim/commit/33fa76dfac36e70bb5fad69dc6a336800c6dacce>`__: Added mandelbrot_fractal shader
- `f22a341 <https://github.com/3b1b/manim/commit/f22a341e8411eae9331d4dd976b5e15bc6db08d9>`__: Saved state before each embed
- `e10a752 <https://github.com/3b1b/manim/commit/e10a752c0001e8981038faa03be4de2603d3565f>`__: Allowed releasing of Textures
- `14fbed7 <https://github.com/3b1b/manim/commit/14fbed76da4b493191136caebb8a955e2d41265b>`__: Consolidated and renamed newton_fractal shader
- `6cdbe0d <https://github.com/3b1b/manim/commit/6cdbe0d67a11ab14a6d84840a114ae6d3af10168>`__: Hade ``ImageMoject`` remember the filepath to the Image
Refactor
^^^^^^^^
- `#1601 <https://github.com/3b1b/manim/pull/1601>`__: Changed back to simpler ``Mobject.scale`` implementation
- `b667db2 <https://github.com/3b1b/manim/commit/b667db2d311a11cbbca2a6ff511d2c3cf1675486>`__: Simplified ``Square``
- `40290ad <https://github.com/3b1b/manim/commit/40290ada8343f10901fa9151cbdf84689667786d>`__: Removed unused parameter ``triangulation_locked``
- `8647a64 <https://github.com/3b1b/manim/commit/8647a6429dd0c52cba14e971b8c09194a93cfd87>`__: Reimplemented ``Arrow``
- `d8378d8 <https://github.com/3b1b/manim/commit/d8378d8157040cd797cc47ef9576beffd8607863>`__: Used ``make_approximately_smooth`` for ``set_points_smoothly`` by default
- `7b4199c <https://github.com/3b1b/manim/commit/7b4199c674e291f1b84678828b63b6bd4fcc6b17>`__: Refactored to call ``_handle_scale_side_effects`` after scaling takes place
- `7356a36 <https://github.com/3b1b/manim/commit/7356a36fa70a8279b43ae74e247cbd43b2bfd411>`__: Refactored to only call ``throw_error_if_no_points`` once for ``get_start_and_end``
- `0787c4f <https://github.com/3b1b/manim/commit/0787c4f36270a6560b50ce3e07b30b0ec5f2ba3e>`__: Made sure framerate is 30 for previewed scenes
- `c635f19 <https://github.com/3b1b/manim/commit/c635f19f2a33e916509e53ded46f55e2afa8f5f2>`__: Pushed ``pixel_coords_to_space_coords`` to ``Window``
- `d5a88d0 <https://github.com/3b1b/manim/commit/d5a88d0fa457cfcf4cb9db417a098c37c95c7051>`__: Refactored to pass tuples and not arrays to uniforms
- `9483f26 <https://github.com/3b1b/manim/commit/9483f26a3b056de0e34f27acabd1a946f1adbdf9>`__: Refactored to copy uniform arrays in ``Mobject.copy``
- `ed1fc4d <https://github.com/3b1b/manim/commit/ed1fc4d5f94467d602a568466281ca2d0368b506>`__: Added ``bounding_box`` as exceptional key to point_cloud mobject
- `329d2c6 <https://github.com/3b1b/manim/commit/329d2c6eaec3d88bfb754b555575a3ea7c97a7e0>`__: Made sure stroke width is always a float
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

@@ -83,22 +83,3 @@ Its value is a dictionary, passed in as ``kwargs`` when initializing the ``Camer
to modify the value of the properties of the ``Camera`` class.
So the nesting of the ``CONFIG`` dictionary **essentially** passes in the value as ``kwargs``.
Common usage
------------
When writing a class by yourself, you can add attributes or modify the attributes
of the parent class through ``CONFIG``.
The most commonly used is to modify the properties of the camera when writing a ``Scene``:
.. code-block:: python
class YourScene(Scene):
CONFIG = {
"camera_config": {
"background_color": WHITE,
},
}
For example, the above dictionary will change the background color to white, etc.

View File

@@ -52,12 +52,14 @@ flag abbr function
``--finder`` Show the output file in finder
``--config`` Guide for automatic configuration
``--file_name FILE_NAME`` Name for the movie or image file
``--start_at_animation_number START_AT_ANIMATION_NUMBER`` ``-n`` Start rendering not from the first animation, but from another, specified by its index. If you passin two comma separated values, e.g. "3,6", it will end the rendering at the second value.
``--start_at_animation_number START_AT_ANIMATION_NUMBER`` ``-n`` Start rendering not from the first animation, but from another, specified by its index. If you passing two comma separated values, e.g. "3,6", it will end the rendering at the second value.
``--embed LINENO`` ``-e`` Takes a line number as an argument, and results in the scene being called as if the line ``self.embed()`` was inserted into the scene code at that line number
``--resolution RESOLUTION`` ``-r`` Resolution, passed as "WxH", e.g. "1920x1080"
``--frame_rate FRAME_RATE`` Frame rate, as an integer
``--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 +87,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
# hierarchy of the resulting mobject ensure that the
# Tex mobject has a subject corresponding to
# each of these strings. For example, the Tex mobject
# below will have 5 subjects, corresponding to the
# 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):
@@ -327,7 +335,7 @@ UpdatersExample
# If the argument itself might change, you can use f_always,
# for which the arguments following the initial Mobject method
# should be functions returning arguments to that method.
# The following line ensures thst decimal.set_value(square.get_y())
# The following line ensures that decimal.set_value(square.get_y())
# is called every frame
f_always(number.set_value, square.get_width)
# You could also write the following equivalent line
@@ -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/
.. toctree::
:maxdepth: 2

View File

@@ -2,7 +2,7 @@ from manimlib import *
import numpy as np
# To watch one of these scenes, run the following:
# python -m manim example_scenes.py SquareToCircle
# manimgl example_scenes.py OpeningManimExample
# Use -s to skip to the end and just save the final frame
# Use -w to write the animation to a file
# Use -o to write it to a file and open it once done
@@ -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
# hierarchy of the resulting mobject ensure that the
# Tex mobject has a subject corresponding to
# each of these strings. For example, the Tex mobject
# below will have 5 subjects, corresponding to the
# 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])
@@ -270,7 +274,7 @@ class UpdatersExample(Scene):
square = Square()
square.set_fill(BLUE_E, 1)
# On all all frames, the constructor Brace(square, UP) will
# On all frames, the constructor Brace(square, UP) will
# be called, and the mobject brace will set its data to match
# that of the newly constructed object
brace = always_redraw(Brace, square, UP)
@@ -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

@@ -1,3 +1,7 @@
import pkg_resources
__version__ = pkg_resources.get_distribution("manimgl").version
from manimlib.constants import *
from manimlib.animation.animation import *
@@ -18,6 +22,7 @@ from manimlib.camera.camera import *
from manimlib.window import *
from manimlib.mobject.boolean_ops import *
from manimlib.mobject.coordinate_systems import *
from manimlib.mobject.changing import *
from manimlib.mobject.frame import *
@@ -32,6 +37,7 @@ from manimlib.mobject.probability import *
from manimlib.mobject.shape_matchers import *
from manimlib.mobject.svg.brace import *
from manimlib.mobject.svg.drawings import *
from manimlib.mobject.svg.mtex_mobject import *
from manimlib.mobject.svg.svg_mobject import *
from manimlib.mobject.svg.tex_mobject import *
from manimlib.mobject.svg.text_mobject import *
@@ -62,4 +68,3 @@ from manimlib.utils.rate_functions import *
from manimlib.utils.simple_functions import *
from manimlib.utils.sounds import *
from manimlib.utils.space_ops import *
from manimlib.utils.strings import *

View File

@@ -1,17 +1,29 @@
#!/usr/bin/env python
import manimlib.config
import manimlib.logger
import manimlib.extract_scene
import manimlib.utils.init_config
from manimlib import __version__
def main():
print(f"ManimGL \033[32mv{__version__}\033[0m")
args = manimlib.config.parse_cli()
if args.version and args.file is None:
return
if args.log_level:
manimlib.logger.log.setLevel(args.log_level)
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)
@@ -170,16 +174,12 @@ class ShowSubmobjectsOneByOne(ShowIncreasingSubsets):
"int_func": np.ceil,
}
def __init__(self, group, **kwargs):
new_group = Group(*group)
super().__init__(new_group, **kwargs)
def update_submobject_list(self, index):
# N = len(self.all_submobs)
if index == 0:
self.mobject.set_submobjects([])
else:
self.mobject.set_submobjects(self.all_submobs[index - 1])
self.mobject.set_submobjects([self.all_submobs[index - 1]])
# TODO, this is broken...

View File

@@ -1,4 +1,5 @@
import numpy as np
import math
from manimlib.constants import *
from manimlib.animation.animation import Animation
@@ -8,17 +9,21 @@ from manimlib.animation.composition import Succession
from manimlib.animation.creation import ShowCreation
from manimlib.animation.creation import ShowPartial
from manimlib.animation.fading import FadeOut
from manimlib.animation.fading import FadeIn
from manimlib.animation.transform import Transform
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.geometry import Circle
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
from manimlib.utils.config_ops import digest_config
from manimlib.utils.rate_functions import there_and_back
from manimlib.utils.rate_functions import wiggle
from manimlib.utils.rate_functions import smooth
from manimlib.utils.rate_functions import squish_rate_func
class FocusOn(Transform):
@@ -152,6 +157,89 @@ 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)
if mobject.is_fixed_in_frame:
path.fix_in_frame()
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,
@@ -278,3 +366,22 @@ class TurnInsideOut(Transform):
def create_target(self):
return self.mobject.copy().reverse_points()
class FlashyFadeIn(AnimationGroup):
CONFIG = {
"fade_lag": 0,
}
def __init__(self, vmobject, stroke_width=2, **kwargs):
digest_config(self, kwargs)
outline = vmobject.copy()
outline.set_fill(opacity=0)
outline.set_stroke(width=stroke_width, opacity=1)
rate_func = kwargs.get("rate_func", smooth)
super().__init__(
FadeIn(vmobject, rate_func=squish_rate_func(rate_func, self.fade_lag, 1)),
VShowPassingFlash(outline, time_width=1),
**kwargs
)

View File

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

View File

@@ -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
@@ -69,10 +68,10 @@ class TransformMatchingParts(AnimationGroup):
anims.append(FadeTransformPieces(fade_source, fade_target, **kwargs))
else:
anims.append(FadeOutToPoint(
fade_source, fade_target.get_center(), **kwargs
fade_source, target_mobject.get_center(), **kwargs
))
anims.append(FadeInFromPoint(
fade_target.copy(), fade_source.get_center(), **kwargs
fade_target.copy(), mobject.get_center(), **kwargs
))
super().__init__(*anims)
@@ -129,7 +128,7 @@ class TransformMatchingShapes(TransformMatchingParts):
class TransformMatchingTex(TransformMatchingParts):
CONFIG = {
"mobject_type": Tex,
"mobject_type": VMobject,
"group_type": VGroup,
}

View File

@@ -1,4 +1,5 @@
import moderngl
import math
from colour import Color
import OpenGL.GL as gl
@@ -67,7 +68,7 @@ class CameraFrame(Mobject):
added_rot_T = rotation_matrix_transpose(angle, axis)
new_rot_T = np.dot(curr_rot_T, added_rot_T)
Fz = new_rot_T[2]
phi = np.arccos(Fz[2])
phi = np.arccos(clip(Fz[2], -1, 1))
theta = angle_of_vector(Fz[:2]) + PI / 2
partial_rot_T = np.dot(
rotation_matrix_transpose(phi, RIGHT),
@@ -77,16 +78,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)
@@ -113,6 +122,15 @@ class CameraFrame(Mobject):
self.refresh_rotation_matrix()
return self
def get_theta(self):
return self.data["euler_angles"][0]
def get_phi(self):
return self.data["euler_angles"][1]
def get_gamma(self):
return self.data["euler_angles"][2]
def get_shape(self):
return (self.get_width(), self.get_height())
@@ -131,6 +149,16 @@ class CameraFrame(Mobject):
def get_focal_distance(self):
return self.focal_distance * self.get_height()
def get_implied_camera_location(self):
theta, phi, gamma = self.get_euler_angles()
dist = self.get_focal_distance()
x, y, z = self.get_center()
return (
x + dist * math.sin(theta) * math.sin(phi),
y - dist * math.cos(theta) * math.sin(phi),
z + dist * math.cos(phi)
)
def interpolate(self, *args, **kwargs):
super().interpolate(*args, **kwargs)
self.refresh_rotation_matrix()
@@ -186,20 +214,30 @@ class Camera(object):
fbo = self.get_fbo(ctx, 0)
else:
fbo = ctx.detect_framebuffer()
self.ctx = ctx
self.fbo = fbo
self.set_ctx_blending()
# For multisample antialiasing
fbo_msaa = self.get_fbo(ctx, self.samples)
fbo_msaa.use()
self.fbo_msaa = fbo_msaa
ctx.enable(moderngl.BLEND)
ctx.blend_func = (
def set_ctx_blending(self, enable=True):
if enable:
self.ctx.enable(moderngl.BLEND)
else:
self.ctx.disable(moderngl.BLEND)
self.ctx.blend_func = (
moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA,
moderngl.ONE, moderngl.ONE
# moderngl.ONE, moderngl.ONE
)
self.ctx = ctx
self.fbo = fbo
self.fbo_msaa = fbo_msaa
def set_ctx_depth_test(self, enable=True):
if enable:
self.ctx.enable(moderngl.DEPTH_TEST)
else:
self.ctx.disable(moderngl.DEPTH_TEST)
def init_light_source(self):
self.light_source = Point(self.light_source_position)
@@ -289,6 +327,9 @@ class Camera(object):
def get_frame_center(self):
return self.frame.get_center()
def get_location(self):
return self.frame.get_implied_camera_location()
def resize_frame_shape(self, fixed_dimension=0):
"""
Changes frame_shape to match the aspect ratio
@@ -308,17 +349,6 @@ class Camera(object):
self.frame.set_height(frame_height)
self.frame.set_width(frame_width)
def pixel_coords_to_space_coords(self, px, py, relative=False):
pw, ph = self.fbo.size
fw, fh = self.get_frame_shape()
fc = self.get_frame_center()
if relative:
return 2 * np.array([px / pw, py / ph, 0])
else:
# Only scale wrt one axis
scale = fh / ph
return fc + scale * np.array([(px - pw / 2), (py - ph / 2), 0])
# Rendering
def capture(self, *mobjects, **kwargs):
self.refresh_perspective_uniforms()
@@ -330,17 +360,11 @@ class Camera(object):
shader_wrapper = render_group["shader_wrapper"]
shader_program = render_group["prog"]
self.set_shader_uniforms(shader_program, shader_wrapper)
self.update_depth_test(shader_wrapper)
self.set_ctx_depth_test(shader_wrapper.depth_test)
render_group["vao"].render(int(shader_wrapper.render_primitive))
if render_group["single_use"]:
self.release_render_group(render_group)
def update_depth_test(self, shader_wrapper):
if shader_wrapper.depth_test:
self.ctx.enable(moderngl.DEPTH_TEST)
else:
self.ctx.disable(moderngl.DEPTH_TEST)
def get_render_group_list(self, mobject):
try:
return self.static_mobject_to_render_group_list[id(mobject)]
@@ -413,8 +437,10 @@ class Camera(object):
for name, path in shader_wrapper.texture_paths.items():
tid = self.get_texture_id(path)
shader[name].value = tid
for name, value in it.chain(shader_wrapper.uniforms.items(), self.perspective_uniforms.items()):
for name, value in it.chain(self.perspective_uniforms.items(), shader_wrapper.uniforms.items()):
try:
if isinstance(value, np.ndarray):
value = tuple(value)
shader[name].value = value
except KeyError:
pass
@@ -428,38 +454,52 @@ class Camera(object):
anti_alias_width = self.anti_alias_width / (ph / fh)
# Orient light
rotation = frame.get_inverse_camera_rotation_matrix()
light_pos = self.light_source.get_location()
light_pos = np.dot(rotation, light_pos)
offset = frame.get_center()
light_pos = np.dot(
rotation, self.light_source.get_location() + offset
)
cam_pos = self.frame.get_implied_camera_location() # TODO
self.perspective_uniforms = {
"frame_shape": frame.get_shape(),
"anti_alias_width": anti_alias_width,
"camera_center": tuple(frame.get_center()),
"camera_offset": tuple(offset),
"camera_rotation": tuple(np.array(rotation).T.flatten()),
"camera_position": tuple(cam_pos),
"light_source_position": tuple(light_pos),
"focal_distance": frame.get_focal_distance(),
}
def init_textures(self):
self.path_to_texture_id = {}
self.n_textures = 0
self.path_to_texture = {}
def get_texture_id(self, path):
if path not in self.path_to_texture_id:
# A way to increase tid's sequentially
tid = len(self.path_to_texture_id)
im = Image.open(path)
if path not in self.path_to_texture:
if self.n_textures == 15: # I have no clue why this is needed
self.n_textures += 1
tid = self.n_textures
self.n_textures += 1
im = Image.open(path).convert("RGBA")
texture = self.ctx.texture(
size=im.size,
components=len(im.getbands()),
data=im.tobytes(),
)
texture.use(location=tid)
self.path_to_texture_id[path] = tid
return self.path_to_texture_id[path]
self.path_to_texture[path] = (tid, texture)
return self.path_to_texture[path][0]
def release_texture(self, path):
tid_and_texture = self.path_to_texture.pop(path, None)
if tid_and_texture:
tid_and_texture[1].release()
return self
# Mostly just defined so old scenes don't break
class ThreeDCamera(Camera):
CONFIG = {
"samples": 4,
"anti_alias_width": 0,
}

View File

@@ -5,10 +5,15 @@ import importlib
import os
import sys
import yaml
from contextlib import contextmanager
from screeninfo import get_monitors
from manimlib.utils.config_ops import merge_dicts_recursively
from manimlib.utils.init_config import init_customization
from manimlib.logger import log
__config_file__ = "custom_config.yml"
def parse_cli():
@@ -111,6 +116,12 @@ def parse_cli():
"in two comma separated values, e.g. \"3,6\", it will end"
"the rendering at the second value",
)
parser.add_argument(
"-e", "--embed", metavar="LINENO",
help="Takes a line number as an argument, and results"
"in the scene being called as if the line `self.embed()`"
"was inserted into the scene code at that line number."
)
parser.add_argument(
"-r", "--resolution",
help="Resolution, passed as \"WxH\", e.g. \"1920x1080\"",
@@ -130,12 +141,25 @@ 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",
)
parser.add_argument(
"-v", "--version",
action="store_true",
help="Display the version of manimgl"
)
parser.add_argument(
"--log-level",
help="Level of messages to Display, can be DEBUG / INFO / WARNING / ERROR / CRITICAL"
)
args = parser.parse_args()
return args
except argparse.ArgumentError as err:
print(str(err))
log.error(str(err))
sys.exit(2)
@@ -148,24 +172,42 @@ def get_manim_dir():
def get_module(file_name):
if file_name is None:
return None
else:
module_name = file_name.replace(os.sep, ".").replace(".py", "")
spec = importlib.util.spec_from_file_location(module_name, file_name)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
module_name = file_name.replace(os.sep, ".").replace(".py", "")
spec = importlib.util.spec_from_file_location(module_name, file_name)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
@contextmanager
def insert_embed_line(file_name, lineno):
with open(file_name, 'r') as fp:
lines = fp.readlines()
line = lines[lineno - 1]
n_spaces = len(line) - len(line.lstrip())
lines.insert(lineno, " " * n_spaces + "self.embed()\n")
alt_file = file_name.replace(".py", "_inserted_embed.py")
with open(alt_file, 'w') as fp:
fp.writelines(lines)
try:
yield alt_file
finally:
os.remove(alt_file)
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,23 +215,55 @@ 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 check_temporary_storage(config):
if config["directories"]["temporary_storage"] == "" and sys.platform == "win32":
log.warning(
"You may be using Windows platform and have not specified the path of"
" `temporary_storage`, which may cause OSError. So it is recommended"
" to specify the `temporary_storage` in the config file (.yml)"
)
def get_configuration(args):
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):
log.error(f"Can't find {args.config_file}.")
if sys.platform == 'win32':
log.info(f"Copying default configuration file to {args.config_file}...")
os.system(f"copy default_config.yml {args.config_file}")
elif sys.platform in ["linux2", "darwin"]:
log.info(f"Copying default configuration file to {args.config_file}...")
os.system(f"cp default_config.yml {args.config_file}")
else:
log.info("Please create the configuration file manually.")
log.info("Read configuration from default_config.yml.")
else:
__config_file__ = args.config_file
global_defaults_file = os.path.join(get_manim_dir(), "manimlib", "default_config.yml")
if not (os.path.exists(global_defaults_file) or os.path.exists(local_config_file)):
print("There is no configuration file detected. Initial configuration:\n")
if not (os.path.exists(global_defaults_file) or os.path.exists(__config_file__)):
log.info("There is no configuration file detected. Switch to the config file initializer:")
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__):
log.info(f"Using the default configuration file, which you can modify in `{global_defaults_file}`")
log.info(
"If you want to create a local configuration file, you can create a file named"
f" `{__config_file__}`, or run `manimgl --config`"
)
custom_config = get_custom_config()
check_temporary_storage(custom_config)
write_file = any([args.write_file, args.open, args.finder])
if args.transparent:
@@ -216,16 +290,22 @@ def get_configuration(args):
"quiet": args.quiet,
}
module = get_module(args.file)
if args.embed is None:
module = get_module(args.file)
else:
with insert_embed_line(args.file, int(args.embed)) as alt_file:
module = get_module(alt_file)
config = {
"module": module,
"scene_names": args.scene_names,
"file_writer_config": file_writer_config,
"quiet": args.quiet or args.write_all,
"write_all": args.write_all,
"skip_animations": args.skip_animations,
"start_at_animation_number": args.start_at_animation_number,
"preview": not write_file,
"end_at_animation_number": None,
"preview": not write_file,
"leave_progress_bars": args.leave_progress_bars,
}
@@ -234,9 +314,11 @@ 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:
if not (args.full_screen or custom_config["full_screen"]):
window_width //= 2
window_height = window_width * 9 // 16
config["window_config"] = {
@@ -253,10 +335,6 @@ def get_configuration(args):
else:
config["start_at_animation_number"] = int(stan)
config["skip_animations"] = any([
args.skip_animations,
args.start_at_animation_number,
])
return config
@@ -292,9 +370,9 @@ def get_camera_configuration(args, custom_config):
try:
bg_color = args.color or custom_config["style"]["background_color"]
camera_config["background_color"] = colour.Color(bg_color)
except AttributeError as err:
print("Please use a valid color")
print(err)
except ValueError as err:
log.error("Please use a valid color")
log.error(err)
sys.exit(2)
# If rendering a transparent image/move, make sure the

View File

@@ -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

@@ -34,6 +34,7 @@ style:
# the window on the monitor, e.g. "960,540"
window_position: UR
window_monitor: 0
full_screen: False
# If break_into_partial_movies is set to True, then many small
# files will be written corresponding to each Scene.play and
# Scene.wait call, and these files will then be combined

View File

@@ -1,9 +1,10 @@
import inspect
import sys
import logging
import copy
from manimlib.scene.scene import Scene
from manimlib.config import get_custom_config
from manimlib.logger import log
class BlankScene(Scene):
@@ -38,11 +39,14 @@ def prompt_user_for_choice(scene_classes):
"\nScene Name or Number: "
)
return [
name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str)-1]
name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str) - 1]
for split_str in user_input.replace(" ", "").split(",")
]
except IndexError:
log.error("Invalid scene number")
sys.exit(2)
except KeyError:
logging.log(logging.ERROR, "Invalid scene")
log.error("Invalid scene name")
sys.exit(2)
except EOFError:
sys.exit(1)
@@ -64,6 +68,26 @@ def get_scene_config(config):
])
def compute_total_frames(scene_class, scene_config):
"""
When a scene is being written to file, a copy of the scene is run with
skip_animations set to true so as to count how many frames it will require.
This allows for a total progress bar on rendering, and also allows runtime
errors to be exposed preemptively for long running scenes. The final frame
is saved by default, so that one can more quickly check that the last frame
looks as expected.
"""
pre_config = copy.deepcopy(scene_config)
pre_config["file_writer_config"]["write_to_movie"] = False
pre_config["file_writer_config"]["save_last_frame"] = True
pre_config["file_writer_config"]["quiet"] = True
pre_config["skip_animations"] = True
pre_scene = scene_class(**pre_config)
pre_scene.run()
total_time = pre_scene.time - pre_scene.skip_time
return int(total_time * scene_config["camera_config"]["frame_rate"])
def get_scenes_to_render(scene_classes, scene_config, config):
if config["write_all"]:
return [sc(**scene_config) for sc in scene_classes]
@@ -73,15 +97,15 @@ def get_scenes_to_render(scene_classes, scene_config, config):
found = False
for scene_class in scene_classes:
if scene_class.__name__ == scene_name:
fw_config = scene_config["file_writer_config"]
if fw_config["write_to_movie"]:
fw_config["total_frames"] = compute_total_frames(scene_class, scene_config)
scene = scene_class(**scene_config)
result.append(scene)
found = True
break
if not found and (scene_name != ""):
logging.log(
logging.ERROR,
f"No scene named {scene_name} found",
)
log.error(f"No scene named {scene_name} found")
if result:
return result
if len(scene_classes) == 1:

13
manimlib/logger.py Normal file
View File

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

View File

@@ -0,0 +1,116 @@
import numpy as np
import pathops
from manimlib.mobject.types.vectorized_mobject import VMobject
# Boolean operations between 2D mobjects
# Borrowed from from https://github.com/ManimCommunity/manim/
def _convert_vmobject_to_skia_path(vmobject):
path = pathops.Path()
subpaths = vmobject.get_subpaths_from_points(vmobject.get_all_points())
for subpath in subpaths:
quads = vmobject.get_bezier_tuples_from_points(subpath)
start = subpath[0]
path.moveTo(*start[:2])
for p0, p1, p2 in quads:
path.quadTo(*p1[:2], *p2[:2])
if vmobject.consider_points_equals(subpath[0], subpath[-1]):
path.close()
return path
def _convert_skia_path_to_vmobject(path, vmobject):
PathVerb = pathops.PathVerb
current_path_start = np.array([0.0, 0.0, 0.0])
for path_verb, points in path:
if path_verb == PathVerb.CLOSE:
vmobject.add_line_to(current_path_start)
else:
points = np.hstack((np.array(points), np.zeros((len(points), 1))))
if path_verb == PathVerb.MOVE:
for point in points:
current_path_start = point
vmobject.start_new_path(point)
elif path_verb == PathVerb.CUBIC:
vmobject.add_cubic_bezier_curve_to(*points)
elif path_verb == PathVerb.LINE:
vmobject.add_line_to(points[0])
elif path_verb == PathVerb.QUAD:
vmobject.add_quadratic_bezier_curve_to(*points)
else:
raise Exception(f"Unsupported: {path_verb}")
return vmobject.reverse_points()
class Union(VMobject):
def __init__(self, *vmobjects, **kwargs):
if len(vmobjects) < 2:
raise ValueError("At least 2 mobjects needed for Union.")
super().__init__(**kwargs)
outpen = pathops.Path()
paths = [
_convert_vmobject_to_skia_path(vmobject)
for vmobject in vmobjects
]
pathops.union(paths, outpen.getPen())
_convert_skia_path_to_vmobject(outpen, self)
class Difference(VMobject):
def __init__(self, subject, clip, **kwargs):
super().__init__(**kwargs)
outpen = pathops.Path()
pathops.difference(
[_convert_vmobject_to_skia_path(subject)],
[_convert_vmobject_to_skia_path(clip)],
outpen.getPen(),
)
_convert_skia_path_to_vmobject(outpen, self)
class Intersection(VMobject):
def __init__(self, *vmobjects, **kwargs):
if len(vmobjects) < 2:
raise ValueError("At least 2 mobjects needed for Intersection.")
super().__init__(**kwargs)
outpen = pathops.Path()
pathops.intersection(
[_convert_vmobject_to_skia_path(vmobjects[0])],
[_convert_vmobject_to_skia_path(vmobjects[1])],
outpen.getPen(),
)
new_outpen = outpen
for _i in range(2, len(vmobjects)):
new_outpen = pathops.Path()
pathops.intersection(
[outpen],
[_convert_vmobject_to_skia_path(vmobjects[_i])],
new_outpen.getPen(),
)
outpen = new_outpen
_convert_skia_path_to_vmobject(outpen, self)
class Exclusion(VMobject):
def __init__(self, *vmobjects, **kwargs):
if len(vmobjects) < 2:
raise ValueError("At least 2 mobjects needed for Exclusion.")
super().__init__(**kwargs)
outpen = pathops.Path()
pathops.xor(
[_convert_vmobject_to_skia_path(vmobjects[0])],
[_convert_vmobject_to_skia_path(vmobjects[1])],
outpen.getPen(),
)
new_outpen = outpen
for _i in range(2, len(vmobjects)):
new_outpen = pathops.Path()
pathops.xor(
[outpen],
[_convert_vmobject_to_skia_path(vmobjects[_i])],
new_outpen.getPen(),
)
outpen = new_outpen
_convert_skia_path_to_vmobject(outpen, self)

View File

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

View File

@@ -1,3 +1,4 @@
from abc import abstractmethod
import numpy as np
import numbers
@@ -10,6 +11,7 @@ from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.number_line import NumberLine
from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.config_ops import digest_config
from manimlib.utils.config_ops import merge_dicts_recursively
from manimlib.utils.simple_functions import binary_search
from manimlib.utils.space_ops import angle_of_vector
@@ -25,13 +27,18 @@ class CoordinateSystem():
"""
CONFIG = {
"dimension": 2,
"x_range": np.array([-8, 8, 1.0]),
"y_range": np.array([-4, 4, 1.0]),
"width": None,
"height": None,
"num_sampled_graph_points_per_tick": 5,
"default_x_range": [-8.0, 8.0, 1.0],
"default_y_range": [-4.0, 4.0, 1.0],
"width": FRAME_WIDTH,
"height": FRAME_HEIGHT,
"num_sampled_graph_points_per_tick": 20,
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
self.x_range = np.array(self.default_x_range)
self.y_range = np.array(self.default_y_range)
def coords_to_point(self, *coords):
raise Exception("Not implemented")
@@ -46,9 +53,17 @@ class CoordinateSystem():
"""Abbreviation for point_to_coords"""
return self.point_to_coords(point)
def get_origin(self):
return self.c2p(*[0] * self.dimension)
@abstractmethod
def get_axes(self):
raise Exception("Not implemented")
@abstractmethod
def get_all_ranges(self):
raise Exception("Not implemented")
def get_axis(self, index):
return self.get_axes()[index]
@@ -121,6 +136,7 @@ class CoordinateSystem():
**kwargs
)
graph.underlying_function = function
graph.x_range = x_range
return graph
def get_parametric_curve(self, function, **kwargs):
@@ -138,14 +154,14 @@ class CoordinateSystem():
else:
alpha = binary_search(
function=lambda a: self.point_to_coords(
graph.point_from_proportion(a)
graph.quick_point_from_proportion(a)
)[0],
target=x,
lower_bound=self.x_range[0],
upper_bound=self.x_range[1],
)
if alpha is not None:
return graph.point_from_proportion(alpha)
return graph.quick_point_from_proportion(alpha)
else:
return None
@@ -276,7 +292,9 @@ class Axes(VGroup, CoordinateSystem):
x_range=None,
y_range=None,
**kwargs):
super().__init__(**kwargs)
CoordinateSystem.__init__(self, **kwargs)
VGroup.__init__(self, **kwargs)
if x_range is not None:
self.x_range[:len(x_range)] = x_range
if y_range is not None:
@@ -319,6 +337,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 +355,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 +369,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 +390,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 +416,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,
}
@@ -425,7 +453,7 @@ class NumberPlane(Axes):
return lines1, lines2
def get_lines_parallel_to_axis(self, axis1, axis2):
freq = axis1.x_step
freq = axis2.x_step
ratio = self.faded_line_ratio
line = Line(axis1.get_start(), axis1.get_end())
dense_freq = (1 + ratio)
@@ -485,15 +513,15 @@ class ComplexPlane(NumberPlane):
def p2n(self, point):
return self.point_to_number(point)
def get_default_coordinate_values(self):
def get_default_coordinate_values(self, skip_first=True):
x_numbers = self.get_x_axis().get_tick_range()[1:]
y_numbers = self.get_y_axis().get_tick_range()[1:]
y_numbers = [complex(0, y) for y in y_numbers if y != 0]
return [*x_numbers, *y_numbers]
def add_coordinate_labels(self, numbers=None, **kwargs):
def add_coordinate_labels(self, numbers=None, skip_first=True, **kwargs):
if numbers is None:
numbers = self.get_default_coordinate_values()
numbers = self.get_default_coordinate_values(skip_first)
self.coordinate_labels = VGroup()
for number in numbers:
@@ -506,6 +534,15 @@ class ComplexPlane(NumberPlane):
axis = self.get_x_axis()
value = z.real
number_mob = axis.get_number_mobject(value, **kwargs)
# For i and -i, remove the "1"
if z.imag == 1:
number_mob.remove(number_mob[0])
if z.imag == -1:
number_mob.remove(number_mob[1])
number_mob[0].next_to(
number_mob[1], LEFT,
buff=number_mob[0].get_width() / 4
)
self.coordinate_labels.add(number_mob)
self.add(self.coordinate_labels)
return self

View File

@@ -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

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

View File

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

View File

@@ -57,18 +57,18 @@ 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):
"""
Matrix can either either include numbres, tex_strings,
Matrix can either include numbers, tex_strings,
or mobjects
"""
VMobject.__init__(self, **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

@@ -4,6 +4,7 @@ import random
import sys
import moderngl
from functools import wraps
from collections.abc import Iterable
import numpy as np
@@ -21,6 +22,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
@@ -42,10 +44,13 @@ class Mobject(object):
"opacity": 1,
"dim": 3, # TODO, get rid of this
# Lighting parameters
# Positive gloss up to 1 makes it reflect the light.
"gloss": 0.0,
# Positive shadow up to 1 makes a side opposite the light darker
# ...
# Larger reflectiveness makes things brighter when facing the light
"reflectiveness": 0.0,
# Larger shadow makes faces opposite the light darker
"shadow": 0.0,
# Makes parts bright where light gets reflected toward the camera
"gloss": 0.0,
# For shaders
"shader_folder": "",
"render_primitive": moderngl.TRIANGLE_STRIP,
@@ -56,9 +61,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):
@@ -83,6 +86,14 @@ class Mobject(object):
def __str__(self):
return self.__class__.__name__
def __add__(self, other: 'Mobject') -> 'Mobject':
assert(isinstance(other, Mobject))
return self.get_group_class()(self, other)
def __mul__(self, other: 'int') -> 'Mobject':
assert(isinstance(other, int))
return self.replicate(other)
def init_data(self):
self.data = {
"points": np.zeros((0, 3)),
@@ -95,10 +106,11 @@ class Mobject(object):
"is_fixed_in_frame": float(self.is_fixed_in_frame),
"gloss": self.gloss,
"shadow": self.shadow,
"reflectiveness": self.reflectiveness,
}
def init_colors(self):
self.set_color(self.color, self.opacity)
def init_colors(self, override=True):
self.set_color(self.color, self.opacity, override)
def init_points(self):
# Typically implemented in subclass, unlpess purposefully left blank
@@ -146,6 +158,7 @@ class Mobject(object):
for mob in self.get_family():
for key in mob.data:
mob.data[key] = mob.data[key][::-1]
self.refresh_unit_normal()
return self
def apply_points_function(self, func, about_point=None, about_edge=ORIGIN, works_on_bounding_box=False):
@@ -176,6 +189,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"]
@@ -300,6 +314,11 @@ class Mobject(object):
self.assemble_family()
return self
def insert_submobject(self, index, new_submob):
self.submobjects.insert(index, new_submob)
self.assemble_family()
return self
def set_submobjects(self, submobject_list):
self.remove(*self.submobjects)
self.add(*submobject_list)
@@ -365,14 +384,17 @@ class Mobject(object):
self.center()
return self
def replicate(self, n):
return self.get_group_class()(
*(self.copy() for x in range(n))
)
def get_grid(self, n_rows, n_cols, height=None, **kwargs):
"""
Returns a new mobject containing multiple copies of this one
arranged in a grid
"""
grid = self.get_group_class()(
*(self.copy() for n in range(n_rows * n_cols))
)
grid = self.replicate(n_rows * n_cols)
grid.arrange_in_grid(n_rows, n_cols, **kwargs)
if height is not None:
grid.set_height(height)
@@ -383,6 +405,7 @@ class Mobject(object):
self.submobjects.sort(key=submob_func)
else:
self.submobjects.sort(key=lambda m: point_to_num_func(m.get_center()))
self.assemble_family()
return self
def shuffle(self, recurse=False):
@@ -390,6 +413,7 @@ class Mobject(object):
for submob in self.submobjects:
submob.shuffle(recurse=True)
random.shuffle(self.submobjects)
self.assemble_family()
return self
# Copying
@@ -408,8 +432,10 @@ class Mobject(object):
for key in self.data:
copy_mobject.data[key] = self.data[key].copy()
# TODO, are uniforms ever numpy arrays?
copy_mobject.uniforms = dict(self.uniforms)
for key in self.uniforms:
if isinstance(self.uniforms[key], np.ndarray):
copy_mobject.uniforms[key] = self.uniforms[key].copy()
copy_mobject.submobjects = []
copy_mobject.add(*[sm.copy() for sm in self.submobjects])
@@ -504,7 +530,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 +587,7 @@ class Mobject(object):
)
return self
def scale(self, scale_factor, **kwargs):
def scale(self, scale_factor, min_scale_factor=1e-8, about_point=None, about_edge=ORIGIN):
"""
Default behavior is to scale about the center of the mobject.
The argument about_edge can be a vector, indicating which side of
@@ -571,13 +597,25 @@ class Mobject(object):
Otherwise, if about_point is given a value, scaling is done with
respect to that point.
"""
if isinstance(scale_factor, Iterable):
scale_factor = np.array(scale_factor).clip(min=min_scale_factor)
else:
scale_factor = max(scale_factor, min_scale_factor)
self.apply_points_function(
lambda points: scale_factor * points,
about_point=about_point,
about_edge=about_edge,
works_on_bounding_box=True,
**kwargs
)
for mob in self.get_family():
mob._handle_scale_side_effects(scale_factor)
return self
def _handle_scale_side_effects(self, scale_factor):
# In case subclasses, such as DecimalNumber, need to make
# any other changes when the size gets altered
pass
def stretch(self, factor, dim, **kwargs):
def func(points):
points[:, dim] *= factor
@@ -753,7 +791,7 @@ class Mobject(object):
return self.rescale_to_fit(height, 1, stretch=True, **kwargs)
def stretch_to_fit_depth(self, depth, **kwargs):
return self.rescale_to_fit(depth, 1, stretch=True, **kwargs)
return self.rescale_to_fit(depth, 2, stretch=True, **kwargs)
def set_width(self, width, stretch=False, **kwargs):
return self.rescale_to_fit(width, 0, stretch=stretch, **kwargs)
@@ -764,6 +802,21 @@ class Mobject(object):
def set_depth(self, depth, stretch=False, **kwargs):
return self.rescale_to_fit(depth, 2, stretch=stretch, **kwargs)
def set_max_width(self, max_width, **kwargs):
if self.get_width() > max_width:
self.set_width(max_width, **kwargs)
return self
def set_max_height(self, max_height, **kwargs):
if self.get_height() > max_height:
self.set_height(max_height, **kwargs)
return self
def set_max_depth(self, max_depth, **kwargs):
if self.get_depth() > max_depth:
self.set_depth(max_depth, **kwargs)
return self
def set_coord(self, value, dim, direction=ORIGIN):
curr = self.get_coord(dim, direction)
shift_vect = np.zeros(self.dim)
@@ -822,7 +875,6 @@ class Mobject(object):
return self
def put_start_and_end_on(self, start, end):
# TODO, this doesn't currently work in 3d
curr_start, curr_end = self.get_start_and_end()
curr_vect = curr_end - curr_start
if np.all(curr_vect == 0):
@@ -834,14 +886,40 @@ class Mobject(object):
)
self.rotate(
angle_of_vector(target_vect) - angle_of_vector(curr_vect),
about_point=curr_start
)
self.shift(start - curr_start)
self.rotate(
np.arctan2(curr_vect[2], get_norm(curr_vect[:2])) - np.arctan2(target_vect[2], get_norm(target_vect[:2])),
axis=np.array([-target_vect[1], target_vect[0], 0]),
)
self.shift(start - self.get_start())
return self
# Color functions
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 +948,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 +957,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)
@@ -912,12 +990,12 @@ class Mobject(object):
def fade(self, darkness=0.5, recurse=True):
self.set_opacity(1.0 - darkness, recurse=recurse)
def get_gloss(self):
return self.uniforms["gloss"]
def get_reflectiveness(self):
return self.uniforms["reflectiveness"]
def set_gloss(self, gloss, recurse=True):
def set_reflectiveness(self, reflectiveness, recurse=True):
for mob in self.get_family(recurse):
mob.uniforms["gloss"] = gloss
mob.uniforms["reflectiveness"] = reflectiveness
return self
def get_shadow(self):
@@ -928,6 +1006,14 @@ class Mobject(object):
mob.uniforms["shadow"] = shadow
return self
def get_gloss(self):
return self.uniforms["gloss"]
def set_gloss(self, gloss, recurse=True):
for mob in self.get_family(recurse):
mob.uniforms["gloss"] = gloss
return self
# Background rectangle
def add_background_rectangle(self, color=None, opacity=0.75, **kwargs):
@@ -1039,14 +1125,16 @@ class Mobject(object):
def get_start(self):
self.throw_error_if_no_points()
return np.array(self.get_points()[0])
return self.get_points()[0].copy()
def get_end(self):
self.throw_error_if_no_points()
return np.array(self.get_points()[-1])
return self.get_points()[-1].copy()
def get_start_and_end(self):
return self.get_start(), self.get_end()
self.throw_error_if_no_points()
points = self.get_points()
return (points[0].copy(), points[-1].copy())
def point_from_proportion(self, alpha):
points = self.get_points()
@@ -1307,11 +1395,13 @@ class Mobject(object):
@affects_shader_info_id
def fix_in_frame(self):
self.uniforms["is_fixed_in_frame"] = 1.0
self.is_fixed_in_frame = True
return self
@affects_shader_info_id
def unfix_from_frame(self):
self.uniforms["is_fixed_in_frame"] = 0.0
self.is_fixed_in_frame = False
return self
@affects_shader_info_id
@@ -1412,7 +1502,7 @@ class Mobject(object):
return result
def check_data_alignment(self, array, data_key):
# Makes sure that self.data[key] can be brodcast into
# Makes sure that self.data[key] can be broadcast into
# the given array, meaning its length has to be either 1
# or the length of the array
d_len = len(self.data[data_key])
@@ -1554,6 +1644,10 @@ class Group(Mobject):
Mobject.__init__(self, **kwargs)
self.add(*mobjects)
def __add__(self, other: 'Mobject' or 'Group'):
assert(isinstance(other, Mobject))
return self.add(other)
class Point(Mobject):
CONFIG = {

View File

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

View File

@@ -5,9 +5,7 @@ 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
class NumberLine(Line):
@@ -84,7 +82,7 @@ class NumberLine(Line):
ticks = VGroup()
for x in self.get_tick_range():
size = self.tick_size
if x in self.numbers_with_elongated_ticks:
if np.isclose(self.numbers_with_elongated_ticks, x).any():
size *= self.longer_tick_multiple
ticks.add(self.get_tick(x, size))
self.add(ticks)
@@ -107,11 +105,13 @@ class NumberLine(Line):
return interpolate(self.get_start(), self.get_end(), alpha)
def point_to_number(self, point):
start, end = self.get_start_and_end()
unit_vect = normalize(end - start)
points = self.get_points()
start = points[0]
end = points[-1]
vect = end - start
proportion = fdiv(
np.dot(point - start, unit_vect),
np.dot(end - start, unit_vect),
np.dot(point - start, vect),
np.dot(end - start, vect),
)
return interpolate(self.x_min, self.x_max, proportion)
@@ -144,7 +144,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 +155,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

@@ -36,7 +36,9 @@ class DecimalNumber(VMobject):
# Add non-numerical bits
if self.show_ellipsis:
self.add(self.string_to_mob("..."))
dots = self.string_to_mob("...")
dots.arrange(RIGHT, buff=2 * dots[0].get_width())
self.add(dots)
if self.unit is not None:
self.unit_sign = self.string_to_mob(self.unit, SingleStringTex)
self.add(self.unit_sign)
@@ -128,16 +130,15 @@ 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 = list(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):
super().scale(scale_factor, **kwargs)
def _handle_scale_side_effects(self, scale_factor):
self.data["font_size"] *= scale_factor
return self
def get_value(self):
return self.number

View File

@@ -149,7 +149,9 @@ class BarChart(VGroup):
"height": 4,
"width": 6,
"n_ticks": 4,
"include_x_ticks": False,
"tick_width": 0.2,
"tick_height": 0.15,
"label_y_axis": True,
"y_axis_label_height": 0.25,
"max_value": 1,
@@ -165,6 +167,7 @@ class BarChart(VGroup):
if self.max_value is None:
self.max_value = max(values)
self.n_ticks_x = len(values)
self.add_axes()
self.add_bars(values)
self.center()
@@ -172,31 +175,42 @@ class BarChart(VGroup):
def add_axes(self):
x_axis = Line(self.tick_width * LEFT / 2, self.width * RIGHT)
y_axis = Line(MED_LARGE_BUFF * DOWN, self.height * UP)
ticks = VGroup()
y_ticks = VGroup()
heights = np.linspace(0, self.height, self.n_ticks + 1)
values = np.linspace(0, self.max_value, self.n_ticks + 1)
for y, value in zip(heights, values):
tick = Line(LEFT, RIGHT)
tick.set_width(self.tick_width)
tick.move_to(y * UP)
ticks.add(tick)
y_axis.add(ticks)
y_tick = Line(LEFT, RIGHT)
y_tick.set_width(self.tick_width)
y_tick.move_to(y * UP)
y_ticks.add(y_tick)
y_axis.add(y_ticks)
if self.include_x_ticks == True:
x_ticks = VGroup()
widths = np.linspace(0, self.width, self.n_ticks_x + 1)
label_values = np.linspace(0, len(self.bar_names), self.n_ticks_x + 1)
for x, value in zip(widths, label_values):
x_tick = Line(UP, DOWN)
x_tick.set_height(self.tick_height)
x_tick.move_to(x * RIGHT)
x_ticks.add(x_tick)
x_axis.add(x_ticks)
self.add(x_axis, y_axis)
self.x_axis, self.y_axis = x_axis, y_axis
if self.label_y_axis:
labels = VGroup()
for tick, value in zip(ticks, values):
for y_tick, value in zip(y_ticks, values):
label = Tex(str(np.round(value, 2)))
label.set_height(self.y_axis_label_height)
label.next_to(tick, LEFT, SMALL_BUFF)
label.next_to(y_tick, LEFT, SMALL_BUFF)
labels.add(label)
self.y_axis_labels = labels
self.add(labels)
def add_bars(self, values):
buff = float(self.width) / (2 * len(values) + 1)
buff = float(self.width) / (2 * len(values))
bars = VGroup()
for i, value in enumerate(values):
bar = Rectangle(
@@ -205,7 +219,7 @@ class BarChart(VGroup):
stroke_width=self.bar_stroke_width,
fill_opacity=self.bar_fill_opacity,
)
bar.move_to((2 * i + 1) * buff * RIGHT, DOWN + LEFT)
bar.move_to((2 * i + 0.5) * buff * RIGHT, DOWN + LEFT * 5)
bars.add(bar)
bars.set_color_by_gradient(*self.bar_colors)

View File

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

@@ -1,5 +1,6 @@
import numpy as np
import math
import copy
from manimlib.animation.composition import AnimationGroup
from manimlib.constants import *
@@ -8,6 +9,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 +63,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):
@@ -86,6 +89,7 @@ class BraceLabel(VMobject):
CONFIG = {
"label_constructor": Tex,
"label_scale": 1,
"label_buff": DEFAULT_MOBJECT_TO_MOBJECT_BUFFER
}
def __init__(self, obj, text, brace_direction=DOWN, **kwargs):
@@ -102,7 +106,7 @@ class BraceLabel(VMobject):
if self.label_scale != 1:
self.label.scale(self.label_scale)
self.brace.put_at_tip(self.label)
self.brace.put_at_tip(self.label, buff=self.label_buff)
self.set_submobjects([self.brace, self.label])
def creation_anim(self, label_anim=FadeIn, brace_anim=GrowFromCenter):

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,10 @@ class Lightbulb(SVGMobject):
"fill_opacity": 0,
}
def __init__(self, **kwargs):
super().__init__("lightbulb", **kwargs)
self.insert_n_curves(25)
class Speedometer(VMobject):
CONFIG = {
@@ -198,12 +201,11 @@ class Laptop(VGroup):
class VideoIcon(SVGMobject):
CONFIG = {
"file_name": "video_icon",
"width": FRAME_WIDTH / 12.,
}
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
super().__init__(file_name="video_icon", **kwargs)
self.center()
self.set_width(self.width)
self.set_stroke(color=WHITE, width=0)

View File

@@ -0,0 +1,657 @@
import itertools as it
import re
import sys
from types import MethodType
from manimlib.constants import BLACK
from manimlib.mobject.svg.svg_mobject import SVGMobject
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.color import color_to_int_rgb
from manimlib.utils.iterables import adjacent_pairs
from manimlib.utils.iterables import remove_list_redundancies
from manimlib.utils.tex_file_writing import tex_to_svg_file
from manimlib.utils.tex_file_writing import get_tex_config
from manimlib.utils.tex_file_writing import display_during_execution
from manimlib.logger import log
SCALE_FACTOR_PER_FONT_POINT = 0.001
TEX_HASH_TO_MOB_MAP = {}
def _contains(span_0, span_1):
return span_0[0] <= span_1[0] and span_1[1] <= span_0[1]
def _get_neighbouring_pairs(iterable):
return list(adjacent_pairs(iterable))[:-1]
class _TexSVG(SVGMobject):
CONFIG = {
"color": BLACK,
"height": None,
"path_string_config": {
"should_subdivide_sharp_curves": True,
"should_remove_null_curves": True,
},
}
@staticmethod
def color_to_label(fill_color):
r, g, b = color_to_int_rgb(fill_color)
rg = r * 256 + g
return rg * 256 + b
def parse_labels(self):
for glyph in self:
glyph.glyph_label = _TexSVG.color_to_label(glyph.fill_color)
return self
class _TexSpan(object):
def __init__(self, script_type, label):
# `script_type`: 0 for normal, 1 for subscript, 2 for superscript.
# Only those spans with `script_type == 0` will be colored.
self.script_type = script_type
self.label = label
self.containing_labels = []
def __repr__(self):
return "_TexSpan(" + ", ".join([
attrib_name + "=" + str(getattr(self, attrib_name))
for attrib_name in ["script_type", "label", "containing_labels"]
]) + ")"
class _TexParser(object):
def __init__(self, tex_string, additional_substrings):
self.tex_string = tex_string
self.tex_spans_dict = {}
self.current_label = -1
self.brace_index_pairs = self.get_brace_index_pairs()
self.existing_color_command_spans = self.get_existing_color_command_spans()
self.has_existing_color_commands = any(self.existing_color_command_spans.values())
self.specified_substring_spans = []
self.add_tex_span((0, len(tex_string)))
self.break_up_by_double_braces()
self.break_up_by_scripts()
self.break_up_by_additional_substrings(additional_substrings)
self.specified_substrings = remove_list_redundancies([
tex_string[slice(*span_tuple)]
for span_tuple in self.specified_substring_spans
])
self.check_if_overlap()
self.analyse_containing_labels()
@staticmethod
def label_to_color_tuple(rgb):
# Get a unique color different from black.
rg, b = divmod(rgb, 256)
r, g = divmod(rg, 256)
return r, g, b
def add_tex_span(self, span_tuple, script_type=0, label=-1):
if span_tuple in self.tex_spans_dict:
return
if script_type == 0:
# Should be additionally labelled.
self.current_label += 1
label = self.current_label
tex_span = _TexSpan(script_type, label)
self.tex_spans_dict[span_tuple] = tex_span
def get_brace_index_pairs(self):
result = []
left_brace_indices = []
for match_obj in re.finditer(r"(\\*)(\{|\})", self.tex_string):
# Braces following even numbers of backslashes are counted.
if len(match_obj.group(1)) % 2 == 1:
continue
if match_obj.group(2) == "{":
left_brace_index = match_obj.span(2)[0]
left_brace_indices.append(left_brace_index)
else:
left_brace_index = left_brace_indices.pop()
right_brace_index = match_obj.span(2)[1]
result.append((left_brace_index, right_brace_index))
if left_brace_indices:
self.raise_tex_parsing_error("unmatched braces")
return result
def get_existing_color_command_spans(self):
tex_string = self.tex_string
color_related_commands_dict = _TexParser.get_color_related_commands_dict()
commands = color_related_commands_dict.keys()
result = {
command_name: []
for command_name in commands
}
brace_index_pairs = self.brace_index_pairs
pattern = "|".join([
re.escape(command_name)
for command_name in commands
])
for match_obj in re.finditer(pattern, tex_string):
span_tuple = match_obj.span()
command_begin_index = span_tuple[0]
command_name = match_obj.group()
n_braces = color_related_commands_dict[command_name]
for _ in range(n_braces):
span_tuple = min(filter(
lambda t: t[0] >= span_tuple[1],
brace_index_pairs
))
result[command_name].append(
(command_begin_index, span_tuple[1])
)
return result
def break_up_by_double_braces(self):
# Match paired double braces (`{{...}}`).
skip_pair = False
for prev_span_tuple, span_tuple in _get_neighbouring_pairs(
self.brace_index_pairs
):
if skip_pair:
skip_pair = False
continue
if all([
span_tuple[0] == prev_span_tuple[0] - 1,
span_tuple[1] == prev_span_tuple[1] + 1
]):
self.add_tex_span(span_tuple)
self.specified_substring_spans.append(span_tuple)
skip_pair = True
def break_up_by_scripts(self):
# Match subscripts & superscripts.
tex_string = self.tex_string
brace_indices_dict = dict(self.brace_index_pairs)
for match_obj in re.finditer(r"((?<!\\)(_|\^)\s*)|(\s+(_|\^)\s*)", tex_string):
script_type = 1 if "_" in match_obj.group() else 2
token_begin, token_end = match_obj.span()
if token_end in brace_indices_dict:
content_span = (token_end, brace_indices_dict[token_end])
else:
content_match_obj = re.match(r"\w|\\[a-zA-Z]+", tex_string[token_end:])
if not content_match_obj:
self.raise_tex_parsing_error("unclear subscript/superscript")
content_span = tuple([
index + token_end for index in content_match_obj.span()
])
self.add_tex_span(content_span)
label = self.tex_spans_dict[content_span].label
self.add_tex_span(
(token_begin, content_span[1]),
script_type=script_type,
label=label
)
def break_up_by_additional_substrings(self, additional_substrings):
tex_string = self.tex_string
all_span_tuples = []
for string in additional_substrings:
# Only match non-crossing strings.
for match_obj in re.finditer(re.escape(string), tex_string):
all_span_tuples.append(match_obj.span())
script_spans_dict = dict([
span_tuple[::-1]
for span_tuple, tex_span in self.tex_spans_dict.items()
if tex_span.script_type != 0
])
for span_begin, span_end in all_span_tuples:
if span_end in script_spans_dict.values():
# Deconstruct spans with subscripts & superscripts.
while span_end in script_spans_dict:
span_end = script_spans_dict[span_end]
if span_begin >= span_end:
continue
span_tuple = (span_begin, span_end)
self.add_tex_span(span_tuple)
self.specified_substring_spans.append(span_tuple)
def check_if_overlap(self):
span_tuples = sorted(
self.tex_spans_dict.keys(),
key=lambda t: (t[0], -t[1])
)
overlapping_span_pairs = []
for i, span_0 in enumerate(span_tuples):
for span_1 in span_tuples[i + 1 :]:
if span_0[1] <= span_1[0]:
continue
if span_0[1] < span_1[1]:
overlapping_span_pairs.append((span_0, span_1))
if overlapping_span_pairs:
tex_string = self.tex_string
log.error("Overlapping substring pairs occur in MTex:")
for span_tuple_pair in overlapping_span_pairs:
log.error(", ".join(
f"\"{tex_string[slice(*span_tuple)]}\""
for span_tuple in span_tuple_pair
))
raise ValueError
def analyse_containing_labels(self):
for span_0, tex_span_0 in self.tex_spans_dict.items():
if tex_span_0.script_type != 0:
continue
for span_1, tex_span_1 in self.tex_spans_dict.items():
if _contains(span_1, span_0):
tex_span_1.containing_labels.append(tex_span_0.label)
def get_labelled_tex_string(self):
tex_string = self.tex_string
if self.current_label == 0 and not self.has_existing_color_commands:
return tex_string
# Remove the span of extire tex string.
indices_with_labels = sorted([
(span_tuple[i], i, span_tuple[1 - i], tex_span.label)
for span_tuple, tex_span in self.tex_spans_dict.items()
if tex_span.script_type == 0
for i in range(2)
], key=lambda t: (t[0], -t[1], -t[2]))[1:]
# Prevent from "\\color[RGB]" being replaced.
# Hopefully tex string doesn't contain such a substring...
color_command_placeholder = "{{\\iffalse \\fi}}"
result = tex_string[: indices_with_labels[0][0]]
for index_with_label, next_index_with_label in _get_neighbouring_pairs(
indices_with_labels
):
index, flag, _, label = index_with_label
next_index, *_ = next_index_with_label
# Adding one more pair of braces will help maintain the glyghs of tex file...
if flag == 0:
color_tuple = _TexParser.label_to_color_tuple(label)
result += "".join([
"{{",
color_command_placeholder,
"{",
",".join(map(str, color_tuple)),
"}"
])
else:
result += "}}"
result += tex_string[index : next_index]
color_related_commands_dict = _TexParser.get_color_related_commands_dict()
for command_name, command_spans in self.existing_color_command_spans.items():
if not command_spans:
continue
n_braces = color_related_commands_dict[command_name]
command_to_replace = command_name + n_braces * "{black}"
commands = {
tex_string[slice(*span_tuple)]
for span_tuple in command_spans
}
for command in commands:
result = result.replace(command, command_to_replace)
return result.replace(color_command_placeholder, "\\color[RGB]")
def raise_tex_parsing_error(self, message):
raise ValueError(f"Failed to parse tex ({message}): \"{self.tex_string}\"")
@staticmethod
def get_color_related_commands_dict():
# Only list a few commands that are commonly used.
return {
"\\color": 1,
"\\textcolor": 1,
"\\pagecolor": 1,
"\\colorbox": 1,
"\\fcolorbox": 2,
}
class MTex(VMobject):
CONFIG = {
"fill_opacity": 1.0,
"stroke_width": 0,
"font_size": 48,
"alignment": "\\centering",
"tex_environment": "align*",
"isolate": [],
"tex_to_color_map": {},
"use_plain_tex_file": False,
}
def __init__(self, tex_string, **kwargs):
super().__init__(**kwargs)
tex_string = tex_string.strip()
# Prevent from passing an empty string.
if not tex_string:
tex_string = "\\quad"
self.tex_string = tex_string
self.generate_mobject()
self.set_color_by_tex_to_color_map(self.tex_to_color_map)
self.scale(SCALE_FACTOR_PER_FONT_POINT * self.font_size)
def get_additional_substrings_to_break_up(self):
result = remove_list_redundancies([
*self.tex_to_color_map.keys(), *self.isolate
])
if "" in result:
result.remove("")
return result
def get_parser(self):
return _TexParser(self.tex_string, self.get_additional_substrings_to_break_up())
def generate_mobject(self):
tex_string = self.tex_string
tex_parser = self.get_parser()
self.tex_spans_dict = tex_parser.tex_spans_dict
self.specified_substrings = tex_parser.specified_substrings
fill_color = self.get_fill_color()
# Cannot simultaneously be false, so at least one file is generated.
require_labelled_tex_file = tex_parser.current_label != 0
require_plain_tex_file = any([
self.use_plain_tex_file,
tex_parser.has_existing_color_commands,
tex_parser.current_label == 0
])
if require_labelled_tex_file:
labelled_full_tex = self.get_tex_file_body(tex_parser.get_labelled_tex_string())
labelled_hash_val = hash(labelled_full_tex)
if labelled_hash_val in TEX_HASH_TO_MOB_MAP:
self.add(*TEX_HASH_TO_MOB_MAP[labelled_hash_val].copy())
else:
with display_during_execution(f"Writing \"{tex_string}\""):
labelled_svg_glyphs = MTex.get_svg_glyphs(labelled_full_tex)
labelled_svg_glyphs.parse_labels()
self.add(*labelled_svg_glyphs)
self.build_submobjects()
TEX_HASH_TO_MOB_MAP[labelled_hash_val] = self.copy()
if not require_plain_tex_file:
self.set_fill(color=fill_color)
return self
# require_plain_tex_file == True
self.set_submobjects([])
full_tex = self.get_tex_file_body(tex_string, fill_color=fill_color)
hash_val = hash(full_tex)
if hash_val in TEX_HASH_TO_MOB_MAP:
self.add(*TEX_HASH_TO_MOB_MAP[hash_val].copy())
else:
with display_during_execution(f"Writing \"{tex_string}\""):
svg_glyphs = MTex.get_svg_glyphs(full_tex)
if require_labelled_tex_file:
labelled_svg_mob = TEX_HASH_TO_MOB_MAP[labelled_hash_val]
for glyph, labelled_glyph in zip(svg_glyphs, it.chain(*labelled_svg_mob)):
glyph.glyph_label = labelled_glyph.glyph_label
else:
for glyph in svg_glyphs:
glyph.glyph_label = 0
self.add(*svg_glyphs)
self.build_submobjects()
TEX_HASH_TO_MOB_MAP[hash_val] = self.copy()
return self
def get_tex_file_body(self, new_tex, fill_color=None):
if self.tex_environment:
new_tex = "\n".join([
f"\\begin{{{self.tex_environment}}}",
new_tex,
f"\\end{{{self.tex_environment}}}"
])
if self.alignment:
new_tex = "\n".join([self.alignment, new_tex])
if fill_color:
int_rgb = color_to_int_rgb(fill_color)
color_command = "".join([
"\\color[RGB]",
"{",
",".join(map(str, int_rgb)),
"}"
])
new_tex = "\n".join(
[color_command, new_tex]
)
tex_config = get_tex_config()
return tex_config["tex_body"].replace(
tex_config["text_to_replace"],
new_tex
)
@staticmethod
def get_svg_glyphs(full_tex):
filename = tex_to_svg_file(full_tex)
return _TexSVG(filename)
def build_submobjects(self):
if not self.submobjects:
return
self.init_colors()
for glyph in self.submobjects:
glyph.set_fill(glyph.fill_color)
self.group_submobjects()
self.sort_scripts_in_tex_order()
self.assign_submob_tex_strings()
def group_submobjects(self):
# Simply pack together adjacent mobjects with the same label.
new_submobjects = []
def append_new_submobject(glyphs):
if glyphs:
submobject = VGroup(*glyphs)
submobject.submob_label = glyphs[0].glyph_label
new_submobjects.append(submobject)
new_glyphs = []
current_glyph_label = 0
for glyph in self.submobjects:
if glyph.glyph_label == current_glyph_label:
new_glyphs.append(glyph)
else:
append_new_submobject(new_glyphs)
new_glyphs = [glyph]
current_glyph_label = glyph.glyph_label
append_new_submobject(new_glyphs)
self.set_submobjects(new_submobjects)
def sort_scripts_in_tex_order(self):
# LaTeX always puts superscripts before subscripts.
# This function sorts the submobjects of scripts in the order of tex given.
tex_spans_dict = self.tex_spans_dict
index_and_span_list = sorted([
(index, span_tuple)
for span_tuple, tex_span in tex_spans_dict.items()
if tex_span.script_type != 0
for index in span_tuple
])
switch_range_pairs = []
for index_and_span_0, index_and_span_1 in _get_neighbouring_pairs(
index_and_span_list
):
index_0, span_tuple_0 = index_and_span_0
index_1, span_tuple_1 = index_and_span_1
if index_0 != index_1:
continue
if not all([
tex_spans_dict[span_tuple_0].script_type == 1,
tex_spans_dict[span_tuple_1].script_type == 2
]):
continue
submob_range_0 = self.range_of_part(
self.get_part_by_span_tuples([span_tuple_0])
)
submob_range_1 = self.range_of_part(
self.get_part_by_span_tuples([span_tuple_1])
)
switch_range_pairs.append((submob_range_0, submob_range_1))
switch_range_pairs.sort(key=lambda t: (t[0].stop, -t[0].start))
indices = list(range(len(self.submobjects)))
for submob_range_0, submob_range_1 in switch_range_pairs:
indices = [
*indices[: submob_range_1.start],
*indices[submob_range_0.start : submob_range_0.stop],
*indices[submob_range_1.stop : submob_range_0.start],
*indices[submob_range_1.start : submob_range_1.stop],
*indices[submob_range_0.stop :]
]
submobs = self.submobjects
self.set_submobjects([submobs[i] for i in indices])
def assign_submob_tex_strings(self):
# Not sure whether this is the best practice...
# This temporarily supports `TransformMatchingTex`.
tex_string = self.tex_string
tex_spans_dict = self.tex_spans_dict
# Use tex strings including "_", "^".
label_dict = {}
for span_tuple, tex_span in tex_spans_dict.items():
if tex_span.script_type != 0:
label_dict[tex_span.label] = span_tuple
else:
if tex_span.label not in label_dict:
label_dict[tex_span.label] = span_tuple
curr_labels = [submob.submob_label for submob in self.submobjects]
prev_labels = [curr_labels[-1], *curr_labels[:-1]]
next_labels = [*curr_labels[1:], curr_labels[0]]
tex_string_spans = []
for curr_label, prev_label, next_label in zip(
curr_labels, prev_labels, next_labels
):
curr_span_tuple = label_dict[curr_label]
prev_span_tuple = label_dict[prev_label]
next_span_tuple = label_dict[next_label]
containing_labels = tex_spans_dict[curr_span_tuple].containing_labels
tex_string_spans.append([
prev_span_tuple[1] if prev_label in containing_labels else curr_span_tuple[0],
next_span_tuple[0] if next_label in containing_labels else curr_span_tuple[1]
])
tex_string_spans[0][0] = label_dict[curr_labels[0]][0]
tex_string_spans[-1][1] = label_dict[curr_labels[-1]][1]
for submob, tex_string_span in zip(self.submobjects, tex_string_spans):
submob.tex_string = tex_string[slice(*tex_string_span)]
# Support `get_tex()` method here.
submob.get_tex = MethodType(lambda inst: inst.tex_string, submob)
def get_part_by_span_tuples(self, span_tuples):
tex_spans_dict = self.tex_spans_dict
labels = set(it.chain(*[
tex_spans_dict[span_tuple].containing_labels
for span_tuple in span_tuples
]))
return VGroup(*filter(
lambda submob: submob.submob_label in labels,
self.submobjects
))
def find_span_components_of_custom_span(self, custom_span_tuple):
tex_string = self.tex_string
span_choices = sorted(filter(
lambda t: _contains(custom_span_tuple, t),
self.tex_spans_dict.keys()
))
# Filter out spans that reach the farthest.
span_choices_dict = dict(span_choices)
span_begin, span_end = custom_span_tuple
result = []
while span_begin != span_end:
if span_begin not in span_choices_dict:
if tex_string[span_begin].strip():
return None
# Whitespaces may occur between spans.
span_begin += 1
continue
next_begin = span_choices_dict[span_begin]
result.append((span_begin, next_begin))
span_begin = next_begin
return result
def get_part_by_custom_span_tuple(self, custom_span_tuple):
span_tuples = self.find_span_components_of_custom_span(custom_span_tuple)
if span_tuples is None:
tex = self.tex_string[slice(*custom_span_tuple)]
raise ValueError(f"Failed to get span of tex: \"{tex}\"")
return self.get_part_by_span_tuples(span_tuples)
def get_parts_by_tex(self, tex):
return VGroup(*[
self.get_part_by_custom_span_tuple(match_obj.span())
for match_obj in re.finditer(re.escape(tex), self.tex_string)
])
def get_part_by_tex(self, tex, index=0):
all_parts = self.get_parts_by_tex(tex)
return all_parts[index]
def set_color_by_tex(self, tex, color):
self.get_parts_by_tex(tex).set_color(color)
return self
def set_color_by_tex_to_color_map(self, tex_to_color_map):
for tex, color in list(tex_to_color_map.items()):
try:
self.set_color_by_tex(tex, color)
except:
pass
return self
def indices_of_part(self, part):
indices = [
i for i, submob in enumerate(self.submobjects)
if submob in part
]
if not indices:
raise ValueError("Failed to find part in tex")
return indices
def indices_of_part_by_tex(self, tex, index=0):
part = self.get_part_by_tex(tex, index=index)
return self.indices_of_part(part)
def range_of_part(self, part):
indices = self.indices_of_part(part)
return range(indices[0], indices[-1] + 1)
def range_of_part_by_tex(self, tex, index=0):
part = self.get_part_by_tex(tex, index=index)
return self.range_of_part(part)
def index_of_part(self, part):
return self.indices_of_part(part)[0]
def index_of_part_by_tex(self, tex, index=0):
part = self.get_part_by_tex(tex, index=index)
return self.index_of_part(part)
def get_tex(self):
return self.tex_string
def get_all_isolated_substrings(self):
tex_string = self.tex_string
return remove_list_redundancies([
tex_string[slice(*span_tuple)]
for span_tuple in self.tex_spans_dict.keys()
])
def get_specified_substrings(self):
return self.specified_substrings
class MTexText(MTex):
CONFIG = {
"tex_environment": None,
}

View File

@@ -1,17 +1,19 @@
import itertools as it
import re
import string
import warnings
import os
import hashlib
from xml.dom import minidom
import cssselect2
from colour import web2hex
from xml.etree import ElementTree
from tinycss2 import serialize as css_serialize
from tinycss2 import parse_stylesheet, parse_declaration_list
from manimlib.constants import DEFAULT_STROKE_WIDTH
from manimlib.constants import ORIGIN, UP, DOWN, LEFT, RIGHT
from manimlib.constants import BLACK
from manimlib.constants import WHITE
from manimlib.constants import ORIGIN, UP, DOWN, LEFT, RIGHT, IN
from manimlib.constants import DEGREES, PI
from manimlib.mobject.geometry import Line
from manimlib.mobject.geometry import Circle
from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.geometry import RoundedRectangle
@@ -21,16 +23,78 @@ from manimlib.utils.color import *
from manimlib.utils.config_ops import digest_config
from manimlib.utils.directories import get_mobject_data_dir
from manimlib.utils.images import get_full_vector_image_path
from manimlib.utils.simple_functions import clip
from manimlib.logger import log
def string_to_numbers(num_string):
num_string = num_string.replace("-", ",-")
num_string = num_string.replace("e,-", "e-")
return [
float(s)
for s in re.split("[ ,]", num_string)
if s != ""
]
DEFAULT_STYLE = {
"fill": "black",
"stroke": "none",
"fill-opacity": "1",
"stroke-opacity": "1",
"stroke-width": 0,
}
def cascade_element_style(element, inherited):
style = inherited.copy()
for attr in DEFAULT_STYLE:
if element.get(attr):
style[attr] = element.get(attr)
if element.get("style"):
declarations = parse_declaration_list(element.get("style"))
for declaration in declarations:
style[declaration.name] = css_serialize(declaration.value)
return style
def parse_color(color):
color = color.strip()
if color[0:3] == "rgb":
splits = color[4:-1].strip().split(",")
if splits[0].strip()[-1] == "%":
parsed_rgbs = [float(i.strip()[:-1]) / 100.0 for i in splits]
else:
parsed_rgbs = [int(i) / 255.0 for i in splits]
return rgb_to_hex(parsed_rgbs)
else:
return web2hex(color)
def fill_default_values(style, default_style):
default = DEFAULT_STYLE.copy()
default.update(default_style)
for attr in default:
if attr not in style:
style[attr] = default[attr]
def parse_style(style, default_style):
manim_style = {}
fill_default_values(style, default_style)
manim_style["fill_opacity"] = float(style["fill-opacity"])
manim_style["stroke_opacity"] = float(style["stroke-opacity"])
manim_style["stroke_width"] = float(style["stroke-width"])
if style["fill"] == "none":
manim_style["fill_opacity"] = 0
else:
manim_style["fill_color"] = parse_color(style["fill"])
if style["stroke"] == "none":
manim_style["stroke_width"] = 0
if "fill_color" in manim_style:
manim_style["stroke_color"] = manim_style["fill_color"]
else:
manim_style["stroke_color"] = parse_color(style["stroke"])
return manim_style
class SVGMobject(VMobject):
@@ -41,8 +105,7 @@ class SVGMobject(VMobject):
# Must be filled in in a subclass, or when called
"file_name": None,
"unpack_groups": True, # if False, creates a hierarchy of VGroups
# TODO, style components should be read in, not defaulted
"stroke_width": DEFAULT_STROKE_WIDTH,
"stroke_width": 0.0,
"fill_opacity": 1.0,
"path_string_config": {}
}
@@ -65,48 +128,68 @@ class SVGMobject(VMobject):
if self.width is not None:
self.set_width(self.width)
def init_colors(self, override=False):
super().init_colors(override=override)
def init_points(self):
doc = minidom.parse(self.file_path)
etree = ElementTree.parse(self.file_path)
wrapper = cssselect2.ElementWrapper.from_xml_root(etree)
svg = etree.getroot()
namespace = svg.tag.split("}")[0][1:]
self.ref_to_element = {}
self.css_matcher = cssselect2.Matcher()
for svg in doc.getElementsByTagName("svg"):
mobjects = self.get_mobjects_from(svg)
if self.unpack_groups:
self.add(*mobjects)
else:
self.add(*mobjects[0].submobjects)
doc.unlink()
for style in etree.findall(f"{{{namespace}}}style"):
self.parse_css_style(style.text)
def get_mobjects_from(self, element):
result = []
if not isinstance(element, minidom.Element):
return result
if element.tagName == 'defs':
self.update_ref_to_element(element)
elif element.tagName == 'style':
pass # TODO, handle style
elif element.tagName in ['g', 'svg', 'symbol']:
result += it.chain(*[
self.get_mobjects_from(child)
for child in element.childNodes
])
elif element.tagName == 'path':
result.append(self.path_string_to_mobject(
element.getAttribute('d')
))
elif element.tagName == 'use':
result += self.use_to_mobjects(element)
elif element.tagName == 'rect':
result.append(self.rect_to_mobject(element))
elif element.tagName == 'circle':
result.append(self.circle_to_mobject(element))
elif element.tagName == 'ellipse':
result.append(self.ellipse_to_mobject(element))
elif element.tagName in ['polygon', 'polyline']:
result.append(self.polygon_to_mobject(element))
mobjects = self.get_mobjects_from(wrapper, dict())
if self.unpack_groups:
self.add(*mobjects)
else:
pass # TODO
# warnings.warn("Unknown element type: " + element.tagName)
self.add(*mobjects[0].submobjects)
def get_mobjects_from(self, wrapper, style):
result = []
element = wrapper.etree_element
if not isinstance(element, ElementTree.Element):
return result
matches = self.css_matcher.match(wrapper)
if matches:
for match in matches:
_, _, _, css_style = match
style.update(css_style)
style = cascade_element_style(element, style)
tag = element.tag.split("}")[-1]
if tag == 'defs':
self.update_ref_to_element(wrapper, style)
elif tag in ['g', 'svg', 'symbol']:
result += it.chain(*(
self.get_mobjects_from(child, style)
for child in wrapper.iter_children()
))
elif tag == 'path':
result.append(self.path_string_to_mobject(
element.get('d'), style
))
elif tag == 'use':
result += self.use_to_mobjects(element, style)
elif tag == 'line':
result.append(self.line_to_mobject(element, style))
elif tag == 'rect':
result.append(self.rect_to_mobject(element, style))
elif tag == 'circle':
result.append(self.circle_to_mobject(element, style))
elif tag == 'ellipse':
result.append(self.ellipse_to_mobject(element, style))
elif tag in ['polygon', 'polyline']:
result.append(self.polygon_to_mobject(element, style))
elif tag == 'style':
pass
else:
log.warning(f"Unsupported element type: {tag}")
pass # TODO, support <text> tag
result = [m for m in result if m is not None]
self.handle_transforms(element, VGroup(*result))
if len(result) > 1 and not self.unpack_groups:
@@ -114,26 +197,51 @@ class SVGMobject(VMobject):
return result
def g_to_mobjects(self, g_element):
mob = VGroup(*self.get_mobjects_from(g_element))
self.handle_transforms(g_element, mob)
return mob.submobjects
def generate_default_style(self):
style = {
"fill-opacity": self.fill_opacity,
"stroke-width": self.stroke_width,
"stroke-opacity": self.stroke_opacity,
}
if self.color:
style["fill"] = style["stroke"] = self.color
if self.fill_color:
style["fill"] = self.fill_color
if self.stroke_color:
style["stroke"] = self.stroke_color
return style
def parse_css_style(self, css):
rules = parse_stylesheet(css, True, True)
for rule in rules:
selectors = cssselect2.compile_selector_list(rule.prelude)
declarations = parse_declaration_list(rule.content)
style = {
declaration.name: css_serialize(declaration.value)
for declaration in declarations
if declaration.name in DEFAULT_STYLE
}
payload = style
for selector in selectors:
self.css_matcher.add_selector(selector, payload)
def path_string_to_mobject(self, path_string):
def path_string_to_mobject(self, path_string, style):
return VMobjectFromSVGPathstring(
path_string,
**self.path_string_config,
**parse_style(style, self.generate_default_style()),
)
def use_to_mobjects(self, use_element):
def use_to_mobjects(self, use_element, local_style):
# Remove initial "#" character
ref = use_element.getAttribute("xlink:href")[1:]
ref = use_element.get(r"{http://www.w3.org/1999/xlink}href")[1:]
if ref not in self.ref_to_element:
warnings.warn(f"{ref} not recognized")
log.warning(f"{ref} not recognized")
return VGroup()
return self.get_mobjects_from(
self.ref_to_element[ref]
)
def_element, def_style = self.ref_to_element[ref]
style = local_style.copy()
style.update(def_style)
return self.get_mobjects_from(def_element, style)
def attribute_to_float(self, attr):
stripped_attr = "".join([
@@ -142,52 +250,38 @@ class SVGMobject(VMobject):
])
return float(stripped_attr)
def polygon_to_mobject(self, polygon_element):
path_string = polygon_element.getAttribute("points")
def polygon_to_mobject(self, polygon_element, style):
path_string = polygon_element.get("points")
for digit in string.digits:
path_string = path_string.replace(f" {digit}", f"L {digit}")
path_string = path_string.replace("L", "M", 1)
return self.path_string_to_mobject(path_string)
return self.path_string_to_mobject(path_string, style)
def circle_to_mobject(self, circle_element):
x, y, r = [
self.attribute_to_float(
circle_element.getAttribute(key)
)
if circle_element.hasAttribute(key)
else 0.0
def circle_to_mobject(self, circle_element, style):
x, y, r = (
self.attribute_to_float(circle_element.get(key, "0.0"))
for key in ("cx", "cy", "r")
]
return Circle(radius=r).shift(x * RIGHT + y * DOWN)
)
return Circle(
radius=r,
**parse_style(style, self.generate_default_style())
).shift(x * RIGHT + y * DOWN)
def ellipse_to_mobject(self, circle_element):
x, y, rx, ry = [
self.attribute_to_float(
circle_element.getAttribute(key)
)
if circle_element.hasAttribute(key)
else 0.0
def ellipse_to_mobject(self, circle_element, style):
x, y, rx, ry = (
self.attribute_to_float(circle_element.get(key, "0.0"))
for key in ("cx", "cy", "rx", "ry")
]
return Circle().scale(rx * RIGHT + ry * UP).shift(x * RIGHT + y * DOWN)
)
result = Circle(**parse_style(style, self.generate_default_style()))
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")
stroke_color = rect_element.getAttribute("stroke")
stroke_width = rect_element.getAttribute("stroke-width")
corner_radius = rect_element.getAttribute("rx")
def rect_to_mobject(self, rect_element, style):
stroke_width = rect_element.get("stroke-width", "")
corner_radius = rect_element.get("rx", "")
# input preprocessing
if fill_color in ["", "none", "#FFF", "#FFFFFF"] or Color(fill_color) == Color(WHITE):
opacity = 0
fill_color = BLACK # shdn't be necessary but avoids error msgs
if fill_color in ["#000", "#000000"]:
fill_color = WHITE
if stroke_color in ["", "none", "#FFF", "#FFFFFF"] or Color(stroke_color) == Color(WHITE):
stroke_width = 0
stroke_color = BLACK
if stroke_color in ["#000", "#000000"]:
stroke_color = WHITE
if stroke_width in ["", "none", "0"]:
stroke_width = 0
@@ -196,98 +290,131 @@ class SVGMobject(VMobject):
corner_radius = float(corner_radius)
parsed_style = parse_style(style, self.generate_default_style())
parsed_style["stroke_width"] = stroke_width
if corner_radius == 0:
mob = Rectangle(
width=self.attribute_to_float(
rect_element.getAttribute("width")
rect_element.get("width", "")
),
height=self.attribute_to_float(
rect_element.getAttribute("height")
rect_element.get("height", "")
),
stroke_width=stroke_width,
stroke_color=stroke_color,
fill_color=fill_color,
fill_opacity=opacity
**parsed_style,
)
else:
mob = RoundedRectangle(
width=self.attribute_to_float(
rect_element.getAttribute("width")
rect_element.get("width", "")
),
height=self.attribute_to_float(
rect_element.getAttribute("height")
rect_element.get("height", "")
),
stroke_width=stroke_width,
stroke_color=stroke_color,
fill_color=fill_color,
fill_opacity=opacity,
corner_radius=corner_radius
corner_radius=corner_radius,
**parsed_style
)
mob.shift(mob.get_center() - mob.get_corner(UP + LEFT))
return mob
def line_to_mobject(self, line_element, style):
x1, y1, x2, y2 = (
self.attribute_to_float(line_element.get(key, "0.0"))
for key in ("x1", "y1", "x2", "y2")
)
return Line(
[x1, -y1, 0], [x2, -y2, 0],
**parse_style(style, self.generate_default_style())
)
def handle_transforms(self, element, mobject):
# TODO, this could use some cleaning...
x, y = 0, 0
try:
x = self.attribute_to_float(element.getAttribute('x'))
# Flip y
y = -self.attribute_to_float(element.getAttribute('y'))
mobject.shift([x, y, 0])
except Exception:
pass
x, y = (
self.attribute_to_float(element.get(key, "0.0"))
for key in ("x", "y")
)
mobject.shift(x * RIGHT + y * DOWN)
transform = element.getAttribute('transform')
transform_names = [
"matrix",
"translate", "translateX", "translateY",
"scale", "scaleX", "scaleY",
"rotate",
"skewX", "skewY"
]
transform_pattern = re.compile("|".join([x + r"[^)]*\)" for x in transform_names]))
number_pattern = re.compile(r"[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?")
transforms = transform_pattern.findall(element.get("transform", ""))[::-1]
try: # transform matrix
prefix = "matrix("
suffix = ")"
if not transform.startswith(prefix) or not transform.endswith(suffix):
raise Exception()
transform = transform[len(prefix):-len(suffix)]
transform = string_to_numbers(transform)
transform = np.array(transform).reshape([3, 2])
x = transform[2][0]
y = -transform[2][1]
matrix = np.identity(self.dim)
matrix[:2, :2] = transform[:2, :]
matrix[1] *= -1
matrix[:, 1] *= -1
for transform in transforms:
op_name, op_args = transform.split("(")
op_name = op_name.strip()
op_args = [float(x) for x in number_pattern.findall(op_args)]
for mob in mobject.family_members_with_points():
mob.apply_matrix(matrix.T)
mobject.shift(x * RIGHT + y * UP)
except:
pass
if op_name == "matrix":
self._handle_matrix_transform(mobject, op_name, op_args)
elif op_name.startswith("translate"):
self._handle_translate_transform(mobject, op_name, op_args)
elif op_name.startswith("scale"):
self._handle_scale_transform(mobject, op_name, op_args)
elif op_name == "rotate":
self._handle_rotate_transform(mobject, op_name, op_args)
elif op_name.startswith("skew"):
self._handle_skew_transform(mobject, op_name, op_args)
try: # transform scale
prefix = "scale("
suffix = ")"
if not transform.startswith(prefix) or not transform.endswith(suffix):
raise Exception()
transform = transform[len(prefix):-len(suffix)]
scale_values = string_to_numbers(transform)
if len(scale_values) == 2:
scale_x, scale_y = scale_values
mobject.scale(np.array([scale_x, scale_y, 1]), about_point=ORIGIN)
elif len(scale_values) == 1:
scale = scale_values[0]
mobject.scale(np.array([scale, scale, 1]), about_point=ORIGIN)
except:
pass
def _handle_matrix_transform(self, mobject, op_name, op_args):
transform = np.array(op_args).reshape([3, 2])
x = transform[2][0]
y = -transform[2][1]
matrix = np.identity(self.dim)
matrix[:2, :2] = transform[:2, :]
matrix[1] *= -1
matrix[:, 1] *= -1
for mob in mobject.family_members_with_points():
mob.apply_matrix(matrix.T)
mobject.shift(x * RIGHT + y * UP)
try: # transform translate
prefix = "translate("
suffix = ")"
if not transform.startswith(prefix) or not transform.endswith(suffix):
raise Exception()
transform = transform[len(prefix):-len(suffix)]
x, y = string_to_numbers(transform)
mobject.shift(x * RIGHT + y * DOWN)
except:
pass
# TODO, ...
def _handle_translate_transform(self, mobject, op_name, op_args):
if op_name.endswith("X"):
x, y = op_args[0], 0
elif op_name.endswith("Y"):
x, y = 0, op_args[0]
else:
x, y = op_args
mobject.shift(x * RIGHT + y * DOWN)
def _handle_scale_transform(self, mobject, op_name, op_args):
if op_name.endswith("X"):
sx, sy = op_args[0], 1
elif op_name.endswith("Y"):
sx, sy = 1, op_args[0]
elif len(op_args) == 2:
sx, sy = op_args
else:
sx = sy = op_args[0]
if sx < 0:
mobject.flip(UP)
sx = -sx
if sy < 0:
mobject.flip(RIGHT)
sy = -sy
mobject.scale(np.array([sx, sy, 1]), about_point=ORIGIN)
def _handle_rotate_transform(self, mobject, op_name, op_args):
if len(op_args) == 1:
mobject.rotate(op_args[0] * DEGREES, axis=IN, about_point=ORIGIN)
else:
deg, x, y = op_args
mobject.rotate(deg * DEGREES, axis=IN, about_point=np.array([x, y, 0]))
def _handle_skew_transform(self, mobject, op_name, op_args):
rad = op_args[0] * DEGREES
if op_name == "skewX":
tana = np.tan(rad)
self._handle_matrix_transform(mobject, None, [1., 0., tana, 1., 0., 0.])
elif op_name == "skewY":
tana = np.tan(rad)
self._handle_matrix_transform(mobject, None, [1., tana, 0., 1., 0., 0.])
def flatten(self, input_list):
output_list = []
@@ -298,24 +425,28 @@ class SVGMobject(VMobject):
output_list.append(i)
return output_list
def get_all_childNodes_have_id(self, element):
all_childNodes_have_id = []
if not isinstance(element, minidom.Element):
def get_all_childWrappers_have_id(self, wrapper):
all_childWrappers_have_id = []
element = wrapper.etree_element
if not isinstance(element, ElementTree.Element):
return
if element.hasAttribute('id'):
return [element]
for e in element.childNodes:
all_childNodes_have_id.append(self.get_all_childNodes_have_id(e))
return self.flatten([e for e in all_childNodes_have_id if e])
if element.get('id'):
return [wrapper]
for e in wrapper.iter_children():
all_childWrappers_have_id.append(self.get_all_childWrappers_have_id(e))
return self.flatten([e for e in all_childWrappers_have_id if e])
def update_ref_to_element(self, defs):
new_refs = dict([(e.getAttribute('id'), e) for e in self.get_all_childNodes_have_id(defs)])
def update_ref_to_element(self, wrapper, style):
new_refs = {
e.etree_element.get('id', ''): (e, style)
for e in self.get_all_childWrappers_have_id(wrapper)
}
self.ref_to_element.update(new_refs)
class VMobjectFromSVGPathstring(VMobject):
CONFIG = {
"long_lines": True,
"long_lines": False,
"should_subdivide_sharp_curves": False,
"should_remove_null_curves": False,
}
@@ -335,11 +466,10 @@ class VMobjectFromSVGPathstring(VMobject):
if os.path.exists(points_filepath) and os.path.exists(tris_filepath):
self.set_points(np.load(points_filepath))
self.triangulation = np.load(tris_filepath)
self.needs_new_triangulation = False
else:
self.relative_point = np.array(ORIGIN)
for command, coord_string in self.get_commands_and_coord_strings():
new_points = self.string_to_points(command, coord_string)
self.handle_command(command, new_points)
self.handle_commands()
if self.should_subdivide_sharp_curves:
# For a healthy triangulation later
self.subdivide_sharp_curves()
@@ -350,6 +480,7 @@ class VMobjectFromSVGPathstring(VMobject):
self.stretch(-1, 1, about_point=ORIGIN)
# Save to a file for future use
np.save(points_filepath, self.get_points())
np.save(tris_filepath, self.get_triangulation())
def get_commands_and_coord_strings(self):
all_commands = list(self.get_command_to_function_map().keys())
@@ -360,43 +491,145 @@ class VMobjectFromSVGPathstring(VMobject):
re.split(pattern, self.path_string)[1:]
)
def handle_command(self, command, new_points):
if command.islower():
# Treat it as a relative command
new_points += self.relative_point
def handle_commands(self):
relative_point = ORIGIN
for command, coord_string in self.get_commands_and_coord_strings():
func, number_types_str = self.command_to_function(command)
upper_command = command.upper()
if upper_command == "Z":
func() # `close_path` takes no arguments
relative_point = self.get_last_point()
continue
func, n_points = self.command_to_function(command)
func(*new_points[:n_points])
leftover_points = new_points[n_points:]
number_types = np.array(list(number_types_str))
n_numbers = len(number_types_str)
number_list = _PathStringParser(coord_string, number_types_str).args
number_groups = np.array(number_list).reshape((-1, n_numbers))
# Recursively handle the rest of the points
if len(leftover_points) > 0:
if command.upper() == "M":
# Treat following points as relative line coordinates
command = "l"
if command.islower():
leftover_points -= self.relative_point
self.relative_point = self.get_last_point()
self.handle_command(command, leftover_points)
else:
# Command is over, reset for future relative commands
self.relative_point = self.get_last_point()
for ind, numbers in enumerate(number_groups):
if command.islower():
# Treat it as a relative command
numbers[number_types == "x"] += relative_point[0]
numbers[number_types == "y"] += relative_point[1]
def string_to_points(self, command, coord_string):
numbers = string_to_numbers(coord_string)
if command.upper() in ["H", "V"]:
i = {"H": 0, "V": 1}[command.upper()]
xy = np.zeros((len(numbers), 2))
xy[:, i] = numbers
if command.isupper():
xy[:, 1 - i] = self.relative_point[1 - i]
elif command.upper() == "A":
raise Exception("Not implemented")
else:
xy = np.array(numbers).reshape((len(numbers) // 2, 2))
result = np.zeros((xy.shape[0], self.dim))
result[:, :2] = xy
return result
if upper_command == "A":
args = [*numbers[:5], np.array([*numbers[5:7], 0.0])]
elif upper_command == "H":
args = [np.array([numbers[0], relative_point[1], 0.0])]
elif upper_command == "V":
args = [np.array([relative_point[0], numbers[0], 0.0])]
else:
args = list(np.hstack((
numbers.reshape((-1, 2)), np.zeros((n_numbers // 2, 1))
)))
if upper_command == "M" and ind != 0:
# M x1 y1 x2 y2 is equal to M x1 y1 L x2 y2
func, _ = self.command_to_function("L")
func(*args)
relative_point = self.get_last_point()
def add_elliptical_arc_to(self, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, point):
def close_to_zero(a, threshold=1e-5):
return abs(a) < threshold
def solve_2d_linear_equation(a, b, c):
"""
Using Crammer's rule to solve the linear equation `[a b]x = c`
where `a`, `b` and `c` are all 2d vectors.
"""
def det(a, b):
return a[0] * b[1] - a[1] * b[0]
d = det(a, b)
if close_to_zero(d):
raise Exception("Cannot handle 0 determinant.")
return [det(c, b) / d, det(a, c) / d]
def get_arc_center_and_angles(x0, y0, rx, ry, phi, large_arc_flag, sweep_flag, x1, y1):
"""
The parameter functions of an ellipse rotated `phi` radians counterclockwise is (on `alpha`):
x = cx + rx * cos(alpha) * cos(phi) + ry * sin(alpha) * sin(phi),
y = cy + rx * cos(alpha) * sin(phi) - ry * sin(alpha) * cos(phi).
Now we have two points sitting on the ellipse: `(x0, y0)`, `(x1, y1)`, corresponding to 4 equations,
and we want to hunt for 4 variables: `cx`, `cy`, `alpha0` and `alpha_1`.
Let `d_alpha = alpha1 - alpha0`, then:
if `sweep_flag = 0` and `large_arc_flag = 1`, then `PI <= d_alpha < 2 * PI`;
if `sweep_flag = 0` and `large_arc_flag = 0`, then `0 < d_alpha <= PI`;
if `sweep_flag = 1` and `large_arc_flag = 0`, then `-PI <= d_alpha < 0`;
if `sweep_flag = 1` and `large_arc_flag = 1`, then `-2 * PI < d_alpha <= -PI`.
"""
xd = x1 - x0
yd = y1 - y0
if close_to_zero(xd) and close_to_zero(yd):
raise Exception("Cannot find arc center since the start point and the end point meet.")
# Find `p = cos(alpha1) - cos(alpha0)`, `q = sin(alpha1) - sin(alpha0)`
eq0 = [rx * np.cos(phi), ry * np.sin(phi), xd]
eq1 = [rx * np.sin(phi), -ry * np.cos(phi), yd]
p, q = solve_2d_linear_equation(*zip(eq0, eq1))
# Find `s = (alpha1 - alpha0) / 2`, `t = (alpha1 + alpha0) / 2`
# If `sin(s) = 0`, this requires `p = q = 0`,
# implying `xd = yd = 0`, which is impossible.
sin_s = (p ** 2 + q ** 2) ** 0.5 / 2
if sweep_flag:
sin_s = -sin_s
sin_s = clip(sin_s, -1, 1)
s = np.arcsin(sin_s)
if large_arc_flag:
if not sweep_flag:
s = PI - s
else:
s = -PI - s
sin_t = -p / (2 * sin_s)
cos_t = q / (2 * sin_s)
cos_t = clip(cos_t, -1, 1)
t = np.arccos(cos_t)
if sin_t <= 0:
t = -t
# We can make sure `0 < abs(s) < PI`, `-PI <= t < PI`.
alpha0 = t - s
alpha_1 = t + s
cx = x0 - rx * np.cos(alpha0) * np.cos(phi) - ry * np.sin(alpha0) * np.sin(phi)
cy = y0 - rx * np.cos(alpha0) * np.sin(phi) + ry * np.sin(alpha0) * np.cos(phi)
return cx, cy, alpha0, alpha_1
def get_point_on_ellipse(cx, cy, rx, ry, phi, angle):
return np.array([
cx + rx * np.cos(angle) * np.cos(phi) + ry * np.sin(angle) * np.sin(phi),
cy + rx * np.cos(angle) * np.sin(phi) - ry * np.sin(angle) * np.cos(phi),
0
])
def convert_elliptical_arc_to_quadratic_bezier_curve(
cx, cy, rx, ry, phi, start_angle, end_angle, n_components=8
):
theta = (end_angle - start_angle) / n_components / 2
handles = np.array([
get_point_on_ellipse(cx, cy, rx / np.cos(theta), ry / np.cos(theta), phi, a)
for a in np.linspace(
start_angle + theta,
end_angle - theta,
n_components,
)
])
anchors = np.array([
get_point_on_ellipse(cx, cy, rx, ry, phi, a)
for a in np.linspace(
start_angle + theta * 2,
end_angle,
n_components,
)
])
return handles, anchors
phi = x_axis_rotation * DEGREES
x0, y0 = self.get_last_point()[:2]
cx, cy, start_angle, end_angle = get_arc_center_and_angles(
x0, y0, rx, ry, phi, large_arc_flag, sweep_flag, point[0], point[1]
)
handles, anchors = convert_elliptical_arc_to_quadratic_bezier_curve(
cx, cy, rx, ry, phi, start_angle, end_angle
)
for handle, anchor in zip(handles, anchors):
self.add_quadratic_bezier_curve_to(handle, anchor)
def command_to_function(self, command):
return self.get_command_to_function_map()[command.upper()]
@@ -404,20 +637,78 @@ class VMobjectFromSVGPathstring(VMobject):
def get_command_to_function_map(self):
"""
Associates svg command to VMobject function, and
the number of arguments it takes in
the types of arguments it takes in
"""
return {
"M": (self.start_new_path, 1),
"L": (self.add_line_to, 1),
"H": (self.add_line_to, 1),
"V": (self.add_line_to, 1),
"C": (self.add_cubic_bezier_curve_to, 3),
"S": (self.add_smooth_cubic_curve_to, 2),
"Q": (self.add_quadratic_bezier_curve_to, 2),
"T": (self.add_smooth_curve_to, 1),
"A": (self.add_quadratic_bezier_curve_to, 2), # TODO
"Z": (self.close_path, 0),
"M": (self.start_new_path, "xy"),
"L": (self.add_line_to, "xy"),
"H": (self.add_line_to, "x"),
"V": (self.add_line_to, "y"),
"C": (self.add_cubic_bezier_curve_to, "xyxyxy"),
"S": (self.add_smooth_cubic_curve_to, "xyxy"),
"Q": (self.add_quadratic_bezier_curve_to, "xyxy"),
"T": (self.add_smooth_curve_to, "xy"),
"A": (self.add_elliptical_arc_to, "uuaffxy"),
"Z": (self.close_path, ""),
}
def get_original_path_string(self):
return self.path_string
class InvalidPathError(ValueError):
pass
class _PathStringParser:
# modified from https://github.com/regebro/svg.path/
def __init__(self, arguments, rules):
self.args = []
arguments = bytearray(arguments, "ascii")
self._strip_array(arguments)
while arguments:
for rule in rules:
self._rule_to_function_map[rule](arguments)
@property
def _rule_to_function_map(self):
return {
"x": self._get_number,
"y": self._get_number,
"a": self._get_number,
"u": self._get_unsigned_number,
"f": self._get_flag,
}
def _strip_array(self, arg_array):
# wsp: (0x9, 0x20, 0xA, 0xC, 0xD) with comma 0x2C
# https://www.w3.org/TR/SVG/paths.html#PathDataBNF
while arg_array and arg_array[0] in [0x9, 0x20, 0xA, 0xC, 0xD, 0x2C]:
arg_array[0:1] = b""
def _get_number(self, arg_array):
pattern = re.compile(rb"^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?")
res = pattern.search(arg_array)
if not res:
raise InvalidPathError(f"Expected a number, got '{arg_array}'")
number = float(res.group())
self.args.append(number)
arg_array[res.start():res.end()] = b""
self._strip_array(arg_array)
return number
def _get_unsigned_number(self, arg_array):
number = self._get_number(arg_array)
if number < 0:
raise InvalidPathError(f"Expected an unsigned number, got '{number}'")
return number
def _get_flag(self, arg_array):
flag = arg_array[0]
if flag != 48 and flag != 49:
raise InvalidPathError(f"Expected a flag (0/1), got '{chr(flag)}'")
flag -= 48
self.args.append(flag)
arg_array[0:1] = b""
self._strip_array(arg_array)
return flag

View File

@@ -1,7 +1,6 @@
from functools import reduce
import operator as op
import re
import itertools as it
from manimlib.constants import *
from manimlib.mobject.geometry import Line
@@ -17,7 +16,7 @@ from manimlib.utils.tex_file_writing import display_during_execution
SCALE_FACTOR_PER_FONT_POINT = 0.001
tex_string_to_mob_map = {}
tex_string_with_color_to_mob_map = {}
class SingleStringTex(VMobject):
@@ -36,24 +35,26 @@ class SingleStringTex(VMobject):
super().__init__(**kwargs)
assert(isinstance(tex_string, str))
self.tex_string = tex_string
if tex_string not in tex_string_to_mob_map:
if tex_string not in tex_string_with_color_to_mob_map:
with display_during_execution(f" Writing \"{tex_string}\""):
full_tex = self.get_tex_file_body(tex_string)
filename = tex_to_svg_file(full_tex)
svg_mob = SVGMobject(
filename,
height=None,
color=self.color,
stroke_width=self.stroke_width,
path_string_config={
"should_subdivide_sharp_curves": True,
"should_remove_null_curves": True,
}
)
tex_string_to_mob_map[tex_string] = svg_mob
tex_string_with_color_to_mob_map[(self.color, tex_string)] = svg_mob
self.add(*(
sm.copy()
for sm in tex_string_to_mob_map[tex_string]
for sm in tex_string_with_color_to_mob_map[(self.color, tex_string)]
))
self.init_colors()
self.init_colors(override=False)
if self.height is None:
self.scale(SCALE_FACTOR_PER_FONT_POINT * self.font_size)
@@ -65,6 +66,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 +75,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()
@@ -131,15 +131,18 @@ class SingleStringTex(VMobject):
def balance_braces(self, tex):
"""
Makes Tex resiliant to unmatched { at start
Makes Tex resiliant to unmatched braces
"""
num_lefts, num_rights = [tex.count(char) for char in "{}"]
while num_rights > num_lefts:
tex = "{" + tex
num_lefts += 1
while num_lefts > num_rights:
tex = tex + "}"
num_rights += 1
num_unclosed_brackets = 0
for char in tex:
if char == "{":
num_unclosed_brackets += 1
elif char == "}":
if num_unclosed_brackets == 0:
tex = "{" + tex
else:
num_unclosed_brackets -= 1
tex += num_unclosed_brackets * "}"
return tex
def get_tex(self):
@@ -152,10 +155,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 +172,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

@@ -1,22 +1,30 @@
import copy
import hashlib
import os
import re
import io
import typing
import xml.etree.ElementTree as ET
import functools
import pygments
import pygments.lexers
import pygments.styles
from contextlib import contextmanager
from pathlib import Path
import manimpango
from manimlib.logger import log
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
from manimpango import PangoUtils, TextSetting, MarkupUtils
TEXT_MOB_SCALE_FACTOR = 0.001048
TEXT_MOB_SCALE_FACTOR = 0.0076
DEFAULT_LINE_SPACING_SCALE = 0.6
class Text(SVGMobject):
@@ -29,7 +37,7 @@ class Text(SVGMobject):
"font": '',
"gradient": None,
"lsh": -1,
"size": 1,
"size": None,
"font_size": 48,
"tab_width": 4,
"slant": NORMAL,
@@ -42,10 +50,19 @@ class Text(SVGMobject):
"disable_ligatures": True,
}
def __init__(self, text, **config):
self.full2short(config)
digest_config(self, config)
self.lsh = self.size if self.lsh == -1 else self.lsh
def __init__(self, text, **kwargs):
self.full2short(kwargs)
digest_config(self, kwargs)
if self.size:
log.warning(
"`self.size` has been deprecated and will "
"be removed in future.",
)
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)
@@ -53,7 +70,7 @@ class Text(SVGMobject):
file_name = self.text2svg()
PangoUtils.remove_last_M(file_name)
self.remove_empty_path(file_name)
SVGMobject.__init__(self, file_name, **config)
SVGMobject.__init__(self, file_name, **kwargs)
self.text = text
if self.disable_ligatures:
self.apply_space_chars()
@@ -66,7 +83,10 @@ 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 init_colors(self, override=True):
super().init_colors(override=override)
def remove_empty_path(self, file_name):
with open(file_name, 'r') as fpr:
@@ -100,6 +120,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'):
@@ -117,75 +150,88 @@ class Text(SVGMobject):
def set_color_by_t2c(self, t2c=None):
t2c = t2c if t2c else self.t2c
for word, color in list(t2c.items()):
for word, color in t2c.items():
for start, end in self.find_indexes(word):
self[start:end].set_color(color)
def set_color_by_t2g(self, t2g=None):
t2g = t2g if t2g else self.t2g
for word, gradient in list(t2g.items()):
for word, gradient in t2g.items():
for start, end in self.find_indexes(word):
self[start:end].set_color_by_gradient(*gradient)
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())
return hasher.hexdigest()[:16]
def text2settings(self):
"""
Substrings specified in t2f, t2s, t2w can occupy each other.
For each category of style, a stack following first-in-last-out is constructed,
and the last value in each stack takes effect.
"""
settings = []
t2x = [self.t2f, self.t2s, self.t2w]
for i in range(len(t2x)):
fsw = [self.font, self.slant, self.weight]
if t2x[i]:
for word, x in list(t2x[i].items()):
for start, end in self.find_indexes(word):
fsw[i] = x
settings.append(TextSetting(start, end, *fsw))
self.line_num = 0
def add_text_settings(start, end, style_stacks):
if start == end:
return
breakdown_indices = [start, *[
i + start + 1 for i, char in enumerate(self.text[start:end]) if char == "\n"
], end]
style = [stack[-1] for stack in style_stacks]
for atom_start, atom_end in zip(breakdown_indices[:-1], breakdown_indices[1:]):
if atom_start < atom_end:
settings.append(TextSetting(atom_start, atom_end, *style, self.line_num))
self.line_num += 1
self.line_num -= 1
# Set All text settings(default font slant weight)
fsw = [self.font, self.slant, self.weight]
settings.sort(key=lambda setting: setting.start)
temp_settings = settings.copy()
start = 0
for setting in settings:
if setting.start != start:
temp_settings.append(TextSetting(start, setting.start, *fsw))
start = setting.end
if start != len(self.text):
temp_settings.append(TextSetting(start, len(self.text), *fsw))
settings = sorted(temp_settings, key=lambda setting: setting.start)
# Set all the default and specified values.
len_text = len(self.text)
t2x_items = sorted([
*[
(0, len_text, t2x_index, value)
for t2x_index, value in enumerate([self.font, self.slant, self.weight])
],
*[
(start, end, t2x_index, value)
for t2x_index, t2x in enumerate([self.t2f, self.t2s, self.t2w])
for word, value in t2x.items()
for start, end in self.find_indexes(word)
]
], key=lambda item: item[0])
if re.search(r'\n', self.text):
line_num = 0
for start, end in self.find_indexes('\n'):
for setting in settings:
if setting.line_num == -1:
setting.line_num = line_num
if start < setting.end:
line_num += 1
new_setting = copy.copy(setting)
setting.end = end
new_setting.start = end
new_setting.line_num = line_num
settings.append(new_setting)
settings.sort(key=lambda setting: setting.start)
break
for setting in settings:
if setting.line_num == -1:
setting.line_num = 0
# Break down ranges and construct settings separately.
active_items = []
style_stacks = [[] for _ in range(3)]
for item, next_start in zip(t2x_items, [*[item[0] for item in t2x_items[1:]], len_text]):
active_items.append(item)
start, end, t2x_index, value = item
style_stacks[t2x_index].append(value)
halting_items = sorted(filter(
lambda item: item[1] <= next_start,
active_items
), key=lambda item: item[1])
atom_start = start
for halting_item in halting_items:
active_items.remove(halting_item)
_, atom_end, t2x_index, _ = halting_item
add_text_settings(atom_start, atom_end, style_stacks)
style_stacks[t2x_index].pop()
atom_start = atom_end
add_text_settings(atom_start, next_start, style_stacks)
del self.line_num
return settings
def text2svg(self):
# 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 +242,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 +258,296 @@ 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)})"
class Code(Text):
CONFIG = {
"font": "Consolas",
"font_size": 24,
"lsh": 1.0,
"language": "python",
# Visit https://pygments.org/demo/ to have a preview of more styles.
"code_style": "monokai",
# If not None, then each character will cover a space of equal width.
"char_width": None
}
def __init__(self, code, **kwargs):
self.full2short(kwargs)
digest_config(self, kwargs)
code = code.lstrip("\n") # avoid mismatches of character indices
lexer = pygments.lexers.get_lexer_by_name(self.language)
tokens_generator = pygments.lex(code, lexer)
styles_dict = dict(pygments.styles.get_style_by_name(self.code_style))
default_color_hex = styles_dict[pygments.token.Text]["color"]
if not default_color_hex:
default_color_hex = self.color[1:]
start_index = 0
t2c = {}
t2s = {}
t2w = {}
for pair in tokens_generator:
ttype, token = pair
end_index = start_index + len(token)
range_str = f"[{start_index}:{end_index}]"
style_dict = styles_dict[ttype]
t2c[range_str] = "#" + (style_dict["color"] or default_color_hex)
t2s[range_str] = ITALIC if style_dict["italic"] else NORMAL
t2w[range_str] = BOLD if style_dict["bold"] else NORMAL
start_index = end_index
t2c.update(self.t2c)
t2s.update(self.t2s)
t2w.update(self.t2w)
kwargs["t2c"] = t2c
kwargs["t2s"] = t2s
kwargs["t2w"] = t2w
Text.__init__(self, code, **kwargs)
if self.char_width is not None:
self.set_monospace(self.char_width)
def set_monospace(self, char_width):
current_char_index = 0
for i, char in enumerate(self.text):
if char == "\n":
current_char_index = 0
continue
self[i].set_x(current_char_index * char_width)
current_char_index += 1
self.center()
@contextmanager
def register_font(font_file: typing.Union[str, Path]):
"""Temporarily add a font file to Pango's search path.
@@ -240,8 +576,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

@@ -5,14 +5,18 @@ from manimlib.mobject.types.surface import Surface
from manimlib.mobject.types.surface import SGroup
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.geometry import Square
from manimlib.mobject.geometry import Polygon
from manimlib.utils.bezier import interpolate
from manimlib.utils.config_ops import digest_config
from manimlib.utils.space_ops import get_norm
from manimlib.utils.space_ops import z_to_vector
from manimlib.utils.space_ops import compass_directions
class SurfaceMesh(VGroup):
CONFIG = {
"resolution": (21, 21),
"resolution": (21, 11),
"stroke_width": 1,
"normal_nudge": 1e-2,
"depth_test": True,
@@ -30,22 +34,34 @@ class SurfaceMesh(VGroup):
full_nu, full_nv = uv_surface.resolution
part_nu, part_nv = self.resolution
u_indices = np.linspace(0, full_nu, part_nu).astype(int)
v_indices = np.linspace(0, full_nv, part_nv).astype(int)
# 'indices' are treated as floats. Later, there will be
# an interpolation between the floor and ceiling of these
# indices
u_indices = np.linspace(0, full_nu - 1, part_nu)
v_indices = np.linspace(0, full_nv - 1, part_nv)
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:
path = VMobject()
full_ui = full_nv * ui
path.set_points_smoothly(nudged_points[full_ui:full_ui + full_nv])
low_ui = full_nv * int(math.floor(ui))
high_ui = full_nv * int(math.ceil(ui))
path.set_points_smoothly(interpolate(
nudged_points[low_ui:low_ui + full_nv],
nudged_points[high_ui:high_ui + full_nv],
ui % 1
))
self.add(path)
for vi in v_indices:
path = VMobject()
path.set_points_smoothly(nudged_points[vi::full_nv])
path.set_points_smoothly(interpolate(
nudged_points[int(math.floor(vi))::full_nv],
nudged_points[int(math.ceil(vi))::full_nv],
vi % 1
))
self.add(path)
@@ -161,15 +177,96 @@ class Cube(SGroup):
"gloss": 0.5,
"square_resolution": (2, 2),
"side_length": 2,
"square_class": Square3D,
}
def init_points(self):
for vect in [OUT, RIGHT, UP, LEFT, DOWN, IN]:
face = Square3D(resolution=self.square_resolution)
face.shift(OUT)
face.apply_matrix(z_to_vector(vect))
self.add(face)
self.set_height(self.side_length)
face = Square3D(
resolution=self.square_resolution,
side_length=self.side_length,
)
self.add(*self.square_to_cube_faces(face))
@staticmethod
def square_to_cube_faces(square):
radius = square.get_height() / 2
square.move_to(radius * OUT)
result = [square]
result.extend([
square.copy().rotate(PI / 2, axis=vect, about_point=ORIGIN)
for vect in compass_directions(4)
])
result.append(square.copy().rotate(PI, RIGHT, about_point=ORIGIN))
return result
def _get_face(self):
return Square3D(resolution=self.square_resolution)
class VCube(VGroup):
CONFIG = {
"fill_color": BLUE_D,
"fill_opacity": 1,
"stroke_width": 0,
"gloss": 0.5,
"shadow": 0.5,
}
def __init__(self, side_length=2, **kwargs):
super().__init__(**kwargs)
face = Square(side_length=side_length)
face.get_triangulation()
self.add(*Cube.square_to_cube_faces(face))
self.init_colors()
self.apply_depth_test()
self.refresh_unit_normal()
class Dodecahedron(VGroup):
CONFIG = {
"fill_color": BLUE_E,
"fill_opacity": 1,
"stroke_width": 1,
"reflectiveness": 0.2,
"gloss": 0.3,
"shadow": 0.2,
"depth_test": True,
}
def init_points(self):
# Star by creating two of the pentagons, meeting
# back to back on the positive x-axis
phi = (1 + math.sqrt(5)) / 2
x, y, z = np.identity(3)
pentagon1 = Polygon(
[phi, 1 / phi, 0],
[1, 1, 1],
[1 / phi, 0, phi],
[1, -1, 1],
[phi, -1 / phi, 0],
)
pentagon2 = pentagon1.copy().stretch(-1, 2, about_point=ORIGIN)
pentagon2.reverse_points()
x_pair = VGroup(pentagon1, pentagon2)
z_pair = x_pair.copy().apply_matrix(np.array([z, -x, -y]).T)
y_pair = x_pair.copy().apply_matrix(np.array([y, z, x]).T)
self.add(*x_pair, *y_pair, *z_pair)
for pentagon in list(self):
pc = pentagon.copy()
pc.apply_function(lambda p: -p)
pc.reverse_points()
self.add(pc)
# # Rotate those two pentagons by all the axis permuations to fill
# # out the dodecahedron
# Id = np.identity(3)
# for i in range(3):
# perm = [j % 3 for j in range(i, i + 3)]
# for b in [1, -1]:
# matrix = b * np.array([Id[0][perm], Id[1][perm], Id[2][perm]])
# self.add(pentagon1.copy().apply_matrix(matrix, about_point=ORIGIN))
# self.add(pentagon2.copy().apply_matrix(matrix, about_point=ORIGIN))
class Prism(Cube):

View File

@@ -2,11 +2,14 @@ import numpy as np
import moderngl
from manimlib.constants import GREY_C
from manimlib.constants import YELLOW
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_GLOW_DOT_RADIUS = 0.2
DEFAULT_GRID_HEIGHT = 6
DEFAULT_BUFF_RATIO = 0.5
@@ -15,7 +18,8 @@ class DotCloud(PMobject):
CONFIG = {
"color": GREY_C,
"opacity": 1,
"radius": DEFAULT_DOT_CLOUD_RADIUS,
"radius": DEFAULT_DOT_RADIUS,
"glow_factor": 0,
"shader_folder": "true_dot",
"render_primitive": moderngl.POINTS,
"shader_dtype": [
@@ -35,6 +39,10 @@ class DotCloud(PMobject):
self.data["radii"] = np.zeros((1, 1))
self.set_radius(self.radius)
def init_uniforms(self):
super().init_uniforms()
self.uniforms["glow_factor"] = self.glow_factor
def to_grid(self, n_rows, n_cols, n_layers=1,
buff_ratio=None,
h_buff_ratio=1.0,
@@ -67,7 +75,9 @@ class DotCloud(PMobject):
return self
def set_radii(self, radii):
self.data["radii"][:] = resize_preserving_order(radii, len(self.data["radii"]))
n_points = len(self.get_points())
radii = np.array(radii).reshape((len(radii), 1))
self.data["radii"] = resize_preserving_order(radii, n_points)
self.refresh_bounding_box()
return self
@@ -82,6 +92,12 @@ class DotCloud(PMobject):
def get_radius(self):
return self.get_radii().max()
def set_glow_factor(self, glow_factor):
self.uniforms["glow_factor"] = glow_factor
def get_glow_factor(self):
return self.uniforms["glow_factor"]
def compute_bounding_box(self):
bb = super().compute_bounding_box()
radius = self.get_radius()
@@ -95,8 +111,8 @@ class DotCloud(PMobject):
self.set_radii(scale_factor * self.get_radii())
return self
def make_3d(self, gloss=0.5, shadow=0.2):
self.set_gloss(gloss)
def make_3d(self, reflectiveness=0.5, shadow=0.2):
self.set_reflectiveness(reflectiveness)
self.set_shadow(shadow)
self.apply_depth_test()
return self
@@ -106,3 +122,16 @@ 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, **kwargs):
super().__init__(points=[center], **kwargs)
class GlowDot(TrueDot):
CONFIG = {
"glow_factor": 2,
"radius": DEFAULT_GLOW_DOT_RADIUS,
"color": YELLOW,
}

View File

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

View File

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

View File

@@ -20,7 +20,8 @@ class Surface(Mobject):
"resolution": (101, 101),
"color": GREY,
"opacity": 1.0,
"gloss": 0.3,
"reflectiveness": 0.3,
"gloss": 0.1,
"shadow": 0.4,
"prefered_creation_axis": 1,
# For du and dv steps. Much smaller and numerical error
@@ -161,6 +162,11 @@ class Surface(Mobject):
tri_is[k::3] = tri_is[k::3][indices]
return self
def always_sort_to_camera(self, camera):
self.add_updater(lambda m: m.sort_faces_back_to_front(
camera.get_location() - self.get_center()
))
# For shaders
def get_shader_data(self):
s_points, du_points, dv_points = self.get_surface_points_and_nudged_points()
@@ -181,9 +187,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)
@@ -254,7 +260,7 @@ class TexturedSurface(Surface):
super().init_uniforms()
self.uniforms["num_textures"] = self.num_textures
def init_colors(self):
def init_colors(self, override=True):
self.data["opacity"] = np.array([self.uv_surface.data["rgbas"][:, 3]])
def set_opacity(self, opacity, recurse=True):

View File

@@ -12,6 +12,7 @@ from manimlib.utils.bezier import get_smooth_quadratic_bezier_handle_points
from manimlib.utils.bezier import get_smooth_cubic_bezier_handle_points
from manimlib.utils.bezier import get_quadratic_approximation_of_cubic
from manimlib.utils.bezier import interpolate
from manimlib.utils.bezier import inverse_interpolate
from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.bezier import partial_quadratic_bezier_points
from manimlib.utils.color import rgb_to_hex
@@ -52,9 +53,8 @@ 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": [
('point', np.float32, (3,)),
('unit_normal', np.float32, (3,)),
@@ -75,7 +75,6 @@ class VMobject(Mobject):
self.needs_new_triangulation = True
self.triangulation = np.zeros(0, dtype='i4')
super().__init__(**kwargs)
self.refresh_unit_normal()
def get_group_class(self):
return VGroup
@@ -91,7 +90,7 @@ class VMobject(Mobject):
})
# Colors
def init_colors(self):
def init_colors(self, override=True):
self.set_fill(
color=self.fill_color or self.color,
opacity=self.fill_opacity,
@@ -104,26 +103,51 @@ class VMobject(Mobject):
)
self.set_gloss(self.gloss)
self.set_flat_stroke(self.flat_stroke)
if not override:
for submobjects in self.submobjects:
submobjects.init_colors(override=False)
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)], dtype=float)
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 set_backstroke(self, color=BLACK, width=3, background=True):
self.set_stroke(color, width, background=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,
@@ -132,6 +156,8 @@ class VMobject(Mobject):
stroke_opacity=None,
stroke_rgba=None,
stroke_width=None,
stroke_background=True,
reflectiveness=None,
gloss=None,
shadow=None,
recurse=True):
@@ -146,15 +172,21 @@ class VMobject(Mobject):
if stroke_rgba is not None:
self.data['stroke_rgba'] = resize_with_interpolation(stroke_rgba, len(fill_rgba))
self.set_stroke(width=stroke_width)
self.set_stroke(
width=stroke_width,
background=stroke_background,
)
else:
self.set_stroke(
color=stroke_color,
width=stroke_width,
opacity=stroke_opacity,
recurse=recurse,
background=stroke_background,
)
if reflectiveness is not None:
self.set_reflectiveness(reflectiveness, recurse=recurse)
if gloss is not None:
self.set_gloss(gloss, recurse=recurse)
if shadow is not None:
@@ -163,9 +195,11 @@ class VMobject(Mobject):
def get_style(self):
return {
"fill_rgba": self.data['fill_rgba'],
"stroke_rgba": self.data['stroke_rgba'],
"stroke_width": self.data['stroke_width'],
"fill_rgba": self.data['fill_rgba'].copy(),
"stroke_rgba": self.data['stroke_rgba'].copy(),
"stroke_width": self.data['stroke_width'].copy(),
"stroke_background": self.draw_stroke_behind_fill,
"reflectiveness": self.get_reflectiveness(),
"gloss": self.get_gloss(),
"shadow": self.get_shadow(),
}
@@ -195,16 +229,17 @@ class VMobject(Mobject):
return self
def fade(self, darkness=0.5, recurse=True):
factor = 1.0 - darkness
self.set_fill(
opacity=factor * self.get_fill_opacity(),
recurse=False,
)
self.set_stroke(
opacity=factor * self.get_stroke_opacity(),
recurse=False,
)
super().fade(darkness, recurse)
mobs = self.get_family() if recurse else [self]
for mob in mobs:
factor = 1.0 - darkness
mob.set_fill(
opacity=factor * mob.get_fill_opacity(),
recurse=False,
)
mob.set_stroke(
opacity=factor * mob.get_stroke_opacity(),
recurse=False,
)
return self
def get_fill_colors(self):
@@ -226,7 +261,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
@@ -254,12 +289,12 @@ class VMobject(Mobject):
return self.get_stroke_opacities()[0]
def get_color(self):
if self.has_stroke():
return self.get_stroke_color()
return self.get_fill_color()
if self.has_fill():
return self.get_fill_color()
return self.get_stroke_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())
@@ -350,7 +385,10 @@ class VMobject(Mobject):
def add_smooth_cubic_curve_to(self, handle, point):
self.throw_error_if_no_points()
new_handle = self.get_reflection_of_last_handle()
if self.get_num_points() == 1:
new_handle = self.get_points()[-1]
else:
new_handle = self.get_reflection_of_last_handle()
self.add_cubic_bezier_curve_to(new_handle, handle, point)
def has_new_path_started(self):
@@ -406,7 +444,10 @@ class VMobject(Mobject):
def set_points_smoothly(self, points, true_smooth=False):
self.set_points_as_corners(points)
self.make_smooth()
if true_smooth:
self.make_smooth()
else:
self.make_approximately_smooth()
return self
def change_anchor_mode(self, mode):
@@ -478,10 +519,10 @@ class VMobject(Mobject):
nppc = self.n_points_per_curve
remainder = len(points) % nppc
points = points[:len(points) - remainder]
return [
return (
points[i:i + nppc]
for i in range(0, len(points), nppc)
]
)
def get_bezier_tuples(self):
return self.get_bezier_tuples_from_points(self.get_points())
@@ -517,12 +558,35 @@ class VMobject(Mobject):
def get_num_curves(self):
return self.get_num_points() // self.n_points_per_curve
def point_from_proportion(self, alpha):
def quick_point_from_proportion(self, alpha):
# Assumes all curves have the same length, so is inaccurate
num_curves = self.get_num_curves()
n, residue = integer_interpolate(0, num_curves, alpha)
curve_func = self.get_nth_curve_function(n)
return curve_func(residue)
def point_from_proportion(self, alpha):
if alpha <= 0:
return self.get_start()
elif alpha >= 1:
return self.get_end()
partials = [0]
for tup in self.get_bezier_tuples():
# Approximate length with straight line from start to end
arclen = get_norm(tup[0] - tup[-1])
partials.append(partials[-1] + arclen)
full = partials[-1]
if full == 0:
return self.get_start()
# First index where the partial lenth is more alpha times the full length
i = next(
(i for i, x in enumerate(partials) if x >= full * alpha),
len(partials) # Default
)
residue = inverse_interpolate(partials[i - 1] / full, partials[i] / full, alpha)
return self.get_nth_curve_function(i - 1)(residue)
def get_anchors_and_handles(self):
"""
returns anchors1, handles, anchors2,
@@ -603,17 +667,19 @@ class VMobject(Mobject):
area_vect = self.get_area_vector()
area = get_norm(area_vect)
if area > 0:
return area_vect / area
normal = area_vect / area
else:
points = self.get_points()
return get_unit_normal(
normal = get_unit_normal(
points[1] - points[0],
points[2] - points[1],
)
self.data["unit_normal"][:] = normal
return normal
def refresh_unit_normal(self):
for mob in self.get_family():
mob.data["unit_normal"][:] = mob.get_unit_normal(recompute=True)
mob.get_unit_normal(recompute=True)
return self
# Alignment
@@ -675,7 +741,7 @@ class VMobject(Mobject):
if len(points) == 1:
return np.repeat(points, nppc * n, 0)
bezier_groups = self.get_bezier_tuples_from_points(points)
bezier_groups = list(self.get_bezier_tuples_from_points(points))
norms = np.array([
get_norm(bg[nppc - 1] - bg[0])
for bg in bezier_groups
@@ -771,7 +837,7 @@ class VMobject(Mobject):
# how to send the points as to the vertex shader.
# First triangles come directly from the points
if normal_vector is None:
normal_vector = self.get_unit_normal()
normal_vector = self.get_unit_normal(recompute=True)
if not self.needs_new_triangulation:
return self.triangulation
@@ -827,11 +893,11 @@ class VMobject(Mobject):
def triggers_refreshed_triangulation(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
old_points = self.get_points()
old_points = self.get_points().copy()
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 +918,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
@@ -972,6 +1039,10 @@ class VGroup(VMobject):
super().__init__(**kwargs)
self.add(*vmobjects)
def __add__(self: 'VGroup', other: 'VMobject' or 'VGroup'):
assert(isinstance(other, VMobject))
return self.add(other)
class VectorizedPoint(Point, VMobject):
CONFIG = {
@@ -983,7 +1054,8 @@ class VectorizedPoint(Point, VMobject):
}
def __init__(self, location=ORIGIN, **kwargs):
super().__init__(**kwargs)
Point.__init__(self, **kwargs)
VMobject.__init__(self, **kwargs)
self.set_points(np.array([location]))

View File

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

View File

@@ -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

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

View File

@@ -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

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

View File

@@ -2,7 +2,6 @@ import inspect
import random
import platform
import itertools as it
import logging
from functools import wraps
from tqdm import tqdm as ProgressDisplay
@@ -11,16 +10,17 @@ import time
from manimlib.animation.animation import prepare_animation
from manimlib.animation.transform import MoveToTarget
from manimlib.mobject.mobject import Point
from manimlib.camera.camera import Camera
from manimlib.constants import DEFAULT_WAIT_TIME
from manimlib.mobject.mobject import Mobject
from manimlib.mobject.mobject import Point
from manimlib.scene.scene_file_writer import SceneFileWriter
from manimlib.utils.config_ops import digest_config
from manimlib.utils.family_ops import extract_mobject_family_members
from manimlib.utils.family_ops import restructure_list_to_exclude_certain_family_members
from manimlib.event_handler.event_type import EventType
from manimlib.event_handler import EVENT_DISPATCHER
from manimlib.logger import log
class Scene(object):
@@ -45,16 +45,19 @@ class Scene(object):
from manimlib.window import Window
self.window = Window(scene=self, **self.window_config)
self.camera_config["ctx"] = self.window.ctx
self.camera_config["frame_rate"] = 30 # Where's that 30 from?
else:
self.window = None
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
self.original_skipping_status = self.skip_animations
if self.start_at_animation_number is not None:
self.skip_animations = True
# Items associated with interaction
self.mouse_point = Point()
@@ -100,10 +103,12 @@ class Scene(object):
# If there is a window, enter a loop
# which updates the frame while under
# the hood calling the pyglet event loop
log.info("Tips: You are now in the interactive mode. Now you can use the keyboard"
" and the mouse to interact with the scene. Just press `q` if you want to quit.")
self.quit_interaction = False
self.lock_static_mobject_data()
while not (self.window.is_closing or self.quit_interaction):
self.update_frame()
self.update_frame(1 / self.camera.frame_rate)
if self.window.is_closing:
self.window.destroy()
if self.quit_interaction:
@@ -117,17 +122,22 @@ class Scene(object):
self.stop_skipping()
self.linger_after_completion = False
self.update_frame()
# Save scene state at the point of embedding
self.save_state()
from IPython.terminal.embed import InteractiveShellEmbed
shell = InteractiveShellEmbed()
# Have the frame update after each command
shell.events.register('post_run_cell', lambda *a, **kw: self.update_frame())
# Use the locals of the caller as the local namespace
# once embeded, and add a few custom shortcuts
# once embedded, and add a few custom shortcuts
local_ns = inspect.currentframe().f_back.f_locals
local_ns["touch"] = self.interact
for term in ("play", "wait", "add", "remove", "clear", "save_state", "restore"):
local_ns[term] = getattr(self, term)
log.info("Tips: Now the embed iPython terminal is open. But you can't interact with"
" the window directly. To do so, you need to type `touch()` or `self.interact()`")
shell(local_ns=local_ns, stack_depth=2)
# End scene when exiting an embed.
raise EndSceneEarlyException()
@@ -176,6 +186,13 @@ class Scene(object):
for mob in self.mobjects
])
def has_time_based_updaters(self):
return any([
sm.has_time_based_updater()
for mob in self.mobjects()
for sm in mob.get_family()
])
# Related to time
def get_time(self):
return self.time
@@ -247,63 +264,70 @@ class Scene(object):
def get_mobject_copies(self):
return [m.copy() for m in self.mobjects]
def point_to_mobject(self, point, search_set=None, buff=0):
"""
E.g. if clicking on the scene, this returns the top layer mobject
under a given point
"""
if search_set is None:
search_set = self.mobjects
for mobject in reversed(search_set):
if mobject.is_point_touching(point, buff=buff):
return mobject
return None
# Related to skipping
def update_skipping_status(self):
if self.start_at_animation_number is not None:
if self.num_plays == self.start_at_animation_number:
self.stop_skipping()
self.skip_time = self.time
if not self.original_skipping_status:
self.stop_skipping()
if self.end_at_animation_number is not None:
if self.num_plays >= self.end_at_animation_number:
raise EndSceneEarlyException()
def stop_skipping(self):
if self.skip_animations:
self.skip_animations = False
self.skip_time += self.time
self.virtual_animation_start_time = self.time
self.skip_animations = False
# Methods associated with running animations
def get_time_progression(self, run_time, n_iterations=None, override_skip_animations=False):
def get_time_progression(self, run_time, n_iterations=None, desc="", override_skip_animations=False):
if self.skip_animations and not override_skip_animations:
times = [run_time]
return [run_time]
else:
step = 1 / self.camera.frame_rate
times = np.arange(0, run_time, step)
time_progression = ProgressDisplay(
if self.file_writer.has_progress_display:
self.file_writer.set_progress_display_subdescription(desc)
return times
return ProgressDisplay(
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,
desc=desc,
)
return time_progression
def get_run_time(self, animations):
return np.max([animation.run_time for animation in animations])
def get_animation_time_progression(self, animations):
run_time = self.get_run_time(animations)
time_progression = self.get_time_progression(run_time)
time_progression.set_description("".join([
f"Animation {self.num_plays}: {animations[0]}",
", etc." if len(animations) > 1 else "",
]))
description = f"{self.num_plays} {animations[0]}"
if len(animations) > 1:
description += ", etc."
time_progression = self.get_time_progression(run_time, desc=description)
return time_progression
def get_wait_time_progression(self, duration, stop_condition):
def get_wait_time_progression(self, duration, stop_condition=None):
kw = {"desc": f"{self.num_plays} Waiting"}
if stop_condition is not None:
time_progression = self.get_time_progression(
duration,
n_iterations=-1, # So it doesn't show % progress
override_skip_animations=True
)
time_progression.set_description(
"Waiting for {}".format(stop_condition.__name__)
)
else:
time_progression = self.get_time_progression(duration)
time_progression.set_description(
"Waiting {}".format(self.num_plays)
)
return time_progression
kw["n_iterations"] = -1 # So it doesn't show % progress
kw["override_skip_animations"] = True
return self.get_time_progression(duration, **kw)
def anims_from_play_args(self, *args, **kwargs):
"""
@@ -443,10 +467,7 @@ class Scene(object):
@handle_play_like_call
def play(self, *args, **kwargs):
if len(args) == 0:
logging.log(
logging.WARNING,
"Called Scene.play with no animations"
)
log.warning("Called Scene.play with no animations")
return
animations = self.anims_from_play_args(*args, **kwargs)
self.lock_static_mobject_data(*animations)
@@ -458,27 +479,18 @@ class Scene(object):
@handle_play_like_call
def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None):
self.update_mobjects(dt=0) # Any problems with this?
if self.should_update_mobjects():
self.lock_static_mobject_data()
time_progression = self.get_wait_time_progression(duration, stop_condition)
last_t = 0
for t in time_progression:
dt = t - last_t
last_t = t
self.update_frame(dt)
self.emit_frame()
if stop_condition is not None and stop_condition():
time_progression.close()
break
self.unlock_mobject_data()
elif self.skip_animations:
# Do nothing
return self
else:
self.update_frame(duration)
n_frames = int(duration * self.camera.frame_rate)
for n in range(n_frames):
self.emit_frame()
self.lock_static_mobject_data()
time_progression = self.get_wait_time_progression(duration, stop_condition)
last_t = 0
for t in time_progression:
dt = t - last_t
last_t = t
self.update_frame(dt)
self.emit_frame()
if stop_condition is not None and stop_condition():
time_progression.close()
break
self.unlock_mobject_data()
return self
def wait_until(self, stop_condition, max_time=60):
@@ -570,7 +582,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)
@@ -586,7 +598,7 @@ class Scene(object):
try:
char = chr(symbol)
except OverflowError:
print(" Warning: The value of the pressed key is too large.")
log.warning("The value of the pressed key is too large.")
return
event_data = {"symbol": symbol, "modifiers": modifiers}

View File

@@ -5,6 +5,7 @@ import subprocess as sp
import os
import sys
import platform
from tqdm import tqdm as ProgressDisplay
from manimlib.constants import FFMPEG_BIN
from manimlib.utils.config_ops import digest_config
@@ -12,6 +13,7 @@ from manimlib.utils.file_ops import guarantee_existence
from manimlib.utils.file_ops import add_extension_if_not_present
from manimlib.utils.file_ops import get_sorted_integer_files
from manimlib.utils.sounds import get_full_sound_file_path
from manimlib.logger import log
class SceneFileWriter(object):
@@ -34,12 +36,15 @@ class SceneFileWriter(object):
"open_file_upon_completion": False,
"show_file_location_upon_completion": False,
"quiet": False,
"total_frames": 0,
"progress_description_len": 60,
}
def __init__(self, scene, **kwargs):
digest_config(self, kwargs)
self.scene = scene
self.writing_process = None
self.has_progress_display = False
self.init_output_directories()
self.init_audio()
@@ -71,10 +76,14 @@ class SceneFileWriter(object):
return path
def get_default_scene_name(self):
if self.file_name is None:
return self.scene.__class__.__name__
else:
return self.file_name
name = str(self.scene)
saan = self.scene.start_at_animation_number
eaan = self.scene.end_at_animation_number
if saan is not None:
name += f"_{saan}"
if eaan is not None:
name += f"_{eaan}"
return name
def get_resolution_directory(self):
pixel_height = self.scene.camera.pixel_height
@@ -183,7 +192,7 @@ class SceneFileWriter(object):
'-s', f'{width}x{height}', # size of one frame
'-pix_fmt', 'rgba',
'-r', str(fps), # frames per second
'-i', '-', # The imput comes from a pipe
'-i', '-', # The input comes from a pipe
'-vf', 'vflip',
'-an', # Tells FFMPEG not to expect any audio
'-loglevel', 'error',
@@ -204,15 +213,39 @@ class SceneFileWriter(object):
command += [self.temp_file_path]
self.writing_process = sp.Popen(command, stdin=sp.PIPE)
if self.total_frames > 0:
self.progress_display = ProgressDisplay(
range(self.total_frames),
# bar_format="{l_bar}{bar}|{n_fmt}/{total_fmt}",
leave=False,
ascii=True if platform.system() == 'Windows' else None,
dynamic_ncols=True,
)
self.has_progress_display = True
def set_progress_display_subdescription(self, sub_desc):
desc_len = self.progress_description_len
file = os.path.split(self.get_movie_file_path())[1]
full_desc = f"Rendering {file} ({sub_desc})"
if len(full_desc) > desc_len:
full_desc = full_desc[:desc_len - 4] + "...)"
else:
full_desc += " " * (desc_len - len(full_desc))
self.progress_display.set_description(full_desc)
def write_frame(self, camera):
if self.write_to_movie:
raw_bytes = camera.get_raw_fbo_data()
self.writing_process.stdin.write(raw_bytes)
if self.has_progress_display:
self.progress_display.update()
def close_movie_pipe(self):
self.writing_process.stdin.close()
self.writing_process.wait()
self.writing_process.terminate()
if self.has_progress_display:
self.progress_display.close()
shutil.move(self.temp_file_path, self.final_file_path)
def combine_movie_files(self):
@@ -231,7 +264,7 @@ class SceneFileWriter(object):
**kwargs
)
if len(partial_movie_files) == 0:
print("No animations in this scene")
log.warning("No animations in this scene")
return
# Write a file partial_file_list.txt containing all
@@ -275,7 +308,7 @@ class SceneFileWriter(object):
)
temp_file_path = stem + "_temp" + ext
commands = [
"ffmpeg",
FFMPEG_BIN,
"-i", movie_file_path,
"-i", sound_file_path,
'-y', # overwrite output file if it exists
@@ -300,7 +333,8 @@ class SceneFileWriter(object):
self.print_file_ready_message(file_path)
def print_file_ready_message(self, file_path):
print(f"\nFile ready at {file_path}\n")
if not self.quiet:
log.info(f"File ready at {file_path}")
def should_open_file(self):
return any([

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

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

View File

@@ -1,6 +1,6 @@
uniform vec2 frame_shape;
uniform float anti_alias_width;
uniform vec3 camera_center;
uniform vec3 camera_offset;
uniform mat3 camera_rotation;
uniform float is_fixed_in_frame;
uniform float focal_distance;

View File

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

View File

@@ -13,39 +13,56 @@ vec4 add_light(vec4 color,
vec3 point,
vec3 unit_normal,
vec3 light_coords,
vec3 cam_coords,
float reflectiveness,
float gloss,
float shadow){
if(gloss == 0.0 && shadow == 0.0) return color;
if(reflectiveness == 0.0 && 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;
vec4 result = color;
// 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;
// cam_coords = vec3(0, 0, focal_distance);
vec3 to_camera = normalize(cam_coords - point);
vec3 to_light = normalize(light_coords - point);
// Note, this effectively treats surfaces as two-sided
// if(dot(to_camera, unit_normal) < 0) unit_normal *= -1;
float light_to_normal = dot(to_light, unit_normal);
// When unit normal points towards light, brighten
float bright_factor = max(light_to_normal, 0) * reflectiveness;
// For glossy surface, add extra shine if light beam go towards camera
vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal);
float dot_prod = dot(normalize(light_reflection), normalize(to_camera));
float shine = gloss * exp(-3 * pow(1 - dot_prod, 2));
float dp2 = dot(normalize(to_light), unit_normal);
float darkening = mix(1, max(dp2, 0), shadow);
return vec4(
darkening * mix(color.rgb, vec3(1.0), shine),
color.a
);
float light_to_cam = dot(light_reflection, to_camera);
float shine = gloss * exp(-3 * pow(1 - light_to_cam, 2));
bright_factor += shine;
result.rgb = mix(result.rgb, vec3(1.0), bright_factor);
if (light_to_normal < 0){
// Darken
result.rgb = mix(result.rgb, vec3(0.0), -light_to_normal * shadow);
}
// float darkening = mix(1, max(light_to_normal, 0), shadow);
// return vec4(
// darkening * mix(color.rgb, vec3(1.0), shine),
// color.a
// );
return result;
}
vec4 finalize_color(vec4 color,
vec3 point,
vec3 unit_normal,
vec3 light_coords,
vec3 cam_coords,
float reflectiveness,
float gloss,
float shadow){
///// INSERT COLOR FUNCTION HERE /////
// The line above may be replaced by arbitrary code snippets, as per
// the method Mobject.set_color_by_code
return add_light(color, point, unit_normal, light_coords, gloss, shadow);
return add_light(
color, point, unit_normal, light_coords, cam_coords,
reflectiveness, gloss, shadow
);
}

View File

@@ -1,5 +1,5 @@
// Assumes the following uniforms exist in the surrounding context:
// uniform vec3 camera_center;
// uniform vec3 camera_offset;
// uniform mat3 camera_rotation;
vec3 get_rotated_surface_unit_normal_vector(vec3 point, vec3 du_point, vec3 dv_point){

View File

@@ -1,6 +1,6 @@
// Assumes the following uniforms exist in the surrounding context:
// uniform float is_fixed_in_frame;
// uniform vec3 camera_center;
// uniform vec3 camera_offset;
// uniform mat3 camera_rotation;
vec3 rotate_point_into_frame(vec3 point){
@@ -15,5 +15,5 @@ vec3 position_point_into_frame(vec3 point){
if(bool(is_fixed_in_frame)){
return point;
}
return rotate_point_into_frame(point - camera_center);
return rotate_point_into_frame(point - camera_offset);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
#INSERT camera_uniform_declarations.glsl
in vec4 color;
in float fill_all; // Either 0 or 1e
in float fill_all; // Either 0 or 1
in float uv_anti_alias_width;
in vec3 xyz_coords;

View File

@@ -11,6 +11,8 @@ uniform float focal_distance;
uniform float is_fixed_in_frame;
// Needed for finalize_color
uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss;
uniform float shadow;
@@ -44,6 +46,8 @@ void emit_vertex_wrapper(vec3 point, int index){
point,
v_global_unit_normal[index],
light_source_position,
camera_position,
reflectiveness,
gloss,
shadow
);

View File

@@ -13,7 +13,9 @@ uniform float flat_stroke;
//Needed for lighting
uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float joint_type;
uniform float reflectiveness;
uniform float gloss;
uniform float shadow;
@@ -259,6 +261,8 @@ void main() {
xyz_coords,
v_global_unit_normal[index_map[i]],
light_source_position,
camera_position,
reflectiveness,
gloss,
shadow
);

View File

@@ -1,8 +1,11 @@
#version 330
uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss;
uniform float shadow;
uniform float focal_distance;
in vec3 xyz_coords;
in vec3 v_normal;
@@ -18,6 +21,8 @@ void main() {
xyz_coords,
normalize(v_normal),
light_source_position,
camera_position,
reflectiveness,
gloss,
shadow
);

View File

@@ -4,8 +4,11 @@ uniform sampler2D LightTexture;
uniform sampler2D DarkTexture;
uniform float num_textures;
uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss;
uniform float shadow;
uniform float focal_distance;
in vec3 xyz_coords;
in vec3 v_normal;
@@ -35,6 +38,8 @@ void main() {
xyz_coords,
normalize(v_normal),
light_source_position,
camera_position,
reflectiveness,
gloss,
shadow
);

View File

@@ -1,9 +1,13 @@
#version 330
uniform vec3 light_source_position;
uniform vec3 camera_position;
uniform float reflectiveness;
uniform float gloss;
uniform float shadow;
uniform float anti_alias_width;
uniform float focal_distance;
uniform float glow_factor;
in vec4 color;
in float radius;
@@ -21,14 +25,23 @@ void main() {
if (signed_dist > 0.5 * anti_alias_width){
discard;
}
vec3 normal = vec3(diff / radius, sqrt(1 - (dist * dist) / (radius * radius)));
frag_color = finalize_color(
color,
vec3(point.xy, 0.0),
normal,
light_source_position,
gloss,
shadow
);
frag_color = color;
if(gloss > 0 || shadow > 0){
vec3 normal = vec3(diff / radius, sqrt(1 - (dist * dist) / (radius * radius)));
frag_color = finalize_color(
frag_color,
vec3(point.xy, 0.0),
normal,
light_source_position,
camera_position,
reflectiveness,
gloss,
shadow
);
}
if(glow_factor > 0){
frag_color.a *= pow(1 - dist / radius, glow_factor);
}
frag_color.a *= smoothstep(0.5, -0.5, signed_dist / anti_alias_width);
}

View File

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

View File

@@ -4,9 +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.simple_functions import clip_in_place
from manimlib.utils.space_ops import normalize
from manimlib.utils.iterables import resize_with_interpolation
def color_to_rgb(color):
@@ -103,21 +103,23 @@ def random_color():
return Color(rgb=(random.random() for i in range(3)))
def get_shaded_rgb(rgb, point, unit_normal_vect, light_source):
to_sun = normalize(light_source - point)
factor = 0.5 * np.dot(unit_normal_vect, to_sun)**3
if factor < 0:
factor *= 0.5
result = rgb + factor
clip_in_place(rgb + factor, 0, 1)
return result
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

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

Some files were not shown because too many files have changed in this diff Show More