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