mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Fix path arc handling for SVGMobject
when a matrix transform is present in the SVG (#2322)
This commit is contained in:
parent
dbfe7ac75d
commit
be7d93cf40
1 changed files with 54 additions and 8 deletions
|
@ -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:])
|
||||||
|
|
Loading…
Add table
Reference in a new issue