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.
Files changed (163) hide show
  1. manim/__init__.py +11 -6
  2. manim/__main__.py +62 -19
  3. manim/_config/__init__.py +10 -9
  4. manim/_config/cli_colors.py +26 -9
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +23 -13
  7. manim/_config/utils.py +662 -468
  8. manim/animation/animation.py +164 -18
  9. manim/animation/changing.py +34 -23
  10. manim/animation/composition.py +265 -67
  11. manim/animation/creation.py +208 -26
  12. manim/animation/fading.py +16 -18
  13. manim/animation/growing.py +35 -15
  14. manim/animation/indication.py +150 -76
  15. manim/animation/movement.py +56 -22
  16. manim/animation/numbers.py +64 -6
  17. manim/animation/rotation.py +78 -7
  18. manim/animation/specialized.py +6 -7
  19. manim/animation/speedmodifier.py +13 -10
  20. manim/animation/transform.py +14 -11
  21. manim/animation/transform_matching_parts.py +3 -4
  22. manim/animation/updaters/mobject_update_utils.py +152 -30
  23. manim/animation/updaters/update.py +10 -7
  24. manim/camera/camera.py +182 -118
  25. manim/camera/mapping_camera.py +34 -3
  26. manim/camera/moving_camera.py +95 -74
  27. manim/camera/multi_camera.py +23 -15
  28. manim/camera/three_d_camera.py +70 -52
  29. manim/cli/__init__.py +17 -0
  30. manim/cli/cfg/group.py +76 -44
  31. manim/cli/checkhealth/checks.py +192 -0
  32. manim/cli/checkhealth/commands.py +90 -0
  33. manim/cli/default_group.py +158 -25
  34. manim/cli/init/commands.py +33 -25
  35. manim/cli/plugins/commands.py +16 -3
  36. manim/cli/render/commands.py +72 -60
  37. manim/cli/render/ease_of_access_options.py +4 -3
  38. manim/cli/render/global_options.py +59 -17
  39. manim/cli/render/output_options.py +6 -5
  40. manim/cli/render/render_options.py +98 -33
  41. manim/constants.py +109 -59
  42. manim/data_structures.py +31 -0
  43. manim/mobject/frame.py +8 -5
  44. manim/mobject/geometry/__init__.py +1 -0
  45. manim/mobject/geometry/arc.py +277 -135
  46. manim/mobject/geometry/boolean_ops.py +32 -31
  47. manim/mobject/geometry/labeled.py +376 -0
  48. manim/mobject/geometry/line.py +192 -87
  49. manim/mobject/geometry/polygram.py +224 -58
  50. manim/mobject/geometry/shape_matchers.py +61 -25
  51. manim/mobject/geometry/tips.py +122 -48
  52. manim/mobject/graph.py +1027 -419
  53. manim/mobject/graphing/coordinate_systems.py +533 -278
  54. manim/mobject/graphing/functions.py +53 -32
  55. manim/mobject/graphing/number_line.py +123 -65
  56. manim/mobject/graphing/probability.py +88 -62
  57. manim/mobject/graphing/scale.py +33 -19
  58. manim/mobject/logo.py +118 -28
  59. manim/mobject/matrix.py +87 -83
  60. manim/mobject/mobject.py +912 -442
  61. manim/mobject/opengl/dot_cloud.py +16 -5
  62. manim/mobject/opengl/opengl_compatibility.py +4 -2
  63. manim/mobject/opengl/opengl_geometry.py +254 -153
  64. manim/mobject/opengl/opengl_image_mobject.py +3 -1
  65. manim/mobject/opengl/opengl_mobject.py +779 -482
  66. manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
  67. manim/mobject/opengl/opengl_surface.py +14 -92
  68. manim/mobject/opengl/opengl_three_dimensions.py +12 -8
  69. manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
  70. manim/mobject/svg/brace.py +173 -41
  71. manim/mobject/svg/svg_mobject.py +139 -53
  72. manim/mobject/table.py +61 -68
  73. manim/mobject/text/code_mobject.py +193 -539
  74. manim/mobject/text/numbers.py +81 -34
  75. manim/mobject/text/tex_mobject.py +130 -78
  76. manim/mobject/text/text_mobject.py +288 -164
  77. manim/mobject/three_d/polyhedra.py +111 -13
  78. manim/mobject/three_d/three_d_utils.py +17 -8
  79. manim/mobject/three_d/three_dimensions.py +239 -106
  80. manim/mobject/types/image_mobject.py +50 -30
  81. manim/mobject/types/point_cloud_mobject.py +120 -75
  82. manim/mobject/types/vectorized_mobject.py +841 -408
  83. manim/mobject/value_tracker.py +105 -38
  84. manim/mobject/vector_field.py +50 -31
  85. manim/opengl/__init__.py +3 -3
  86. manim/plugins/__init__.py +14 -1
  87. manim/plugins/plugins_flags.py +10 -14
  88. manim/renderer/cairo_renderer.py +65 -50
  89. manim/renderer/opengl_renderer.py +89 -69
  90. manim/renderer/opengl_renderer_window.py +39 -18
  91. manim/renderer/shader.py +123 -87
  92. manim/renderer/shader_wrapper.py +44 -28
  93. manim/renderer/vectorized_mobject_rendering.py +38 -10
  94. manim/scene/moving_camera_scene.py +32 -3
  95. manim/scene/scene.py +507 -242
  96. manim/scene/scene_file_writer.py +371 -220
  97. manim/scene/section.py +20 -16
  98. manim/scene/three_d_scene.py +14 -22
  99. manim/scene/vector_space_scene.py +223 -129
  100. manim/scene/zoomed_scene.py +46 -41
  101. manim/typing.py +990 -0
  102. manim/utils/bezier.py +1823 -371
  103. manim/utils/caching.py +12 -5
  104. manim/utils/color/AS2700.py +236 -0
  105. manim/utils/color/BS381.py +318 -0
  106. manim/utils/color/DVIPSNAMES.py +96 -0
  107. manim/utils/color/SVGNAMES.py +179 -0
  108. manim/utils/color/X11.py +533 -0
  109. manim/utils/color/XKCD.py +952 -0
  110. manim/utils/color/__init__.py +61 -0
  111. manim/utils/color/core.py +1667 -0
  112. manim/utils/color/manim_colors.py +218 -0
  113. manim/utils/commands.py +48 -20
  114. manim/utils/config_ops.py +39 -19
  115. manim/utils/debug.py +8 -7
  116. manim/utils/deprecation.py +86 -39
  117. manim/utils/docbuild/__init__.py +17 -0
  118. manim/utils/docbuild/autoaliasattr_directive.py +236 -0
  119. manim/utils/docbuild/autocolor_directive.py +99 -0
  120. manim/utils/docbuild/manim_directive.py +94 -41
  121. manim/utils/docbuild/module_parsing.py +245 -0
  122. manim/utils/exceptions.py +6 -0
  123. manim/utils/family.py +5 -3
  124. manim/utils/family_ops.py +17 -4
  125. manim/utils/file_ops.py +27 -17
  126. manim/utils/hashing.py +55 -45
  127. manim/utils/images.py +13 -7
  128. manim/utils/ipython_magic.py +13 -7
  129. manim/utils/iterables.py +163 -120
  130. manim/utils/module_ops.py +66 -24
  131. manim/utils/opengl.py +77 -24
  132. manim/utils/parameter_parsing.py +32 -0
  133. manim/utils/paths.py +30 -33
  134. manim/utils/polylabel.py +235 -0
  135. manim/utils/qhull.py +218 -0
  136. manim/utils/rate_functions.py +98 -32
  137. manim/utils/simple_functions.py +25 -33
  138. manim/utils/sounds.py +7 -1
  139. manim/utils/space_ops.py +188 -115
  140. manim/utils/testing/__init__.py +17 -0
  141. manim/utils/testing/_frames_testers.py +13 -8
  142. manim/utils/testing/_show_diff.py +5 -3
  143. manim/utils/testing/_test_class_makers.py +34 -18
  144. manim/utils/testing/frames_comparison.py +37 -19
  145. manim/utils/tex.py +130 -198
  146. manim/utils/tex_file_writing.py +77 -47
  147. manim/utils/tex_templates.py +2 -1
  148. manim/utils/unit.py +6 -5
  149. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
  150. manim-0.19.1.dist-info/RECORD +220 -0
  151. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
  152. manim-0.19.1.dist-info/entry_points.txt +3 -0
  153. {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
  154. manim/cli/new/group.py +0 -189
  155. manim/communitycolors.py +0 -9
  156. manim/gui/__init__.py +0 -0
  157. manim/gui/gui.py +0 -82
  158. manim/plugins/import_plugins.py +0 -43
  159. manim/utils/color.py +0 -552
  160. manim-0.17.0.dist-info/RECORD +0 -206
  161. manim-0.17.0.dist-info/entry_points.txt +0 -4
  162. /manim/cli/{new → checkhealth}/__init__.py +0 -0
  163. {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__(self, renderer, size=config.window_size, **kwargs):
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 size == "default":
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
- size = tuple(size)
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(self, x, y, dx, dy, buttons, modifiers):
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(self, size, monitor):
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
- return tuple(map(int, custom_position.split(",")))
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(unfiltered_attributes, 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 updater in self.time_based_updaters:
187
- updater(self, dt)
188
- for updater in self.non_time_updaters:
189
- updater(self)
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(self, update_function, index=None, call_updater=True):
202
- if "dt" in get_parameters(update_function):
203
- updater_list = self.time_based_updaters
204
- else:
205
- updater_list = self.non_time_updaters
206
-
207
- if index is None:
208
- updater_list.append(update_function)
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
- updater_list.insert(index, update_function)
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 remove_updater(self, update_function):
218
- for updater_list in [self.time_based_updaters, self.non_time_updaters]:
219
- while update_function in updater_list:
220
- updater_list.remove(update_function)
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, mobject):
263
+ def match_updaters(self, mesh: Object3D) -> Self:
231
264
  self.clear_updaters()
232
- for updater in mobject.get_updaters():
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.index
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
- from moderngl.program_members import Attribute
316
-
317
- shader_attributes = []
318
- for k, v in self.shader.shader_program._members.items():
319
- if isinstance(v, Attribute):
320
- shader_attributes.append(k)
321
- shader_attributes = filter_attributes(self.attributes, shader_attributes)
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(shader_attributes.tobytes())
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
- *shader_attributes.dtype.names,
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
- else:
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
- try:
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()