mirror of
https://github.com/3b1b/manim.git
synced 2025-08-01 17:29:06 +08:00
Merge branch 'master' of github.com:3b1b/manim into video-work
This commit is contained in:
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
Reference in New Issue
Block a user