mirror of
https://github.com/3b1b/manim.git
synced 2026-01-13 00:18:05 -05:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4356c42e00 | ||
|
|
aea79be6cc | ||
|
|
8ef42fae24 | ||
|
|
6be6bd3075 | ||
|
|
a33eac7aa8 | ||
|
|
9d6a28bc29 | ||
|
|
06405d5758 | ||
|
|
46e356e791 | ||
|
|
97ca42d454 | ||
|
|
a4eee6f44c | ||
|
|
8cac16b452 | ||
|
|
719cd8cde3 | ||
|
|
0bb9216c14 | ||
|
|
6f9df8db26 | ||
|
|
3756605a45 | ||
|
|
0cab23b2ba | ||
|
|
aef02bfcf9 | ||
|
|
9d04e287d7 | ||
|
|
97c0f4857b | ||
|
|
7f9b0a7eac | ||
|
|
133724d29a | ||
|
|
559b96e7ce | ||
|
|
f29ef87bba | ||
|
|
fc1e916f42 | ||
|
|
b3b7d214ad | ||
|
|
602809758e | ||
|
|
f9351536e4 | ||
|
|
67f5b10626 | ||
|
|
baba6929df | ||
|
|
d6b20a7306 | ||
|
|
4c3ba7f674 | ||
|
|
3883f57bf8 | ||
|
|
d2e0811285 | ||
|
|
1e2a6ffb8a | ||
|
|
56e5696163 |
13
.github/workflows/publish.yml
vendored
13
.github/workflows/publish.yml
vendored
@@ -8,6 +8,11 @@ jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python: ["py36", "py37", "py38", "py39", "py310"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -20,11 +25,13 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools wheel twine build
|
||||
|
||||
- name: Build and publish
|
||||
|
||||
- name: Build wheels
|
||||
run: python setup.py bdist_wheel --python-tag ${{ matrix.python }}
|
||||
|
||||
- name: Upload wheels
|
||||
env:
|
||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
run: |
|
||||
python -m build
|
||||
twine upload dist/*
|
||||
@@ -1,38 +1,87 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
v1.5.0
|
||||
------
|
||||
|
||||
Fixed bugs
|
||||
^^^^^^^^^^
|
||||
- Bug fix for the case of calling ``Write`` on a null object (`#1740 <https://github.com/3b1b/manim/pull/1740>`__)
|
||||
|
||||
|
||||
New Features
|
||||
^^^^^^^^^^^^
|
||||
- Added ``TransformMatchingMTex`` (`#1725 <https://github.com/3b1b/manim/pull/1725>`__)
|
||||
- Added ``ImplicitFunction`` (`#1727 <https://github.com/3b1b/manim/pull/1727>`__)
|
||||
- Added ``Polyline`` (`#1731 <https://github.com/3b1b/manim/pull/1731>`__)
|
||||
- Allowed ``Mobject.set_points`` to take in an empty list, and added ``Mobject.add_point`` (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/a64259158538eae6043566aaf3d3329ff4ac394b>`__)
|
||||
- Added ``Scene.refresh_locked_data`` (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/33d2894c167c577a15fdadbaf26488ff1f5bff87>`__)
|
||||
- Added presenter mode to scenes with ``-p`` option (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/9a9cc8bdacb7541b7cd4a52ad705abc21f3e27fe>`__ and `#1742 <https://github.com/3b1b/manim/pull/1742>`__)
|
||||
- Allowed for an embed by hitting ``ctrl+shift+e`` during interaction (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/9df12fcb7d8360e51cd7021d6877ca1a5c31835e>`__ and `#1746 <https://github.com/3b1b/manim/pull/1746>`__)
|
||||
- Added ``Mobject.set_min_width/height/depth`` (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/2798d15591a0375ae6bb9135473e6f5328267323>`__)
|
||||
- Allowed ``Mobject.match_coord/x/y/z`` to take in a point (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/29a4d3e82ba94c007c996b2d1d0f923941452698>`__)
|
||||
- Added ``text_config`` to ``DecimalNumber`` (`#1744 <https://github.com/3b1b/manim/pull/1744>`__)
|
||||
|
||||
|
||||
Refactor
|
||||
^^^^^^^^
|
||||
- Refactored ``MTex`` (`#1725 <https://github.com/3b1b/manim/pull/1725>`__)
|
||||
- Refactored ``SVGMobject`` with svgelements (`#1731 <https://github.com/3b1b/manim/pull/1731>`__)
|
||||
- Made sure ``ParametricCurve`` has at least one point (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/2488b9e866fb1ecb842a27dd9f4956ec167e3dee>`__)
|
||||
- Set default to no tips on ``Axes`` (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/6c6d387a210756c38feca7d34838aa9ac99bb58a>`__)
|
||||
- Stopped displaying when writing tex string is happening (`#1739 <https://github.com/3b1b/manim/pull/1739/commits/58e06e8f6b7c5059ff315d51fd0018fec5cfbb05>`__)
|
||||
- Reorganize inheriting order and refactor SVGMobject (`#1745 <https://github.com/3b1b/manim/pull/1745>`__)
|
||||
|
||||
|
||||
Dependencies
|
||||
^^^^^^^^^^^^
|
||||
- Added dependency on ``isosurfaces`` (`#1727 <https://github.com/3b1b/manim/pull/1727>`__)
|
||||
- Removed dependency on ``argparse`` since it's a built-in module (`#1728 <https://github.com/3b1b/manim/pull/1728>`__)
|
||||
- Removed dependency on ``pyreadline`` (`#1728 <https://github.com/3b1b/manim/pull/1728>`__)
|
||||
- Removed dependency on ``cssselect2`` (`#1731 <https://github.com/3b1b/manim/pull/1731>`__)
|
||||
- Added dependency on ``svgelements`` (`#1731 <https://github.com/3b1b/manim/pull/1731>`__)
|
||||
|
||||
|
||||
v1.4.1
|
||||
------
|
||||
|
||||
Fixed bugs
|
||||
^^^^^^^^^^
|
||||
- Temporarily fixed boolean operations' bug (`#1724 <https://github.com/3b1b/manim/pull/1724>`__)
|
||||
- Import ``Iterable`` from ``collections.abc`` instead of ``collections`` which is deprecated since python 3.9 (`d2e0811 <https://github.com/3b1b/manim/commit/d2e0811285f7908e71a65e664fec88b1af1c6144>`__)
|
||||
|
||||
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``
|
||||
- Temporarily fixed ``Lightbulb`` (`f1996f8 <https://github.com/3b1b/manim/pull/1697/commits/f1996f8479f9e33d626b3b66e9eb6995ce231d86>`__)
|
||||
- Fixed some bugs of ``SVGMobject`` (`#1712 <https://github.com/3b1b/manim/pull/1712>`__)
|
||||
- Fixed some bugs of SVG path string parser (`#1717 <https://github.com/3b1b/manim/pull/1717>`__)
|
||||
- Fixed some bugs of ``MTex`` (`#1720 <https://github.com/3b1b/manim/pull/1720>`__)
|
||||
|
||||
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``
|
||||
- Added option to add ticks on x-axis in ``BarChart`` (`#1694 <https://github.com/3b1b/manim/pull/1694>`__)
|
||||
- Added ``lable_buff`` config parameter for ``Brace`` (`#1704 <https://github.com/3b1b/manim/pull/1704>`__)
|
||||
- Added support for ``rotate skewX skewY`` transform in SVG (`#1712 <https://github.com/3b1b/manim/pull/1712>`__)
|
||||
- Added style support to ``SVGMobject`` (`#1717 <https://github.com/3b1b/manim/pull/1717>`__)
|
||||
- Added parser to <style> element of SVG (`#1719 <https://github.com/3b1b/manim/pull/1719>`__)
|
||||
- Added support for <line> element in ``SVGMobject`` (`#1719 <https://github.com/3b1b/manim/pull/1719>`__)
|
||||
|
||||
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``
|
||||
- Used ``FFMPEG_BIN`` instead of ``"ffmpeg"`` for sound incorporation (`5aa8d15 <https://github.com/3b1b/manim/pull/1697/commits/5aa8d15d85797f68a8f169ca69fd90d441a3abbe>`__)
|
||||
- Decorated ``CoordinateSystem.get_axes`` and ``.get_all_ranges`` as abstract method (`#1709 <https://github.com/3b1b/manim/pull/1709>`__)
|
||||
- Refactored SVG path string parser (`#1712 <https://github.com/3b1b/manim/pull/1712>`__)
|
||||
- Allowed ``Mobject.scale`` to receive iterable ``scale_factor`` (`#1712 <https://github.com/3b1b/manim/pull/1712>`__)
|
||||
- Refactored ``MTex`` (`#1716 <https://github.com/3b1b/manim/pull/1716>`__)
|
||||
- Improved config helper (``manimgl --config``) (`#1721 <https://github.com/3b1b/manim/pull/1721>`__)
|
||||
- Refactored ``MTex`` (`#1723 <https://github.com/3b1b/manim/pull/1723>`__)
|
||||
|
||||
Dependencies
|
||||
^^^^^^^^^^^^
|
||||
- `#1719 <https://github.com/3b1b/manim/pull/1719>`__: Added dependency on python package `cssselect2 <https://github.com/Kozea/cssselect2>`__
|
||||
- Added dependency on python package `cssselect2 <https://github.com/Kozea/cssselect2>`__ (`#1719 <https://github.com/3b1b/manim/pull/1719>`__)
|
||||
|
||||
|
||||
v1.3.0
|
||||
@@ -41,63 +90,63 @@ 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``
|
||||
- Fixed ``Mobject.stretch_to_fit_depth`` (`#1653 <https://github.com/3b1b/manim/pull/1653>`__)
|
||||
- Fixed the bug of rotating camera (`#1655 <https://github.com/3b1b/manim/pull/1655>`__)
|
||||
- Fixed ``SurfaceMesh`` to be evenly spaced (`c73d507 <https://github.com/3b1b/manim/pull/1688/commits/c73d507c76af5c8602d4118bc7538ba04c03ebae>`__)
|
||||
- Fixed ``angle_between_vectors`` add ``rotation_between_vectors`` (`82bd02d <https://github.com/3b1b/manim/pull/1688/commits/82bd02d21fbd89b71baa21e077e143f440df9014>`__)
|
||||
- Fixed ``VMobject.fade`` (`a717314 <https://github.com/3b1b/manim/pull/1688/commits/a7173142bf93fd309def0cc10f3c56f5e6972332>`__)
|
||||
- Fixed ``angle_between_vectors`` (`fbc329d <https://github.com/3b1b/manim/pull/1688/commits/fbc329d7ce3b11821d47adf6052d932f7eff724a>`__)
|
||||
- Fixed bug in ``ShowSubmobjectsOneByOne`` (`bcd0990 <https://github.com/3b1b/manim/pull/1688/commits/bcd09906bea5eaaa5352e7bee8f3153f434cf606>`__)
|
||||
- Fixed bug in ``TransformMatchingParts`` (`7023548 <https://github.com/3b1b/manim/pull/1691/commits/7023548ec62c4adb2f371aab6a8c7f62deb7c33c>`__)
|
||||
|
||||
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``
|
||||
- Added CLI flag ``--log-level`` to specify log level (`e10f850 <https://github.com/3b1b/manim/commit/e10f850d0d9f971931cc85d44befe67dc842af6d>`__)
|
||||
- Added operations (``+`` and ``*``) for ``Mobject`` (`#1667 <https://github.com/3b1b/manim/pull/1667>`__)
|
||||
- Added 4 boolean operations for ``VMobject`` in ``manimlib/mobject/boolean_ops.py`` (`#1675 <https://github.com/3b1b/manim/pull/1675>`__)
|
||||
|
||||
- ``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
|
||||
- Added reflectiveness (`81c3ae3 <https://github.com/3b1b/manim/pull/1688/commits/81c3ae30372e288dc772633dbd17def6e603753e>`__)
|
||||
- Enabled ``glow_factor`` on ``DotCloud`` (`2c7689e <https://github.com/3b1b/manim/pull/1688/commits/2c7689ed9e81229ce87c648f97f26267956c0bc9>`__)
|
||||
- Added option ``-e`` to insert embed line from the command line (`d065e19 <https://github.com/3b1b/manim/pull/1688/commits/d065e1973d1d6ebd2bece81ce4bdf0c2fff7c772>`__)
|
||||
- Improved ``point_from_proportion`` to account for arc length (`0e78027 <https://github.com/3b1b/manim/pull/1688/commits/0e78027186a976f7e5fa8d586f586bf6e6baab8d>`__)
|
||||
- Added shortcut ``set_backstroke`` for setting black background stroke (`781a993 <https://github.com/3b1b/manim/pull/1688/commits/781a9934fda6ba11f22ba32e8ccddcb3ba78592e>`__)
|
||||
- Added ``Suface.always_sort_to_camera`` (`0b898a5 <https://github.com/3b1b/manim/pull/1688/commits/0b898a5594203668ed9cad38b490ab49ba233bd4>`__)
|
||||
- Added getter methods for specific euler angles (`e899604 <https://github.com/3b1b/manim/pull/1688/commits/e899604a2d05f78202fcb3b9824ec34647237eae>`__)
|
||||
- Hade ``rotation_between_vectors`` handle identical/similar vectors (`407c53f <https://github.com/3b1b/manim/pull/1688/commits/407c53f97c061bfd8a53beacd88af4c786f9e9ee>`__)
|
||||
- Added ``Mobject.insert_submobject`` method (`49743da <https://github.com/3b1b/manim/pull/1688/commits/49743daf3244bfa11a427040bdde8e2bb79589e8>`__)
|
||||
- Created single progress display for full scene render (`9dd1f47 <https://github.com/3b1b/manim/pull/1688/commits/9dd1f47dabca1580d6102e34e44574b0cba556e7>`__)
|
||||
- Added ``Circle.get_radius`` (`264f7b1 <https://github.com/3b1b/manim/pull/1691/commits/264f7b11726e9e736f0fe472f66e38539f74e848>`__)
|
||||
- Added ``Dodecahedron`` (`83841ae <https://github.com/3b1b/manim/pull/1691/commits/83841ae41568a9c9dff44cd163106c19a74ac281>`__)
|
||||
- Added ``GlowDot`` (`a1d5147 <https://github.com/3b1b/manim/pull/1691/commits/a1d51474ea1ce3b7aa3efbe4c5e221be70ee2f5b>`__)
|
||||
- Added ``MTex`` , see `#1678 <https://github.com/3b1b/manim/pull/1678>`__ for details (`#1678 <https://github.com/3b1b/manim/pull/1678>`__)
|
||||
|
||||
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
|
||||
- Refactored support for command ``A`` in path of SVG (`#1662 <https://github.com/3b1b/manim/pull/1662>`__)
|
||||
- Refactored ``SingleStringTex.balance_braces`` (`#1662 <https://github.com/3b1b/manim/pull/1662>`__)
|
||||
- Slight tweaks to how saturation_factor works on newton-fractal (`8b454fb <https://github.com/3b1b/manim/pull/1688/commits/8b454fbe9335a7011e947093230b07a74ba9c653>`__)
|
||||
- Made it possible to set full screen preview as a default (`317a5d6 <https://github.com/3b1b/manim/pull/1688/commits/317a5d6226475b6b54a78db7116c373ef84ea923>`__)
|
||||
- Used ``quick_point_from_proportion`` for graph points (`e764da3 <https://github.com/3b1b/manim/pull/1688/commits/e764da3c3adc5ae2a4ce877b340d2b6abcddc2fc>`__)
|
||||
- Made sure ``Line.set_length`` returns self (`d2182b9 <https://github.com/3b1b/manim/pull/1688/commits/d2182b9112300558b6c074cefd685f97c10b3898>`__)
|
||||
- Better align ``SurfaceMesh`` to the corresponding surface polygons (`eea3c6b <https://github.com/3b1b/manim/pull/1688/commits/eea3c6b29438f9e9325329c4355e76b9f635e97a>`__)
|
||||
- Match ``fix_in_frame`` status for ``FlashAround`` mobject (`ee1594a <https://github.com/3b1b/manim/pull/1688/commits/ee1594a3cb7a79b8fc361e4c4397a88c7d20c7e3>`__)
|
||||
- Made sure ``Mobject.is_fixed_in_frame`` stays updated with uniforms (`ba23fbe <https://github.com/3b1b/manim/pull/1688/commits/ba23fbe71e4a038201cd7df1d200514ed1c13bc2>`__)
|
||||
- Made sure ``skip_animations`` and ``start_at_animation_number`` play well together (`98b0d26 <https://github.com/3b1b/manim/pull/1691/commits/98b0d266d2475926a606331923cca3dc1dea97ad>`__)
|
||||
- Updated progress display for full scene render (`f8e6e7d <https://github.com/3b1b/manim/pull/1691/commits/f8e6e7df3ceb6f3d845ced4b690a85b35e0b8d00>`__)
|
||||
- ``VectorizedPoint`` should call ``__init__`` for both super classes (`8f1dfab <https://github.com/3b1b/manim/pull/1691/commits/8f1dfabff04a8456f5c4df75b0f97d50b2755003>`__)
|
||||
- Used array copy when checking need for refreshing triangulation (`758f329 <https://github.com/3b1b/manim/pull/1691/commits/758f329a06a0c198b27a48c577575d94554305bf>`__)
|
||||
|
||||
|
||||
Dependencies
|
||||
^^^^^^^^^^^^
|
||||
|
||||
- `#1675 <https://github.com/3b1b/manim/pull/1675>`__: Added dependency on python package `skia-pathops <https://github.com/fonttools/skia-pathops>`__
|
||||
- Added dependency on python package `skia-pathops <https://github.com/fonttools/skia-pathops>`__ (`#1675 <https://github.com/3b1b/manim/pull/1675>`__)
|
||||
|
||||
v1.2.0
|
||||
------
|
||||
@@ -105,57 +154,57 @@ 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
|
||||
- Fixed ``put_start_and_end_on`` in 3D (`#1592 <https://github.com/3b1b/manim/pull/1592>`__)
|
||||
- Fixed ``DecimalNumber``'s scaling issue (`#1601 <https://github.com/3b1b/manim/pull/1601>`__)
|
||||
- Fixed bug with common range array used for all coordinate systems (`56df154 <https://github.com/3b1b/manim/commit/56df15453f3e3837ed731581e52a1d76d5692077>`__)
|
||||
- Fixed ``CoordinateSystem`` init bug (`8645894 <https://github.com/3b1b/manim/commit/86458942550c639a241267d04d57d0e909fcf252>`__)
|
||||
- Fixed bug for single-valued ``ValueTracker`` (`0dc096b <https://github.com/3b1b/manim/commit/0dc096bf576ea900b351e6f4a80c13a77676f89b>`__)
|
||||
- Fixed bug with SVG rectangles (`54ad355 <https://github.com/3b1b/manim/commit/54ad3550ef0c0e2fda46b26700a43fa8cde0973f>`__)
|
||||
- Fixed ``DotCloud.set_radii`` (`d45ea28 <https://github.com/3b1b/manim/commit/d45ea28dc1d92ab9c639a047c00c151382eb0131>`__)
|
||||
- Temporarily fixed bug for ``PMobject`` array resizing (`b543cc0 <https://github.com/3b1b/manim/commit/b543cc0e32d45399ee81638b6d4fb631437664cd>`__)
|
||||
- Fixed ``match_style`` (`5f878a2 <https://github.com/3b1b/manim/commit/5f878a2c1aa531b7682bd048468c72d2835c7fe5>`__)
|
||||
- Fixed negative ``path_arc`` case (`719c81d <https://github.com/3b1b/manim/commit/719c81d72b00dcf49f148d7c146774b22e0fe348>`__)
|
||||
- Fixed bug with ``CoordinateSystem.get_lines_parallel_to_axis`` (`c726eb7 <https://github.com/3b1b/manim/commit/c726eb7a180b669ee81a18555112de26a8aff6d6>`__)
|
||||
- Fixed ``ComplexPlane`` -i display bug (`7732d2f <https://github.com/3b1b/manim/commit/7732d2f0ee10449c5731499396d4911c03e89648>`__)
|
||||
|
||||
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
|
||||
- Supported the elliptical arc command ``A`` for ``SVGMobject`` (`#1598 <https://github.com/3b1b/manim/pull/1598>`__)
|
||||
- Added ``FlashyFadeIn`` (`#1607 <https://github.com/3b1b/manim/pull/1607>`__)
|
||||
- Save triangulation (`#1607 <https://github.com/3b1b/manim/pull/1607>`__)
|
||||
- Added new ``Code`` mobject (`#1625 <https://github.com/3b1b/manim/pull/1625>`__)
|
||||
- Add warnings and use rich to display log (`#1637 <https://github.com/3b1b/manim/pull/1637>`__)
|
||||
- Added ``VCube`` (`bd356da <https://github.com/3b1b/manim/commit/bd356daa99bfe3134fcb192a5f72e0d76d853801>`__)
|
||||
- Supported ``ValueTracker`` to track vectors (`6d72893 <https://github.com/3b1b/manim/commit/6d7289338234acc6658b9377c0f0084aa1fa7119>`__)
|
||||
- Added ``set_max_width``, ``set_max_height``, ``set_max_depth`` to ``Mobject`` (`3bb8f3f <https://github.com/3b1b/manim/commit/3bb8f3f0422a5dfba0da6ef122dc0c01f31aff03>`__)
|
||||
- Added ``TracgTail`` (`a35dd5a <https://github.com/3b1b/manim/commit/a35dd5a3cbdeffa3891d5aa5f80287c18dba2f7f>`__)
|
||||
- Added ``Scene.point_to_mobject`` (`acba13f <https://github.com/3b1b/manim/commit/acba13f4991b78d54c0bf93cce7ca3b351c25476>`__)
|
||||
- Added poly_fractal shader (`f84b8a6 <https://github.com/3b1b/manim/commit/f84b8a66fe9e8b3872e5c716c5c240c14bb555ee>`__)
|
||||
- Added kwargs to ``TipableVMobject.set_length`` (`b24ba19 <https://github.com/3b1b/manim/commit/b24ba19dec48ba4e38acbde8eec6d3a308b6ab83>`__)
|
||||
- Added ``Mobject.replicate`` (`17c2772 <https://github.com/3b1b/manim/commit/17c2772b84abf6392a4170030e36e981de4737d0>`__)
|
||||
- Added mandelbrot_fractal shader (`33fa76d <https://github.com/3b1b/manim/commit/33fa76dfac36e70bb5fad69dc6a336800c6dacce>`__)
|
||||
- Saved state before each embed (`f22a341 <https://github.com/3b1b/manim/commit/f22a341e8411eae9331d4dd976b5e15bc6db08d9>`__)
|
||||
- Allowed releasing of Textures (`e10a752 <https://github.com/3b1b/manim/commit/e10a752c0001e8981038faa03be4de2603d3565f>`__)
|
||||
- Consolidated and renamed newton_fractal shader (`14fbed7 <https://github.com/3b1b/manim/commit/14fbed76da4b493191136caebb8a955e2d41265b>`__)
|
||||
- Hade ``ImageMoject`` remember the filepath to the Image (`6cdbe0d <https://github.com/3b1b/manim/commit/6cdbe0d67a11ab14a6d84840a114ae6d3af10168>`__)
|
||||
|
||||
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
|
||||
- Changed back to simpler ``Mobject.scale`` implementation (`#1601 <https://github.com/3b1b/manim/pull/1601>`__)
|
||||
- Simplified ``Square`` (`b667db2 <https://github.com/3b1b/manim/commit/b667db2d311a11cbbca2a6ff511d2c3cf1675486>`__)
|
||||
- Removed unused parameter ``triangulation_locked`` (`40290ad <https://github.com/3b1b/manim/commit/40290ada8343f10901fa9151cbdf84689667786d>`__)
|
||||
- Reimplemented ``Arrow`` (`8647a64 <https://github.com/3b1b/manim/commit/8647a6429dd0c52cba14e971b8c09194a93cfd87>`__)
|
||||
- Used ``make_approximately_smooth`` for ``set_points_smoothly`` by default (`d8378d8 <https://github.com/3b1b/manim/commit/d8378d8157040cd797cc47ef9576beffd8607863>`__)
|
||||
- Refactored to call ``_handle_scale_side_effects`` after scaling takes place (`7b4199c <https://github.com/3b1b/manim/commit/7b4199c674e291f1b84678828b63b6bd4fcc6b17>`__)
|
||||
- Refactored to only call ``throw_error_if_no_points`` once for ``get_start_and_end`` (`7356a36 <https://github.com/3b1b/manim/commit/7356a36fa70a8279b43ae74e247cbd43b2bfd411>`__)
|
||||
- Made sure framerate is 30 for previewed scenes (`0787c4f <https://github.com/3b1b/manim/commit/0787c4f36270a6560b50ce3e07b30b0ec5f2ba3e>`__)
|
||||
- Pushed ``pixel_coords_to_space_coords`` to ``Window`` (`c635f19 <https://github.com/3b1b/manim/commit/c635f19f2a33e916509e53ded46f55e2afa8f5f2>`__)
|
||||
- Refactored to pass tuples and not arrays to uniforms (`d5a88d0 <https://github.com/3b1b/manim/commit/d5a88d0fa457cfcf4cb9db417a098c37c95c7051>`__)
|
||||
- Refactored to copy uniform arrays in ``Mobject.copy`` (`9483f26 <https://github.com/3b1b/manim/commit/9483f26a3b056de0e34f27acabd1a946f1adbdf9>`__)
|
||||
- Added ``bounding_box`` as exceptional key to point_cloud mobject (`ed1fc4d <https://github.com/3b1b/manim/commit/ed1fc4d5f94467d602a568466281ca2d0368b506>`__)
|
||||
- Made sure stroke width is always a float (`329d2c6 <https://github.com/3b1b/manim/commit/329d2c6eaec3d88bfb754b555575a3ea7c97a7e0>`__)
|
||||
|
||||
|
||||
v1.1.0
|
||||
|
||||
@@ -43,6 +43,7 @@ flag abbr function
|
||||
``--hd`` Render at a 1080p quality
|
||||
``--uhd`` Render at a 4k quality
|
||||
``--full_screen`` ``-f`` Show window in full screen
|
||||
``--presenter_mode`` ``-p`` Scene will stay paused during wait calls until space bar or right arrow is hit, like a slide show
|
||||
``--save_pngs`` ``-g`` Save each frame as a png
|
||||
``--save_as_gif`` ``-i`` Save the video as gif
|
||||
``--transparent`` ``-t`` Render to a movie file with an alpha channel
|
||||
@@ -58,7 +59,7 @@ flag abbr function
|
||||
``--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
|
||||
``--video_dir VIDEO_DIR`` Directory to write video
|
||||
``--config_file CONFIG_FILE`` Path to the custom configuration file
|
||||
========================================================== ====== =================================================================================================================================================================================================
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from manimlib.animation.animation import Animation
|
||||
from manimlib.animation.composition import Succession
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.mobject.mobject import Group
|
||||
from manimlib.utils.bezier import integer_interpolate
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.rate_functions import linear
|
||||
@@ -147,7 +146,7 @@ class Write(DrawBorderThenFill):
|
||||
else:
|
||||
self.run_time = 2
|
||||
if self.lag_ratio is None:
|
||||
self.lag_ratio = min(4.0 / length, 0.2)
|
||||
self.lag_ratio = min(4.0 / (length + 1.0), 0.2)
|
||||
|
||||
|
||||
class ShowIncreasingSubsets(Animation):
|
||||
|
||||
@@ -17,7 +17,6 @@ class Broadcast(LaggedStart):
|
||||
"remover": True,
|
||||
"lag_ratio": 0.2,
|
||||
"run_time": 3,
|
||||
"remover": True,
|
||||
}
|
||||
|
||||
def __init__(self, focal_point, **kwargs):
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import numpy as np
|
||||
import itertools as it
|
||||
|
||||
from manimlib.animation.composition import AnimationGroup
|
||||
from manimlib.animation.fading import FadeTransformPieces
|
||||
from manimlib.animation.fading import FadeInFromPoint
|
||||
from manimlib.animation.fading import FadeOutToPoint
|
||||
from manimlib.animation.transform import ReplacementTransform
|
||||
from manimlib.animation.transform import Transform
|
||||
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.mobject.mobject import Group
|
||||
from manimlib.mobject.svg.mtex_mobject import MTex
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.iterables import remove_list_redundancies
|
||||
|
||||
|
||||
class TransformMatchingParts(AnimationGroup):
|
||||
@@ -139,3 +143,108 @@ class TransformMatchingTex(TransformMatchingParts):
|
||||
@staticmethod
|
||||
def get_mobject_key(mobject):
|
||||
return mobject.get_tex()
|
||||
|
||||
|
||||
class TransformMatchingMTex(AnimationGroup):
|
||||
CONFIG = {
|
||||
"key_map": dict(),
|
||||
}
|
||||
|
||||
def __init__(self, source_mobject, target_mobject, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
assert isinstance(source_mobject, MTex)
|
||||
assert isinstance(target_mobject, MTex)
|
||||
anims = []
|
||||
rest_source_submobs = source_mobject.submobjects.copy()
|
||||
rest_target_submobs = target_mobject.submobjects.copy()
|
||||
|
||||
def add_anim_from(anim_class, func, source_attr, target_attr=None):
|
||||
if target_attr is None:
|
||||
target_attr = source_attr
|
||||
source_parts = func(source_mobject, source_attr)
|
||||
target_parts = func(target_mobject, target_attr)
|
||||
filtered_source_parts = [
|
||||
submob_part for submob_part in source_parts
|
||||
if all([
|
||||
submob in rest_source_submobs
|
||||
for submob in submob_part
|
||||
])
|
||||
]
|
||||
filtered_target_parts = [
|
||||
submob_part for submob_part in target_parts
|
||||
if all([
|
||||
submob in rest_target_submobs
|
||||
for submob in submob_part
|
||||
])
|
||||
]
|
||||
if not (filtered_source_parts and filtered_target_parts):
|
||||
return
|
||||
anims.append(anim_class(
|
||||
VGroup(*filtered_source_parts),
|
||||
VGroup(*filtered_target_parts),
|
||||
**kwargs
|
||||
))
|
||||
for submob in it.chain(*filtered_source_parts):
|
||||
rest_source_submobs.remove(submob)
|
||||
for submob in it.chain(*filtered_target_parts):
|
||||
rest_target_submobs.remove(submob)
|
||||
|
||||
def get_submobs_from_keys(mobject, keys):
|
||||
if not isinstance(keys, tuple):
|
||||
keys = (keys,)
|
||||
indices = []
|
||||
for key in keys:
|
||||
if isinstance(key, int):
|
||||
indices.append(key)
|
||||
elif isinstance(key, range):
|
||||
indices.extend(key)
|
||||
elif isinstance(key, str):
|
||||
all_parts = mobject.get_parts_by_tex(key)
|
||||
indices.extend(it.chain(*[
|
||||
mobject.indices_of_part(part) for part in all_parts
|
||||
]))
|
||||
else:
|
||||
raise TypeError(key)
|
||||
return VGroup(VGroup(*[
|
||||
mobject[i] for i in remove_list_redundancies(indices)
|
||||
]))
|
||||
|
||||
for source_key, target_key in self.key_map.items():
|
||||
add_anim_from(
|
||||
ReplacementTransform, get_submobs_from_keys,
|
||||
source_key, target_key
|
||||
)
|
||||
|
||||
common_specified_substrings = sorted(list(
|
||||
set(source_mobject.get_specified_substrings()).intersection(
|
||||
target_mobject.get_specified_substrings()
|
||||
)
|
||||
), key=len, reverse=True)
|
||||
for part_tex_string in common_specified_substrings:
|
||||
add_anim_from(
|
||||
FadeTransformPieces, MTex.get_parts_by_tex, part_tex_string
|
||||
)
|
||||
|
||||
common_submob_tex_strings = {
|
||||
source_submob.get_tex() for source_submob in source_mobject
|
||||
}.intersection({
|
||||
target_submob.get_tex() for target_submob in target_mobject
|
||||
})
|
||||
for tex_string in common_submob_tex_strings:
|
||||
add_anim_from(
|
||||
FadeTransformPieces,
|
||||
lambda mobject, attr: VGroup(*[
|
||||
VGroup(mob) for mob in mobject
|
||||
if mob.get_tex() == attr
|
||||
]),
|
||||
tex_string
|
||||
)
|
||||
|
||||
anims.append(FadeOutToPoint(
|
||||
VGroup(*rest_source_submobs), target_mobject.get_center(), **kwargs
|
||||
))
|
||||
anims.append(FadeInFromPoint(
|
||||
VGroup(*rest_target_submobs), source_mobject.get_center(), **kwargs
|
||||
))
|
||||
|
||||
super().__init__(*anims)
|
||||
|
||||
@@ -23,7 +23,7 @@ def parse_cli():
|
||||
module_location.add_argument(
|
||||
"file",
|
||||
nargs="?",
|
||||
help="path to file holding the python code for the scene",
|
||||
help="Path to file holding the python code for the scene",
|
||||
)
|
||||
parser.add_argument(
|
||||
"scene_names",
|
||||
@@ -65,6 +65,12 @@ def parse_cli():
|
||||
action="store_true",
|
||||
help="Show window in full screen",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p", "--presenter_mode",
|
||||
action="store_true",
|
||||
help="Scene will stay paused during wait calls until "
|
||||
"space bar or right arrow is hit, like a slide show"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-g", "--save_pngs",
|
||||
action="store_true",
|
||||
@@ -306,6 +312,7 @@ def get_configuration(args):
|
||||
"start_at_animation_number": args.start_at_animation_number,
|
||||
"end_at_animation_number": None,
|
||||
"preview": not write_file,
|
||||
"presenter_mode": args.presenter_mode,
|
||||
"leave_progress_bars": args.leave_progress_bars,
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ def get_scene_config(config):
|
||||
"end_at_animation_number",
|
||||
"leave_progress_bars",
|
||||
"preview",
|
||||
"presenter_mode",
|
||||
]
|
||||
])
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ def _convert_skia_path_to_vmobject(path, vmobject):
|
||||
vmobject.add_quadratic_bezier_curve_to(*points)
|
||||
else:
|
||||
raise Exception(f"Unsupported: {path_verb}")
|
||||
return vmobject
|
||||
return vmobject.reverse_points()
|
||||
|
||||
|
||||
class Union(VMobject):
|
||||
|
||||
@@ -277,7 +277,7 @@ class CoordinateSystem():
|
||||
class Axes(VGroup, CoordinateSystem):
|
||||
CONFIG = {
|
||||
"axis_config": {
|
||||
"include_tip": True,
|
||||
"include_tip": False,
|
||||
"numbers_to_exclude": [0],
|
||||
},
|
||||
"x_axis_config": {},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from isosurfaces import plot_isoline
|
||||
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
@@ -42,6 +44,8 @@ class ParametricCurve(VMobject):
|
||||
self.add_points_as_corners(points[1:])
|
||||
if self.use_smoothing:
|
||||
self.make_approximately_smooth()
|
||||
if not self.has_points():
|
||||
self.set_points([self.t_func(t_min)])
|
||||
return self
|
||||
|
||||
|
||||
@@ -68,3 +72,40 @@ class FunctionGraph(ParametricCurve):
|
||||
|
||||
def get_point_from_function(self, x):
|
||||
return self.t_func(x)
|
||||
|
||||
|
||||
class ImplicitFunction(VMobject):
|
||||
CONFIG = {
|
||||
"x_range": [-FRAME_X_RADIUS, FRAME_X_RADIUS],
|
||||
"y_range": [-FRAME_Y_RADIUS, FRAME_Y_RADIUS],
|
||||
"min_depth": 5,
|
||||
"max_quads": 1500,
|
||||
"use_smoothing": True
|
||||
}
|
||||
|
||||
def __init__(self, func, x_range=None, y_range=None, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.function = func
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def init_points(self):
|
||||
p_min, p_max = (
|
||||
np.array([self.x_range[0], self.y_range[0]]),
|
||||
np.array([self.x_range[1], self.y_range[1]]),
|
||||
)
|
||||
curves = plot_isoline(
|
||||
fn=lambda u: self.function(u[0], u[1]),
|
||||
pmin=p_min,
|
||||
pmax=p_max,
|
||||
min_depth=self.min_depth,
|
||||
max_quads=self.max_quads,
|
||||
) # returns a list of lists of 2D points
|
||||
curves = [
|
||||
np.pad(curve, [(0, 0), (0, 1)]) for curve in curves if curve != []
|
||||
] # add z coord as 0
|
||||
for curve in curves:
|
||||
self.start_new_path(curve[0])
|
||||
self.add_points_as_corners(curve[1:])
|
||||
if self.use_smoothing:
|
||||
self.make_smooth()
|
||||
return self
|
||||
|
||||
@@ -608,8 +608,8 @@ class Arrow(Line):
|
||||
self.insert_tip_anchor()
|
||||
return self
|
||||
|
||||
def init_colors(self, override=True):
|
||||
super().init_colors(override)
|
||||
def init_colors(self):
|
||||
super().init_colors()
|
||||
self.create_tip_with_stroke_width()
|
||||
|
||||
def get_arc_length(self):
|
||||
@@ -849,6 +849,11 @@ class Polygon(VMobject):
|
||||
return self
|
||||
|
||||
|
||||
class Polyline(Polygon):
|
||||
def init_points(self):
|
||||
self.set_points_as_corners(self.vertices)
|
||||
|
||||
|
||||
class RegularPolygon(Polygon):
|
||||
CONFIG = {
|
||||
"start_angle": None,
|
||||
|
||||
@@ -112,7 +112,7 @@ class Matrix(VMobject):
|
||||
"\\left[",
|
||||
"\\begin{array}{c}",
|
||||
*height * ["\\quad \\\\"],
|
||||
"\\end{array}"
|
||||
"\\end{array}",
|
||||
"\\right]",
|
||||
]))[0]
|
||||
bracket_pair.set_height(
|
||||
|
||||
@@ -4,7 +4,7 @@ import random
|
||||
import sys
|
||||
import moderngl
|
||||
from functools import wraps
|
||||
from collections import Iterable
|
||||
from collections.abc import Iterable
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -109,8 +109,8 @@ class Mobject(object):
|
||||
"reflectiveness": self.reflectiveness,
|
||||
}
|
||||
|
||||
def init_colors(self, override=True):
|
||||
self.set_color(self.color, self.opacity, override)
|
||||
def init_colors(self):
|
||||
self.set_color(self.color, self.opacity)
|
||||
|
||||
def init_points(self):
|
||||
# Typically implemented in subclass, unlpess purposefully left blank
|
||||
@@ -817,6 +817,21 @@ class Mobject(object):
|
||||
self.set_depth(max_depth, **kwargs)
|
||||
return self
|
||||
|
||||
def set_min_width(self, min_width, **kwargs):
|
||||
if self.get_width() < min_width:
|
||||
self.set_width(min_width, **kwargs)
|
||||
return self
|
||||
|
||||
def set_min_height(self, min_height, **kwargs):
|
||||
if self.get_height() < min_height:
|
||||
self.set_height(min_height, **kwargs)
|
||||
return self
|
||||
|
||||
def set_min_depth(self, min_depth, **kwargs):
|
||||
if self.get_depth() < min_depth:
|
||||
self.set_depth(min_depth, **kwargs)
|
||||
return self
|
||||
|
||||
def set_coord(self, value, dim, direction=ORIGIN):
|
||||
curr = self.get_coord(dim, direction)
|
||||
shift_vect = np.zeros(self.dim)
|
||||
@@ -1181,21 +1196,21 @@ class Mobject(object):
|
||||
def match_depth(self, mobject, **kwargs):
|
||||
return self.match_dim_size(mobject, 2, **kwargs)
|
||||
|
||||
def match_coord(self, mobject, dim, direction=ORIGIN):
|
||||
return self.set_coord(
|
||||
mobject.get_coord(dim, direction),
|
||||
dim=dim,
|
||||
direction=direction,
|
||||
)
|
||||
def match_coord(self, mobject_or_point, dim, direction=ORIGIN):
|
||||
if isinstance(mobject_or_point, Mobject):
|
||||
coord = mobject_or_point.get_coord(dim, direction)
|
||||
else:
|
||||
coord = mobject_or_point[dim]
|
||||
return self.set_coord(coord, dim=dim, direction=direction)
|
||||
|
||||
def match_x(self, mobject, direction=ORIGIN):
|
||||
return self.match_coord(mobject, 0, direction)
|
||||
def match_x(self, mobject_or_point, direction=ORIGIN):
|
||||
return self.match_coord(mobject_or_point, 0, direction)
|
||||
|
||||
def match_y(self, mobject, direction=ORIGIN):
|
||||
return self.match_coord(mobject, 1, direction)
|
||||
def match_y(self, mobject_or_point, direction=ORIGIN):
|
||||
return self.match_coord(mobject_or_point, 1, direction)
|
||||
|
||||
def match_z(self, mobject, direction=ORIGIN):
|
||||
return self.match_coord(mobject, 2, direction)
|
||||
def match_z(self, mobject_or_point, direction=ORIGIN):
|
||||
return self.match_coord(mobject_or_point, 2, direction)
|
||||
|
||||
def align_to(self, mobject_or_point, direction=ORIGIN):
|
||||
"""
|
||||
|
||||
@@ -2,6 +2,7 @@ from manimlib.constants import *
|
||||
from manimlib.mobject.svg.tex_mobject import SingleStringTex
|
||||
from manimlib.mobject.svg.text_mobject import Text
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.utils.iterables import hash_obj
|
||||
|
||||
|
||||
string_to_mob_map = {}
|
||||
@@ -20,23 +21,23 @@ class DecimalNumber(VMobject):
|
||||
"include_background_rectangle": False,
|
||||
"edge_to_fix": LEFT,
|
||||
"font_size": 48,
|
||||
"text_config": {} # Do not pass in font_size here
|
||||
}
|
||||
|
||||
def __init__(self, number=0, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.set_submobjects_from_number(number)
|
||||
self.init_colors()
|
||||
|
||||
def set_submobjects_from_number(self, number):
|
||||
self.number = number
|
||||
self.set_submobjects([])
|
||||
|
||||
string_to_mob_ = lambda s: self.string_to_mob(s, **self.text_config)
|
||||
num_string = self.get_num_string(number)
|
||||
self.add(*map(self.string_to_mob, num_string))
|
||||
self.add(*map(string_to_mob_, num_string))
|
||||
|
||||
# Add non-numerical bits
|
||||
if self.show_ellipsis:
|
||||
dots = self.string_to_mob("...")
|
||||
dots = string_to_mob_("...")
|
||||
dots.arrange(RIGHT, buff=2 * dots[0].get_width())
|
||||
self.add(dots)
|
||||
if self.unit is not None:
|
||||
@@ -85,10 +86,10 @@ class DecimalNumber(VMobject):
|
||||
def get_font_size(self):
|
||||
return self.data["font_size"][0]
|
||||
|
||||
def string_to_mob(self, string, mob_class=Text):
|
||||
if string not in string_to_mob_map:
|
||||
string_to_mob_map[string] = mob_class(string, font_size=1)
|
||||
mob = string_to_mob_map[string].copy()
|
||||
def string_to_mob(self, string, mob_class=Text, **kwargs):
|
||||
if (string, hash_obj(kwargs)) not in string_to_mob_map:
|
||||
string_to_mob_map[(string, hash_obj(kwargs))] = mob_class(string, font_size=1, **kwargs)
|
||||
mob = string_to_mob_map[(string, hash_obj(kwargs))].copy()
|
||||
mob.scale(self.get_font_size())
|
||||
return mob
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,100 +1,31 @@
|
||||
import itertools as it
|
||||
import re
|
||||
import string
|
||||
import os
|
||||
import hashlib
|
||||
import itertools as it
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
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 ORIGIN, UP, DOWN, LEFT, RIGHT, IN
|
||||
from manimlib.constants import DEGREES, PI
|
||||
import svgelements as se
|
||||
import numpy as np
|
||||
|
||||
from manimlib.constants import RIGHT
|
||||
from manimlib.mobject.geometry import Line
|
||||
from manimlib.mobject.geometry import Circle
|
||||
from manimlib.mobject.geometry import Polygon
|
||||
from manimlib.mobject.geometry import Polyline
|
||||
from manimlib.mobject.geometry import Rectangle
|
||||
from manimlib.mobject.geometry import RoundedRectangle
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
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.utils.iterables import hash_obj
|
||||
from manimlib.logger import log
|
||||
|
||||
|
||||
DEFAULT_STYLE = {
|
||||
"fill": "black",
|
||||
"stroke": "none",
|
||||
"fill-opacity": "1",
|
||||
"stroke-opacity": "1",
|
||||
"stroke-width": 0,
|
||||
}
|
||||
SVG_HASH_TO_MOB_MAP = {}
|
||||
|
||||
|
||||
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
|
||||
def _convert_point_to_3d(x, y):
|
||||
return np.array([x, y, 0.0])
|
||||
|
||||
|
||||
class SVGMobject(VMobject):
|
||||
@@ -102,24 +33,234 @@ class SVGMobject(VMobject):
|
||||
"should_center": True,
|
||||
"height": 2,
|
||||
"width": None,
|
||||
# Must be filled in in a subclass, or when called
|
||||
"file_name": None,
|
||||
"unpack_groups": True, # if False, creates a hierarchy of VGroups
|
||||
"stroke_width": 0.0,
|
||||
"fill_opacity": 1.0,
|
||||
"path_string_config": {}
|
||||
# Style that overrides the original svg
|
||||
"color": None,
|
||||
"opacity": None,
|
||||
"fill_color": None,
|
||||
"fill_opacity": None,
|
||||
"stroke_width": None,
|
||||
"stroke_color": None,
|
||||
"stroke_opacity": None,
|
||||
# Style that fills only when not specified
|
||||
# If None, regarded as default values from svg standard
|
||||
"svg_default": {
|
||||
"color": None,
|
||||
"opacity": None,
|
||||
"fill_color": None,
|
||||
"fill_opacity": None,
|
||||
"stroke_width": None,
|
||||
"stroke_color": None,
|
||||
"stroke_opacity": None,
|
||||
},
|
||||
"path_string_config": {},
|
||||
}
|
||||
|
||||
def __init__(self, file_name=None, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.file_name = file_name or self.file_name
|
||||
if file_name is None:
|
||||
raise Exception("Must specify file for SVGMobject")
|
||||
self.file_path = get_full_vector_image_path(file_name)
|
||||
|
||||
super().__init__(**kwargs)
|
||||
self.file_name = file_name or self.file_name
|
||||
self.init_svg_mobject()
|
||||
self.init_colors()
|
||||
self.move_into_position()
|
||||
|
||||
def init_svg_mobject(self):
|
||||
hash_val = hash_obj(self.hash_seed)
|
||||
if hash_val in SVG_HASH_TO_MOB_MAP:
|
||||
mob = SVG_HASH_TO_MOB_MAP[hash_val].copy()
|
||||
self.add(*mob)
|
||||
return
|
||||
|
||||
self.generate_mobject()
|
||||
SVG_HASH_TO_MOB_MAP[hash_val] = self.copy()
|
||||
|
||||
@property
|
||||
def hash_seed(self):
|
||||
# Returns data which can uniquely represent the result of `init_points`.
|
||||
# The hashed value of it is stored as a key in `SVG_HASH_TO_MOB_MAP`.
|
||||
return (
|
||||
self.__class__.__name__,
|
||||
self.svg_default,
|
||||
self.path_string_config,
|
||||
self.file_name
|
||||
)
|
||||
|
||||
def generate_mobject(self):
|
||||
file_path = self.get_file_path()
|
||||
element_tree = ET.parse(file_path)
|
||||
new_tree = self.modify_xml_tree(element_tree)
|
||||
# Create a temporary svg file to dump modified svg to be parsed
|
||||
modified_file_path = file_path.replace(".svg", "_.svg")
|
||||
new_tree.write(modified_file_path)
|
||||
|
||||
svg = se.SVG.parse(modified_file_path)
|
||||
os.remove(modified_file_path)
|
||||
|
||||
mobjects = self.get_mobjects_from(svg)
|
||||
self.add(*mobjects)
|
||||
self.flip(RIGHT) # Flip y
|
||||
|
||||
def get_file_path(self):
|
||||
if self.file_name is None:
|
||||
raise Exception("Must specify file for SVGMobject")
|
||||
return get_full_vector_image_path(self.file_name)
|
||||
|
||||
def modify_xml_tree(self, element_tree):
|
||||
config_style_dict = self.generate_config_style_dict()
|
||||
style_keys = (
|
||||
"fill",
|
||||
"fill-opacity",
|
||||
"stroke",
|
||||
"stroke-opacity",
|
||||
"stroke-width",
|
||||
"style"
|
||||
)
|
||||
root = element_tree.getroot()
|
||||
root_style_dict = {
|
||||
k: v for k, v in root.attrib.items()
|
||||
if k in style_keys
|
||||
}
|
||||
|
||||
new_root = ET.Element("svg", {})
|
||||
config_style_node = ET.SubElement(new_root, "g", config_style_dict)
|
||||
root_style_node = ET.SubElement(config_style_node, "g", root_style_dict)
|
||||
root_style_node.extend(root)
|
||||
return ET.ElementTree(new_root)
|
||||
|
||||
def generate_config_style_dict(self):
|
||||
keys_converting_dict = {
|
||||
"fill": ("color", "fill_color"),
|
||||
"fill-opacity": ("opacity", "fill_opacity"),
|
||||
"stroke": ("color", "stroke_color"),
|
||||
"stroke-opacity": ("opacity", "stroke_opacity"),
|
||||
"stroke-width": ("stroke_width",)
|
||||
}
|
||||
svg_default_dict = self.svg_default
|
||||
result = {}
|
||||
for svg_key, style_keys in keys_converting_dict.items():
|
||||
for style_key in style_keys:
|
||||
if svg_default_dict[style_key] is None:
|
||||
continue
|
||||
result[svg_key] = str(svg_default_dict[style_key])
|
||||
return result
|
||||
|
||||
def get_mobjects_from(self, svg):
|
||||
result = []
|
||||
for shape in svg.elements():
|
||||
if isinstance(shape, se.Group):
|
||||
continue
|
||||
mob = self.get_mobject_from(shape)
|
||||
if mob is None:
|
||||
continue
|
||||
if isinstance(shape, se.Transformable) and shape.apply:
|
||||
self.handle_transform(mob, shape.transform)
|
||||
result.append(mob)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def handle_transform(mob, matrix):
|
||||
mat = np.array([
|
||||
[matrix.a, matrix.c],
|
||||
[matrix.b, matrix.d]
|
||||
])
|
||||
vec = np.array([matrix.e, matrix.f, 0.0])
|
||||
mob.apply_matrix(mat)
|
||||
mob.shift(vec)
|
||||
return mob
|
||||
|
||||
def get_mobject_from(self, shape):
|
||||
shape_class_to_func_map = {
|
||||
se.Path: self.path_to_mobject,
|
||||
se.SimpleLine: self.line_to_mobject,
|
||||
se.Rect: self.rect_to_mobject,
|
||||
se.Circle: self.circle_to_mobject,
|
||||
se.Ellipse: self.ellipse_to_mobject,
|
||||
se.Polygon: self.polygon_to_mobject,
|
||||
se.Polyline: self.polyline_to_mobject,
|
||||
# se.Text: self.text_to_mobject, # TODO
|
||||
}
|
||||
for shape_class, func in shape_class_to_func_map.items():
|
||||
if isinstance(shape, shape_class):
|
||||
mob = func(shape)
|
||||
self.apply_style_to_mobject(mob, shape)
|
||||
return mob
|
||||
|
||||
shape_class_name = shape.__class__.__name__
|
||||
if shape_class_name != "SVGElement":
|
||||
log.warning(f"Unsupported element type: {shape_class_name}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def apply_style_to_mobject(mob, shape):
|
||||
mob.set_style(
|
||||
stroke_width=shape.stroke_width,
|
||||
stroke_color=shape.stroke.hex,
|
||||
stroke_opacity=shape.stroke.opacity,
|
||||
fill_color=shape.fill.hex,
|
||||
fill_opacity=shape.fill.opacity
|
||||
)
|
||||
return mob
|
||||
|
||||
def path_to_mobject(self, path):
|
||||
return VMobjectFromSVGPath(path, **self.path_string_config)
|
||||
|
||||
def line_to_mobject(self, line):
|
||||
return Line(
|
||||
start=_convert_point_to_3d(line.x1, line.y1),
|
||||
end=_convert_point_to_3d(line.x2, line.y2)
|
||||
)
|
||||
|
||||
def rect_to_mobject(self, rect):
|
||||
if rect.rx == 0 or rect.ry == 0:
|
||||
mob = Rectangle(
|
||||
width=rect.width,
|
||||
height=rect.height,
|
||||
)
|
||||
else:
|
||||
mob = RoundedRectangle(
|
||||
width=rect.width,
|
||||
height=rect.height * rect.rx / rect.ry,
|
||||
corner_radius=rect.rx
|
||||
)
|
||||
mob.stretch_to_fit_height(rect.height)
|
||||
mob.shift(_convert_point_to_3d(
|
||||
rect.x + rect.width / 2,
|
||||
rect.y + rect.height / 2
|
||||
))
|
||||
return mob
|
||||
|
||||
def circle_to_mobject(self, circle):
|
||||
# svgelements supports `rx` & `ry` but `r`
|
||||
mob = Circle(radius=circle.rx)
|
||||
mob.shift(_convert_point_to_3d(
|
||||
circle.cx, circle.cy
|
||||
))
|
||||
return mob
|
||||
|
||||
def ellipse_to_mobject(self, ellipse):
|
||||
mob = Circle(radius=ellipse.rx)
|
||||
mob.stretch_to_fit_height(2 * ellipse.ry)
|
||||
mob.shift(_convert_point_to_3d(
|
||||
ellipse.cx, ellipse.cy
|
||||
))
|
||||
return mob
|
||||
|
||||
def polygon_to_mobject(self, polygon):
|
||||
points = [
|
||||
_convert_point_to_3d(*point)
|
||||
for point in polygon
|
||||
]
|
||||
return Polygon(*points)
|
||||
|
||||
def polyline_to_mobject(self, polyline):
|
||||
points = [
|
||||
_convert_point_to_3d(*point)
|
||||
for point in polyline
|
||||
]
|
||||
return Polyline(*points)
|
||||
|
||||
def text_to_mobject(self, text):
|
||||
pass
|
||||
|
||||
def move_into_position(self):
|
||||
if self.should_center:
|
||||
self.center()
|
||||
@@ -128,338 +269,26 @@ 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):
|
||||
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 style in etree.findall(f"{{{namespace}}}style"):
|
||||
self.parse_css_style(style.text)
|
||||
|
||||
mobjects = self.get_mobjects_from(wrapper, dict())
|
||||
if self.unpack_groups:
|
||||
self.add(*mobjects)
|
||||
else:
|
||||
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:
|
||||
result = [VGroup(*result)]
|
||||
|
||||
return result
|
||||
|
||||
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, style):
|
||||
return VMobjectFromSVGPathstring(
|
||||
path_string,
|
||||
**self.path_string_config,
|
||||
**parse_style(style, self.generate_default_style()),
|
||||
)
|
||||
|
||||
def use_to_mobjects(self, use_element, local_style):
|
||||
# Remove initial "#" character
|
||||
ref = use_element.get(r"{http://www.w3.org/1999/xlink}href")[1:]
|
||||
if ref not in self.ref_to_element:
|
||||
log.warning(f"{ref} not recognized")
|
||||
return VGroup()
|
||||
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([
|
||||
char for char in attr
|
||||
if char in string.digits + "." + "-"
|
||||
])
|
||||
return float(stripped_attr)
|
||||
|
||||
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, style)
|
||||
|
||||
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,
|
||||
**parse_style(style, self.generate_default_style())
|
||||
).shift(x * RIGHT + y * DOWN)
|
||||
|
||||
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")
|
||||
)
|
||||
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, style):
|
||||
stroke_width = rect_element.get("stroke-width", "")
|
||||
corner_radius = rect_element.get("rx", "")
|
||||
|
||||
if stroke_width in ["", "none", "0"]:
|
||||
stroke_width = 0
|
||||
|
||||
if corner_radius in ["", "0", "none"]:
|
||||
corner_radius = 0
|
||||
|
||||
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.get("width", "")
|
||||
),
|
||||
height=self.attribute_to_float(
|
||||
rect_element.get("height", "")
|
||||
),
|
||||
**parsed_style,
|
||||
)
|
||||
else:
|
||||
mob = RoundedRectangle(
|
||||
width=self.attribute_to_float(
|
||||
rect_element.get("width", "")
|
||||
),
|
||||
height=self.attribute_to_float(
|
||||
rect_element.get("height", "")
|
||||
),
|
||||
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):
|
||||
x, y = (
|
||||
self.attribute_to_float(element.get(key, "0.0"))
|
||||
for key in ("x", "y")
|
||||
)
|
||||
mobject.shift(x * RIGHT + y * DOWN)
|
||||
|
||||
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]
|
||||
|
||||
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)]
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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 = []
|
||||
for i in input_list:
|
||||
if isinstance(i, list):
|
||||
output_list.extend(self.flatten(i))
|
||||
else:
|
||||
output_list.append(i)
|
||||
return output_list
|
||||
|
||||
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.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, 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):
|
||||
class VMobjectFromSVGPath(VMobject):
|
||||
CONFIG = {
|
||||
"long_lines": False,
|
||||
"should_subdivide_sharp_curves": False,
|
||||
"should_remove_null_curves": False,
|
||||
}
|
||||
|
||||
def __init__(self, path_string, **kwargs):
|
||||
self.path_string = path_string
|
||||
def __init__(self, path_obj, **kwargs):
|
||||
# Get rid of arcs
|
||||
path_obj.approximate_arcs_with_quads()
|
||||
self.path_obj = path_obj
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def init_points(self):
|
||||
# After a given svg_path has been converted into points, the result
|
||||
# will be saved to a file so that future calls for the same path
|
||||
# don't need to retrace the same computation.
|
||||
hasher = hashlib.sha256(self.path_string.encode())
|
||||
path_string = self.path_obj.d()
|
||||
hasher = hashlib.sha256(path_string.encode())
|
||||
path_hash = hasher.hexdigest()[:16]
|
||||
points_filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}_points.npy")
|
||||
tris_filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}_tris.npy")
|
||||
@@ -476,239 +305,23 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||
if self.should_remove_null_curves:
|
||||
# Get rid of any null curves
|
||||
self.set_points(self.get_points_without_null_curves())
|
||||
# SVG treats y-coordinate differently
|
||||
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())
|
||||
all_commands += [c.lower() for c in all_commands]
|
||||
pattern = "[{}]".format("".join(all_commands))
|
||||
return zip(
|
||||
re.findall(pattern, self.path_string),
|
||||
re.split(pattern, self.path_string)[1:]
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
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))
|
||||
|
||||
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]
|
||||
|
||||
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()]
|
||||
|
||||
def get_command_to_function_map(self):
|
||||
"""
|
||||
Associates svg command to VMobject function, and
|
||||
the types of arguments it takes in
|
||||
"""
|
||||
return {
|
||||
"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, ""),
|
||||
segment_class_to_func_map = {
|
||||
se.Move: (self.start_new_path, ("end",)),
|
||||
se.Close: (self.close_path, ()),
|
||||
se.Line: (self.add_line_to, ("end",)),
|
||||
se.QuadraticBezier: (self.add_quadratic_bezier_curve_to, ("control", "end")),
|
||||
se.CubicBezier: (self.add_cubic_bezier_curve_to, ("control1", "control2", "end"))
|
||||
}
|
||||
|
||||
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
|
||||
for segment in self.path_obj:
|
||||
segment_class = segment.__class__
|
||||
func, attr_names = segment_class_to_func_map[segment_class]
|
||||
points = [
|
||||
_convert_point_to_3d(*segment.__getattribute__(attr_name))
|
||||
for attr_name in attr_names
|
||||
]
|
||||
func(*points)
|
||||
|
||||
@@ -5,7 +5,6 @@ import re
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.geometry import Line
|
||||
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.config_ops import digest_config
|
||||
from manimlib.utils.tex_file_writing import tex_to_svg_file
|
||||
@@ -16,51 +15,51 @@ from manimlib.utils.tex_file_writing import display_during_execution
|
||||
SCALE_FACTOR_PER_FONT_POINT = 0.001
|
||||
|
||||
|
||||
tex_string_with_color_to_mob_map = {}
|
||||
|
||||
|
||||
class SingleStringTex(VMobject):
|
||||
class SingleStringTex(SVGMobject):
|
||||
CONFIG = {
|
||||
"height": None,
|
||||
"fill_opacity": 1.0,
|
||||
"stroke_width": 0,
|
||||
"should_center": True,
|
||||
"svg_default": {
|
||||
"color": WHITE,
|
||||
},
|
||||
"path_string_config": {
|
||||
"should_subdivide_sharp_curves": True,
|
||||
"should_remove_null_curves": True,
|
||||
},
|
||||
"font_size": 48,
|
||||
"height": None,
|
||||
"organize_left_to_right": False,
|
||||
"alignment": "\\centering",
|
||||
"math_mode": True,
|
||||
"organize_left_to_right": False,
|
||||
}
|
||||
|
||||
def __init__(self, tex_string, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
assert(isinstance(tex_string, str))
|
||||
assert isinstance(tex_string, str)
|
||||
self.tex_string = tex_string
|
||||
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_with_color_to_mob_map[(self.color, tex_string)] = svg_mob
|
||||
self.add(*(
|
||||
sm.copy()
|
||||
for sm in tex_string_with_color_to_mob_map[(self.color, tex_string)]
|
||||
))
|
||||
self.init_colors(override=False)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if self.height is None:
|
||||
self.scale(SCALE_FACTOR_PER_FONT_POINT * self.font_size)
|
||||
if self.organize_left_to_right:
|
||||
self.organize_submobjects_left_to_right()
|
||||
|
||||
@property
|
||||
def hash_seed(self):
|
||||
return (
|
||||
self.__class__.__name__,
|
||||
self.svg_default,
|
||||
self.path_string_config,
|
||||
self.tex_string,
|
||||
self.alignment,
|
||||
self.math_mode
|
||||
)
|
||||
|
||||
def get_file_path(self):
|
||||
full_tex = self.get_tex_file_body(self.tex_string)
|
||||
with display_during_execution(f"Writing \"{self.tex_string}\""):
|
||||
file_path = tex_to_svg_file(full_tex)
|
||||
return file_path
|
||||
|
||||
def get_tex_file_body(self, tex_string):
|
||||
new_tex = self.get_modified_expression(tex_string)
|
||||
if self.math_mode:
|
||||
@@ -134,7 +133,11 @@ class SingleStringTex(VMobject):
|
||||
Makes Tex resiliant to unmatched braces
|
||||
"""
|
||||
num_unclosed_brackets = 0
|
||||
for char in tex:
|
||||
for i in range(len(tex)):
|
||||
if i > 0 and tex[i - 1] == "\\":
|
||||
# So as to not count '\{' type expressions
|
||||
continue
|
||||
char = tex[i]
|
||||
if char == "{":
|
||||
num_unclosed_brackets += 1
|
||||
elif char == "}":
|
||||
|
||||
@@ -85,9 +85,6 @@ class Text(SVGMobject):
|
||||
if self.height is None:
|
||||
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:
|
||||
content = fpr.read()
|
||||
|
||||
@@ -21,6 +21,8 @@ class PMobject(Mobject):
|
||||
return self
|
||||
|
||||
def set_points(self, points):
|
||||
if len(points) == 0:
|
||||
points = np.zeros((0, 3))
|
||||
super().set_points(points)
|
||||
self.resize_points(len(points))
|
||||
return self
|
||||
@@ -34,14 +36,18 @@ class PMobject(Mobject):
|
||||
if color is not None:
|
||||
if opacity is None:
|
||||
opacity = self.data["rgbas"][-1, 3]
|
||||
new_rgbas = np.repeat(
|
||||
rgbas = np.repeat(
|
||||
[color_to_rgba(color, opacity)],
|
||||
len(points),
|
||||
axis=0
|
||||
)
|
||||
elif rgbas is not None:
|
||||
new_rgbas = rgbas
|
||||
self.data["rgbas"][-len(new_rgbas):] = new_rgbas
|
||||
if rgbas is not None:
|
||||
self.data["rgbas"][-len(rgbas):] = rgbas
|
||||
return self
|
||||
|
||||
def add_point(self, point, rgba=None, color=None, opacity=None):
|
||||
rgbas = None if rgba is None else [rgba]
|
||||
self.add_points([point], rgbas, color, opacity)
|
||||
return self
|
||||
|
||||
def set_color_by_gradient(self, *colors):
|
||||
|
||||
@@ -260,7 +260,7 @@ class TexturedSurface(Surface):
|
||||
super().init_uniforms()
|
||||
self.uniforms["num_textures"] = self.num_textures
|
||||
|
||||
def init_colors(self, override=True):
|
||||
def init_colors(self):
|
||||
self.data["opacity"] = np.array([self.uv_surface.data["rgbas"][:, 3]])
|
||||
|
||||
def set_opacity(self, opacity, recurse=True):
|
||||
|
||||
@@ -90,7 +90,7 @@ class VMobject(Mobject):
|
||||
})
|
||||
|
||||
# Colors
|
||||
def init_colors(self, override=True):
|
||||
def init_colors(self):
|
||||
self.set_fill(
|
||||
color=self.fill_color or self.color,
|
||||
opacity=self.fill_opacity,
|
||||
@@ -103,9 +103,6 @@ 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):
|
||||
|
||||
@@ -277,7 +277,6 @@ class DiscreteGraphScene(Scene):
|
||||
def trace_cycle(self, cycle=None, color="yellow", run_time=2.0):
|
||||
if cycle is None:
|
||||
cycle = self.graph.region_cycles[0]
|
||||
time_per_edge = run_time / len(cycle)
|
||||
next_in_cycle = it.cycle(cycle)
|
||||
next(next_in_cycle) # jump one ahead
|
||||
self.traced_cycle = Mobject(*[
|
||||
|
||||
@@ -36,6 +36,7 @@ class Scene(object):
|
||||
"end_at_animation_number": None,
|
||||
"leave_progress_bars": False,
|
||||
"preview": True,
|
||||
"presenter_mode": False,
|
||||
"linger_after_completion": True,
|
||||
}
|
||||
|
||||
@@ -62,6 +63,7 @@ class Scene(object):
|
||||
# Items associated with interaction
|
||||
self.mouse_point = Point()
|
||||
self.mouse_drag_point = Point()
|
||||
self.hold_on_wait = self.presenter_mode
|
||||
|
||||
# Much nicer to work with deterministic scenes
|
||||
if self.random_seed is not None:
|
||||
@@ -114,7 +116,7 @@ class Scene(object):
|
||||
if self.quit_interaction:
|
||||
self.unlock_mobject_data()
|
||||
|
||||
def embed(self):
|
||||
def embed(self, close_scene_on_exit=True):
|
||||
if not self.preview:
|
||||
# If the scene is just being
|
||||
# written, ignore embed calls
|
||||
@@ -139,8 +141,9 @@ class Scene(object):
|
||||
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()
|
||||
# End scene when exiting an embed
|
||||
if close_scene_on_exit:
|
||||
raise EndSceneEarlyException()
|
||||
|
||||
def __str__(self):
|
||||
return self.__class__.__name__
|
||||
@@ -432,6 +435,11 @@ class Scene(object):
|
||||
def unlock_mobject_data(self):
|
||||
self.camera.release_static_mobjects()
|
||||
|
||||
def refresh_locked_data(self):
|
||||
self.unlock_mobject_data()
|
||||
self.lock_static_mobject_data()
|
||||
return self
|
||||
|
||||
def begin_animations(self, animations):
|
||||
for animation in animations:
|
||||
animation.begin()
|
||||
@@ -477,19 +485,30 @@ class Scene(object):
|
||||
self.unlock_mobject_data()
|
||||
|
||||
@handle_play_like_call
|
||||
def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None):
|
||||
def wait(self,
|
||||
duration=DEFAULT_WAIT_TIME,
|
||||
stop_condition=None,
|
||||
note=None,
|
||||
ignore_presenter_mode=False):
|
||||
if note:
|
||||
log.info(note)
|
||||
self.update_mobjects(dt=0) # Any problems with this?
|
||||
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
|
||||
if self.presenter_mode and not self.skip_animations and not ignore_presenter_mode:
|
||||
while self.hold_on_wait:
|
||||
self.update_frame(dt=1 / self.camera.frame_rate)
|
||||
self.hold_on_wait = True
|
||||
else:
|
||||
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
|
||||
|
||||
@@ -610,6 +629,10 @@ class Scene(object):
|
||||
self.camera.frame.to_default_state()
|
||||
elif char == "q":
|
||||
self.quit_interaction = True
|
||||
elif char == " " or symbol == 65363: # Space or right arrow
|
||||
self.hold_on_wait = False
|
||||
elif char == "e" and modifiers == 3: # ctrl + shift + e
|
||||
self.embed(close_scene_on_exit=False)
|
||||
|
||||
def on_resize(self, width: int, height: int):
|
||||
self.camera.reset_pixel_shape(width, height)
|
||||
|
||||
@@ -287,9 +287,6 @@ class LinearTransformationScene(VectorScene):
|
||||
},
|
||||
"background_plane_kwargs": {
|
||||
"color": GREY,
|
||||
"axis_config": {
|
||||
"stroke_color": GREY_B,
|
||||
},
|
||||
"axis_config": {
|
||||
"color": GREY,
|
||||
},
|
||||
|
||||
@@ -14,10 +14,10 @@ def bezier(points):
|
||||
n = len(points) - 1
|
||||
|
||||
def result(t):
|
||||
return sum([
|
||||
return sum(
|
||||
((1 - t)**(n - k)) * (t**k) * choose(n, k) * point
|
||||
for k, point in enumerate(points)
|
||||
])
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -139,3 +139,14 @@ def remove_nones(sequence):
|
||||
|
||||
def concatenate_lists(*list_of_lists):
|
||||
return [item for l in list_of_lists for item in l]
|
||||
|
||||
|
||||
def hash_obj(obj):
|
||||
if isinstance(obj, dict):
|
||||
new_obj = {k: hash_obj(v) for k, v in obj.items()}
|
||||
return hash(tuple(frozenset(sorted(new_obj.items()))))
|
||||
|
||||
if isinstance(obj, (set, tuple, list)):
|
||||
return hash(tuple(hash_obj(e) for e in obj))
|
||||
|
||||
return hash(obj)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import inspect
|
||||
import numpy as np
|
||||
from scipy import special
|
||||
import math
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
@@ -10,7 +10,11 @@ def sigmoid(x):
|
||||
|
||||
@lru_cache(maxsize=10)
|
||||
def choose(n, k):
|
||||
return special.comb(n, k, exact=True)
|
||||
return math.comb(n, k)
|
||||
|
||||
|
||||
def gen_choose(n, r):
|
||||
return np.prod(np.arange(n, n - r, -1)) / math.factorial(r)
|
||||
|
||||
|
||||
def get_num_args(function):
|
||||
|
||||
@@ -126,6 +126,9 @@ def dvi_to_svg(dvi_file, regen_if_exists=False):
|
||||
def display_during_execution(message):
|
||||
# Only show top line
|
||||
to_print = message.split("\n")[0]
|
||||
max_characters = os.get_terminal_size().columns - 1
|
||||
if len(to_print) > max_characters:
|
||||
to_print = to_print[:max_characters - 3] + "..."
|
||||
try:
|
||||
print(to_print, end="\r")
|
||||
yield
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
argparse
|
||||
colour
|
||||
numpy
|
||||
Pillow
|
||||
@@ -15,9 +14,9 @@ pygments
|
||||
pyyaml
|
||||
rich
|
||||
screeninfo
|
||||
pyreadline; sys_platform == 'win32'
|
||||
validators
|
||||
ipython
|
||||
PyOpenGL
|
||||
manimpango>=0.2.0,<0.4.0
|
||||
cssselect2
|
||||
isosurfaces
|
||||
svgelements
|
||||
|
||||
24
setup.cfg
24
setup.cfg
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = manimgl
|
||||
version = 1.4.0
|
||||
version = 1.5.0
|
||||
author = Grant Sanderson
|
||||
author_email= grant@3blue1brown.com
|
||||
description = Animation engine for explanatory math videos
|
||||
@@ -12,12 +12,24 @@ project_urls =
|
||||
Documentation = https://3b1b.github.io/manim/
|
||||
Source Code = https://github.com/3b1b/manim
|
||||
license = MIT
|
||||
classifiers =
|
||||
Development Status :: 4 - Beta
|
||||
License :: OSI Approved :: MIT License
|
||||
Topic :: Scientific/Engineering
|
||||
Topic :: Multimedia :: Video
|
||||
Topic :: Multimedia :: Graphics
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Natural Language :: English
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
include_package_data=True
|
||||
install_requires =
|
||||
argparse
|
||||
include_package_data = True
|
||||
install_requires =
|
||||
colour
|
||||
numpy
|
||||
Pillow
|
||||
@@ -34,12 +46,12 @@ install_requires =
|
||||
pyyaml
|
||||
rich
|
||||
screeninfo
|
||||
pyreadline; sys_platform == 'win32'
|
||||
validators
|
||||
ipython
|
||||
PyOpenGL
|
||||
manimpango>=0.2.0,<0.4.0
|
||||
cssselect2
|
||||
isosurfaces
|
||||
svgelements
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
|
||||
Reference in New Issue
Block a user