mirror of
https://github.com/3b1b/manim.git
synced 2025-11-14 12:07:45 +00:00
Merge branch 'master' into some1-video-changes
This commit is contained in:
commit
8624168ed9
36 changed files with 359 additions and 480 deletions
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
|
|
@ -19,12 +19,12 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools wheel twine
|
||||
pip install setuptools wheel twine build
|
||||
|
||||
- name: Build and publish
|
||||
env:
|
||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
run: |
|
||||
python setup.py sdist bdist_wheel
|
||||
python -m build
|
||||
twine upload dist/*
|
||||
2
MANIFEST.in
Normal file
2
MANIFEST.in
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
graft manimlib
|
||||
recursive-exclude manimlib *.pyc *.DS_Store
|
||||
12
README.md
12
README.md
|
|
@ -7,16 +7,18 @@
|
|||
[](https://pypi.org/project/manimgl/)
|
||||
[](http://choosealicense.com/licenses/mit/)
|
||||
[](https://www.reddit.com/r/manim/)
|
||||
[](https://discord.gg/mMRrZQW)
|
||||
[](https://discord.com/invite/bYCyhM9Kz2)
|
||||
[](https://3b1b.github.io/manim/)
|
||||
|
||||
Manim is an engine for precise programmatic animations, designed for creating explanatory math videos.
|
||||
|
||||
Note, there are two versions of manim. This repository began as a personal project by the author of [3Blue1Brown](https://www.3blue1brown.com/) for the purpose of animating those videos, with video-specific code available [here](https://github.com/3b1b/videos). In 2020 a group of developers forked it into what is now the [community edition](https://github.com/ManimCommunity/manim/), with a goal of being more stable, better tested, quicker to respond to community contributions, and all around friendlier to get started with. You can engage with that community by joining the discord.
|
||||
|
||||
Since the fork, this version has evolved to work on top of OpenGL, and allows real-time rendering to an interactive window before scenes are finalized and written to a file.
|
||||
Note, there are two versions of manim. This repository began as a personal project by the author of [3Blue1Brown](https://www.3blue1brown.com/) for the purpose of animating those videos, with video-specific code available [here](https://github.com/3b1b/videos). In 2020 a group of developers forked it into what is now the [community edition](https://github.com/ManimCommunity/manim/), with a goal of being more stable, better tested, quicker to respond to community contributions, and all around friendlier to get started with. See [this page](https://docs.manim.community/en/stable/installation/versions.html?highlight=OpenGL#which-version-to-use) for more details.
|
||||
|
||||
## Installation
|
||||
> **WARNING:** These instructions are for ManimGL _only_. Trying to use these instructions to install [ManimCommunity/manim](https://github.com/ManimCommunity/manim) or instructions there to install this version will cause problems. You should first decide which version you wish to install, then only follow the instructions for your desired version.
|
||||
>
|
||||
> **Note**: To install manim directly through pip, please pay attention to the name of the installed package. This repository is ManimGL of 3b1b. The package name is `manimgl` instead of `manim` or `manimlib`. Please use `pip install manimgl` to install the version in this repository.
|
||||
|
||||
Manim runs on Python 3.6 or higher (Python 3.8 is recommended).
|
||||
|
||||
System requirements are [FFmpeg](https://ffmpeg.org/), [OpenGL](https://www.opengl.org/) and [LaTeX](https://www.latex-project.org) (optional, if you want to use LaTeX).
|
||||
|
|
@ -102,7 +104,7 @@ Take a look at custom_config.yml for further configuration. To add your customi
|
|||
Look through the [example scenes](https://3b1b.github.io/manim/getting_started/example_scenes.html) to get a sense of how it is used, and feel free to look through the code behind [3blue1brown videos](https://github.com/3b1b/videos) for a much larger set of example. Note, however, that developments are often made to the library without considering backwards compatibility with those old videos. To run an old project with a guarantee that it will work, you will have to go back to the commit which completed that project.
|
||||
|
||||
### Documentation
|
||||
Documentation is in progress at [3b1b.github.io/manim](https://3b1b.github.io/manim/). And there is also a Chinese version maintained by **@manim-kindergarten**: [manim.ml](https://manim.ml/) (in Chinese).
|
||||
Documentation is in progress at [3b1b.github.io/manim](https://3b1b.github.io/manim/). And there is also a Chinese version maintained by [**@manim-kindergarten**](https://manim.org.cn): [docs.manim.org.cn](https://docs.manim.org.cn/) (in Chinese).
|
||||
|
||||
[manim-kindergarten](https://github.com/manim-kindergarten/) wrote and collected some useful extra classes and some codes of videos in [manim_sandbox repo](https://github.com/manim-kindergarten/manim_sandbox).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,293 +0,0 @@
|
|||
p.color-text {
|
||||
font-size: inherit;
|
||||
font-family: var(--font-stack--monospace);
|
||||
margin-top: 25px;
|
||||
color: WHITE;
|
||||
}
|
||||
|
||||
p.color-text-small {
|
||||
font-size: small;
|
||||
font-family: var(--font-stack--monospace);
|
||||
margin-top: 28px;
|
||||
color: WHITE;
|
||||
}
|
||||
|
||||
.colors {
|
||||
float: left;
|
||||
padding: 10px;
|
||||
border: 10px;
|
||||
margin: 0;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.BLUE_A {
|
||||
background: #C7E9F1;
|
||||
color:#C7E9F1;
|
||||
}
|
||||
|
||||
.BLUE_B {
|
||||
background: #9CDCEB;
|
||||
color:#9CDCEB;
|
||||
}
|
||||
|
||||
.BLUE_C {
|
||||
background: #58C4DD;
|
||||
color:#58C4DD;
|
||||
}
|
||||
|
||||
.BLUE_D {
|
||||
background: #29ABCA;
|
||||
color:#29ABCA;
|
||||
}
|
||||
|
||||
.BLUE_E {
|
||||
background: #1C758A;
|
||||
color:#1C758A;
|
||||
}
|
||||
|
||||
.TEAL_A {
|
||||
background: #ACEAD7;
|
||||
color:#ACEAD7 ;
|
||||
}
|
||||
|
||||
.TEAL_B {
|
||||
background: #76DDC0;
|
||||
color: #76DDC0;
|
||||
}
|
||||
|
||||
.TEAL_C {
|
||||
background: #5CD0B3;
|
||||
color: #5CD0B3;
|
||||
}
|
||||
|
||||
.TEAL_D {
|
||||
background: #55C1A7;
|
||||
color: #55C1A7;
|
||||
}
|
||||
|
||||
.TEAL_E {
|
||||
background: #49A88F;
|
||||
color: #49A88F;
|
||||
}
|
||||
|
||||
.GREEN_A {
|
||||
background: #C9E2AE;
|
||||
color: #C9E2AE;
|
||||
}
|
||||
|
||||
.GREEN_B {
|
||||
background: #A6CF8C;
|
||||
color: #A6CF8C;
|
||||
}
|
||||
|
||||
.GREEN_C {
|
||||
background: #83C167;
|
||||
color: #83C167;
|
||||
}
|
||||
|
||||
.GREEN_D {
|
||||
background: #77B05D;
|
||||
color: #77B05D;
|
||||
}
|
||||
|
||||
.GREEN_E {
|
||||
background: #699C52;
|
||||
color: #699C52;
|
||||
}
|
||||
|
||||
.YELLOW_A {
|
||||
background: #FFF1B6;
|
||||
color: #FFF1B6;
|
||||
}
|
||||
|
||||
.YELLOW_B {
|
||||
background: #FFEA94;
|
||||
color:#FFEA94 ;
|
||||
}
|
||||
|
||||
.YELLOW_C {
|
||||
background: #FFFF00;
|
||||
color: #FFFF00;
|
||||
}
|
||||
|
||||
.YELLOW_D {
|
||||
background: #F4D345;
|
||||
color: #F4D345;
|
||||
}
|
||||
|
||||
.YELLOW_E {
|
||||
background: #E8C11C;
|
||||
color: #E8C11C;
|
||||
}
|
||||
|
||||
.GOLD_A {
|
||||
background: #F7C797;
|
||||
color:#F7C797;
|
||||
}
|
||||
|
||||
.GOLD_B {
|
||||
background: #F9B775;
|
||||
color:#F9B775;
|
||||
}
|
||||
|
||||
.GOLD_C {
|
||||
background: #F0AC5F;
|
||||
color:#F0AC5F;
|
||||
}
|
||||
|
||||
.GOLD_D {
|
||||
background: #E1A158;
|
||||
color:#E1A158;
|
||||
}
|
||||
|
||||
.GOLD_E {
|
||||
background: #C78D46;
|
||||
color:#C78D46;
|
||||
}
|
||||
|
||||
.RED_A {
|
||||
background: #F7A1A3;
|
||||
color:#F7A1A3;
|
||||
}
|
||||
|
||||
.RED_B {
|
||||
background: #FF8080;
|
||||
color:#FF8080;
|
||||
}
|
||||
|
||||
.RED_C {
|
||||
background: #FC6255;
|
||||
color:#FC6255;
|
||||
}
|
||||
|
||||
.RED_D {
|
||||
background: #E65A4C;
|
||||
color:#E65A4C;
|
||||
}
|
||||
|
||||
.RED_E {
|
||||
background: #CF5044;
|
||||
color:#CF5044;
|
||||
}
|
||||
|
||||
.MAROON_A {
|
||||
background: #ECABC1;
|
||||
color: #ECABC1;
|
||||
}
|
||||
|
||||
.MAROON_B {
|
||||
background: #EC92AB;
|
||||
color: #EC92AB;
|
||||
}
|
||||
|
||||
.MAROON_C {
|
||||
background: #C55F73;
|
||||
color: #C55F73;
|
||||
}
|
||||
|
||||
.MAROON_D {
|
||||
background: #A24D61;
|
||||
color: #A24D61;
|
||||
}
|
||||
|
||||
.MAROON_E {
|
||||
background: #94424F;
|
||||
color: #94424F;
|
||||
}
|
||||
|
||||
.PURPLE_A {
|
||||
background: #CAA3E8;
|
||||
color: #CAA3E8;
|
||||
}
|
||||
|
||||
.PURPLE_B {
|
||||
background: #B189C6;
|
||||
color: #B189C6;
|
||||
}
|
||||
|
||||
.PURPLE_C {
|
||||
background: #9A72AC;
|
||||
color: #9A72AC;
|
||||
}
|
||||
|
||||
.PURPLE_D {
|
||||
background: #715582;
|
||||
color: #715582;
|
||||
}
|
||||
|
||||
.PURPLE_E {
|
||||
background: #644172;
|
||||
color: #644172;
|
||||
}
|
||||
|
||||
.GREY_A {
|
||||
background: #DDDDDD;
|
||||
color: #DDDDDD;
|
||||
}
|
||||
|
||||
.GREY_B {
|
||||
background: #BBBBBB;
|
||||
color: #BBBBBB;
|
||||
}
|
||||
|
||||
.GREY_C {
|
||||
background: #888888;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.GREY_D {
|
||||
background: #444444;
|
||||
color: #444444;
|
||||
}
|
||||
|
||||
.GREY_E {
|
||||
background: #222222;
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
.WHITE {
|
||||
background: #FFFFFF;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.BLACK {
|
||||
background: #000000;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.GREY_BROWN {
|
||||
background: #736357;
|
||||
color: #736357;
|
||||
}
|
||||
|
||||
.DARK_BROWN {
|
||||
background: #8B4513;
|
||||
color: #8B4513;
|
||||
}
|
||||
|
||||
.LIGHT_BROWN {
|
||||
background: #CD853F;
|
||||
color: #CD853F;
|
||||
}
|
||||
|
||||
.PINK {
|
||||
background: #D147BD;
|
||||
color: #D147BD;
|
||||
}
|
||||
|
||||
.LIGHT_PINK {
|
||||
background: #DC75CD;
|
||||
color: #DC75CD;
|
||||
}
|
||||
|
||||
.GREEN_SCREEN {
|
||||
background: #00FF00;
|
||||
color: #00FF00;
|
||||
}
|
||||
|
||||
.ORANGE {
|
||||
background: #FF862F;
|
||||
color: #FF862F;
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
p {
|
||||
font-size: initial;
|
||||
}
|
||||
|
||||
span.caption-text {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
span.pre {
|
||||
font-size: initial;
|
||||
}
|
||||
|
||||
.highlight-python.notranslate {
|
||||
margin-top: 0em;
|
||||
}
|
||||
|
||||
.manim-video {
|
||||
width: 99.9%;
|
||||
padding: 8px 0;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.manim-example {
|
||||
background-color: #333333;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 2px 2px 4px #ddd;
|
||||
}
|
||||
|
||||
.manim-example .manim-video {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.manim-example img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h5.example-header {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding: 8px 16px;
|
||||
color: white;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
text-transform: none;
|
||||
margin-top: -0.4em;
|
||||
margin-bottom: -0.2em;
|
||||
}
|
||||
|
||||
.manim-example .highlight {
|
||||
background-color: #fafafa;
|
||||
border: 2px solid #333333;
|
||||
padding: 8px 8px 10px 8px;
|
||||
font-size: large;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.manim-example .highlight pre {
|
||||
background-color: inherit;
|
||||
border-left: none;
|
||||
margin: 0;
|
||||
padding: 0 6px 0 6px;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 603 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
|
@ -31,7 +31,10 @@ master_doc = 'index'
|
|||
pygments_style = 'default'
|
||||
|
||||
html_static_path = ["_static"]
|
||||
html_css_files = ["custom.css", "colors.css"]
|
||||
html_css_files = [
|
||||
"https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/custom.css",
|
||||
"https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/colors.css"
|
||||
]
|
||||
html_theme = 'furo' # pip install furo==2020.10.5b9
|
||||
html_favicon = '_static/icon.png'
|
||||
html_logo = '../../logo/transparent_graph.png'
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ InteractiveDevlopment
|
|||
---------------------
|
||||
|
||||
.. manim-example:: InteractiveDevelopment
|
||||
:media: ../_static/example_scenes/InteractiveDevelopment.mp4
|
||||
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/InteractiveDevelopment.mp4
|
||||
|
||||
from manimlib import *
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ AnimatingMethods
|
|||
----------------
|
||||
|
||||
.. manim-example:: AnimatingMethods
|
||||
:media: ../_static/example_scenes/AnimatingMethods.mp4
|
||||
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/AnimatingMethods.mp4
|
||||
|
||||
class AnimatingMethods(Scene):
|
||||
def construct(self):
|
||||
|
|
@ -124,7 +124,7 @@ TextExample
|
|||
-----------
|
||||
|
||||
.. manim-example:: TextExample
|
||||
:media: ../_static/example_scenes/TextExample.mp4
|
||||
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/TextExample.mp4
|
||||
|
||||
class TextExample(Scene):
|
||||
def construct(self):
|
||||
|
|
@ -178,7 +178,7 @@ TexTransformExample
|
|||
-------------------
|
||||
|
||||
.. manim-example:: TexTransformExample
|
||||
:media: ../_static/example_scenes/TexTransformExample.mp4
|
||||
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/TexTransformExample.mp4
|
||||
|
||||
class TexTransformExample(Scene):
|
||||
def construct(self):
|
||||
|
|
@ -303,7 +303,7 @@ UpdatersExample
|
|||
---------------
|
||||
|
||||
.. manim-example:: UpdatersExample
|
||||
:media: ../_static/example_scenes/UpdatersExample.mp4
|
||||
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/UpdatersExample.mp4
|
||||
|
||||
class UpdatersExample(Scene):
|
||||
def construct(self):
|
||||
|
|
@ -388,7 +388,7 @@ CoordinateSystemExample
|
|||
-----------------------
|
||||
|
||||
.. manim-example:: CoordinateSystemExample
|
||||
:media: ../_static/example_scenes/CoordinateSystemExample.mp4
|
||||
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/CoordinateSystemExample.mp4
|
||||
|
||||
class CoordinateSystemExample(Scene):
|
||||
def construct(self):
|
||||
|
|
@ -472,7 +472,7 @@ GraphExample
|
|||
------------
|
||||
|
||||
.. manim-example:: GraphExample
|
||||
:media: ../_static/example_scenes/GraphExample.mp4
|
||||
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/GraphExample.mp4
|
||||
|
||||
class GraphExample(Scene):
|
||||
def construct(self):
|
||||
|
|
@ -558,7 +558,7 @@ SurfaceExample
|
|||
--------------
|
||||
|
||||
.. manim-example:: SurfaceExample
|
||||
:media: ../_static/example_scenes/SurfaceExample.mp4
|
||||
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/SurfaceExample.mp4
|
||||
|
||||
class SurfaceExample(Scene):
|
||||
CONFIG = {
|
||||
|
|
@ -659,7 +659,7 @@ OpeningManimExample
|
|||
-------------------
|
||||
|
||||
.. manim-example:: OpeningManimExample
|
||||
:media: ../_static/example_scenes/OpeningManimExample.mp4
|
||||
:media: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/example_scenes/OpeningManimExample.mp4
|
||||
|
||||
|
||||
class OpeningManimExample(Scene):
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ At this time, no window will pop up. When the program is finished, this rendered
|
|||
image will be automatically opened (saved in the subdirectory ``images/`` of the same
|
||||
level directory of ``start.py`` by default):
|
||||
|
||||
.. image:: ../_static/quickstart/SquareToCircle.png
|
||||
.. image:: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/quickstart/SquareToCircle.png
|
||||
:align: center
|
||||
|
||||
Make an image
|
||||
|
|
@ -162,7 +162,7 @@ opened after the operation is over:
|
|||
|
||||
.. raw:: html
|
||||
|
||||
<video class="manim-video" controls loop autoplay src="../_static/quickstart/SquareToCircle.mp4"></video>
|
||||
<video class="manim-video" controls loop autoplay src="https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/quickstart/SquareToCircle.mp4"></video>
|
||||
|
||||
Let's take a look at the code this time. The first 7 lines are the same as the previous
|
||||
ones, and the 8th line is similar to the 5th line, which creates an instance of the
|
||||
|
|
@ -237,7 +237,7 @@ You will get an animation similar to the following:
|
|||
|
||||
.. raw:: html
|
||||
|
||||
<video class="manim-video" controls loop autoplay src="../_static/quickstart/SquareToCircleEmbed.mp4"></video>
|
||||
<video class="manim-video" controls loop autoplay src="https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/quickstart/SquareToCircleEmbed.mp4"></video>
|
||||
|
||||
If you want to enter the interactive mode directly, you don't have to write an
|
||||
empty scene containing only ``self.embed()``, you can directly run the following command
|
||||
|
|
|
|||
|
|
@ -120,9 +120,9 @@ Inheritance structure of manim's classes
|
|||
is a pdf showed inheritance structure of manim's classes, large,
|
||||
but basically all classes have included:
|
||||
|
||||
.. image:: ../_static/manim_shaders_structure.png
|
||||
.. image:: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/manim_shaders_structure.png
|
||||
|
||||
Manim execution process
|
||||
-----------------------
|
||||
|
||||
.. image:: ../_static/manim_shaders_process_en.png
|
||||
.. image:: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/manim_shaders_process_en.png
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
Manim's documentation
|
||||
=====================
|
||||
|
||||
.. image:: ../../logo/white_with_name.png
|
||||
.. image:: https://cdn.jsdelivr.net/gh/3b1b/manim@master/logo/white_with_name.png
|
||||
|
||||
Manim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos
|
||||
at `3Blue1Brown <https://www.3blue1brown.com/>`_.
|
||||
|
|
|
|||
|
|
@ -639,14 +639,14 @@ class ControlsExample(Scene):
|
|||
self.checkbox = Checkbox()
|
||||
self.color_picker = ColorSliders()
|
||||
self.panel = ControlPanel(
|
||||
Text("Text", size=0.5), self.textbox, Line(),
|
||||
Text("Show/Hide Text", size=0.5), self.checkbox, Line(),
|
||||
Text("Color of Text", size=0.5), self.color_picker
|
||||
Text("Text", font_size=24), self.textbox, Line(),
|
||||
Text("Show/Hide Text", font_size=24), self.checkbox, Line(),
|
||||
Text("Color of Text", font_size=24), self.color_picker
|
||||
)
|
||||
self.add(self.panel)
|
||||
|
||||
def construct(self):
|
||||
text = Text("", size=2)
|
||||
text = Text("text", font_size=96)
|
||||
|
||||
def text_updater(old_text):
|
||||
assert(isinstance(old_text, Text))
|
||||
|
|
|
|||
|
|
@ -439,7 +439,7 @@ class ControlPanel(Group):
|
|||
},
|
||||
"opener_text_kwargs": {
|
||||
"text": "Control Panel",
|
||||
"size": 0.4
|
||||
"font_size": 20
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,9 +57,7 @@ class Mobject(object):
|
|||
# Must match in attributes of vert shader
|
||||
"shader_dtype": [
|
||||
('point', np.float32, (3,)),
|
||||
],
|
||||
# Event listener
|
||||
"listen_to_events": False
|
||||
]
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
|
|
|||
|
|
@ -2,8 +2,12 @@ import copy
|
|||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import io
|
||||
import typing
|
||||
import warnings
|
||||
import xml.etree.ElementTree as ET
|
||||
import functools
|
||||
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -15,8 +19,7 @@ from manimlib.mobject.types.vectorized_mobject import VGroup
|
|||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.customization import get_customization
|
||||
from manimlib.utils.directories import get_downloads_dir, get_text_dir
|
||||
from manimpango import PangoUtils
|
||||
from manimpango import TextSetting
|
||||
from manimpango import PangoUtils, TextSetting, MarkupUtils
|
||||
|
||||
TEXT_MOB_SCALE_FACTOR = 0.0076
|
||||
DEFAULT_LINE_SPACING_SCALE = 0.3
|
||||
|
|
@ -80,11 +83,6 @@ class Text(SVGMobject):
|
|||
if self.height is None:
|
||||
self.scale(TEXT_MOB_SCALE_FACTOR)
|
||||
|
||||
# Just a temporary hack to get better triangulation
|
||||
# See pr #1552 for details
|
||||
for i in self.submobjects:
|
||||
i.insert_n_curves(len(i.get_points()))
|
||||
|
||||
def remove_empty_path(self, file_name):
|
||||
with open(file_name, 'r') as fpr:
|
||||
content = fpr.read()
|
||||
|
|
@ -243,6 +241,237 @@ class Text(SVGMobject):
|
|||
)
|
||||
|
||||
|
||||
class MarkupText(SVGMobject):
|
||||
CONFIG = {
|
||||
# Mobject
|
||||
"color": WHITE,
|
||||
"height": None,
|
||||
# Text
|
||||
"font": '',
|
||||
"font_size": 48,
|
||||
"lsh": None,
|
||||
"justify": False,
|
||||
"slant": NORMAL,
|
||||
"weight": NORMAL,
|
||||
"tab_width": 4,
|
||||
"gradient": None,
|
||||
"disable_ligatures": True,
|
||||
}
|
||||
def __init__(self, text, **config):
|
||||
digest_config(self, config)
|
||||
self.text = f'<span>{text}</span>'
|
||||
self.original_text = self.text
|
||||
self.text_for_parsing = self.text
|
||||
text_without_tabs = text
|
||||
if "\t" in text:
|
||||
text_without_tabs = text.replace("\t", " " * self.tab_width)
|
||||
try:
|
||||
colormap = self.extract_color_tags()
|
||||
gradientmap = self.extract_gradient_tags()
|
||||
except ET.ParseError:
|
||||
# let pango handle that error
|
||||
pass
|
||||
validate_error = MarkupUtils.validate(self.text)
|
||||
if validate_error:
|
||||
raise ValueError(validate_error)
|
||||
file_name = self.text2svg()
|
||||
PangoUtils.remove_last_M(file_name)
|
||||
super().__init__(
|
||||
file_name,
|
||||
**config,
|
||||
)
|
||||
self.chars = self.get_group_class()(*self.submobjects)
|
||||
self.text = text_without_tabs.replace(" ", "").replace("\n", "")
|
||||
if self.gradient:
|
||||
self.set_color_by_gradient(*self.gradient)
|
||||
for col in colormap:
|
||||
self.chars[
|
||||
col["start"]
|
||||
- col["start_offset"] : col["end"]
|
||||
- col["start_offset"]
|
||||
- col["end_offset"]
|
||||
].set_color(self._parse_color(col["color"]))
|
||||
for grad in gradientmap:
|
||||
self.chars[
|
||||
grad["start"]
|
||||
- grad["start_offset"] : grad["end"]
|
||||
- grad["start_offset"]
|
||||
- grad["end_offset"]
|
||||
].set_color_by_gradient(
|
||||
*(self._parse_color(grad["from"]), self._parse_color(grad["to"]))
|
||||
)
|
||||
# anti-aliasing
|
||||
if self.height is None:
|
||||
self.scale(TEXT_MOB_SCALE_FACTOR)
|
||||
def text2hash(self):
|
||||
"""Generates ``sha256`` hash for file name."""
|
||||
settings = (
|
||||
"MARKUPPANGO" + self.font + self.slant + self.weight + self.color
|
||||
) # to differentiate from classical Pango Text
|
||||
settings += str(self.lsh) + str(self.font_size)
|
||||
settings += str(self.disable_ligatures)
|
||||
settings += str(self.justify)
|
||||
id_str = self.text + settings
|
||||
hasher = hashlib.sha256()
|
||||
hasher.update(id_str.encode())
|
||||
return hasher.hexdigest()[:16]
|
||||
|
||||
def text2svg(self):
|
||||
"""Convert the text to SVG using Pango."""
|
||||
size = self.font_size
|
||||
dir_name = get_text_dir()
|
||||
disable_liga = self.disable_ligatures
|
||||
if not os.path.exists(dir_name):
|
||||
os.makedirs(dir_name)
|
||||
hash_name = self.text2hash()
|
||||
file_name = os.path.join(dir_name, hash_name) + ".svg"
|
||||
if os.path.exists(file_name):
|
||||
return file_name
|
||||
|
||||
extra_kwargs = {}
|
||||
extra_kwargs['justify'] = self.justify
|
||||
extra_kwargs['pango_width'] = DEFAULT_PIXEL_WIDTH - 100
|
||||
if self.lsh:
|
||||
extra_kwargs['line_spacing']=self.lsh
|
||||
return MarkupUtils.text2svg(
|
||||
f'<span foreground="{self.color}">{self.text}</span>',
|
||||
self.font,
|
||||
self.slant,
|
||||
self.weight,
|
||||
size,
|
||||
0, # empty parameter
|
||||
disable_liga,
|
||||
file_name,
|
||||
START_X,
|
||||
START_Y,
|
||||
DEFAULT_PIXEL_WIDTH, # width
|
||||
DEFAULT_PIXEL_HEIGHT, # height
|
||||
**extra_kwargs
|
||||
)
|
||||
|
||||
|
||||
def _parse_color(self, col):
|
||||
"""Parse color given in ``<color>`` or ``<gradient>`` tags."""
|
||||
if re.match("#[0-9a-f]{6}", col):
|
||||
return col
|
||||
else:
|
||||
return globals()[col.upper()] # this is hacky
|
||||
|
||||
@functools.lru_cache(10)
|
||||
def get_text_from_markup(self, element=None):
|
||||
if not element:
|
||||
element = ET.fromstring(self.text_for_parsing)
|
||||
final_text = ''
|
||||
for i in element.itertext():
|
||||
final_text += i
|
||||
return final_text
|
||||
|
||||
def extract_color_tags(self, text=None, colormap = None):
|
||||
"""Used to determine which parts (if any) of the string should be formatted
|
||||
with a custom color.
|
||||
Removes the ``<color>`` tag, as it is not part of Pango's markup and would cause an error.
|
||||
Note: Using the ``<color>`` tags is deprecated. As soon as the legacy syntax is gone, this function
|
||||
will be removed.
|
||||
"""
|
||||
if not text:
|
||||
text = self.text_for_parsing
|
||||
if not colormap:
|
||||
colormap = list()
|
||||
elements = ET.fromstring(text)
|
||||
text_from_markup = self.get_text_from_markup()
|
||||
final_xml = ET.fromstring(f'<span>{elements.text if elements.text else ""}</span>')
|
||||
def get_color_map(elements):
|
||||
for element in elements:
|
||||
if element.tag == 'color':
|
||||
element_text = self.get_text_from_markup(element)
|
||||
start = text_from_markup.find(element_text)
|
||||
end = start + len(element_text)
|
||||
offsets = element.get('offset').split(",") if element.get('offset') else [0]
|
||||
start_offset = int(offsets[0]) if offsets[0] else 0
|
||||
end_offset = int(offsets[1]) if len(offsets) == 2 and offsets[1] else 0
|
||||
colormap.append(
|
||||
{
|
||||
"start": start,
|
||||
"end": end,
|
||||
"color": element.get('col'),
|
||||
"start_offset": start_offset,
|
||||
"end_offset": end_offset,
|
||||
}
|
||||
)
|
||||
|
||||
_elements_list = list(element.iter())
|
||||
if len(_elements_list) <= 1:
|
||||
final_xml.append(ET.fromstring(f'<span>{element.text if element.text else ""}</span>'))
|
||||
else:
|
||||
final_xml.append(_elements_list[-1])
|
||||
else:
|
||||
if len(list(element.iter())) == 1:
|
||||
final_xml.append(element)
|
||||
else:
|
||||
get_color_map(element)
|
||||
get_color_map(elements)
|
||||
with io.BytesIO() as f:
|
||||
tree = ET.ElementTree()
|
||||
tree._setroot(final_xml)
|
||||
tree.write(f)
|
||||
self.text = f.getvalue().decode()
|
||||
self.text_for_parsing = self.text # gradients will use it
|
||||
return colormap
|
||||
|
||||
def extract_gradient_tags(self, text=None,gradientmap=None):
|
||||
"""Used to determine which parts (if any) of the string should be formatted
|
||||
with a gradient.
|
||||
Removes the ``<gradient>`` tag, as it is not part of Pango's markup and would cause an error.
|
||||
"""
|
||||
if not text:
|
||||
text = self.text_for_parsing
|
||||
if not gradientmap:
|
||||
gradientmap = list()
|
||||
|
||||
elements = ET.fromstring(text)
|
||||
text_from_markup = self.get_text_from_markup()
|
||||
final_xml = ET.fromstring(f'<span>{elements.text if elements.text else ""}</span>')
|
||||
def get_gradient_map(elements):
|
||||
for element in elements:
|
||||
if element.tag == 'gradient':
|
||||
element_text = self.get_text_from_markup(element)
|
||||
start = text_from_markup.find(element_text)
|
||||
end = start + len(element_text)
|
||||
offsets = element.get('offset').split(",") if element.get('offset') else [0]
|
||||
start_offset = int(offsets[0]) if offsets[0] else 0
|
||||
end_offset = int(offsets[1]) if len(offsets) == 2 and offsets[1] else 0
|
||||
gradientmap.append(
|
||||
{
|
||||
"start": start,
|
||||
"end": end,
|
||||
"from": element.get('from'),
|
||||
"to": element.get('to'),
|
||||
"start_offset": start_offset,
|
||||
"end_offset": end_offset,
|
||||
}
|
||||
)
|
||||
_elements_list = list(element.iter())
|
||||
if len(_elements_list) == 1:
|
||||
final_xml.append(ET.fromstring(f'<span>{element.text if element.text else ""}</span>'))
|
||||
else:
|
||||
final_xml.append(_elements_list[-1])
|
||||
else:
|
||||
if len(list(element.iter())) == 1:
|
||||
final_xml.append(element)
|
||||
else:
|
||||
get_gradient_map(element)
|
||||
get_gradient_map(elements)
|
||||
with io.BytesIO() as f:
|
||||
tree = ET.ElementTree()
|
||||
tree._setroot(final_xml)
|
||||
tree.write(f)
|
||||
self.text = f.getvalue().decode()
|
||||
|
||||
return gradientmap
|
||||
|
||||
def __repr__(self):
|
||||
return f"MarkupText({repr(self.original_text)})"
|
||||
|
||||
@contextmanager
|
||||
def register_font(font_file: typing.Union[str, Path]):
|
||||
"""Temporarily add a font file to Pango's search path.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from manimlib.mobject.svg.tex_mobject import Tex
|
|||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.scene.scene import Scene
|
||||
|
||||
import itertools as it
|
||||
|
||||
class CountingScene(Scene):
|
||||
CONFIG = {
|
||||
|
|
@ -219,7 +220,7 @@ class CountInTernary(PowerCounter):
|
|||
def construct(self):
|
||||
self.count(27)
|
||||
|
||||
# def get_template_configuration(self):
|
||||
# def get_template_configuration(self, place):
|
||||
# return [ORIGIN, UP]
|
||||
|
||||
|
||||
|
|
@ -233,7 +234,7 @@ class CountInBinaryTo256(PowerCounter):
|
|||
def construct(self):
|
||||
self.count(128, 0.3)
|
||||
|
||||
def get_template_configuration(self):
|
||||
def get_template_configuration(self, place):
|
||||
return [ORIGIN, UP]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class Scene(object):
|
|||
|
||||
self.camera = self.camera_class(**self.camera_config)
|
||||
self.file_writer = SceneFileWriter(self, **self.file_writer_config)
|
||||
self.mobjects = []
|
||||
self.mobjects = [self.camera.frame]
|
||||
self.num_plays = 0
|
||||
self.time = 0
|
||||
self.skip_time = 0
|
||||
|
|
@ -570,7 +570,7 @@ class Scene(object):
|
|||
frame = self.camera.frame
|
||||
if self.window.is_key_pressed(ord("z")):
|
||||
factor = 1 + np.arctan(10 * offset[1])
|
||||
frame.scale(factor, about_point=point)
|
||||
frame.scale(1/factor, about_point=point)
|
||||
else:
|
||||
transform = frame.get_inverse_camera_rotation_matrix()
|
||||
shift = np.dot(np.transpose(transform), offset)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ def init_customization():
|
|||
"background_color": "",
|
||||
},
|
||||
"window_position": "UR",
|
||||
"window_position": 0,
|
||||
"window_monitor": 0,
|
||||
"break_into_partial_movies": False,
|
||||
"camera_qualities": {
|
||||
"low": {
|
||||
|
|
|
|||
|
|
@ -359,81 +359,64 @@ def earclip_triangulation(verts, ring_ends):
|
|||
the ends of new paths are
|
||||
"""
|
||||
|
||||
# First, connect all the rings so that the polygon
|
||||
# with holes is instead treated as a (very convex)
|
||||
# polygon with one edge. Do this by drawing connections
|
||||
# between rings close to each other
|
||||
rings = [
|
||||
list(range(e0, e1))
|
||||
for e0, e1 in zip([0, *ring_ends], ring_ends)
|
||||
]
|
||||
|
||||
def is_in(point, ring_id):
|
||||
return abs(abs(get_winding_number([i - point for i in verts[rings[ring_id]]])) - 1) < 1e-5
|
||||
|
||||
def ring_area(ring_id):
|
||||
ring = rings[ring_id]
|
||||
s = 0
|
||||
for i, j in zip(ring[1:], ring):
|
||||
s += cross2d(verts[i], verts[j])
|
||||
return abs(s) / 2
|
||||
|
||||
# Points at the same position may cause problems
|
||||
for i in rings:
|
||||
verts[i[0]] += (verts[i[1]] - verts[i[0]]) * 5e-6
|
||||
verts[i[-1]] += (verts[i[-2]] - verts[i[-1]]) * 5e-6
|
||||
attached_rings = rings[:1]
|
||||
detached_rings = rings[1:]
|
||||
loop_connections = dict()
|
||||
verts[i[0]] += (verts[i[1]]-verts[i[0]]) * 1e-6
|
||||
verts[i[-1]] += (verts[i[-2]]-verts[i[-1]]) * 1e-6
|
||||
|
||||
while detached_rings:
|
||||
i_range, j_range = [
|
||||
list(filter(
|
||||
# Ignore indices that are already being
|
||||
# used to draw some connection
|
||||
lambda i: i not in loop_connections,
|
||||
it.chain(*ring_group)
|
||||
))
|
||||
for ring_group in (attached_rings, detached_rings)
|
||||
]
|
||||
# First, we should know which rings are directly contained in it for each ring
|
||||
|
||||
# Closet point on the atttached rings to an estimated midpoint
|
||||
# of the detached rings
|
||||
tmp_j_vert = midpoint(
|
||||
verts[j_range[0]],
|
||||
verts[j_range[len(j_range) // 2]]
|
||||
)
|
||||
i = min(i_range, key=lambda i: norm_squared(verts[i] - tmp_j_vert))
|
||||
# Closet point of the detached rings to the aforementioned
|
||||
# point of the attached rings
|
||||
j = min(j_range, key=lambda j: norm_squared(verts[i] - verts[j]))
|
||||
# Recalculate i based on new j
|
||||
i = min(i_range, key=lambda i: norm_squared(verts[i] - verts[j]))
|
||||
right = [max(verts[rings[i], 0]) for i in range(len(rings))]
|
||||
left = [min(verts[rings[i], 0]) for i in range(len(rings))]
|
||||
top = [max(verts[rings[i], 1]) for i in range(len(rings))]
|
||||
bottom = [min(verts[rings[i], 1]) for i in range(len(rings))]
|
||||
area = [ring_area(i) for i in range(len(rings))]
|
||||
|
||||
# Remember to connect the polygon at these points
|
||||
loop_connections[i] = j
|
||||
loop_connections[j] = i
|
||||
# The larger ring must be outside
|
||||
rings_sorted = list(range(len(rings)))
|
||||
rings_sorted.sort(key=lambda x: area[x], reverse=True)
|
||||
|
||||
# Move the ring which j belongs to from the
|
||||
# attached list to the detached list
|
||||
new_ring = next(filter(
|
||||
lambda ring: ring[0] <= j <= ring[-1],
|
||||
detached_rings
|
||||
))
|
||||
detached_rings.remove(new_ring)
|
||||
attached_rings.append(new_ring)
|
||||
def is_in_fast(ring_a, ring_b):
|
||||
# Whether a is in b
|
||||
return (left[ring_b] <= left[ring_a] <= right[ring_a] <= right[ring_b] and
|
||||
bottom[ring_b] <= bottom[ring_a] <= top[ring_a] <= top[ring_b] and
|
||||
is_in(verts[rings[ring_a][0]], ring_b))
|
||||
|
||||
# Setup linked list
|
||||
after = []
|
||||
end0 = 0
|
||||
for end1 in ring_ends:
|
||||
after.extend(range(end0 + 1, end1))
|
||||
after.append(end0)
|
||||
end0 = end1
|
||||
chilren = [[] for i in rings]
|
||||
for idx, i in enumerate(rings_sorted):
|
||||
for j in rings_sorted[:idx][::-1]:
|
||||
if is_in_fast(i, j):
|
||||
chilren[j].append(i)
|
||||
break
|
||||
|
||||
# Find an ordering of indices walking around the polygon
|
||||
indices = []
|
||||
i = 0
|
||||
for x in range(len(verts) + len(ring_ends) - 1):
|
||||
# starting = False
|
||||
if i in loop_connections:
|
||||
j = loop_connections[i]
|
||||
indices.extend([i, j])
|
||||
i = after[j]
|
||||
else:
|
||||
indices.append(i)
|
||||
i = after[i]
|
||||
if i == 0:
|
||||
break
|
||||
res = []
|
||||
|
||||
meta_indices = earcut(verts[indices, :2], [len(indices)])
|
||||
return [indices[mi] for mi in meta_indices]
|
||||
# Then, we can use earcut for each part
|
||||
used = [False] * len(rings)
|
||||
for i in rings_sorted:
|
||||
if used[i]:
|
||||
continue
|
||||
v = rings[i]
|
||||
ring_ends = [len(v)]
|
||||
for j in chilren[i]:
|
||||
used[j] = True
|
||||
v += rings[j]
|
||||
ring_ends.append(len(v))
|
||||
res += [v[i] for i in earcut(verts[v, :2], ring_ends)]
|
||||
|
||||
return res
|
||||
|
|
|
|||
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
39
setup.cfg
39
setup.cfg
|
|
@ -1,22 +1,43 @@
|
|||
[metadata]
|
||||
name = manimgl
|
||||
version = 1.0.1
|
||||
author = Grant Sanderson
|
||||
author-email= grant@3blue1brown.com
|
||||
summary = Animation engine for explanatory math videos
|
||||
description-file = README.md
|
||||
description-content-type = text/markdown; charset=UTF-8
|
||||
home-page = https://github.com/3b1b/manim
|
||||
author_email= grant@3blue1brown.com
|
||||
description = Animation engine for explanatory math videos
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown; charset=UTF-8
|
||||
home_page = https://github.com/3b1b/manim
|
||||
project_urls =
|
||||
Bug Tracker = https://github.com/3b1b/manim/issues
|
||||
Documentation = https://3b1b.github.io/manim/
|
||||
Source Code = https://github.com/3b1b/manim
|
||||
license = MIT
|
||||
|
||||
[files]
|
||||
packages = manimlib
|
||||
extra_files = requirements.txt
|
||||
[options]
|
||||
packages = find:
|
||||
include_package_data=True
|
||||
install_requires =
|
||||
argparse
|
||||
colour
|
||||
numpy
|
||||
Pillow
|
||||
scipy
|
||||
sympy
|
||||
tqdm
|
||||
mapbox-earcut
|
||||
matplotlib
|
||||
moderngl
|
||||
moderngl_window
|
||||
pydub
|
||||
pyyaml
|
||||
screeninfo
|
||||
pyreadline; sys_platform == 'win32'
|
||||
validators
|
||||
ipython
|
||||
PyOpenGL
|
||||
manimpango>=0.2.0,<0.4.0
|
||||
|
||||
[entry_points]
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
manimgl = manimlib.__main__:main
|
||||
manim-render = manimlib.__main__:main
|
||||
|
|
|
|||
8
setup.py
8
setup.py
|
|
@ -1,8 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
setup_requires=['pbr'],
|
||||
pbr=True,
|
||||
)
|
||||
Loading…
Add table
Reference in a new issue