Merge branch 'master' of github.com:3b1b/manim into video-work

This commit is contained in:
Grant Sanderson
2021-10-24 09:48:56 -07:00
3 changed files with 52 additions and 76 deletions

View File

@ -6,7 +6,7 @@ Manim's documentation
Manim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos Manim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos
at `3Blue1Brown <https://www.3blue1brown.com/>`_. at `3Blue1Brown <https://www.3blue1brown.com/>`_.
And here is a Chinese version of this documentation: https://docs.manim.org.cn/shaders And here is a Chinese version of this documentation: https://docs.manim.org.cn/
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2

View File

@ -345,10 +345,7 @@ class VMobjectFromSVGPathstring(VMobject):
self.triangulation = np.load(tris_filepath) self.triangulation = np.load(tris_filepath)
self.needs_new_triangulation = False self.needs_new_triangulation = False
else: else:
self.relative_point = np.array(ORIGIN) self.handle_commands()
for command, coord_string in self.get_commands_and_coord_strings():
new_points = self.string_to_points(command, coord_string)
self.handle_command(command, new_points)
if self.should_subdivide_sharp_curves: if self.should_subdivide_sharp_curves:
# For a healthy triangulation later # For a healthy triangulation later
self.subdivide_sharp_curves() self.subdivide_sharp_curves()
@ -370,64 +367,40 @@ class VMobjectFromSVGPathstring(VMobject):
re.split(pattern, self.path_string)[1:] re.split(pattern, self.path_string)[1:]
) )
def handle_command(self, command, new_points): def handle_commands(self):
if command.islower(): relative_point = ORIGIN
# Treat it as a relative command for command, coord_string in self.get_commands_and_coord_strings():
if command == "a": func, number_types_str = self.command_to_function(command)
# Only the last `self.dim` columns refer to points upper_command = command.upper()
new_points[:, -self.dim:] += self.relative_point if upper_command == "Z":
else: func() # `close_path` takes no arguments
new_points += self.relative_point continue
func, n_points = self.command_to_function(command) number_types = np.array(list(number_types_str))
command_points = new_points[:n_points] n_numbers = len(number_types_str)
if command.upper() == "A": number_groups = np.array(string_to_numbers(coord_string)).reshape((-1, n_numbers))
func(*command_points[0][:-self.dim], np.array(command_points[0][-self.dim:]))
else:
func(*command_points)
leftover_points = new_points[n_points:]
# Recursively handle the rest of the points for numbers in number_groups:
if len(leftover_points) > 0: if command.islower():
if command.upper() == "M": # Treat it as a relative command
# Treat following points as relative line coordinates numbers[number_types == "x"] += relative_point[0]
command = "l" numbers[number_types == "y"] += relative_point[1]
if command.islower():
if command == "a": if upper_command == "A":
leftover_points[:, -self.dim:] -= self.relative_point args = [*numbers[:5], np.array([*numbers[5:7], 0.0])]
elif upper_command == "H":
args = [np.array([numbers[0], relative_point[1], 0.0])]
elif upper_command == "V":
args = [np.array([relative_point[0], numbers[0], 0.0])]
else: else:
leftover_points -= self.relative_point args = list(np.hstack((
self.relative_point = self.get_last_point() numbers.reshape((-1, 2)), np.zeros((n_numbers // 2, 1))
self.handle_command(command, leftover_points) )))
else: func(*args)
# Command is over, reset for future relative commands relative_point = self.get_last_point()
self.relative_point = self.get_last_point()
def string_to_points(self, command, coord_string):
numbers = string_to_numbers(coord_string)
if command.upper() == "A":
# Only the last `self.dim` columns refer to points
# Each "point" returned here has a size of `(5 + self.dim)`
params = np.array(numbers).reshape((-1, 7))
result = np.zeros((params.shape[0], 5 + self.dim))
result[:, :7] = params
return result
if command.upper() in ["H", "V"]:
i = {"H": 0, "V": 1}[command.upper()]
xy = np.zeros((len(numbers), 2))
xy[:, i] = numbers
if command.isupper():
xy[:, 1 - i] = self.relative_point[1 - i]
else:
xy = np.array(numbers).reshape((-1, 2))
result = np.zeros((xy.shape[0], self.dim))
result[:, :2] = xy
return result
def add_elliptical_arc_to(self, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, point): def add_elliptical_arc_to(self, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, point):
"""
In fact, this method only suits 2d VMobjects.
"""
def close_to_zero(a, threshold=1e-5): def close_to_zero(a, threshold=1e-5):
return abs(a) < threshold return abs(a) < threshold
@ -536,19 +509,19 @@ class VMobjectFromSVGPathstring(VMobject):
def get_command_to_function_map(self): def get_command_to_function_map(self):
""" """
Associates svg command to VMobject function, and Associates svg command to VMobject function, and
the number of arguments it takes in the types of arguments it takes in
""" """
return { return {
"M": (self.start_new_path, 1), "M": (self.start_new_path, "xy"),
"L": (self.add_line_to, 1), "L": (self.add_line_to, "xy"),
"H": (self.add_line_to, 1), "H": (self.add_line_to, "x"),
"V": (self.add_line_to, 1), "V": (self.add_line_to, "y"),
"C": (self.add_cubic_bezier_curve_to, 3), "C": (self.add_cubic_bezier_curve_to, "xyxyxy"),
"S": (self.add_smooth_cubic_curve_to, 2), "S": (self.add_smooth_cubic_curve_to, "xyxy"),
"Q": (self.add_quadratic_bezier_curve_to, 2), "Q": (self.add_quadratic_bezier_curve_to, "xyxy"),
"T": (self.add_smooth_curve_to, 1), "T": (self.add_smooth_curve_to, "xy"),
"A": (self.add_elliptical_arc_to, 1), "A": (self.add_elliptical_arc_to, "-----xy"),
"Z": (self.close_path, 0), "Z": (self.close_path, ""),
} }
def get_original_path_string(self): def get_original_path_string(self):

View File

@ -129,15 +129,18 @@ class SingleStringTex(VMobject):
def balance_braces(self, tex): def balance_braces(self, tex):
""" """
Makes Tex resiliant to unmatched { at start Makes Tex resiliant to unmatched braces
""" """
num_lefts, num_rights = [tex.count(char) for char in "{}"] num_unclosed_brackets = 0
while num_rights > num_lefts: for char in tex:
tex = "{" + tex if char == "{":
num_lefts += 1 num_unclosed_brackets += 1
while num_lefts > num_rights: elif char == "}":
tex = tex + "}" if num_unclosed_brackets == 0:
num_rights += 1 tex = "{" + tex
else:
num_unclosed_brackets -= 1
tex += num_unclosed_brackets * "}"
return tex return tex
def get_tex(self): def get_tex(self):