Animate Arc with OpenGL

This commit is contained in:
Devin Neal 2021-02-19 20:41:01 -08:00
commit 448d10b590
45 changed files with 2474 additions and 204 deletions

View file

@ -357,8 +357,9 @@ A list of all config options
'preview', 'progress_bar', 'quality', 'right_side', 'save_as_gif', 'save_last_frame',
'save_pngs', 'scene_names', 'show_in_file_browser', 'sound', 'tex_dir',
'tex_template', 'tex_template_file', 'text_dir', 'top', 'transparent',
'upto_animation_number', 'use_webgl_renderer', 'verbosity', 'video_dir',
'webgl_renderer_path', 'webgl_updater_fps', 'write_all', 'write_to_movie']
'upto_animation_number', 'use_opengl_renderer', 'use_webgl_renderer', 'verbosity',
'video_dir', 'webgl_renderer_path', 'webgl_updater_fps', 'write_all',
'write_to_movie']
A list of all CLI flags

View file

@ -2,6 +2,10 @@
from manim import *
if config["use_opengl_renderer"]:
from manim.opengl import *
# To watch one of these scenes, run the following:
# python --quality m manim example_scenes.py SquareToCircle -p
#
@ -71,15 +75,21 @@ class OpeningManim(Scene):
class SquareToCircle(Scene):
def construct(self):
circle = Circle()
square = Square()
square.flip(RIGHT)
square.rotate(-3 * TAU / 8)
circle.set_fill(PINK, opacity=0.5)
# circle = Circle()
# square = Square()
# square.flip(RIGHT)
# square.rotate(-3 * TAU / 8)
# circle.set_fill(PINK, opacity=0.5)
self.play(ShowCreation(square))
self.play(Transform(square, circle))
self.play(FadeOut(square))
# self.play(ShowCreation(square))
# self.play(Transform(square, circle))
# self.play(FadeOut(square))
square = Arc()
square2 = Arc()
square2.data["points"] += 2 * RIGHT
square2.points += 2 * RIGHT
self.play(Transform(square, square2))
class WarpSquare(Scene):

View file

@ -41,6 +41,7 @@ from .mobject.changing import *
from .mobject.frame import *
from .mobject.functions import *
from .mobject.geometry import *
from .mobject.graph import *
from .mobject.logo import *
from .mobject.matrix import *

View file

@ -1,6 +1,7 @@
import os
import sys
import traceback
from manim.renderer.opengl_renderer import OpenGLRenderer
from manim import logger, console, config, __version__
from manim.utils.module_ops import (
@ -73,7 +74,20 @@ def main():
else:
config.digest_args(args)
input_file = config.get_dir("input_file")
if config["use_webgl_renderer"]:
if config["use_opengl_renderer"]:
from manim.renderer.opengl_renderer import OpenGLRenderer
for SceneClass in scene_classes_from_file(input_file):
try:
renderer = OpenGLRenderer()
scene = SceneClass(renderer)
scene.render()
except Exception:
print("\n\n")
traceback.print_exc()
print("\n\n")
elif config["use_webgl_renderer"]:
try:
from manim.grpc.impl import frame_server_impl

View file

@ -93,6 +93,9 @@ webgl_renderer_path =
# --webgl_updater_fps
webgl_updater_fps = 15
# --use_opengl_renderer
use_opengl_renderer = False
# If the -t (--transparent) flag is used, these will be replaced with the
# values specified in the [TRANSPARENT] section later in this file.
png_mode = RGB

View file

@ -411,6 +411,13 @@ def _parse_args_no_subcmd(args: list) -> argparse.Namespace:
"the rendering at the second value",
)
parser.add_argument(
"--use_opengl_renderer",
help="Render animations using the OpenGL renderer",
action="store_const",
const=True,
)
parser.add_argument(
"--use_webgl_renderer",
help="Render animations using the WebGL frontend",

View file

@ -286,6 +286,7 @@ class ManimConfig(MutableMapping):
"tex_template_file",
"text_dir",
"upto_animation_number",
"use_opengl_renderer",
"use_webgl_renderer",
"webgl_updater_fps",
"verbosity",
@ -509,6 +510,7 @@ class ManimConfig(MutableMapping):
"disable_caching",
"flush_cache",
"custom_folders",
"use_opengl_renderer",
"use_webgl_renderer",
]:
setattr(self, key, parser["CLI"].getboolean(key, fallback=False))
@ -628,6 +630,7 @@ class ManimConfig(MutableMapping):
"scene_names",
"verbosity",
"background_color",
"use_opengl_renderer",
"use_webgl_renderer",
"webgl_updater_fps",
]:
@ -1038,9 +1041,21 @@ class ManimConfig(MutableMapping):
"save_last_frame, save_pngs, or save_as_gif)"
)
@property
def use_opengl_renderer(self):
"""Whether or not to use the OpenGL renderer."""
return self._d["use_opengl_renderer"]
@use_opengl_renderer.setter
def use_opengl_renderer(self, val: bool) -> None:
self._d["use_opengl_renderer"] = val
if val:
self["disable_caching"] = True
@property
def use_webgl_renderer(self):
"""Whether to use WebGL renderer or not (default)."""
"""Whether or not to use WebGL renderer."""
return self._d["use_webgl_renderer"]
@use_webgl_renderer.setter

View file

@ -60,8 +60,29 @@ class Mobject(Container):
self.reset_points()
self.generate_points()
self.init_colors()
self.data = dict()
self.depth_test = False
self.is_fixed_in_frame = False
self.gloss = 0.0
self.shadow = 0.0
# OpenGL data.
self.init_gl_data()
self.init_gl_points()
self.init_gl_colors()
Container.__init__(self, **kwargs)
def init_gl_data(self):
pass
def init_gl_points(self):
pass
def init_gl_colors(self):
pass
@property
def animate(self):
"""Used to animate the application of a method.
@ -135,6 +156,9 @@ class Mobject(Container):
# Typically implemented in subclass, unless purposefully left blank
pass
def refresh_bounding_box(self):
pass
def add(self, *mobjects):
"""Add mobjects as submobjects.
@ -1166,7 +1190,10 @@ class Mobject(Container):
return self.get_all_points()
def get_num_points(self):
return len(self.points)
if config["use_opengl_renderer"]:
return len(self.data["points"])
else:
return len(self.points)
def get_extremum_along_dim(self, points=None, dim=0, key=0):
if points is None:
@ -1548,7 +1575,12 @@ class Mobject(Container):
self.add(dotL, dotR, dotMiddle)
"""
self.points = path_func(mobject1.points, mobject2.points, alpha)
if config["use_opengl_renderer"]:
self.data["points"][:] = path_func(
mobject1.data["points"], mobject2.data["points"], alpha
)
else:
self.points = path_func(mobject1.points, mobject2.points, alpha)
self.interpolate_color(mobject1, mobject2, alpha)
return self

View file

@ -0,0 +1,97 @@
from ..utils.space_ops import angle_of_vector
from ..utils.space_ops import angle_between_vectors
from ..utils.space_ops import compass_directions
from ..utils.space_ops import find_intersection
from ..utils.space_ops import get_norm
from ..utils.space_ops import normalize
from ..utils.space_ops import rotate_vector
from ..utils.space_ops import rotation_matrix_transpose
from ..utils.iterables import adjacent_n_tuples
from ..constants import *
from ..mobject.geometry import TipableVMobject
class OpenGLArc(TipableVMobject):
"""A circular arc."""
opengl_compatible = True
def __init__(
self,
start_angle=0,
angle=TAU / 4,
radius=1.0,
num_components=8,
anchors_span_full_range=True,
arc_center=ORIGIN,
**kwargs
):
if radius is None: # apparently None is passed by ArcBetweenPoints
radius = 1.0
self.radius = radius
self.num_components = num_components
self.anchors_span_full_range = anchors_span_full_range
self.arc_center = arc_center
self.start_angle = start_angle
self.angle = angle
self._failed_to_get_center = False
TipableVMobject.__init__(self, **kwargs)
def init_gl_points(self):
self.set_points(
OpenGLArc.create_quadratic_bezier_points(
angle=self.angle,
start_angle=self.start_angle,
n_components=self.num_components,
)
)
self.scale(self.radius, about_point=ORIGIN)
self.shift(self.arc_center)
@staticmethod
def create_quadratic_bezier_points(angle, start_angle=0, n_components=8):
samples = np.array(
[
[np.cos(a), np.sin(a), 0]
for a in np.linspace(
start_angle,
start_angle + angle,
2 * n_components + 1,
)
]
)
theta = angle / n_components
samples[1::2] /= np.cos(theta / 2)
points = np.zeros((3 * n_components, 3))
points[0::3] = samples[0:-1:2]
points[1::3] = samples[1::2]
points[2::3] = samples[2::2]
return points
def get_arc_center(self):
"""
Looks at the normals to the first two
anchors, and finds their intersection points
"""
# First two anchors and handles
a1, h, a2 = self.get_points()[:3]
# Tangent vectors
t1 = h - a1
t2 = h - a2
# Normals
n1 = rotate_vector(t1, TAU / 4)
n2 = rotate_vector(t2, TAU / 4)
return find_intersection(a1, n1, a2, n2)
def get_start_angle(self):
angle = angle_of_vector(self.get_start() - self.get_arc_center())
return angle % TAU
def get_stop_angle(self):
angle = angle_of_vector(self.get_end() - self.get_arc_center())
return angle % TAU
def move_arc_center_to(self, point):
self.shift(point - self.get_arc_center())
return self

View file

@ -11,6 +11,7 @@ __all__ = [
]
from ... import config
import itertools as it
import sys
import colour
@ -44,6 +45,21 @@ from ...utils.space_ops import shoelace_direction
class VMobject(Mobject):
fill_dtype = [
("point", np.float32, (3,)),
("unit_normal", np.float32, (3,)),
("color", np.float32, (4,)),
("vert_index", np.float32, (1,)),
]
stroke_dtype = [
("point", np.float32, (3,)),
("prev_point", np.float32, (3,)),
("next_point", np.float32, (3,)),
("unit_normal", np.float32, (3,)),
("stroke_width", np.float32, (1,)),
("color", np.float32, (4,)),
]
def __init__(
self,
fill_color=None,
@ -96,8 +112,53 @@ class VMobject(Mobject):
self.shade_in_3d = shade_in_3d
self.tolerance_for_point_equality = tolerance_for_point_equality
self.n_points_per_cubic_curve = n_points_per_cubic_curve
Mobject.__init__(self, **kwargs)
def init_gl_data(self):
self.needs_new_triangulation = True
self.joint_type = "auto"
self.flat_stroke = True
self.data["points"] = np.zeros(0)
self.data["unit_normal"] = np.array([[0.0, 0.0, 1.0]])
self.data["stroke_width"] = np.array([[4]])
def init_gl_points(self):
self.data["points"] = np.array(
[
[1.0, 1.0, 0.0],
[0.5, 1.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[-0.5, 1.0, 0.0],
[-1.0, 1.0, 0.0],
[-1.0, 1.0, 0.0],
[-1.0, 0.5, 0.0],
[-1.0, 0.0, 0.0],
[-1.0, 0.0, 0.0],
[-1.0, -0.5, 0.0],
[-1.0, -1.0, 0.0],
[-1.0, -1.0, 0.0],
[-0.5, -1.0, 0.0],
[0.0, -1.0, 0.0],
[0.0, -1.0, 0.0],
[0.5, -1.0, 0.0],
[1.0, -1.0, 0.0],
[1.0, -1.0, 0.0],
[1.0, -0.5, 0.0],
[1.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 0.5, 0.0],
[1.0, 1.0, 0.0],
]
)
def init_gl_colors(self):
self.data["stroke_rgba"] = np.array([[1.0, 1.0, 1.0, 1.0]])
self.data["fill_rgba"] = np.array([[1.0, 1.0, 1.0, 0.0]])
def get_group_class(self):
return VGroup
@ -438,8 +499,18 @@ class VMobject(Mobject):
# Points
def set_points(self, points):
self.points = np.array(points)
return self
if config["use_opengl_renderer"]:
if len(points) == len(self.data["points"]):
self.data["points"][:] = points
elif isinstance(points, np.ndarray):
self.data["points"] = points.copy()
else:
self.data["points"] = np.array(points)
self.refresh_bounding_box()
return self
else:
self.points = np.array(points)
return self
def get_points(self):
return np.array(self.points)

3
manim/opengl/__init__.py Normal file
View file

@ -0,0 +1,3 @@
from ..mobject.opengl_geometry import *
Arc = OpenGLArc

View file

@ -118,7 +118,7 @@ class CairoRenderer:
kwargs["include_submobjects"] = include_submobjects
self.camera.capture_mobjects(mobjects, **kwargs)
def render(self, scene, moving_mobjects):
def render(self, scene, time, moving_mobjects):
self.update_frame(scene, moving_mobjects)
self.add_frame(self.get_frame())

View file

@ -0,0 +1,323 @@
import moderngl
from .opengl_renderer_window import Window
from .shader_wrapper import ShaderWrapper
import numpy as np
from ..mobject.types.vectorized_mobject import VMobject
import itertools as it
import time
from ..mobject import opengl_geometry
class OpenGLCamera:
use_z_index = True
frame_rate = 60
points_per_curve = 3
JOINT_TYPE_MAP = {
"auto": 0,
"round": 1,
"bevel": 2,
"miter": 3,
}
class OpenGLRenderer:
def __init__(self):
self.num_plays = 0
self.skip_animations = False
self.window = Window(size=(854, 480))
self.camera = OpenGLCamera()
self.context = self.window.ctx
self.context.enable(moderngl.BLEND)
self.context.blend_func = (
moderngl.SRC_ALPHA,
moderngl.ONE_MINUS_SRC_ALPHA,
moderngl.ONE,
moderngl.ONE,
)
self.frame_buffer_object = self.context.detect_framebuffer()
self.id_to_shader_program = {}
def update_depth_test(self, context, shader_wrapper):
if shader_wrapper.depth_test:
self.context.enable(moderngl.DEPTH_TEST)
else:
self.context.disable(moderngl.DEPTH_TEST)
def render_mobject(self, mob):
shader_wrapper_list = self.get_shader_wrapper_list(mob)
render_group_list = map(
lambda x: self.get_render_group(self.context, x), shader_wrapper_list
)
for render_group in render_group_list:
self.render_render_group(render_group)
def render_render_group(self, render_group):
shader_wrapper = render_group["shader_wrapper"]
shader_program = render_group["prog"]
self.set_shader_uniforms(render_group["prog"], render_group["shader_wrapper"])
self.update_depth_test(self.context, shader_wrapper)
render_group["vao"].render(int(shader_wrapper.render_primitive))
if render_group["single_use"]:
for key in ["vbo", "ibo", "vao"]:
if render_group[key] is not None:
render_group[key].release()
def read_data_to_shader(self, mob, shader_data, shader_data_key, data_key):
# Check data alignment.
d_len = len(mob.data[data_key])
if d_len != 1 and d_len != len(shader_data):
mob.data[data_key] = resize_with_interpolation(
mob.data[data_key], len(shader_data)
)
shader_data[shader_data_key] = mob.data[data_key]
def get_stroke_shader_data(self, mob):
points = mob.data["points"]
stroke_data = np.zeros(len(points), dtype=VMobject.stroke_dtype)
stroke_data["point"] = points
stroke_data["prev_point"][:points_per_curve] = points[-points_per_curve:]
stroke_data["prev_point"][points_per_curve:] = points[:-points_per_curve]
stroke_data["next_point"][:-points_per_curve] = points[points_per_curve:]
stroke_data["next_point"][-points_per_curve:] = points[:points_per_curve]
self.read_data_to_shader(mob, stroke_data, "color", "stroke_rgba")
self.read_data_to_shader(mob, stroke_data, "stroke_width", "stroke_width")
self.read_data_to_shader(mob, stroke_data, "unit_normal", "unit_normal")
return stroke_data
def get_fill_shader_data(self, mob):
points = mob.data["points"]
fill_data = np.zeros(len(points), dtype=VMobject.fill_dtype)
fill_data["vert_index"][:, 0] = range(len(points))
self.read_data_to_shader(mob, fill_data, "point", "points")
self.read_data_to_shader(mob, fill_data, "color", "fill_rgba")
self.read_data_to_shader(mob, fill_data, "unit_normal", "unit_normal")
return fill_data
def get_stroke_shader_wrapper(self, mob):
return ShaderWrapper(
vert_data=self.get_stroke_shader_data(mob),
shader_folder="quadratic_bezier_stroke",
render_primitive=moderngl.TRIANGLES,
uniforms=self.get_stroke_uniforms(mob),
depth_test=mob.depth_test,
)
def get_fill_shader_wrapper(self, mob):
return ShaderWrapper(
vert_data=self.get_fill_shader_data(mob),
vert_indices=self.get_triangulation(mob),
shader_folder="quadratic_bezier_fill",
render_primitive=moderngl.TRIANGLES,
uniforms=self.get_fill_uniforms(mob),
depth_test=mob.depth_test,
)
def get_shader_wrapper_list(self, mob):
fill_shader_wrappers = []
stroke_shader_wrappers = []
back_stroke_shader_wrappers = []
if any(mob.data["fill_rgba"][:, 3]):
fill_shader_wrappers.append(self.get_fill_shader_wrapper(mob))
if any(mob.data["stroke_rgba"][:, 3]) and mob.data["stroke_width"][0]:
stroke_shader_wrapper = self.get_stroke_shader_wrapper(mob)
# TODO: Handle background_stroke
# if mob.draw_stroke_behind_fill:
# back_stroke_shader_wrappers.append(ssw)
# else:
stroke_shader_wrappers.append(stroke_shader_wrapper)
# Combine data lists
wrapper_lists = [
back_stroke_shader_wrappers,
fill_shader_wrappers,
stroke_shader_wrappers,
]
result = []
for wrapper_list in wrapper_lists:
if wrapper_list:
wrapper = wrapper_list[0]
wrapper.combine_with(*wrapper_list[1:])
result.append(wrapper)
return result
def get_render_group(self, context, shader_wrapper, single_use=True):
# Data buffers
vertex_buffer_object = self.context.buffer(shader_wrapper.vert_data.tobytes())
if shader_wrapper.vert_indices is None:
index_buffer_object = None
else:
vert_index_data = shader_wrapper.vert_indices.astype("i4").tobytes()
if vert_index_data:
index_buffer_object = self.context.buffer(vert_index_data)
else:
index_buffer_object = None
# Program and vertex array
shader_program, vert_format = self.get_shader_program(
self.context, shader_wrapper
)
vertex_array_object = self.context.vertex_array(
program=shader_program,
content=[
(vertex_buffer_object, vert_format, *shader_wrapper.vert_attributes)
],
index_buffer=index_buffer_object,
)
return {
"vbo": vertex_buffer_object,
"ibo": index_buffer_object,
"vao": vertex_array_object,
"prog": shader_program,
"shader_wrapper": shader_wrapper,
"single_use": single_use,
}
def get_shader_program(self, context, shader_wrapper):
sid = shader_wrapper.get_program_id()
if sid not in self.id_to_shader_program:
# Create shader program for the first time, then cache
# in self.id_to_shader_program.
program_code = shader_wrapper.get_program_code()
program = self.context.program(**program_code)
vert_format = moderngl.detect_format(
program, shader_wrapper.vert_attributes
)
self.id_to_shader_program[sid] = (program, vert_format)
return self.id_to_shader_program[sid]
def get_triangulation(self, mob, normal_vector=None):
# Figure out how to triangulate the interior to know
# how to send the points as to the vertex shader.
# First triangles come directly from the points
if normal_vector is None:
normal_vector = mob.get_unit_normal()
if not mob.needs_new_triangulation:
return mob.triangulation
points = mob.data["points"]
if len(points) <= 1:
mob.triangulation = np.zeros(0, dtype="i4")
mob.needs_new_triangulation = False
return mob.triangulation
if not np.isclose(normal_vector, OUT).all():
# Rotate points such that unit normal vector is OUT
points = np.dot(points, z_to_vector(normal_vector))
indices = np.arange(len(points), dtype=int)
b0s = points[0::3]
b1s = points[1::3]
b2s = points[2::3]
v01s = b1s - b0s
v12s = b2s - b1s
crosses = cross2d(v01s, v12s)
convexities = np.sign(crosses)
atol = tolerance_for_point_equality
end_of_loop = np.zeros(len(b0s), dtype=bool)
end_of_loop[:-1] = (np.abs(b2s[:-1] - b0s[1:]) > atol).any(1)
end_of_loop[-1] = True
concave_parts = convexities < 0
# These are the vertices to which we'll apply a polygon triangulation
inner_vert_indices = np.hstack(
[
indices[0::3],
indices[1::3][concave_parts],
indices[2::3][end_of_loop],
]
)
inner_vert_indices.sort()
rings = np.arange(1, len(inner_vert_indices) + 1)[inner_vert_indices % 3 == 2]
# Triangulate
inner_verts = points[inner_vert_indices]
inner_tri_indices = inner_vert_indices[
earclip_triangulation(inner_verts, rings)
]
tri_indices = np.hstack([indices, inner_tri_indices])
mob.triangulation = tri_indices
mob.needs_new_triangulation = False
return tri_indices
def get_stroke_uniforms(self, mob):
return dict(
**self.get_fill_uniforms(mob),
joint_type=JOINT_TYPE_MAP[mob.joint_type],
flat_stroke=float(mob.flat_stroke),
)
def get_fill_uniforms(self, mob):
return dict(
is_fixed_in_frame=float(mob.is_fixed_in_frame),
gloss=mob.gloss,
shadow=mob.shadow,
)
def set_shader_uniforms(self, shader, shader_wrapper):
perspective_uniforms = {
"frame_shape": (14.222222222222221, 8.0),
"anti_alias_width": 0.016666666666666666,
"camera_center": (0.0, 0.0, 0.0),
"camera_rotation": (1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0),
"light_source_position": (-10.0, 10.0, 10.0),
"focal_distance": 16.0,
}
# for name, path in shader_wrapper.texture_paths.items():
# tid = self.get_texture_id(path)
# shader[name].value = tid
for name, value in it.chain(
shader_wrapper.uniforms.items(), perspective_uniforms.items()
):
try:
shader[name].value = value
except KeyError:
pass
def init_scene(self, scene):
self.file_writer = None
def play(self, scene, *args, **kwargs):
if scene.compile_animation_data(*args, **kwargs):
self.animation_start_time = time.time()
self.animation_elapsed_time = 0
scene.play_internal()
self.num_plays += 1
def render(self, scene, frame_offset, moving_mobjects):
def update_frame():
self.frame_buffer_object.clear(*window_background_color)
self.render_mobject(scene.mobjects[0])
self.window.swap_buffers()
self.animation_elapsed_time = time.time() - self.animation_start_time
window_background_color = (0.2, 0.2, 0.2, 1)
update_frame()
while self.animation_elapsed_time < frame_offset:
update_frame()
def scene_finished(self, scene):
pass
def save_static_frame_data(self, scene, static_mobjects):
pass

View file

@ -0,0 +1,25 @@
from .. import config
import moderngl_window as mglw
from moderngl_window.context.pyglet.window import Window as PygletWindow
from moderngl_window.timers.clock import Timer
class Window(PygletWindow):
fullscreen = False
resizable = True
gl_version = (3, 3)
vsync = True
cursor = True
def __init__(self, size=(config["pixel_width"], config["pixel_height"]), **kwargs):
super().__init__()
self.pressed_keys = set()
self.title = "Title goes here"
self.size = size
mglw.activate_context(window=self)
self.timer = Timer()
self.config = mglw.WindowConfig(ctx=self.ctx, wnd=self, timer=self.timer)
self.timer.start()

View file

@ -0,0 +1,194 @@
import os
import re
import moderngl
import numpy as np
import copy
from .. import logger
# from manimlib.utils.directories import get_shader_dir
# from manimlib.utils.file_ops import find_file
# Mobjects that should be rendered with
# the same shader will be organized and
# clumped together based on keeping track
# of a dict holding all the relevant information
# to that shader
def get_shader_dir():
return "manim/renderer/shaders"
def find_file(file_name, directories=None):
# Check if what was passed in is already a valid path to a file
if os.path.exists(file_name):
return file_name
possible_paths = (os.path.join(directory, file_name) for directory in directories)
for path in possible_paths:
if os.path.exists(path):
return path
else:
logger.info(f"{path} does not exist.")
raise IOError(f"{file_name} not Found")
class ShaderWrapper(object):
def __init__(
self,
vert_data=None,
vert_indices=None,
shader_folder=None,
uniforms=None, # A dictionary mapping names of uniform variables
texture_paths=None, # A dictionary mapping names to filepaths for textures.
depth_test=False,
render_primitive=moderngl.TRIANGLE_STRIP,
):
self.vert_data = vert_data
self.vert_indices = vert_indices
self.vert_attributes = vert_data.dtype.names
self.shader_folder = shader_folder
self.uniforms = uniforms or dict()
self.texture_paths = texture_paths or dict()
self.depth_test = depth_test
self.render_primitive = str(render_primitive)
self.init_program_code()
self.refresh_id()
def copy(self):
result = copy.copy(self)
result.vert_data = np.array(self.vert_data)
if result.vert_indices is not None:
result.vert_indices = np.array(self.vert_indices)
if self.uniforms:
result.uniforms = dict(self.uniforms)
if self.texture_paths:
result.texture_paths = dict(self.texture_paths)
return result
def is_valid(self):
return all(
[
self.vert_data is not None,
self.program_code["vertex_shader"] is not None,
self.program_code["fragment_shader"] is not None,
]
)
def get_id(self):
return self.id
def get_program_id(self):
return self.program_id
def create_id(self):
# A unique id for a shader
return "|".join(
map(
str,
[
self.program_id,
self.uniforms,
self.texture_paths,
self.depth_test,
self.render_primitive,
],
)
)
def refresh_id(self):
self.program_id = self.create_program_id()
self.id = self.create_id()
def create_program_id(self):
return hash(
"".join(
(
self.program_code[f"{name}_shader"] or ""
for name in ("vertex", "geometry", "fragment")
)
)
)
def init_program_code(self):
def get_code(name):
return get_shader_code_from_file(
os.path.join(self.shader_folder, f"{name}.glsl")
)
self.program_code = {
"vertex_shader": get_code("vert"),
"geometry_shader": get_code("geom"),
"fragment_shader": get_code("frag"),
}
def get_program_code(self):
return self.program_code
def replace_code(self, old, new):
code_map = self.program_code
for (name, code) in code_map.items():
if code_map[name] is None:
continue
code_map[name] = re.sub(old, new, code_map[name])
self.refresh_id()
def combine_with(self, *shader_wrappers):
# Assume they are of the same type
if len(shader_wrappers) == 0:
return
if self.vert_indices is not None:
num_verts = len(self.vert_data)
indices_list = [self.vert_indices]
data_list = [self.vert_data]
for sw in shader_wrappers:
indices_list.append(sw.vert_indices + num_verts)
data_list.append(sw.vert_data)
num_verts += len(sw.vert_data)
self.vert_indices = np.hstack(indices_list)
self.vert_data = np.hstack(data_list)
else:
self.vert_data = np.hstack(
[self.vert_data, *[sw.vert_data for sw in shader_wrappers]]
)
return self
# For caching
filename_to_code_map = {}
def get_shader_code_from_file(filename):
if not filename:
return None
if filename in filename_to_code_map:
return filename_to_code_map[filename]
try:
filepath = find_file(
filename,
directories=[get_shader_dir(), "/"],
)
except IOError:
return None
with open(filepath, "r") as f:
result = f.read()
# To share functionality between shaders, some functions are read in
# from other files an inserted into the relevant strings before
# passing to ctx.program for compiling
# Replace "#INSERT " lines with relevant code
insertions = re.findall(r"^#INSERT .*\.glsl$", result, flags=re.MULTILINE)
for line in insertions:
inserted_code = get_shader_code_from_file(
os.path.join("inserts", line.replace("#INSERT ", ""))
)
result = result.replace(line, inserted_code)
filename_to_code_map[filename] = result
return result
def get_colormap_code(rgb_list):
data = ",".join("vec3({}, {}, {})".format(*rgb) for rgb in rgb_list)
return f"vec3[{len(rgb_list)}]({data})"

View file

@ -0,0 +1,13 @@
#version 330
uniform sampler2D Texture;
in vec2 v_im_coords;
in float v_opacity;
out vec4 frag_color;
void main() {
frag_color = texture(Texture, v_im_coords);
frag_color.a = v_opacity;
}

View file

@ -0,0 +1,22 @@
#version 330
#INSERT camera_uniform_declarations.glsl
uniform sampler2D Texture;
in vec3 point;
in vec2 im_coords;
in float opacity;
out vec2 v_im_coords;
out float v_opacity;
// Analog of import for manim only
#INSERT get_gl_Position.glsl
#INSERT position_point_into_frame.glsl
void main(){
v_im_coords = im_coords;
v_opacity = opacity;
gl_Position = get_gl_Position(position_point_into_frame(point));
}

View file

@ -0,0 +1,7 @@
There seems to be no analog to #include in C++ for OpenGL shaders. While there are other options for sharing code between shaders, a lot of them aren't great, especially if the goal is to have all the logic for which specific bits of code to share handled in the shader file itself. So the way manim currently works is to replace any line which looks like
#INSERT <file_name>
with the code from one of the files in this folder.
The functions in this file often include reference to uniforms which are assumed to be part of the surrounding context into which they are inserted.

View file

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

View file

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

View file

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

View file

@ -0,0 +1,31 @@
// Assumes the following uniforms exist in the surrounding context:
// uniform vec2 frame_shape;
// uniform float focal_distance;
// uniform float is_fixed_in_frame;
const vec2 DEFAULT_FRAME_SHAPE = vec2(8.0 * 16.0 / 9.0, 8.0);
float perspective_scale_factor(float z, float focal_distance){
return max(0.0, focal_distance / (focal_distance - z));
}
vec4 get_gl_Position(vec3 point){
vec4 result = vec4(point, 1.0);
if(!bool(is_fixed_in_frame)){
result.x *= 2.0 / frame_shape.x;
result.y *= 2.0 / frame_shape.y;
float psf = perspective_scale_factor(result.z, focal_distance);
if (psf > 0){
result.xy *= psf;
// TODO, what's the better way to do this?
// This is to keep vertices too far out of frame from getting cut.
result.z *= 0.01;
}
} else{
result.x *= 2.0 / DEFAULT_FRAME_SHAPE.x;
result.y *= 2.0 / DEFAULT_FRAME_SHAPE.y;
}
result.z *= -1;
return result;
}

View file

@ -0,0 +1,16 @@
// Assumes the following uniforms exist in the surrounding context:
// uniform vec3 camera_center;
// uniform mat3 camera_rotation;
vec3 get_rotated_surface_unit_normal_vector(vec3 point, vec3 du_point, vec3 dv_point){
vec3 cp = cross(
(du_point - point),
(dv_point - point)
);
if(length(cp) == 0){
// Instead choose a normal to just dv_point - point in the direction of point
vec3 v2 = dv_point - point;
cp = cross(cross(v2, point), v2);
}
return normalize(rotate_point_into_frame(cp));
}

View file

@ -0,0 +1,22 @@
vec3 get_unit_normal(in vec3[3] points){
float tol = 1e-6;
vec3 v1 = normalize(points[1] - points[0]);
vec3 v2 = normalize(points[2] - points[0]);
vec3 cp = cross(v1, v2);
float cp_norm = length(cp);
if(cp_norm < tol){
// Three points form a line, so find a normal vector
// to that line in the plane shared with the z-axis
vec3 k_hat = vec3(0.0, 0.0, 1.0);
vec3 new_cp = cross(cross(v2, k_hat), v2);
float new_cp_norm = length(new_cp);
if(new_cp_norm < tol){
// We only come here if all three points line up
// on the z-axis.
return vec3(0.0, -1.0, 0.0);
// return k_hat;
}
return new_cp / new_cp_norm;
}
return cp / cp_norm;
}

View file

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

View file

@ -0,0 +1,107 @@
// Must be inserted in a context with a definition for modify_distance_for_endpoints
// All of this is with respect to a curve that's been rotated/scaled
// so that b0 = (0, 0) and b1 = (1, 0). That is, b2 entirely
// determines the shape of the curve
vec2 bezier(float t, vec2 b2){
// Quick returns for the 0 and 1 cases
if (t == 0) return vec2(0, 0);
else if (t == 1) return b2;
// Everything else
return vec2(
2 * t * (1 - t) + b2.x * t*t,
b2.y * t * t
);
}
float cube_root(float x){
return sign(x) * pow(abs(x), 1.0 / 3.0);
}
int cubic_solve(float a, float b, float c, float d, out float roots[3]){
// Normalize so a = 1
b = b / a;
c = c / a;
d = d / a;
float p = c - b*b / 3.0;
float q = b * (2.0*b*b - 9.0*c) / 27.0 + d;
float p3 = p*p*p;
float disc = q*q + 4.0*p3 / 27.0;
float offset = -b / 3.0;
if(disc >= 0.0){
float z = sqrt(disc);
float u = (-q + z) / 2.0;
float v = (-q - z) / 2.0;
u = cube_root(u);
v = cube_root(v);
roots[0] = offset + u + v;
return 1;
}
float u = sqrt(-p / 3.0);
float v = acos(-sqrt( -27.0 / p3) * q / 2.0) / 3.0;
float m = cos(v);
float n = sin(v) * 1.732050808;
float all_roots[3] = float[3](
offset + u * (n - m),
offset - u * (n + m),
offset + u * (m + m)
);
// Only accept roots with a positive derivative
int n_valid_roots = 0;
for(int i = 0; i < 3; i++){
float r = all_roots[i];
if(3*r*r + 2*b*r + c > 0){
roots[n_valid_roots] = r;
n_valid_roots++;
}
}
return n_valid_roots;
}
float dist_to_line(vec2 p, vec2 b2){
float t = clamp(p.x / b2.x, 0, 1);
float dist;
if(t == 0) dist = length(p);
else if(t == 1) dist = distance(p, b2);
else dist = abs(p.y);
return modify_distance_for_endpoints(p, dist, t);
}
float dist_to_point_on_curve(vec2 p, float t, vec2 b2){
t = clamp(t, 0, 1);
return modify_distance_for_endpoints(
p, length(p - bezier(t, b2)), t
);
}
float min_dist_to_curve(vec2 p, vec2 b2, float degree){
// Check if curve is really a a line
if(degree == 1) return dist_to_line(p, b2);
// Try finding the exact sdf by solving the equation
// (d/dt) dist^2(t) = 0, which amount to the following
// cubic.
float xm2 = uv_b2.x - 2.0;
float y = uv_b2.y;
float a = xm2*xm2 + y*y;
float b = 3 * xm2;
float c = -(p.x*xm2 + p.y*y) + 2;
float d = -p.x;
float roots[3];
int n = cubic_solve(a, b, c, d, roots);
// At most 2 roots will have been populated.
float d0 = dist_to_point_on_curve(p, roots[0], b2);
if(n == 1) return d0;
float d1 = dist_to_point_on_curve(p, roots[1], b2);
return min(d0, d1);
}

View file

@ -0,0 +1,92 @@
float cross2d(vec2 v, vec2 w){
return v.x * w.y - w.x * v.y;
}
mat3 get_xy_to_uv(vec2 b0, vec2 b1){
mat3 shift = mat3(
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
-b0.x, -b0.y, 1.0
);
float sf = length(b1 - b0);
vec2 I = (b1 - b0) / sf;
vec2 J = vec2(-I.y, I.x);
mat3 rotate = mat3(
I.x, J.x, 0.0,
I.y, J.y, 0.0,
0.0, 0.0, 1.0
);
return (1 / sf) * rotate * shift;
}
// Orthogonal matrix to convert to a uv space defined so that
// b0 goes to [0, 0] and b1 goes to [1, 0]
mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 unit_normal){
mat4 shift = mat4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-b0.x, -b0.y, -b0.z, 1
);
float scale_factor = length(b1 - b0);
vec3 I = (b1 - b0) / scale_factor;
vec3 K = unit_normal;
vec3 J = cross(K, I);
// Transpose (hence inverse) of matrix taking
// i-hat to I, k-hat to unit_normal, and j-hat to their cross
mat4 rotate = mat4(
I.x, J.x, K.x, 0.0,
I.y, J.y, K.y, 0.0,
I.z, J.z, K.z, 0.0,
0.0, 0.0, 0.0, 1.0
);
return (1 / scale_factor) * rotate * shift;
}
// Returns 0 for null curve, 1 for linear, 2 for quadratic.
// Populates new_points with bezier control points for the curve,
// which for quadratics will be the same, but for linear and null
// might change. The idea is to inform the caller of the degree,
// while also passing tangency information in the linear case.
// float get_reduced_control_points(vec3 b0, vec3 b1, vec3 b2, out vec3 new_points[3]){
float get_reduced_control_points(in vec3 points[3], out vec3 new_points[3]){
float length_threshold = 1e-6;
float angle_threshold = 5e-2;
vec3 p0 = points[0];
vec3 p1 = points[1];
vec3 p2 = points[2];
vec3 v01 = (p1 - p0);
vec3 v12 = (p2 - p1);
float dot_prod = clamp(dot(normalize(v01), normalize(v12)), -1, 1);
bool aligned = acos(dot_prod) < angle_threshold;
bool distinct_01 = length(v01) > length_threshold; // v01 is considered nonzero
bool distinct_12 = length(v12) > length_threshold; // v12 is considered nonzero
int n_uniques = int(distinct_01) + int(distinct_12);
bool quadratic = (n_uniques == 2) && !aligned;
bool linear = (n_uniques == 1) || ((n_uniques == 2) && aligned);
bool constant = (n_uniques == 0);
if(quadratic){
new_points[0] = p0;
new_points[1] = p1;
new_points[2] = p2;
return 2.0;
}else if(linear){
new_points[0] = p0;
new_points[1] = (p0 + p2) / 2.0;
new_points[2] = p2;
return 1.0;
}else{
new_points[0] = p0;
new_points[1] = p0;
new_points[2] = p0;
return 0.0;
}
}

View file

@ -0,0 +1,66 @@
#version 330
#INSERT camera_uniform_declarations.glsl
in vec4 color;
in float fill_all; // Either 0 or 1e
in float uv_anti_alias_width;
in vec3 xyz_coords;
in float orientation;
in vec2 uv_coords;
in vec2 uv_b2;
in float bezier_degree;
out vec4 frag_color;
// Needed for quadratic_bezier_distance insertion below
float modify_distance_for_endpoints(vec2 p, float dist, float t){
return dist;
}
#INSERT quadratic_bezier_distance.glsl
float sdf(){
if(bezier_degree < 2){
return abs(uv_coords[1]);
}
float u2 = uv_b2.x;
float v2 = uv_b2.y;
// For really flat curves, just take the distance to x-axis
if(abs(v2 / u2) < 0.1 * uv_anti_alias_width){
return abs(uv_coords[1]);
}
// For flat-ish curves, take the curve
else if(abs(v2 / u2) < 0.5 * uv_anti_alias_width){
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
}
// I know, I don't love this amount of arbitrary-seeming branching either,
// but a number of strange dimples and bugs pop up otherwise.
// This converts uv_coords to yet another space where the bezier points sit on
// (0, 0), (1/2, 0) and (1, 1), so that the curve can be expressed implicityly
// as y = x^2.
mat2 to_simple_space = mat2(
v2, 0,
2 - u2, 4 * v2
);
vec2 p = to_simple_space * uv_coords;
// Sign takes care of whether we should be filling the inside or outside of curve.
float sgn = orientation * sign(v2);
float Fp = (p.x * p.x - p.y);
if(sgn * Fp < 0){
return 0.0;
}else{
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
}
}
void main() {
if (color.a == 0) discard;
frag_color = color;
if (fill_all == 1.0) return;
frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width);
}

View file

@ -0,0 +1,134 @@
#version 330
layout (triangles) in;
layout (triangle_strip, max_vertices = 5) out;
uniform float anti_alias_width;
// Needed for get_gl_Position
uniform vec2 frame_shape;
uniform float focal_distance;
uniform float is_fixed_in_frame;
// Needed for finalize_color
uniform vec3 light_source_position;
uniform float gloss;
uniform float shadow;
in vec3 bp[3];
in vec3 v_global_unit_normal[3];
in vec4 v_color[3];
in float v_vert_index[3];
out vec4 color;
out float fill_all;
out float uv_anti_alias_width;
out vec3 xyz_coords;
out float orientation;
// uv space is where b0 = (0, 0), b1 = (1, 0), and transform is orthogonal
out vec2 uv_coords;
out vec2 uv_b2;
out float bezier_degree;
// Analog of import for manim only
#INSERT quadratic_bezier_geometry_functions.glsl
#INSERT get_gl_Position.glsl
#INSERT get_unit_normal.glsl
#INSERT finalize_color.glsl
void emit_vertex_wrapper(vec3 point, int index){
color = finalize_color(
v_color[index],
point,
v_global_unit_normal[index],
light_source_position,
gloss,
shadow
);
xyz_coords = point;
gl_Position = get_gl_Position(xyz_coords);
EmitVertex();
}
void emit_simple_triangle(){
for(int i = 0; i < 3; i++){
emit_vertex_wrapper(bp[i], i);
}
EndPrimitive();
}
void emit_pentagon(vec3[3] points, vec3 normal){
vec3 p0 = points[0];
vec3 p1 = points[1];
vec3 p2 = points[2];
// Tangent vectors
vec3 t01 = normalize(p1 - p0);
vec3 t12 = normalize(p2 - p1);
// Vectors perpendicular to the curve in the plane of the curve pointing outside the curve
vec3 p0_perp = cross(t01, normal);
vec3 p2_perp = cross(t12, normal);
bool fill_inside = orientation > 0;
float aaw = anti_alias_width;
vec3 corners[5];
if(fill_inside){
// Note, straight lines will also fall into this case, and since p0_perp and p2_perp
// will point to the right of the curve, it's just what we want
corners = vec3[5](
p0 + aaw * p0_perp,
p0,
p1 + 0.5 * aaw * (p0_perp + p2_perp),
p2,
p2 + aaw * p2_perp
);
}else{
corners = vec3[5](
p0,
p0 - aaw * p0_perp,
p1,
p2 - aaw * p2_perp,
p2
);
}
mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, normal);
uv_b2 = (xyz_to_uv * vec4(p2, 1)).xy;
uv_anti_alias_width = anti_alias_width / length(p1 - p0);
for(int i = 0; i < 5; i++){
vec3 corner = corners[i];
uv_coords = (xyz_to_uv * vec4(corner, 1)).xy;
int j = int(sign(i - 1) + 1); // Maps i = [0, 1, 2, 3, 4] onto j = [0, 0, 1, 2, 2]
emit_vertex_wrapper(corner, j);
}
EndPrimitive();
}
void main(){
// If vert indices are sequential, don't fill all
fill_all = float(
(v_vert_index[1] - v_vert_index[0]) != 1.0 ||
(v_vert_index[2] - v_vert_index[1]) != 1.0
);
if(fill_all == 1.0){
emit_simple_triangle();
return;
}
vec3 new_bp[3];
bezier_degree = get_reduced_control_points(vec3[3](bp[0], bp[1], bp[2]), new_bp);
vec3 local_unit_normal = get_unit_normal(new_bp);
orientation = sign(dot(v_global_unit_normal[0], local_unit_normal));
if(bezier_degree >= 1){
emit_pentagon(new_bp, local_unit_normal);
}
// Don't emit any vertices for bezier_degree 0
}

View file

@ -0,0 +1,23 @@
#version 330
#INSERT camera_uniform_declarations.glsl
in vec3 point;
in vec3 unit_normal;
in vec4 color;
in float vert_index;
out vec3 bp; // Bezier control point
out vec3 v_global_unit_normal;
out vec4 v_color;
out float v_vert_index;
// Analog of import for manim only
#INSERT position_point_into_frame.glsl
void main(){
bp = position_point_into_frame(point);
v_global_unit_normal = rotate_point_into_frame(unit_normal);
v_color = color;
v_vert_index = vert_index;
}

View file

@ -0,0 +1,93 @@
#version 330
#INSERT camera_uniform_declarations.glsl
in vec2 uv_coords;
in vec2 uv_b2;
in float uv_stroke_width;
in vec4 color;
in float uv_anti_alias_width;
in float has_prev;
in float has_next;
in float bevel_start;
in float bevel_end;
in float angle_from_prev;
in float angle_to_next;
in float bezier_degree;
out vec4 frag_color;
float cross2d(vec2 v, vec2 w){
return v.x * w.y - w.x * v.y;
}
float modify_distance_for_endpoints(vec2 p, float dist, float t){
float buff = 0.5 * uv_stroke_width - uv_anti_alias_width;
// Check the beginning of the curve
if(t == 0){
// Clip the start
if(has_prev == 0) return max(dist, -p.x + buff);
// Bevel start
if(bevel_start == 1){
float a = angle_from_prev;
mat2 rot = mat2(
cos(a), sin(a),
-sin(a), cos(a)
);
// Dist for intersection of two lines
float bevel_d = max(abs(p.y), abs((rot * p).y));
// Dist for union of this intersection with the real curve
// intersected with radius 2 away from curve to smooth out
// really sharp corners
return max(min(dist, bevel_d), dist / 2);
}
// Otherwise, start will be rounded off
}else if(t == 1){
// Check the end of the curve
// TODO, too much code repetition
vec2 v21 = (bezier_degree == 2) ? vec2(1, 0) - uv_b2 : vec2(-1, 0);
float len_v21 = length(v21);
if(len_v21 == 0){
v21 = -uv_b2;
len_v21 = length(v21);
}
float perp_dist = dot(p - uv_b2, v21) / len_v21;
if(has_next == 0) return max(dist, -perp_dist + buff);
// Bevel end
if(bevel_end == 1){
float a = -angle_to_next;
mat2 rot = mat2(
cos(a), sin(a),
-sin(a), cos(a)
);
vec2 v21_unit = v21 / length(v21);
float bevel_d = max(
abs(cross2d(p - uv_b2, v21_unit)),
abs(cross2d((rot * (p - uv_b2)), v21_unit))
);
return max(min(dist, bevel_d), dist / 2);
}
// Otherwise, end will be rounded off
}
return dist;
}
#INSERT quadratic_bezier_distance.glsl
void main() {
if (uv_stroke_width == 0) discard;
float dist_to_curve = min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
// An sdf for the region around the curve we wish to color.
float signed_dist = abs(dist_to_curve) - 0.5 * uv_stroke_width;
frag_color = color;
frag_color.a *= smoothstep(0.5, -0.5, signed_dist / uv_anti_alias_width);
}

View file

@ -0,0 +1,272 @@
#version 330
layout (triangles) in;
layout (triangle_strip, max_vertices = 5) out;
// Needed for get_gl_Position
uniform vec2 frame_shape;
uniform float focal_distance;
uniform float is_fixed_in_frame;
uniform float anti_alias_width;
uniform float flat_stroke;
//Needed for lighting
uniform vec3 light_source_position;
uniform float joint_type;
uniform float gloss;
uniform float shadow;
in vec3 bp[3];
in vec3 prev_bp[3];
in vec3 next_bp[3];
in vec3 v_global_unit_normal[3];
in vec4 v_color[3];
in float v_stroke_width[3];
out vec4 color;
out float uv_stroke_width;
out float uv_anti_alias_width;
out float has_prev;
out float has_next;
out float bevel_start;
out float bevel_end;
out float angle_from_prev;
out float angle_to_next;
out float bezier_degree;
out vec2 uv_coords;
out vec2 uv_b2;
// Codes for joint types
const float AUTO_JOINT = 0;
const float ROUND_JOINT = 1;
const float BEVEL_JOINT = 2;
const float MITER_JOINT = 3;
const float PI = 3.141592653;
#INSERT quadratic_bezier_geometry_functions.glsl
#INSERT get_gl_Position.glsl
#INSERT get_unit_normal.glsl
#INSERT finalize_color.glsl
void flatten_points(in vec3[3] points, out vec2[3] flat_points){
for(int i = 0; i < 3; i++){
float sf = perspective_scale_factor(points[i].z, focal_distance);
flat_points[i] = sf * points[i].xy;
}
}
float angle_between_vectors(vec2 v1, vec2 v2){
float v1_norm = length(v1);
float v2_norm = length(v2);
if(v1_norm == 0 || v2_norm == 0) return 0.0;
float dp = dot(v1, v2) / (v1_norm * v2_norm);
float angle = acos(clamp(dp, -1.0, 1.0));
float sn = sign(cross2d(v1, v2));
return sn * angle;
}
bool find_intersection(vec2 p0, vec2 v0, vec2 p1, vec2 v1, out vec2 intersection){
// Find the intersection of a line passing through
// p0 in the direction v0 and one passing through p1 in
// the direction p1.
// That is, find a solutoin to p0 + v0 * t = p1 + v1 * s
float det = -v0.x * v1.y + v1.x * v0.y;
if(det == 0) return false;
float t = cross2d(p0 - p1, v1) / det;
intersection = p0 + v0 * t;
return true;
}
void create_joint(float angle, vec2 unit_tan, float buff,
vec2 static_c0, out vec2 changing_c0,
vec2 static_c1, out vec2 changing_c1){
float shift;
if(abs(angle) < 1e-3){
// No joint
shift = 0;
}else if(joint_type == MITER_JOINT){
shift = buff * (-1.0 - cos(angle)) / sin(angle);
}else{
// For a Bevel joint
shift = buff * (1.0 - cos(angle)) / sin(angle);
}
changing_c0 = static_c0 - shift * unit_tan;
changing_c1 = static_c1 + shift * unit_tan;
}
// This function is responsible for finding the corners of
// a bounding region around the bezier curve, which can be
// emitted as a triangle fan
int get_corners(vec2 controls[3], int degree, float stroke_widths[3], out vec2 corners[5]){
vec2 p0 = controls[0];
vec2 p1 = controls[1];
vec2 p2 = controls[2];
// Unit vectors for directions between control points
vec2 v10 = normalize(p0 - p1);
vec2 v12 = normalize(p2 - p1);
vec2 v01 = -v10;
vec2 v21 = -v12;
vec2 p0_perp = vec2(-v01.y, v01.x); // Pointing to the left of the curve from p0
vec2 p2_perp = vec2(-v12.y, v12.x); // Pointing to the left of the curve from p2
// aaw is the added width given around the polygon for antialiasing.
// In case the normal is faced away from (0, 0, 1), the vector to the
// camera, this is scaled up.
float aaw = anti_alias_width;
float buff0 = 0.5 * stroke_widths[0] + aaw;
float buff2 = 0.5 * stroke_widths[2] + aaw;
float aaw0 = (1 - has_prev) * aaw;
float aaw2 = (1 - has_next) * aaw;
vec2 c0 = p0 - buff0 * p0_perp + aaw0 * v10;
vec2 c1 = p0 + buff0 * p0_perp + aaw0 * v10;
vec2 c2 = p2 + buff2 * p2_perp + aaw2 * v12;
vec2 c3 = p2 - buff2 * p2_perp + aaw2 * v12;
// Account for previous and next control points
if(has_prev > 0) create_joint(angle_from_prev, v01, buff0, c0, c0, c1, c1);
if(has_next > 0) create_joint(angle_to_next, v21, buff2, c3, c3, c2, c2);
// Linear case is the simplest
if(degree == 1){
// The order of corners should be for a triangle_strip. Last entry is a dummy
corners = vec2[5](c0, c1, c3, c2, vec2(0.0));
return 4;
}
// Otherwise, form a pentagon around the curve
float orientation = sign(cross2d(v01, v12)); // Positive for ccw curves
if(orientation > 0) corners = vec2[5](c0, c1, p1, c2, c3);
else corners = vec2[5](c1, c0, p1, c3, c2);
// Replace corner[2] with convex hull point accounting for stroke width
find_intersection(corners[0], v01, corners[4], v21, corners[2]);
return 5;
}
void set_adjascent_info(vec2 c0, vec2 tangent,
int degree,
vec2 adj[3],
out float bevel,
out float angle
){
bool linear_adj = (angle_between_vectors(adj[1] - adj[0], adj[2] - adj[1]) < 1e-3);
angle = angle_between_vectors(c0 - adj[1], tangent);
// Decide on joint type
bool one_linear = (degree == 1 || linear_adj);
bool should_bevel = (
(joint_type == AUTO_JOINT && one_linear) ||
joint_type == BEVEL_JOINT
);
bevel = should_bevel ? 1.0 : 0.0;
}
void find_joint_info(vec2 controls[3], vec2 prev[3], vec2 next[3], int degree){
float tol = 1e-6;
// Made as floats not bools so they can be passed to the frag shader
has_prev = float(distance(prev[2], controls[0]) < tol);
has_next = float(distance(next[0], controls[2]) < tol);
if(bool(has_prev)){
vec2 tangent = controls[1] - controls[0];
set_adjascent_info(
controls[0], tangent, degree, prev,
bevel_start, angle_from_prev
);
}
if(bool(has_next)){
vec2 tangent = controls[1] - controls[2];
set_adjascent_info(
controls[2], tangent, degree, next,
bevel_end, angle_to_next
);
angle_to_next *= -1;
}
}
void main() {
// Convert control points to a standard form if they are linear or null
vec3 controls[3];
vec3 prev[3];
vec3 next[3];
bezier_degree = get_reduced_control_points(vec3[3](bp[0], bp[1], bp[2]), controls);
if(bezier_degree == 0.0) return; // Null curve
int degree = int(bezier_degree);
get_reduced_control_points(vec3[3](prev_bp[0], prev_bp[1], prev_bp[2]), prev);
get_reduced_control_points(vec3[3](next_bp[0], next_bp[1], next_bp[2]), next);
// Adjust stroke width based on distance from the camera
float scaled_strokes[3];
for(int i = 0; i < 3; i++){
float sf = perspective_scale_factor(controls[i].z, focal_distance);
if(bool(flat_stroke)){
vec3 to_cam = normalize(vec3(0.0, 0.0, focal_distance) - controls[i]);
sf *= abs(dot(v_global_unit_normal[i], to_cam));
}
scaled_strokes[i] = v_stroke_width[i] * sf;
}
// Control points are projected to the xy plane before drawing, which in turn
// gets tranlated to a uv plane. The z-coordinate information will be remembered
// by what's sent out to gl_Position, and by how it affects the lighting and stroke width
vec2 flat_controls[3];
vec2 flat_prev[3];
vec2 flat_next[3];
flatten_points(controls, flat_controls);
flatten_points(prev, flat_prev);
flatten_points(next, flat_next);
find_joint_info(flat_controls, flat_prev, flat_next, degree);
// Corners of a bounding region around curve
vec2 corners[5];
int n_corners = get_corners(flat_controls, degree, scaled_strokes, corners);
int index_map[5] = int[5](0, 0, 1, 2, 2);
if(n_corners == 4) index_map[2] = 2;
// Find uv conversion matrix
mat3 xy_to_uv = get_xy_to_uv(flat_controls[0], flat_controls[1]);
float scale_factor = length(flat_controls[1] - flat_controls[0]);
uv_anti_alias_width = anti_alias_width / scale_factor;
uv_b2 = (xy_to_uv * vec3(flat_controls[2], 1.0)).xy;
// Emit each corner
for(int i = 0; i < n_corners; i++){
uv_coords = (xy_to_uv * vec3(corners[i], 1.0)).xy;
uv_stroke_width = scaled_strokes[index_map[i]] / scale_factor;
// Apply some lighting to the color before sending out.
// vec3 xyz_coords = vec3(corners[i], controls[index_map[i]].z);
vec3 xyz_coords = vec3(corners[i], controls[index_map[i]].z);
color = finalize_color(
v_color[index_map[i]],
xyz_coords,
v_global_unit_normal[index_map[i]],
light_source_position,
gloss,
shadow
);
gl_Position = vec4(
get_gl_Position(vec3(corners[i], 0.0)).xy,
get_gl_Position(controls[index_map[i]]).zw
);
EmitVertex();
}
EndPrimitive();
}

View file

@ -0,0 +1,34 @@
#version 330
#INSERT camera_uniform_declarations.glsl
in vec3 point;
in vec3 prev_point;
in vec3 next_point;
in vec3 unit_normal;
in float stroke_width;
in vec4 color;
// Bezier control point
out vec3 bp;
out vec3 prev_bp;
out vec3 next_bp;
out vec3 v_global_unit_normal;
out float v_stroke_width;
out vec4 v_color;
const float STROKE_WIDTH_CONVERSION = 0.01;
#INSERT position_point_into_frame.glsl
void main(){
bp = position_point_into_frame(point);
prev_bp = position_point_into_frame(prev_point);
next_bp = position_point_into_frame(next_point);
v_global_unit_normal = rotate_point_into_frame(unit_normal);
v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width;
v_color = color;
}

View file

@ -0,0 +1,13 @@
#version 330
#INSERT camera_uniform_declarations.glsl
in vec3 point;
// Analog of import for manim only
#INSERT get_gl_Position.glsl
#INSERT position_point_into_frame.glsl
void main(){
gl_Position = get_gl_Position(position_point_into_frame(point));
}

View file

@ -0,0 +1,24 @@
#version 330
uniform vec3 light_source_position;
uniform float gloss;
uniform float shadow;
in vec3 xyz_coords;
in vec3 v_normal;
in vec4 v_color;
out vec4 frag_color;
#INSERT finalize_color.glsl
void main() {
frag_color = finalize_color(
v_color,
xyz_coords,
normalize(v_normal),
light_source_position,
gloss,
shadow
);
}

View file

@ -0,0 +1,23 @@
#version 330
#INSERT camera_uniform_declarations.glsl
in vec3 point;
in vec3 du_point;
in vec3 dv_point;
in vec4 color;
out vec3 xyz_coords;
out vec3 v_normal;
out vec4 v_color;
#INSERT position_point_into_frame.glsl
#INSERT get_gl_Position.glsl
#INSERT get_rotated_surface_unit_normal_vector.glsl
void main(){
xyz_coords = position_point_into_frame(point);
v_normal = get_rotated_surface_unit_normal_vector(point, du_point, dv_point);
v_color = color;
gl_Position = get_gl_Position(xyz_coords);
}

View file

@ -0,0 +1,42 @@
#version 330
uniform sampler2D LightTexture;
uniform sampler2D DarkTexture;
uniform float num_textures;
uniform vec3 light_source_position;
uniform float gloss;
uniform float shadow;
in vec3 xyz_coords;
in vec3 v_normal;
in vec2 v_im_coords;
in float v_opacity;
out vec4 frag_color;
#INSERT finalize_color.glsl
const float dark_shift = 0.2;
void main() {
vec4 color = texture(LightTexture, v_im_coords);
if(num_textures == 2.0){
vec4 dark_color = texture(DarkTexture, v_im_coords);
float dp = dot(
normalize(light_source_position - xyz_coords),
normalize(v_normal)
);
float alpha = smoothstep(-dark_shift, dark_shift, dp);
color = mix(dark_color, color, alpha);
}
frag_color = finalize_color(
color,
xyz_coords,
normalize(v_normal),
light_source_position,
gloss,
shadow
);
frag_color.a = v_opacity;
}

View file

@ -0,0 +1,26 @@
#version 330
#INSERT camera_uniform_declarations.glsl
in vec3 point;
in vec3 du_point;
in vec3 dv_point;
in vec2 im_coords;
in float opacity;
out vec3 xyz_coords;
out vec3 v_normal;
out vec2 v_im_coords;
out float v_opacity;
#INSERT position_point_into_frame.glsl
#INSERT get_gl_Position.glsl
#INSERT get_rotated_surface_unit_normal_vector.glsl
void main(){
xyz_coords = position_point_into_frame(point);
v_normal = get_rotated_surface_unit_normal_vector(point, du_point, dv_point);
v_im_coords = im_coords;
v_opacity = opacity;
gl_Position = get_gl_Position(xyz_coords);
}

View file

@ -0,0 +1,34 @@
#version 330
uniform vec3 light_source_position;
uniform float gloss;
uniform float shadow;
uniform float anti_alias_width;
in vec4 color;
in float radius;
in vec2 center;
in vec2 point;
out vec4 frag_color;
#INSERT finalize_color.glsl
void main() {
vec2 diff = point - center;
float dist = length(diff);
float signed_dist = dist - radius;
if (signed_dist > 0.5 * anti_alias_width){
discard;
}
vec3 normal = vec3(diff / radius, sqrt(1 - (dist * dist) / (radius * radius)));
frag_color = finalize_color(
color,
vec3(point.xy, 0.0),
normal,
light_source_position,
gloss,
shadow
);
frag_color.a *= smoothstep(0.5, -0.5, signed_dist / anti_alias_width);
}

View file

@ -0,0 +1,43 @@
#version 330
layout (points) in;
layout (triangle_strip, max_vertices = 4) out;
// Needed for get_gl_Position
uniform vec2 frame_shape;
uniform float focal_distance;
uniform float is_fixed_in_frame;
uniform float anti_alias_width;
in vec3 v_point[1];
in float v_radius[1];
in vec4 v_color[1];
out vec4 color;
out float radius;
out vec2 center;
out vec2 point;
#INSERT get_gl_Position.glsl
void main() {
color = v_color[0];
radius = v_radius[0];
center = v_point[0].xy;
radius = v_radius[0] / max(1.0 - v_point[0].z / focal_distance / frame_shape.y, 0.0);
float rpa = radius + anti_alias_width;
for(int i = 0; i < 4; i++){
// To account for perspective
int x_index = 2 * (i % 2) - 1;
int y_index = 2 * (i / 2) - 1;
vec3 corner = v_point[0] + vec3(x_index * rpa, y_index * rpa, 0.0);
gl_Position = get_gl_Position(corner);
point = corner.xy;
EmitVertex();
}
EndPrimitive();
}

View file

@ -0,0 +1,19 @@
#version 330
#INSERT camera_uniform_declarations.glsl
in vec3 point;
in float radius;
in vec4 color;
out vec3 v_point;
out float v_radius;
out vec4 v_color;
#INSERT position_point_into_frame.glsl
void main(){
v_point = position_point_into_frame(point);
v_radius = radius;
v_color = color;
}

View file

@ -841,7 +841,7 @@ class Scene(Container):
for t in self.time_progression:
self.update_to_time(t)
if not skip_rendering:
self.renderer.render(self, self.moving_mobjects)
self.renderer.render(self, t, self.moving_mobjects)
if self.stop_condition is not None and self.stop_condition():
self.time_progression.close()
break

View file

@ -24,6 +24,7 @@ __all__ = [
"complex_func_to_R3_func",
"center_of_mass",
"midpoint",
"find_intersection",
"line_intersection",
"get_winding_number",
]
@ -101,6 +102,22 @@ def thick_diagonal(dim, thickness=2):
return (np.abs(row_indices - col_indices) < thickness).astype("uint8")
def rotation_matrix_transpose(angle, axis):
if axis[0] == 0 and axis[1] == 0:
# axis = [0, 0, z] case is common enough it's worth
# having a shortcut
sgn = 1 if axis[2] > 0 else -1
cos_a = math.cos(angle)
sin_a = math.sin(angle) * sgn
return [
[cos_a, sin_a, 0],
[-sin_a, cos_a, 0],
[0, 0, 1],
]
quat = quaternion_from_angle_axis(angle, axis)
return rotation_matrix_transpose_from_quaternion(quat)
def rotation_matrix(angle, axis):
"""
Rotation in R^3 about a specified axis of rotation.
@ -245,6 +262,35 @@ def line_intersection(line1, line2):
return np.array([x, y, 0])
def find_intersection(p0, v0, p1, v1, threshold=1e-5):
"""
Return the intersection of a line passing through p0 in direction v0
with one passing through p1 in direction v1. (Or array of intersections
from arrays of such points/directions).
For 3d values, it returns the point on the ray p0 + v0 * t closest to the
ray p1 + v1 * t
"""
p0 = np.array(p0, ndmin=2)
v0 = np.array(v0, ndmin=2)
p1 = np.array(p1, ndmin=2)
v1 = np.array(v1, ndmin=2)
m, n = np.shape(p0)
assert n in [2, 3]
numer = np.cross(v1, p1 - p0)
denom = np.cross(v1, v0)
if n == 3:
d = len(np.shape(numer))
new_numer = np.multiply(numer, numer).sum(d - 1)
new_denom = np.multiply(denom, numer).sum(d - 1)
numer, denom = new_numer, new_denom
denom[abs(denom) < threshold] = np.inf # So that ratio goes to 0 there
ratio = numer / denom
ratio = np.repeat(ratio, n).reshape((m, n))
return p0 + ratio * v0
def get_winding_number(points):
total_angle = 0
for p1, p2 in adjacent_pairs(points):

521
poetry.lock generated
View file

@ -8,7 +8,7 @@ python-versions = "*"
[[package]]
name = "anyio"
version = "2.0.2"
version = "2.1.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
category = "main"
optional = true
@ -24,7 +24,7 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
curio = ["curio (>=1.4)"]
doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
test = ["coverage (>=4.5)", "hypothesis (>=4.0)", "pytest (>=4.3)", "trustme", "uvloop"]
test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=6.0)", "trustme", "uvloop"]
trio = ["trio (>=0.16)"]
[[package]]
@ -148,7 +148,7 @@ d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
[[package]]
name = "bleach"
version = "3.2.3"
version = "3.3.0"
description = "An easy safelist-based HTML-sanitizing tool."
category = "main"
optional = true
@ -161,7 +161,7 @@ webencodings = "*"
[[package]]
name = "cffi"
version = "1.14.4"
version = "1.14.5"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = true
@ -206,7 +206,7 @@ optional = false
python-versions = "*"
[package.extras]
test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
test = ["flake8 (3.7.8)", "hypothesis (3.55.3)"]
[[package]]
name = "contextvars"
@ -270,6 +270,14 @@ category = "main"
optional = true
python-versions = ">=2.7"
[[package]]
name = "glcontext"
version = "2.3.2"
description = "Portable OpenGL Context"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "grpcio"
version = "1.33.2"
@ -325,12 +333,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "immutables"
version = "0.14"
version = "0.15"
description = "Immutable Collections"
category = "main"
optional = true
python-versions = ">=3.5"
[package.extras]
test = ["flake8 (>=3.8.4,<3.9.0)", "pycodestyle (>=2.6.0,<2.7.0)"]
[[package]]
name = "importlib-metadata"
version = "1.7.0"
@ -436,12 +447,12 @@ python-versions = ">=3.6"
parso = ">=0.8.0,<0.9.0"
[package.extras]
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
qa = ["flake8 (3.8.3)", "mypy (0.782)"]
testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"]
[[package]]
name = "jinja2"
version = "2.11.2"
version = "2.11.3"
description = "A very fast and expressive template engine."
category = "main"
optional = false
@ -503,7 +514,7 @@ test = ["jedi (<=0.17.2)", "ipykernel", "ipython", "mock", "pytest", "pytest-asy
[[package]]
name = "jupyter-core"
version = "4.7.0"
version = "4.7.1"
description = "Jupyter core package. A base package on which Jupyter projects rely."
category = "main"
optional = true
@ -515,7 +526,7 @@ traitlets = "*"
[[package]]
name = "jupyter-server"
version = "1.2.2"
version = "1.3.0"
description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications."
category = "main"
optional = true
@ -542,7 +553,7 @@ test = ["coverage", "requests", "pytest", "pytest-cov", "pytest-tornasync", "pyt
[[package]]
name = "jupyterlab"
version = "3.0.5"
version = "3.0.8"
description = "The JupyterLab server extension."
category = "main"
optional = true
@ -575,7 +586,7 @@ pygments = ">=2.4.1,<3"
[[package]]
name = "jupyterlab-server"
version = "2.1.3"
version = "2.2.1"
description = "JupyterLab Server"
category = "main"
optional = true
@ -611,7 +622,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "manimpango"
version = "0.2.0"
version = "0.2.3"
description = "Bindings for Pango for using with Manim."
category = "main"
optional = false
@ -657,6 +668,53 @@ category = "main"
optional = true
python-versions = "*"
[[package]]
name = "moderngl"
version = "5.6.3"
description = "ModernGL: High performance rendering for Python 3"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
glcontext = ">=2,<3"
[[package]]
name = "moderngl-window"
version = "2.3.0"
description = "A cross platform helper library for ModernGL making window creation and resource loading simple"
category = "main"
optional = false
python-versions = ">=3.5"
[package.dependencies]
moderngl = "<6"
numpy = ">=1.16,<2"
Pillow = ">=5"
pyglet = ">=1.5.8,<2"
pyrr = ">=0.10.3,<1"
[package.extras]
pysdl2 = ["pysdl2"]
pyside2 = ["PySide2 (<6)"]
glfw = ["glfw"]
pygame = ["pygame (2.0.0.dev10)"]
pyqt5 = ["pyqt5"]
pywavefront = ["pywavefront (>=1.2.0,<2)"]
tk = ["pyopengltk (>=0.0.3)"]
trimesh = ["trimesh (>=3.2.6,<4)", "scipy (>=1.3.2)"]
[[package]]
name = "multipledispatch"
version = "0.6.0"
description = "Multiple dispatch"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
six = "*"
[[package]]
name = "mypy-extensions"
version = "0.4.3"
@ -682,11 +740,11 @@ test = ["pytest", "pytest-tornasync", "pytest-console-scripts"]
[[package]]
name = "nbclient"
version = "0.5.1"
version = "0.5.2"
description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor."
category = "main"
optional = true
python-versions = ">=3.6"
python-versions = ">=3.6.1"
[package.dependencies]
async-generator = "*"
@ -724,11 +782,11 @@ testpath = "*"
traitlets = ">=4.2"
[package.extras]
all = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (==0.2.2)", "tornado (>=4.0)", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"]
all = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (0.2.2)", "tornado (>=4.0)", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"]
docs = ["sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"]
serve = ["tornado (>=4.0)"]
test = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (==0.2.2)"]
webpdf = ["pyppeteer (==0.2.2)"]
test = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (0.2.2)"]
webpdf = ["pyppeteer (0.2.2)"]
[[package]]
name = "nbformat"
@ -819,7 +877,7 @@ python-versions = ">=3.6"
[[package]]
name = "packaging"
version = "20.8"
version = "20.9"
description = "Core utilities for Python packages"
category = "main"
optional = false
@ -845,7 +903,7 @@ optional = true
python-versions = ">=3.6"
[package.extras]
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
qa = ["flake8 (3.8.3)", "mypy (0.782)"]
testing = ["docopt", "pytest (<6.0.0)"]
[[package]]
@ -910,7 +968,7 @@ twisted = ["twisted"]
[[package]]
name = "prompt-toolkit"
version = "3.0.14"
version = "3.0.16"
description = "Library for building powerful interactive command lines in Python"
category = "main"
optional = true
@ -970,9 +1028,17 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pyglet"
version = "1.5.15"
description = "Cross-platform windowing and multimedia library"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pygments"
version = "2.7.4"
version = "2.8.0"
description = "Pygments is a syntax highlighting package written in Python."
category = "main"
optional = false
@ -980,14 +1046,14 @@ python-versions = ">=3.5"
[[package]]
name = "pylint"
version = "2.6.0"
version = "2.6.2"
description = "python code static checker"
category = "dev"
optional = false
python-versions = ">=3.5.*"
[package.dependencies]
astroid = ">=2.4.0,<=2.5"
astroid = ">=2.4.0,<2.5"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
isort = ">=4.2.5,<6"
mccabe = ">=0.6,<0.7"
@ -1001,6 +1067,18 @@ category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "pyrr"
version = "0.10.3"
description = "3D mathematical functions using NumPy"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
multipledispatch = "*"
numpy = "*"
[[package]]
name = "pyrsistent"
version = "0.17.3"
@ -1044,7 +1122,7 @@ six = ">=1.5"
[[package]]
name = "pytz"
version = "2020.5"
version = "2021.1"
description = "World timezone definitions, modern and historical"
category = "main"
optional = false
@ -1068,7 +1146,7 @@ python-versions = "*"
[[package]]
name = "pyzmq"
version = "22.0.0"
version = "22.0.3"
description = "Python bindings for 0MQ"
category = "main"
optional = true
@ -1109,7 +1187,7 @@ python-versions = "*"
[package.extras]
security = ["cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)"]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
[[package]]
name = "rich"
@ -1177,7 +1255,7 @@ python-versions = "*"
[[package]]
name = "sphinx"
version = "3.4.3"
version = "3.5.1"
description = "Python documentation generator"
category = "dev"
optional = false
@ -1203,7 +1281,7 @@ sphinxcontrib-serializinghtml = "*"
[package.extras]
docs = ["sphinxcontrib-websupport"]
lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.790)", "docutils-stubs"]
lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"]
test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"]
[[package]]
@ -1319,7 +1397,7 @@ python-versions = ">= 3.5"
[[package]]
name = "tqdm"
version = "4.56.0"
version = "4.57.0"
description = "Fast, Extensible Progress Meter"
category = "main"
optional = false
@ -1363,7 +1441,7 @@ python-versions = "*"
[[package]]
name = "watchdog"
version = "1.0.2"
version = "2.0.1"
description = "Filesystem events monitoring"
category = "main"
optional = true
@ -1406,16 +1484,16 @@ python-versions = ">=3.6"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
[extras]
webgl_renderer = ["grpcio", "grpcio-tools", "watchdog"]
jupyterlab = ["jupyterlab"]
webgl_renderer = ["grpcio", "grpcio-tools", "watchdog"]
[metadata]
lock-version = "1.1"
python-versions = "^3.6.2"
content-hash = "21918cc04dffbf25919dbe7fd088f4f18984ee59ce778cc5ce8f3992cb0739f1"
content-hash = "0396636548f0b80f59e84ff57b7b9e33dfc4330726c4a5e818760ead768bf58d"
[metadata.files]
alabaster = [
@ -1423,8 +1501,8 @@ alabaster = [
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
]
anyio = [
{file = "anyio-2.0.2-py3-none-any.whl", hash = "sha256:01cce0087b8fd8b6b7e629dc11505dcde02f916ce903332892cb2ae9817b597d"},
{file = "anyio-2.0.2.tar.gz", hash = "sha256:35075abd32cf20fd7e0be2fee3614e80b92d5392eba257c8d2f33de3df7ca237"},
{file = "anyio-2.1.0-py3-none-any.whl", hash = "sha256:c286818ccd5dcbd5d385b223f16a055393474527b1d5650da489828a9887d559"},
{file = "anyio-2.1.0.tar.gz", hash = "sha256:8a56e08623dc55955a06719d4ad62de6009bb3f1dd04936e60b2104dd58da484"},
]
appdirs = [
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
@ -1482,46 +1560,47 @@ black = [
{file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
]
bleach = [
{file = "bleach-3.2.3-py2.py3-none-any.whl", hash = "sha256:2d3b3f7e7d69148bb683b26a3f21eabcf62fa8fb7bc75d0e7a13bcecd9568d4d"},
{file = "bleach-3.2.3.tar.gz", hash = "sha256:c6ad42174219b64848e2e2cd434e44f56cd24a93a9b4f8bc52cfed55a1cd5aad"},
{file = "bleach-3.3.0-py2.py3-none-any.whl", hash = "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125"},
{file = "bleach-3.3.0.tar.gz", hash = "sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433"},
]
cffi = [
{file = "cffi-1.14.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775"},
{file = "cffi-1.14.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06"},
{file = "cffi-1.14.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26"},
{file = "cffi-1.14.4-cp27-cp27m-win32.whl", hash = "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c"},
{file = "cffi-1.14.4-cp27-cp27m-win_amd64.whl", hash = "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b"},
{file = "cffi-1.14.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d"},
{file = "cffi-1.14.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca"},
{file = "cffi-1.14.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698"},
{file = "cffi-1.14.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b"},
{file = "cffi-1.14.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293"},
{file = "cffi-1.14.4-cp35-cp35m-win32.whl", hash = "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2"},
{file = "cffi-1.14.4-cp35-cp35m-win_amd64.whl", hash = "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7"},
{file = "cffi-1.14.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"},
{file = "cffi-1.14.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362"},
{file = "cffi-1.14.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec"},
{file = "cffi-1.14.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b"},
{file = "cffi-1.14.4-cp36-cp36m-win32.whl", hash = "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668"},
{file = "cffi-1.14.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009"},
{file = "cffi-1.14.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb"},
{file = "cffi-1.14.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d"},
{file = "cffi-1.14.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03"},
{file = "cffi-1.14.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01"},
{file = "cffi-1.14.4-cp37-cp37m-win32.whl", hash = "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e"},
{file = "cffi-1.14.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35"},
{file = "cffi-1.14.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d"},
{file = "cffi-1.14.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b"},
{file = "cffi-1.14.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53"},
{file = "cffi-1.14.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e"},
{file = "cffi-1.14.4-cp38-cp38-win32.whl", hash = "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d"},
{file = "cffi-1.14.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375"},
{file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"},
{file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"},
{file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"},
{file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"},
{file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"},
{file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"},
{file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"},
{file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"},
{file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"},
{file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"},
{file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"},
{file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"},
{file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"},
{file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"},
{file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"},
{file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"},
{file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"},
{file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"},
{file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"},
{file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"},
{file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"},
{file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"},
{file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"},
{file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"},
{file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"},
{file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"},
{file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"},
{file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"},
{file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"},
{file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"},
{file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"},
{file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"},
{file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"},
{file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"},
{file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"},
{file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"},
{file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"},
{file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"},
{file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"},
{file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"},
{file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"},
{file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"},
{file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"},
]
click = [
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
@ -1566,6 +1645,32 @@ entrypoints = [
{file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
{file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
]
glcontext = [
{file = "glcontext-2.3.2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:8b92a3bcca1cd0be5fa6add8c3feb723747a160b372523c6f720485c7648838d"},
{file = "glcontext-2.3.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3c62af1f972f97565deac6d08230bcd3ce09e6ab580b822eebe30dd9c902d19b"},
{file = "glcontext-2.3.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:771c36d0a2fc03b94130f46ff996e0f607c72b54fcba72fb7493cf2dd18fe46a"},
{file = "glcontext-2.3.2-cp35-cp35m-win32.whl", hash = "sha256:fdf3ae07f8de29b7fa9c6006b5bc6b8fc8e508455574405473f680ecba8272b7"},
{file = "glcontext-2.3.2-cp35-cp35m-win_amd64.whl", hash = "sha256:eb35d324239cc42df402b8716f0aa0d7f30d68b895f183f4d4a3f424b3b9579b"},
{file = "glcontext-2.3.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:17cf58e4a70897da8a99d72044cf1437e2fd232d4e3af599e018e858e27df6d7"},
{file = "glcontext-2.3.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:470474a7b2e963af284c07116dd9b3f7fab0a5195da308f33599fcad1ef5de7f"},
{file = "glcontext-2.3.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:842492c39ec7fc87f982300def79751dab7888b985c6f1dab13b5937f56f5a0d"},
{file = "glcontext-2.3.2-cp36-cp36m-win32.whl", hash = "sha256:b59ec600fbdd7bffeb22ac96fd1639980fe08a41a5935c9deb0c2012cdd190da"},
{file = "glcontext-2.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:4385d2d6626a7f3d33b06eefe5b506bb1c4afc2b2996b9ac8c5ff32d7de2b432"},
{file = "glcontext-2.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ecea09f612eed473b5eec2f56b68e3278ce683e7220b2895e5864b6c4ccef87f"},
{file = "glcontext-2.3.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:59bf670a5899998ee7190bc5130da025512c765b36b89dcb06477535f67ae882"},
{file = "glcontext-2.3.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eb83051e4246160c1f60943b74accb5fe91835022deb753ffc18e3087d82a011"},
{file = "glcontext-2.3.2-cp37-cp37m-win32.whl", hash = "sha256:6257b0319bf05859630c117550d46b71973b591e863bd8b1eb6faec0f51199fe"},
{file = "glcontext-2.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7804bc3ef37ba0d35845e02e5e428713857b181bd2826ffc48b863613e40649b"},
{file = "glcontext-2.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eae3ff044064f2b6b5a6023afbccd6751aa6b1c1896dd4bb411e36cc8f64c143"},
{file = "glcontext-2.3.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:01632bc0c33e71f57801daacb0f3e552a01a426e1e9cd45dadd969bc66fc0c9c"},
{file = "glcontext-2.3.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e34b8d6f0598d27ce02fb1b535d37827b37259ddc811ec689091d68e9f8df054"},
{file = "glcontext-2.3.2-cp38-cp38-win32.whl", hash = "sha256:beeb8aa834773cc3dc1c37809841cfb26be16dfd2dedffdf76a246fb9c92de51"},
{file = "glcontext-2.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:dad729ebbb870a725aba7fba75589d3b90e1b495b8d4b4533721b8ea798898f8"},
{file = "glcontext-2.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54324b5b5486ca53f83a64410939c08549ee5253845675131b54e57c8f9bc11a"},
{file = "glcontext-2.3.2-cp39-cp39-win32.whl", hash = "sha256:44f8c5a07add0b90a46b10e3f328731f51d7ac973047dfada6e0338d3bf3f77e"},
{file = "glcontext-2.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:950bdbfad1162ecaee03cdd070ab02b0e0ce4a9efe03708f706bd3c5985f29f7"},
{file = "glcontext-2.3.2.tar.gz", hash = "sha256:f716c05689ddf911afe68c7e7e3ac2b283e40a184031d81055141d310f07235e"},
]
grpcio = [
{file = "grpcio-1.33.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c5030be8a60fb18de1fc8d93d130d57e4296c02f229200df814f6578da00429e"},
{file = "grpcio-1.33.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5b21d3de520a699cb631cfd3a773a57debeb36b131be366bf832153405cc5404"},
@ -1674,18 +1779,21 @@ imagesize = [
{file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
]
immutables = [
{file = "immutables-0.14-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:860666fab142401a5535bf65cbd607b46bc5ed25b9d1eb053ca8ed9a1a1a80d6"},
{file = "immutables-0.14-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ce01788878827c3f0331c254a4ad8d9721489a5e65cc43e19c80040b46e0d297"},
{file = "immutables-0.14-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:8797eed4042f4626b0bc04d9cf134208918eb0c937a8193a2c66df5041e62d2e"},
{file = "immutables-0.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:33ce2f977da7b5e0dddd93744862404bdb316ffe5853ec853e53141508fa2e6a"},
{file = "immutables-0.14-cp36-cp36m-win_amd64.whl", hash = "sha256:6c8eace4d98988c72bcb37c05e79aae756832738305ae9497670482a82db08bc"},
{file = "immutables-0.14-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ab6c18b7b2b2abc83e0edc57b0a38bf0915b271582a1eb8c7bed1c20398f8040"},
{file = "immutables-0.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c099212fd6504513a50e7369fe281007c820cf9d7bb22a336486c63d77d6f0b2"},
{file = "immutables-0.14-cp37-cp37m-win_amd64.whl", hash = "sha256:714aedbdeba4439d91cb5e5735cb10631fc47a7a69ea9cc8ecbac90322d50a4a"},
{file = "immutables-0.14-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:1c11050c49e193a1ec9dda1747285333f6ba6a30bbeb2929000b9b1192097ec0"},
{file = "immutables-0.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c453e12b95e1d6bb4909e8743f88b7f5c0c97b86a8bc0d73507091cb644e3c1e"},
{file = "immutables-0.14-cp38-cp38-win_amd64.whl", hash = "sha256:ef9da20ec0f1c5853b5c8f8e3d9e1e15b8d98c259de4b7515d789a606af8745e"},
{file = "immutables-0.14.tar.gz", hash = "sha256:a0a1cc238b678455145bae291d8426f732f5255537ed6a5b7645949704c70a78"},
{file = "immutables-0.15-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:6728f4392e3e8e64b593a5a0cd910a1278f07f879795517e09f308daed138631"},
{file = "immutables-0.15-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f0836cd3bdc37c8a77b192bbe5f41dbcc3ce654db048ebbba89bdfe6db7a1c7a"},
{file = "immutables-0.15-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:8703d8abfd8687932f2a05f38e7de270c3a6ca3bd1c1efb3c938656b3f2f985a"},
{file = "immutables-0.15-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b8ad986f9b532c026f19585289384b0769188fcb68b37c7f0bd0df9092a6ca54"},
{file = "immutables-0.15-cp36-cp36m-win_amd64.whl", hash = "sha256:6f117d9206165b9dab8fd81c5129db757d1a044953f438654236ed9a7a4224ae"},
{file = "immutables-0.15-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b75ade826920c4e490b1bb14cf967ac14e61eb7c5562161c5d7337d61962c226"},
{file = "immutables-0.15-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b7e13c061785e34f73c4f659861f1b3e4a5fd918e4395c84b21c4e3d449ebe27"},
{file = "immutables-0.15-cp37-cp37m-win_amd64.whl", hash = "sha256:3035849accee4f4e510ed7c94366a40e0f5fef9069fbe04a35f4787b13610a4a"},
{file = "immutables-0.15-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:b04fa69174e0c8f815f9c55f2a43fc9e5a68452fab459a08e904a74e8471639f"},
{file = "immutables-0.15-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:141c2e9ea515a3a815007a429f0b47a578ebeb42c831edaec882a245a35fffca"},
{file = "immutables-0.15-cp38-cp38-win_amd64.whl", hash = "sha256:cbe8c64640637faa5535d539421b293327f119c31507c33ca880bd4f16035eb6"},
{file = "immutables-0.15-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:a0a4e4417d5ef4812d7f99470cd39347b58cb927365dd2b8da9161040d260db0"},
{file = "immutables-0.15-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3b15c08c71c59e5b7c2470ef949d49ff9f4263bb77f488422eaa157da84d6999"},
{file = "immutables-0.15-cp39-cp39-win_amd64.whl", hash = "sha256:2283a93c151566e6830aee0e5bee55fc273455503b43aa004356b50f9182092b"},
{file = "immutables-0.15.tar.gz", hash = "sha256:3713ab1ebbb6946b7ce1387bb9d1d7f5e09c45add58c2a2ee65f963c171e746b"},
]
importlib-metadata = [
{file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
@ -1716,8 +1824,8 @@ jedi = [
{file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"},
]
jinja2 = [
{file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
{file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
{file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"},
{file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"},
]
json5 = [
{file = "json5-0.9.5-py2.py3-none-any.whl", hash = "sha256:af1a1b9a2850c7f62c23fde18be4749b3599fd302f494eebf957e2ada6b9e42c"},
@ -1732,24 +1840,24 @@ jupyter-client = [
{file = "jupyter_client-6.1.11.tar.gz", hash = "sha256:649ca3aca1e28f27d73ef15868a7c7f10d6e70f761514582accec3ca6bb13085"},
]
jupyter-core = [
{file = "jupyter_core-4.7.0-py3-none-any.whl", hash = "sha256:0a451c9b295e4db772bdd8d06f2f1eb31caeec0e81fbb77ba37d4a3024e3b315"},
{file = "jupyter_core-4.7.0.tar.gz", hash = "sha256:aa1f9496ab3abe72da4efe0daab0cb2233997914581f9a071e07498c6add8ed3"},
{file = "jupyter_core-4.7.1-py3-none-any.whl", hash = "sha256:8c6c0cac5c1b563622ad49321d5ec47017bd18b94facb381c6973a0486395f8e"},
{file = "jupyter_core-4.7.1.tar.gz", hash = "sha256:79025cb3225efcd36847d0840f3fc672c0abd7afd0de83ba8a1d3837619122b4"},
]
jupyter-server = [
{file = "jupyter_server-1.2.2-py3-none-any.whl", hash = "sha256:49fd3f9f6f4e866c2b8d7494baa2b6e6a7e44236006e443f2c04c407f7f55918"},
{file = "jupyter_server-1.2.2.tar.gz", hash = "sha256:26a98cd5c45b8ebd1e10215586c350a8fa3ca2971e757ee6bf517a180f9933ae"},
{file = "jupyter_server-1.3.0-py3-none-any.whl", hash = "sha256:ae992e84f9ce38af804b5487e44ec5fd10f60d0d4c79a40eb46d66697b7cea89"},
{file = "jupyter_server-1.3.0.tar.gz", hash = "sha256:b9d32d102df25f66ec3c1fe508c62cd0c856123452d973741da84fee4be01912"},
]
jupyterlab = [
{file = "jupyterlab-3.0.5-py3-none-any.whl", hash = "sha256:ad6337a3fc86e9b2a1c29fca82dfd49a75148ca28b695c94962d7808d968f64d"},
{file = "jupyterlab-3.0.5.tar.gz", hash = "sha256:ea75d43d9a054e9192b78ae1eefa72270818d1d787ec21f19db1a92d5cc8db35"},
{file = "jupyterlab-3.0.8-py3-none-any.whl", hash = "sha256:50da506d8881fb9137928341f8d7509d5d4110c94bed43116e14ec10de9e9e60"},
{file = "jupyterlab-3.0.8.tar.gz", hash = "sha256:e2250dec8042bb824d9dd5381f38b2b34eff7f93bed693bd486b252abafddcbe"},
]
jupyterlab-pygments = [
{file = "jupyterlab_pygments-0.1.2-py2.py3-none-any.whl", hash = "sha256:abfb880fd1561987efaefcb2d2ac75145d2a5d0139b1876d5be806e32f630008"},
{file = "jupyterlab_pygments-0.1.2.tar.gz", hash = "sha256:cfcda0873626150932f438eccf0f8bf22bfa92345b814890ab360d666b254146"},
]
jupyterlab-server = [
{file = "jupyterlab_server-2.1.3-py3-none-any.whl", hash = "sha256:9d459d5aba43e626f41cce76d9d00c025e4591fa85feee2d36670295ed1a51fa"},
{file = "jupyterlab_server-2.1.3.tar.gz", hash = "sha256:2af96b04bacf49a17bd2abdd461a219ab62724c39aea2d39ba95ded4be9a171a"},
{file = "jupyterlab_server-2.2.1-py3-none-any.whl", hash = "sha256:b647c532bbb45dad83b1dcd18e9b8cd9a22a68f799811cbca35616b3e8d27acc"},
{file = "jupyterlab_server-2.2.1.tar.gz", hash = "sha256:8b619ec5e13c2d1ac2e3a43a8147382f41fb17b425b50fa38b1cc84849c7bf94"},
]
kiwisolver = [
{file = "kiwisolver-1.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9"},
@ -1809,31 +1917,31 @@ lazy-object-proxy = [
{file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"},
]
manimpango = [
{file = "manimpango-0.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fcabfd409387e80e518cf7d62ec09032f8f89b01038f84754b7c41d847a1e689"},
{file = "manimpango-0.2.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:73f247d2931d2f0546cb81061b7bd0e6374ba77adac3083070bea6549577e22f"},
{file = "manimpango-0.2.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:f06e6a39770df7bb33e3014b3d5981c73be4a3ad9de1c4fc16a04f2f55b912f9"},
{file = "manimpango-0.2.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:92b0af8ef1fb346d93b3113789db43bbe822f128b0509cabf54d1c5e805336b8"},
{file = "manimpango-0.2.0-cp36-cp36m-win32.whl", hash = "sha256:4ddd1eb5f2c461092500a234a9a9c7fc5d8e5171ef7b81333e4eb46ae3932ccb"},
{file = "manimpango-0.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4070bc4a5fe25607ea5ecefd2d546b65e708c16accc3ed2c04ad2d65c5f5e8ea"},
{file = "manimpango-0.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1cac244fa1bc272ea44253b1a21398ba4e98bf87b5c01ef477d60ebdd904693c"},
{file = "manimpango-0.2.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:5e0b06bfa803ea72e30b838a7ca1b1a5411af69e4d7dd987794b15c1bfdbe7c4"},
{file = "manimpango-0.2.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:c5d70804b5941cd2d1282ccfaaae315cad20cee0e03cd2131171294ed3acdec3"},
{file = "manimpango-0.2.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:fca6b73c4695432643169b8df755779787d7455a7bcdb4ac81675ed87b6f0977"},
{file = "manimpango-0.2.0-cp37-cp37m-win32.whl", hash = "sha256:c37cf70be4f906be7d1a229d11dfe939c234115637a1ef932a398f453d223190"},
{file = "manimpango-0.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b52f87c81197f078f69a71bc63568e78af759d9b87e67d2e3548f46514e4272b"},
{file = "manimpango-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f560ee9b91f03ed9d2f36a41048f43357966f72ae8c1d5afeadaa54afcdfdf05"},
{file = "manimpango-0.2.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:445453de3b82ee3f9954c477e7374d3f7a092eea1b5b2826385a687dfc6a9e94"},
{file = "manimpango-0.2.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:9220f18da18183a9c53fb5856d4ce5612a48658efc17ccc65ea93dcc77d679e0"},
{file = "manimpango-0.2.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:95a970e9c4cb8a8934ffdb6c2df5bfcfecaf34cdf51745bbaca50e279d5ad345"},
{file = "manimpango-0.2.0-cp38-cp38-win32.whl", hash = "sha256:4c8a9fbc7803010855d80ad55f2ce68d6d87edeae889861cca392ba6c1d2ddfe"},
{file = "manimpango-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:5db533f09d658ec70c10a83aa89dce59218595e1a0009a8b013e4e50cf77d2c1"},
{file = "manimpango-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0f7cef0734da69549f26408a9ca619452fae89bbad8c517ef799166724a15aa3"},
{file = "manimpango-0.2.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7a0f55389a52a83ea1ac0b58655544c1678f42b62fa355191a7c8645fd189927"},
{file = "manimpango-0.2.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:677501ce0131aaacca42cce5bda2c0d2ffa2ac01e54f21b4b5f090608e9305fd"},
{file = "manimpango-0.2.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:c7022961086d8c4aa701e1d1c98800c3a91fa26894c10b5bfebf5214c722a993"},
{file = "manimpango-0.2.0-cp39-cp39-win32.whl", hash = "sha256:5303ff354023f2647aa7b0ca1bdc11b913aad45705e8a81a75c219b3af3c9443"},
{file = "manimpango-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:39850bf35577f54eef607e60bb0f1abd1d4041c59d04ccef2e1a935eead0c046"},
{file = "manimpango-0.2.0.tar.gz", hash = "sha256:78c827856932b51d7fda30c2c7dbb30431310c38e58a951925db94e9721df558"},
{file = "ManimPango-0.2.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d35c2f3708aa0ffb4790ae46599be5402393cbd5336a2995c3fa2c2448007bb"},
{file = "ManimPango-0.2.3-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:81d95566af85e85840269299244ff4c8bc0b9f9346a1323e782d9a42ec0837e9"},
{file = "ManimPango-0.2.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:803cabe0e861b901803b488157d110b055fb6f4ab9f39f72c80dafaab7e04329"},
{file = "ManimPango-0.2.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:244c97e5a8fc9b0cdce6205f4f08675ea0d74d98d70af81025d1670633d46e12"},
{file = "ManimPango-0.2.3-cp36-cp36m-win32.whl", hash = "sha256:890bc0fb0f9459702faca23c9182ac9b69a7cee00038367385980eeb22ec19e5"},
{file = "ManimPango-0.2.3-cp36-cp36m-win_amd64.whl", hash = "sha256:25ea6c6122fa098af0fdf24642468aa615fd82d468abdfd2238c08dc574f0421"},
{file = "ManimPango-0.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc1299e166e10361bb42003e54ef22713d52aea14a30fbac42184e803a4f8839"},
{file = "ManimPango-0.2.3-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ed9509ac237f0f45f2456b07573ed3f5a34dd9431f0b643cd876b429d992e3e0"},
{file = "ManimPango-0.2.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ec1df0e89f794f6c48e6c89f16b7999aa748898ddaa884447bdd0fd2024014cc"},
{file = "ManimPango-0.2.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:470dbec83eb3ebdc9ca969bac5614a0656f9262bc06baf97b631d8ab812c891b"},
{file = "ManimPango-0.2.3-cp37-cp37m-win32.whl", hash = "sha256:b3191f458b978d3da3cecc1eac9267d4e7e33be209c6c38c5b6c35370eec079e"},
{file = "ManimPango-0.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:bb0355cd4653d1cbbc14c8d499929718e73ce0124e0ee71efdc07781b5f89f1a"},
{file = "ManimPango-0.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7737bf65eb8e524a7937ecbdb77cd5c43918d8813b8b546c558d94adfe1a35d3"},
{file = "ManimPango-0.2.3-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:43b008b6a40bf8838ffd0bfd35643b7870731e58b7d7bde00e9d75a5f404effe"},
{file = "ManimPango-0.2.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:1a946655fbe7668a5cdc05101b2dee9c21cdeb5efd30ce80a20a4d7f9d2213f1"},
{file = "ManimPango-0.2.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:5a3f34d3e90cbc3c2e9b484706df8e8408adb600c41abd396ed8bc159555fe9e"},
{file = "ManimPango-0.2.3-cp38-cp38-win32.whl", hash = "sha256:e6a42c26dcc9ceb69fe3928144e6e0adcd1eaadd2940f01b45bd523126d3ac2a"},
{file = "ManimPango-0.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:17061f2aae5d52ecf6d0ade23d4f6ccd8d8bd17069a55b18d5f9b52e48214354"},
{file = "ManimPango-0.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c8fead8edac9dfc3529c58f292e7d9edbffe5862ff006435df2fc3952500f273"},
{file = "ManimPango-0.2.3-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:172b19bda5fa855a2d72ac25d070df24d2c446cee27396ae62c7fd939ba45c51"},
{file = "ManimPango-0.2.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:df835d47ea19bf1091f80336a8d6d436ce1c0aab1760f601d2501b37e5ed98d8"},
{file = "ManimPango-0.2.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:67f3eb4bd4d5f947eb38d9502a38a036b7ebe48b1f8ef2f7a744fe63c0a37187"},
{file = "ManimPango-0.2.3-cp39-cp39-win32.whl", hash = "sha256:8a603f3ee8eaa1bb87f67bd8ad4bc8b2e7e9b61d34601c16ecbc090d449a2358"},
{file = "ManimPango-0.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:515649c1dd574121efa2cb3654a3fef8491bd2c4fafdd4176edda41d0720ea49"},
{file = "ManimPango-0.2.3.tar.gz", hash = "sha256:6fe4fe0a8623b52de96393e9b2275cce7734ff92e674391e6a10baac84abf4de"},
]
markupsafe = [
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
@ -1905,6 +2013,40 @@ mistune = [
{file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"},
{file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
]
moderngl = [
{file = "moderngl-5.6.3-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:19c90bbbc007523eade0b3cdfa0f0c37b0e0f6f81c61b583b444814f7aedcd51"},
{file = "moderngl-5.6.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c5d664ed88e6955a76dd8f928d47e1d9f311a6bf704dad7b7b35461e0a984549"},
{file = "moderngl-5.6.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:5ef6a239f5032ea853d854139581f77f2d41d4385f8a5bd556303e439cd87595"},
{file = "moderngl-5.6.3-cp35-cp35m-win32.whl", hash = "sha256:e8f09e1cb64f4289d50529d78ac475e9722347222db3d9daba5f909514d60cc2"},
{file = "moderngl-5.6.3-cp35-cp35m-win_amd64.whl", hash = "sha256:66d12d9ba34e29a9c95f2d68276fbb43d2c402bc9b13fb24bcc4c20efb05042d"},
{file = "moderngl-5.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:35cde6729a3064ebb7f5ee648f550ecc804afd5c90ec6a5268dbfb2c976d8e73"},
{file = "moderngl-5.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b3e6c11afb98380d4c29c4b186a555dc622a445ba692e9a7826252c4d4af4f63"},
{file = "moderngl-5.6.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5030d771d98f90bbf9407a41cae9af91d4a0aa17571fea9b8f89a3d165dd0583"},
{file = "moderngl-5.6.3-cp36-cp36m-win32.whl", hash = "sha256:06a23be54b4a074376ab1cb07f01ff8e69049ef42ce6e2307835dfd7cb2173d1"},
{file = "moderngl-5.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:93f533f589b7e2187e6f5f25df70da79ede9d842d366c4f1d1c1fb4900a7424d"},
{file = "moderngl-5.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46ebbb16a30587ba6e736b0e615e9df72ecd5f6c21396ccb69c3c8f9d42391c8"},
{file = "moderngl-5.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:8d03b92605ba2ee8f479d17783dcc9f9b6ffd07139cbc628c9de04e08b1d5866"},
{file = "moderngl-5.6.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f17e8ed6c51629abceadd1229303df72273254ac2099e013b694c32250d36fad"},
{file = "moderngl-5.6.3-cp37-cp37m-win32.whl", hash = "sha256:098440c892dbb18fb15bfe10a6397383cb13261ac70034112269484af7e13a9e"},
{file = "moderngl-5.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6bb07e8d0f53a430a2704946567329d31787792f157624bdbcdb20c78475cba3"},
{file = "moderngl-5.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7be625b4cf47eb5c64ddfb13fb494e0b81358fb15c8494507dda167386ac176d"},
{file = "moderngl-5.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:505da992031932c39c4782d83b58b504f6b20a39e99b466fa52aed75d637415c"},
{file = "moderngl-5.6.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6110a341d14135ca26674d2105306cf4e3723e0acc1a56dde546dfba70f89f9a"},
{file = "moderngl-5.6.3-cp38-cp38-win32.whl", hash = "sha256:4245d74687fd271f609186e8584cb5a9dec50d43ed2d383753fddddbce4e9453"},
{file = "moderngl-5.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:de314e698ba9535f6916a5f8762e5361291c53625aad62fb12aba56ef211f230"},
{file = "moderngl-5.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1684e8088a6fdd4b3c10b65a430f7b216f83f230a13cd74bdbc2946c11827b35"},
{file = "moderngl-5.6.3-cp39-cp39-win32.whl", hash = "sha256:f2619fc24585322a101e07cc3442ebbbaaa57fc014ffe656cd7ef2cf36d25f72"},
{file = "moderngl-5.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:1ca62e287cd930eb70a560044446a907b4ed724624819f72b28de801c9b29f37"},
{file = "moderngl-5.6.3.tar.gz", hash = "sha256:5b8c5f4032c17e80daf11e0366ca9dec88e94c5a3f24527400d61f933abe8f5c"},
]
moderngl-window = [
{file = "moderngl_window-2.3.0-py3-none-any.whl", hash = "sha256:79056e6b6a1e8c540031166d2ec308a40806baf461e5d492730966c3cf372a31"},
]
multipledispatch = [
{file = "multipledispatch-0.6.0-py2-none-any.whl", hash = "sha256:407e6d8c5fa27075968ba07c4db3ef5f02bea4e871e959570eeb69ee39a6565b"},
{file = "multipledispatch-0.6.0-py3-none-any.whl", hash = "sha256:a55c512128fb3f7c2efd2533f2550accb93c35f1045242ef74645fc92a2c3cba"},
{file = "multipledispatch-0.6.0.tar.gz", hash = "sha256:a7ab1451fd0bf9b92cab3edbd7b205622fb767aeefb4fb536c2e3de9e0a38bea"},
]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
@ -1914,8 +2056,8 @@ nbclassic = [
{file = "nbclassic-0.2.6.tar.gz", hash = "sha256:b649436ff85dc731ba8115deef089e5abbe827d7a6dccbad42c15b8d427104e8"},
]
nbclient = [
{file = "nbclient-0.5.1-py3-none-any.whl", hash = "sha256:4d6b116187c795c99b9dba13d46e764d596574b14c296d60670c8dfe454db364"},
{file = "nbclient-0.5.1.tar.gz", hash = "sha256:01e2d726d16eaf2cde6db74a87e2451453547e8832d142f73f72fddcd4fe0250"},
{file = "nbclient-0.5.2-py3-none-any.whl", hash = "sha256:1e0375490cd33fda6c23e61084476298a87f34d02607a038aa8ecc6e8901615f"},
{file = "nbclient-0.5.2.tar.gz", hash = "sha256:0ed6e5700ad18818030a3a5f0f201408c5972d8e38793840cd9339488fd9f7c4"},
]
nbconvert = [
{file = "nbconvert-6.0.7-py3-none-any.whl", hash = "sha256:39e9f977920b203baea0be67eea59f7b37a761caa542abe80f5897ce3cf6311d"},
@ -1974,8 +2116,8 @@ numpy = [
{file = "numpy-1.19.5.zip", hash = "sha256:a76f502430dd98d7546e1ea2250a7360c065a5fdea52b2dffe8ae7180909b6f4"},
]
packaging = [
{file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"},
{file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"},
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
]
pandocfilters = [
{file = "pandocfilters-1.4.3.tar.gz", hash = "sha256:bc63fbb50534b4b1f8ebe1860889289e8af94a23bff7445259592df25a3906eb"},
@ -2039,8 +2181,8 @@ prometheus-client = [
{file = "prometheus_client-0.9.0.tar.gz", hash = "sha256:9da7b32f02439d8c04f7777021c304ed51d9ec180604700c1ba72a4d44dceb03"},
]
prompt-toolkit = [
{file = "prompt_toolkit-3.0.14-py3-none-any.whl", hash = "sha256:c96b30925025a7635471dc083ffb6af0cc67482a00611bd81aeaeeeb7e5a5e12"},
{file = "prompt_toolkit-3.0.14.tar.gz", hash = "sha256:7e966747c18ececaec785699626b771c1ba8344c8d31759a1915d6b12fad6525"},
{file = "prompt_toolkit-3.0.16-py3-none-any.whl", hash = "sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd"},
{file = "prompt_toolkit-3.0.16.tar.gz", hash = "sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974"},
]
protobuf = [
{file = "protobuf-3.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:629b03fd3caae7f815b0c66b41273f6b1900a579e2ccb41ef4493a4f5fb84f3a"},
@ -2077,8 +2219,6 @@ pycairo = [
{file = "pycairo-1.20.0-cp37-cp37m-win_amd64.whl", hash = "sha256:273a33c56aba724ec42fe1d8f94c86c2e2660c1277470be9b04e5113d7c5b72d"},
{file = "pycairo-1.20.0-cp38-cp38-win32.whl", hash = "sha256:2088100a099c09c5e90bf247409ce6c98f51766b53bd13f96d6aac7addaa3e66"},
{file = "pycairo-1.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:ceb1edcbeb48dabd5fbbdff2e4b429aa88ddc493d6ebafe78d94b050ac0749e2"},
{file = "pycairo-1.20.0-cp39-cp39-win32.whl", hash = "sha256:57a768f4edc8a9890d98070dd473a812ac3d046cef4bc1c817d68024dab9a9b4"},
{file = "pycairo-1.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:57166119e424d71eccdba6b318bd731bdabd17188e2ba10d4f315f7bf16ace3f"},
{file = "pycairo-1.20.0.tar.gz", hash = "sha256:5695a10cb7f9ae0d01f665b56602a845b0a8cb17e2123bfece10c2e58552468c"},
]
pycparser = [
@ -2089,18 +2229,26 @@ pydub = [
{file = "pydub-0.24.1-py2.py3-none-any.whl", hash = "sha256:25fdfbbfd4c69363006a27c7bd2346c4b886a0dd3da264c14d858b71a9593284"},
{file = "pydub-0.24.1.tar.gz", hash = "sha256:630c68bfff9bb27cbc5e1f02923f717c3bc5f4d73fd685fda08b6ce90f76dc69"},
]
pyglet = [
{file = "pyglet-1.5.15-py3-none-any.whl", hash = "sha256:4401cc176580e4e17e2df8bbf7536f27e691327dc3f38f209a12f1859c70aed2"},
{file = "pyglet-1.5.15.zip", hash = "sha256:da9d8337388cedabf1f1c5dc21a45bb2b0e5327fba47f996c8573818c3dfa478"},
]
pygments = [
{file = "Pygments-2.7.4-py3-none-any.whl", hash = "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435"},
{file = "Pygments-2.7.4.tar.gz", hash = "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"},
{file = "Pygments-2.8.0-py3-none-any.whl", hash = "sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"},
{file = "Pygments-2.8.0.tar.gz", hash = "sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0"},
]
pylint = [
{file = "pylint-2.6.0-py3-none-any.whl", hash = "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f"},
{file = "pylint-2.6.0.tar.gz", hash = "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210"},
{file = "pylint-2.6.2-py3-none-any.whl", hash = "sha256:e71c2e9614a4f06e36498f310027942b0f4f2fde20aebb01655b31edc63b9eaf"},
{file = "pylint-2.6.2.tar.gz", hash = "sha256:718b74786ea7ed07aa0c58bf572154d4679f960d26e9641cc1de204a30b87fc9"},
]
pyparsing = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
]
pyrr = [
{file = "pyrr-0.10.3-py3-none-any.whl", hash = "sha256:d8af23fb9bb29262405845e1c98f7339fbba5e49323b98528bd01160a75c65ac"},
{file = "pyrr-0.10.3.tar.gz", hash = "sha256:3c0f7b20326e71f706a610d58f2190fff73af01eef60c19cb188b186f0ec7e1d"},
]
pyrsistent = [
{file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"},
]
@ -2113,8 +2261,8 @@ python-dateutil = [
{file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
]
pytz = [
{file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"},
{file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"},
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
]
pywin32 = [
{file = "pywin32-300-cp35-cp35m-win32.whl", hash = "sha256:1c204a81daed2089e55d11eefa4826c05e604d27fe2be40b6bf8db7b6a39da63"},
@ -2141,35 +2289,38 @@ pywinpty = [
{file = "pywinpty-0.5.7.tar.gz", hash = "sha256:2d7e9c881638a72ffdca3f5417dd1563b60f603e1b43e5895674c2a1b01f95a0"},
]
pyzmq = [
{file = "pyzmq-22.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a55335ecabc0c17ce6bd51bd96a8c5d48289ff715fcc292f0bc785b21c6abb75"},
{file = "pyzmq-22.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e49ceb319240eaa38ae402939c6a0779205f6d3c9b9e860b37513cd3af5d39b0"},
{file = "pyzmq-22.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9b412fb8fb0f5f85e0e63587fe5f16018688c0dc1db25e28691ee23e193ebb2c"},
{file = "pyzmq-22.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:09741d6c934e4a20c3b5019de23981298547695326fa01b4872d73d34e593f97"},
{file = "pyzmq-22.0.0-cp36-cp36m-win32.whl", hash = "sha256:f588bee64a592cf949d53f5fc26802d8832c5ef419a4ec08cb9302a35918c46a"},
{file = "pyzmq-22.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f5642b639d14351b1ae8480eb75a80f5933947864391ffd1a93280c620f7447c"},
{file = "pyzmq-22.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e823078f28f1c11e32f513a4a638559036cd8cbddf7360e5fe72074e6050b5b4"},
{file = "pyzmq-22.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9e89b982b041b4b3727eb5818029c9cb9050d490a4d175ea0bba876965a0dadc"},
{file = "pyzmq-22.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eb855cc6d4f61d27171f05950d76338d606d0f118dbc4ac9156d6b47db322c92"},
{file = "pyzmq-22.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c59bfdb14f1c51eb999624cbd5346dcaf9d888a0936d7aa0a4ea37f6cad2d2ca"},
{file = "pyzmq-22.0.0-cp37-cp37m-win32.whl", hash = "sha256:296bf85bde1405d4b01019a6afc3ad1e3cb51510419424e306b4497b809a461d"},
{file = "pyzmq-22.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:91190910c62b9609f25ac6f3665b232010631f53e8021f2a11aa8bb01c4c98ab"},
{file = "pyzmq-22.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:471d46de1440645e58fd541490223c84b2583a909d5f16f6cab5e6584c4ba049"},
{file = "pyzmq-22.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:4b1bdc20771203048eabd2385a67c5ebf5503dd86f3a09e44e34cfaf744decd7"},
{file = "pyzmq-22.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5bbbf21998a97f6f3864a628890f1bef44308774be094c95933783e39b5c083e"},
{file = "pyzmq-22.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:ed9f3146edff26677aa95922c1ce9f2b17c04be23c5d247910d4907606ee9188"},
{file = "pyzmq-22.0.0-cp38-cp38-win32.whl", hash = "sha256:5e742a8f24154285ba806b331ed130e036bc8fa5652a5931709a33d776f9555e"},
{file = "pyzmq-22.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:99132b52be295879ff5a05df8b69be88face3c103550ea32debc22380add4042"},
{file = "pyzmq-22.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:00049ade45a08deee510ee5eefa5800d02163608e5efbf9d7a649ac9188791a5"},
{file = "pyzmq-22.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bdd35a506d184cab584df7ba826a0f1e8524d8d22e0e97777a100800ab9fbc8f"},
{file = "pyzmq-22.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:3fd5c49cdc31b13685fe1253700c31d1b073460c08508b4aac885decc4f24f0b"},
{file = "pyzmq-22.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ee0366ab14db240b8ac578e1a6eac866d592dc79979efdfc51e85e7086cd7f1"},
{file = "pyzmq-22.0.0-cp39-cp39-win32.whl", hash = "sha256:e001f00d45f39b234b66d8e37fb7e71c8d47557f3a9695501379984a5aae9729"},
{file = "pyzmq-22.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:33ecd0f77136c6d56ac3b3af66d673d27c41bc976ffa8f6b77cf1e6e2fa529ee"},
{file = "pyzmq-22.0.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e0a9955cd4ddd5da498757642184733b703ed1160bc47af7e6f1c500edb64915"},
{file = "pyzmq-22.0.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:e66d04813dbf7e3343e0e23bcf935115f60765f33f9919e27690f115c95a8d2c"},
{file = "pyzmq-22.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6c19894d744c92d1f2c2e232278e8cffe7a463ee3e6a0b60421ddf479934a6d6"},
{file = "pyzmq-22.0.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:afae6fd49a3ba3ef57bb2b263df0474218d0da72a9597247b4b3c376de51fe0f"},
{file = "pyzmq-22.0.0.tar.gz", hash = "sha256:10b86bd04343b1de89ee03ec0bbaac646291de1a6c873228bb9ed22b4d8b32a2"},
{file = "pyzmq-22.0.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c0cde362075ee8f3d2b0353b283e203c2200243b5a15d5c5c03b78112a17e7d4"},
{file = "pyzmq-22.0.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:ff1ea14075bbddd6f29bf6beb8a46d0db779bcec6b9820909584081ec119f8fd"},
{file = "pyzmq-22.0.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:26380487eae4034d6c2a3fb8d0f2dff6dd0d9dd711894e8d25aa2d1938950a33"},
{file = "pyzmq-22.0.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:3e29f9cf85a40d521d048b55c63f59d6c772ac1c4bf51cdfc23b62a62e377c33"},
{file = "pyzmq-22.0.3-cp36-cp36m-win32.whl", hash = "sha256:4f34a173f813b38b83f058e267e30465ed64b22cd0cf6bad21148d3fa718f9bb"},
{file = "pyzmq-22.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:30df70f81fe210506aa354d7fd486a39b87d9f7f24c3d3f4f698ec5d96b8c084"},
{file = "pyzmq-22.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7026f0353977431fc884abd4ac28268894bd1a780ba84bb266d470b0ec26d2ed"},
{file = "pyzmq-22.0.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6d4163704201fff0f3ab0cd5d7a0ea1514ecfffd3926d62ec7e740a04d2012c7"},
{file = "pyzmq-22.0.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:763c175294d861869f18eb42901d500eda7d3fa4565f160b3b2fd2678ea0ebab"},
{file = "pyzmq-22.0.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:61e4bb6cd60caf1abcd796c3f48395e22c5b486eeca6f3a8797975c57d94b03e"},
{file = "pyzmq-22.0.3-cp37-cp37m-win32.whl", hash = "sha256:b25e5d339550a850f7e919fe8cb4c8eabe4c917613db48dab3df19bfb9a28969"},
{file = "pyzmq-22.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3ef50d74469b03725d781a2a03c57537d86847ccde587130fe35caafea8f75c6"},
{file = "pyzmq-22.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60e63577b85055e4cc43892fecd877b86695ee3ef12d5d10a3c5d6e77a7cc1a3"},
{file = "pyzmq-22.0.3-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:f5831eff6b125992ec65d973f5151c48003b6754030094723ac4c6e80a97c8c4"},
{file = "pyzmq-22.0.3-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:9221783dacb419604d5345d0e097bddef4459a9a95322de6c306bf1d9896559f"},
{file = "pyzmq-22.0.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b62ea18c0458a65ccd5be90f276f7a5a3f26a6dea0066d948ce2fa896051420f"},
{file = "pyzmq-22.0.3-cp38-cp38-win32.whl", hash = "sha256:81e7df0da456206201e226491aa1fc449da85328bf33bbeec2c03bb3a9f18324"},
{file = "pyzmq-22.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:f52070871a0fd90a99130babf21f8af192304ec1e995bec2a9533efc21ea4452"},
{file = "pyzmq-22.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:c5e29fe4678f97ce429f076a2a049a3d0b2660ada8f2c621e5dc9939426056dd"},
{file = "pyzmq-22.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d18ddc6741b51f3985978f2fda57ddcdae359662d7a6b395bc8ff2292fca14bd"},
{file = "pyzmq-22.0.3-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4231943514812dfb74f44eadcf85e8dd8cf302b4d0bce450ce1357cac88dbfdc"},
{file = "pyzmq-22.0.3-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:23a74de4b43c05c3044aeba0d1f3970def8f916151a712a3ac1e5cd9c0bc2902"},
{file = "pyzmq-22.0.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:532af3e6dddea62d9c49062ece5add998c9823c2419da943cf95589f56737de0"},
{file = "pyzmq-22.0.3-cp39-cp39-win32.whl", hash = "sha256:33acd2b9790818b9d00526135acf12790649d8d34b2b04d64558b469c9d86820"},
{file = "pyzmq-22.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:a558c5bc89d56d7253187dccc4e81b5bb0eac5ae9511eb4951910a1245d04622"},
{file = "pyzmq-22.0.3-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:581787c62eaa0e0db6c5413cedc393ebbadac6ddfd22e1cf9a60da23c4f1a4b2"},
{file = "pyzmq-22.0.3-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:38e3dca75d81bec4f2defa14b0a65b74545812bb519a8e89c8df96bbf4639356"},
{file = "pyzmq-22.0.3-pp36-pypy36_pp73-win32.whl", hash = "sha256:2f971431aaebe0a8b54ac018e041c2f0b949a43745444e4dadcc80d0f0ef8457"},
{file = "pyzmq-22.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da7d4d4c778c86b60949d17531e60c54ed3726878de8a7f8a6d6e7f8cc8c3205"},
{file = "pyzmq-22.0.3-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:13465c1ff969cab328bc92f7015ce3843f6e35f8871ad79d236e4fbc85dbe4cb"},
{file = "pyzmq-22.0.3-pp37-pypy37_pp73-win32.whl", hash = "sha256:279cc9b51db48bec2db146f38e336049ac5a59e5f12fb3a8ad864e238c1c62e3"},
{file = "pyzmq-22.0.3.tar.gz", hash = "sha256:f7f63ce127980d40f3e6a5fdb87abf17ce1a7c2bd8bf2c7560e1bbce8ab1f92d"},
]
recommonmark = [
{file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"},
@ -2270,8 +2421,8 @@ snowballstemmer = [
{file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"},
]
sphinx = [
{file = "Sphinx-3.4.3-py3-none-any.whl", hash = "sha256:c314c857e7cd47c856d2c5adff514ac2e6495f8b8e0f886a8a37e9305dfea0d8"},
{file = "Sphinx-3.4.3.tar.gz", hash = "sha256:41cad293f954f7d37f803d97eb184158cfd90f51195131e94875bc07cd08b93c"},
{file = "Sphinx-3.5.1-py3-none-any.whl", hash = "sha256:e90161222e4d80ce5fc811ace7c6787a226b4f5951545f7f42acf97277bfc35c"},
{file = "Sphinx-3.5.1.tar.gz", hash = "sha256:11d521e787d9372c289472513d807277caafb1684b33eb4f08f7574c405893a9"},
]
sphinxcontrib-applehelp = [
{file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},
@ -2353,8 +2504,8 @@ tornado = [
{file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"},
]
tqdm = [
{file = "tqdm-4.56.0-py2.py3-none-any.whl", hash = "sha256:4621f6823bab46a9cc33d48105753ccbea671b68bab2c50a9f0be23d4065cb5a"},
{file = "tqdm-4.56.0.tar.gz", hash = "sha256:fe3d08dd00a526850568d542ff9de9bbc2a09a791da3c334f3213d8d0bbbca65"},
{file = "tqdm-4.57.0-py2.py3-none-any.whl", hash = "sha256:70657337ec104eb4f3fb229285358f23f045433f6aea26846cdd55f0fd68945c"},
{file = "tqdm-4.57.0.tar.gz", hash = "sha256:65185676e9fdf20d154cffd1c5de8e39ef9696ff7e59fe0156b1b08e468736af"},
]
traitlets = [
{file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"},
@ -2398,23 +2549,23 @@ typing-extensions = [
{file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
]
watchdog = [
{file = "watchdog-1.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e2a531e71be7b5cc3499ae2d1494d51b6a26684bcc7c3146f63c810c00e8a3cc"},
{file = "watchdog-1.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e7c73edef48f4ceeebb987317a67e0080e5c9228601ff67b3c4062fa020403c7"},
{file = "watchdog-1.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85e6574395aa6c1e14e0f030d9d7f35c2340a6cf95d5671354ce876ac3ffdd4d"},
{file = "watchdog-1.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:27d9b4666938d5d40afdcdf2c751781e9ce36320788b70208d0f87f7401caf93"},
{file = "watchdog-1.0.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2f1ade0d0802503fda4340374d333408831cff23da66d7e711e279ba50fe6c4a"},
{file = "watchdog-1.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f1d0e878fd69129d0d68b87cee5d9543f20d8018e82998efb79f7e412d42154a"},
{file = "watchdog-1.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d948ad9ab9aba705f9836625b32e965b9ae607284811cd98334423f659ea537a"},
{file = "watchdog-1.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:101532b8db506559e52a9b5d75a308729b3f68264d930670e6155c976d0e52a0"},
{file = "watchdog-1.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:b1d723852ce90a14abf0ec0ca9e80689d9509ee4c9ee27163118d87b564a12ac"},
{file = "watchdog-1.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:68744de2003a5ea2dfbb104f9a74192cf381334a9e2c0ed2bbe1581828d50b61"},
{file = "watchdog-1.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:602dbd9498592eacc42e0632c19781c3df1728ef9cbab555fab6778effc29eeb"},
{file = "watchdog-1.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:016b01495b9c55b5d4126ed8ae75d93ea0d99377084107c33162df52887cee18"},
{file = "watchdog-1.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:5f1f3b65142175366ba94c64d8d4c8f4015825e0beaacee1c301823266b47b9b"},
{file = "watchdog-1.0.2-py3-none-win32.whl", hash = "sha256:57f05e55aa603c3b053eed7e679f0a83873c540255b88d58c6223c7493833bac"},
{file = "watchdog-1.0.2-py3-none-win_amd64.whl", hash = "sha256:f84146f7864339c8addf2c2b9903271df21d18d2c721e9a77f779493234a82b5"},
{file = "watchdog-1.0.2-py3-none-win_ia64.whl", hash = "sha256:ee21aeebe6b3e51e4ba64564c94cee8dbe7438b9cb60f0bb350c4fa70d1b52c2"},
{file = "watchdog-1.0.2.tar.gz", hash = "sha256:376cbc2a35c0392b0fe7ff16fbc1b303fd99d4dd9911ab5581ee9d69adc88982"},
{file = "watchdog-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9fa5a0d741c308657c6d60de246943b5a02647fe2a697fff6e0f46ec926f1069"},
{file = "watchdog-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eda07ba4c309dc7a04db6eb069626b94a047fedf5b4919c5739ac2f9535c851e"},
{file = "watchdog-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3164d69f27daa43ecd2817346e7c4c97a8491138d53a1873cf37abb469ff7583"},
{file = "watchdog-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:916d8ccd2b9f0536efb0af18b1661cda02588a1cc5c6af9b972212aa1c883e68"},
{file = "watchdog-2.0.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f1b8a0224cf2b599302ed06d1633d343a199345cab773e3a4cd7a1b0296589dd"},
{file = "watchdog-2.0.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bcab67402ac95f6e922f11078fc71d7bdcd631f0add8849033a50683b92c0e89"},
{file = "watchdog-2.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:218c0d9be3b5b17080133332645f3323483d648ea518d1e241a3bf66247cb357"},
{file = "watchdog-2.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:38b257718c8b31ee5e4693f87691de550b4340b2890c46fda0ddf7ac21b74f50"},
{file = "watchdog-2.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:c89f388c06ef189e8656fd5a5b333da3c5a2984a959e29ef2e80f8b4422b9574"},
{file = "watchdog-2.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:9e167710ed335762eb39954dd22aa313fd625d1ec7372deb3782332d1d5522f3"},
{file = "watchdog-2.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:075352d18f4dd071a2a6c4ca8791437f231746264b6f57eee02d6bd2c22714a3"},
{file = "watchdog-2.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:e7a0cba4546683496fa2e4759b39a1a4b6e2c250e7be15b73035500c9f2bfa28"},
{file = "watchdog-2.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:69099f4940e63d34b341979d077777b9b8c63c916a3c239f21916ef21f459400"},
{file = "watchdog-2.0.1-py3-none-win32.whl", hash = "sha256:2a786da9cba25029cc6c0190eb8c3c9bd8e67cfe23e339b2add4f1bb4a4a6bcd"},
{file = "watchdog-2.0.1-py3-none-win_amd64.whl", hash = "sha256:83249804d3f49f45de80a39494f21dd83c22e5cdccc6024edf557ae8461e25b7"},
{file = "watchdog-2.0.1-py3-none-win_ia64.whl", hash = "sha256:54c44620c1b377af4faa0fc594723905b21b0fc3b2bcee417084889c2187f2a1"},
{file = "watchdog-2.0.1.tar.gz", hash = "sha256:0d1c763652c255e2af00d76cf7d05c7b4867e960092b2696db031f69661c0785"},
]
wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},

View file

@ -42,6 +42,8 @@ grpcio = { version = "1.33.*", optional = true }
grpcio-tools = { version = "1.33.*", optional = true }
watchdog = { version = "*", optional = true }
jupyterlab = { version = "^3.0", optional = true }
moderngl = "^5.6.3"
moderngl-window = "^2.3.0"
[tool.poetry.extras]
webgl_renderer = ["grpcio","grpcio-tools","watchdog"]