FreeCAD/src/Mod/Draft/importSVG.py
Sebastian Hoogen 48c9e2fbe8 Draft importSVG reimplentation of path and transformation parsing
All basic elements besides elliptic arcs and rounded rects are now
supported
2012-01-16 18:51:04 +01:00

985 lines
44 KiB
Python

#***************************************************************************
#* *
#* Copyright (c) 2009 Yorik van Havre <yorik@gmx.fr> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
__title__="FreeCAD Draft Workbench - SVG importer/exporter"
__author__ = "Yorik van Havre, Sebastian Hoogen"
__url__ = ["http://free-cad.sourceforge.net"]
'''
This script imports SVG files in FreeCAD. Currently only reads the following entities:
paths, lines, circular arcs ,rects, circles, ellipses, polygons, polylines.
currently unsupported: image, rounded rect(rx,ry), elliptical arcs
'''
#ToDo:
# elliptical arc segments
# rounded rects (elliptical arcs)
# ignoring CDATA
import xml.sax, string, FreeCAD, os, math, re, Draft
from draftlibs import fcvec
from FreeCAD import Vector
try: import FreeCADGui
except: gui = False
else: gui = True
try: draftui = FreeCADGui.draftToolBar
except: draftui = None
pythonopen = open
svgcolors = {
'Pink': [255, 192, 203],
'Blue': [0, 0, 255],
'Honeydew': [240, 255, 240],
'Purple': [128, 0, 128],
'Fuchsia': [255, 0, 255],
'LawnGreen': [124, 252, 0],
'Amethyst': [153, 102, 204],
'Crimson': [220, 20, 60],
'White': [255, 255, 255],
'NavajoWhite': [255, 222, 173],
'Cornsilk': [255, 248, 220],
'Bisque': [255, 228, 196],
'PaleGreen': [152, 251, 152],
'Brown': [165, 42, 42],
'DarkTurquoise': [0, 206, 209],
'DarkGreen': [0, 100, 0],
'MediumOrchid': [186, 85, 211],
'Chocolate': [210, 105, 30],
'PapayaWhip': [255, 239, 213],
'Olive': [128, 128, 0],
'Silver': [192, 192, 192],
'PeachPuff': [255, 218, 185],
'Plum': [221, 160, 221],
'DarkGoldenrod': [184, 134, 11],
'SlateGrey': [112, 128, 144],
'MintCream': [245, 255, 250],
'CornflowerBlue': [100, 149, 237],
'Gold': [255, 215, 0],
'HotPink': [255, 105, 180],
'DarkBlue': [0, 0, 139],
'LimeGreen': [50, 205, 50],
'DeepSkyBlue': [0, 191, 255],
'DarkKhaki': [189, 183, 107],
'LightGrey': [211, 211, 211],
'Yellow': [255, 255, 0],
'Gainsboro': [220, 220, 220],
'MistyRose': [255, 228, 225],
'SandyBrown': [244, 164, 96],
'DeepPink': [255, 20, 147],
'Magenta': [255, 0, 255],
'AliceBlue': [240, 248, 255],
'DarkCyan': [0, 139, 139],
'DarkSlateGrey': [47, 79, 79],
'GreenYellow': [173, 255, 47],
'DarkOrchid': [153, 50, 204],
'OliveDrab': [107, 142, 35],
'Chartreuse': [127, 255, 0],
'Peru': [205, 133, 63],
'Orange': [255, 165, 0],
'Red': [255, 0, 0],
'Wheat': [245, 222, 179],
'LightCyan': [224, 255, 255],
'LightSeaGreen': [32, 178, 170],
'BlueViolet': [138, 43, 226],
'LightSlateGrey': [119, 136, 153],
'Cyan': [0, 255, 255],
'MediumPurple': [147, 112, 219],
'MidnightBlue': [25, 25, 112],
'FireBrick': [178, 34, 34],
'PaleTurquoise': [175, 238, 238],
'PaleGoldenrod': [238, 232, 170],
'Gray': [128, 128, 128],
'MediumSeaGreen': [60, 179, 113],
'Moccasin': [255, 228, 181],
'Ivory': [255, 255, 240],
'DarkSlateBlue': [72, 61, 139],
'Beige': [245, 245, 220],
'Green': [0, 128, 0],
'SlateBlue': [106, 90, 205],
'Teal': [0, 128, 128],
'Azure': [240, 255, 255],
'LightSteelBlue': [176, 196, 222],
'DimGrey': [105, 105, 105],
'Tan': [210, 180, 140],
'AntiqueWhite': [250, 235, 215],
'SkyBlue': [135, 206, 235],
'GhostWhite': [248, 248, 255],
'MediumTurquoise': [72, 209, 204],
'FloralWhite': [255, 250, 240],
'LavenderBlush': [255, 240, 245],
'SeaGreen': [46, 139, 87],
'Lavender': [230, 230, 250],
'BlanchedAlmond': [255, 235, 205],
'DarkOliveGreen': [85, 107, 47],
'DarkSeaGreen': [143, 188, 143],
'SpringGreen': [0, 255, 127],
'Navy': [0, 0, 128],
'Orchid': [218, 112, 214],
'SaddleBrown': [139, 69, 19],
'IndianRed': [205, 92, 92],
'Snow': [255, 250, 250],
'SteelBlue': [70, 130, 180],
'MediumSlateBlue': [123, 104, 238],
'Black': [0, 0, 0],
'LightBlue': [173, 216, 230],
'Turquoise': [64, 224, 208],
'MediumVioletRed': [199, 21, 133],
'DarkViolet': [148, 0, 211],
'DarkGray': [169, 169, 169],
'Salmon': [250, 128, 114],
'DarkMagenta': [139, 0, 139],
'Tomato': [255, 99, 71],
'WhiteSmoke': [245, 245, 245],
'Goldenrod': [218, 165, 32],
'MediumSpringGreen': [0, 250, 154],
'DodgerBlue': [30, 144, 255],
'Aqua': [0, 255, 255],
'ForestGreen': [34, 139, 34],
'LemonChiffon': [255, 250, 205],
'LightSlateGray': [119, 136, 153],
'SlateGray': [112, 128, 144],
'LightGray': [211, 211, 211],
'Indigo': [75, 0, 130],
'CadetBlue': [95, 158, 160],
'LightYellow': [255, 255, 224],
'DarkOrange': [255, 140, 0],
'PowderBlue': [176, 224, 230],
'RoyalBlue': [65, 105, 225],
'Sienna': [160, 82, 45],
'Thistle': [216, 191, 216],
'Lime': [0, 255, 0],
'Seashell': [255, 245, 238],
'DarkRed': [139, 0, 0],
'LightSkyBlue': [135, 206, 250],
'YellowGreen': [154, 205, 50],
'Aquamarine': [127, 255, 212],
'LightCoral': [240, 128, 128],
'DarkSlateGray': [47, 79, 79],
'Khaki': [240, 230, 140],
'DarkGrey': [169, 169, 169],
'BurlyWood': [222, 184, 135],
'LightGoldenrodYellow': [250, 250, 210],
'MediumBlue': [0, 0, 205],
'DarkSalmon': [233, 150, 122],
'RosyBrown': [188, 143, 143],
'LightSalmon': [255, 160, 122],
'PaleVioletRed': [219, 112, 147],
'Coral': [255, 127, 80],
'Violet': [238, 130, 238],
'Grey': [128, 128, 128],
'LightGreen': [144, 238, 144],
'Linen': [250, 240, 230],
'OrangeRed': [255, 69, 0],
'DimGray': [105, 105, 105],
'Maroon': [128, 0, 0],
'LightPink': [255, 182, 193],
'MediumAquamarine': [102, 205, 170],
'OldLace': [253, 245, 230]
}
def getcolor(color):
"checks if the given string is a RGB value, or if it is a named color. returns 1-based RGBA tuple."
if (color[:1] == "#"):
r = float(int(color[1:3],16)/255.0)
g = float(int(color[3:5],16)/255.0)
b = float(int(color[5:],16)/255.0)
return (r,g,b,0.0)
else:
for k,v in svgcolors.iteritems():
if (k.lower() == color.lower()):
r = float(v[0]/255.0)
g = float(v[1]/255.0)
b = float(v[2]/255.0)
return (r,g,b,0.0)
def getsize(width):
"extracts a number from the given string (removes suffixes)"
if width[-1] == "%":
return float(width[:-1])
elif len(width) > 1:
for s in ['pt','pc','mm','cm','in','px']:
if width[-2:] == s:
return float(width[:-2])
try:
s = float(width)
return s
except ValueError:
return width
def getrgb(color):
"returns a rgb value #000000 from a freecad color"
r = str(hex(int(color[0]*255)))[2:].zfill(2)
g = str(hex(int(color[1]*255)))[2:].zfill(2)
b = str(hex(int(color[2]*255)))[2:].zfill(2)
return "#"+r+g+b
class svgHandler(xml.sax.ContentHandler):
"this handler parses the svg files and creates freecad objects"
def __init__(self):
"retrieving Draft parameters"
params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
self.style = params.GetInt("svgstyle")
self.count = 0
self.transform = None
self.grouptransform = []
self.lastdim = None
global Part
import Part
if gui and draftui:
r = float(draftui.color.red()/255.0)
g = float(draftui.color.green()/255.0)
b = float(draftui.color.blue()/255.0)
self.lw = float(draftui.widthButton.value())
else:
self.lw = float(params.GetInt("linewidth"))
c = params.GetUnsigned("color")
r = float(((c>>24)&0xFF)/255)
g = float(((c>>16)&0xFF)/255)
b = float(((c>>8)&0xFF)/255)
self.col = (r,g,b,0.0)
def format(self,obj):
"applies styles to passed object"
if self.style and gui:
v = obj.ViewObject
if self.color: v.LineColor = self.color
if self.width: v.LineWidth = self.width
if self.fill: v.ShapeColor = self.fill
def startElement(self, name, attrs):
# reorganizing data into a nice clean dictionary
self.count += 1
print "processing element ",self.count,": ",name
print "existing group transform: ", self.grouptransform
data = {}
for (keyword,content) in attrs.items():
content = content.replace(',',' ')
content = content.split()
data[keyword]=content
if 'style' in data:
if not data['style']:
pass#empty style attribute stops inhertig from parent
else:
content = data['style'][0].replace(' ','')
content = content.split(';')
for i in content:
pair = i.split(':')
if len(pair)>1: data[pair[0]]=pair[1]
for k in ['x','y','x1','y1','x2','y2','r','rx','ry','cx','cy','width','height']:
if k in data:
data[k] = getsize(data[k][0])
for k in ['fill','stroke','stroke-width','font-size']:
if k in data:
if isinstance(data[k],list):
data[k]=data[k][0]
# extracting style info
self.fill = None
self.color = None
self.width = None
self.text = None
if 'fill' in data:
if data['fill'][0] != 'none':
self.fill = getcolor(data['fill'])
if 'stroke' in data:
if data['stroke'][0] != 'none':
self.color = getcolor(data['stroke'])
if 'stroke-width' in data:
if data['stroke-width'] != 'none':
self.width = getsize(data['stroke-width'])
if 'transform' in data:
m = self.getMatrix(attrs.getValue('transform'))
if name == "g":
self.grouptransform.append(m)
else:
self.transform = m
else:
if name == "g":
self.grouptransform.append(FreeCAD.Matrix())
if (self.style == 1):
self.color = self.col
self.width = self.lw
pathname = None
if 'id' in data:
pathname = data['id'][0]
print "name: ",pathname
# processing paths
if name == "path":
print data
if not pathname: pathname = 'Path'
path = []
point = []
lastvec = Vector(0,0,0)
lastpole = None
command = None
relative = False
firstvec = None
if "freecad:basepoint1" in data:
p1 = data["freecad:basepoint1"]
p1 = Vector(float(p1[0]),-float(p1[1]),0)
p2 = data["freecad:basepoint2"]
p2 = Vector(float(p2[0]),-float(p2[1]),0)
p3 = data["freecad:dimpoint"]
p3 = Vector(float(p3[0]),-float(p3[1]),0)
obj = Draft.makeDimension(p1,p2,p3)
self.applyTrans(obj)
self.format(obj)
pathdata = []
self.lastdim = obj
pathcommandsre=re.compile('\s*?([mMlLhHvVaAcCqQsStTzZ])\s*?([^mMlLhHvVaAcCqQsStTzZ]*)\s*?',re.DOTALL)
for d,pointsstr in pathcommandsre.findall(' '.join(data['d'])):
#for d in pathdata:
if (d == "M"):
command = "move"
relative = False
point = []
elif (d == "m"):
command = "move"
relative = True
point = []
elif (d == "L"):
command = "line"
relative = False
point = []
elif (d == "l"):
command = "line"
relative = True
point = []
elif (d == "H"):
command = "horizontal"
relative = False
point = []
elif (d == "h"):
command = "horizontal"
relative = True
point = []
elif (d == "V"):
command = "vertical"
relative = False
point = []
elif (d == "v"):
command = "vertical"
relative = True
point = []
elif (d == "A"):
command = "arc"
relative = False
point = []
elif (d == "a"):
command = "arc"
relative = True
point = []
elif (d == "Z") or (d == "z"):
command = "close"
point = []
elif (d == "C"):
command = "cubic"
relative = False
smooth = False
point = []
elif (d == "c"):
command = "cubic"
relative = True
smooth = False
point = []
elif (d == "Q"):
command = "quadratic"
relative = False
smooth = False
point = []
elif (d == "q"):
command = "quadratic"
relative = True
smooth = False
point = []
elif (d == "S"):
command = "cubic"
relative = False
smooth = True
point = []
elif (d == "s"):
command = "cubic"
relative = True
smooth = True
point = []
elif (d == "T"):
command = "quadratic"
relative = False
smooth = True
point = []
elif (d == "t"):
command = "quadratic"
relative = True
smooth = True
point = []
pointlist = pointsstr.replace(',',' ').split()
while pointlist:
if pointlist:
point.append(float(pointlist.pop(0)))
print "command: ",command, ' point: ',point
if (len(point)==2) and (command=="move"):
if path:
sh = Part.Wire(path)
if self.fill: sh = Part.Face(sh)
sh = self.applyTrans(sh)
obj = self.doc.addObject("Part::Feature",pathname)
obj.Shape = sh
self.format(obj)
path = []
if firstvec:
lastvec = firstvec #Move relative to last move command not last draw command
if relative:
lastvec = lastvec.add(Vector(point[0],-point[1],0))
command="line"
else:
lastvec = Vector(point[0],-point[1],0)
firstvec = lastvec
print "move ",lastvec
command = "line"
lastpole = None
point = []
elif (len(point)==2) and (command=="line"):
if relative:
currentvec = lastvec.add(Vector(point[0],-point[1],0))
else:
currentvec = Vector(point[0],-point[1],0)
if not fcvec.equals(lastvec,currentvec):
seg = Part.Line(lastvec,currentvec).toShape()
print "line ",lastvec,currentvec
lastvec = currentvec
path.append(seg)
lastpole = None
point = []
elif (len(point)==1) and (command=="horizontal"):
if relative:
currentvec = lastvec.add(Vector(point[0],0,0))
else:
lasty = path[-1].y
currentvec = Vector(point[0],lasty,0)
seg = Part.Line(lastvec,currentvec).toShape()
lastvec = currentvec
lastpole = None
path.append(seg)
point = []
elif (len(point)==1) and (command=="vertical"):
if relative:
currentvec = lastvec.add(Vector(0,-point[0],0))
else:
lastx = path[-1].x
currentvec = Vector(lastx,-point[0],0)
seg = Part.Line(lastvec,currentvec).toShape()
lastvec = currentvec
lastpole = None
path.append(seg)
point = []
elif (len(point)==7) and (command=="arc"):
#support for large-arc and x-rotation are missing
rx,ry,xrotation, largeflag, sweepflag = point[0:5]
if relative:
currentvec = lastvec.add(Vector(point[-2],-point[-1],0))
else:
currentvec = Vector(point[-2],-point[-1],0)
chord = currentvec.sub(lastvec)
# perp = chord.cross(Vector(0,0,-1))
# here is a better way to find the perpendicular
if sweepflag == 1:
# clockwise
perp = fcvec.rotate2D(chord,-math.pi/2)
else:
# anticlockwise
perp = fcvec.rotate2D(chord,math.pi/2)
chord = fcvec.scale(chord,.5)
if chord.Length > rx: a = 0
else: a = math.sqrt(rx**2-chord.Length**2)
s = rx - a
perp = fcvec.scale(perp,s/perp.Length)
midpoint = lastvec.add(chord.add(perp))
seg = Part.Arc(lastvec,midpoint,currentvec).toShape()
lastvec = currentvec
lastpole = None
path.append(seg)
point = []
elif (command=="cubic") and (((smooth==False) and (len(point)==6)) or (smooth==True and (len(point)==4))) :
if smooth:
if relative:
currentvec = lastvec.add(Vector(point[2],-point[3],0))
pole2 = lastvec.add(Vector(point[0],-point[1],0))
else:
currentvec = Vector(point[2],-point[3],0)
pole2 = Vector(point[0],-point[1],0)
if lastpole is not None and lastpole[0]=='cubic':
pole1 = lastvec.sub(lastpole[1]).add(lastvec)
else:
pole1 = lastvec
else: #not smooth
if relative:
currentvec = lastvec.add(Vector(point[4],-point[5],0))
pole1 = lastvec.add(Vector(point[0],-point[1],0))
pole2 = lastvec.add(Vector(point[2],-point[3],0))
else:
currentvec = Vector(point[4],-point[5],0)
pole1 = Vector(point[0],-point[1],0)
pole2 = Vector(point[2],-point[3],0)
if not fcvec.equals(currentvec,lastvec):
mainv = currentvec.sub(lastvec)
pole1v = lastvec.add(pole1)
pole2v = currentvec.add(pole2)
print "cubic curve data:",mainv.normalize(),pole1v.normalize(),pole2v.normalize()
if True and \
pole1.distanceToLine(lastvec,currentvec) < 20**(-1*Draft.precision()) and \
pole2.distanceToLine(lastvec,currentvec) < 20**(-1*Draft.precision()):
print "straight segment"
seg = Part.Line(lastvec,currentvec).toShape()
else:
print "cubic bezier segment"
b = Part.BezierCurve()
b.setPoles([lastvec,pole1,pole2,currentvec])
seg = b.toShape()
print "connect ",lastvec,currentvec
lastvec = currentvec
lastpole = ('cubic',pole2)
path.append(seg)
point = []
elif (command=="quadratic") and (((smooth==False) and (len(point)==4)) or (smooth==True and (len(point)==2))) :
if smooth:
if relative:
currentvec = lastvec.add(Vector(point[0],-point[1],0))
else:
currentvec = Vector(point[0],-point[1],0)
if lastpole is not None and lastpole[0]=='quadratic':
pole1 = lastvec.sub(lastpole[1]).add(lastvec)
else:
pole1 = lastvec
else: #not smooth
if relative:
currentvec = lastvec.add(Vector(point[2],-point[3],0))
pole1 = lastvec.add(Vector(point[0],-point[1],0))
else:
currentvec = Vector(point[2],-point[3],0)
pole1 = Vector(point[0],-point[1],0)
if not fcvec.equals(currentvec,lastvec):
if True and pole1.distanceToLine(lastvec,currentvec) < 20**(-1*Draft.precision()):
print "straight segment"
seg = Part.Line(lastvec,currentvec).toShape()
else:
print "quadratic bezier segment"
b = Part.BezierCurve()
b.setPoles([lastvec,pole1,currentvec])
seg = b.toShape()
print "connect ",lastvec,currentvec
lastvec = currentvec
lastpole = ('quadratic',pole1)
path.append(seg)
point = []
#while pointlist or command:
else:
if (command == "close"):
if not fcvec.equals(lastvec,firstvec):
seg = Part.Line(lastvec,firstvec).toShape()
path.append(seg)
if path: #the path should be closed by now
sh = Part.Wire(path)
if not sh.isClosed:
#Code from wmayer forum p15549 to fix the tolerance problem
comp=Part.Compound(path)
sh = comp.connectEdgesToWires(False,10**(-1*Draft.precision())).Wires[0] #original tolerance = 0.00001
if self.fill: sh = Part.Face(sh)
sh = self.applyTrans(sh)
obj = self.doc.addObject("Part::Feature",pathname)
obj.Shape = sh
self.format(obj)
path = []
if firstvec:
lastvec = firstvec #Move relative to last move command not last draw command
point = []
command = None
if path:
sh = Part.Wire(path)
if self.fill: sh = Part.Face(sh)
sh = self.applyTrans(sh)
obj = self.doc.addObject("Part::Feature",pathname)
obj.Shape = sh
self.format(obj)
# processing rects
if name == "rect":
if not pathname: pathname = 'Rectangle'
edges = []
# if ('rx' not in data or data['rx'] < 10**(-1*Draft.precision())) and \
# ('ry' not in data or data['ry'] < 10**(-1*Draft.precision())): #negative values are invalid
if True:
p1 = Vector(data['x'],-data['y'],0)
p2 = Vector(data['x']+data['width'],-data['y'],0)
p3 = Vector(data['x']+data['width'],-data['y']-data['height'],0)
p4 = Vector(data['x'],-data['y']-data['height'],0)
edges.append(Part.Line(p1,p2).toShape())
edges.append(Part.Line(p2,p3).toShape())
edges.append(Part.Line(p3,p4).toShape())
edges.append(Part.Line(p4,p1).toShape())
else: #rounded edges
rx = data.get('rx')
ry = data.get('ry') or rx
rx = rx or ry
if rx > 2 * data['width']:
rx = data['width'] / 2.0
if ry > 2 * data['height']:
ry = data['height'] / 2.0
#TBD
# Part.Ellipse(c,rx,ry).toShape() #needs a proxy object
sh = Part.Wire(edges)
if self.fill: sh = Part.Face(sh)
sh = self.applyTrans(sh)
obj = self.doc.addObject("Part::Feature",pathname)
obj.Shape = sh
self.format(obj)
# processing lines
if name == "line":
if not pathname: pathname = 'Line'
p1 = Vector(data['x1'],-data['y1'],0)
p2 = Vector(data['x2'],-data['y2'],0)
sh = Part.Line(p1,p2).toShape()
sh = self.applyTrans(sh)
obj = self.doc.addObject("Part::Feature",pathname)
obj.Shape = sh
self.format(obj)
# processing polylines and polygons
if name == "polyline" or name == "polygon":
'''a simpler implementation would be sh = Part.makePolygon([Vector(svgx,-svgy,0) for svgx,svgy in zip(points[0::2],points[1::2])])
but there would be more difficlult to search for duplicate points beforehand.'''
if not pathname: pathname = 'Polyline'
points=[float(d) for d in data['points']]
print points
lenpoints=len(points)
if lenpoints>=4 and lenpoints % 2 == 0:
lastvec = Vector(points[0],-points[1],0)
path=[]
if name == 'polygon':
points=points+points[:2] # emulate closepath
for svgx,svgy in zip(points[2::2],points[3::2]):
currentvec = Vector(svgx,-svgy,0)
if not fcvec.equals(lastvec,currentvec):
seg = Part.Line(lastvec,currentvec).toShape()
print "polyline seg ",lastvec,currentvec
lastvec = currentvec
path.append(seg)
if path:
sh = Part.Wire(path)
if self.fill: sh = Part.Face(sh)
sh = self.applyTrans(sh)
obj = self.doc.addObject("Part::Feature",pathname)
obj.Shape = sh
# processing ellipses
if (name == "ellipse") :
if not pathname: pathname = 'Ellipse'
c = Vector(data.get('cx',0),-data.get('cy',0),0)
rx = data['rx']
ry = data['ry']
sh = Part.Ellipse(c,rx,ry).toShape() #needs a proxy object
if self.fill:
sh = Part.Wire([sh])
sh = Part.Face(sh)
sh.translate(c)
sh = self.applyTrans(sh)
obj = self.doc.addObject("Part::Feature",pathname)
obj.Shape = sh
self.format(obj)
# processing circles
if (name == "circle") and (not ("freecad:skip" in data)) :
if not pathname: pathname = 'Circle'
c = Vector(data.get('cx',0),-data.get('cy',0),0)
r = data['r']
sh = Part.makeCircle(r)
if self.fill:
sh = Part.Wire([sh])
sh = Part.Face(sh)
sh.translate(c)
sh = self.applyTrans(sh)
obj = self.doc.addObject("Part::Feature",pathname)
obj.Shape = sh
self.format(obj)
# processing texts
if name in ["text","tspan"]:
if not("freecad:skip" in data):
print "processing a text"
if 'x' in data:
self.x = data['x']
else:
self.x = 0
if 'y' in data:
self.y = data['y']
else:
self.y = 0
if 'font-size' in data:
if data['font-size'] != 'none':
self.text = getsize(data['font-size'])
else:
self.text = 1
else:
if self.lastdim:
self.lastdim.ViewObject.FontSize = int(getsize(data['font-size']))
print "done processing element ",self.count
def characters(self,content):
if self.text:
print "reading characters", str(content)
obj=self.doc.addObject("App::Annotation",'Text')
obj.LabelText = content.encode('latin1')
vec = Vector(self.x,-self.y,0)
if self.transform:
vec = self.translateVec(vec,self.transform)
print "own transform: ",self.transform, vec
for transform in self.grouptransform[::-1]:
#vec = self.translateVec(vec,transform)
vec = transform.multiply(vec)
print "applying vector: ",vec
obj.Position = vec
if gui:
obj.ViewObject.FontSize = int(self.text)
if self.fill: obj.ViewObject.TextColor = self.fill
else: obj.ViewObject.TextColor = (0.0,0.0,0.0,0.0)
def endElement(self, name):
if not name in ["tspan"]:
self.transform = None
self.text = None
if name == "g":
print "closing group"
self.grouptransform.pop()
def applyTrans(self,sh):
if isinstance(sh,Part.Shape):
if self.transform:
print "applying object transform: ",self.transform
sh = sh.transformGeometry(self.transform)
for transform in self.grouptransform[::-1]:
print "applying group transform: ", transform
sh = sh.transformGeometry(transform)
return sh
elif Draft.getType(sh) == "Dimension":
pts = []
for p in [sh.Start,sh.End,sh.Dimline]:
cp = Vector(p)
if self.transform:
print "applying object transform: ",self.transform
cp = self.transform.multiply(cp)
for transform in self.grouptransform[::-1]:
print "applying group transform: ",transform
cp = transform.multiply(cp)
pts.append(cp)
sh.Start = pts[0]
sh.End = pts[1]
sh.Dimline = pts[2]
def translateVec(self,vec,mat):
v = Vector(mat.A14,mat.A24,mat.A34)
return vec.add(v)
def getMatrix(self,tr):
"returns a FreeCAD matrix from a svg transform attribute"
transformre=re.compile('(matrix|translate|scale|rotate|skewX|skewY)\s*?\((.*?)\)',re.DOTALL)
m = FreeCAD.Matrix()
for transformation, arguments in transformre.findall(tr):
argsplit=[float(arg) for arg in arguments.replace(',',' ').split()]
#m.multiply(FreeCAD.Matrix (1,0,0,0,0,-1))
print '%s:%s %s %d' % (transformation, arguments,argsplit,len(argsplit))
if transformation == 'translate':
tx = argsplit[0]
ty = argsplit[1] if len(argsplit) > 1 else 0.0
m.move(Vector(tx,-ty,0))
elif transformation == 'scale':
sx = argsplit[0]
sy = argsplit[1] if len(argsplit) > 1 else sx
m.scale(Vector(sx,sy,1))
elif transformation == 'rotate':
angle = argsplit[0]
if len(argsplit) >= 3:
cx = argsplit[1]
cy = argsplit[2]
m.move(Vector(cx,-cy,0))
m.rotateZ(math.radians(-angle)) #mirroring one axis equals changing the direction of rotaion
if len(argsplit) >= 3:
m.move(Vector(-cx,cy,0))
elif transformation == 'skewX':
m=m.multiply(FreeCAD.Matrix(1,-math.tan(math.radians(argsplit[0]))))
elif transformation == 'skewY':
m=m.multiply(FreeCAD.Matrix(1,0,0,0,-math.tan(math.radians(argsplit[0]))))
elif transformation == 'matrix':
# '''transformation matrix:
# FreeCAD SVG
# (+A -C +0 +E) (A C 0 E)
# (-B +D -0 -F) = (-Y) * (B D 0 F) *(-Y)
# (+0 -0 +1 +0) (0 0 1 0)
# (+0 -0 +0 +1) (0 0 0 1)'''
m=m.multiply(FreeCAD.Matrix(argsplit[0],-argsplit[2],0,argsplit[4],-argsplit[1],argsplit[3],0,-argsplit[5]))
else:
print 'SKIPPED %s' % transformation
print "m= ",m
print "generating transformation: ",m
return m
def decodeName(name):
"decodes encoded strings"
try:
decodedName = (name.decode("utf8"))
except UnicodeDecodeError:
try:
decodedName = (name.decode("latin1"))
except UnicodeDecodeError:
print "svg: error: couldn't determine character encoding"
decodedName = name
return decodedName
def getContents(filename,tag,stringmode=False):
"gets the contents of all the occurences of the given tag in the given file"
result = {}
if stringmode:
contents = filename
else:
f = pythonopen(filename)
contents = ''
for line in f: contents += line
f.close()
contents = contents.replace('\n','_linebreak')
searchpat = '<'+tag+'.*?</'+tag+'>'
tags = re.findall(searchpat,contents)
for t in tags:
tagid = re.findall('id="(.*?)"',t)
if tagid:
tagid = tagid[0]
else:
tagid = 'none'
res = t.replace('_linebreak','\n')
result[tagid] = res
return result
def open(filename):
docname=os.path.split(filename)[1]
doc=FreeCAD.newDocument(docname)
doc.Label = decodeName(docname[:-4])
parser = xml.sax.make_parser()
parser.setContentHandler(svgHandler())
parser._cont_handler.doc = doc
f = pythonopen(filename)
parser.parse(f)
f.close()
doc.recompute()
return doc
def insert(filename,docname):
try:
doc=FreeCAD.getDocument(docname)
except:
doc=FreeCAD.newDocument(docname)
parser = xml.sax.make_parser()
parser.setContentHandler(svgHandler())
parser._cont_handler.doc = doc
parser.parse(pythonopen(filename))
doc.recompute()
def export(exportList,filename):
"called when freecad exports a file"
# finding sheet size
minx = 10000
miny = 10000
maxx = 0
maxy = 0
for ob in exportList:
if ob.isDerivedFrom("Part::Feature"):
for v in ob.Shape.Vertexes:
if v.Point.x < minx: minx = v.Point.x
if v.Point.x > maxx: maxx = v.Point.x
if v.Point.y < miny: miny = v.Point.y
if v.Point.y > maxy: maxy = v.Point.y
margin = (maxx-minx)*.01
minx -= margin
maxx += margin
miny -= margin
maxy += margin
sizex = maxx-minx
sizey = maxy-miny
miny += margin
boty = sizey+miny
# writing header
svg = pythonopen(filename,'wb')
svg.write('<?xml version="1.0"?>\n')
svg.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"')
svg.write(' "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
svg.write('<svg')
svg.write(' width="' + str(sizex) + '" height="' + str(sizey) + '"')
svg.write(' viewBox="0 0 ' + str(sizex) + ' ' + str(sizey) + '"')
svg.write(' xmlns="http://www.w3.org/2000/svg" version="1.1"')
svg.write('>\n')
# writing paths
for ob in exportList:
svg.write('<g transform="translate('+str(-minx)+','+str(-miny+(2*margin))+') scale(1,-1)">\n')
svg.write(Draft.getSVG(ob))
svg.write('</g>\n')
# closing
svg.write('</svg>')
svg.close()
FreeCAD.Console.PrintMessage("successfully exported "+filename)