BPython:
[blender-staging.git] / release / scripts / ac3d_export.py
1 #!BPY
2
3 """ Registration info for Blender menus:
4 Name: 'AC3D'
5 Blender: 232
6 Group: 'Export'
7 Submenu: 'All meshes...' all
8 Submenu: 'Only selected...' sel
9 Submenu: 'Configure +' config
10 Tip: 'Export to AC3D (.ac) format.'
11 """
12
13 # --------------------------------------------------------------------------
14 # AC3DExport version 2.32-1 Jan 21, 2004
15 # Program versions: Blender 2.32+ and AC3Db files (means version 0xb)
16 # --------------------------------------------------------------------------
17 # ***** BEGIN GPL LICENSE BLOCK *****
18 #
19 # Copyright (C) 2004: Willian P. Germano, wgermano@ig.com.br
20 #
21 # This program is free software; you can redistribute it and/or
22 # modify it under the terms of the GNU General Public License
23 # as published by the Free Software Foundation; either version 2
24 # of the License, or (at your option) any later version.
25 #
26 # This program is distributed in the hope that it will be useful,
27 # but WITHOUT ANY WARRANTY; without even the implied warranty of
28 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29 # GNU General Public License for more details.
30 #
31 # You should have received a copy of the GNU General Public License
32 # along with this program; if not, write to the Free Software Foundation,
33 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
34 #
35 # ***** END GPL LICENCE BLOCK *****
36 # --------------------------------------------------------------------------
37
38 import Blender
39
40 ARG = __script__['arg'] # user selected argument
41
42 HELPME = 0 # help window
43
44 SKIP_DATA = 1
45 MIRCOL_AS_AMB = 0
46 MIRCOL_AS_EMIS = 0
47 ADD_DEFAULT_MAT = 1
48
49 # Looking for a saved key in Blender.Registry dict:
50 rd = Blender.Registry.GetKey('AC3DExport')
51 if rd:
52   SKIP_DATA = rd['SKIP_DATA']
53   MIRCOL_AS_AMB = rd['MIRCOL_AS_AMB']
54   MIRCOL_AS_EMIS = rd['MIRCOL_AS_EMIS']
55   ADD_DEFAULT_MAT = rd['ADD_DEFAULT_MAT']
56
57 def update_RegistryInfo():
58   d = {}
59   d['SKIP_DATA'] = SKIP_DATA
60   d['MIRCOL_AS_AMB'] = MIRCOL_AS_AMB
61   d['MIRCOL_AS_EMIS'] = MIRCOL_AS_EMIS
62   d['ADD_DEFAULT_MAT'] = ADD_DEFAULT_MAT
63   Blender.Registry.SetKey('AC3DExport', d)
64
65 # The default material to be used when necessary (see right above)
66 DEFAULT_MAT = \
67 'MATERIAL "DefaultWhite" rgb 1 1 1  amb 1 1 1  emis 0 0 0  spec 0.5 0.5 0.5 shi 64  trans 0'
68
69 # This transformation aligns Blender and AC3D coordinate systems:
70 acmatrix = [[1,0,0,0],[0,0,-1,0],[0,1,0,0],[0,0,0,1]]
71
72 def Round(f):
73     r = round(f,6) # precision set to 10e-06
74     if r == int(r):
75         return str(int(r))
76     else:
77         return str(r)
78     
79 def transform_verts(verts, m):
80     r = []
81     for v in verts:
82         t = [0,0,0]
83         t[0] = m[0][0]*v[0] + m[1][0]*v[1] + m[2][0]*v[2] + m[3][0]
84         t[1] = m[0][1]*v[0] + m[1][1]*v[1] + m[2][1]*v[2] + m[3][1]
85         t[2] = m[0][2]*v[0] + m[1][2]*v[1] + m[2][2]*v[2] + m[3][2]
86         r.append(t)
87     return r
88
89 def matrix_mul(m, n = acmatrix):
90     indices = [0,1,2,3]
91     t = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
92     for i in indices:
93         for j in indices:
94             for k in indices:
95                 t[i][j] += m[i][k]*n[k][j]
96     return t
97
98 # ---
99
100 errmsg = ''
101
102 class AC3DExport:
103
104     def __init__(self, scene, filename):
105
106         global ARG, SKIP_DATA, ADD_DEFAULT_MAT, DEFAULT_MAT, errmsg
107
108         print 'Trying AC3DExport...'
109
110         header = 'AC3Db'
111         self.buf = ''
112         self.mbuf = ''
113         world_kids = 0
114         self.mlist = []
115         kids_dict = {}
116         objlist = []
117         bl_objlist2 = []
118  
119         if ARG == 'all': bl_objlist = scene.getChildren()
120         elif ARG == 'sel': bl_objlist = Blender.Object.GetSelected()
121
122         for obj in bl_objlist:
123             if obj.getType() != 'Mesh' and obj.getType() != 'Empty':
124                 continue
125             else: kids_dict[obj.name] = 0
126             if obj.getParent() == None:
127                 objlist.append(obj.name)
128             else:
129                 bl_objlist2.append(obj)
130
131         bl_objlist = bl_objlist2[:]
132         world_kids = len(objlist)
133
134         while bl_objlist2:
135             for obj in bl_objlist:
136                 obj2 = obj
137                 dad = obj.getParent()
138                 kids_dict[dad.name] += 1
139                 while dad.name not in objlist:
140                     obj2 = dad
141                     dad = dad.getParent()
142                     kids_dict[dad.name] += 1
143                 objlist.insert(objlist.index(dad.name)+1, obj2.name)
144                 bl_objlist2.remove(obj2)
145
146         for object in objlist:
147             obj = Blender.Object.Get(object)
148             self.obj = obj
149
150             if obj.getType() == 'Empty':
151                 self.OBJECT("group")
152                 self.name(obj.name)
153                 #self.rot(obj.rot)
154                 #self.loc(obj.loc)
155             else:
156                 mesh = self.mesh = obj.getData()
157                 self.MATERIAL(mesh.materials)
158                 self.OBJECT("poly")
159                 self.name(obj.name)
160                 if not SKIP_DATA: self.data(mesh.name)
161                 self.texture(mesh.faces)
162                 self.numvert(mesh.verts, obj.getMatrix())
163                 self.numsurf(mesh.faces, mesh.hasFaceUV())
164
165             self.kids(kids_dict[object])
166
167         if not self.mbuf or ADD_DEFAULT_MAT:
168             self.mbuf = DEFAULT_MAT + '\n' + self.mbuf
169             print "\nNo materials: a default (white) has been assigned.\n"
170         self.mbuf = self.mbuf + "%s\n%s %s\n" \
171                     % ('OBJECT world', 'kids', world_kids)
172         buf = "%s\n%s%s" % (header, self.mbuf, self.buf)
173
174         if filename.find('.ac', -3) <= 0: filename += '.ac'
175
176         try:
177             file = open(filename, 'w')
178         except IOError, (errno, strerror):
179             errmsg = "IOError #%s: %s" % (errno, strerror)
180             return None
181         file.write(buf)
182         file.close()
183
184         print "Done. Saved to %s\n" % filename
185
186     def MATERIAL(self, mat):
187         if mat == [None]:
188             print "Notice -- object %s has no material linked to it:" % self.name
189             print "\tThe first entry in the .ac file will be used."
190             return
191         mbuf = ''
192         mlist = self.mlist
193         for m in xrange(len(mat)):
194             name = mat[m].name
195             try:
196                 mlist.index(name)
197             except ValueError:
198                 mlist.append(name)
199                 M = Blender.Material.Get(name)
200                 material = 'MATERIAL "%s"' % name
201                 mirCol = "%s %s %s" % (Round(M.mirCol[0]),
202                                        Round(M.mirCol[1]), Round(M.mirCol[2]))
203                 rgb = "rgb %s %s %s" % (Round(M.R), Round(M.G), Round(M.B))
204                 amb = "amb %s %s %s" % (Round(M.amb), Round(M.amb), Round(M.amb))
205                 if MIRCOL_AS_AMB:
206                     amb = "amb %s" % mirCol 
207                 emis = "emis 0 0 0"
208                 if MIRCOL_AS_EMIS:
209                     emis = "emis %s" % mirCol
210                 spec = "spec %s %s %s" % (Round(M.specCol[0]),
211                                           Round(M.specCol[1]), Round(M.specCol[2]))
212                 shi = "shi 72"
213                 trans = "trans %s" % (Round(1 - M.alpha))
214                 mbuf = mbuf + "%s %s %s %s %s %s %s\n" \
215                        % (material, rgb, amb, emis, spec, shi, trans)
216         self.mlist = mlist
217         self.mbuf = self.mbuf + mbuf
218
219     def OBJECT(self, type):
220         self.buf = self.buf + "OBJECT %s\n" % type
221
222     def name(self, name):
223         self.buf = self.buf + 'name "%s"\n' % name
224
225     def data(self, name):
226         self.buf = self.buf + 'data %s\n%s\n' % (len(name), name)
227
228     def texture(self, faces):
229         tex = []
230         for f in faces:
231             if f.image and f.image.name not in tex:
232                 tex.append(f.image.name)
233         if tex:
234             if len(tex) > 1:
235                 print "\nAC3Db format supports only one texture per object."
236                 print "Object %s -- using only the first one: %s\n" % (self.obj.name, tex[0])
237             image = Blender.Image.Get(tex[0])
238             buf = 'texture "%s"\n' % image.filename
239             xrep = image.xrep
240             yrep = image.yrep
241             buf += 'texrep %s %s\n' % (xrep, yrep)
242             self.buf = self.buf + buf
243
244     def rot(self, matrix):
245         rot = ''
246         not_I = 0
247         for i in [0, 1, 2]:
248             r = map(Round, matrix[i])
249             not_I += (r[0] != '0.0')+(r[1] != '0.0')+(r[2] != '0.0')
250             not_I -= (r[i] == '1.0')
251             for j in [0, 1, 2]:
252                 rot = "%s %s" % (rot, r[j])
253         if not_I:
254             rot = rot.strip()
255             buf = 'rot %s\n' % rot
256             self.buf = self.buf + buf
257         
258     def loc(self, loc):
259         loc = map(Round, loc)
260         if loc[0] or loc[1] or loc[2]:
261             buf = 'loc %s %s %s\n' % (loc[0], loc[1], loc[2])
262             self.buf = self.buf + buf
263
264     def numvert(self, verts, matrix):
265         buf = "numvert %s\n" % len(verts)
266         m = matrix_mul(matrix)
267         verts = transform_verts(verts, m)
268         for v in verts:
269             v = map(Round, v)
270             buf = buf + "%s %s %s\n" % (v[0], v[1], v[2])
271         self.buf = self.buf + buf
272
273     def numsurf(self, faces, hasFaceUV):
274
275         global ADD_DEFAULT_MAT
276         
277         buf = "numsurf %s\n" % len(faces)
278         
279         mlist = self.mlist
280         indexerror = 0
281         omlist = {}
282         objmats = self.mesh.materials
283         for i in range(len(objmats)):
284             objmats[i] = objmats[i].name
285         for f in faces:
286             m_idx = f.materialIndex
287             try:
288                 m_idx = mlist.index(objmats[m_idx])
289             except IndexError:
290                 if not indexerror:
291                     print "\nNotice: object " + self.obj.name + \
292                           " has at least one material *index* assigned"
293                     print "\tbut not defined (not linked to an existing material)."
294                     print "\tThis can cause some of its faces to be exported with a wrong color."
295                     print "\tYou can fix the problem in the Blender Edit Buttons Window (F9).\n"
296                     indexerror = 1
297                 m_idx = 0
298             refs = len(f)
299             flaglow = (refs == 2) << 1
300             two_side = f.mode & Blender.NMesh.FaceModes['TWOSIDE']
301             two_side = (two_side > 0) << 1
302             flaghigh = f.smooth | two_side
303             buf = buf + "SURF 0x%d%d\n" % (flaghigh, flaglow)
304             if ADD_DEFAULT_MAT and objmats: m_idx += 1
305             buf = buf + "mat %s\n" % m_idx
306             buf = buf + "refs %s\n" % refs
307             u, v, vi = 0, 0, 0
308             for vert in f.v:
309                 vindex = self.mesh.verts.index(vert)
310                 if hasFaceUV:
311                     u = f.uv[vi][0]
312                     v = f.uv[vi][1]
313                     vi += 1
314                 buf = buf + "%s %s %s\n" % (vindex, u, v)
315         self.buf = self.buf + buf
316
317     def kids(self, kids = 0):
318         self.buf = self.buf + "kids %s\n" % kids
319
320 # End of Class AC3DExport
321
322 from Blender import Draw, BGL
323
324 def gui():
325   global SKIP_DATA, MIRCOL_AS_AMB, MIRCOL_AS_EMIS, ADD_DEFAULT_MAT, HELPME
326   global HELPME
327
328   if HELPME:
329     BGL.glClearColor(0.6,0.6,0.9,1)
330     BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
331     BGL.glColor3f(1,1,1)
332     BGL.glRasterPos2i(20, 270)
333     Draw.Text("AC3D Exporter")
334     BGL.glRasterPos2i(30, 250)
335     Draw.Text("AC3D is a simple, affordable commercial 3d modeller that can be found at www.ac3d.org .")
336     BGL.glRasterPos2i(30, 230)
337     Draw.Text("It uses a nice text file format (extension .ac) which supports uv-textured meshes")
338     BGL.glRasterPos2i(30, 210)
339     Draw.Text("with parenting (grouping) information.")
340     BGL.glRasterPos2i(30, 190)
341     Draw.Text("Notes: AC3D has a 'data' token that assigns a string to each mesh, useful for games,")
342     BGL.glRasterPos2i(67, 170)
343     Draw.Text("for example. You can use Blender's mesh datablock name for that.")
344     BGL.glRasterPos2i(67, 150)
345     Draw.Text("The .ac format is well supported by the PLib 3d gaming library. You can use this")
346     BGL.glRasterPos2i(67, 130)
347     Draw.Text("exporter to have your Blender models in games and other apps written with PLib.")
348     Draw.Button("Ok", 21, 285, 80, 100, 40, "Click to return to previous screen.")
349   else:
350     BGL.glClearColor(0,0,1,1)
351     BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
352     BGL.glColor3f(1,1,1)
353     BGL.glRasterPos2i(20, 150)
354     Draw.Text("AC3D Exporter")
355     Draw.Toggle("Default mat", 1, 15, 100, 90, 20, ADD_DEFAULT_MAT, "Objects without materials assigned get a default (white) one automatically.")
356     Draw.Toggle("Skip data", 2, 15, 80, 90, 20, SKIP_DATA, "Don't export mesh names as 'data' info.")
357     Draw.Toggle("Mir2Amb", 3, 15, 50, 90, 20, MIRCOL_AS_AMB, "Get AC3D's ambient RGB color for each object from its mirror color in Blender.")
358     Draw.Toggle("Mir2Emis", 4, 15, 30, 90, 20, MIRCOL_AS_EMIS, "Get AC3D's emissive RGB color for each object from its mirror color in Blender.")
359     Draw.Button("Export All...", 10, 140, 80, 110, 30, "Export all meshes to an AC3D file.")
360     Draw.Button("Export Selected...", 11, 140, 40, 110, 30, "Export selected meshes to an AC3D file.")
361     Draw.Button("HELP", 20, 285, 80, 100, 40, "Click for additional info.")
362     Draw.Button("EXIT", 22, 285, 30, 100, 40, "Click to leave.")
363
364 def event(evt, val):
365   global HELPME
366
367   if not val: return
368
369   if HELPME:
370     if evt == Draw.ESCKEY:
371       HELPME = 0
372       Draw.Register(gui, event, b_event)
373       return
374     else: return
375
376   if evt == Draw.ESCKEY:
377     update_RegistryInfo()
378     Draw.Exit()
379     return
380   else: return
381
382   Draw.Register(gui, event, b_event)
383
384 def b_event(evt):
385   global ARG, SKIP_DATA, MIRCOL_AS_AMB, MIRCOL_AS_EMIS, ADD_DEFAULT_MAT
386   global HELPME
387
388   if evt == 1:
389     ADD_DEFAULT_MAT = 1 - ADD_DEFAULT_MAT
390     Draw.Redraw(1)
391   elif evt == 2:
392     SKIP_DATA = 1 - SKIP_DATA
393     Draw.Redraw(1)
394   elif evt == 3:
395     MIRCOL_AS_AMB = 1 - MIRCOL_AS_AMB
396     Draw.Redraw(1)
397   elif evt == 4:
398     MIRCOL_AS_EMIS = 1 - MIRCOL_AS_EMIS
399     Draw.Redraw(1)
400   elif evt == 10:
401     ARG = 'all'
402     Blender.Window.FileSelector(fs_callback, "AC3D Export")
403   elif evt == 11:
404     ARG = 'sel'
405     Blender.Window.FileSelector(fs_callback, "AC3D Export")
406   elif evt == 20:
407     HELPME = 1 - HELPME
408     Draw.Redraw(1)
409   elif evt == 21: # leave Help screen
410     HELPME = 0
411     Draw.Register(gui, event, b_event)
412   elif evt == 22:
413     update_RegistryInfo()
414     Draw.Exit()
415   else:
416     Draw.Register(gui, event, b_event)
417
418 def fs_callback(filename):
419   scene = Blender.Scene.GetCurrent()
420   test = AC3DExport(scene, filename)
421
422 if __script__['arg'] == 'config':
423   Draw.Register(gui, event, b_event)
424 else:
425   Blender.Window.FileSelector(fs_callback, "AC3D Export")