
export Names as "id" attributes and Lables to "title" elements. avoid duplicate path names in SVG export handle faces with holes in SVG export sort the edges in given wires using fixWire()
1193 lines
64 KiB
Python
1193 lines
64 KiB
Python
|
|
#***************************************************************************
|
|
#* *
|
|
#* Copyright (c) 2009 Yorik van Havre <yorik@uncreated.net> *
|
|
#* *
|
|
#* 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://www.freecadweb.org"]
|
|
|
|
'''
|
|
This script imports SVG files in FreeCAD. Currently only reads the following entities:
|
|
paths, lines, circular arcs ,rects, circles, ellipses, polygons, polylines.
|
|
currently unsupported: use, image
|
|
'''
|
|
#ToDo:
|
|
# ignoring CDATA
|
|
# handle image element (external references and inline base64)
|
|
# debug Problem with 'Sans' font from Inkscape
|
|
# debug Problem with fill color
|
|
# implement inherting fill style from group
|
|
# handle relative units
|
|
|
|
import xml.sax, string, FreeCAD, os, math, re, Draft, DraftVecUtils
|
|
from FreeCAD import Vector
|
|
|
|
try: import FreeCADGui
|
|
except ImportError: gui = False
|
|
else: gui = True
|
|
try: draftui = FreeCADGui.draftToolBar
|
|
except AttributeError: draftui = None
|
|
|
|
if open.__module__ == '__builtin__':
|
|
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)
|
|
}
|
|
svgcolorslower = dict((key.lower(),value) for key,value in \
|
|
list(svgcolors.items()))
|
|
|
|
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[0] == "#"):
|
|
if len(color) == 7:
|
|
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)
|
|
elif len(color) == 4: #expand the hex digits
|
|
r = float(int(color[1],16)*17/255.0)
|
|
g = float(int(color[2],16)*17/255.0)
|
|
b = float(int(color[3],16)*17/255.0)
|
|
return (r,g,b,0.0)
|
|
elif color.lower().startswith('rgb('):
|
|
cvalues=color[3:].lstrip('(').rstrip(')').replace('%',' ').split(',')
|
|
if '%' in color:
|
|
r,g,b = [int(cv)/100.0 for cv in cvalues]
|
|
else:
|
|
r,g,b = [int(cv)/255.0 for cv in cvalues]
|
|
return (r,g,b,0.0)
|
|
else:
|
|
v=svgcolorslower.get(color.lower())
|
|
if v:
|
|
r,g,b = [float(vf)/255.0 for vf in v]
|
|
return (r,g,b,0.0)
|
|
#for k,v in svgcolors.iteritems():
|
|
# if (k.lower() == color.lower()): pass
|
|
|
|
def getsize(length,mode='discard',base=1):
|
|
"""parses length values containing number and unit
|
|
with mode 'discard': extracts a number from the given string (removes unit suffixes)
|
|
with mode 'tuple': return number and unit as a tuple
|
|
with mode 'css': convert the unit to px assuming 90dpi
|
|
with mode 'mm': convert the unit to millimeter assuming 90dpi"""
|
|
tomm={
|
|
'' : 25.4/90, #default
|
|
'px' : 25.4/90,
|
|
'pt' : 1.25*25.4/90,
|
|
'pc' : 15*25.4/90,
|
|
'mm' : 1.0,
|
|
'cm' : 10.0,
|
|
'in' : 25.4,
|
|
'em': 15*2.54/90, #arbitrarily chosen; has to depend on font size
|
|
'ex': 10*2.54/90, #arbitrarily chosen; has to depend on font size
|
|
|
|
'%': 100 #arbitrarily chosen; has to depend on vieport size or (for filling patterns) on bounding box
|
|
}
|
|
topx={
|
|
'' : 1.0, #default
|
|
'px' : 1.0,
|
|
'pt' : 1.25,
|
|
'pc' : 15,
|
|
'mm' : 90.0/25.4,
|
|
'cm' : 90.0/254.0,
|
|
'in' : 90,
|
|
'em': 15, #arbitrarily chosen; has to depend on font size
|
|
'ex': 10, #arbitrarily chosen; has to depend on font size
|
|
|
|
'%': 100 #arbitrarily chosen; has to depend on vieport size or (for filling patterns) on bounding box
|
|
}
|
|
number, exponent, unit=re.findall('([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)(px|pt|pc|mm|cm|in|em|ex|%)?',length)[0]
|
|
if mode =='discard':
|
|
return float(number)
|
|
elif mode == 'tuple':
|
|
return float(number),unit
|
|
elif mode == 'isabsolute':
|
|
return unit in ('mm','cm','in','px','pt')
|
|
elif mode == 'mm':
|
|
return float(number)*tomm[unit]
|
|
elif mode == 'css':
|
|
if unit != '%':
|
|
return float(number)*topx[unit]
|
|
else:
|
|
return float(number)*base
|
|
|
|
def makewire(path,checkclosed=False,donttry=False):
|
|
'''try to make a wire out of the list of edges. If the 'Wire' functions fails or the wire is not
|
|
closed if required the 'connectEdgesToWires' function is used'''
|
|
if not donttry:
|
|
try:
|
|
import DraftGeomUtils
|
|
sh = Part.Wire(DraftGeomUtils.sortEdges(path))
|
|
#sh = Part.Wire(path)
|
|
isok = (not checkclosed) or sh.isClosed()
|
|
except Part.OCCError:# BRep_API:command not done
|
|
isok = False
|
|
if donttry or not isok:
|
|
#Code from wmayer forum p15549 to fix the tolerance problem
|
|
#original tolerance = 0.00001
|
|
comp=Part.Compound(path)
|
|
sh = comp.connectEdgesToWires(False,10**(-1*(Draft.precision()-2))).Wires[0]
|
|
return sh
|
|
|
|
def arccenter2end(center,rx,ry,angle1,angledelta,xrotation=0.0):
|
|
'''calculate start and end vector and flags of an arc given in center parametrization
|
|
see http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
|
|
returns (v1,v2,largerc,sweep)'''
|
|
vr1=Vector(rx*math.cos(angle1),ry*math.sin(angle1),0)
|
|
vr2=Vector(rx*math.cos(angle1+angledelta),ry*math.sin(angle1+angledelta),0)
|
|
mxrot=FreeCAD.Matrix()
|
|
mxrot.rotateZ(xrotation)
|
|
v1 = mxrot.multiply(vr1).add(center)
|
|
v2 = mxrot.multiply(vr2).add(center)
|
|
fa = ((abs(angledelta) / math.pi) % 2) > 1 # <180deg
|
|
fs = angledelta < 0
|
|
return v1,v2,fa,fs
|
|
|
|
def arcend2center(lastvec,currentvec,rx,ry,xrotation=0.0,correction=False):
|
|
'''calculate (positive and negative) possible centers for an arc in endpoint parameterization
|
|
see http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
|
|
rotation or x-axis has to be specified in radians (CCW)
|
|
the sweepflag is interpreted as: sweepflag <==> arc is travelled clockwise
|
|
returns [(vcenter+,angle1+,angledelta+),(...-)]'''
|
|
#scalefacsign = 1 if (largeflag != sweepflag) else -1
|
|
rx = float(rx)
|
|
ry = float(ry)
|
|
v0 = lastvec.sub(currentvec)
|
|
v0.multiply(0.5)
|
|
m1=FreeCAD.Matrix()
|
|
m1.rotateZ(-xrotation) #Formular 6.5.1
|
|
v1=m1.multiply(v0)
|
|
if correction:
|
|
eparam = v1.x**2 / rx**2 + v1.y**2 / ry**2
|
|
if eparam > 1:
|
|
eproot = math.sqrt(eparam)
|
|
rx = eproot * rx
|
|
ry = eproot * ry
|
|
denom = rx**2 * v1.y**2+ ry**2 * v1.x**2
|
|
numer = rx**2 * ry**2 -denom
|
|
results=[]
|
|
if abs(numer/denom) < 10**(-1*(Draft.precision())):
|
|
scalefacpos = 0
|
|
else:
|
|
try:
|
|
scalefacpos = math.sqrt(numer/denom)
|
|
except ValueError:
|
|
FreeCAD.Console.PrintMessage('sqrt(%f/%f)\n' % (numer,denom))
|
|
scalefacpos = 0
|
|
for scalefacsign in (1,-1):
|
|
scalefac = scalefacpos * scalefacsign
|
|
vcx1 = Vector(v1.y*rx/ry,-v1.x*ry/rx,0).multiply(scalefac) # Step2 F.6.5.2
|
|
m2=FreeCAD.Matrix()
|
|
m2.rotateZ(xrotation)
|
|
centeroff = currentvec.add(lastvec)
|
|
centeroff.multiply(.5)
|
|
vcenter = m2.multiply(vcx1).add(centeroff) # Step3 F.6.5.3
|
|
#angle1 = Vector(1,0,0).getAngle(Vector((v1.x-vcx1.x)/rx,(v1.y-vcx1.y)/ry,0)) # F.6.5.5
|
|
#angledelta = Vector((v1.x-vcx1.x)/rx,(v1.y-vcx1.y)/ry,0).getAngle(Vector((-v1.x-vcx1.x)/rx,(-v1.y-vcx1.y)/ry,0)) # F.6.5.6
|
|
#we need the right sign for the angle
|
|
angle1 = DraftVecUtils.angle(Vector(1,0,0),Vector((v1.x-vcx1.x)/rx,(v1.y-vcx1.y)/ry,0)) # F.6.5.5
|
|
angledelta = DraftVecUtils.angle(Vector((v1.x-vcx1.x)/rx,(v1.y-vcx1.y)/ry,0),Vector((-v1.x-vcx1.x)/rx,(-v1.y-vcx1.y)/ry,0)) # F.6.5.6
|
|
results.append((vcenter,angle1,angledelta))
|
|
return results,(rx,ry)
|
|
|
|
|
|
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
|
|
self.viewbox = 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.linewidth)
|
|
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
|
|
|
|
FreeCAD.Console.PrintMessage('processing element %d: %s\n'%(self.count,name))
|
|
FreeCAD.Console.PrintMessage('existing group transform: %s\n'%(str(self.grouptransform)))
|
|
|
|
data = {}
|
|
for (keyword,content) in list(attrs.items()):
|
|
#print keyword,content
|
|
content = content.replace(',',' ')
|
|
content = content.split()
|
|
#print keyword,content
|
|
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],'css')
|
|
|
|
for k in ['fill','stroke','stroke-width','font-size']:
|
|
if k in data:
|
|
if isinstance(data[k],list):
|
|
if data[k][0].lower().startswith("rgb("):
|
|
data[k] = ",".join(data[k])
|
|
else:
|
|
data[k]=data[k][0]
|
|
|
|
# extracting style info
|
|
|
|
self.fill = None
|
|
self.color = None
|
|
self.width = None
|
|
self.text = None
|
|
|
|
if name == 'svg':
|
|
m=FreeCAD.Matrix()
|
|
if 'width' in data and 'height' in data and \
|
|
'viewBox' in data:
|
|
vbw=float(data['viewBox'][2])
|
|
vbh=float(data['viewBox'][3])
|
|
w=attrs.getValue('width')
|
|
h=attrs.getValue('height')
|
|
self.viewbox=(vbw,vbh)
|
|
if len(self.grouptransform)==0:
|
|
unitmode='mm'
|
|
else: #nested svg element
|
|
unitmode='css'
|
|
abw = getsize(w,unitmode)
|
|
abh = getsize(h,unitmode)
|
|
sx=abw/vbw
|
|
sy=abh/vbh
|
|
preservearstr=' '.join(data.get('preserveAspectRatio',[])).lower()
|
|
uniformscaling = round(sx/sy,5) == 1
|
|
if uniformscaling:
|
|
m.scale(Vector(sx,sy,1))
|
|
else:
|
|
FreeCAD.Console.PrintWarning('Scaling Factors do not match!!!\n')
|
|
if preservearstr.startswith('none'):
|
|
m.scale(Vector(sx,sy,1))
|
|
else: #preserve the aspect ratio
|
|
if preservearstr.endswith('slice'):
|
|
sxy=max(sx,sy)
|
|
else:
|
|
sxy=min(sx,sy)
|
|
m.scale(Vector(sxy,sxy,1))
|
|
elif len(self.grouptransform)==0:
|
|
#fallback to 90 dpi
|
|
m.scale(Vector(25.4/90.0,25.4/90.0,1))
|
|
self.grouptransform.append(m)
|
|
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'],'css')
|
|
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]
|
|
FreeCAD.Console.PrintMessage('name: %s\n'%pathname)
|
|
|
|
# processing paths
|
|
|
|
if name == "path":
|
|
FreeCAD.Console.PrintMessage('data: %s\n'%str(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)
|
|
self.lastdim = obj
|
|
data['d']=[]
|
|
pathcommandsre=re.compile('\s*?([mMlLhHvVaAcCqQsStTzZ])\s*?([^mMlLhHvVaAcCqQsStTzZ]*)\s*?',re.DOTALL)
|
|
pointsre=re.compile('([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)',re.DOTALL)
|
|
for d,pointsstr in pathcommandsre.findall(' '.join(data['d'])):
|
|
relative = d.islower()
|
|
pointlist = [float(number) for number,exponent in pointsre.findall(pointsstr.replace(',',' '))]
|
|
|
|
if (d == "M" or d == "m"):
|
|
x = pointlist.pop(0)
|
|
y = pointlist.pop(0)
|
|
if path:
|
|
#sh = Part.Wire(path)
|
|
sh = makewire(path)
|
|
if self.fill and sh.isClosed():
|
|
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(x,-y,0))
|
|
else:
|
|
lastvec = Vector(x,-y,0)
|
|
firstvec = lastvec
|
|
FreeCAD.Console.PrintMessage('move %s\n'%str(lastvec))
|
|
lastpole = None
|
|
if (d == "L" or d == "l") or \
|
|
((d == 'm' or d == 'M') and pointlist) :
|
|
for x,y in zip(pointlist[0::2],pointlist[1::2]):
|
|
if relative:
|
|
currentvec = lastvec.add(Vector(x,-y,0))
|
|
else:
|
|
currentvec = Vector(x,-y,0)
|
|
if not DraftVecUtils.equals(lastvec,currentvec):
|
|
seg = Part.Line(lastvec,currentvec).toShape()
|
|
FreeCAD.Console.PrintMessage("line %s %s\n" %(lastvec,currentvec))
|
|
lastvec = currentvec
|
|
path.append(seg)
|
|
lastpole = None
|
|
elif (d == "H" or d == "h"):
|
|
for x in pointlist:
|
|
if relative:
|
|
currentvec = lastvec.add(Vector(x,0,0))
|
|
else:
|
|
currentvec = Vector(x,lastvec.y,0)
|
|
seg = Part.Line(lastvec,currentvec).toShape()
|
|
lastvec = currentvec
|
|
lastpole = None
|
|
path.append(seg)
|
|
elif (d == "V" or d == "v"):
|
|
for y in pointlist:
|
|
if relative:
|
|
currentvec = lastvec.add(Vector(0,-y,0))
|
|
else:
|
|
currentvec = Vector(lastvec.x,-y,0)
|
|
seg = Part.Line(lastvec,currentvec).toShape()
|
|
lastvec = currentvec
|
|
lastpole = None
|
|
path.append(seg)
|
|
elif (d == "A" or d == "a"):
|
|
for rx,ry,xrotation, largeflag, sweepflag,x,y in \
|
|
zip(pointlist[0::7],pointlist[1::7],pointlist[2::7],pointlist[3::7],pointlist[4::7],pointlist[5::7],pointlist[6::7]):
|
|
|
|
#support for large-arc and x-rotation are missing
|
|
if relative:
|
|
currentvec = lastvec.add(Vector(x,-y,0))
|
|
else:
|
|
currentvec = Vector(x,-y,0)
|
|
chord = currentvec.sub(lastvec)
|
|
if (not largeflag) and abs(rx-ry) < 10**(-1*Draft.precision()): # small circular arc
|
|
# perp = chord.cross(Vector(0,0,-1))
|
|
# here is a better way to find the perpendicular
|
|
if sweepflag == 1:
|
|
# clockwise
|
|
perp = DraftVecUtils.rotate2D(chord,-math.pi/2)
|
|
else:
|
|
# anticlockwise
|
|
perp = DraftVecUtils.rotate2D(chord,math.pi/2)
|
|
chord.multiply(.5)
|
|
if chord.Length > rx: a = 0
|
|
else: a = math.sqrt(rx**2-chord.Length**2)
|
|
s = rx - a
|
|
perp.multiply(s/perp.Length)
|
|
midpoint = lastvec.add(chord.add(perp))
|
|
seg = Part.Arc(lastvec,midpoint,currentvec).toShape()
|
|
else:# big arc or elliptical arc
|
|
solution,(rx,ry) = arcend2center(lastvec,currentvec,rx,ry,math.radians(-xrotation),True)
|
|
negsol = (largeflag != sweepflag)
|
|
vcenter,angle1,angledelta = solution[negsol]
|
|
#print angle1
|
|
#print angledelta
|
|
if ry > rx:
|
|
rx,ry=ry,rx
|
|
swapaxis = True
|
|
else:
|
|
swapaxis = False
|
|
#print 'Elliptical arc %s rx=%f ry=%f' % (vcenter,rx,ry)
|
|
e1 = Part.Ellipse(vcenter,rx,ry)
|
|
if sweepflag:
|
|
#angledelta=-(-angledelta % (math.pi *2)) # Step4
|
|
#angledelta=(-angledelta % (math.pi *2)) # Step4
|
|
angle1 = angle1+angledelta
|
|
angledelta = -angledelta
|
|
#angle1 = math.pi - angle1
|
|
|
|
e1a = Part.Arc(e1,angle1-swapaxis*math.radians(90),\
|
|
angle1+angledelta-swapaxis*math.radians(90))
|
|
#e1a = Part.Arc(e1,angle1-0*swapaxis*math.radians(90),angle1+angledelta-0*swapaxis*math.radians(90))
|
|
if swapaxis or xrotation > 10**(-1*Draft.precision()):
|
|
m3=FreeCAD.Matrix()
|
|
m3.move(vcenter)
|
|
rot90=FreeCAD.Matrix(0,-1,0,0,1,0) #90
|
|
#swapaxism=FreeCAD.Matrix(0,1,0,0,1,0)
|
|
if swapaxis:
|
|
m3=m3.multiply(rot90)
|
|
m3.rotateZ(math.radians(-xrotation))
|
|
m3.move(vcenter.multiply(-1))
|
|
e1a.transform(m3)
|
|
seg = e1a.toShape()
|
|
if sweepflag:
|
|
seg.reverse()
|
|
#obj = self.doc.addObject("Part::Feature",'DEBUG %s'%pathname) #DEBUG
|
|
#obj.Shape = seg #DEBUG
|
|
#seg = Part.Line(lastvec,currentvec).toShape() #DEBUG
|
|
lastvec = currentvec
|
|
lastpole = None
|
|
path.append(seg)
|
|
elif (d == "C" or d == "c") or\
|
|
(d =="S" or d == "s"):
|
|
smooth = (d == 'S' or d == 's')
|
|
if smooth:
|
|
piter = list(zip(pointlist[2::4],pointlist[3::4],pointlist[0::4],pointlist[1::4],pointlist[2::4],pointlist[3::4]))
|
|
else:
|
|
piter = list(zip(pointlist[0::6],pointlist[1::6],pointlist[2::6],pointlist[3::6],pointlist[4::6],pointlist[5::6]))
|
|
for p1x,p1y,p2x,p2y,x,y in piter:
|
|
if smooth:
|
|
if lastpole is not None and lastpole[0]=='cubic':
|
|
pole1 = lastvec.sub(lastpole[1]).add(lastvec)
|
|
else:
|
|
pole1 = lastvec
|
|
else:
|
|
if relative:
|
|
pole1 = lastvec.add(Vector(p1x,-p1y,0))
|
|
else:
|
|
pole1 = Vector(p1x,-p1y,0)
|
|
if relative:
|
|
currentvec = lastvec.add(Vector(x,-y,0))
|
|
pole2 = lastvec.add(Vector(p2x,-p2y,0))
|
|
else:
|
|
currentvec = Vector(x,-y,0)
|
|
pole2 = Vector(p2x,-p2y,0)
|
|
|
|
if not DraftVecUtils.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) < 10**(-1*(2+Draft.precision())) and \
|
|
pole2.distanceToLine(lastvec,currentvec) < 10**(-1*(2+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)
|
|
elif (d == "Q" or d == "q") or\
|
|
(d =="T" or d == "t"):
|
|
smooth = (d == 'T' or d == 't')
|
|
if smooth:
|
|
piter = list(zip(pointlist[1::2],pointlist[1::2],pointlist[0::2],pointlist[1::2]))
|
|
else:
|
|
piter = list(zip(pointlist[0::4],pointlist[1::4],pointlist[2::4],pointlist[3::4]))
|
|
for px,py,x,y in piter:
|
|
if smooth:
|
|
if lastpole is not None and lastpole[0]=='quadratic':
|
|
pole = lastvec.sub(lastpole[1]).add(lastvec)
|
|
else:
|
|
pole = lastvec
|
|
else:
|
|
if relative:
|
|
pole = lastvec.add(Vector(px,-py,0))
|
|
else:
|
|
pole = Vector(px,-py,0)
|
|
if relative:
|
|
currentvec = lastvec.add(Vector(x,-y,0))
|
|
else:
|
|
currentvec = Vector(x,-y,0)
|
|
|
|
if not DraftVecUtils.equals(currentvec,lastvec):
|
|
if True and \
|
|
pole.distanceToLine(lastvec,currentvec) < 20**(-1*(2+Draft.precision())):
|
|
#print "straight segment"
|
|
seg = Part.Line(lastvec,currentvec).toShape()
|
|
else:
|
|
#print "quadratic bezier segment"
|
|
b = Part.BezierCurve()
|
|
b.setPoles([lastvec,pole,currentvec])
|
|
seg = b.toShape()
|
|
#print "connect ",lastvec,currentvec
|
|
lastvec = currentvec
|
|
lastpole = ('quadratic',pole)
|
|
path.append(seg)
|
|
elif (d == "Z") or (d == "z"):
|
|
if not DraftVecUtils.equals(lastvec,firstvec):
|
|
try:
|
|
seg = Part.Line(lastvec,firstvec).toShape()
|
|
except Part.OCCError:
|
|
pass
|
|
else:
|
|
path.append(seg)
|
|
if path: #the path should be closed by now
|
|
#sh=makewire(path,True)
|
|
sh=makewire(path,donttry=False)
|
|
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 recent draw command
|
|
point = []
|
|
command = None
|
|
if path:
|
|
sh=makewire(path,checkclosed=False)
|
|
#sh = Part.Wire(path)
|
|
if self.fill and sh.isClosed():
|
|
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
|
|
#ToTo: check for ry>rx !!!!
|
|
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
|
|
if rx > ry:
|
|
mj = rx
|
|
mi = ry
|
|
else:
|
|
mj = ry
|
|
mi = rx
|
|
p1=Vector(data['x']+rx,-data['y']-data['height']+ry,0)
|
|
e1=Part.Ellipse(p1,mj,mi)
|
|
p2=Vector(data['x']+data['width']-rx,-data['y']-data['height']+ry,0)
|
|
e2=Part.Ellipse(p2,mj,mi)
|
|
p3=Vector(data['x']+data['width']-rx,-data['y']-ry,0)
|
|
e3=Part.Ellipse(p3,mj,mi)
|
|
p4=Vector(data['x']+rx,-data['y']-ry,0)
|
|
e4=Part.Ellipse(p4,mj,mi)
|
|
if rx > ry:
|
|
e1a=Part.Arc(e1,math.radians(180),math.radians(270))
|
|
e2a=Part.Arc(e2,math.radians(270),math.radians(360))
|
|
e3a=Part.Arc(e3,math.radians(0),math.radians(90))
|
|
e4a=Part.Arc(e4,math.radians(90),math.radians(180))
|
|
esh=[e1a.toShape(),e2a.toShape(),e3a.toShape(),e4a.toShape()]
|
|
else:
|
|
e1a=Part.Arc(e1,math.radians(90),math.radians(180))
|
|
e2a=Part.Arc(e2,math.radians(180),math.radians(270))
|
|
e3a=Part.Arc(e3,math.radians(270),math.radians(360))
|
|
e4a=Part.Arc(e4,math.radians(0),math.radians(90))
|
|
rot90=FreeCAD.Matrix(0,-1,0,0,1,0)
|
|
esh=[]
|
|
for arc,point in ((e1a,p1),(e2a,p2),(e3a,p3),(e4a,p4)):
|
|
m1=FreeCAD.Matrix()
|
|
m1.move(point.multiply(1))
|
|
m1=m1.multiply(rot90)
|
|
m1.move(point.multiply(-1))
|
|
#m1.move(point)
|
|
arc.transform(m1)
|
|
esh.append(arc.toShape())
|
|
for esh1,esh2 in zip(esh[-1:]+esh[:-1],esh):
|
|
p1,p2 = esh1.Vertexes[-1].Point,esh2.Vertexes[0].Point
|
|
if not DraftVecUtils.equals(p1,p2):
|
|
edges.append(Part.Line(esh1.Vertexes[-1].Point,esh2.Vertexes[0].Point).toShape()) #straight segments
|
|
edges.append(esh2) # elliptical segments
|
|
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']]
|
|
FreeCAD.Console.PrintMessage('points %s\n'%str(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 DraftVecUtils.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 and sh.isClosed():
|
|
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']
|
|
if rx > ry:
|
|
sh = Part.Ellipse(c,rx,ry).toShape()
|
|
else:
|
|
sh = Part.Ellipse(c,ry,rx).toShape()
|
|
m3=FreeCAD.Matrix()
|
|
m3.move(c)
|
|
rot90=FreeCAD.Matrix(0,-1,0,0,1,0) #90
|
|
m3=m3.multiply(rot90)
|
|
m3.move(c.multiply(-1))
|
|
sh.transformShape(m3)
|
|
#sh = sh.transformGeometry(m3)
|
|
if self.fill:
|
|
sh = Part.Wire([sh])
|
|
sh = Part.Face(sh)
|
|
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):
|
|
FreeCAD.Console.PrintMessage("processing a text\n")
|
|
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'],'css')
|
|
else:
|
|
self.text = 1
|
|
else:
|
|
if self.lastdim:
|
|
self.lastdim.ViewObject.FontSize = int(getsize(data['font-size']))
|
|
|
|
FreeCAD.Console.PrintMessage("done processing element %d\n"%self.count)
|
|
|
|
def characters(self,content):
|
|
if self.text:
|
|
FreeCAD.Console.PrintMessage("reading characters %s\n" % 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" or name == "svg":
|
|
FreeCAD.Console.PrintMessage("closing group\n")
|
|
self.grouptransform.pop()
|
|
|
|
def applyTrans(self,sh):
|
|
if isinstance(sh,Part.Shape):
|
|
if self.transform:
|
|
FreeCAD.Console.PrintMessage("applying object transform: %s\n" % self.transform)
|
|
sh = sh.transformGeometry(self.transform)
|
|
for transform in self.grouptransform[::-1]:
|
|
FreeCAD.Console.PrintMessage("applying group transform: %s\n" % 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:
|
|
FreeCAD.Console.PrintMessage("applying object transform: %s\n" % self.transform)
|
|
cp = self.transform.multiply(cp)
|
|
for transform in self.grouptransform[::-1]:
|
|
FreeCAD.Console.PrintMessage("applying group transform: %s\n" % 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:
|
|
FreeCAD.Console.PrintError("svg: error: couldn't determine character encoding\n")
|
|
|
|
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 = f.read()
|
|
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 NameError:
|
|
doc=FreeCAD.newDocument(docname)
|
|
FreeCAD.ActiveDocument = doc
|
|
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"
|
|
|
|
svg_export_style = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetInt("svg_export_style")
|
|
if svg_export_style != 0 and svg_export_style != 1:
|
|
FreeCAD.Console.PrintMessage("unknown svg export style, switching to Translated\n")
|
|
svg_export_style = 0
|
|
|
|
# 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
|
|
if svg_export_style == 0:
|
|
# translated-style exports get a bit of a margin
|
|
margin = (maxx-minx)*.01
|
|
else:
|
|
# raw-style exports get no margin
|
|
margin = 0
|
|
|
|
minx -= margin
|
|
maxx += margin
|
|
miny -= margin
|
|
maxy += margin
|
|
sizex = maxx-minx
|
|
sizey = maxy-miny
|
|
miny += margin
|
|
|
|
# writing header
|
|
# we specify the svg width and height in FreeCAD's physical units (mm),
|
|
# and specify the viewBox so that user units maps one-to-one to mm
|
|
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) + 'mm" height="' + str(sizey) + 'mm"')
|
|
if svg_export_style == 0:
|
|
# translated-style exports have the viewbox starting at X=0, Y=0
|
|
svg.write(' viewBox="0 0 ' + str(sizex) + ' ' + str(sizey) + '"')
|
|
else:
|
|
# raw-style exports have the viewbox starting at X=xmin, Y=-ymax
|
|
# we need the funny Y here because SVG is upside down, and we
|
|
# flip the sketch right-way up with a scale later
|
|
svg.write(' viewBox="%f %f %f %f"' %(minx,-maxy,sizex,sizey))
|
|
svg.write(' xmlns="http://www.w3.org/2000/svg" version="1.1"')
|
|
svg.write('>\n')
|
|
|
|
# writing paths
|
|
for ob in exportList:
|
|
if svg_export_style == 0:
|
|
# translated-style exports have the entire sketch translated to fit in the X>0, Y>0 quadrant
|
|
#svg.write('<g transform="translate('+str(-minx)+','+str(-miny+(2*margin))+') scale(1,-1)">\n')
|
|
svg.write('<g id="%s" transform="translate(%f,%f) '
|
|
'scale(1,-1)">\n'% (ob.Name,-minx,maxy))
|
|
else:
|
|
# raw-style exports do not translate the sketch
|
|
svg.write('<g id="%s" transform="scale(1,-1)">\n' %\
|
|
ob.Name)
|
|
svg.write(Draft.getSVG(ob))
|
|
svg.write('<title>%s</title>\n' % ob.Label.encode('utf8')\
|
|
.replace('<','<').replace('>','>'))
|
|
# replace('"',\ """)
|
|
svg.write('</g>\n')
|
|
# closing
|
|
svg.write('</svg>')
|
|
svg.close()
|