Example #1
0
    def __init__(self,
                 d: str,
                 canvas_height: float,
                 transform_origin=True,
                 transformation=None):
        self.canvas_height = canvas_height
        self.transform_origin = transform_origin

        self.curves = []
        self.initial_point = Vector(0, 0)  # type: Vector
        self.current_point = Vector(0, 0)
        self.last_control = None  # type: Vector

        self.transformation = Transformation()

        if self.transform_origin:
            self.transformation.add_translation(0, canvas_height)
            self.transformation.add_scale(1, -1)

        if transformation is not None:
            self.transformation.extend(transformation)

        try:
            self._parse_commands(d)
        except Exception as generic_exception:
            warnings.warn(
                f"Terminating path. The following unforeseen exception occurred: {generic_exception}"
            )
    def to_svg_path(self, wrapped=True, transform=False, height=None):
        """A handy debugging function which the current line-chain in svg form"""

        if transform:
            assert height
            start_ = Vector(self._curves[0].start.x,
                            height - self._curves[0].start.y)
        else:
            start_ = Vector(self._curves[0].start.x, self._curves[0].start.y)

        d = f"M{start_.x} {start_.y}"

        for line in self._curves:
            end_ = Vector(line.end.x, height -
                          line.end.y) if transform else Vector(
                              line.end.x, line.end.y)
            d += f" L {end_.x} {end_.y}"

        if not wrapped:
            return d

        style = "fill:none;stroke:black;stroke-width:0.864583px;stroke-linecap:butt;stroke-linejoin:miter;stroke" \
                "-opacity:1 "

        return f"""<path\nd="{d}"\nstyle="{style}"\n/>"""
Example #3
0
def to_svg_path(line_segment_chain: LineSegmentChain, transformation=None, color="black", 
                stroke_width="0.864583px", draw_arrows=False) -> ElementTree.Element:
    """
    A handy debugging function which converts the current line-chain to svg form

    :param line_segment_chain: The LineSegmentChain to the converted.
    :param transformation: A transformation to apply to every line before converting it.
    :param color: The path's color.
    :param stroke_width: The path's stroke width.
    :param stroke_width: Whether or not to draw arrows at the end of each segment. Requires placing the output of
    arrow_defs() in the document.
    """

    start = Vector(line_segment_chain.get(0).start.x, line_segment_chain.get(0).start.y)
    if transformation:
        start = transformation.apply_transformation(start)

    d = f"M{start.x} {start.y}"

    for line in line_segment_chain:
        end = Vector(line.end.x, line.end.y)
        if transformation:
            end = transformation.apply_transformation(end)
        d += f" L {end.x} {end.y}"

    style = f"fill:none;stroke:{color};stroke-width:{stroke_width};stroke-linecap:butt;stroke-linejoin:miter;stroke" \
            "-opacity:1 "

    path = ElementTree.Element("{%s}path" % svg_namespace)
    path.set("d", d)
    path.set("style", style)
    if draw_arrows:
        path.set("marker-mid", "url(#arrow-346)")

    return path
Example #4
0
def rotate(p, r, inverted=False):
    """Rotate a point p by r radians. Remember that the y-axis is inverted in the svg standard."""
    x, y = p

    if inverted:
        return Vector(x * math.cos(r) + y * math.sin(r),
                      -x * math.sin(r) + y * math.cos(r))

    return Vector(x * math.cos(r) - y * math.sin(r),
                  +x * math.sin(r) + y * math.cos(r))
Example #5
0
    def _transform_coordinate_system(self, point: Vector):
        """
        If both do_vertical_mirror and do_vertical_translate are true, it will transform a point form a coordinate
        system with the origin at the top-left, to one with origin at the bottom-right.
        """

        if self.do_vertical_mirror:
            point = Vector(point.x, -point.y)

        if self.do_vertical_translate:
            point += Vector(0, self.canvas_height)

        return point
Example #6
0
        def absolute_cubic_bezier_extension(x2, y2, x, y):
            start = self.end
            control2 = Vector(x2, y2)
            end = Vector(x, y)

            if self.last_control:
                control1 = 2 * start - self.last_control
                bazier = absolute_cubic_bazier(*control1, *control2, *end)
            else:
                bazier = absolute_quadratic_bazier(*control2, *end)

            self.end = start

            return bazier
Example #7
0
        def absolute_quadratic_bazier(control1_x, control1_y, x, y):

            trans_end = self._apply_transformations(self.current_point)
            trans_new_end = self._apply_transformations(Vector(x, y))
            trans_control1 = self._apply_transformations(
                Vector(control1_x, control1_y))

            quadratic_bezier = QuadraticBezier(trans_end, trans_new_end,
                                               trans_control1)

            self.last_control = Vector(control1_x, control1_y)
            self.current_point = Vector(x, y)

            return quadratic_bezier
Example #8
0
        def absolute_quadratic_bazier(control1_x, control1_y, x, y):

            trans_end = self._transform_coordinate_system(self.end)
            trans_new_end = self._transform_coordinate_system(Vector(x, y))
            trans_control1 = self._transform_coordinate_system(
                Vector(control1_x, control1_y))

            quadratic_bezier = QuadraticBezier(trans_end, trans_new_end,
                                               trans_control1)

            self.last_control = Vector(control1_x, control1_y)
            self.end = Vector(x, y)

            return quadratic_bezier
Example #9
0
def center_to_endpoint_parameterization(center, radii, rotation, start_angle,
                                        sweep_angle):
    rotation_matrix = RotationMatrix(rotation)

    start = rotation_matrix * Vector(radii.x * math.cos(start_angle),
                                     radii.y * math.sin(start_angle)) + center

    end_angle = start_angle + sweep_angle
    end = rotation_matrix * Vector(radii.x * math.cos(end_angle),
                                   radii.y * math.sin(end_angle)) + center

    large_arc_flag = 1 if abs(sweep_angle) > math.pi else 0
    sweep_flag = 1 if sweep_angle > 0 else 0

    return start, end, large_arc_flag, sweep_flag
Example #10
0
    def linear_move(self, x=None, y=None, z=None):

        if self._next_speed is None:
            raise ValueError("Undefined movement speed. Call set_movement_speed before executing movement commands.")

        # Don't do anything if linear move was called without passing a value.
        if x is None and y is None and z is None:
            warnings.warn("linear_move command invoked without arguments.")
            return ''

        # Todo, investigate G0 command and replace movement speeds with G1 (normal speed) and G0 (fast move)
        command = "G1"

        if self._current_speed != self._next_speed:
            self._current_speed = self._next_speed
            command += f" F{self._current_speed}"

        # Move if not 0 and not None
        command += f" X{x:.{self.precision}f}" if x is not None else ''
        command += f" Y{y:.{self.precision}f}" if y is not None else ''
        command += f" Z{z:.{self.precision}f}" if z is not None else ''

        if self.position is not None or (x is not None and y is not None):
            if x is None:
                x = self.position.x

            if y is None:
                y = self.position.y

            self.position = Vector(x, y)

        if verbose:
            print(f"Move to {x}, {y}, {z}")

        return command + ';'
Example #11
0
def angle_between_vectors(v1, v2):
    """Compute angle between two vectors v1, v2"""
    angle = math.acos(Vector.dot_product(v1, v2) / (abs(v1) * abs(v2)))

    angle *= -1 if v1.x * v2.y - v1.y * v2.x > 0 else 1

    return angle
Example #12
0
        def absolute_cubic_bazier(control1_x, control1_y, control2_x,
                                  control2_y, x, y):

            trans_start = self._apply_transformations(self.current_point)
            trans_end = self._apply_transformations(Vector(x, y))
            trans_control1 = self._apply_transformations(
                Vector(control1_x, control1_y))
            trans_control2 = self._apply_transformations(
                Vector(control2_x, control2_y))

            cubic_bezier = CubicBazier(trans_start, trans_end, trans_control1,
                                       trans_control2)

            self.last_control = Vector(control2_x, control2_y)
            self.current_point = Vector(x, y)

            return cubic_bezier
Example #13
0
    def angle_to_point(self, angle):
        transformed_radii = Vector(self.radii.x * math.cos(angle),
                                   self.radii.y * math.sin(angle))
        point = RotationMatrix(self.rotation) * transformed_radii + self.center

        if self.transformation:
            point = self.transformation.apply_affine_transformation(point)

        return point
Example #14
0
    def apply_affine_transformation(self, vector: Vector) -> Vector:
        """
        Apply the full affine transformation (linear + translation) to a vector. Generally used to transform points.
        Eg the center of an ellipse.
        """
        vector_4d = Matrix([[vector.x], [vector.y], [1], [1]])
        vector_4d = self.translation_matrix * vector_4d

        return Vector(vector_4d.matrix_list[0][0], vector_4d.matrix_list[1][0])
Example #15
0
        def absolute_line(x, y):
            start = self.current_point
            end = Vector(x, y)

            line = Line(self.transformation.apply_affine_transformation(start),
                        self.transformation.apply_affine_transformation(end))

            self.current_point = end

            return line
Example #16
0
        def absolute_line(x, y):
            start = self.end
            end = Vector(x, y)

            line = Line(self._transform_coordinate_system(start),
                        self._transform_coordinate_system(end))

            self.end = end

            return line
Example #17
0
    def linear_move(self, x=None, y=None, z=None) -> str:
        if self.position is not None or (x is not None and y is not None):
            if x is None:
                x = self.position.x

            if y is None:
                y = self.position.y

            self.position = Vector(x, y)
        return f"g{x:.1f},{y:.1f}"
Example #18
0
def angle_between_vectors(v1, v2):
    """Compute angle between two vectors v1, v2"""
    cos_angle = Vector.dot_product(v1, v2) / (abs(v1) * abs(v2))
    cos_angle = tolerance_constrain(cos_angle, 1, -1)

    angle = math.acos(cos_angle)

    angle *= 1 if v1.x * v2.y - v1.y * v2.x > 0 else -1

    return angle
Example #19
0
        def absolute_quadratic_bazier_extension(x, y):
            start = self.end
            end = Vector(x, y)

            if self.last_control:
                control = 2 * start - self.last_control
                bazier = absolute_quadratic_bazier(*control, *end)
            else:
                bazier = absolute_quadratic_bazier(*start, *end)

            self.end = end
            return bazier
Example #20
0
    def multiply_vector(self, other_vector: Vector):
        if self.number_of_columns != 2:
            raise ValueError(
                f"can't multiply matrix with 2D vector. The matrix must have 2 columns, not "
                f"{self.number_of_columns}")

        x = sum([
            self[0][k] * other_vector[k] for k in range(self.number_of_columns)
        ])
        y = sum([
            self[1][k] * other_vector[k] for k in range(self.number_of_columns)
        ])

        return Vector(x, y)
Example #21
0
        def absolute_arc(rx, ry, deg_from_horizontal, large_arc_flag,
                         sweep_flag, x, y):
            end = Vector(x, y)
            start = self.current_point

            radii = Vector(rx, ry)

            rotation_rad = math.radians(deg_from_horizontal)

            if abs(start - end) == 0:
                raise ValueError("start and end points can't be equal")

            radii, center, start_angle, sweep_angle = formulas.endpoint_to_center_parameterization(
                start, end, radii, rotation_rad, large_arc_flag, sweep_flag)

            arc = EllipticalArc(center,
                                radii,
                                rotation_rad,
                                start_angle,
                                sweep_angle,
                                transformation=self.transformation)

            self.current_point = end
            return arc
Example #22
0
def endpoint_to_center_parameterization(start, end, radii, rotation_rad,
                                        large_arc_flag, sweep_flag):
    # Find and select one of the two possible eclipse centers by undoing the rotation (to simplify the math) and
    # then re-applying it.
    rotated_primed_values = (
        start -
        end) / 2  # Find the primed_values of the start and the end points.
    primed_values = RotationMatrix(rotation_rad, True) * rotated_primed_values
    px, py = primed_values.x, primed_values.y

    # Correct out-of-range radii
    rx = abs(radii.x)
    ry = abs(radii.y)

    delta = px**2 / rx**2 + py**2 / ry**2

    if delta > 1:
        rx *= math.sqrt(delta)
        ry *= math.sqrt(delta)

    if math.sqrt(delta) > 1:
        center = Vector(0, 0)
    else:
        radicant = ((rx * ry)**2 - (rx * py)**2 -
                    (ry * px)**2) / ((rx * py)**2 + (ry * px)**2)
        radicant = max(0, radicant)

        # Find center using w3.org's formula
        center = math.sqrt(radicant) * Vector((rx * py) / ry, -(ry * px) / rx)

        center *= -1 if large_arc_flag == sweep_flag else 1  # Select one of the two solutions based on flags

    rotated_center = RotationMatrix(rotation_rad) * center + (
        start + end) / 2  # re-apply the rotation

    cx, cy = center.x, center.y
    u = Vector((px - cx) / rx, (py - cy) / ry)
    v = Vector((-px - cx) / rx, (-py - cy) / ry)

    max_angle = 2 * math.pi

    start_angle = angle_between_vectors(Vector(1, 0), u)
    sweep_angle_unbounded = angle_between_vectors(u, v)
    sweep_angle = sweep_angle_unbounded % max_angle

    if not sweep_flag and sweep_angle > 0:
        sweep_angle -= max_angle

    if sweep_flag and sweep_angle < 0:
        sweep_angle += max_angle

    return Vector(rx, ry), rotated_center, start_angle, sweep_angle
Example #23
0
    def __init__(self,
                 d: str,
                 canvas_height: float,
                 do_vertical_mirror=True,
                 do_vertical_translate=True):
        self.canvas_height = canvas_height
        self.do_vertical_mirror = do_vertical_mirror
        self.do_vertical_translate = do_vertical_translate

        self.curves = []
        self.start = None  # type: Vector
        self.end = Vector(0, 0)
        self.last_control = None  # type: Vector

        try:
            self._parse_commands(d)
        except Exception as generic_exception:
            warnings.warn(
                f"Terminating path. The following unforeseen exception occurred: {generic_exception}"
            )
Example #24
0
        def absolute_cubic_bazier(control1_x, control1_y, control2_x,
                                  control2_y, x, y):

            self.start = Vector(x, y)

            trans_start = self._transform_coordinate_system(self.end)
            trans_end = self._transform_coordinate_system(Vector(x, y))
            trans_control1 = self._transform_coordinate_system(
                Vector(control1_x, control1_y))
            trans_control2 = self._transform_coordinate_system(
                Vector(control2_x, control2_y))

            cubic_bezier = CubicBazier(trans_start, trans_end, trans_control1,
                                       trans_control2)

            self.last_control = Vector(control2_x, control2_y)
            self.end = Vector(x, y)

            return cubic_bezier
Example #25
0
        def absolute_arc(rx, ry, deg_from_horizontal, large_arc_flag,
                         sweep_flag, x, y):
            start = self.end
            end = Vector(x, y)

            rotation_rad = math.radians(deg_from_horizontal)
            max_angle = 2 * math.pi
            rotation_rad = formulas.mod_constrain(rotation_rad, -max_angle,
                                                  max_angle)

            # Find and select one of the two possible eclipse centers by undoing the rotation (to simplify the math) and
            # then re-applying it.
            rotated_primed_values = (
                start - end
            ) / 2  # Find the primed_values of the start and the end points.
            primed_values = formulas.rotate(
                rotated_primed_values, -rotation_rad,
                True)  # Undo the ellipse's rotation.
            px, py = primed_values.x, primed_values.y

            # Correct out-of-range radii
            # ToDo investigate buggy behaviour when sweep angle > 180 deg
            rx = abs(rx)
            ry = abs(ry)
            if rx <= TOLERANCES['operation'] or ry <= TOLERANCES['operation']:
                return absolute_line(x, y)

            delta = px**2 / rx**2 + py**2 / ry**2

            if delta > 1:
                rx *= math.sqrt(delta)
                ry *= math.sqrt(delta)

            if math.sqrt(delta) > 1:
                center = Vector(0, 0)
            else:
                radicant = ((rx * ry)**2 - (rx * py)**2 -
                            (ry * px)**2) / ((rx * py)**2 + (ry * px)**2)

                # Find center using w3.org's formula
                center = math.sqrt(radicant) * Vector(
                    (rx * py) / ry, -(ry * px) / rx)

                center *= -1 if large_arc_flag == sweep_flag else 1  # Select one of the two solutions based on flags

            rotated_center = formulas.rotate(
                center, rotation_rad,
                False) + (start + end) / 2  # re-apply the rotation

            cx, cy = center.x, center.y
            u = Vector((px - cx) / rx, (py - cy) / ry)
            v = Vector((-px - cx) / rx, (-py - cy) / ry)

            start_angle = formulas.angle_between_vectors(Vector(1, 0), u)
            sweep_angle_unbounded = formulas.angle_between_vectors(u, v)
            sweep_angle = sweep_angle_unbounded % max_angle

            if not sweep_flag and sweep_angle_unbounded > 0:
                sweep_angle -= max_angle

            if sweep_flag and sweep_angle_unbounded < 0:
                sweep_angle += max_angle

            transformed_center = self._transform_coordinate_system(
                rotated_center)
            sweep_angle *= -1 if self.do_vertical_mirror else 1
            start_angle *= 1 if self.do_vertical_mirror else 1

            arc = EllipticalArc(transformed_center, Vector(rx, ry),
                                rotation_rad, start_angle, sweep_angle)

            self.end = Vector(x, y)

            return arc
Example #26
0
 def angle_to_point(self, rad):
     at_origin = self.radius * Vector(math.cos(rad), math.sin(rad))
     translated = at_origin + self.center
     return translated
Example #27
0
 def relative_line(dx, dy):
     return absolute_line(*(self.end + Vector(dx, dy)))
Example #28
0
from svg_to_gcode.geometry import Vector
from svg_to_gcode.formulas import center_to_endpoint_parameterization
from svg_to_gcode.formulas import endpoint_to_center_parameterization as endpoint_to_center_parameterization

from svg_to_gcode import TOLERANCES


def to_svg(start, end, radii, rotation, large_arc_flag, sweep_flag):
    return f"M {start.x} {start.y} A {radii.x} {radii.y} {rotation} {large_arc_flag} {sweep_flag} {end.x} {end.y}"


# center parametrization
arc = "why_not"
if arc == "simple":
    center = Vector(100, 100)
    radii = Vector(20, 60)
    rotation = 0
    start_angle = 0
    sweep_angle = math.pi
else:
    center = Vector(100, 100.0)
    radii = Vector(50, 50)
    rotation = math.radians(90)
    start_angle = math.radians(0)
    sweep_angle = math.radians(270)

# end-pint parametrization
start, end, large_arc_flag, sweep_flag = center_to_endpoint_parameterization(center, radii, rotation, start_angle,
                                                                             sweep_angle)
Example #29
0
 def absolute_move(x, y):
     self.end = Vector(x, y)
     return None
Example #30
0
    def _add_svg_curve(self, command_key: str, command_arguments: List[float]):
        """
        Offer a representation of a curve using the geometry sub-module.
        Based on Mozilla Docs: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths

        Each sub-method must be implemented with the following structure:
        def descriptive_name(*command_arguments):
            execute calculations and transformations, **do not modify or create any instance variables**
            generate curve
            modify instance variables
            return curve

        Alternatively a sub-method may simply call a base command.

        :param command_key: a character representing a specific command based on the svg standard
        :param command_arguments: A list containing the arguments for the current command_key
        """

        # Only move end point
        def absolute_move(x, y):
            self.end = Vector(x, y)
            return None

        def relative_move(dx, dy):
            return absolute_move(*(self.end + Vector(dx, dy)))

        # Draw straight line
        def absolute_line(x, y):
            start = self.end
            end = Vector(x, y)

            line = Line(self._transform_coordinate_system(start),
                        self._transform_coordinate_system(end))

            self.end = end

            return line

        def relative_line(dx, dy):
            return absolute_line(*(self.end + Vector(dx, dy)))

        def absolute_horizontal_line(x):
            return absolute_line(x, self.end.y)

        def relative_horizontal_line(dx):
            return absolute_horizontal_line(self.end.x + dx)

        def absolute_vertical_line(y):
            return absolute_line(self.end.x, y)

        def relative_vertical_line(dy):
            return absolute_vertical_line(self.end.y + dy)

        def close_path():
            return absolute_line(*self.start)

        # Draw Curves
        def absolute_cubic_bazier(control1_x, control1_y, control2_x,
                                  control2_y, x, y):

            self.start = Vector(x, y)

            trans_start = self._transform_coordinate_system(self.end)
            trans_end = self._transform_coordinate_system(Vector(x, y))
            trans_control1 = self._transform_coordinate_system(
                Vector(control1_x, control1_y))
            trans_control2 = self._transform_coordinate_system(
                Vector(control2_x, control2_y))

            cubic_bezier = CubicBazier(trans_start, trans_end, trans_control1,
                                       trans_control2)

            self.last_control = Vector(control2_x, control2_y)
            self.end = Vector(x, y)

            return cubic_bezier

        def relative_cubic_bazier(dx1, dy1, dx2, dy2, dx, dy):
            return absolute_cubic_bazier(self.end.x + dx1, self.end.y + dy1,
                                         self.end.x + dx2, self.end.y + dy2,
                                         self.end.x + dx, self.end.y + dy)

        def absolute_cubic_bezier_extension(x2, y2, x, y):
            start = self.end
            control2 = Vector(x2, y2)
            end = Vector(x, y)

            if self.last_control:
                control1 = 2 * start - self.last_control
                bazier = absolute_cubic_bazier(*control1, *control2, *end)
            else:
                bazier = absolute_quadratic_bazier(*control2, *end)

            self.end = start

            return bazier

        def relative_cubic_bazier_extension(dx2, dy2, dx, dy):
            return absolute_cubic_bezier_extension(self.end.x + dx2,
                                                   self.end.y + dy2,
                                                   self.end.x + dx,
                                                   self.end.y + dy)

        def absolute_quadratic_bazier(control1_x, control1_y, x, y):

            trans_end = self._transform_coordinate_system(self.end)
            trans_new_end = self._transform_coordinate_system(Vector(x, y))
            trans_control1 = self._transform_coordinate_system(
                Vector(control1_x, control1_y))

            quadratic_bezier = QuadraticBezier(trans_end, trans_new_end,
                                               trans_control1)

            self.last_control = Vector(control1_x, control1_y)
            self.end = Vector(x, y)

            return quadratic_bezier

        def relative_quadratic_bazier(dx1, dy1, dx, dy):
            return absolute_quadratic_bazier(self.end.x + dx1,
                                             self.end.y + dy1, self.end.x + dx,
                                             self.end.y + dy)

        def absolute_quadratic_bazier_extension(x, y):
            start = self.end
            end = Vector(x, y)

            if self.last_control:
                control = 2 * start - self.last_control
                bazier = absolute_quadratic_bazier(*control, *end)
            else:
                bazier = absolute_quadratic_bazier(*start, *end)

            self.end = end
            return bazier

        def relative_quadratic_bazier_extension(dx, dy):
            return absolute_quadratic_bazier_extension(self.end.x + dx,
                                                       self.end.y + dy)

        # Generate EllipticalArc with center notation from svg endpoint notation.
        # Based on w3.org implementation notes. https://www.w3.org/TR/SVG2/implnote.html
        def absolute_arc(rx, ry, deg_from_horizontal, large_arc_flag,
                         sweep_flag, x, y):
            start = self.end
            end = Vector(x, y)

            rotation_rad = math.radians(deg_from_horizontal)
            max_angle = 2 * math.pi
            rotation_rad = formulas.mod_constrain(rotation_rad, -max_angle,
                                                  max_angle)

            # Find and select one of the two possible eclipse centers by undoing the rotation (to simplify the math) and
            # then re-applying it.
            rotated_primed_values = (
                start - end
            ) / 2  # Find the primed_values of the start and the end points.
            primed_values = formulas.rotate(
                rotated_primed_values, -rotation_rad,
                True)  # Undo the ellipse's rotation.
            px, py = primed_values.x, primed_values.y

            # Correct out-of-range radii
            # ToDo investigate buggy behaviour when sweep angle > 180 deg
            rx = abs(rx)
            ry = abs(ry)
            if rx <= TOLERANCES['operation'] or ry <= TOLERANCES['operation']:
                return absolute_line(x, y)

            delta = px**2 / rx**2 + py**2 / ry**2

            if delta > 1:
                rx *= math.sqrt(delta)
                ry *= math.sqrt(delta)

            if math.sqrt(delta) > 1:
                center = Vector(0, 0)
            else:
                radicant = ((rx * ry)**2 - (rx * py)**2 -
                            (ry * px)**2) / ((rx * py)**2 + (ry * px)**2)

                # Find center using w3.org's formula
                center = math.sqrt(radicant) * Vector(
                    (rx * py) / ry, -(ry * px) / rx)

                center *= -1 if large_arc_flag == sweep_flag else 1  # Select one of the two solutions based on flags

            rotated_center = formulas.rotate(
                center, rotation_rad,
                False) + (start + end) / 2  # re-apply the rotation

            cx, cy = center.x, center.y
            u = Vector((px - cx) / rx, (py - cy) / ry)
            v = Vector((-px - cx) / rx, (-py - cy) / ry)

            start_angle = formulas.angle_between_vectors(Vector(1, 0), u)
            sweep_angle_unbounded = formulas.angle_between_vectors(u, v)
            sweep_angle = sweep_angle_unbounded % max_angle

            if not sweep_flag and sweep_angle_unbounded > 0:
                sweep_angle -= max_angle

            if sweep_flag and sweep_angle_unbounded < 0:
                sweep_angle += max_angle

            transformed_center = self._transform_coordinate_system(
                rotated_center)
            sweep_angle *= -1 if self.do_vertical_mirror else 1
            start_angle *= 1 if self.do_vertical_mirror else 1

            arc = EllipticalArc(transformed_center, Vector(rx, ry),
                                rotation_rad, start_angle, sweep_angle)

            self.end = Vector(x, y)

            return arc

        def relative_arc(rx, ry, deg_from_horizontal, large_arc_flag,
                         sweep_flag, dx, dy):
            return absolute_arc(rx, ry, deg_from_horizontal, large_arc_flag,
                                sweep_flag, self.end.x + dx, self.end.x + dy)

        command_methods = {
            # Only move end point
            'M': absolute_move,
            'm': relative_move,

            # Draw straight line
            'L': absolute_line,
            'l': relative_line,
            'H': absolute_horizontal_line,
            'h': relative_horizontal_line,
            'V': absolute_vertical_line,
            'v': relative_vertical_line,
            'Z': close_path,
            'z': close_path,

            # Draw bazier curves
            'C': absolute_cubic_bazier,
            'c': relative_cubic_bazier,
            'S': absolute_cubic_bezier_extension,
            's': relative_cubic_bazier_extension,
            'Q': absolute_quadratic_bazier,
            'q': relative_quadratic_bazier,
            'T': absolute_quadratic_bazier_extension,
            't': relative_quadratic_bazier_extension,

            # Draw elliptical arcs
            'A': absolute_arc,
            'a': relative_arc
        }

        try:
            curve = command_methods[command_key](*command_arguments)
        except TypeError as type_error:
            warnings.warn(
                f"Mis-formed input. Skipping command {command_key, command_arguments} because it caused the "
                f"following error: \n{type_error}")
        except ValueError as value_error:
            warnings.warn(
                f"Impossible geometry. Skipping curve {command_key, command_arguments} because it caused the "
                f"following value error:\n{value_error}")
        else:
            if curve is not None:
                self.curves.append(curve)

        if self.start is None:
            self.start = Vector(*self.end)

        if verbose:
            print(f"{command_key}{tuple(command_arguments)} -> {curve}")