From ded3f7e26f5a88f6157951205eb54c330d5ca233 Mon Sep 17 00:00:00 2001 From: snoyer Date: Sun, 2 Feb 2025 15:30:23 +0400 Subject: [PATCH] improve transformations handling (#42) --- ocpsvg/svg.py | 51 +++++++++++++++++++++++++++++++---------------- tests/test_svg.py | 34 ++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 20 deletions(-) diff --git a/ocpsvg/svg.py b/ocpsvg/svg.py index fa40218..a6c1e06 100755 --- a/ocpsvg/svg.py +++ b/ocpsvg/svg.py @@ -21,11 +21,11 @@ import svgelements from OCP.BRepAdaptor import BRepAdaptor_Curve -from OCP.BRepBuilderAPI import BRepBuilderAPI_Transform +from OCP.BRepBuilderAPI import BRepBuilderAPI_GTransform, BRepBuilderAPI_Transform from OCP.Geom import Geom_BezierCurve from OCP.GeomAbs import GeomAbs_CurveType from OCP.GeomAdaptor import GeomAdaptor_Curve -from OCP.gp import gp_Ax1, gp_Dir, gp_Pnt, gp_Trsf, gp_Vec +from OCP.gp import gp_Ax1, gp_Dir, gp_GTrsf, gp_Pnt, gp_Trsf, gp_Vec from OCP.StdFail import StdFail_NotDone from OCP.TopoDS import TopoDS, TopoDS_Edge, TopoDS_Face, TopoDS_Shape, TopoDS_Wire @@ -318,27 +318,44 @@ def check_unskewed_transform() -> Union[tuple[float, float, float], None]: else: return None - element.reify() - - if isinstance(element, (svgelements.Circle, svgelements.Ellipse)) and ( - scale_and_angle := check_unskewed_transform() - ): + def transform_if_needed(wire: TopoDS_Wire): + if element.transform.is_identity(): + return wire + elif check_unskewed_transform(): + trsf = gp_Trsf() + trsf.SetValues( + *(element.transform.a, element.transform.c, 0.0, element.transform.e), # type: ignore + *(element.transform.b, element.transform.d, 0.0, element.transform.f), # type: ignore + *(0.0, 0.0, 1.0, 0.0), # type: ignore + ) + return TopoDS.Wire_s(BRepBuilderAPI_Transform(wire, trsf).Shape()) + else: + gtrsf = gp_GTrsf() + gtrsf.SetValue(1, 1, element.transform.a) # type: ignore + gtrsf.SetValue(2, 1, element.transform.b) # type: ignore + gtrsf.SetValue(1, 2, element.transform.c) # type: ignore + gtrsf.SetValue(2, 2, element.transform.d) # type: ignore + gtrsf.SetValue(1, 4, element.transform.e) # type: ignore + gtrsf.SetValue(1, 4, element.transform.f) # type: ignore + return TopoDS.Wire_s(BRepBuilderAPI_GTransform(wire, gtrsf).Shape()) + + if isinstance(element, (svgelements.Circle, svgelements.Ellipse)): cx = float(element.cx) # type: ignore cy = float(element.cy) # type: ignore - origin = svgelements.Point(cx, cy) * element.transform - center = gp_Pnt(origin.real, origin.imag, 0) # type: ignore - scale_x, scale_y, angle = scale_and_angle - r1 = float(element.rx) * scale_x # type: ignore - r2 = float(element.ry) * scale_y # type: ignore + rx = float(element.rx) # type: ignore + ry = float(element.ry) # type: ignore + center = gp_Pnt(cx, cy, 0) circle_or_ellpise = ( - circle_curve(r1, center=center) - if r1 == r2 - else ellipse_curve(r1, r2, center=center, rotation=degrees(angle)) + circle_curve(rx, center=center) + if rx == ry + else ellipse_curve(rx, ry, center=center) + ) + yield transform_if_needed( + wire_from_continuous_edges((edge_from_curve(circle_or_ellpise),)) ) - yield wire_from_continuous_edges((edge_from_curve(circle_or_ellpise),)) elif path := svg_element_to_path(element): - yield from wires_from_svg_path(path) + yield from map(transform_if_needed, wires_from_svg_path(path)) def svg_element_to_path(element: ShapeElement): diff --git a/tests/test_svg.py b/tests/test_svg.py index 0ab0fbd..ffdae0a 100755 --- a/tests/test_svg.py +++ b/tests/test_svg.py @@ -979,7 +979,7 @@ def test_circles_and_ellipses( """ buf = StringIO(svg_src) - imported = list(import_svg_document(buf)) + imported = list(import_svg_document(buf, flip_y=False)) assert len(imported) == 1 assert isinstance(imported[0], TopoDS_Wire) @@ -991,8 +991,36 @@ def test_circles_and_ellipses( curve = curves[0] assert type(curve) is curve_type assert isinstance(curve, (Geom_Circle, Geom_Ellipse)) - loc = curve.Axis().Location() - assert (loc.X(), loc.Y()) == approx(center) + x0, y0, _, x1, y1, _ = bounding_box(imported[0]).Get() + assert ((x0 + x1) / 2, (y0 + y1) / 2) == approx(center) + + +@pytest.mark.parametrize( + "element, svg_d", + [ + ( + 'circle r="40" cx="1" cy="5" transform="skewX(25)"', + "M -15.320313,-35 A 50.39898,31.746674 38.437866 0 0 -36.667969,5 " + "50.39898,31.746674 38.437866 0 0 21.984375,45 50.39898,31.746674 " + "38.437866 0 0 43.332031,5 50.39898,31.746674 38.437866 0 0 -15.320313,-35 Z", + ), + ], +) +def test_skewed_circles_and_ellipses(element: str, svg_d: str): + svg_src = f""" + + <{element} fill="none"/> + + + """ + buf = StringIO(svg_src) + imported = list(import_svg_document(buf, flip_y=False)) + assert len(imported) == 2 + assert isinstance(imported[0], TopoDS_Wire) + assert isinstance(imported[1], TopoDS_Wire) + bounds0 = bounding_box(imported[0]).Get() + bounds1 = bounding_box(imported[1]).Get() + assert bounds0 == approx(bounds1, abs=1e-3) @dataclass