mirror of
https://github.com/3b1b/manim.git
synced 2025-07-30 21:44:19 +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
|
||||
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::
|
||||
:maxdepth: 2
|
||||
|
@ -345,10 +345,7 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||
self.triangulation = np.load(tris_filepath)
|
||||
self.needs_new_triangulation = False
|
||||
else:
|
||||
self.relative_point = np.array(ORIGIN)
|
||||
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)
|
||||
self.handle_commands()
|
||||
if self.should_subdivide_sharp_curves:
|
||||
# For a healthy triangulation later
|
||||
self.subdivide_sharp_curves()
|
||||
@ -370,64 +367,40 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||
re.split(pattern, self.path_string)[1:]
|
||||
)
|
||||
|
||||
def handle_command(self, command, new_points):
|
||||
if command.islower():
|
||||
# Treat it as a relative command
|
||||
if command == "a":
|
||||
# Only the last `self.dim` columns refer to points
|
||||
new_points[:, -self.dim:] += self.relative_point
|
||||
else:
|
||||
new_points += self.relative_point
|
||||
def handle_commands(self):
|
||||
relative_point = ORIGIN
|
||||
for command, coord_string in self.get_commands_and_coord_strings():
|
||||
func, number_types_str = self.command_to_function(command)
|
||||
upper_command = command.upper()
|
||||
if upper_command == "Z":
|
||||
func() # `close_path` takes no arguments
|
||||
continue
|
||||
|
||||
func, n_points = self.command_to_function(command)
|
||||
command_points = new_points[:n_points]
|
||||
if command.upper() == "A":
|
||||
func(*command_points[0][:-self.dim], np.array(command_points[0][-self.dim:]))
|
||||
else:
|
||||
func(*command_points)
|
||||
leftover_points = new_points[n_points:]
|
||||
number_types = np.array(list(number_types_str))
|
||||
n_numbers = len(number_types_str)
|
||||
number_groups = np.array(string_to_numbers(coord_string)).reshape((-1, n_numbers))
|
||||
|
||||
# Recursively handle the rest of the points
|
||||
if len(leftover_points) > 0:
|
||||
if command.upper() == "M":
|
||||
# Treat following points as relative line coordinates
|
||||
command = "l"
|
||||
if command.islower():
|
||||
if command == "a":
|
||||
leftover_points[:, -self.dim:] -= self.relative_point
|
||||
for numbers in number_groups:
|
||||
if command.islower():
|
||||
# Treat it as a relative command
|
||||
numbers[number_types == "x"] += relative_point[0]
|
||||
numbers[number_types == "y"] += relative_point[1]
|
||||
|
||||
if upper_command == "A":
|
||||
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:
|
||||
leftover_points -= self.relative_point
|
||||
self.relative_point = self.get_last_point()
|
||||
self.handle_command(command, leftover_points)
|
||||
else:
|
||||
# Command is over, reset for future relative commands
|
||||
self.relative_point = self.get_last_point()
|
||||
args = list(np.hstack((
|
||||
numbers.reshape((-1, 2)), np.zeros((n_numbers // 2, 1))
|
||||
)))
|
||||
func(*args)
|
||||
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):
|
||||
"""
|
||||
In fact, this method only suits 2d VMobjects.
|
||||
"""
|
||||
def close_to_zero(a, threshold=1e-5):
|
||||
return abs(a) < threshold
|
||||
|
||||
@ -536,19 +509,19 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||
def get_command_to_function_map(self):
|
||||
"""
|
||||
Associates svg command to VMobject function, and
|
||||
the number of arguments it takes in
|
||||
the types of arguments it takes in
|
||||
"""
|
||||
return {
|
||||
"M": (self.start_new_path, 1),
|
||||
"L": (self.add_line_to, 1),
|
||||
"H": (self.add_line_to, 1),
|
||||
"V": (self.add_line_to, 1),
|
||||
"C": (self.add_cubic_bezier_curve_to, 3),
|
||||
"S": (self.add_smooth_cubic_curve_to, 2),
|
||||
"Q": (self.add_quadratic_bezier_curve_to, 2),
|
||||
"T": (self.add_smooth_curve_to, 1),
|
||||
"A": (self.add_elliptical_arc_to, 1),
|
||||
"Z": (self.close_path, 0),
|
||||
"M": (self.start_new_path, "xy"),
|
||||
"L": (self.add_line_to, "xy"),
|
||||
"H": (self.add_line_to, "x"),
|
||||
"V": (self.add_line_to, "y"),
|
||||
"C": (self.add_cubic_bezier_curve_to, "xyxyxy"),
|
||||
"S": (self.add_smooth_cubic_curve_to, "xyxy"),
|
||||
"Q": (self.add_quadratic_bezier_curve_to, "xyxy"),
|
||||
"T": (self.add_smooth_curve_to, "xy"),
|
||||
"A": (self.add_elliptical_arc_to, "-----xy"),
|
||||
"Z": (self.close_path, ""),
|
||||
}
|
||||
|
||||
def get_original_path_string(self):
|
||||
|
@ -129,15 +129,18 @@ class SingleStringTex(VMobject):
|
||||
|
||||
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 "{}"]
|
||||
while num_rights > num_lefts:
|
||||
tex = "{" + tex
|
||||
num_lefts += 1
|
||||
while num_lefts > num_rights:
|
||||
tex = tex + "}"
|
||||
num_rights += 1
|
||||
num_unclosed_brackets = 0
|
||||
for char in tex:
|
||||
if char == "{":
|
||||
num_unclosed_brackets += 1
|
||||
elif char == "}":
|
||||
if num_unclosed_brackets == 0:
|
||||
tex = "{" + tex
|
||||
else:
|
||||
num_unclosed_brackets -= 1
|
||||
tex += num_unclosed_brackets * "}"
|
||||
return tex
|
||||
|
||||
def get_tex(self):
|
||||
|
Reference in New Issue
Block a user