manim 0.17.0__py3-none-any.whl → 0.19.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- manim/__init__.py +11 -6
- manim/__main__.py +62 -19
- manim/_config/__init__.py +10 -9
- manim/_config/cli_colors.py +26 -9
- manim/_config/default.cfg +1 -3
- manim/_config/logger_utils.py +23 -13
- manim/_config/utils.py +662 -468
- manim/animation/animation.py +164 -18
- manim/animation/changing.py +34 -23
- manim/animation/composition.py +265 -67
- manim/animation/creation.py +208 -26
- manim/animation/fading.py +16 -18
- manim/animation/growing.py +35 -15
- manim/animation/indication.py +150 -76
- manim/animation/movement.py +56 -22
- manim/animation/numbers.py +64 -6
- manim/animation/rotation.py +78 -7
- manim/animation/specialized.py +6 -7
- manim/animation/speedmodifier.py +13 -10
- manim/animation/transform.py +14 -11
- manim/animation/transform_matching_parts.py +3 -4
- manim/animation/updaters/mobject_update_utils.py +152 -30
- manim/animation/updaters/update.py +10 -7
- manim/camera/camera.py +182 -118
- manim/camera/mapping_camera.py +34 -3
- manim/camera/moving_camera.py +95 -74
- manim/camera/multi_camera.py +23 -15
- manim/camera/three_d_camera.py +70 -52
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +76 -44
- manim/cli/checkhealth/checks.py +192 -0
- manim/cli/checkhealth/commands.py +90 -0
- manim/cli/default_group.py +158 -25
- manim/cli/init/commands.py +33 -25
- manim/cli/plugins/commands.py +16 -3
- manim/cli/render/commands.py +72 -60
- manim/cli/render/ease_of_access_options.py +4 -3
- manim/cli/render/global_options.py +59 -17
- manim/cli/render/output_options.py +6 -5
- manim/cli/render/render_options.py +98 -33
- manim/constants.py +109 -59
- manim/data_structures.py +31 -0
- manim/mobject/frame.py +8 -5
- manim/mobject/geometry/__init__.py +1 -0
- manim/mobject/geometry/arc.py +277 -135
- manim/mobject/geometry/boolean_ops.py +32 -31
- manim/mobject/geometry/labeled.py +376 -0
- manim/mobject/geometry/line.py +192 -87
- manim/mobject/geometry/polygram.py +224 -58
- manim/mobject/geometry/shape_matchers.py +61 -25
- manim/mobject/geometry/tips.py +122 -48
- manim/mobject/graph.py +1027 -419
- manim/mobject/graphing/coordinate_systems.py +533 -278
- manim/mobject/graphing/functions.py +53 -32
- manim/mobject/graphing/number_line.py +123 -65
- manim/mobject/graphing/probability.py +88 -62
- manim/mobject/graphing/scale.py +33 -19
- manim/mobject/logo.py +118 -28
- manim/mobject/matrix.py +87 -83
- manim/mobject/mobject.py +912 -442
- manim/mobject/opengl/dot_cloud.py +16 -5
- manim/mobject/opengl/opengl_compatibility.py +4 -2
- manim/mobject/opengl/opengl_geometry.py +254 -153
- manim/mobject/opengl/opengl_image_mobject.py +3 -1
- manim/mobject/opengl/opengl_mobject.py +779 -482
- manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
- manim/mobject/opengl/opengl_surface.py +14 -92
- manim/mobject/opengl/opengl_three_dimensions.py +12 -8
- manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
- manim/mobject/svg/brace.py +173 -41
- manim/mobject/svg/svg_mobject.py +139 -53
- manim/mobject/table.py +61 -68
- manim/mobject/text/code_mobject.py +193 -539
- manim/mobject/text/numbers.py +81 -34
- manim/mobject/text/tex_mobject.py +130 -78
- manim/mobject/text/text_mobject.py +288 -164
- manim/mobject/three_d/polyhedra.py +111 -13
- manim/mobject/three_d/three_d_utils.py +17 -8
- manim/mobject/three_d/three_dimensions.py +239 -106
- manim/mobject/types/image_mobject.py +50 -30
- manim/mobject/types/point_cloud_mobject.py +120 -75
- manim/mobject/types/vectorized_mobject.py +841 -408
- manim/mobject/value_tracker.py +105 -38
- manim/mobject/vector_field.py +50 -31
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +14 -1
- manim/plugins/plugins_flags.py +10 -14
- manim/renderer/cairo_renderer.py +65 -50
- manim/renderer/opengl_renderer.py +89 -69
- manim/renderer/opengl_renderer_window.py +39 -18
- manim/renderer/shader.py +123 -87
- manim/renderer/shader_wrapper.py +44 -28
- manim/renderer/vectorized_mobject_rendering.py +38 -10
- manim/scene/moving_camera_scene.py +32 -3
- manim/scene/scene.py +507 -242
- manim/scene/scene_file_writer.py +371 -220
- manim/scene/section.py +20 -16
- manim/scene/three_d_scene.py +14 -22
- manim/scene/vector_space_scene.py +223 -129
- manim/scene/zoomed_scene.py +46 -41
- manim/typing.py +990 -0
- manim/utils/bezier.py +1823 -371
- manim/utils/caching.py +12 -5
- manim/utils/color/AS2700.py +236 -0
- manim/utils/color/BS381.py +318 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +533 -0
- manim/utils/color/XKCD.py +952 -0
- manim/utils/color/__init__.py +61 -0
- manim/utils/color/core.py +1667 -0
- manim/utils/color/manim_colors.py +218 -0
- manim/utils/commands.py +48 -20
- manim/utils/config_ops.py +39 -19
- manim/utils/debug.py +8 -7
- manim/utils/deprecation.py +86 -39
- manim/utils/docbuild/__init__.py +17 -0
- manim/utils/docbuild/autoaliasattr_directive.py +236 -0
- manim/utils/docbuild/autocolor_directive.py +99 -0
- manim/utils/docbuild/manim_directive.py +94 -41
- manim/utils/docbuild/module_parsing.py +245 -0
- manim/utils/exceptions.py +6 -0
- manim/utils/family.py +5 -3
- manim/utils/family_ops.py +17 -4
- manim/utils/file_ops.py +27 -17
- manim/utils/hashing.py +55 -45
- manim/utils/images.py +13 -7
- manim/utils/ipython_magic.py +13 -7
- manim/utils/iterables.py +163 -120
- manim/utils/module_ops.py +66 -24
- manim/utils/opengl.py +77 -24
- manim/utils/parameter_parsing.py +32 -0
- manim/utils/paths.py +30 -33
- manim/utils/polylabel.py +235 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +98 -32
- manim/utils/simple_functions.py +25 -33
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +188 -115
- manim/utils/testing/__init__.py +17 -0
- manim/utils/testing/_frames_testers.py +13 -8
- manim/utils/testing/_show_diff.py +5 -3
- manim/utils/testing/_test_class_makers.py +34 -18
- manim/utils/testing/frames_comparison.py +37 -19
- manim/utils/tex.py +130 -198
- manim/utils/tex_file_writing.py +77 -47
- manim/utils/tex_templates.py +2 -1
- manim/utils/unit.py +6 -5
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
- manim-0.19.1.dist-info/RECORD +220 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
- manim-0.19.1.dist-info/entry_points.txt +3 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
- manim/cli/new/group.py +0 -189
- manim/communitycolors.py +0 -9
- manim/gui/__init__.py +0 -0
- manim/gui/gui.py +0 -82
- manim/plugins/import_plugins.py +0 -43
- manim/utils/color.py +0 -552
- manim-0.17.0.dist-info/RECORD +0 -206
- manim-0.17.0.dist-info/entry_points.txt +0 -4
- /manim/cli/{new → checkhealth}/__init__.py +0 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
3
5
|
import moderngl_window as mglw
|
|
4
6
|
from moderngl_window.context.pyglet.window import Window as PygletWindow
|
|
5
7
|
from moderngl_window.timers.clock import Timer
|
|
6
|
-
from screeninfo import get_monitors
|
|
8
|
+
from screeninfo import Monitor, get_monitors
|
|
7
9
|
|
|
8
10
|
from .. import __version__, config
|
|
9
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .opengl_renderer import OpenGLRenderer
|
|
14
|
+
|
|
15
|
+
__all__ = ["Window"]
|
|
16
|
+
|
|
10
17
|
|
|
11
18
|
class Window(PygletWindow):
|
|
12
19
|
fullscreen = False
|
|
@@ -15,15 +22,19 @@ class Window(PygletWindow):
|
|
|
15
22
|
vsync = True
|
|
16
23
|
cursor = True
|
|
17
24
|
|
|
18
|
-
def __init__(
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
renderer: OpenGLRenderer,
|
|
28
|
+
window_size: str = config.window_size,
|
|
29
|
+
**kwargs: Any,
|
|
30
|
+
) -> None:
|
|
19
31
|
monitors = get_monitors()
|
|
20
32
|
mon_index = config.window_monitor
|
|
21
33
|
monitor = monitors[min(mon_index, len(monitors) - 1)]
|
|
22
34
|
|
|
23
|
-
if
|
|
35
|
+
if window_size == "default":
|
|
24
36
|
# make window_width half the width of the monitor
|
|
25
37
|
# but make it full screen if --fullscreen
|
|
26
|
-
|
|
27
38
|
window_width = monitor.width
|
|
28
39
|
if not config.fullscreen:
|
|
29
40
|
window_width //= 2
|
|
@@ -33,8 +44,13 @@ class Window(PygletWindow):
|
|
|
33
44
|
window_width * config.frame_height // config.frame_width,
|
|
34
45
|
)
|
|
35
46
|
size = (window_width, window_height)
|
|
47
|
+
elif len(window_size.split(",")) == 2:
|
|
48
|
+
(window_width, window_height) = tuple(map(int, window_size.split(",")))
|
|
49
|
+
size = (window_width, window_height)
|
|
36
50
|
else:
|
|
37
|
-
|
|
51
|
+
raise ValueError(
|
|
52
|
+
"Window_size must be specified as 'width,height' or 'default'.",
|
|
53
|
+
)
|
|
38
54
|
|
|
39
55
|
super().__init__(size=size)
|
|
40
56
|
|
|
@@ -53,13 +69,13 @@ class Window(PygletWindow):
|
|
|
53
69
|
self.position = initial_position
|
|
54
70
|
|
|
55
71
|
# Delegate event handling to scene.
|
|
56
|
-
def on_mouse_motion(self, x, y, dx, dy):
|
|
72
|
+
def on_mouse_motion(self, x: int, y: int, dx: int, dy: int) -> None:
|
|
57
73
|
super().on_mouse_motion(x, y, dx, dy)
|
|
58
74
|
point = self.renderer.pixel_coords_to_space_coords(x, y)
|
|
59
75
|
d_point = self.renderer.pixel_coords_to_space_coords(dx, dy, relative=True)
|
|
60
76
|
self.renderer.scene.on_mouse_motion(point, d_point)
|
|
61
77
|
|
|
62
|
-
def on_mouse_scroll(self, x, y, x_offset: float, y_offset: float):
|
|
78
|
+
def on_mouse_scroll(self, x: int, y: int, x_offset: float, y_offset: float) -> None:
|
|
63
79
|
super().on_mouse_scroll(x, y, x_offset, y_offset)
|
|
64
80
|
point = self.renderer.pixel_coords_to_space_coords(x, y)
|
|
65
81
|
offset = self.renderer.pixel_coords_to_space_coords(
|
|
@@ -69,28 +85,32 @@ class Window(PygletWindow):
|
|
|
69
85
|
)
|
|
70
86
|
self.renderer.scene.on_mouse_scroll(point, offset)
|
|
71
87
|
|
|
72
|
-
def on_key_press(self, symbol, modifiers):
|
|
88
|
+
def on_key_press(self, symbol: int, modifiers: int) -> bool:
|
|
73
89
|
self.renderer.pressed_keys.add(symbol)
|
|
74
|
-
super().on_key_press(symbol, modifiers)
|
|
90
|
+
event_handled: bool = super().on_key_press(symbol, modifiers)
|
|
75
91
|
self.renderer.scene.on_key_press(symbol, modifiers)
|
|
92
|
+
return event_handled
|
|
76
93
|
|
|
77
|
-
def on_key_release(self, symbol, modifiers):
|
|
94
|
+
def on_key_release(self, symbol: int, modifiers: int) -> None:
|
|
78
95
|
if symbol in self.renderer.pressed_keys:
|
|
79
96
|
self.renderer.pressed_keys.remove(symbol)
|
|
80
97
|
super().on_key_release(symbol, modifiers)
|
|
81
98
|
self.renderer.scene.on_key_release(symbol, modifiers)
|
|
82
99
|
|
|
83
|
-
def on_mouse_drag(
|
|
100
|
+
def on_mouse_drag(
|
|
101
|
+
self, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int
|
|
102
|
+
) -> None:
|
|
84
103
|
super().on_mouse_drag(x, y, dx, dy, buttons, modifiers)
|
|
85
104
|
point = self.renderer.pixel_coords_to_space_coords(x, y)
|
|
86
105
|
d_point = self.renderer.pixel_coords_to_space_coords(dx, dy, relative=True)
|
|
87
106
|
self.renderer.scene.on_mouse_drag(point, d_point, buttons, modifiers)
|
|
88
107
|
|
|
89
|
-
def find_initial_position(
|
|
108
|
+
def find_initial_position(
|
|
109
|
+
self, size: tuple[int, int], monitor: Monitor
|
|
110
|
+
) -> tuple[int, int]:
|
|
90
111
|
custom_position = config.window_position
|
|
91
112
|
window_width, window_height = size
|
|
92
|
-
# Position might be specified with a string of the form
|
|
93
|
-
# x,y for integers x and y
|
|
113
|
+
# Position might be specified with a string of the form x,y for integers x and y
|
|
94
114
|
if len(custom_position) == 1:
|
|
95
115
|
raise ValueError(
|
|
96
116
|
"window_position must specify both Y and X positions (Y/X -> UR). Also accepts LEFT/RIGHT/ORIGIN/UP/DOWN.",
|
|
@@ -103,20 +123,21 @@ class Window(PygletWindow):
|
|
|
103
123
|
elif custom_position == "ORIGIN":
|
|
104
124
|
custom_position = "O" * 2
|
|
105
125
|
elif "," in custom_position:
|
|
106
|
-
|
|
126
|
+
pos_y, pos_x = tuple(map(int, custom_position.split(",")))
|
|
127
|
+
return (pos_x, pos_y)
|
|
107
128
|
|
|
108
129
|
# Alternatively, it might be specified with a string like
|
|
109
130
|
# UR, OO, DL, etc. specifying what corner it should go to
|
|
110
131
|
char_to_n = {"L": 0, "U": 0, "O": 1, "R": 2, "D": 2}
|
|
111
|
-
width_diff = monitor.width - window_width
|
|
112
|
-
height_diff = monitor.height - window_height
|
|
132
|
+
width_diff: int = monitor.width - window_width
|
|
133
|
+
height_diff: int = monitor.height - window_height
|
|
113
134
|
|
|
114
135
|
return (
|
|
115
136
|
monitor.x + char_to_n[custom_position[1]] * width_diff // 2,
|
|
116
137
|
-monitor.y + char_to_n[custom_position[0]] * height_diff // 2,
|
|
117
138
|
)
|
|
118
139
|
|
|
119
|
-
def on_mouse_press(self, x, y, button, modifiers):
|
|
140
|
+
def on_mouse_press(self, x: int, y: int, button: int, modifiers: int) -> None:
|
|
120
141
|
super().on_mouse_press(x, y, button, modifiers)
|
|
121
142
|
point = self.renderer.pixel_coords_to_space_coords(x, y)
|
|
122
143
|
mouse_button_map = {
|
manim/renderer/shader.py
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
4
|
+
import inspect
|
|
3
5
|
import re
|
|
4
6
|
import textwrap
|
|
7
|
+
from collections.abc import Callable, Iterator, Sequence
|
|
5
8
|
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
6
10
|
|
|
7
11
|
import moderngl
|
|
8
12
|
import numpy as np
|
|
13
|
+
import numpy.typing as npt
|
|
14
|
+
from typing_extensions import Self, TypeAlias
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from manim.renderer.opengl_renderer import OpenGLRenderer
|
|
18
|
+
|
|
19
|
+
MeshTimeBasedUpdater: TypeAlias = Callable[["Object3D", float], None]
|
|
20
|
+
MeshNonTimeBasedUpdater: TypeAlias = Callable[["Object3D"], None]
|
|
21
|
+
MeshUpdater: TypeAlias = MeshNonTimeBasedUpdater | MeshTimeBasedUpdater
|
|
22
|
+
|
|
23
|
+
from manim.typing import MatrixMN, Point3D
|
|
9
24
|
|
|
10
25
|
from .. import config
|
|
11
26
|
from ..utils import opengl
|
|
12
|
-
from ..utils.simple_functions import get_parameters
|
|
13
27
|
|
|
14
28
|
SHADER_FOLDER = Path(__file__).parent / "shaders"
|
|
15
|
-
shader_program_cache: dict = {}
|
|
16
|
-
file_path_to_code_map: dict = {}
|
|
29
|
+
shader_program_cache: dict[str, moderngl.Program] = {}
|
|
30
|
+
file_path_to_code_map: dict[Path, str] = {}
|
|
17
31
|
|
|
18
32
|
__all__ = [
|
|
19
33
|
"Object3D",
|
|
@@ -42,7 +56,9 @@ def get_shader_code_from_file(file_path: Path) -> str:
|
|
|
42
56
|
return source
|
|
43
57
|
|
|
44
58
|
|
|
45
|
-
def filter_attributes(
|
|
59
|
+
def filter_attributes(
|
|
60
|
+
unfiltered_attributes: npt.NDArray, attributes: Sequence[str]
|
|
61
|
+
) -> npt.NDArray:
|
|
46
62
|
# Construct attributes for only those needed by the shader.
|
|
47
63
|
filtered_attributes_dtype = []
|
|
48
64
|
for i, dtype_name in enumerate(unfiltered_attributes.dtype.names):
|
|
@@ -68,28 +84,28 @@ def filter_attributes(unfiltered_attributes, attributes):
|
|
|
68
84
|
|
|
69
85
|
|
|
70
86
|
class Object3D:
|
|
71
|
-
def __init__(self, *children):
|
|
87
|
+
def __init__(self, *children: Object3D):
|
|
72
88
|
self.model_matrix = np.eye(4)
|
|
73
89
|
self.normal_matrix = np.eye(4)
|
|
74
|
-
self.children = []
|
|
75
|
-
self.parent = None
|
|
90
|
+
self.children: list[Object3D] = []
|
|
91
|
+
self.parent: Object3D | None = None
|
|
76
92
|
self.add(*children)
|
|
77
93
|
self.init_updaters()
|
|
78
94
|
|
|
79
95
|
# TODO: Use path_func.
|
|
80
|
-
def interpolate(self, start, end, alpha, _):
|
|
96
|
+
def interpolate(self, start: Object3D, end: Object3D, alpha: float, _: Any) -> None:
|
|
81
97
|
self.model_matrix = (1 - alpha) * start.model_matrix + alpha * end.model_matrix
|
|
82
98
|
self.normal_matrix = (
|
|
83
99
|
1 - alpha
|
|
84
100
|
) * start.normal_matrix + alpha * end.normal_matrix
|
|
85
101
|
|
|
86
|
-
def single_copy(self):
|
|
102
|
+
def single_copy(self) -> Object3D:
|
|
87
103
|
copy = Object3D()
|
|
88
104
|
copy.model_matrix = self.model_matrix.copy()
|
|
89
105
|
copy.normal_matrix = self.normal_matrix.copy()
|
|
90
106
|
return copy
|
|
91
107
|
|
|
92
|
-
def copy(self):
|
|
108
|
+
def copy(self) -> Object3D:
|
|
93
109
|
node_to_copy = {}
|
|
94
110
|
|
|
95
111
|
bfs = [self]
|
|
@@ -105,7 +121,7 @@ class Object3D:
|
|
|
105
121
|
node_to_copy[node.parent].add(node_copy)
|
|
106
122
|
return node_to_copy[self]
|
|
107
123
|
|
|
108
|
-
def add(self, *children):
|
|
124
|
+
def add(self, *children: Object3D) -> None:
|
|
109
125
|
for child in children:
|
|
110
126
|
if child.parent is not None:
|
|
111
127
|
raise Exception(
|
|
@@ -116,7 +132,7 @@ class Object3D:
|
|
|
116
132
|
for child in children:
|
|
117
133
|
child.parent = self
|
|
118
134
|
|
|
119
|
-
def remove(self, *children, current_children_only=True):
|
|
135
|
+
def remove(self, *children: Object3D, current_children_only: bool = True) -> None:
|
|
120
136
|
if current_children_only:
|
|
121
137
|
for child in children:
|
|
122
138
|
if child.parent != self:
|
|
@@ -127,14 +143,14 @@ class Object3D:
|
|
|
127
143
|
for child in children:
|
|
128
144
|
child.parent = None
|
|
129
145
|
|
|
130
|
-
def get_position(self):
|
|
146
|
+
def get_position(self) -> Point3D:
|
|
131
147
|
return self.model_matrix[:, 3][:3]
|
|
132
148
|
|
|
133
|
-
def set_position(self, position):
|
|
149
|
+
def set_position(self, position: Point3D) -> Self:
|
|
134
150
|
self.model_matrix[:, 3][:3] = position
|
|
135
151
|
return self
|
|
136
152
|
|
|
137
|
-
def get_meshes(self):
|
|
153
|
+
def get_meshes(self) -> Iterator[Mesh]:
|
|
138
154
|
dfs = [self]
|
|
139
155
|
while dfs:
|
|
140
156
|
parent = dfs.pop()
|
|
@@ -142,17 +158,17 @@ class Object3D:
|
|
|
142
158
|
yield parent
|
|
143
159
|
dfs.extend(parent.children)
|
|
144
160
|
|
|
145
|
-
def get_family(self):
|
|
161
|
+
def get_family(self) -> Iterator[Object3D]:
|
|
146
162
|
dfs = [self]
|
|
147
163
|
while dfs:
|
|
148
164
|
parent = dfs.pop()
|
|
149
165
|
yield parent
|
|
150
166
|
dfs.extend(parent.children)
|
|
151
167
|
|
|
152
|
-
def align_data_and_family(self, _):
|
|
168
|
+
def align_data_and_family(self, _: Any) -> None:
|
|
153
169
|
pass
|
|
154
170
|
|
|
155
|
-
def hierarchical_model_matrix(self):
|
|
171
|
+
def hierarchical_model_matrix(self) -> MatrixMN:
|
|
156
172
|
if self.parent is None:
|
|
157
173
|
return self.model_matrix
|
|
158
174
|
|
|
@@ -163,7 +179,7 @@ class Object3D:
|
|
|
163
179
|
current_object = current_object.parent
|
|
164
180
|
return np.linalg.multi_dot(list(reversed(model_matrices)))
|
|
165
181
|
|
|
166
|
-
def hierarchical_normal_matrix(self):
|
|
182
|
+
def hierarchical_normal_matrix(self) -> MatrixMN:
|
|
167
183
|
if self.parent is None:
|
|
168
184
|
return self.normal_matrix[:3, :3]
|
|
169
185
|
|
|
@@ -174,76 +190,93 @@ class Object3D:
|
|
|
174
190
|
current_object = current_object.parent
|
|
175
191
|
return np.linalg.multi_dot(list(reversed(normal_matrices)))[:3, :3]
|
|
176
192
|
|
|
177
|
-
def init_updaters(self):
|
|
178
|
-
self.time_based_updaters = []
|
|
179
|
-
self.non_time_updaters = []
|
|
193
|
+
def init_updaters(self) -> None:
|
|
194
|
+
self.time_based_updaters: list[MeshTimeBasedUpdater] = []
|
|
195
|
+
self.non_time_updaters: list[MeshNonTimeBasedUpdater] = []
|
|
180
196
|
self.has_updaters = False
|
|
181
197
|
self.updating_suspended = False
|
|
182
198
|
|
|
183
|
-
def update(self, dt=0):
|
|
199
|
+
def update(self, dt: float = 0) -> Self:
|
|
184
200
|
if not self.has_updaters or self.updating_suspended:
|
|
185
201
|
return self
|
|
186
|
-
for
|
|
187
|
-
|
|
188
|
-
for
|
|
189
|
-
|
|
202
|
+
for time_based_updater in self.time_based_updaters:
|
|
203
|
+
time_based_updater(self, dt)
|
|
204
|
+
for non_time_based_updater in self.non_time_updaters:
|
|
205
|
+
non_time_based_updater(self)
|
|
190
206
|
return self
|
|
191
207
|
|
|
192
|
-
def get_time_based_updaters(self):
|
|
208
|
+
def get_time_based_updaters(self) -> list[MeshTimeBasedUpdater]:
|
|
193
209
|
return self.time_based_updaters
|
|
194
210
|
|
|
195
|
-
def has_time_based_updater(self):
|
|
211
|
+
def has_time_based_updater(self) -> bool:
|
|
196
212
|
return len(self.time_based_updaters) > 0
|
|
197
213
|
|
|
198
|
-
def get_updaters(self):
|
|
214
|
+
def get_updaters(self) -> list[MeshUpdater]:
|
|
199
215
|
return self.time_based_updaters + self.non_time_updaters
|
|
200
216
|
|
|
201
|
-
def add_updater(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if
|
|
208
|
-
|
|
217
|
+
def add_updater(
|
|
218
|
+
self,
|
|
219
|
+
update_function: MeshUpdater,
|
|
220
|
+
index: int | None = None,
|
|
221
|
+
call_updater: bool = True,
|
|
222
|
+
) -> Self:
|
|
223
|
+
if "dt" in inspect.signature(update_function).parameters:
|
|
224
|
+
self._add_time_based_updater(update_function, index) # type: ignore[arg-type]
|
|
209
225
|
else:
|
|
210
|
-
|
|
226
|
+
self._add_non_time_updater(update_function, index) # type: ignore[arg-type]
|
|
211
227
|
|
|
212
228
|
self.refresh_has_updater_status()
|
|
213
229
|
if call_updater:
|
|
214
230
|
self.update()
|
|
215
231
|
return self
|
|
216
232
|
|
|
217
|
-
def
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
233
|
+
def _add_time_based_updater(
|
|
234
|
+
self, update_function: MeshTimeBasedUpdater, index: int | None = None
|
|
235
|
+
) -> None:
|
|
236
|
+
if index is None:
|
|
237
|
+
self.time_based_updaters.append(update_function)
|
|
238
|
+
else:
|
|
239
|
+
self.time_based_updaters.insert(index, update_function)
|
|
240
|
+
|
|
241
|
+
def _add_non_time_updater(
|
|
242
|
+
self, update_function: MeshNonTimeBasedUpdater, index: int | None = None
|
|
243
|
+
) -> None:
|
|
244
|
+
if index is None:
|
|
245
|
+
self.non_time_updaters.append(update_function)
|
|
246
|
+
else:
|
|
247
|
+
self.non_time_updaters.insert(index, update_function)
|
|
248
|
+
|
|
249
|
+
def remove_updater(self, update_function: MeshUpdater) -> Self:
|
|
250
|
+
while update_function in self.time_based_updaters:
|
|
251
|
+
self.time_based_updaters.remove(update_function) # type: ignore[arg-type]
|
|
252
|
+
while update_function in self.non_time_updaters:
|
|
253
|
+
self.non_time_updaters.remove(update_function) # type: ignore[arg-type]
|
|
221
254
|
self.refresh_has_updater_status()
|
|
222
255
|
return self
|
|
223
256
|
|
|
224
|
-
def clear_updaters(self):
|
|
257
|
+
def clear_updaters(self) -> Self:
|
|
225
258
|
self.time_based_updaters = []
|
|
226
259
|
self.non_time_updaters = []
|
|
227
260
|
self.refresh_has_updater_status()
|
|
228
261
|
return self
|
|
229
262
|
|
|
230
|
-
def match_updaters(self,
|
|
263
|
+
def match_updaters(self, mesh: Object3D) -> Self:
|
|
231
264
|
self.clear_updaters()
|
|
232
|
-
for updater in
|
|
265
|
+
for updater in mesh.get_updaters():
|
|
233
266
|
self.add_updater(updater)
|
|
234
267
|
return self
|
|
235
268
|
|
|
236
|
-
def suspend_updating(self):
|
|
269
|
+
def suspend_updating(self) -> Self:
|
|
237
270
|
self.updating_suspended = True
|
|
238
271
|
return self
|
|
239
272
|
|
|
240
|
-
def resume_updating(self, call_updater=True):
|
|
273
|
+
def resume_updating(self, call_updater: bool = True) -> Self:
|
|
241
274
|
self.updating_suspended = False
|
|
242
275
|
if call_updater:
|
|
243
276
|
self.update(dt=0)
|
|
244
277
|
return self
|
|
245
278
|
|
|
246
|
-
def refresh_has_updater_status(self):
|
|
279
|
+
def refresh_has_updater_status(self) -> Self:
|
|
247
280
|
self.has_updaters = len(self.get_updaters()) > 0
|
|
248
281
|
return self
|
|
249
282
|
|
|
@@ -251,23 +284,23 @@ class Object3D:
|
|
|
251
284
|
class Mesh(Object3D):
|
|
252
285
|
def __init__(
|
|
253
286
|
self,
|
|
254
|
-
shader=None,
|
|
255
|
-
attributes=None,
|
|
256
|
-
geometry=None,
|
|
257
|
-
material=None,
|
|
258
|
-
indices=None,
|
|
259
|
-
use_depth_test=True,
|
|
260
|
-
primitive=moderngl.TRIANGLES,
|
|
287
|
+
shader: Shader | None = None,
|
|
288
|
+
attributes: npt.NDArray | None = None,
|
|
289
|
+
geometry: Mesh | None = None,
|
|
290
|
+
material: Shader | None = None,
|
|
291
|
+
indices: npt.NDArray | None = None,
|
|
292
|
+
use_depth_test: bool = True,
|
|
293
|
+
primitive: int = moderngl.TRIANGLES,
|
|
261
294
|
):
|
|
262
295
|
super().__init__()
|
|
263
296
|
if shader is not None and attributes is not None:
|
|
264
|
-
self.shader = shader
|
|
297
|
+
self.shader: Shader = shader
|
|
265
298
|
self.attributes = attributes
|
|
266
299
|
self.indices = indices
|
|
267
300
|
elif geometry is not None and material is not None:
|
|
268
301
|
self.shader = material
|
|
269
302
|
self.attributes = geometry.attributes
|
|
270
|
-
self.indices = geometry.
|
|
303
|
+
self.indices = geometry.indices
|
|
271
304
|
else:
|
|
272
305
|
raise Exception(
|
|
273
306
|
"Mesh requires either attributes and a Shader or a Geometry and a "
|
|
@@ -275,10 +308,10 @@ class Mesh(Object3D):
|
|
|
275
308
|
)
|
|
276
309
|
self.use_depth_test = use_depth_test
|
|
277
310
|
self.primitive = primitive
|
|
278
|
-
self.skip_render = False
|
|
311
|
+
self.skip_render: bool = False
|
|
279
312
|
self.init_updaters()
|
|
280
313
|
|
|
281
|
-
def single_copy(self):
|
|
314
|
+
def single_copy(self) -> Mesh:
|
|
282
315
|
copy = Mesh(
|
|
283
316
|
attributes=self.attributes.copy(),
|
|
284
317
|
shader=self.shader,
|
|
@@ -292,7 +325,7 @@ class Mesh(Object3D):
|
|
|
292
325
|
# TODO: Copy updaters?
|
|
293
326
|
return copy
|
|
294
327
|
|
|
295
|
-
def set_uniforms(self, renderer):
|
|
328
|
+
def set_uniforms(self, renderer: OpenGLRenderer) -> None:
|
|
296
329
|
self.shader.set_uniform(
|
|
297
330
|
"u_model_matrix",
|
|
298
331
|
opengl.matrix_to_shader_input(self.model_matrix),
|
|
@@ -303,7 +336,7 @@ class Mesh(Object3D):
|
|
|
303
336
|
renderer.camera.projection_matrix,
|
|
304
337
|
)
|
|
305
338
|
|
|
306
|
-
def render(self):
|
|
339
|
+
def render(self) -> None:
|
|
307
340
|
if self.skip_render:
|
|
308
341
|
return
|
|
309
342
|
|
|
@@ -312,15 +345,17 @@ class Mesh(Object3D):
|
|
|
312
345
|
else:
|
|
313
346
|
self.shader.context.disable(moderngl.DEPTH_TEST)
|
|
314
347
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
348
|
+
shader_attribute_names: list[str] = []
|
|
349
|
+
for member_name, member in self.shader.shader_program._members.items():
|
|
350
|
+
if isinstance(member, moderngl.Attribute):
|
|
351
|
+
shader_attribute_names.append(member_name)
|
|
352
|
+
filtered_shader_attributes = filter_attributes(
|
|
353
|
+
self.attributes, shader_attribute_names
|
|
354
|
+
)
|
|
322
355
|
|
|
323
|
-
vertex_buffer_object = self.shader.context.buffer(
|
|
356
|
+
vertex_buffer_object = self.shader.context.buffer(
|
|
357
|
+
filtered_shader_attributes.tobytes()
|
|
358
|
+
)
|
|
324
359
|
if self.indices is None:
|
|
325
360
|
index_buffer_object = None
|
|
326
361
|
else:
|
|
@@ -332,7 +367,7 @@ class Mesh(Object3D):
|
|
|
332
367
|
vertex_array_object = self.shader.context.simple_vertex_array(
|
|
333
368
|
self.shader.shader_program,
|
|
334
369
|
vertex_buffer_object,
|
|
335
|
-
*
|
|
370
|
+
*filtered_shader_attributes.dtype.names,
|
|
336
371
|
index_buffer=index_buffer_object,
|
|
337
372
|
)
|
|
338
373
|
vertex_array_object.render(self.primitive)
|
|
@@ -345,13 +380,14 @@ class Mesh(Object3D):
|
|
|
345
380
|
class Shader:
|
|
346
381
|
def __init__(
|
|
347
382
|
self,
|
|
348
|
-
context,
|
|
349
|
-
name=None,
|
|
350
|
-
source=None,
|
|
383
|
+
context: moderngl.Context,
|
|
384
|
+
name: str | None = None,
|
|
385
|
+
source: dict[str, Any] | None = None,
|
|
351
386
|
):
|
|
352
387
|
global shader_program_cache
|
|
353
388
|
self.context = context
|
|
354
389
|
self.name = name
|
|
390
|
+
self.source = source
|
|
355
391
|
|
|
356
392
|
# See if the program is cached.
|
|
357
393
|
if (
|
|
@@ -359,10 +395,10 @@ class Shader:
|
|
|
359
395
|
and shader_program_cache[self.name].ctx == self.context
|
|
360
396
|
):
|
|
361
397
|
self.shader_program = shader_program_cache[self.name]
|
|
362
|
-
elif source is not None:
|
|
398
|
+
elif self.source is not None:
|
|
363
399
|
# Generate the shader from inline code if it was passed.
|
|
364
|
-
self.shader_program = context.program(**source)
|
|
365
|
-
|
|
400
|
+
self.shader_program = context.program(**self.source)
|
|
401
|
+
elif self.name is not None:
|
|
366
402
|
# Search for a file containing the shader.
|
|
367
403
|
source_dict = {}
|
|
368
404
|
source_dict_key = {
|
|
@@ -370,30 +406,30 @@ class Shader:
|
|
|
370
406
|
"frag": "fragment_shader",
|
|
371
407
|
"geom": "geometry_shader",
|
|
372
408
|
}
|
|
373
|
-
shader_folder = SHADER_FOLDER / name
|
|
409
|
+
shader_folder = SHADER_FOLDER / self.name
|
|
374
410
|
for shader_file in shader_folder.iterdir():
|
|
375
411
|
shader_file_path = shader_folder / shader_file
|
|
376
412
|
shader_source = get_shader_code_from_file(shader_file_path)
|
|
377
413
|
source_dict[source_dict_key[shader_file_path.stem]] = shader_source
|
|
378
414
|
self.shader_program = context.program(**source_dict)
|
|
415
|
+
else:
|
|
416
|
+
raise Exception("Must either pass shader name or shader source.")
|
|
379
417
|
|
|
380
418
|
# Cache the shader.
|
|
381
|
-
if name is not None and name not in shader_program_cache:
|
|
419
|
+
if self.name is not None and self.name not in shader_program_cache:
|
|
382
420
|
shader_program_cache[self.name] = self.shader_program
|
|
383
421
|
|
|
384
|
-
def set_uniform(self, name, value):
|
|
385
|
-
|
|
422
|
+
def set_uniform(self, name: str, value: Any) -> None:
|
|
423
|
+
with contextlib.suppress(KeyError):
|
|
386
424
|
self.shader_program[name] = value
|
|
387
|
-
except KeyError:
|
|
388
|
-
pass
|
|
389
425
|
|
|
390
426
|
|
|
391
427
|
class FullScreenQuad(Mesh):
|
|
392
428
|
def __init__(
|
|
393
429
|
self,
|
|
394
|
-
context,
|
|
395
|
-
fragment_shader_source=None,
|
|
396
|
-
fragment_shader_name=None,
|
|
430
|
+
context: moderngl.Context,
|
|
431
|
+
fragment_shader_source: str | None = None,
|
|
432
|
+
fragment_shader_name: str | None = None,
|
|
397
433
|
):
|
|
398
434
|
if fragment_shader_source is None and fragment_shader_name is None:
|
|
399
435
|
raise Exception("Must either pass shader name or shader source.")
|
|
@@ -440,5 +476,5 @@ class FullScreenQuad(Mesh):
|
|
|
440
476
|
)
|
|
441
477
|
super().__init__(shader, attributes)
|
|
442
478
|
|
|
443
|
-
def render(self):
|
|
479
|
+
def render(self) -> None:
|
|
444
480
|
super().render()
|