From f314054081e26cd0012e49fd49e425851ab40c70 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 14 Jan 2021 15:07:35 -1000 Subject: [PATCH 1/9] Small cleanup/reorganizing of Mobject methods --- manimlib/mobject/mobject.py | 237 ++++++++++++++++++------------------ 1 file changed, 116 insertions(+), 121 deletions(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 14b2b027..34ab6dc8 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1,7 +1,5 @@ -from functools import reduce import copy import itertools as it -import operator as op import random import sys import moderngl @@ -31,9 +29,6 @@ from manimlib.shader_wrapper import ShaderWrapper from manimlib.shader_wrapper import get_colormap_code -# TODO: Explain array_attrs -# TODO: Incorporate shader defaults - class Mobject(object): """ Mathematical Object @@ -298,6 +293,84 @@ class Mobject(object): self.set_submobjects(list_update(self.submobjects, mobject_attrs)) return self + # Submobject organization + + def arrange(self, direction=RIGHT, center=True, **kwargs): + for m1, m2 in zip(self.submobjects, self.submobjects[1:]): + m2.next_to(m1, direction, **kwargs) + if center: + self.center() + return self + + def arrange_in_grid(self, n_rows=None, n_cols=None, + buff=None, + h_buff=None, + v_buff=None, + buff_ratio=None, + h_buff_ratio=0.5, + v_buff_ratio=0.5, + aligned_edge=ORIGIN, + fill_rows_first=True): + submobs = self.submobjects + if n_rows is None and n_cols is None: + n_rows = int(np.sqrt(len(submobs))) + if n_rows is None: + n_rows = len(submobs) // n_cols + if n_cols is None: + n_cols = len(submobs) // n_rows + + if buff is not None: + h_buff = buff + v_buff = buff + else: + if buff_ratio is not None: + v_buff_ratio = buff_ratio + h_buff_ratio = buff_ratio + if h_buff is None: + h_buff = h_buff_ratio * self[0].get_width() + if v_buff is None: + v_buff = v_buff_ratio * self[0].get_height() + + x_unit = h_buff + max([sm.get_width() for sm in submobs]) + y_unit = v_buff + max([sm.get_height() for sm in submobs]) + + for index, sm in enumerate(submobs): + if fill_rows_first: + x, y = index % n_cols, index // n_cols + else: + x, y = index // n_rows, index % n_rows + sm.move_to(ORIGIN, aligned_edge) + sm.shift(x * x_unit * RIGHT + y * y_unit * DOWN) + self.center() + return self + + def get_grid(self, n_rows, n_cols, height=None, **kwargs): + """ + Returns a new mobject containing multiple copies of this one + arranged in a grid + """ + grid = self.get_group_class()( + *(self.copy() for n in range(n_rows * n_cols)) + ) + grid.arrange_in_grid(n_rows, n_cols, **kwargs) + if height is not None: + grid.set_height(height) + return grid + + def sort(self, point_to_num_func=lambda p: p[0], submob_func=None): + if submob_func is not None: + self.submobjects.sort(key=submob_func) + else: + self.submobjects.sort(key=lambda m: point_to_num_func(m.get_center())) + return self + + def shuffle(self, recurse=False): + if recurse: + for submob in self.submobjects: + submob.shuffle(recurse=True) + random.shuffle(self.submobjects) + return self + # Copying def copy(self): @@ -373,16 +446,16 @@ class Mobject(object): self.has_updaters = False self.updating_suspended = False - def update(self, dt=0, recursive=True): + def update(self, dt=0, recurse=True): if not self.has_updaters or self.updating_suspended: return self for updater in self.time_based_updaters: updater(self, dt) for updater in self.non_time_updaters: updater(self) - if recursive: + if recurse: for submob in self.submobjects: - submob.update(dt, recursive) + submob.update(dt, recurse) return self def get_time_based_updaters(self): @@ -419,13 +492,13 @@ class Mobject(object): updater_list.remove(update_function) return self - def clear_updaters(self, recursive=True): + def clear_updaters(self, recurse=True): self.time_based_updaters = [] self.non_time_updaters = [] - if recursive: + if recurse: for submob in self.submobjects: submob.clear_updaters() - self.suspend_updating(recursive) + self.suspend_updating(recurse) return self def match_updaters(self, mobject): @@ -434,22 +507,22 @@ class Mobject(object): self.add_updater(updater) return self - def suspend_updating(self, recursive=True): + def suspend_updating(self, recurse=True): self.updating_suspended = True - if recursive: + if recurse: for submob in self.submobjects: - submob.suspend_updating(recursive) + submob.suspend_updating(recurse) return self - def resume_updating(self, recursive=True, call_updater=True): + def resume_updating(self, recurse=True, call_updater=True): self.updating_suspended = False - if recursive: + if recurse: for submob in self.submobjects: - submob.resume_updating(recursive) + submob.resume_updating(recurse) for parent in self.parents: - parent.resume_updating(recursive=False, call_updater=False) + parent.resume_updating(recurse=False, call_updater=False) if call_updater: - self.update(dt=0, recursive=recursive) + self.update(dt=0, recurse=recurse) return self def refresh_has_updater_status(self): @@ -744,30 +817,6 @@ class Mobject(object): self.shift(start - curr_start) return self - # Background rectangle - - def add_background_rectangle(self, color=BLACK, opacity=0.75, **kwargs): - # TODO, this does not behave well when the mobject has points, - # since it gets displayed on top - from manimlib.mobject.shape_matchers import BackgroundRectangle - self.background_rectangle = BackgroundRectangle( - self, color=color, - fill_opacity=opacity, - **kwargs - ) - self.add_to_back(self.background_rectangle) - return self - - def add_background_rectangle_to_submobjects(self, **kwargs): - for submobject in self.submobjects: - submobject.add_background_rectangle(**kwargs) - return self - - def add_background_rectangle_to_family_members_with_points(self, **kwargs): - for mob in self.family_members_with_points(): - mob.add_background_rectangle(**kwargs) - return self - # Color functions def set_rgba_array(self, color=None, opacity=None, name="rgbas", recurse=True): @@ -857,6 +906,30 @@ class Mobject(object): mob.uniforms["shadow"] = shadow return self + # Background rectangle + + def add_background_rectangle(self, color=BLACK, opacity=0.75, **kwargs): + # TODO, this does not behave well when the mobject has points, + # since it gets displayed on top + from manimlib.mobject.shape_matchers import BackgroundRectangle + self.background_rectangle = BackgroundRectangle( + self, color=color, + fill_opacity=opacity, + **kwargs + ) + self.add_to_back(self.background_rectangle) + return self + + def add_background_rectangle_to_submobjects(self, **kwargs): + for submobject in self.submobjects: + submobject.add_background_rectangle(**kwargs) + return self + + def add_background_rectangle_to_family_members_with_points(self, **kwargs): + for mob in self.family_members_with_points(): + mob.add_background_rectangle(**kwargs) + return self + # Getters def get_bounding_box_point(self, direction): @@ -1037,84 +1110,6 @@ class Mobject(object): def get_group_class(self): return Group - # Submobject organization - - def arrange(self, direction=RIGHT, center=True, **kwargs): - for m1, m2 in zip(self.submobjects, self.submobjects[1:]): - m2.next_to(m1, direction, **kwargs) - if center: - self.center() - return self - - def arrange_in_grid(self, n_rows=None, n_cols=None, - buff=None, - h_buff=None, - v_buff=None, - buff_ratio=None, - h_buff_ratio=0.5, - v_buff_ratio=0.5, - aligned_edge=ORIGIN, - fill_rows_first=True): - submobs = self.submobjects - if n_rows is None and n_cols is None: - n_rows = int(np.sqrt(len(submobs))) - if n_rows is None: - n_rows = len(submobs) // n_cols - if n_cols is None: - n_cols = len(submobs) // n_rows - - if buff is not None: - h_buff = buff - v_buff = buff - else: - if buff_ratio is not None: - v_buff_ratio = buff_ratio - h_buff_ratio = buff_ratio - if h_buff is None: - h_buff = h_buff_ratio * self[0].get_width() - if v_buff is None: - v_buff = v_buff_ratio * self[0].get_height() - - x_unit = h_buff + max([sm.get_width() for sm in submobs]) - y_unit = v_buff + max([sm.get_height() for sm in submobs]) - - for index, sm in enumerate(submobs): - if fill_rows_first: - x, y = index % n_cols, index // n_cols - else: - x, y = index // n_rows, index % n_rows - sm.move_to(ORIGIN, aligned_edge) - sm.shift(x * x_unit * RIGHT + y * y_unit * DOWN) - self.center() - return self - - def get_grid(self, n_rows, n_cols, height=None, **kwargs): - """ - Returns a new mobject containing multiple copies of this one - arranged in a grid - """ - grid = self.get_group_class()( - *(self.copy() for n in range(n_rows * n_cols)) - ) - grid.arrange_in_grid(n_rows, n_cols, **kwargs) - if height is not None: - grid.set_height(height) - return grid - - def sort(self, point_to_num_func=lambda p: p[0], submob_func=None): - if submob_func is not None: - self.submobjects.sort(key=submob_func) - else: - self.submobjects.sort(key=lambda m: point_to_num_func(m.get_center())) - return self - - def shuffle(self, recurse=False): - if recurse: - for submob in self.submobjects: - submob.shuffle(recurse=True) - random.shuffle(self.submobjects) - return self - # Alignment def align_data_and_family(self, mobject): From d4c89e520ffbbe609a6fbf1bef83c7d9e00492c5 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 15 Jan 2021 08:55:19 -1000 Subject: [PATCH 2/9] Reorganize methods and how triangulation gets refreshed --- manimlib/mobject/types/vectorized_mobject.py | 173 ++++++++++--------- 1 file changed, 92 insertions(+), 81 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 834725b2..80aaf7be 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -90,13 +90,6 @@ class VMobject(Mobject): "unit_normal": np.zeros((1, 3)) }) - def set_points(self, points): - old_points = self.get_points() - super().set_points(points) - if not np.all(points == old_points): - self.refresh_triangulation() - return self - # Colors def init_colors(self): self.set_fill( @@ -458,19 +451,6 @@ class VMobject(Mobject): self.resize_data(len(self.get_points() - 1)) self.append_points(new_points) - # TODO, how to be smart about tangents here? - def apply_function(self, function): - Mobject.apply_function(self, function) - if self.make_smooth_after_applying_functions: - self.make_smooth() - self.refresh_triangulation() - return self - - def flip(self, *args, **kwargs): - super().flip(*args, **kwargs) - self.refresh_unit_normal() - self.refresh_triangulation() - # def consider_points_equals(self, p0, p1): return get_norm(p1 - p0) < self.tolerance_for_point_equality @@ -761,6 +741,98 @@ class VMobject(Mobject): vmob.pointwise_become_partial(self, a, b) return vmob + # Related to triangulation + + def refresh_triangulation(self): + for mob in self.get_family(): + mob.needs_new_triangulation = True + return self + + def get_triangulation(self, 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 = self.get_unit_normal() + + if not self.needs_new_triangulation: + return self.triangulation + + points = self.get_points() + + if len(points) <= 1: + self.triangulation = np.zeros(0, dtype='i4') + self.needs_new_triangulation = False + return self.triangulation + + # Rotate points such that unit normal vector is OUT + # TODO, 99% of the time this does nothing. Do a check for that? + 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 = self.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]) + self.triangulation = tri_indices + self.needs_new_triangulation = False + return tri_indices + + def triggers_refreshed_triangulation(func): + def wrapper(self, *args, **kwargs): + old_points = self.get_points() + func(self, *args, **kwargs) + if not np.all(self.get_points() == old_points): + self.refresh_triangulation() + self.refresh_unit_normal() + return wrapper + + @triggers_refreshed_triangulation + def set_points(self, points): + super().set_points(points) + + @triggers_refreshed_triangulation + def set_data(self, data): + super().set_data(data) + + # TODO, how to be smart about tangents here? + @triggers_refreshed_triangulation + def apply_function(self, function): + super().apply_function(function) + if self.make_smooth_after_applying_functions: + self.make_smooth() + return self + + @triggers_refreshed_triangulation + def flip(self, *args, **kwargs): + super().flip(*args, **kwargs) + # For shaders def init_shader_data(self): self.fill_data = np.zeros(0, dtype=self.fill_dtype) @@ -849,67 +921,6 @@ class VMobject(Mobject): return self.stroke_data - def refresh_triangulation(self): - for mob in self.get_family(): - mob.needs_new_triangulation = True - return self - - def get_triangulation(self, 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 = self.get_unit_normal() - - if not self.needs_new_triangulation: - return self.triangulation - - points = self.get_points() - - if len(points) <= 1: - self.triangulation = np.zeros(0, dtype='i4') - self.needs_new_triangulation = False - return self.triangulation - - # Rotate points such that unit normal vector is OUT - # TODO, 99% of the time this does nothing. Do a check for that? - 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 = self.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]) - self.triangulation = tri_indices - self.needs_new_triangulation = False - return tri_indices - def get_fill_shader_data(self): points = self.get_points() if len(self.fill_data) != len(points): From 1f3b058aa9ffbd038e0881ad6e2824e514b6f9d6 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 15 Jan 2021 08:55:40 -1000 Subject: [PATCH 3/9] Simplify Point.set_location --- manimlib/mobject/mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 34ab6dc8..f1b69281 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1461,4 +1461,4 @@ class Point(Mobject): return self.get_location() def set_location(self, new_loc): - self.set_points(np.array(new_loc, ndmin=2, dtype=float)) + self.set_points([new_loc]) From 08e22cf8596d7a077669263d0af25026de96d9e2 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 15 Jan 2021 09:02:07 -1000 Subject: [PATCH 4/9] Bug fix to apply_points_function --- manimlib/mobject/mobject.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index f1b69281..db174ef7 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -141,9 +141,12 @@ class Mobject(object): about_point = self.get_bounding_box_point(about_edge) for mob in self.get_family(): - arrs = [mob.get_points()] + arrs = [] + if mob.has_points(): + arrs.append(mob.get_points()) if works_on_bounding_box: arrs.append(mob.get_bounding_box()) + for arr in arrs: if about_point is None: arr[:] = func(arr) From d37de184d2ccab4f9fd11668c5d33bb7fc77cb2d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 15 Jan 2021 09:27:51 -1000 Subject: [PATCH 5/9] Set gamma properly --- manimlib/camera/camera.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 3dfcd214..50862ad6 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -95,7 +95,7 @@ class CameraFrame(Mobject): return self.set_rotation(phi=phi) def set_gamma(self, gamma): - return self.set_rotation(phi=phi) + return self.set_rotation(gamma=gamma) def increment_theta(self, dtheta): return self.set_rotation(theta=self.euler_angles[0] + dtheta) From b423a423b523ce3f2ba491c6f80530191fa6b6b0 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 15 Jan 2021 10:16:37 -1000 Subject: [PATCH 6/9] Make customization more accessible --- custom_defaults.yml | 1 + manimlib/imports.py | 1 + manimlib/mobject/svg/text_mobject.py | 7 ++++--- manimlib/utils/customization.py | 23 +++++++++++++++++++++++ manimlib/utils/directories.py | 21 ++------------------- 5 files changed, 31 insertions(+), 22 deletions(-) create mode 100644 manimlib/utils/customization.py diff --git a/custom_defaults.yml b/custom_defaults.yml index 7fd8abdb..7c3d03f6 100644 --- a/custom_defaults.yml +++ b/custom_defaults.yml @@ -27,6 +27,7 @@ tex: # intermediate_filetype: "xdv" universal_import_line: "from manimlib.imports import *" style: + font: "Consolas" background_color: "#333333" camera_qualities: low: diff --git a/manimlib/imports.py b/manimlib/imports.py index f8a58696..cc01279d 100644 --- a/manimlib/imports.py +++ b/manimlib/imports.py @@ -77,6 +77,7 @@ from manimlib.scene.vector_space_scene import * from manimlib.utils.bezier import * from manimlib.utils.color import * from manimlib.utils.config_ops import * +from manimlib.utils.customization import * from manimlib.utils.debug import * from manimlib.utils.directories import * from manimlib.utils.images import * diff --git a/manimlib/mobject/svg/text_mobject.py b/manimlib/mobject/svg/text_mobject.py index 1a42eea0..b1718dae 100644 --- a/manimlib/mobject/svg/text_mobject.py +++ b/manimlib/mobject/svg/text_mobject.py @@ -6,6 +6,7 @@ import cairo from manimlib.constants import * from manimlib.mobject.svg.svg_mobject import SVGMobject from manimlib.utils.config_ops import digest_config +from manimlib.utils.customization import get_customization from manimlib.utils.directories import get_text_dir @@ -135,7 +136,7 @@ class Text(SVGMobject): settings = self.font + self.slant + self.weight settings += str(self.t2f) + str(self.t2s) + str(self.t2w) settings += str(self.lsh) + str(self.size) - id_str = self.text+settings + id_str = self.text + settings hasher = hashlib.sha256() hasher.update(id_str.encode()) return hasher.hexdigest()[:16] @@ -192,11 +193,11 @@ class Text(SVGMobject): lsh = self.lsh * 10 if self.font == '': - print(NOT_SETTING_FONT_MSG) + self.font = get_customization()['style']['font'] dir_name = get_text_dir() hash_name = self.text2hash() - file_name = os.path.join(dir_name, hash_name) +'.svg' + file_name = os.path.join(dir_name, hash_name) + '.svg' if os.path.exists(file_name): return file_name diff --git a/manimlib/utils/customization.py b/manimlib/utils/customization.py new file mode 100644 index 00000000..333a41c6 --- /dev/null +++ b/manimlib/utils/customization.py @@ -0,0 +1,23 @@ +import os +import tempfile + +from manimlib.config import get_custom_defaults +from manimlib.config import get_manim_dir + +CUSTOMIZATION = {} + + +def get_customization(): + if not CUSTOMIZATION: + CUSTOMIZATION.update(get_custom_defaults()) + directories = CUSTOMIZATION["directories"] + # Unless user has specified otherwise, use the system default temp + # directory for storing tex files, mobject_data, etc. + if not directories["temporary_storage"]: + directories["temporary_storage"] = tempfile.gettempdir() + + # Assumes all shaders are written into manimlib/shaders + directories["shaders"] = os.path.join( + get_manim_dir(), "manimlib", "shaders" + ) + return CUSTOMIZATION diff --git a/manimlib/utils/directories.py b/manimlib/utils/directories.py index efdaffe4..346312a0 100644 --- a/manimlib/utils/directories.py +++ b/manimlib/utils/directories.py @@ -1,28 +1,11 @@ import os -import tempfile from manimlib.utils.file_ops import guarantee_existence -from manimlib.config import get_custom_defaults -from manimlib.config import get_manim_dir - -PRE_COMPUTED_DIRS = {} +from manimlib.utils.customization import get_customization def get_directories(): - if not PRE_COMPUTED_DIRS: - custom_defaults = get_custom_defaults() - PRE_COMPUTED_DIRS.update(custom_defaults["directories"]) - - # Unless user has specified otherwise, use the system default temp - # directory for storing tex files, mobject_data, etc. - if not PRE_COMPUTED_DIRS["temporary_storage"]: - PRE_COMPUTED_DIRS["temporary_storage"] = tempfile.gettempdir() - - # Assumes all shaders are written into manimlib/shaders - PRE_COMPUTED_DIRS["shaders"] = os.path.join( - get_manim_dir(), "manimlib", "shaders" - ) - return PRE_COMPUTED_DIRS + return get_customization()["directories"] def get_temp_dir(): From 4e4a7b98868c8a90ac13456c2db9b75395eec26a Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 15 Jan 2021 10:20:50 -1000 Subject: [PATCH 7/9] Have background rectangle match background color by default --- manimlib/mobject/mobject.py | 2 +- manimlib/mobject/shape_matchers.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index db174ef7..58cec6dc 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -911,7 +911,7 @@ class Mobject(object): # Background rectangle - def add_background_rectangle(self, color=BLACK, opacity=0.75, **kwargs): + def add_background_rectangle(self, color=None, opacity=0.75, **kwargs): # TODO, this does not behave well when the mobject has points, # since it gets displayed on top from manimlib.mobject.shape_matchers import BackgroundRectangle diff --git a/manimlib/mobject/shape_matchers.py b/manimlib/mobject/shape_matchers.py index a6933f21..f9a6ba51 100644 --- a/manimlib/mobject/shape_matchers.py +++ b/manimlib/mobject/shape_matchers.py @@ -4,6 +4,7 @@ from manimlib.mobject.geometry import Rectangle from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.utils.color import Color +from manimlib.utils.customization import get_customization from manimlib.utils.config_ops import digest_config @@ -23,15 +24,16 @@ class SurroundingRectangle(Rectangle): class BackgroundRectangle(SurroundingRectangle): CONFIG = { - "color": BLACK, "stroke_width": 0, "stroke_opacity": 0, "fill_opacity": 0.75, "buff": 0 } - def __init__(self, mobject, **kwargs): - SurroundingRectangle.__init__(self, mobject, **kwargs) + def __init__(self, mobject, color=None, **kwargs): + if color is None: + color = get_customization()['style']['background_color'] + SurroundingRectangle.__init__(self, mobject, color=color, **kwargs) self.original_fill_opacity = self.fill_opacity def pointwise_become_partial(self, mobject, a, b): From c698c6ea444dbf4f99e36eb1e7b3b244a7ac99d6 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 15 Jan 2021 11:10:57 -1000 Subject: [PATCH 8/9] Fix Point.set_location --- manimlib/mobject/mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 58cec6dc..cff64ae9 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1464,4 +1464,4 @@ class Point(Mobject): return self.get_location() def set_location(self, new_loc): - self.set_points([new_loc]) + self.set_points(np.array(new_loc, ndmin=2, dtype=float)) From 2daf62ecea6e0c83f2a6d6fde137d73493a01d48 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 15 Jan 2021 11:11:07 -1000 Subject: [PATCH 9/9] Add notes to SurfaceExample --- example_scenes.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/example_scenes.py b/example_scenes.py index 510a09b3..999e3830 100644 --- a/example_scenes.py +++ b/example_scenes.py @@ -310,22 +310,33 @@ class SurfaceExample(Scene): } def construct(self): + surface_text = Text("For 3d scenes, try using surfaces") + surface_text.fix_in_frame() + surface_text.to_edge(UP) + self.add(surface_text) + self.wait(0.1) + torus1 = Torus(r1=1, r2=1) torus2 = Torus(r1=3, r2=1) sphere = Sphere(radius=3, resolution=torus1.resolution) # You can texture a surface with up to two images, which will # be interpreted as the side towards the light, and away from # the light. These can be either urls, or paths to a local file - # in whatever you've set as the iamge directory in + # in whatever you've set as the image directory in # the custom_defaults.yml file + + # day_texture = "EarthTextureMap" + # night_texture = "NightEarthTextureMap" day_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Whole_world_-_land_and_oceans.jpg/1280px-Whole_world_-_land_and_oceans.jpg" night_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/The_earth_at_night.jpg/1280px-The_earth_at_night.jpg" + surfaces = [ TexturedSurface(surface, day_texture, night_texture) for surface in [sphere, torus1, torus2] ] for mob in surfaces: + mob.shift(IN) mob.mesh = SurfaceMesh(mob) mob.mesh.set_stroke(BLUE, 1, opacity=0.5) @@ -365,12 +376,23 @@ class SurfaceExample(Scene): frame.add_updater(lambda m, dt: m.increment_theta(-0.1 * dt)) # Play around with where the light is + light_text = Text("You can move around the light source") + light_text.move_to(surface_text) + light_text.fix_in_frame() + + self.play(FadeTransform(surface_text, light_text)) light = self.camera.light_source self.add(light) light.save_state() self.play(light.move_to, 3 * IN, run_time=5) self.play(light.shift, 10 * OUT, run_time=5) - self.wait(4) + + drag_text = Text("Try clicking and dragging while pressing d") + drag_text.move_to(light_text) + drag_text.fix_in_frame() + + self.play(FadeTransform(light_text, drag_text)) + self.wait() # See https://github.com/3b1b/videos for many, many more