import svgpathtools import numpy as np from math import sin, cos, sqrt, pi, acos, fmod, degrees # See https://gist.github.com/dov/8d9b0304ba85e3229aabccac3c6468ef/d007910a9b68201a6c95852a220f96711ba3faa1 def tpl(cplx): """Convert a complex number to a tuple""" return (cplx.real, cplx.imag) def angle_between(u, v): """Find the angle between the vectors u an v""" ux, uy = u vx, vy = v sign = 1 if ux * vy - uy * vx > 0 else -1 arg = (ux * vx + uy * vy) / (sqrt(ux * ux + uy * uy) * sqrt(vx * vx + vy * vy)) return sign * acos(arg) # Implementation of https://www.w3.org/TR/SVG/implnote.html#ArcConversionCenterToEndpoint def arc_endpoint_to_center(start, end, flag_a, flag_s, radius, phi): """Convert a endpoint elliptical arc description to a center description""" rx, ry = radius.real, radius.imag x1, y1 = start.real, start.imag x2, y2 = end.real, end.imag cosphi = cos(phi) sinphi = sin(phi) rx2 = rx * rx ry2 = ry * ry # Step 1. Compute x1p,y1p x1p, y1p = ( np.array([[cosphi, sinphi], [-sinphi, cosphi]]) @ np.array([x1 - x2, y1 - y2]) * 0.5 ).flatten() x1p2 = x1p * x1p y1p2 = y1p * y1p # Step 2: Compute (cx', cy') cxyp = sqrt( (rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2) / (rx2 * y1p2 + ry2 * x1p2) ) * np.array([rx * y1p / ry, -ry * x1p / rx]) if flag_a == flag_s: cxyp = -cxyp cxp, cyp = cxyp.flatten() # Step 3: compute (cx,cy) from (cx',cy') cx, cy = ( cosphi * cxp - sinphi * cyp + 0.5 * (x1 + x2), sinphi * cxp + cosphi * cyp + 0.5 * (y1 + y2), ) # Step 4: compute theta1 and deltatheta theta1 = angle_between((1, 0), ((x1p - cxp) / rx, (y1p - cyp) / ry)) delta_theta = fmod( angle_between( ((x1p - cxp) / rx, (y1p - cyp) / ry), ((-x1p - cxp) / rx, (-y1p - cyp) / ry) ), 2 * pi, ) # Choose the right edge according to the flags if not flag_s and delta_theta > 0: delta_theta -= 2 * pi elif flag_s and delta_theta < 0: delta_theta += 2 * pi return (cx, cy), theta1, delta_theta def addSvgPath(self, path): """Add the svg path object to the current workspace""" res = self path_start = None arc_id = 0 for p in path: if path_start is None: path_start = p.start res = res.moveTo(*tpl(p.start)) # Support the four svgpathtools different objects if isinstance(p, svgpathtools.CubicBezier): coords = (tpl(p.start), tpl(p.control1), tpl(p.control2), tpl(p.end)) res = res.bezier(coords) elif isinstance(p, svgpathtools.QuadraticBezier): coords = (tpl(p.start), tpl(p.control), tpl(p.end)) res = res.bezier(coords) pass elif isinstance(p, svgpathtools.Arc): arc_id += 1 center, theta1, delta_theta = arc_endpoint_to_center( p.start, p.end, p.large_arc, p.sweep, p.radius, p.rotation ) res = res.ellipseArc( x_radius=p.radius.real, y_radius=p.radius.imag, rotation_angle=degrees(p.rotation), angle1=degrees(theta1), angle2=degrees(theta1 + delta_theta), ) elif isinstance(p, svgpathtools.Line): res = res.lineTo(p.end.real, p.end.imag) if path_start == p.end: path_start = None res = res.close() return res