From e22266d5eb4098a9dd6b93683eefc8e63a76bc60 Mon Sep 17 00:00:00 2001 From: Sebastian Hoogen Date: Wed, 13 Aug 2014 09:28:51 +0200 Subject: [PATCH] improvements to exportDRAWEXE load only MODELING module instead of ALL show TypeId of unsupported Objects use angel of revolution for cones improve sweep output support for point, vertex, Part and Draft Polygon cache object names to reuse them in DRAWEXE do not expode spine which are allready edges don't try to make faces from arcs (if this failed in FreeCAD allready) use rounded floats if they safe digits Sometimes the floats seems to be rounded to twelve decimal places. we round them if it allows so safe digigts. round angles --- src/Mod/OpenSCAD/exportDRAWEXE.py | 924 +++++++++++++++++------------- 1 file changed, 530 insertions(+), 394 deletions(-) diff --git a/src/Mod/OpenSCAD/exportDRAWEXE.py b/src/Mod/OpenSCAD/exportDRAWEXE.py index e069f4285c..7dac60c4f7 100644 --- a/src/Mod/OpenSCAD/exportDRAWEXE.py +++ b/src/Mod/OpenSCAD/exportDRAWEXE.py @@ -28,10 +28,49 @@ import FreeCAD, Part if open.__module__ == '__builtin__': pythonopen = open -def f2s(n): - '''convert to numerical value to string''' +# unsupported primitives +# Part:: Plane, Helix, Spiral, Wedge, Elipsoid +# Draft: Rectangle, BSpline, BezCurve + +def f2s(n,angle=False): + '''convert to numerical value to string + try to remove no significant digits, by guessing a former rounding + if it fail use 18 decimal place in fixed point notation + ''' + if abs(n) < 1e-14: return '0' + elif len(('%0.13e' % n).split('e')[0].rstrip('0') ) < 6: + return ('%0.10f' % n).rstrip('0').rstrip('.') + elif not angle and len(('%0.15e' % n).split('e')[0].rstrip('0') ) < 15: + return ('%0.15f' % n).rstrip('0').rstrip('.') + elif angle and len(('%0.6e' % n).split('e')[0].rstrip('0') ) < 3: + return ('%0.5f' % n).rstrip('0').rstrip('.') + else: + return ('%0.18f' % n).rstrip('0').rstrip('.') #return str(float(n)) - return ('%0.18f' % n).rstrip('0').rstrip('.') + +def polygonstr(r,pcount): + import math + v=FreeCAD.Vector(r,0,0) + m=FreeCAD.Matrix() + m.rotateZ(2*math.pi/pcount) + points=[] + for i in range(pcount): + points.append(v) + v=m.multiply(v) + points.append(v) + return ' '.join('%s %s %s'%(f2s(v.x),f2s(v.y),f2s(v.z)) \ + for v in points) + +def formatobjtype(ob): + objtype=ob.TypeId + if (ob.isDerivedFrom('Part::FeaturePython') or \ + ob.isDerivedFrom('Part::Part2DObjectPython') or\ + ob.isDerivedFrom('App::FeaturePython')) and \ + hasattr(ob.Proxy,'__module__'): + return '%s::%s.%s' % (ob.TypeId,ob.Proxy.__module__,\ + ob.Proxy.__class__.__name__) + else: + return ob.TypeId def placement2draw(placement,name='object'): """converts a FreeCAD Placement to trotate and ttranslate commands""" @@ -41,7 +80,7 @@ def placement2draw(placement,name='object'): dx,dy,dz=placement.Rotation.Axis an=math.degrees(placement.Rotation.Angle) drawcommand += "trotate %s 0 0 0 %s %s %s %s\n" % \ - (name,f2s(dx),f2s(dy),f2s(dz),f2s(an)) + (name,f2s(dx),f2s(dy),f2s(dz),f2s(an,angle=True)) if placement.Base.Length > 1e-8: x,y,z=placement.Base drawcommand += "ttranslate %s %s %s %s\n" % \ @@ -76,94 +115,6 @@ def saveShape(csg,filename,shape,name,hasplacement = True,cleanshape=False): sh.exportBrep(breppath) return hasplacement -def saveSweep(csg,ob,filename): - import Part - spine,subshapelst=ob.Spine - #process_object(csg,spine,filename) - explodeshape = process_object(spine) - if explodeshape: - try: - #raise NotImplementedError # hit the fallback - # currently all subshapes are edges - process_object(spine,csg,filename) - csg.write('explode %s E\n' % spine.Name ) - edgelst = ' '.join(('%s_%s' % (spine.Name,ss[4:]) for ss \ - in subshapelst)) - spinename = '%s-0-spine' % ob.Name - csg.write('wire %s %s\n' %(spinename,edgelst)) - except: - explodeshape = False # fallback - raise - if not explodeshape: # extract only the used subshape - path=Part.Wire([spine.Shape.getElement(subshapename) for \ - subshapename in subshapelst]) - if spine.Shape.ShapeType == 'Edge': - path = spine.Shape - elif spine.Shape.ShapeType == 'Wire': - path = Part.Wire(spine.Shape) - else: - raise ValueError('Unsuitabel Shape Type') - spinename = '%s-0-spine' % ob.Name - saveShape(csg,filename, path,spinename,None) # placement with shape - #safePlacement(ob.Placement,ob.Name) - csg.write('mksweep %s\n' % spinename) - #setsweep - setoptions=[] - buildoptions=[] - if ob.Frenet: - setoptions.append('-FR') - else: - setoptions.append('-CF') - if ob.Transition == 'Transformed': - buildoptions.append('-M') - elif ob.Transition == 'Right corner': - buildoptions.append('-C') - elif ob.Transition == 'Round corner': - buildoptions.append('-R') - if ob.Solid: - buildoptions.append('-S') - csg.write('setsweep %s\n' % (" ".join(setoptions))) - #addsweep - sections=ob.Sections - sectionnames = [] - for i,subobj in enumerate(ob.Sections): - #process_object(csg,subobj,filename) - #sectionsnames.append(subobj.Name) - #d1['basename']=subobj.Name - sectionname = '%s-0-section-%02d-%s' % (ob.Name,i,subobj.Name) - addoptions=[] - explodeshape = process_object(subobj) - if explodeshape: - sh = subobj.Shape - if sh.ShapeType == 'Wire' or sh.ShapeType == 'Edge' or \ - sh.ShapeType == 'Face' and len(sh.Wires) == 1: - process_object(subobj,csg,filename) - if sh.ShapeType == 'Wire': - #csg.write('tcopy %s %s\n' %(subobj.Name,sectionname)) - sectionname = subobj.Name - if sh.ShapeType == 'Edge': - csg.write('explode %s E\n' % subobj.Name ) - csg.write('wire %s %s_1\n' %(sectionname,subobj.Name)) - if sh.ShapeType == 'Face': - #we should use outer wire when it becomes avaiable - csg.write('explode %s W\n' % subobj.Name ) - #csg.write('tcopy %s_1 %s\n' %(subobj.Name,sectionname)) - sectionname ='%s_1' % subobj.Name - else: - explodeshape = False - if not explodeshape: # extract only the used subshape - sh = subobj.Shape - if sh.ShapeType == 'Vertex': - pass - elif sh.ShapeType == 'Wire' or sh.ShapeType == 'Edge': - sh = Part.Wire(sh) - elif sh.ShapeType == 'Face': - sh = sh.OuterWire - else: - raise ValueError('Unrecognized Shape Type') - saveShape(csg,filename,sh,sectionname,None) # placement with shape - csg.write('addsweep %s %s\n' % (sectionname," ".join(addoptions))) - csg.write('buildsweep %s %s\n' % (ob.Name," ".join(buildoptions))) def isDraftFeature(ob): @@ -185,6 +136,16 @@ def isDraftCircle(ob): import Draft return isinstance(ob.Proxy,Draft._Circle) +def isDraftPolygon(ob): + if isDraftFeature(ob): + import Draft + return isinstance(ob.Proxy,Draft._Polygon) + +def isDraftPoint(ob): + if isDraftFeature(ob): + import Draft + return isinstance(ob.Proxy,Draft._Point) + def isDraftWire(ob): if isDraftFeature(ob): import Draft @@ -224,315 +185,490 @@ def isDeform(ob): -def process_object(ob,csg=None,filename='unnamed'): - d1 = {'name':ob.Name} - hasplacement = not ob.Placement.isNull() - if ob.TypeId in ["Part::Cut","Part::Fuse","Part::Common","Part::Section"]: - if csg is None: - return True # The object is supported - d1.update({'part':ob.Base.Name,'tool':ob.Tool.Name,\ - 'command':'b%s' % ob.TypeId[6:].lower()}) - process_object(ob.Base,csg,filename) - process_object(ob.Tool,csg,filename) - csg.write("%(command)s %(name)s %(part)s %(tool)s\n"%d1) - elif ob.TypeId == "Part::Sphere" : - if csg is None: - return True # The object is supported - d1.update({'radius':f2s(ob.Radius),'angle1':f2s(ob.Angle1),\ - 'angle2':f2s(ob.Angle2),'angle3':f2s(ob.Angle3)}) - csg.write('psphere %(name)s %(radius)s %(angle1)s %(angle2)s '\ - '%(angle3)s\n'%d1) - elif ob.TypeId == "Part::Box" : - if csg is None: - return True # The object is supported - d1.update({'dx':f2s(ob.Length),'dy':f2s(ob.Width),'dz':f2s(ob.Height)}) - csg.write('box %(name)s %(dx)s %(dy)s %(dz)s\n'%d1) - elif ob.TypeId == "Part::Cylinder" : - if csg is None: - return True # The object is supported - d1.update({'radius':f2s(ob.Radius),'height':f2s(ob.Height),\ - 'angle':f2s(ob.Angle)}) - csg.write('pcylinder %(name)s %(radius)s %(height)s %(angle)s\n'%d1) - elif ob.TypeId == "Part::Cone" : - if csg is None: - return True # The object is supported - d1.update({'radius1':f2s(ob.Radius1),'radius2':f2s(ob.Radius2),\ - 'height':f2s(ob.Height)}) - csg.write('pcone %(name)s %(radius1)s %(radius2)s %(height)s\n'%d1) - elif ob.TypeId == "Part::Torus" : - if csg is None: - return True # The object is supported - d1.update({'radius1':f2s(ob.Radius1),'radius2':f2s(ob.Radius2),\ - 'angle1': f2s(ob.Angle1),'angle2':f2s(ob.Angle2),\ - 'angle3': f2s(ob.Angle3)}) - csg.write('ptorus %(name)s %(radius1)s %(radius2)s %(angle1)s '\ - '%(angle2)s %(angle3)s\n' % d1) - elif ob.TypeId == "Part::Mirroring" : - if csg is None: - return True # The object is supported - process_object(ob.Source,csg,filename) - csg.write('tcopy %s %s\n'%(ob.Source.Name,d1['name'])) - b=ob.Base - d1['x']=f2s(ob.Base.x) - d1['y']=f2s(ob.Base.y) - d1['z']=f2s(ob.Base.z) - d1['dx']=f2s(ob.Normal.x) - d1['dy']=f2s(ob.Normal.y) - d1['dz']=f2s(ob.Normal.z) - csg.write('tmirror %(name)s %(x)s %(y)s %(z)s %(dx)s %(dy)s %(dz)s\n' \ - % d1) - elif ob.TypeId == 'Part::Compound': - if len(ob.Links) == 0: - pass - elif len(ob.Links) == 1: - if csg is None: - return process_object(ob.Links[0],None,filename) - process_object(ob.Links[0],csg,filename) - csg.write('tcopy %s %s\n'%(ob.Links[0].Name,d1['name'])) + +class Drawexporter(object): + def __init__(self, filename): + self.objectcache=set() + self.csg = pythonopen(filename,'w') + #self.csg=csg + self.filename=filename + #settings + self.alwaysexplode = True + self.cleanshape = False + + def __enter__(self): + return self + + def write_header(self): + import FreeCAD + self.csg.write('#generated by FreeCAD %s\n' % \ + '.'.join(FreeCAD.Version()[0:3])) + self.csg.write('pload MODELING\n') + + def write_displayonly(self,objlst): + self.csg.write('donly %s\n'%' '.join([obj.Name for obj in objlst])) + + def saveSweep(self,ob): + import Part + spine,subshapelst=ob.Spine + #process_object(csg,spine,filename) + explodeshape = self.alwaysexplode or self.process_object(spine,True) + if explodeshape: + self.process_object(spine) + if len(subshapelst) and spine.Shape.ShapeType != 'Edge': + #raise NotImplementedError # hit the fallback + # currently all subshapes are edges + self.csg.write('explode %s E\n' % spine.Name ) + edgelst = ' '.join(('%s_%s' % (spine.Name,ss[4:]) for ss \ + in subshapelst)) + spinename = '%s-0-spine' % ob.Name + self.csg.write('wire %s %s\n' %(spinename,edgelst)) + elif spine.Shape.ShapeType == 'Wire': + spinename = spine.Name + elif spine.Shape.ShapeType == 'Edge': + spinename = '%s-0-spine' % ob.Name + self.csg.write('wire %s %s\n' %(spinename,spine.Name)) + else: # extract only the used subshape + if len(subshapelst): + path=Part.Wire([spine.Shape.getElement(subshapename) for \ + subshapename in subshapelst]) + elif spine.Shape.ShapeType == 'Edge': + path = spine.Shape + elif spine.Shape.ShapeType == 'Wire': + path = Part.Wire(spine.Shape) + else: + raise ValueError('Unsuitabel Shape Type') + spinename = '%s-0-spine' % ob.Name + saveShape(self.csg,self.filename, path,spinename,None,\ + self.cleanshape) # placement with shape + #safePlacement(ob.Placement,ob.Name) + self.csg.write('mksweep %s\n' % spinename) + #setsweep + setoptions=[] + buildoptions=[] + if ob.Frenet: + setoptions.append('-FR') else: - if csg is None: - return True # The object is supported - basenames=[] - for i,subobj in enumerate(ob.Links): - process_object(subobj,csg,filename) - basenames.append(subobj.Name) - csg.write('compound %s %s\n' % (' '.join(basenames),ob.Name)) - elif ob.TypeId in ["Part::MultiCommon", "Part::MultiFuse"]: - if len(ob.Shapes) == 0: - pass - elif len(ob.Shapes) == 1: - if csg is None: - return process_object(ob.Shapes[0],None,filename) - process_object(ob.Shapes[0],csg,filename) - csg.write('tcopy %s %s\n'%(ob.Shapes[0].Name,d1['name'])) - else: - if csg is None: - return True # The object is supported - topname = ob.Name - command = 'b%s' % ob.TypeId[11:].lower() - lst1=ob.Shapes[:] - current=lst1.pop(0) - curname=current.Name - process_object(current,csg,filename) - i=1 - while lst1: - if len(lst1) >= 2: - nxtname='to-%s-%03d-t'%(topname,i) - else: - nxtname=topname - nxt=lst1.pop(0) - process_object(nxt,csg,filename) - csg.write("%s %s %s %s\n"%(command,nxtname,curname,nxt.Name)) - curname=nxtname - i+=1 - elif ob.TypeId == "Part::Prism" : - if csg is None: - return True # The object is supported - import math - polyname = '%s-polyline' % d1['name'] - wirename = '%s-polywire' % d1['name'] - facename = '%s-polyface' % d1['name'] - d1['base']= facename - m=FreeCAD.Matrix() - v=FreeCAD.Vector(ob.Circumradius.Value,0,0) - m.rotateZ(2*math.pi/ob.Polygon) - points=[] - for i in range(ob.Polygon): - points.append(v) - v=m.multiply(v) - points.append(v) - pointstr=' '.join('%s %s %s'%(f2s(v.x),f2s(v.y),f2s(v.z)) \ - for v in points) - csg.write('polyline %s %s\n' % (polyname,pointstr)) - csg.write('wire %s %s\n' %(wirename,polyname)) - csg.write('mkplane %s %s\n' % (facename,polyname)) - csg.write('prism %s %s 0 0 %s\n' % (d1['name'],facename,\ - f2s(ob.Height.Value))) - elif ob.TypeId == "Part::Extrusion" and ob.TaperAngle.Value == 0: - if csg is None: - return True # The object is supported - process_object(ob.Base,csg,filename) - #Warning does not fully ressemle the functionallity of - #Part::Extrusion - #csg.write('tcopy %s %s\n'%(ob.Base.Name,d1['name'])) - facename=ob.Base.Name - csg.write('prism %s %s %s %s %s\n' % (d1['name'],facename,\ - f2s(ob.Dir.x),f2s(ob.Dir.y),f2s(ob.Dir.z))) - elif ob.TypeId == "Part::Fillet" and True: #disabled - if csg is None: - return True # The object is supported - process_object(ob.Base,csg,filename) - csg.write('explode %s E\n' % ob.Base.Name ) - csg.write('blend %s %s %s\n' % (d1['name'],ob.Base.Name,\ - ' '.join(('%s %s'%(f2s(e[1]),'%s_%d' % (ob.Base.Name,e[0])) \ - for e in ob.Edges)))) - elif ob.TypeId == "Part::Sweep" and True: - if csg is None: - return True # The object is supported - saveSweep(csg,ob,filename) - elif ob.TypeId == "Part::Loft": - if csg is None: - return True # The object is supported - sectionnames=[] + setoptions.append('-CF') + if ob.Transition == 'Transformed': + buildoptions.append('-M') + elif ob.Transition == 'Right corner': + buildoptions.append('-C') + elif ob.Transition == 'Round corner': + buildoptions.append('-R') + if ob.Solid: + buildoptions.append('-S') + self.csg.write('setsweep %s\n' % (" ".join(setoptions))) + #addsweep + sections=ob.Sections + sectionnames = [] for i,subobj in enumerate(ob.Sections): - explodeshape = process_object(suboobj) - if explodeshape and False: #diabled TBD - try: - raise NotImplementedError - sectionname = '%s-%02d-section' % (ob.Name,i) - sh = subobj.Shape - if sh.isNull(): - raise ValueError # hit the fallback - tempname=spine.Name - if sh.ShapeType == 'Compound': - sh = sh.childShapes()[0] - csg.write('explode %s\n' % tempname ) - tempname = '%s_1' % tempname + #process_object(csg,subobj,filename) + #sectionsnames.append(subobj.Name) + #d1['basename']=subobj.Name + sectionname = '%s-0-section-%02d-%s' % (ob.Name,i,subobj.Name) + addoptions=[] + explodeshape = self.alwaysexplode or \ + self.process_object(subobj,True) + if explodeshape: + sh = subobj.Shape + if sh.ShapeType == 'Vertex' or sh.ShapeType == 'Wire' or \ + sh.ShapeType == 'Edge' or \ + sh.ShapeType == 'Face' and len(sh.Wires) == 1: + self.process_object(subobj) + if sh.ShapeType == 'Wire' or sh.ShapeType == 'Vertex': + #csg.write('tcopy %s %s\n' %(subobj.Name,sectionname)) + sectionname = subobj.Name + if sh.ShapeType == 'Edge': + self.csg.write('explode %s E\n' % subobj.Name ) + self.csg.write('wire %s %s_1\n' %(sectionname,subobj.Name)) if sh.ShapeType == 'Face': - #sh = sh.OuterWire #not available - if len(sh.Wires) == 1: - sh=sh.Wires[0] - csg.write('explode %s\n W' % tempname ) - tempname = '%s_1' % tempname - else: - raise NotImplementedError - elif sh.ShapeType == 'Edge': - csg.write('wire %s %s\n' %(sectionname,tempname)) - tempname = sectionname - sectionname = tempname - except NotImplementedError: - explodeshape = False # fallback + #we should use outer wire when it becomes avaiable + self.csg.write('explode %s W\n' % subobj.Name ) + #csg.write('tcopy %s_1 %s\n' %(subobj.Name,sectionname)) + sectionname ='%s_1' % subobj.Name + else: + explodeshape = False if not explodeshape: # extract only the used subshape sh = subobj.Shape - if not sh.isNull(): - if sh.ShapeType == 'Compound': - sh = sh.childShapes()[0] - if sh.ShapeType == 'Face': - sh = sh.OuterWire - elif sh.ShapeType == 'Edge': - import Part - sh = Part.Wire([sh]) - elif sh.ShapeType == 'Wire': - import Part - sh = Part.Wire(sh) - elif sh.ShapeType == 'Vertex': - pass - else: - raise ValueError('Unsuitabel Shape Type') - sectionname = '%s-%02d-section' % (ob.Name,i) - saveShape(csg,filename, sh,sectionname,None) - # placement with shape - sectionnames.append(sectionname) - if ob.Closed: - sectionnames.append(sectionnames[0]) - csg.write('thrusections %s %d %d %s\n' % (ob.Name,int(ob.Solid),\ - int(ob.Ruled), ' '.join(sectionnames))) - elif isDeform(ob): #non-uniform scaling - if csg is None: - return True # The object is supported - m=ob.Matrix - process_object(ob.Base,csg,filename) - #csg.write('tcopy %s %s\n'%(ob.Base.Name,d1['name'])) - d1['basename']=ob.Base.Name - d1['cx']=f2s(m.A11) - d1['cy']=f2s(m.A22) - d1['cz']=f2s(m.A33) - csg.write('deform %(name)s %(basename)s %(cx)s %(cy)s %(cz)s\n' % d1) - if m.A14 > 1e-8 or m.A24 > 1e-8 or m.A34 > 1e-8: - csg.write("ttranslate %s %s %s %s\n" % \ - (ob.Name,f2s(m.A14),f2s(m.A24),f2s(m.A34))) - elif isDraftCircle(ob): - if csg is None: - return True # The object is supported - "circle name x y [z [dx dy dz]] [ux uy [uz]] radius" - d1['radius']=ob.Radius.Value - pfirst=f2s(ob.FirstAngle.getValueAs('rad').Value) - plast=f2s(ob.LastAngle.getValueAs('rad').Value) - - #todo ofirst and p last as arguements to mkedge getValueAs('rad').Value - curvename = '%s-curve' % d1['name'] - edgename = '%s-edge' % d1['name'] - wirename = '%s-dwirewire' % d1['name'] - csg.write('circle %s 0 0 0 %s\n' % (curvename,ob.Radius.Value)) - csg.write('mkedge %s %s %s %s\n' % (edgename,curvename,pfirst,plast)) - csg.write('wire %s %s\n' %(wirename,edgename)) - if ob.MakeFace: - csg.write('mkplane %s %s\n' % (d1['name'],wirename)) - else: - csg.write("renamevar %s %s\n"%(wirename,d1['name'])) #the wire is the final object - elif isDraftWire(ob): - if csg is None: - return True # The object is supported - points=ob.Points - if ob.Closed: - points.append(points[0]) - polyname = '%s-dwireline' % d1['name'] - pointstr=' '.join('%s %s %s'%(f2s(v.x),f2s(v.y),f2s(v.z)) \ - for v in points) - csg.write('polyline %s %s\n' % (polyname,pointstr)) - if ob.MakeFace: - wirename = '%s-dwirewire' % d1['name'] - csg.write('wire %s %s\n' %(wirename,polyname)) - facename = d1['name'] - csg.write('mkplane %s %s\n' % (facename,polyname)) - else: - wirename = d1['name'] - csg.write('wire %s %s\n' %(wirename,polyname)) - elif isDraftClone(ob): - if csg is None: - return True # The object is supported - x,y,z=ob.Scale.x - if x == y == z: #uniform scaling - d1['scale']=f2s(x) - else: - d1['cx']=f2s(x) - d1['cy']=f2s(y) - d1['cz']=f2s(z) - if len(ob.Objects) == 1: - d1['basename']=ob.Objects[0].Name - process_object(ob.Objects[0],csg,filename) - if x == y == z: #uniform scaling - csg.write('tcopy %(basename)s %(name)s\n' % d1) - csg.write('pscale %(name)s 0 0 0 %(scale)s\n' % d1) - else: - csg.write('deform %(name)s %(basename)s'\ - ' %(cx)s %(cy)s %(cz)s\n' % d1) - else: #compound - newnames=[] - for i,subobj in enumerate(ob.Objects): - process_object(subobj,csg,filename) - d1['basename']=subobj.Name - newname='%s-%2d' % (ob.Name,i) - d1['newname']=newname - newnames.append(newname) - if x == y == z: #uniform scaling - csg.write('tcopy %(basename)s %(newname)s\n' % d1) - csg.write('pscale %(newname)s 0 0 0 %(scale)s\n' % d1) + if sh.ShapeType == 'Vertex': + pass + elif sh.ShapeType == 'Wire' or sh.ShapeType == 'Edge': + sh = Part.Wire(sh) + elif sh.ShapeType == 'Face': + sh = sh.OuterWire else: - csg.write('deform %(newname)s %(basename)s'\ + raise ValueError('Unrecognized Shape Type') + saveShape(self.csg,self.filename,sh,sectionname,None,\ + self.cleanshape) # placement with shape + self.csg.write('addsweep %s %s\n' % \ + (sectionname," ".join(addoptions))) + self.csg.write('buildsweep %s %s\n' % (ob.Name," ".join(buildoptions))) + + def process_object(self,ob,checksupported=False,toplevel=False): + if not checksupported and ob.Name in self.objectcache: + return # object in present + if not checksupported: + self.objectcache.add(ob.Name) + d1 = {'name':ob.Name} + if hasattr(ob,'Placement'): + hasplacement = not ob.Placement.isNull() + else: + hasplacement = False + if ob.TypeId in ["Part::Cut","Part::Fuse","Part::Common","Part::Section"]: + if checksupported: return True # The object is supported + d1.update({'part':ob.Base.Name,'tool':ob.Tool.Name,\ + 'command':'b%s' % ob.TypeId[6:].lower()}) + self.process_object(ob.Base) + self.process_object(ob.Tool) + self.csg.write("%(command)s %(name)s %(part)s %(tool)s\n"%d1) + elif ob.TypeId == "Part::Sphere" : + if checksupported: return True # The object is supported + d1.update({'radius':f2s(ob.Radius),'angle1':f2s(ob.Angle1),\ + 'angle2':f2s(ob.Angle2),'angle3':f2s(ob.Angle3)}) + self.csg.write('psphere %(name)s %(radius)s %(angle1)s %(angle2)s '\ + '%(angle3)s\n'%d1) + elif ob.TypeId == "Part::Box" : + if checksupported: return True # The object is supported + d1.update({'dx':f2s(ob.Length),'dy':f2s(ob.Width),'dz':f2s(ob.Height)}) + self.csg.write('box %(name)s %(dx)s %(dy)s %(dz)s\n'%d1) + elif ob.TypeId == "Part::Cylinder" : + if checksupported: return True # The object is supported + d1.update({'radius':f2s(ob.Radius),'height':f2s(ob.Height),\ + 'angle':f2s(ob.Angle)}) + self.csg.write('pcylinder %(name)s %(radius)s %(height)s %(angle)s\n'%d1) + elif ob.TypeId == "Part::Cone" : + if checksupported: return True # The object is supported + d1.update({'radius1':f2s(ob.Radius1),'radius2':f2s(ob.Radius2),\ + 'height':f2s(ob.Height),'angle':f2s(ob.Angle)}) + self.csg.write('pcone %(name)s %(radius1)s %(radius2)s %(height)s %(angle)s\n'%d1) + elif ob.TypeId == "Part::Torus" : + if checksupported: return True # The object is supported + d1.update({'radius1':f2s(ob.Radius1),'radius2':f2s(ob.Radius2),\ + 'angle1': f2s(ob.Angle1),'angle2':f2s(ob.Angle2),\ + 'angle3': f2s(ob.Angle3)}) + self.csg.write('ptorus %(name)s %(radius1)s %(radius2)s %(angle1)s '\ + '%(angle2)s %(angle3)s\n' % d1) + elif ob.TypeId == "Part::Mirroring" : + if checksupported: return True # The object is supported + self.process_object(ob.Source) + self.csg.write('tcopy %s %s\n'%(ob.Source.Name,d1['name'])) + b=ob.Base + d1['x']=f2s(ob.Base.x) + d1['y']=f2s(ob.Base.y) + d1['z']=f2s(ob.Base.z) + d1['dx']=f2s(ob.Normal.x) + d1['dy']=f2s(ob.Normal.y) + d1['dz']=f2s(ob.Normal.z) + self.csg.write('tmirror %(name)s %(x)s %(y)s %(z)s %(dx)s %(dy)s %(dz)s\n' \ + % d1) + elif ob.TypeId == 'Part::Compound': + if len(ob.Links) == 0: + pass + elif len(ob.Links) == 1: + if checksupported: + return self.process_object(ob.Links[0],True) + self.process_object(ob.Links[0]) + self.csg.write('tcopy %s %s\n'%(ob.Links[0].Name,d1['name'])) + else: + if checksupported: return True # The object is supported + basenames=[] + for i,subobj in enumerate(ob.Links): + self.process_object(subobj) + basenames.append(subobj.Name) + self.csg.write('compound %s %s\n' % (' '.join(basenames),ob.Name)) + elif ob.TypeId in ["Part::MultiCommon", "Part::MultiFuse"]: + if len(ob.Shapes) == 0: + pass + elif len(ob.Shapes) == 1: + if checksupported: + return self.process_object(ob.Shapes[0],True) + self.process_object(ob.Shapes[0],) + self.csg.write('tcopy %s %s\n'%(ob.Shapes[0].Name,d1['name'])) + else: + if checksupported: return True # The object is supported + topname = ob.Name + command = 'b%s' % ob.TypeId[11:].lower() + lst1=ob.Shapes[:] + current=lst1.pop(0) + curname=current.Name + self.process_object(current) + i=1 + while lst1: + if len(lst1) >= 2: + nxtname='to-%s-%03d-t'%(topname,i) + else: + nxtname=topname + nxt=lst1.pop(0) + self.process_object(nxt) + self.csg.write("%s %s %s %s\n"%(command,nxtname,curname,nxt.Name)) + curname=nxtname + i+=1 + elif (isDraftPolygon(ob) and ob.ChamferSize.Value == 0 and\ + ob.FilletRadius.Value == 0 and ob.Support == None) or\ + ob.TypeId == "Part::Prism" or \ + ob.TypeId == "Part::RegularPolygon": + if checksupported: return True # The object is supported + draftpolygon = isDraftPolygon(ob) + if draftpolygon: + pcount = ob.FacesNumber + if ob.DrawMode =='inscribed': + r=ob.Radius.Value + elif ob.DrawMode =='circumscribed': + import math + r = ob.Radius.Value/math.cos(math.pi/pcount) + else: + raise ValueError + else: + pcount = ob.Polygon + r=ob.Circumradius.Value + justwire = ob.TypeId == "Part::RegularPolygon" or \ + (draftpolygon and ob.MakeFace == False) + polyname = '%s-polyline' % d1['name'] + if justwire: + wirename = d1['name'] + else: + wirename = '%s-polywire' % d1['name'] + if ob.TypeId == "Part::Prism": + facename = '%s-polyface' % d1['name'] + else: + facename = d1['name'] + self.csg.write('polyline %s %s\n' % (polyname,polygonstr(r,pcount))) + self.csg.write('wire %s %s\n' %(wirename,polyname)) + if not justwire: + self.csg.write('mkplane %s %s\n' % (facename,polyname)) + if ob.TypeId == "Part::Prism": + self.csg.write('prism %s %s 0 0 %s\n' % \ + (d1['name'],facename, f2s(ob.Height.Value))) + elif ob.TypeId == "Part::Extrusion" and ob.TaperAngle.Value == 0: + if checksupported: return True # The object is supported + self.process_object(ob.Base) + #Warning does not fully ressemle the functionallity of + #Part::Extrusion + #csg.write('tcopy %s %s\n'%(ob.Base.Name,d1['name'])) + facename=ob.Base.Name + self.csg.write('prism %s %s %s %s %s\n' % (d1['name'],facename,\ + f2s(ob.Dir.x),f2s(ob.Dir.y),f2s(ob.Dir.z))) + elif ob.TypeId == "Part::Fillet" and True: #disabled + if checksupported: return True # The object is supported + self.process_object(ob.Base) + self.csg.write('explode %s E\n' % ob.Base.Name ) + self.csg.write('blend %s %s %s\n' % (d1['name'],ob.Base.Name,\ + ' '.join(('%s %s'%(f2s(e[1]),'%s_%d' % (ob.Base.Name,e[0])) \ + for e in ob.Edges)))) + elif ob.TypeId == "Part::Sweep" and True: + if checksupported: return True # The object is supported + self.saveSweep(ob) + elif ob.TypeId == "Part::Loft": + if checksupported: return True # The object is supported + sectionnames=[] + for i,subobj in enumerate(ob.Sections): + explodeshape = self.alwaysexplode or \ + self.process_object(suboobj,True) + if explodeshape and False: #diabled TBD + try: + raise NotImplementedError + sectionname = '%s-%02d-section' % (ob.Name,i) + sh = subobj.Shape + if sh.isNull(): + raise ValueError # hit the fallback + tempname=spine.Name + if sh.ShapeType == 'Compound': + sh = sh.childShapes()[0] + self.csg.write('explode %s\n' % tempname ) + tempname = '%s_1' % tempname + if sh.ShapeType == 'Face': + #sh = sh.OuterWire #not available + if len(sh.Wires) == 1: + sh=sh.Wires[0] + self.csg.write('explode %s\n W' % tempname ) + tempname = '%s_1' % tempname + else: + raise NotImplementedError + elif sh.ShapeType == 'Edge': + self.csg.write('wire %s %s\n' %(sectionname,tempname)) + tempname = sectionname + sectionname = tempname + except NotImplementedError: + explodeshape = False # fallback + if not explodeshape: # extract only the used subshape + sh = subobj.Shape + if not sh.isNull(): + if sh.ShapeType == 'Compound': + sh = sh.childShapes()[0] + if sh.ShapeType == 'Face': + sh = sh.OuterWire + elif sh.ShapeType == 'Edge': + import Part + sh = Part.Wire([sh]) + elif sh.ShapeType == 'Wire': + import Part + sh = Part.Wire(sh) + elif sh.ShapeType == 'Vertex': + pass + else: + raise ValueError('Unsuitabel Shape Type') + sectionname = '%s-%02d-section' % (ob.Name,i) + saveShape(self.csg,self.filename, sh,sectionname,None,\ + self.cleanshape) # placement with shape + sectionnames.append(sectionname) + if ob.Closed: + sectionnames.append(sectionnames[0]) + self.csg.write('thrusections %s %d %d %s\n' % \ + (ob.Name,int(ob.Solid),\ + int(ob.Ruled), ' '.join(sectionnames))) + elif isDeform(ob): #non-uniform scaling + if checksupported: return True # The object is supported + m=ob.Matrix + self.process_object(ob.Base) + #csg.write('tcopy %s %s\n'%(ob.Base.Name,d1['name'])) + d1['basename']=ob.Base.Name + d1['cx']=f2s(m.A11) + d1['cy']=f2s(m.A22) + d1['cz']=f2s(m.A33) + self.csg.write('deform %(name)s %(basename)s %(cx)s %(cy)s %(cz)s\n' % d1) + if m.A14 > 1e-8 or m.A24 > 1e-8 or m.A34 > 1e-8: + self.csg.write("ttranslate %s %s %s %s\n" % \ + (ob.Name,f2s(m.A14),f2s(m.A24),f2s(m.A34))) + elif isDraftPoint(ob) or ob.TypeId == "Part::Vertex": + if checksupported: return True # The object is supported + d1['x']=f2s(ob.X) + d1['y']=f2s(ob.Y) + d1['z']=f2s(ob.Z) + self.csg.write('vertex %(name)s %(x)s %(y)s %(z)s\n' % d1) + + elif isDraftCircle(ob) or ob.TypeId == "Part::Circle": + if checksupported: return True # The object is supported + "circle name x y [z [dx dy dz]] [ux uy [uz]] radius" + curvename = '%s-curve' % d1['name'] + if ob.TypeId == "Part::Circle": + radius=f2s(float(ob.Radius)) + pfirst=f2s(ob.Angle0.getValueAs('rad').Value) + plast=f2s(ob.Angle1.getValueAs('rad').Value) + self.csg.write('circle %s 0 0 0 %s\n' % (curvename,radius)) + self.csg.write('mkedge %s %s %s %s\n' % \ + (d1['name'],curvename,pfirst,plast)) + else: + radius=f2s(ob.Radius.Value) + pfirst=f2s(ob.FirstAngle.getValueAs('rad').Value) + plast=f2s(ob.LastAngle.getValueAs('rad').Value) + makeface = ob.MakeFace and \ + (ob.Shape.isNull() or ob.Shape.ShapeType == 'Face') + #FreeCAD ignore a failed mkplane but it may + #brake the model in DRAWEXE + + edgename = '%s-edge' % d1['name'] + self.csg.write('circle %s 0 0 0 %s\n' % (curvename,radius)) + self.csg.write('mkedge %s %s %s %s\n' % \ + (edgename,curvename,pfirst,plast)) + if makeface: + wirename = '%s-wire' % d1['name'] + self.csg.write('wire %s %s\n' %(wirename,edgename)) + self.csg.write('mkplane %s %s\n' % (d1['name'],wirename)) + else: + self.csg.write('wire %s %s\n' %(d1['name'],edgename)) + + elif ob.TypeId == "Part::Line": + if checksupported: return True # The object is supported + self.csg.write('polyline %s %s %s %s %s %s %s\n' % \ + (d1['name'],f2s(ob.X1),f2s(ob.Y1),f2s(ob.Z1),\ + f2s(ob.X2),f2s(ob.Y2),f2s(ob.Z2))) + elif isDraftWire(ob): + if checksupported: return True # The object is supported + points=ob.Points + if ob.Closed: + points.append(points[0]) + polyname = '%s-dwireline' % d1['name'] + pointstr=' '.join('%s %s %s'%(f2s(v.x),f2s(v.y),f2s(v.z)) \ + for v in points) + self.csg.write('polyline %s %s\n' % (polyname,pointstr)) + if ob.MakeFace: + wirename = '%s-dwirewire' % d1['name'] + self.csg.write('wire %s %s\n' %(wirename,polyname)) + facename = d1['name'] + self.csg.write('mkplane %s %s\n' % (facename,polyname)) + else: + wirename = d1['name'] + self.csg.write('wire %s %s\n' %(wirename,polyname)) + elif isDraftClone(ob): + if checksupported: return True # The object is supported + x,y,z=ob.Scale.x + if x == y == z: #uniform scaling + d1['scale']=f2s(x) + else: + d1['cx']=f2s(x) + d1['cy']=f2s(y) + d1['cz']=f2s(z) + if len(ob.Objects) == 1: + d1['basename']=ob.Objects[0].Name + self.process_object(ob.Objects[0]) + if x == y == z: #uniform scaling + self.csg.write('tcopy %(basename)s %(name)s\n' % d1) + self.csg.write('pscale %(name)s 0 0 0 %(scale)s\n' % d1) + else: + self.csg.write('deform %(name)s %(basename)s'\ ' %(cx)s %(cy)s %(cz)s\n' % d1) - csg.write('compound %s %s\n' % (' '.join(newnames),ob.Name)) - - #elif ob.isDerivedFrom('Part::FeaturePython') and \ - # hasattr(ob.Proxy,'__module__'): - # pass - elif ob.isDerivedFrom('Part::Feature') : - if ob.Shape.isNull(): #would crash in exportBrep otherwise - raise ValueError('Shape of %s is Null' % ob.Name) - if csg is None: - return False # The object is not supported - hasplacement = saveShape(csg,filename,ob.Shape,ob.Name,hasplacement) - if hasplacement: - csg.write(placement2draw(ob.Placement,ob.Name)) + else: #compound + newnames=[] + for i,subobj in enumerate(ob.Objects): + self.process_object(subobj) + d1['basename']=subobj.Name + newname='%s-%2d' % (ob.Name,i) + d1['newname']=newname + newnames.append(newname) + if x == y == z: #uniform scaling + self.csg.write('tcopy %(basename)s %(newname)s\n' % d1) + self.csg.write('pscale %(newname)s 0 0 0 %(scale)s\n' % d1) + else: + self.csg.write('deform %(newname)s %(basename)s'\ + ' %(cx)s %(cy)s %(cz)s\n' % d1) + self.csg.write('compound %s %s\n' % (' '.join(newnames),ob.Name)) + + #elif ob.isDerivedFrom('Part::FeaturePython') and \ + # hasattr(ob.Proxy,'__module__'): + # pass + elif ob.isDerivedFrom('Part::Feature') : + if ob.Shape.isNull(): #would crash in exportBrep otherwise + raise ValueError('Shape of %s is Null' % ob.Name) + if checksupported: return False # The object is not supported + self.csg.write('#saved shape of unsupported %s Object\n' % \ + formatobjtype(ob)) + hasplacement = saveShape(self.csg,self.filename,ob.Shape,ob.Name,\ + hasplacement,self.cleanshape) + else: # not derived from Part::Feature + if not toplevel: + raise ValueError('Can not export child object') + else: + if ob.Name != ob.Label: + labelstr = 'Label %s' % ob.Label.encode('unicode-escape') + else: + labelstr = '' + self.csg.write('#omitted unsupported %s Object %s%s\n' %\ + (formatobjtype(ob),ob.Name,labelstr)) + self.csg.write('#Properties: %s\n' % \ + ','.join(ob.PropertiesList)) + return False + #The object is not present and can not be referenced + if hasplacement: + self.csg.write(placement2draw(ob.Placement,ob.Name)) + if ob.Name != ob.Label: + self.csg.write('#Object Label: %s\n' % ob.Label.encode('unicode-escape')) + return ob.Name #The object is present and can be referenced + + def export_objects(self,objlst,toplevel=True): + self.write_header() + toplevelobjs = [self.process_object(ob, toplevel=toplevel)\ + for ob in objlst] + names = [name for name in toplevelobjs if name is not False] + self.csg.write('donly %s\n'%(' '.join(names))) + #for ob in objlst: + # self.process_object(ob,toplevel=toplevel) + #self.write_displayonly(objlst) + + def __exit__(self,exc_type, exc_val, exc_tb ): + self.csg.close() def export(exportList,filename): "called when freecad exports a file" - # process Objects - csg = pythonopen(filename,'w') - import FreeCAD - csg.write('#generated by FreeCAD %s\n' % '.'.join(FreeCAD.Version()[0:3])) - csg.write('pload ALL\n') - for ob in exportList: - process_object(ob,csg,filename) - csg.write('donly %s\n'%' '.join([obj.Name for obj in exportList])) - csg.close() + with Drawexporter(filename) as exporter: + exporter.export_objects(exportList)