""" Copyright (C) 2011-2013 Parametric Products Intellectual Holdings, LLC This file is part of CadQuery. CadQuery is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. CadQuery 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; If not, see """ import FreeCAD from FreeCAD import Drawing try: import xml.etree.cElementTree as ET except ImportError: import xml.etree.ElementTree as ET class ExportFormats: STL = "STL" BREP = "BREP" STEP = "STEP" AMF = "AMF" IGES = "IGES" class UNITS: MM = "mm" IN = "in" def guessUnitOfMeasure(shape): """ Guess the unit of measure of a shape. """ bb = shape.BoundBox dimList = [ bb.XLength, bb.YLength,bb.ZLength ] #no real part would likely be bigger than 10 inches on any side if max(dimList) > 10: return UNITS.MM #no real part would likely be smaller than 0.1 mm on all dimensions if min(dimList) < 0.1: return UNITS.IN #no real part would have the sum of its dimensions less than about 5mm if sum(dimList) < 10: return UNITS.IN return UNITS.MM class AmfExporter(object): def __init__(self,tessellation): self.units = "mm" self.tessellation = tessellation def writeAmf(self,outFileName): amf = ET.Element('amf',units=self.units) #TODO: if result is a compound, we need to loop through them object = ET.SubElement(amf,'object',id="0") mesh = ET.SubElement(object,'mesh') vertices = ET.SubElement(mesh,'vertices') volume = ET.SubElement(mesh,'volume') #add vertices for v in self.tessellation[0]: vtx = ET.SubElement(vertices,'vertex') coord = ET.SubElement(vtx,'coordinates') x = ET.SubElement(coord,'x') x.text = str(v.x) y = ET.SubElement(coord,'y') y.text = str(v.y) z = ET.SubElement(coord,'z') z.text = str(v.z) #add triangles for t in self.tessellation[1]: triangle = ET.SubElement(volume,'triangle') v1 = ET.SubElement(triangle,'v1') v1.text = str(t[0]) v2 = ET.SubElement(triangle,'v2') v2.text = str(t[1]) v3 = ET.SubElement(triangle,'v3') v3.text = str(t[2]) ET.ElementTree(amf).write(outFileName,encoding='ISO-8859-1') """ Objects that represent three.js JSON object notation https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3.0 """ class JsonExporter(object): def __init__(self): self.vertices = []; self.faces = []; self.nVertices = 0; self.nFaces = 0; def addVertex(self,x,y,z): self.nVertices += 1; self.vertices.extend([x,y,z]); #add triangle composed of the three provided vertex indices def addTriangleFace(self, i,j,k): #first position means justa simple triangle self.nFaces += 1; self.faces.extend([0,int(i),int(j),int(k)]); """ Get a json model from this model. For now we'll forget about colors, vertex normals, and all that stuff """ def toJson(self): return JSON_TEMPLATE % { 'vertices' : str(self.vertices), 'faces' : str(self.faces), 'nVertices': self.nVertices, 'nFaces' : self.nFaces }; def getPaths(freeCadSVG): """ freeCad svg is worthless-- except for paths, which are fairly useful this method accepts svg from fReeCAD and returns a list of strings suitable for inclusion in a path element returns two lists-- one list of visible lines, and one list of hidden lines HACK ALERT!!!!! FreeCAD does not give a way to determine which lines are hidden and which are not the only way to tell is that hidden lines are in a with 0.15 stroke and visible are 0.35 stroke. so we actually look for that as a way to parse. to make it worse, elementTree xpath attribute selectors do not work in python 2.6, and we cannot use python 2.7 due to freecad. So its necessary to look for the pure strings! ick! """ hiddenPaths = [] visiblePaths = [] if len(freeCadSVG) > 0: #yuk, freecad returns svg fragments. stupid stupid fullDoc = "%s" % freeCadSVG e = ET.ElementTree(ET.fromstring(fullDoc)) segments = e.findall("//g") for s in segments: paths = s.findall("path") if s.get("stroke-width") == "0.15": #hidden line HACK HACK HACK mylist = hiddenPaths else: mylist = visiblePaths for p in paths: mylist.append(p.get("d")) return (hiddenPaths,visiblePaths) else: return ([],[]) def getSVG(shape,opts=None): """ Export a shape to SVG """ d = {'width':800,'height':240,'marginLeft':200,'marginTop':20} if opts: d.update(opts) #need to guess the scale and the coordinate center uom = guessUnitOfMeasure(shape) width=float(d['width']) height=float(d['height']) marginLeft=float(d['marginLeft']) marginTop=float(d['marginTop']) #TODO: provide option to give 3 views viewVector = FreeCAD.Base.Vector(-1.75,1.1,5) (visibleG0,visibleG1,hiddenG0,hiddenG1) = Drawing.project(shape,viewVector) (hiddenPaths,visiblePaths) = getPaths(Drawing.projectToSVG(shape,viewVector,"ShowHiddenLines")) #this param is totally undocumented! #get bounding box -- these are all in 2-d space bb = visibleG0.BoundBox bb.add(visibleG1.BoundBox) bb.add(hiddenG0.BoundBox) bb.add(hiddenG1.BoundBox) #width pixels for x, height pixesl for y unitScale = min( width / bb.XLength * 0.75 , height / bb.YLength * 0.75 ) #compute amount to translate-- move the top left into view (xTranslate,yTranslate) = ( (0 - bb.XMin) + marginLeft/unitScale ,(0- bb.YMax) - marginTop/unitScale) #compute paths ( again -- had to strip out freecad crap ) hiddenContent = "" for p in hiddenPaths: hiddenContent += PATHTEMPLATE % p visibleContent = "" for p in visiblePaths: visibleContent += PATHTEMPLATE % p svg = SVG_TEMPLATE % ( { "unitScale" : str(unitScale), "strokeWidth" : str(1.0/unitScale), "hiddenContent" : hiddenContent , "visibleContent" :visibleContent, "xTranslate" : str(xTranslate), "yTranslate" : str(yTranslate), "width" : str(width), "height" : str(height), "textboxY" :str(height - 30), "uom" : str(uom) } ) #svg = SVG_TEMPLATE % ( # {"content": projectedContent} #) return svg def exportSVG(shape, fileName): """ accept a cadquery shape, and export it to the provided file TODO: should use file-like objects, not a fileName, and/or be able to return a string instead export a view of a part to svg """ svg = getSVG(shape.val().wrapped) f = open(fileName,'w') f.write(svg) f.close() JSON_TEMPLATE= """\ { "metadata" : { "formatVersion" : 3, "generatedBy" : "ParametricParts", "vertices" : %(nVertices)d, "faces" : %(nFaces)d, "normals" : 0, "colors" : 0, "uvs" : 0, "materials" : 1, "morphTargets" : 0 }, "scale" : 1.0, "materials": [ { "DbgColor" : 15658734, "DbgIndex" : 0, "DbgName" : "Material", "colorAmbient" : [0.0, 0.0, 0.0], "colorDiffuse" : [0.6400000190734865, 0.10179081114814892, 0.126246120426746], "colorSpecular" : [0.5, 0.5, 0.5], "shading" : "Lambert", "specularCoef" : 50, "transparency" : 1.0, "vertexColors" : false }], "vertices": %(vertices)s, "morphTargets": [], "normals": [], "colors": [], "uvs": [[]], "faces": %(faces)s } """ SVG_TEMPLATE = """ %(hiddenContent)s %(visibleContent)s X Y Z """ PATHTEMPLATE="\t\t\t\n"