Merge branch 'master' into some1-video-changes

This commit is contained in:
Grant Sanderson 2021-07-28 07:53:04 -07:00 committed by GitHub
commit 8624168ed9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 359 additions and 480 deletions

View file

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

2
MANIFEST.in Normal file
View file

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

View file

@ -7,16 +7,18 @@
[![pypi version](https://img.shields.io/pypi/v/manimgl?logo=pypi)](https://pypi.org/project/manimgl/)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://choosealicense.com/licenses/mit/)
[![Manim Subreddit](https://img.shields.io/reddit/subreddit-subscribers/manim.svg?color=ff4301&label=reddit&logo=reddit)](https://www.reddit.com/r/manim/)
[![Manim Discord](https://img.shields.io/discord/581738731934056449.svg?label=discord&logo=discord)](https://discord.gg/mMRrZQW)
[![Manim Discord](https://img.shields.io/discord/581738731934056449.svg?label=discord&logo=discord)](https://discord.com/invite/bYCyhM9Kz2)
[![docs](https://github.com/3b1b/manim/workflows/docs/badge.svg)](https://3b1b.github.io/manim/)
Manim is an engine for precise 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).

View file

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

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View file

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

View file

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

View file

@ -59,7 +59,7 @@ At this time, no window will pop up. When the program is finished, this rendered
image will be automatically opened (saved in the subdirectory ``images/`` of the same
level directory of ``start.py`` by default):
.. image:: ../_static/quickstart/SquareToCircle.png
.. image:: https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/quickstart/SquareToCircle.png
:align: center
Make an image
@ -162,7 +162,7 @@ opened after the operation is over:
.. raw:: html
<video class="manim-video" controls loop autoplay src="../_static/quickstart/SquareToCircle.mp4"></video>
<video class="manim-video" controls loop autoplay src="https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/quickstart/SquareToCircle.mp4"></video>
Let's take a look at the code this time. The first 7 lines are the same as the previous
ones, and the 8th line is similar to the 5th line, which creates an instance of the
@ -237,7 +237,7 @@ You will get an animation similar to the following:
.. raw:: html
<video class="manim-video" controls loop autoplay src="../_static/quickstart/SquareToCircleEmbed.mp4"></video>
<video class="manim-video" controls loop autoplay src="https://cdn.jsdelivr.net/gh/manim-kindergarten/CDN@master/manimgl_assets/quickstart/SquareToCircleEmbed.mp4"></video>
If you want to enter the interactive mode directly, you don't have to write an
empty scene containing only ``self.embed()``, you can directly run the following command

View file

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

View file

@ -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/>`_.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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": {

View file

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

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

View file

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

View file

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