FreeCAD

Форк
0
/
exportDRAWEXE.py 
852 строки · 38.3 Кб
1

2
#***************************************************************************
3
#*                                                                         *
4
#*   Copyright (c) 2014 Sebastian Hoogen <github@sebastianhoogen.de>       *
5
#*                                                                         *
6
#*   This program is free software; you can redistribute it and/or modify  *
7
#*   it under the terms of the GNU Lesser General Public License (LGPL)    *
8
#*   as published by the Free Software Foundation; either version 2 of     *
9
#*   the License, or (at your option) any later version.                   *
10
#*   for detail see the LICENCE text file.                                 *
11
#*                                                                         *
12
#*   This program is distributed in the hope that it will be useful,       *
13
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
14
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
15
#*   GNU Library General Public License for more details.                  *
16
#*                                                                         *
17
#*   You should have received a copy of the GNU Library General Public     *
18
#*   License along with this program; if not, write to the Free Software   *
19
#*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
20
#*   USA                                                                   *
21
#*                                                                         *
22
#***************************************************************************
23
__title__="FreeCAD OpenSCAD Workbench - DRAWEXE exporter"
24
__author__ = "Sebastian Hoogen <github@sebastianhoogen.de>"
25

26
import FreeCAD, Part
27
from builtins import open as pyopen
28

29

30

31
# unsupported primitives
32
# Part:: Wedge, Helix, Spiral, Elipsoid
33
# Draft: Rectangle, BSpline, BezCurve
34

35
def quaternionToString(rot):
36
    def shorthexfloat(f):
37
        s=f.hex()
38
        mantisse, exponent = f.hex().split('p',1)
39
        return '%sp%s' % (mantisse.rstrip('0'),exponent)
40
    x,y,z,w=rot.Q
41
    return 'q=%s+%s*i+%s*j+%s*k' % (shorthexfloat(w),shorthexfloat(x),
42
            shorthexfloat(y),shorthexfloat(z))
43

44
def f2s(n,angle=False,axis=False):
45
    '''convert to numerical value to string
46
    try to remove no significant digits, by guessing a former rounding
47
    '''
48
    if abs(n) < 1e-14: return '0'
49
    if angle and len(('%0.6e' % n).split('e')[0].rstrip('0') ) < 3:
50
        return ('%0.5f' % n).rstrip('0').rstrip('.')
51
    elif axis and len(('%0.13e' % n).split('e')[0].rstrip('0') ) < 6:
52
        return ('%0.10f' % n).rstrip('0').rstrip('.')
53
    else:
54
        for i in range(20):
55
            s = ('%%1.%df'% i) % n
56
            if float(s) == n:
57
                return s
58
        for i in range(20):
59
            s = ('%%0.%de'% i) % n
60
            if float(s) == n:
61
                return s
62

63
def ax2_xdir(normal):
64
    #adapted from gp_Ax2.ccc (c) OpenCascade SAS LGPL 2.1+
65

66
    xa=abs(normal.x)
67
    ya=abs(normal.y)
68
    za=abs(normal.z)
69
    if ya <= xa and ya <= za:
70
        if xa > za:
71
            return FreeCAD.Vector(-normal.z,0, normal.x)
72
        else:
73
            return FreeCAD.Vector( normal.z,0,-normal.x)
74
    elif xa <= ya and xa <= za:
75
        if ya > za:
76
            return FreeCAD.Vector(0,-normal.z, normal.y)
77
        else:
78
            return FreeCAD.Vector(0, normal.z,-normal.y)
79
    else:
80
        if xa > ya:
81
            return FreeCAD.Vector(-normal.y, normal.x,0)
82
        else:
83
            return FreeCAD.Vector( normal.y,-normal.x,0)
84

85
def occversiontuple():
86
    import FreeCAD,Part
87
    occmajs,occmins,occfixs = FreeCAD.ConfigGet('OCC_VERSION').split('.')[:3]
88
    return (int(occmajs),int(occmins),int(occfixs))
89

90
def polygonstr(r,pcount):
91
    import math
92
    v=FreeCAD.Vector(r,0,0)
93
    m=FreeCAD.Matrix()
94
    m.rotateZ(2*math.pi/pcount)
95
    points=[]
96
    for i in range(pcount):
97
        points.append(v)
98
        v=m.multiply(v)
99
    points.append(v)
100
    return ' '.join('%s %s %s'%(f2s(v.x),f2s(v.y),f2s(v.z)) \
101
            for v in points)
102

103
def formatobjtype(ob):
104
    objtype=ob.TypeId
105
    if (ob.isDerivedFrom('Part::FeaturePython') or \
106
            ob.isDerivedFrom('Part::Part2DObjectPython') or\
107
            ob.isDerivedFrom('App::FeaturePython')) and \
108
            hasattr(ob.Proxy,'__module__'):
109
        return '%s::%s.%s' % (ob.TypeId,ob.Proxy.__module__,\
110
                    ob.Proxy.__class__.__name__)
111
    else:
112
        return ob.TypeId
113

114
def placement2draw(placement,name='object'):
115
    """converts a FreeCAD Placement to trotate and ttranslate commands"""
116
    drawcommand=''
117
    if not placement.Rotation.isNull():
118
        import math
119
        #dx,dy,dz=placement.Rotation.Axis
120
        ax=placement.Rotation.Axis
121
        import itertools
122
        # denormalize rotation axis
123
        for t in itertools.product((0,1,-1),repeat=3):
124
            if t != (0,0,0):
125
                if (ax-FreeCAD.Vector(*t).normalize()).Length < 1e-15:
126
                    dx,dy,dz = t
127
                    break
128
        else:
129
            dx,dy,dz=placement.Rotation.Axis
130
            #drawcommand += "# %s\n" %quaternionToString(placement.Rotation)
131
        an=math.degrees(placement.Rotation.Angle)
132
        drawcommand += "trotate %s 0 0 0 %s %s %s %s\n" % (name,\
133
            f2s(dx),f2s(dy),f2s(dz),\
134
#            f2s(dx,axis=True),f2s(dy,axis=True),f2s(dz,axis=True),\
135
            f2s(an,angle=True))
136
    if placement.Base.Length > 1e-8:
137
        x,y,z=placement.Base
138
        drawcommand += "ttranslate %s %s %s %s\n" % \
139
            (name,f2s(x),f2s(y),f2s(z))
140
    return drawcommand
141

142
def saveShape(csg,filename,shape,name,hasplacement = True,cleanshape=False):
143
    import os
144
    spath,sname = os.path.split(filename)
145
    sname=sname.replace('.','-')
146
    uname='%s-%s' %(sname,name)
147
    breppath=os.path.join(spath,'%s.brep'%uname)
148
    csg.write("restore %s.brep %s\n" % (uname,name))
149
    if cleanshape:
150
        import Part
151
        try:
152
            shape = shape.cleaned()
153
        except Part.OCCError:
154
            shape = shape.copy()
155
    if hasplacement is None:  # saved with placement
156
        hasplacement = False # saved with placement
157
        shape.exportBrep(breppath)
158
    elif not hasplacement: #doesn't matter
159
        shape.exportBrep(breppath)
160
    else: #remove placement
161
        sh=shape.copy()
162
        sh.Placement=FreeCAD.Placement()
163
        # it not yet tested if changing the placement recreated the
164
        # tessellation. But for now we simply do the cleaning once again
165
        # to stay on the safe side
166
        if cleanshape:
167
            shape = shape.cleaned()
168
        sh.exportBrep(breppath)
169
    return hasplacement
170

171

172

173
def isDraftFeature(ob):
174
    if (ob.isDerivedFrom('Part::FeaturePython') or \
175
            ob.isDerivedFrom('Part::Part2DObjectPython')) and \
176
            hasattr(ob.Proxy,'__module__') and \
177
            ob.Proxy.__module__ == 'Draft':
178
        return True
179

180
def isDraftClone(ob):
181
    if (ob.isDerivedFrom('Part::FeaturePython') or \
182
            ob.isDerivedFrom('Part::Part2DObjectPython')) and \
183
            hasattr(ob.Proxy,'__module__') and \
184
            ob.Proxy.__module__ == 'Draft':
185
        import Draft
186
        return isinstance(ob.Proxy,Draft._Clone)
187

188
def isDraftCircle(ob):
189
    if isDraftFeature(ob):
190
        import Draft
191
        return isinstance(ob.Proxy,Draft._Circle)
192

193
def isDraftEllipse(ob):
194
    if isDraftFeature(ob):
195
        import Draft
196
        return isinstance(ob.Proxy,Draft._Ellipse)
197

198
def isDraftPolygon(ob):
199
    if isDraftFeature(ob):
200
        import Draft
201
        return isinstance(ob.Proxy,Draft._Polygon)
202

203
def isDraftPoint(ob):
204
    if isDraftFeature(ob):
205
        import Draft
206
        return isinstance(ob.Proxy,Draft._Point)
207

208
def isDraftWire(ob):
209
    if isDraftFeature(ob):
210
        import Draft
211
        if isinstance(ob.Proxy,Draft._Wire):
212
            #only return true if we support all options
213
            #"Closed" append last point at the end
214
            #"MakeFace"
215
            #"Points" data we need
216
            # the usage of 'start' and 'end' is not clear
217
            if ob.Base is None and ob.Tool is None and \
218
                    ob.FilletRadius.Value == 0.0 and \
219
                    ob.ChamferSize.Value == 0.0:
220
                return True
221

222
def isDraftShape2DView(ob):
223
    if isDraftFeature(ob):
224
        import Draft
225
        return isinstance(ob.Proxy,Draft._Shape2DView)
226

227
def isOpenSCADFeature(ob):
228
    if ob.isDerivedFrom('Part::FeaturePython') and \
229
            hasattr(ob.Proxy,'__module__') and \
230
            ob.Proxy.__module__ == 'OpenSCADFeatures':
231
        return True
232

233
def isOpenSCADMultMatrixFeature(ob):
234
    if ob.isDerivedFrom('Part::FeaturePython') and \
235
            hasattr(ob.Proxy,'__module__') and \
236
            ob.Proxy.__module__ == 'OpenSCADFeatures':
237
        import OpenSCADFeatures
238
        return isinstance(ob.Proxy,OpenSCADFeatures.MatrixTransform)
239

240
def isDeform(ob):
241
    """tests whether the object is a Matrix transformation
242
    that does a non-uniform scaling"""
243
    # the [ is important to exclude cases with additional
244
    # rotation or mirroring.
245
    # TBD decompose complex matrix operations
246
    return isOpenSCADMultMatrixFeature(ob) and \
247
            ob.Matrix.analyze().startswith('Scale [')
248

249

250

251

252
class Drawexporter(object):
253
    def __init__(self, filename):
254
        self.objectcache=set()
255
        self.csg = pyopen(filename,'w')
256
        #self.csg=csg
257
        self.filename=filename
258
        #settings
259
        self.alwaysexplode = True
260
        self.cleanshape = False
261

262
    def __enter__(self):
263
        return self
264

265
    def write_header(self):
266
        import FreeCAD
267
        #self.csg.write('#!/usr/bin/env DRAWEXE\n')
268
        self.csg.write('#generated by FreeCAD %s\n' % \
269
                '.'.join(FreeCAD.Version()[0:3]))
270
        self.csg.write('pload MODELING\n')
271

272
    def write_displayonly(self,objlst):
273
        self.csg.write('donly %s\n'%' '.join([obj.Name for obj in objlst]))
274

275
    def saveSweep(self,ob):
276
        import Part
277
        spine,subshapelst=ob.Spine
278
        #process_object(csg,spine,filename)
279
        explodeshape = self.alwaysexplode or self.process_object(spine,True)
280
        if explodeshape:
281
            self.process_object(spine)
282
            if len(subshapelst) and spine.Shape.ShapeType != 'Edge':
283
                #raise NotImplementedError # hit the fallback
284
                # currently all subshapes are edges
285
                self.csg.write('explode %s E\n' % spine.Name )
286
                edgelst = ' '.join(('%s_%s' % (spine.Name,ss[4:]) for ss \
287
                        in subshapelst))
288
                spinename = '%s-0-spine' % ob.Name
289
                self.csg.write('wire %s %s\n' %(spinename,edgelst))
290
            elif spine.Shape.ShapeType == 'Wire':
291
                spinename = spine.Name
292
            elif spine.Shape.ShapeType == 'Edge':
293
                spinename = '%s-0-spine' % ob.Name
294
                self.csg.write('wire %s %s\n' %(spinename,spine.Name))
295
        else: # extract only the used subshape
296
            if len(subshapelst):
297
                path=Part.Wire([spine.Shape.getElement(subshapename) for \
298
                        subshapename in subshapelst])
299
            elif spine.Shape.ShapeType == 'Edge':
300
                path = spine.Shape
301
            elif spine.Shape.ShapeType == 'Wire':
302
                path = Part.Wire(spine.Shape)
303
            else:
304
                raise ValueError('Unsuitabel Shape Type')
305
            spinename = '%s-0-spine' % ob.Name
306
            saveShape(self.csg,self.filename, path,spinename,None,\
307
                    self.cleanshape) # placement with shape
308
        #safePlacement(ob.Placement,ob.Name)
309
        self.csg.write('mksweep %s\n' % spinename)
310
        #setsweep
311
        setoptions=[]
312
        buildoptions=[]
313
        if ob.Frenet:
314
            setoptions.append('-FR')
315
        else:
316
            setoptions.append('-CF')
317
        if ob.Transition == 'Transformed':
318
            buildoptions.append('-M')
319
        elif ob.Transition == 'Right corner':
320
            buildoptions.append('-C')
321
        elif ob.Transition == 'Round corner':
322
            buildoptions.append('-R')
323
        if ob.Solid:
324
            buildoptions.append('-S')
325
        self.csg.write('setsweep %s\n' % (" ".join(setoptions)))
326
        #addsweep
327
        sections=ob.Sections
328
        sectionnames = []
329
        for i,subobj in enumerate(ob.Sections):
330
            #process_object(csg,subobj,filename)
331
            #sectionsnames.append(subobj.Name)
332
            #d1['basename']=subobj.Name
333
            sectionname = '%s-0-section-%02d-%s' % (ob.Name,i,subobj.Name)
334
            addoptions=[]
335
            explodeshape = self.alwaysexplode or \
336
                    self.process_object(subobj,True)
337
            if explodeshape:
338
                sh = subobj.Shape
339
                if sh.ShapeType == 'Vertex' or sh.ShapeType == 'Wire' or \
340
                        sh.ShapeType == 'Edge' or \
341
                        sh.ShapeType == 'Face' and len(sh.Wires) == 1:
342
                    self.process_object(subobj)
343
                    if sh.ShapeType == 'Wire' or sh.ShapeType == 'Vertex':
344
                        #csg.write('tcopy %s %s\n' %(subobj.Name,sectionname))
345
                        sectionname = subobj.Name
346
                    if sh.ShapeType == 'Edge':
347
                        self.csg.write('explode %s E\n' % subobj.Name )
348
                        self.csg.write('wire %s %s_1\n' %(sectionname,subobj.Name))
349
                    if sh.ShapeType == 'Face':
350
                        #we should use outer wire when it becomes available
351
                        self.csg.write('explode %s W\n' % subobj.Name )
352
                        #csg.write('tcopy %s_1 %s\n' %(subobj.Name,sectionname))
353
                        sectionname ='%s_1' % subobj.Name
354
                else:
355
                    explodeshape = False
356
            if not explodeshape: # extract only the used subshape
357
                sh = subobj.Shape
358
                if sh.ShapeType == 'Vertex':
359
                    pass
360
                elif sh.ShapeType == 'Wire' or sh.ShapeType == 'Edge':
361
                    sh = Part.Wire(sh)
362
                elif sh.ShapeType == 'Face':
363
                    sh = sh.OuterWire
364
                else:
365
                    raise ValueError('Unrecognized Shape Type')
366
                saveShape(self.csg,self.filename,sh,sectionname,None,\
367
                        self.cleanshape) # placement with shape
368
            self.csg.write('addsweep %s %s\n' % \
369
                    (sectionname," ".join(addoptions)))
370
        self.csg.write('buildsweep %s %s\n' % (ob.Name," ".join(buildoptions)))
371

372
    def process_object(self,ob,checksupported=False,toplevel=False):
373
        if not checksupported and ob.Name in self.objectcache:
374
            return # object in present
375
        if not checksupported:
376
            self.objectcache.add(ob.Name)
377
        d1 = {'name':ob.Name}
378
        if hasattr(ob,'Placement'):
379
            hasplacement = not ob.Placement.isNull()
380
        else:
381
            hasplacement = False
382
        if ob.TypeId in ["Part::Cut","Part::Fuse","Part::Common",\
383
                "Part::Section"]:
384
            if checksupported: return True # The object is supported
385
            d1.update({'part':ob.Base.Name,'tool':ob.Tool.Name,\
386
                'command':'b%s' % ob.TypeId[6:].lower()})
387
            self.process_object(ob.Base)
388
            self.process_object(ob.Tool)
389
            self.csg.write("%(command)s %(name)s %(part)s %(tool)s\n"%d1)
390
        elif ob.TypeId == "Part::Plane" :
391
            if checksupported: return True # The object is supported
392
            d1.update({'uname':'%s-untrimmed' % d1['name'],\
393
                    'length': f2s(ob.Length),'width': f2s(ob.Width)})
394
            self.csg.write("plane %s 0 0 0\n"%d1['uname'])
395
            self.csg.write(\
396
                    "mkface %(name)s %(uname)s 0 %(length)s 0 %(width)s\n"%d1)
397
        elif ob.TypeId == "Part::Ellipse" :
398
            if checksupported: return True # The object is supported
399
            d1.update({'uname':'%s-untrimmed'%d1['name'], 'maj':\
400
                    f2s(ob.MajorRadius), 'min': f2s(ob.MinorRadius),\
401
                    'pf':f2s(ob.Angle0.getValueAs('rad').Value), \
402
                    'pl':f2s(ob.Angle1.getValueAs('rad').Value)})
403
            self.csg.write("ellipse %(uname)s 0 0 0 %(maj)s %(min)s\n"%d1)
404
            self.csg.write('mkedge %(name)s %(uname)s %(pf)s %(pl)s\n' % d1)
405
        elif ob.TypeId == "Part::Sphere" :
406
            if checksupported: return True # The object is supported
407
            d1.update({'radius':f2s(ob.Radius),'angle1':f2s(ob.Angle1),\
408
                'angle2':f2s(ob.Angle2),'angle3':f2s(ob.Angle3)})
409
            if ob.Angle1.Value == -90 and ob.Angle2.Value == 90 and \
410
                    ob.Angle3.Value == 360:
411
                self.csg.write('psphere %(name)s %(radius)s\n'%d1)
412
            else:
413
                self.csg.write('psphere %(name)s %(radius)s %(angle1)s '
414
                        '%(angle2)s %(angle3)s\n'%d1)
415
        elif ob.TypeId == "Part::Box" :
416
            if checksupported: return True # The object is supported
417
            d1.update({'dx':f2s(ob.Length),'dy':f2s(ob.Width),'dz':f2s(ob.Height)})
418
            self.csg.write('box %(name)s %(dx)s %(dy)s %(dz)s\n'%d1)
419
        elif ob.TypeId == "Part::Cylinder" :
420
            if checksupported: return True # The object is supported
421
            d1.update({'radius':f2s(ob.Radius),'height':f2s(ob.Height),\
422
                'angle':f2s(ob.Angle)})
423
            if ob.Angle.Value == 360:
424
                self.csg.write('pcylinder %(name)s %(radius)s %(height)s\n'%d1)
425
            else:
426
                self.csg.write('pcylinder %(name)s %(radius)s %(height)s '\
427
                        '%(angle)s\n'%d1)
428
        elif ob.TypeId == "Part::Cone" :
429
            if checksupported: return True # The object is supported
430
            d1.update({'radius1':f2s(ob.Radius1),'radius2':f2s(ob.Radius2),\
431
                    'height':f2s(ob.Height),'angle':f2s(ob.Angle)})
432
            if ob.Angle.Value == 360:
433
                self.csg.write('pcone %(name)s %(radius1)s %(radius2)s '\
434
                        '%(height)s\n'%d1)
435
            else:
436
                self.csg.write('pcone %(name)s %(radius1)s %(radius2)s '\
437
                        '%(height)s %(angle)s\n'%d1)
438
        elif ob.TypeId == "Part::Torus" :
439
            if checksupported: return True # The object is supported
440
            d1.update({'radius1':f2s(ob.Radius1),'radius2':f2s(ob.Radius2),\
441
                'angle1': f2s(ob.Angle1),'angle2':f2s(ob.Angle2),\
442
                'angle3': f2s(ob.Angle3)})
443
            if ob.Angle1.Value == -180 and ob.Angle2.Value == 180 and \
444
                    ob.Angle3.Value == 360:
445
                self.csg.write('ptorus %(name)s %(radius1)s %(radius2)s\n'%d1)
446
            else:
447
                self.csg.write('ptorus %(name)s %(radius1)s %(radius2)s '\
448
                        '%(angle1)s %(angle2)s %(angle3)s\n' % d1)
449
        elif ob.TypeId == "Part::Mirroring" :
450
            if checksupported: return True # The object is supported
451
            self.process_object(ob.Source)
452
            self.csg.write('tcopy %s %s\n'%(ob.Source.Name,d1['name']))
453
            b=ob.Base
454
            d1['x']=f2s(ob.Base.x)
455
            d1['y']=f2s(ob.Base.y)
456
            d1['z']=f2s(ob.Base.z)
457
            d1['dx']=f2s(ob.Normal.x)
458
            d1['dy']=f2s(ob.Normal.y)
459
            d1['dz']=f2s(ob.Normal.z)
460
            self.csg.write('tmirror %(name)s %(x)s %(y)s %(z)s %(dx)s %(dy)s %(dz)s\n' \
461
                    % d1)
462
        elif ob.TypeId == 'Part::Compound':
463
            if len(ob.Links) == 0:
464
                pass
465
            elif len(ob.Links) == 1:
466
                if checksupported:
467
                    return self.process_object(ob.Links[0],True)
468
                self.process_object(ob.Links[0])
469
                self.csg.write('tcopy %s %s\n'%(ob.Links[0].Name,d1['name']))
470
            else:
471
                if checksupported: return True # The object is supported
472
                basenames=[]
473
                for i,subobj in enumerate(ob.Links):
474
                    self.process_object(subobj)
475
                    basenames.append(subobj.Name)
476
                self.csg.write('compound %s %s\n' % (' '.join(basenames),ob.Name))
477
        elif ob.TypeId in ["Part::MultiCommon", "Part::MultiFuse"]:
478
            if len(ob.Shapes) == 0:
479
                pass
480
            elif len(ob.Shapes) == 1:
481
                if checksupported:
482
                    return self.process_object(ob.Shapes[0],True)
483
                self.process_object(ob.Shapes[0],)
484
                self.csg.write('tcopy %s %s\n'%(ob.Shapes[0].Name,d1['name']))
485
            elif ob.TypeId == "Part::MultiFuse" and \
486
                    occversiontuple() >= (6,8,1):
487
                if checksupported: return True # The object is supported
488
                for subobj in ob.Shapes:
489
                    self.process_object(subobj)
490
                self.csg.write("bclearobjects\nbcleartools\n")
491
                self.csg.write("baddobjects %s\n" % ob.Shapes[0].Name)
492
                self.csg.write("baddtools %s\n" % " ".join(subobj.Name for \
493
                        subobj in ob.Shapes[1:]))
494
                self.csg.write("bfillds\n")
495
                self.csg.write("bbop %s 1\n" % ob.Name) #BOPAlgo_FUSE == 1
496
            else:
497
                if checksupported: return True # The object is supported
498
                topname = ob.Name
499
                command = 'b%s' % ob.TypeId[11:].lower()
500
                lst1=ob.Shapes[:]
501
                current=lst1.pop(0)
502
                curname=current.Name
503
                self.process_object(current)
504
                i=1
505
                while lst1:
506
                    if len(lst1) >= 2:
507
                        nxtname='to-%s-%03d-t'%(topname,i)
508
                    else:
509
                        nxtname=topname
510
                    nxt=lst1.pop(0)
511
                    self.process_object(nxt)
512
                    self.csg.write("%s %s %s %s\n"%(command,nxtname,curname,nxt.Name))
513
                    curname=nxtname
514
                    i+=1
515
        elif (isDraftPolygon(ob) and ob.ChamferSize.Value == 0 and\
516
                ob.FilletRadius.Value == 0 and ob.AttachmentSupport is None) or\
517
                ob.TypeId == "Part::Prism" or \
518
                ob.TypeId == "Part::RegularPolygon":
519
            if checksupported: return True # The object is supported
520
            draftpolygon = isDraftPolygon(ob)
521
            if draftpolygon:
522
                pcount = ob.FacesNumber
523
                if ob.DrawMode =='inscribed':
524
                    r=ob.Radius.Value
525
                elif ob.DrawMode =='circumscribed':
526
                    import math
527
                    r =  ob.Radius.Value/math.cos(math.pi/pcount)
528
                else:
529
                    raise ValueError
530
            else:
531
                pcount = ob.Polygon
532
                r=ob.Circumradius.Value
533
            justwire = ob.TypeId == "Part::RegularPolygon" or \
534
                    (draftpolygon and ob.MakeFace == False)
535
            polyname = '%s-polyline' % d1['name']
536
            if justwire:
537
                wirename = d1['name']
538
            else:
539
                wirename = '%s-polywire' % d1['name']
540
                if ob.TypeId == "Part::Prism":
541
                    facename = '%s-polyface' % d1['name']
542
                else:
543
                    facename = d1['name']
544
            self.csg.write('polyline %s %s\n' % (polyname,polygonstr(r,pcount)))
545
            self.csg.write('wire %s %s\n' %(wirename,polyname))
546
            if not justwire:
547
                self.csg.write('mkplane %s %s\n' % (facename,polyname))
548
                if ob.TypeId == "Part::Prism":
549
                    self.csg.write('prism %s %s 0 0 %s\n' % \
550
                            (d1['name'],facename, f2s(ob.Height.Value)))
551
        elif ob.TypeId == "Part::Extrusion" and ob.TaperAngle.Value == 0:
552
            if checksupported: return True # The object is supported
553
            self.process_object(ob.Base)
554
            #Warning does not fully resemble the functionality of
555
            #Part::Extrusion
556
            #csg.write('tcopy %s %s\n'%(ob.Base.Name,d1['name']))
557
            facename=ob.Base.Name
558
            self.csg.write('prism %s %s %s %s %s\n' % (d1['name'],facename,\
559
                f2s(ob.Dir.x),f2s(ob.Dir.y),f2s(ob.Dir.z)))
560
        elif ob.TypeId == "Part::Fillet" and True: #disabled
561
            if checksupported: return True # The object is supported
562
            self.process_object(ob.Base)
563
            self.csg.write('explode %s E\n' % ob.Base.Name )
564
            self.csg.write('blend %s %s %s\n' % (d1['name'],ob.Base.Name,\
565
                ' '.join(('%s %s'%(f2s(e[1]),'%s_%d' % (ob.Base.Name,e[0])) \
566
                for e in ob.Edges))))
567
        elif ob.TypeId == "Part::Thickness" and not ob.SelfIntersection and \
568
                ob.Mode == 'Skin':
569
            if checksupported: return True # The object is supported
570
            jointype = {'Arc':'a','Intersection':'i','Tangent':'t'} #Join
571
            inter = {False: 'p', True: 'c'} #Intersection
572
            baseobj, facelist = ob.Faces
573
            self.process_object(baseobj)
574
            faces = ' '.join([('%s_%s' %(baseobj.Name,f[4:])) \
575
                for f in facelist])
576
            value = f2s(ob.Value)
577
            self.csg.write('explode %s F\n' % baseobj.Name )
578
            self.csg.write('offsetparameter 1e-7 %s %s\n' % \
579
                    (inter[ob.Intersection],jointype[ob.Join]))
580
            self.csg.write('offsetload %s %s %s\n'%(baseobj.Name,value,faces))
581
            self.csg.write('offsetperform %s\n' % d1['name'] )
582

583
        elif ob.TypeId == "Part::Sweep" and True:
584
            if checksupported: return True # The object is supported
585
            self.saveSweep(ob)
586
        elif ob.TypeId == "Part::Loft":
587
            if checksupported: return True # The object is supported
588
            sectionnames=[]
589
            for i,subobj in enumerate(ob.Sections):
590
                explodeshape = self.alwaysexplode or \
591
                        self.process_object(suboobj,True)
592
                if explodeshape and False: #disabled TBD
593
                    try:
594
                        raise NotImplementedError
595
                        sectionname = '%s-%02d-section' % (ob.Name,i)
596
                        sh = subobj.Shape
597
                        if sh.isNull():
598
                            raise ValueError # hit the fallback
599
                        tempname=spine.Name
600
                        if sh.ShapeType == 'Compound':
601
                            sh = sh.childShapes()[0]
602
                            self.csg.write('explode %s\n' % tempname )
603
                            tempname = '%s_1' % tempname
604
                        if sh.ShapeType == 'Face':
605
                            #sh = sh.OuterWire #not available
606
                            if len(sh.Wires) == 1:
607
                                sh=sh.Wires[0]
608
                                self.csg.write('explode %s\n W' % tempname )
609
                                tempname = '%s_1' % tempname
610
                            else:
611
                                raise NotImplementedError
612
                        elif sh.ShapeType == 'Edge':
613
                            self.csg.write('wire %s %s\n' %(sectionname,tempname))
614
                            tempname = sectionname
615
                        sectionname = tempname
616
                    except NotImplementedError:
617
                        explodeshape = False # fallback
618
                else:
619
                    explodeshape = False # fallback if we hit the False before
620
                if not explodeshape: # extract only the used subshape
621
                    sh = subobj.Shape
622
                    if not sh.isNull():
623
                        if sh.ShapeType == 'Compound':
624
                            sh = sh.childShapes()[0]
625
                        if sh.ShapeType == 'Face':
626
                            sh = sh.OuterWire
627
                        elif sh.ShapeType == 'Edge':
628
                            import Part
629
                            sh = Part.Wire([sh])
630
                        elif sh.ShapeType == 'Wire':
631
                            import Part
632
                            sh = Part.Wire(sh)
633
                        elif sh.ShapeType == 'Vertex':
634
                            pass
635
                        else:
636
                            raise ValueError('Unsuitabel Shape Type')
637
                        sectionname = '%s-%02d-section' % (ob.Name,i)
638
                        saveShape(self.csg,self.filename, sh,sectionname,None,\
639
                                self.cleanshape) # placement with shape
640
                    sectionnames.append(sectionname)
641
            if ob.Closed:
642
                sectionnames.append(sectionnames[0])
643
            self.csg.write('thrusections %s %d %d %s\n' % \
644
                    (ob.Name,int(ob.Solid),\
645
                    int(ob.Ruled), ' '.join(sectionnames)))
646
        elif isDeform(ob): #non-uniform scaling
647
            if checksupported: return True # The object is supported
648
            m=ob.Matrix
649
            self.process_object(ob.Base)
650
            #csg.write('tcopy %s %s\n'%(ob.Base.Name,d1['name']))
651
            d1['basename']=ob.Base.Name
652
            d1['cx']=f2s(m.A11)
653
            d1['cy']=f2s(m.A22)
654
            d1['cz']=f2s(m.A33)
655
            self.csg.write('deform %(name)s %(basename)s %(cx)s %(cy)s %(cz)s\n' % d1)
656
            if m.A14 > 1e-8 or m.A24 > 1e-8 or m.A34 > 1e-8:
657
                self.csg.write("ttranslate %s %s %s %s\n" % \
658
                    (ob.Name,f2s(m.A14),f2s(m.A24),f2s(m.A34)))
659
        elif isDraftPoint(ob) or ob.TypeId == "Part::Vertex":
660
            if checksupported: return True # The object is supported
661
            d1['x']=f2s(ob.X)
662
            d1['y']=f2s(ob.Y)
663
            d1['z']=f2s(ob.Z)
664
            self.csg.write('vertex %(name)s %(x)s %(y)s %(z)s\n' % d1)
665
        elif isDraftCircle(ob) or ob.TypeId == "Part::Circle" or \
666
                isDraftEllipse(ob):
667
            if checksupported: return True # The object is supported
668
            isdraftcircle=isDraftCircle(ob)
669
            isdraftellipse=isDraftCircle(ob)
670
            "circle name x y [z [dx dy dz]] [ux uy [uz]] radius"
671
            curvename = '%s-curve' % d1['name']
672
            if ob.TypeId == "Part::Circle":
673
                radius=f2s(float(ob.Radius))
674
                pfirst=f2s(ob.Angle0.getValueAs('rad').Value)
675
                plast=f2s(ob.Angle1.getValueAs('rad').Value)
676
                self.csg.write('circle %s 0 0 0 %s\n' % (curvename,radius))
677
                self.csg.write('mkedge %s %s %s %s\n' % \
678
                    (d1['name'],curvename,pfirst,plast))
679
            else: #draft
680
                makeface = ob.MakeFace and \
681
                    (ob.Shape.isNull() or ob.Shape.ShapeType == 'Face')
682
                #FreeCAD ignores a failed mkplane but it may
683
                #break the model in DRAWEXE
684
                edgename  = '%s-edge' % d1['name']
685

686
                if isdraftcircle:
687
                    pfirst=f2s(ob.FirstAngle.getValueAs('rad').Value)
688
                    plast=f2s(ob.LastAngle.getValueAs('rad').Value)
689
                    radius=f2s(ob.Radius.Value)
690
                    self.csg.write('circle %s 0 0 0 %s\n' % (curvename,radius))
691
                else: #draft ellipse
692
                    import math
693
                    majr=f2s(float(ob.MajorRadius))
694
                    minr=f2s(float(ob.MinorRadius))
695
                    pfirst=f2s(math.radians(ob.FirstAngle))
696
                    plast =f2s(math.radians(ob.LastAngle))
697
                    self.csg.write('ellipse %s 0 0 0 %s %s\n' % \
698
                            (curvename,majr,minr))
699
                self.csg.write('mkedge %s %s %s %s\n' % \
700
                        (edgename,curvename,pfirst,plast))
701
                if makeface:
702
                    wirename = '%s-wire' % d1['name']
703
                    self.csg.write('wire %s %s\n' %(wirename,edgename))
704
                    self.csg.write('mkplane %s %s\n' % (d1['name'],wirename))
705
                else:
706
                    self.csg.write('wire %s %s\n' %(d1['name'],edgename))
707
        elif ob.TypeId == "Part::Line":
708
            if checksupported: return True # The object is supported
709
            self.csg.write('polyline %s %s %s %s %s %s %s\n' % \
710
                    (d1['name'],f2s(ob.X1),f2s(ob.Y1),f2s(ob.Z1),\
711
                    f2s(ob.X2),f2s(ob.Y2),f2s(ob.Z2)))
712
        elif isDraftWire(ob):
713
            if checksupported: return True # The object is supported
714
            points=ob.Points
715
            if ob.Closed:
716
                points.append(points[0])
717
            polyname = '%s-dwireline' % d1['name']
718
            pointstr=' '.join('%s %s %s'%(f2s(v.x),f2s(v.y),f2s(v.z)) \
719
                    for v in points)
720
            self.csg.write('polyline %s %s\n' % (polyname,pointstr))
721
            if ob.MakeFace:
722
                wirename = '%s-dwirewire' % d1['name']
723
                self.csg.write('wire %s %s\n' %(wirename,polyname))
724
                facename =  d1['name']
725
                self.csg.write('mkplane %s %s\n' % (facename,polyname))
726
            else:
727
                wirename =  d1['name']
728
                self.csg.write('wire %s %s\n' %(wirename,polyname))
729
        elif isDraftClone(ob):
730
            if checksupported: return True # The object is supported
731
            x,y,z=ob.Scale
732
            if x == y == z: #uniform scaling
733
                d1['scale']=f2s(x)
734
            else:
735
                d1['cx']=f2s(x)
736
                d1['cy']=f2s(y)
737
                d1['cz']=f2s(z)
738
            if len(ob.Objects) == 1:
739
                d1['basename']=ob.Objects[0].Name
740
                self.process_object(ob.Objects[0])
741
                if x == y == z: #uniform scaling
742
                    self.csg.write('tcopy %(basename)s %(name)s\n' % d1)
743
                    self.csg.write('pscale %(name)s 0 0 0 %(scale)s\n' % d1)
744
                else:
745
                    self.csg.write('deform %(name)s %(basename)s'\
746
                            ' %(cx)s %(cy)s %(cz)s\n' % d1)
747
            else: #compound
748
                newnames=[]
749
                for i,subobj in enumerate(ob.Objects):
750
                    self.process_object(subobj)
751
                    d1['basename']=subobj.Name
752
                    newname='%s-%2d' % (ob.Name,i)
753
                    d1['newname']=newname
754
                    newnames.append(newname)
755
                    if x == y == z: #uniform scaling
756
                        self.csg.write('tcopy %(basename)s %(newname)s\n' % d1)
757
                        self.csg.write('pscale %(newname)s 0 0 0 %(scale)s\n' % d1)
758
                    else:
759
                        self.csg.write('deform %(newname)s %(basename)s'\
760
                                ' %(cx)s %(cy)s %(cz)s\n' % d1)
761
                self.csg.write('compound %s %s\n' % (' '.join(newnames),ob.Name))
762
        elif isDraftShape2DView(ob) and not ob.Tessellation and \
763
                ob.ProjectionMode == "Solid" and ob.Base is not None and \
764
                hasattr(ob.Base,'Shape'):
765
        # not supported are groups, Arch/Sections and individual faces mode
766
            if checksupported: return True # The object is supported
767
            self.process_object(ob.Base)
768
            v=ob.Projection
769
            x=ax2_xdir(v)
770
            self.csg.write('hprj %s_proj 0 0 0 %s %s %s %s %s %s\n' % \
771
                    ( ob.Name,f2s(v.x),f2s(v.y),f2s(v.z)\
772
                    , f2s(x.x),f2s(x.y),f2s(x.z)))
773
            self.csg.write('houtl %s_outl %s\n' % (ob.Name, ob.Base.Name))
774
            self.csg.write('hfill %s_outl %s_proj 0\n' %(ob.Name,ob.Name)) #0?
775
            self.csg.write('hload %s_outl\n' % (ob.Name))
776
            self.csg.write('hsetprj %s_proj\n' % (ob.Name))
777
            self.csg.write('hupdate\n')
778
            self.csg.write('hhide\n')
779
            self.csg.write('unset -nocomplain vl v1l vnl vol vil hl h1l hnl hol hil\n')
780
            self.csg.write('hres2d\n')
781
            if ob.HiddenLines:
782
                self.csg.write('compound vl v1l vnl vol vil hl h1l hnl hol hil %s\n' % ob.Name)
783
            else:
784
                self.csg.write('compound vl v1l vnl vol vil %s\n' % ob.Name)
785
        #elif ob.isDerivedFrom('Part::FeaturePython') and \
786
        #    hasattr(ob.Proxy,'__module__'):
787
        #    pass
788
        elif ob.isDerivedFrom('Part::Feature') :
789
            if ob.Shape.isNull(): #would crash in exportBrep otherwise
790
                raise ValueError('Shape of %s is Null' % ob.Name)
791
            if checksupported: return False # The object is not supported
792
            self.csg.write('#saved shape of unsupported %s Object\n' % \
793
                    formatobjtype(ob))
794
            hasplacement = saveShape(self.csg,self.filename,ob.Shape,ob.Name,\
795
                    hasplacement,self.cleanshape)
796
        elif ob.isDerivedFrom('App::Annotation') :
797
            return False # ignored here
798
            #anntotations needs to be drawn after erase/donly
799
        else: # not derived from Part::Feature
800
            if not toplevel:
801
                raise ValueError('Can not export child object')
802
            else:
803
                if ob.Name != ob.Label:
804
                    labelstr = 'Label %s' % ob.Label.encode('unicode-escape')
805
                else:
806
                    labelstr = ''
807
                self.csg.write('#omitted unsupported %s Object %s%s\n' %\
808
                        (formatobjtype(ob),ob.Name,labelstr))
809
                self.csg.write('#Properties: %s\n' % \
810
                        ','.join(ob.PropertiesList))
811
                return False
812
                #The object is not present and can not be referenced
813
        if hasplacement:
814
            self.csg.write(placement2draw(ob.Placement,ob.Name))
815
        if ob.Name != ob.Label:
816
            self.csg.write('#Object Label: %s\n' % ob.Label.encode('unicode-escape'))
817
        return ob.Name #The object is present and can be referenced
818

819
    def export_annotations(self,objlst):
820
        for ob in objlst:
821
            if ob.isDerivedFrom('App::Annotation') :
822
                if ob.Name != ob.Label:
823
                    self.csg.write('#Annotation Name %s Label %s"\n' % \
824
                            (ob.Name,ob.Label.encode('unicode-escape')))
825
                else:
826
                    self.csg.write('#Annotation %s\n' % (ob.Name))
827
                v=ob.Position
828
                self.csg.write('dtext %s %s %s "%s"\n' % \
829
                        (f2s(v.x),f2s(v.y),f2s(v.z), '\\n'.join(\
830
                        ob.LabelText).encode(\
831
                        'ascii', errors='xmlcharrefreplace')))
832

833
    def export_objects(self,objlst,toplevel=True):
834
        self.write_header()
835
        toplevelobjs = [self.process_object(ob, toplevel=toplevel)\
836
                for ob in objlst]
837
        names = [name for name in toplevelobjs if name is not False]
838
        self.csg.write('donly %s\n'%(' '.join(names)))
839
        self.export_annotations(objlst)
840
        #for ob in objlst:
841
        #    self.process_object(ob,toplevel=toplevel)
842
        #self.write_displayonly(objlst)
843

844
    def __exit__(self,exc_type, exc_val, exc_tb ):
845
        self.csg.close()
846

847
def export(exportList,filename):
848
    "called when freecad exports a file"
849
    with Drawexporter(filename) as exporter:
850
        exporter.export_objects(exportList)
851

852
if 'tcl' not in FreeCAD.getExportType():
853
    FreeCAD.addExportType("DRAWEXE script (*.tcl)","exportDRAWEXE")
854

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.