Skip to content

Instantly share code, notes, and snippets.

@dov
Last active April 22, 2024 17:47
Show Gist options
  • Save dov/8d9b0304ba85e3229aabccac3c6468ef to your computer and use it in GitHub Desktop.
Save dov/8d9b0304ba85e3229aabccac3c6468ef to your computer and use it in GitHub Desktop.
Proof of concept importing svg into CadQuery
#!/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')
Display the source blob
Display the rendered blob
Raw
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="50.058437mm"
height="41.506351mm"
viewBox="0 0 50.058437 41.506351"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1"
transform="translate(-24.636192,-102.35282)">
<path
id="path1"
style="opacity:0.5;fill:#00ffff;stroke:#000000;stroke-width:0.499999;stroke-linecap:round;stroke-linejoin:round"
d="m 74.229578,102.60821 c -0.04674,-0.011 -0.101708,-0.005 -0.165365,0.0186 -2.037019,0.76177 -9.067147,17.77411 -9.067147,17.77411 0,0 -6.13385,-12.57082 -20.171378,-16.64705 -14.037529,-4.076222 -19.73368,13.08603 -19.73368,13.08603 -1.719272,11.03283 7.491868,24.35424 22.530925,24.91527 15.039058,0.56104 15.817711,-11.07506 17.467668,-14.05805 1.649953,-2.98301 7.819677,15.9122 7.819677,15.9122 0,0 -0.4407,-16.51711 -2.074292,-20.36206 -1.582541,-3.7248 4.842472,-20.29933 3.393592,-20.63905 z m -37.721212,8.9948 a 2.1232522,2.3364165 0 0 1 2.123384,2.33681 2.1232522,2.3364165 0 0 1 -2.123384,2.33629 2.1232522,2.3364165 0 0 1 -2.123385,-2.33629 2.1232522,2.3364165 0 0 1 2.123385,-2.33681 z" />
</g>
</svg>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment