Last active
April 22, 2024 17:47
-
-
Save dov/8d9b0304ba85e3229aabccac3c6468ef to your computer and use it in GitHub Desktop.
Proof of concept importing svg into CadQuery
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
###################################################################### | |
# A proof of concept adding a svg path into a cadQuery Workspace | |
# object. | |
# | |
# This file is in the public domain. | |
# | |
# Dov Grobgeld <[email protected]> | |
# 2024-03-10 Sun | |
###################################################################### | |
import svgpathtools | |
from svgpathtools import svg2paths | |
import cadquery as cq | |
from cadquery import exporters | |
import numpy as np | |
from math import sin, cos, sqrt, pi, acos, fmod, degrees | |
import pdb | |
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 | |
cq.Workplane.addSvgPath = addSvgPath | |
paths, attributes = svg2paths('fish.svg') | |
res = (cq | |
.Workplane('XY') | |
.addSvgPath(paths[0]) | |
.extrude(10) | |
# Why do I get an error trying to fillet here? | |
) | |
exporters.export(res, 'fish.stl') | |
print('ok') | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment