Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6a96e2a
Remove Scene decorators and consolidate play() and wait()
eulertour Oct 5, 2020
e42c48f
Add CairoRenderer, move Scene.get_frame()
eulertour Oct 5, 2020
aef7eec
Move Scene.update_frame()
eulertour Oct 5, 2020
b1f8f6b
Move Scene.freeze_background() and Camera.extract_mobject_family_memb…
eulertour Oct 5, 2020
2555bc7
Move Scene.update_skipping_status()
eulertour Oct 6, 2020
0fd9074
Remove Scene.get_mobjects_from_last_animation()
eulertour Oct 6, 2020
a7fb0ee
Remove Scene.force_skipping()
eulertour Oct 7, 2020
304fd4d
Move Scene.add_frame()
eulertour Oct 7, 2020
7af7998
Move Scene.show_frame()
eulertour Oct 7, 2020
c74256c
Move more stuff out of Scene
eulertour Oct 9, 2020
d9eee53
Move Scene.file_writer
eulertour Oct 9, 2020
eddbb06
Remove some Scene references from SceneFileWriter
eulertour Oct 9, 2020
f8a55aa
Update ThreeDScene, move Scene.camera
eulertour Oct 9, 2020
97f0715
Update Camera test
eulertour Oct 9, 2020
cfcdca0
Merge remote-tracking branch 'origin/master' into renderer-refactor
eulertour Oct 10, 2020
cab3396
Use wait-specific caching function
eulertour Oct 10, 2020
067f0c1
Add renderer/__init__.py
eulertour Oct 10, 2020
a74313f
Update Camera variants
eulertour Oct 10, 2020
57a2ead
Move caching functions
eulertour Oct 11, 2020
4051ee1
Pass Scene to play() and wait()
eulertour Oct 11, 2020
ca618a6
Remove scene references from CairoRenderer
eulertour Oct 11, 2020
200d83c
Initialize CairoRenderer without a Scene
eulertour Oct 11, 2020
e7a38a9
Decouple CairoRenderer from Scene
eulertour Oct 11, 2020
7286453
Pass scene argument to play and wait
eulertour Oct 11, 2020
e00f7b3
Restore --save_last_frame functionality
eulertour Oct 12, 2020
d06faaf
Merge remote-tracking branch 'origin/master' into renderer-refactor
eulertour Oct 13, 2020
44a0dee
Address review comments
eulertour Oct 15, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/examples/3d.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
]), v_min=0, v_max=TAU, u_min=-PI / 2, u_max=PI / 2,
checkerboard_colors=[RED_D, RED_E], resolution=(15, 32)
)
self.camera.light_source.move_to(3*IN) # changes the source of the light
self.renderer.camera.light_source.move_to(3*IN) # changes the source of the light
self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)
self.add(axes, sphere)

Expand Down
2 changes: 2 additions & 0 deletions manim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from .animation.transform import *
from .animation.update import *

from .renderer.cairo_renderer import *

from .camera.camera import *
from .camera.mapping_camera import *
from .camera.moving_camera import *
Expand Down
10 changes: 2 additions & 8 deletions manim/__main__.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import inspect
import os
import platform
import subprocess as sp
import sys
import re
import traceback
import importlib.util
import types

from . import constants, logger, console, file_writer_config
from . import logger, file_writer_config
from .config.config import camera_config, args
from .config import cfg_subcmds
from .utils.module_ops import (
get_module,
get_scene_classes_from_module,
get_scenes_to_render,
)
from .scene.scene import Scene
from .utils.file_ops import open_file as open_media_file
from .grpc.impl import frame_server_impl

Expand Down Expand Up @@ -79,7 +73,7 @@ def main():
else:
scene = SceneClass()
scene.render()
open_file_if_needed(scene.file_writer)
open_file_if_needed(scene.renderer.file_writer)
except Exception:
print("\n\n")
traceback.print_exc()
Expand Down
38 changes: 7 additions & 31 deletions manim/camera/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from ..utils.simple_functions import fdiv
from ..utils.space_ops import angle_of_vector
from ..utils.space_ops import get_norm
from ..utils.family import extract_mobject_family_members


class Camera(object):
Expand Down Expand Up @@ -66,7 +67,7 @@ class Camera(object):
"use_z_index": True,
}

def __init__(self, background=None, **kwargs):
def __init__(self, video_quality_config, background=None, **kwargs):
"""Initialises the Camera.

Parameters
Expand Down Expand Up @@ -363,33 +364,6 @@ def set_frame_to_background(self, background):

####

# TODO, it's weird that this is part of camera.
# Clearly it should live elsewhere.
def extract_mobject_family_members(self, mobjects, only_those_with_points=False):
"""Returns a list of the types of mobjects and
their family members present.

Parameters
----------
mobjects : Mobject
The Mobjects currently in the Scene
only_those_with_points : bool, optional
Whether or not to only do this for
those mobjects that have points. By default False

Returns
-------
list
list of the mobjects and family members.
"""
if only_those_with_points:
method = Mobject.family_members_with_points
else:
method = Mobject.get_family
if self.use_z_index:
mobjects = sorted(mobjects, key=lambda m: m.z_index)
return remove_list_redundancies(list(it.chain(*[method(m) for m in mobjects])))

def get_mobjects_to_display(
self, mobjects, include_submobjects=True, excluded_mobjects=None
):
Expand All @@ -411,11 +385,13 @@ def get_mobjects_to_display(
list of mobjects
"""
if include_submobjects:
mobjects = self.extract_mobject_family_members(
mobjects, only_those_with_points=True
mobjects = extract_mobject_family_members(
mobjects, use_z_index=self.use_z_index, only_those_with_points=True
)
if excluded_mobjects:
all_excluded = self.extract_mobject_family_members(excluded_mobjects)
all_excluded = extract_mobject_family_members(
excluded_mobjects, use_z_index=self.use_z_index
)
mobjects = list_difference_update(mobjects, all_excluded)
return mobjects

Expand Down
4 changes: 2 additions & 2 deletions manim/camera/moving_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class MovingCamera(Camera):
"default_frame_stroke_width": 0,
}

def __init__(self, frame=None, **kwargs):
def __init__(self, video_quality_config, frame=None, **kwargs):
"""
frame is a Mobject, (should almost certainly be a rectangle)
determining which region of space the camera displys
Expand All @@ -59,7 +59,7 @@ def __init__(self, frame=None, **kwargs):
self.default_frame_stroke_width,
)
self.frame = frame
Camera.__init__(self, **kwargs)
Camera.__init__(self, video_quality_config, **kwargs)

# TODO, make these work for a rotated frame
@property
Expand Down
11 changes: 7 additions & 4 deletions manim/camera/multi_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ class MultiCamera(MovingCamera):
"allow_cameras_to_capture_their_own_display": False,
}

def __init__(self, *image_mobjects_from_cameras, **kwargs):
def __init__(
self, video_quality_config, image_mobjects_from_cameras=None, **kwargs
):
"""Initalises the MultiCamera

Parameters:
Expand All @@ -25,9 +27,10 @@ def __init__(self, *image_mobjects_from_cameras, **kwargs):
Any valid keyword arguments of MovingCamera.
"""
self.image_mobjects_from_cameras = []
for imfc in image_mobjects_from_cameras:
self.add_image_mobject_from_camera(imfc)
MovingCamera.__init__(self, **kwargs)
if image_mobjects_from_cameras is not None:
for imfc in image_mobjects_from_cameras:
self.add_image_mobject_from_camera(imfc)
MovingCamera.__init__(self, video_quality_config, **kwargs)

def add_image_mobject_from_camera(self, image_mobject_from_camera):
"""Adds an ImageMobject that's been obtained from the camera
Expand Down
2 changes: 1 addition & 1 deletion manim/grpc/impl/frame_server_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def GetFrameAtTime(self, request, context):
selected_scene.static_image,
)
serialized_mobject_list, duration = selected_scene.add_frame(
selected_scene.get_frame()
selected_scene.renderer.get_frame()
)
resp = list_to_frame_response(
selected_scene, duration, serialized_mobject_list
Expand Down
Empty file added manim/renderer/__init__.py
Empty file.
215 changes: 215 additions & 0 deletions manim/renderer/cairo_renderer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import numpy as np
from .. import config, camera_config, file_writer_config
from ..utils.iterables import list_update
from ..utils.exceptions import EndSceneEarlyException
from ..constants import DEFAULT_WAIT_TIME
from ..scene.scene_file_writer import SceneFileWriter
from ..utils.caching import handle_caching_play, handle_caching_wait
from ..camera.camera import Camera


def pass_scene_reference(func):
def wrapper(self, scene, *args, **kwargs):
func(self, scene, *args, **kwargs)

return wrapper


def handle_play_like_call(func):
"""
This method is used internally to wrap the
passed function, into a function that
actually writes to the video stream.
Simultaneously, it also adds to the number
of animations played.

Parameters
----------
func : function
The play() like function that has to be
written to the video file stream.

Returns
-------
function
The play() like function that can now write
to the video file stream.
"""

def wrapper(self, scene, *args, **kwargs):
allow_write = not file_writer_config["skip_animations"]
self.file_writer.begin_animation(allow_write)
func(self, scene, *args, **kwargs)
self.file_writer.end_animation(allow_write)
self.num_plays += 1

return wrapper


class CairoRenderer:
"""A renderer using Cairo.

num_plays : Number of play() functions in the scene.
time: time elapsed since initialisation of scene.
"""

def __init__(self, camera_class=None, **kwargs):
# All of the following are set to EITHER the value passed via kwargs,
# OR the value stored in the global config dict at the time of
# _instance construction_. Before, they were in the CONFIG dict, which
# is a class attribute and is defined at the time of _class
# definition_. This did not allow for creating two Cameras with
# different configurations in the same session.
self.file_writer = None
self.video_quality_config = {}
for attr in [
"pixel_height",
"pixel_width",
"frame_height",
"frame_width",
"frame_rate",
]:
self.video_quality_config[attr] = kwargs.get(attr, config[attr])
camera_cls = camera_class if camera_class is not None else Camera
self.camera = camera_cls(self.video_quality_config, **camera_config)
self.original_skipping_status = file_writer_config["skip_animations"]
self.animations_hashes = []
self.num_plays = 0
self.time = 0

def init(self, scene):
self.file_writer = SceneFileWriter(
self,
self.video_quality_config,
scene.__class__.__name__,
**file_writer_config,
)

@pass_scene_reference
@handle_caching_play
@handle_play_like_call
def play(self, scene, *args, **kwargs):
scene.play_internal(*args, **kwargs)

@pass_scene_reference
@handle_caching_wait
@handle_play_like_call
def wait(self, scene, duration=DEFAULT_WAIT_TIME, stop_condition=None):
scene.wait_internal(duration=duration, stop_condition=stop_condition)

def update_frame( # TODO Description in Docstring
self,
scene,
mobjects=None,
background=None,
include_submobjects=True,
ignore_skipping=True,
**kwargs,
):
"""Update the frame.

Parameters
----------
mobjects: list, optional
list of mobjects

background: np.ndarray, optional
Pixel Array for Background.

include_submobjects: bool, optional

ignore_skipping : bool, optional

**kwargs

"""
if file_writer_config["skip_animations"] and not ignore_skipping:
return
if mobjects is None:
mobjects = list_update(
scene.mobjects,
scene.foreground_mobjects,
)
if background is not None:
self.camera.set_frame_to_background(background)
else:
self.camera.reset()

kwargs["include_submobjects"] = include_submobjects
self.camera.capture_mobjects(mobjects, **kwargs)

def get_frame(self):
"""
Gets the current frame as NumPy array.

Returns
-------
np.array
NumPy array of pixel values of each pixel in screen.
The shape of the array is height x width x 3
"""
return np.array(self.camera.pixel_array)

def add_frame(self, frame, num_frames=1):
"""
Adds a frame to the video_file_stream

Parameters
----------
frame : numpy.ndarray
The frame to add, as a pixel array.
num_frames: int
The number of times to add frame.
"""
dt = 1 / self.camera.frame_rate
self.time += num_frames * dt
if file_writer_config["skip_animations"]:
return
for _ in range(num_frames):
self.file_writer.write_frame(frame)

def show_frame(self):
"""
Opens the current frame in the Default Image Viewer
of your system.
"""
self.update_frame(ignore_skipping=True)
self.camera.get_image().show()

def update_skipping_status(self):
"""
This method is used internally to check if the current
animation needs to be skipped or not. It also checks if
the number of animations that were played correspond to
the number of animations that need to be played, and
raises an EndSceneEarlyException if they don't correspond.
"""
if file_writer_config["from_animation_number"]:
if self.num_plays < file_writer_config["from_animation_number"]:
file_writer_config["skip_animations"] = True
if file_writer_config["upto_animation_number"]:
if self.num_plays > file_writer_config["upto_animation_number"]:
file_writer_config["skip_animations"] = True
raise EndSceneEarlyException()

def revert_to_original_skipping_status(self):
"""
Forces the scene to go back to its original skipping status,
by setting skip_animations to whatever it reads
from original_skipping_status.

Returns
-------
Scene
The Scene, with the original skipping status.
"""
if hasattr(self, "original_skipping_status"):
file_writer_config["skip_animations"] = self.original_skipping_status
return self

def finish(self, scene):
file_writer_config["skip_animations"] = False
self.file_writer.finish()
if file_writer_config["save_last_frame"]:
self.update_frame(scene, ignore_skipping=True)
self.file_writer.save_final_image(self.camera.get_image())
1 change: 0 additions & 1 deletion manim/scene/js_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ def progress_through_animations(self):
logger.error(e)
self.animation_finished.wait()

@scene.handle_play_like_call
def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None):
self.update_mobjects(dt=0) # Any problems with this?
self.animations = []
Expand Down
Loading