Fix path arc handling for SVGMobject when a matrix transform is present in the SVG (#2322)

This commit is contained in:
jkjkil4 2025-03-21 02:59:06 +08:00 committed by GitHub
parent dbfe7ac75d
commit be7d93cf40
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -8,6 +8,7 @@ import io
from pathlib import Path from pathlib import Path
from manimlib.constants import RIGHT from manimlib.constants import RIGHT
from manimlib.constants import TAU
from manimlib.logger import log from manimlib.logger import log
from manimlib.mobject.geometry import Circle from manimlib.mobject.geometry import Circle
from manimlib.mobject.geometry import Line from manimlib.mobject.geometry import Line
@ -16,8 +17,10 @@ from manimlib.mobject.geometry import Polyline
from manimlib.mobject.geometry import Rectangle from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.geometry import RoundedRectangle from manimlib.mobject.geometry import RoundedRectangle
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.bezier import quadratic_bezier_points_for_arc
from manimlib.utils.images import get_full_vector_image_path from manimlib.utils.images import get_full_vector_image_path
from manimlib.utils.iterables import hash_obj from manimlib.utils.iterables import hash_obj
from manimlib.utils.space_ops import rotation_about_z
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
@ -300,8 +303,9 @@ class VMobjectFromSVGPath(VMobject):
path_obj: se.Path, path_obj: se.Path,
**kwargs **kwargs
): ):
# Get rid of arcs # caches (transform.inverse(), rot, shift)
path_obj.approximate_arcs_with_quads() self.transform_cache: tuple[se.Matrix, np.ndarray, np.ndarray] | None = None
self.path_obj = path_obj self.path_obj = path_obj
super().__init__(**kwargs) super().__init__(**kwargs)
@ -328,6 +332,9 @@ class VMobjectFromSVGPath(VMobject):
} }
for segment in self.path_obj: for segment in self.path_obj:
segment_class = segment.__class__ segment_class = segment.__class__
if segment_class is se.Arc:
self.handle_arc(segment)
else:
func, attr_names = segment_class_to_func_map[segment_class] func, attr_names = segment_class_to_func_map[segment_class]
points = [ points = [
_convert_point_to_3d(*segment.__getattribute__(attr_name)) _convert_point_to_3d(*segment.__getattribute__(attr_name))
@ -338,3 +345,42 @@ class VMobjectFromSVGPath(VMobject):
# Get rid of the side effect of trailing "Z M" commands. # Get rid of the side effect of trailing "Z M" commands.
if self.has_new_path_started(): if self.has_new_path_started():
self.resize_points(self.get_num_points() - 2) self.resize_points(self.get_num_points() - 2)
def handle_arc(self, arc: se.Arc) -> None:
if self.transform_cache is not None:
transform, rot, shift = self.transform_cache
else:
# The transform obtained in this way considers the combined effect
# of all parent group transforms in the SVG.
# Therefore, the arc can be transformed inversely using this transform
# to correctly compute the arc path before transforming it back.
transform = se.Matrix(self.path_obj.values.get('transform', ''))
rot = np.array([
[transform.a, transform.c],
[transform.b, transform.d]
])
shift = np.array([transform.e, transform.f, 0])
transform.inverse()
self.transform_cache = (transform, rot, shift)
# Apply inverse transformation to the arc so that its path can be correctly computed
arc *= transform
# The value of n_components is chosen based on the implementation of VMobject.arc_to
n_components = int(np.ceil(8 * abs(arc.sweep) / TAU))
# Obtain the required angular segments on the unit circle
arc_points = quadratic_bezier_points_for_arc(arc.sweep, n_components)
arc_points @= np.array(rotation_about_z(arc.get_start_t())).T
# Transform to an ellipse, considering rotation and translating the ellipse center
arc_points[:, 0] *= arc.rx
arc_points[:, 1] *= arc.ry
arc_points @= np.array(rotation_about_z(arc.get_rotation().as_radians)).T
arc_points += [*arc.center, 0]
# Transform back
arc_points[:, :2] @= rot.T
arc_points += shift
self.append_points(arc_points[1:])