DXF-Exporter update
[blender-staging.git] / release / scripts / export_dxf.py
1 #!BPY
2
3 """
4  Name: 'Autodesk DXF (.dxf)'
5  Blender: 247
6  Group: 'Export'
7  Tooltip: 'Export geometry to DXF-r12 (Drawing eXchange Format).'
8 """
9
10 __version__ = "v1.27beta - 2008.10.07"
11 __author__  = "Remigiusz Fiedler (AKA migius)"
12 __license__ = "GPL"
13 __url__  = "http://wiki.blender.org/index.php/Scripts/Manual/Export/autodesk_dxf"
14 __bpydoc__ ="""The script exports Blender geometry to DXF format r12 version.
15
16 Version %s
17 Copyright %s
18 License %s
19
20 extern dependances: dxfLibrary.py
21
22 See the homepage for documentation.
23 url: %s
24
25 IDEAs:
26  - correct normals for POLYLINE-POLYFACE via proper point-order
27  - HPGL output for 2d and flattened 3d content
28                 
29 TODO:
30 - optimize back-faces removal (probably needs matrix transform)
31 - optimize POLYFACE routine: remove double-vertices
32 - optimize POLYFACE routine: remove unused vertices
33 - support hierarchies: groups, instances, parented structures
34 - support 210-code (3d orientation vector)
35 - presets for architectural scales
36 - write drawing extends for automatic view positioning in CAD
37
38 History
39 v1.27 - 2008.10.07 by migius
40 - exclude Stani's DXF-Library to extern module
41 v1.26 - 2008.10.05 by migius
42 - add "hidden mode" substitut: back-faces removal
43 - add support for mesh ->POLYFACE
44 - optimized code for "Flat" procedure
45 v1.25 - 2008.09.28 by migius
46 - modif FACE class for r12
47 - add mesh-polygon -> Bezier-curve converter (Yorik's code)
48 - add support for curves ->POLYLINEs
49 - add "3d-View to Flat" - geometry projection to XY-plane
50 v1.24 - 2008.09.27 by migius
51 - add start UI with preferences
52 - modif POLYLINE class for r12
53 - changing output format from r9 to r12(AC1009)
54 v1.23 - 2008.09.26 by migius
55 - add finish message-box
56 v1.22 - 2008.09.26 by migius
57 - add support for curves ->LINEs
58 - add support for mesh-edges ->LINEs
59 v1.21 - 2008.06.04 by migius
60 - initial adaptation for Blender
61 v1.1 (20/6/2005) by Stani Michiels www.stani.be/python/sdxf
62 - Python library to generate dxf drawings
63 ______________________________________________________________
64 """ % (__author__,__version__,__license__,__url__)
65
66 # --------------------------------------------------------------------------
67 # Script copyright (C) 2008 Remigiusz Fiedler (AKA migius)
68 # --------------------------------------------------------------------------
69 # ***** BEGIN GPL LICENSE BLOCK *****
70 #
71 # This program is free software; you can redistribute it and/or
72 # modify it under the terms of the GNU General Public License
73 # as published by the Free Software Foundation; either version 2
74 # of the License, or (at your option) any later version.
75 #
76 # This program is distributed in the hope that it will be useful,
77 # but WITHOUT ANY WARRANTY; without even the implied warranty of
78 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
79 # GNU General Public License for more details.
80 #
81 # You should have received a copy of the GNU General Public License
82 # along with this program; if not, write to the Free Software Foundation,
83 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
84 #
85 # ***** END GPL LICENCE BLOCK *****
86
87
88 import Blender
89 from Blender import Mathutils, Window, Scene, sys, Draw
90 import BPyMessages
91
92 #import dxfLibrary
93 #reload(dxfLibrary)
94 from  dxfLibrary import *
95
96
97 #-----------------------------------------------------
98 def hidden_status(faces, mx_n):
99         #print 'HIDDEN_MODE: caution! not full implemented yet'
100         ok_faces = []
101         ok_edges = []
102         #sort out back-faces = with normals pointed away from camera
103         for f in faces:
104                 #print 'deb: face=', f #---------
105                 # get its normal-vector in localCS
106                 vec_normal = f.no.copy()
107                 #print 'deb: vec_normal=', vec_normal #------------------
108                 #must be transfered to camera/view-CS
109                 vec_normal *= mx_n
110                 #vec_normal *= mb.rotationPart()
111                 #print 'deb:2vec_normal=', vec_normal #------------------
112                 #vec_normal *= mw0.rotationPart()
113                 #print 'deb:3vec_normal=', vec_normal, '\n' #------------------
114
115                 # normal must point the Z direction-hemisphere
116                 if vec_normal[2] > 0.0 :
117                         ok_faces.append(f.index)
118                         for key in f.edge_keys:
119                                 #this test can be done faster with set()
120                                 if key not in ok_edges:
121                                          ok_edges.append(key)
122         #print 'deb: amount of visible faces=', len(ok_faces) #---------
123         #print 'deb: visible faces=', ok_faces #---------
124         #print 'deb: amount of visible edges=', len(ok_edges) #---------
125         #print 'deb: visible edges=', ok_edges #---------
126         return ok_faces, ok_edges
127
128
129 #-----------------------------------------------------
130 def projected_co(vec, mw):
131         # convert the world coordinates of vector to screen coordinates
132         #co = vec.co.copy().resize4D()
133         co = vec.copy().resize4D()
134         co[3] = 1.0
135         sc = co * mw
136         #print 'deb: viewprojection=', sc #---------
137         return [sc[0],sc[1],0.0]
138
139
140 #--------not used---------------------------------------------
141 def flatten(points, mw):
142         for i,v in enumerate(points):
143                 v = projected_co(v, mw)
144                 points[i]=v
145         #print 'deb: flatten points=', points #---------
146         return points
147
148 #-----------------------------------------------------
149 def     exportMesh(ob, mx, mx_n):
150         entities = []
151         me = ob.getData(mesh=1)
152         #me.transform(mx)
153         # above is eventualy faster, but bad, cause
154         # directly transforms origin geometry and write back rounding errors
155         me_verts = me.verts[:] #we dont want manipulate origin data
156         #print 'deb: me_verts=', me_verts #---------
157         #me.transform(mx_inv) #counterpart to - back to the origin state
158         for v in me_verts:
159                 v.co *= mx
160         faces=[]
161         edges=[]
162         if HIDDEN_MODE:
163                 ok_faces, ok_edges = hidden_status(me.faces, mx_n)
164
165         #if (not FLATTEN) and len(me.faces)>0 and ONLYFACES:
166         if ONLYFACES:
167                 if POLYFACES: #export 3D as POLYFACEs
168                         allpoints = []
169                         allfaces = []
170                         allpoints =  [v.co[:3] for v in me_verts]
171                         for f in me.faces:
172                                 #print 'deb: face=', f #---------
173                                 if not HIDDEN_MODE or \
174                                         (HIDDEN_MODE and f.index in ok_faces):
175                                         if 1:
176                                                 points = f.verts
177                                                 face = [p.index+1 for p in points]
178                                                 #print 'deb: face=', face #---------
179                                                 allfaces.append(face)
180                                         else: #bad, cause create multiple vertex instances
181                                                 points  = f.verts
182                                                 points = [ me_verts[p.index].co[:3] for p in points]
183                                                 #points = [p.co[:3] for p in points]
184                                                 #print 'deb: points=', points #---------
185                                                 index = len(allpoints)+1
186                                                 face = [index+i for i in range(len(points))]
187                                                 allpoints.extend(points)
188                                                 allfaces.append(face)
189                         if allpoints and allfaces:
190                                 #print 'deb: allpoints=', allpoints #---------
191                                 #print 'deb: allfaces=', allfaces #---------
192                                 dxfPOLYFACE = PolyLine([allpoints, allfaces], flag=64)
193                                 entities.append(dxfPOLYFACE)
194                 else: #export 3D as 3DFACEs
195                         for f in me.faces:
196                                 #print 'deb: face=', f #---------
197                                 if not HIDDEN_MODE or \
198                                         (HIDDEN_MODE and f.index in ok_faces):
199                                         points  = f.verts
200                                         points = [ me_verts[p.index].co[:3] for p in points]
201                                         #points = [p.co[:3] for p in points]
202                                         #print 'deb: points=', points #---------
203                                         dxfFACE = Face(points)
204                                         entities.append(dxfFACE)
205
206         else:   #export 3D as LINEs
207                 if HIDDEN_MODE and len(me.faces)!=0:
208                         for e in ok_edges:
209                                 points = [ me_verts[key].co[:3] for key in e]
210                                 dxfLINE = Line(points)
211                                 entities.append(dxfLINE)
212                                 
213                 else:
214                         for e in me.edges:
215                                 #print 'deb: edge=', e #---------
216                                 points=[]
217                                 #points = [e.v1.co*mx, e.v2.co*mx]
218                                 points = [ me_verts[key].co[:3] for key in e.key]
219                                 #print 'deb: points=', points #---------
220                                 dxfLINE = Line(points)
221                                 entities.append(dxfLINE)
222         return entities
223
224
225 #-----------------------------------------------------
226 def exportCurve(ob, mx):
227         entities = []
228         curve = ob.getData()
229         for cur in curve:
230                 #print 'deb: START cur=', cur #--------------
231                 if 1: #not cur.isNurb():
232                         #print 'deb: START points' #--------------
233                         points = []
234                         org_point = [0.0,0.0,0.0]
235                         for point in cur:
236                                 #print 'deb: point=', point #---------
237                                 if cur.isNurb():
238                                         vec = point[0:3]
239                                 else:
240                                         point = point.getTriple()
241                                         #print 'deb: point=', point #---------
242                                         vec = point[1]
243                                 #print 'deb: vec=', vec #---------
244                                 pkt = Mathutils.Vector(vec) * mx
245                                 #print 'deb: pkt=', pkt #---------
246                                 #pkt *= SCALE_FACTOR
247                                 if 0: #FLATTEN:
248                                         pkt = projected_co(pkt, mw)
249                                 points.append(pkt)
250                         if cur.isCyclic(): closed = 1
251                         else: closed = 0
252                         if len(points)>1:
253                                 #print 'deb: points', points #--------------
254                                 if POLYLINES: dxfPLINE = PolyLine(points,org_point,closed)
255                                 else: dxfPLINE = LineList(points,org_point,closed)
256                                 entities.append(dxfPLINE)
257         return entities
258
259 #-----------------------------------------------------
260 def do_export(sel_group, filepath):
261         Window.WaitCursor(1)
262         t = sys.time()
263
264         #init Drawing ---------------------
265         d=Drawing()
266         #add Tables -----------------
267         #d.blocks.append(b)                                     #table blocks
268         d.styles.append(Style())                        #table styles
269         d.views.append(View('Normal'))          #table view
270         d.views.append(ViewByWindow('Window',leftBottom=(1,0),rightTop=(2,1)))  #idem
271
272         #add Entities --------------------
273         something_ready = False
274         #ViewVector = Mathutils.Vector(Window.GetViewVector())
275         #print 'deb: ViewVector=', ViewVector #------------------
276         mw0 = Window.GetViewMatrix()
277         #mw0 = Window.GetPerspMatrix() #TODO: how get it working?
278         mw = mw0.copy()
279         if FLATTEN:
280                 m0 = Mathutils.Matrix()
281                 m0[2][2]=0.0
282                 mw *= m0 #flatten ViewMatrix
283
284         for ob in sel_group:
285                 entities = []
286                 mx = ob.matrix.copy()
287                 mb = mx.copy()
288                 #print 'deb: mb    =\n', mb     #---------
289                 #print 'deb: mw0    =\n', mw0     #---------
290                 mx_n = mx.rotationPart() * mw0.rotationPart() #trans-matrix for normal_vectors
291                 if SCALE_FACTOR!=1.0: mx *= SCALE_FACTOR
292                 if FLATTEN:     mx *= mw
293                         
294                 #mx_inv = mx.copy().invert()
295                 #print 'deb: mx    =\n', mx     #---------
296                 #print 'deb: mx_inv=\n', mx_inv #---------
297
298                 if (ob.type == 'Mesh'):
299                         entities = exportMesh(ob, mx, mx_n)
300                 elif (ob.type == 'Curve'):
301                         entities = exportCurve(ob, mx)
302
303                 for e in entities:
304                         d.append(e)
305                         something_ready = True
306
307         if something_ready:
308                 d.saveas(filepath)
309                 Window.WaitCursor(0)
310                 #Draw.PupMenu('DXF Exporter: job finished')
311                 print 'exported to %s' % filepath
312                 print 'finished in %.2f seconds' % (sys.time()-t)
313         else:
314                 Window.WaitCursor(0)
315                 print "Abort: selected objects dont mach choosen export option, nothing exported!"
316                 Draw.PupMenu('DXF Exporter:   nothing exported!|selected objects dont mach choosen export option!')
317
318 #----globals------------------------------------------
319 ONLYSELECTED = True
320 POLYLINES = True
321 ONLYFACES = False
322 POLYFACES = 1
323 FLATTEN = 0
324 HIDDEN_MODE = False #filter out hidden lines
325 SCALE_FACTOR = 1.0 #optional, can be done later in CAD too
326
327
328
329 #-----------------------------------------------------
330 def dxf_export_ui(filepath):
331         global  ONLYSELECTED,\
332         POLYLINES,\
333         ONLYFACES,\
334         POLYFACES,\
335         FLATTEN,\
336         HIDDEN_MODE,\
337         SCALE_FACTOR
338
339         print '\n\nDXF-Export %s -----------------------' %__version__
340         #filepath = 'blend_test.dxf'
341         # Dont overwrite
342         if not BPyMessages.Warning_SaveOver(filepath):
343                 print 'Aborted by user: nothing exported'
344                 return
345         #test():return
346
347
348         PREF_ONLYSELECTED= Draw.Create(ONLYSELECTED)
349         PREF_POLYLINES= Draw.Create(POLYLINES)
350         PREF_ONLYFACES= Draw.Create(ONLYFACES)
351         PREF_POLYFACES= Draw.Create(POLYFACES)
352         PREF_FLATTEN= Draw.Create(FLATTEN)
353         PREF_HIDDEN_MODE= Draw.Create(HIDDEN_MODE)
354         PREF_SCALE_FACTOR= Draw.Create(SCALE_FACTOR)
355         PREF_HELP= Draw.Create(0)
356         block = [\
357         ("only selected", PREF_ONLYSELECTED, "export only selected geometry"),\
358         ("global Scale:", PREF_SCALE_FACTOR, 0.001, 1000, "set global Scale factor for exporting geometry"),\
359         ("only faces", PREF_ONLYFACES, "from mesh-objects export only faces, otherwise only edges"),\
360         ("write POLYFACE", PREF_POLYFACES, "export mesh to POLYFACE, otherwise to 3DFACEs"),\
361         ("write POLYLINEs", PREF_POLYLINES, "export curve to POLYLINE, otherwise to LINEs"),\
362         ("3D-View to Flat", PREF_FLATTEN, "flatten geometry according current 3d-View"),\
363         ("Hidden Mode", PREF_HIDDEN_MODE, "filter out hidden lines"),\
364         #(''),\
365         ("online Help", PREF_HELP, "calls DXF-Exporter Manual Page on Wiki.Blender.org"),\
366         ]
367         
368         if not Draw.PupBlock("DXF-Exporter %s" %__version__[:10], block): return
369
370         if PREF_HELP.val!=0:
371                 try:
372                         import webbrowser
373                         webbrowser.open('http://wiki.blender.org/index.php?title=Scripts/Manual/Export/autodesk_dxf')
374                 except:
375                         Draw.PupMenu('DXF Exporter: %t|no connection to manual-page on Blender-Wiki!    try:|\
376 http://wiki.blender.org/index.php?title=Scripts/Manual/Export/autodesk_dxf')
377                 return
378
379         ONLYSELECTED = PREF_ONLYSELECTED.val
380         POLYLINES = PREF_POLYLINES.val
381         ONLYFACES = PREF_ONLYFACES.val
382         POLYFACES = PREF_POLYFACES.val
383         FLATTEN = PREF_FLATTEN.val
384         HIDDEN_MODE = PREF_HIDDEN_MODE.val
385         SCALE_FACTOR = PREF_SCALE_FACTOR.val
386
387         sce = Scene.GetCurrent()
388         if ONLYSELECTED: sel_group = sce.objects.selected
389         else: sel_group = sce.objects
390
391         if sel_group: do_export(sel_group, filepath)
392         else:
393                 print "Abort: selection was empty, no object to export!"
394                 Draw.PupMenu('DXF Exporter:   nothing exported!|empty selection!')
395         # Timing the script is a good way to be aware on any speed hits when scripting
396
397
398
399 #-----------------------------------------------------
400 if __name__=='__main__':
401         #main()
402         if not copy:
403                 Draw.PupMenu('Error%t|This script requires a full python install')
404         Window.FileSelector(dxf_export_ui, 'EXPORT DXF', sys.makename(ext='.dxf'))
405         
406         
407