Conversation
c7aa27d to
64021f9
Compare
nikolajmunk
left a comment
There was a problem hiding this comment.
Some quick documentation suggestions. I haven't done much here, since I'm guessing much of this will be rewritten soonish. Otherwise this looks very nice :)
| Cairo 3D camera queries | ||
| ----------------------- | ||
|
|
||
| Calls such as ``camera.project_point()`` use the camera's current angle trackers, | ||
| even when no frame has been drawn. Reading the rotation matrix does not change | ||
| the camera's movie-cache key: the matrix is computed from the trackers, not a | ||
| separate camera setting. | ||
|
|
||
| ``rotation_matrix`` is read-only. To rotate the camera, change its angle trackers | ||
| or use setters such as ``set_theta()``. Subclasses can customize | ||
| ``generate_rotation_matrix()`` rather than assigning a matrix to the property. | ||
| If that customization depends on state beyond the angle trackers, call | ||
| ``reset_rotation_matrix()`` when those extra inputs change. |
There was a problem hiding this comment.
I feel like this information fits better in the camera guide.
| Evaluation calls ``setup()``, ``construct()`` and ``tear_down()``. Play and wait | ||
| calls use the same animation loop as rendering with caching and skipping turned | ||
| off: interpolation, updaters, stop conditions, and animation finish/cleanup run | ||
| as usual. The clock follows the same frame-rate sampling and frozen-wait rounding | ||
| rules described in :doc:`deep_dive`. Time comes from running those steps, not | ||
| from adding up the requested animation durations. | ||
|
|
||
| The manager ignores cached movie segments, animation-range selection, and skip | ||
| flags, including those set by sections. All play calls reached by the scene's | ||
| Python code are evaluated. |
There was a problem hiding this comment.
| Evaluation calls ``setup()``, ``construct()`` and ``tear_down()``. Play and wait | |
| calls use the same animation loop as rendering with caching and skipping turned | |
| off: interpolation, updaters, stop conditions, and animation finish/cleanup run | |
| as usual. The clock follows the same frame-rate sampling and frozen-wait rounding | |
| rules described in :doc:`deep_dive`. Time comes from running those steps, not | |
| from adding up the requested animation durations. | |
| The manager ignores cached movie segments, animation-range selection, and skip | |
| flags, including those set by sections. All play calls reached by the scene's | |
| Python code are evaluated. | |
| Evaluation calls ``setup()``, ``construct()`` and ``tear_down()``. Play and wait | |
| calls use the same animation loop as rendering with caching and skipping turned | |
| off: interpolation, updaters, stop conditions, and animation finish/cleanup run | |
| as usual. The clock follows the same frame-rate sampling and frozen-wait rounding | |
| rules described in :doc:`deep_dive`. |
Removing redundant info.
| * Constructing geometry, typesetting text, loading images, and running updaters | ||
| still take time. Evaluation does not make expensive Python code instantaneous. |
There was a problem hiding this comment.
I would remove this bullet point - let's assume the reader is already aware that Python code is still subject to the laws of space and time.
| * Choose the renderer and frame rate before constructing the scene. Normal | ||
| configuration validation and renderer-specific mobject classes still apply. |
There was a problem hiding this comment.
Is this not the case always? If so, it probably doesn't need a separate callout.
| by evaluation. User code still runs and can perform its own I/O: this is | ||
| not a sandbox. |
There was a problem hiding this comment.
IMO this "this is not a sandbox" warning is fine to include in the thematic guide, but I think it can be left out of the docstring. At this point in the docstring, the user is already aware that the scene's methods (and therefore the code inside it) will be executed.
| or getattr(self.renderer, "_target", None) is not None | ||
| or getattr(self.renderer, "_context", None) is not None | ||
| ): | ||
| raise RuntimeError("No-raster evaluation requires a cold, unused Scene.") |
There was a problem hiding this comment.
| raise RuntimeError("No-raster evaluation requires a cold, unused Scene.") | |
| raise RuntimeError("No-raster evaluation requires an unused Scene.") |
| the matrix corresponding to the current position of the camera | ||
| """ | ||
| self.rotation_matrix = self.generate_rotation_matrix() | ||
| """Recompute the cached rotation matrix from the camera's current angles.""" |
There was a problem hiding this comment.
| """Recompute the cached rotation matrix from the camera's current angles.""" | |
| """Recompute and cache the rotation matrix from the camera's current angles.""" |
Excluded and cached plays produce no frames, but they still called renderer.render() for every sample, drew and read back frozen frames, prepared Cairo's static frame, and presented frozen frames. Nothing consumed the result: still output redraws explicitly in scene_finished, and OpenGL never presented skipped frames. A skipped wait_until drew 900 frames at 15 fps, and an OpenGL window busy-waited for the wall-clock duration of a skipped frozen wait. Shortcut plays now take the same path evaluation does, which Manager.evaluate already established does not change scene state. Eliding those draws exposed a latent cache-identity bug. Vertex buffers, the triangulation, shader-wrapper buffers and the OpenGL camera's view matrices are all filled in on first draw, and hashing walks __dict__, so a play hashed differently depending on whether an earlier play in the same run had been drawn. Classes now declare such derived state in _hash_excluded_attributes, and cached_property values are excluded automatically; ThreeDCamera's existing inline exclusion moves to the same mechanism. The collected names are memoized per MRO rather than per class, because ConvertToOpenGL rebases already created classes when config.renderer changes.
Manager.add_sound dropped every request made while skip_animations was set, which conflates two unrelated situations. A cache hit fast-forwards a play but its span is still present in the artifact, so dropping the sound silently changed a scene's audio depending on whether its partial movie files happened to exist: a plain re-render of a three-sound scene kept only the first. Exclusion by -n, a skipped section, or still output is different. Those plays are absent from the artifact, whose timeline then covers only part of the scene, while sound is placed at scene time. Such requests are still dropped, because positioning them needs a map from scene time onto the selected output span. That mapping, and the mixer it belongs to, are separate work; add_sound and the deep dive now state the limitation instead of leaving it implicit. Exclusion is therefore tracked separately from skipping rather than inferred from the overloaded flag.
64021f9 to
dbd8b90
Compare
pydub shells out to the ffprobe binary, which is not installed on CI runners, so test_reused_segments_produce_an_identical_audio_track failed on every platform with FileNotFoundError. PyAV is already a manim dependency, and the suite already decodes video with it. The assertion now compares decoded samples rather than pydub's duration and loudness, which still catches the bug this covers: dropping sounds on a cache hit removes roughly two thirds of the samples.
Memoizing on the MRO kept every class the hasher ever saw alive, which matters for scenes that build classes per run. The names are now memoized weakly per class and discarded by the renderer setter, the one place that rebases classes, so a class that merely inherits from a rebased one is recollected too.
Overview: What does this pull request change?
Stacked on #5004.
Adds
Manager.evaluate()to run a scene's animation logic without drawing its frames or producing media. This is useful for inspecting animation time, mobject state, and values computed by updaters without first rendering a movie.Evaluation calls
setup(),construct(), andtear_down()through the same animation loop used by normal rendering. It runs interpolation, updaters, stop checks, and animation finish/cleanup, with the same frame-rate sampling and frozen-wait rounding. It does not estimate elapsed time by summing requested durations.Motivation and Explanation: Why and how do your changes improve the library?
Skipping animations or reusing cached movies takes shortcuts through Python state updates. Neither is a substitute for running all animation steps without rendering. This PR separates that use case from output: evaluation ignores movie caches, animation-range selection, and skip flags, while running the scene's animation logic normally.
The manager does not create a file writer, encoder, Cairo drawing buffer, OpenGL context, preview window, or file log for evaluation. It also skips output finalization and automatic last-frame capture.
Usage and limits
next_section(),add_subcaption(), andadd_sound()calls are allowed but produce no output or report. Sound files are neither checked nor decoded. Timeline export is a separate follow-up.scene.get_image()call can draw the resulting state afterward; that is a separate rendering operation.This is not a sandbox or a guarantee of zero I/O. Geometry construction, typesetting, image loading, and arbitrary user code still run. Code that creates independent renderers, scenes, files, or processes is not isolated by this API. Ordinary Python-side Cairo and OpenGL mobject updates are supported; shader-only visual effects are not evaluated.
Cairo 3D projection
Camera projection queries now use the current angle trackers without depending on a preceding draw. The rotation matrix is computed when needed and excluded from the camera's cache key, so querying it does not invalidate cached movies.
rotation_matrixis read-only: change the angle trackers or customizegenerate_rotation_matrix()instead of assigning the cached matrix.Links to added or changed documentation pages
docs/source/guides/evaluation.rst.Manager.evaluate()and camera API descriptions.