2 changed files with 143 additions and 6 deletions
@ -0,0 +1,118 @@ |
|||||
|
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) |
||||
|
print("cubic bezier") |
||||
|
elif isinstance(p, svgpathtools.QuadraticBezier): |
||||
|
coords = (tpl(p.start), tpl(p.control), tpl(p.end)) |
||||
|
res = res.bezier(coords) |
||||
|
print("quadratic bezier") |
||||
|
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), |
||||
|
) |
||||
|
print("ellipse arc") |
||||
|
elif isinstance(p, svgpathtools.Line): |
||||
|
res = res.lineTo(p.end.real, p.end.imag) |
||||
|
print("line") |
||||
|
|
||||
|
if path_start == p.end: |
||||
|
path_start = None |
||||
|
res = res.close() |
||||
|
|
||||
|
return res |
Loading…
Reference in new issue