Scripts:
[blender.git] / release / scripts / ac3d_import.py
1 #!BPY
2
3 """ Registration info for Blender menus:
4 Name: 'AC3D (.ac)...'
5 Blender: 232
6 Group: 'Import'
7 Tip: 'Import an AC3D (.ac) file.'
8 """
9
10 __author__ = "Willian P. Germano"
11 __url__ = ("blender", "elysiun", "AC3D's homepage, http://www.ac3d.org",
12         "PLib 3d gaming lib, http://plib.sf.net")
13 __version__ = "2.34 07/26/04"
14
15 __bpydoc__ = """\
16 This script imports AC3D models into Blender.
17
18 AC3D is a simple and affordable commercial 3d modeller also built with OpenGL.
19 The .ac file format is an easy to parse text format well supported,
20 for example, by the PLib 3d gaming library (AC3D v3.x).
21
22 Supported:<br>
23     UV-textured meshes with hierarchy (grouping) information.
24
25 Missing:<br>
26     Support for AC3D 4's crease tag (simple, will be added soon).
27
28 Known issues:<br>
29     None.
30
31 Notes:<br>
32     Check script's source code for options that can be tweaked (using Blender
33 empties to emulate AC3D's grouping info and setting a default folder for
34 textures, for the case of wrong paths in the .ac file(s).
35 """
36
37 # $Id$
38 #
39 # --------------------------------------------------------------------------
40 # AC3DImport version 2.34 Jul 26, 2004
41 # Program versions: Blender 2.32+ and AC3Db files (means version 0xb)
42 # small update to allow a default path for textures, see TEXDIR below.
43 # --------------------------------------------------------------------------
44 # ***** BEGIN GPL LICENSE BLOCK *****
45 #
46 # Copyright (C) 2004: Willian P. Germano, wgermano _at_ ig.com.br
47 #
48 # This program is free software; you can redistribute it and/or
49 # modify it under the terms of the GNU General Public License
50 # as published by the Free Software Foundation; either version 2
51 # of the License, or (at your option) any later version.
52 #
53 # This program is distributed in the hope that it will be useful,
54 # but WITHOUT ANY WARRANTY; without even the implied warranty of
55 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
56 # GNU General Public License for more details.
57 #
58 # You should have received a copy of the GNU General Public License
59 # along with this program; if not, write to the Free Software Foundation,
60 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
61 #
62 # ***** END GPL LICENCE BLOCK *****
63 # --------------------------------------------------------------------------
64
65 # Note:
66 # Blender doesn't handle n-gons (polygons with more than 4 vertices):
67 #   The script triangulates them, but concave polygons come out wrong and need
68 #   fixing. Avoiding or triangulating concave n-gons in AC3D is a simple way to
69 #   avoid problems.
70
71 # Default folder for AC3D textures, to override wrong paths, change to your
72 # liking or leave as "":
73 TEXDIR = ""
74
75 # Set 'GROUP' to 1 to make Blender group imported objects using Empties,
76 # to reproduce the object hierarchy in the .ac file
77 GROUP = 0
78
79 import Blender
80
81 if TEXDIR:
82     TEXDIR = TEXDIR.replace('\\','/')
83     if TEXDIR[-1] != '/': TEXDIR += '/'
84
85 errmsg = ""
86
87 class Obj:
88     
89     def __init__(self, type):
90         self.type = type
91         self.dad = None
92         self.name = ''
93         self.data = ''
94         self.tex = ''
95         self.texrep = [1,1]
96         self.texoff = None
97         self.loc = [0, 0, 0]
98         self.rot = []
99         self.vlist = []
100         self.flist = []
101         self.matlist = []
102         self.kids = 0
103
104 class AC3DImport:
105
106     def __init__(self, filename):
107
108         global errmsg
109
110         print "Trying to import AC3D model(s) from %s ..." % filename
111
112         self.i = 0
113         errmsg = ''
114         self.importdir = Blender.sys.dirname(filename)
115         try:
116             file = open(filename, 'r')
117         except IOError, (errno, strerror):
118             errmsg = "IOError #%s: %s" % (errno, strerror)
119             print errmsg
120             return None
121         header = file.read(5)
122         header, version = header[:4], header[-1]
123         if header != 'AC3D':
124             file.close()
125             errmsg = 'Invalid file -- AC3D header not found.'
126             print errmsg
127             return None
128         elif version != 'b':
129             print 'AC3D file version 0x%s.' % version
130             print 'This importer is for version 0xb, so it may fail.' 
131
132         self.token = {'OBJECT':     self.parse_obj,
133                       'numvert':    self.parse_vert,
134                       'numsurf':    self.parse_surf,
135                       'name':       self.parse_name,
136                       'data':       self.parse_data,
137                       'kids':       self.parse_kids,
138                       'loc':        self.parse_loc,
139                       'rot':        self.parse_rot,
140                       'MATERIAL':   self.parse_mat,
141                       'texture':    self.parse_tex,
142                       'texrep':     self.parse_texrep,
143                       'texoff':     self.parse_texoff}
144
145         self.objlist = []
146         self.mlist = []
147         self.dads = []
148         self.kids = []
149         self.dad = None
150
151         self.lines = file.readlines()
152         self.lines.append('')
153         self.parse_file()
154         file.close()
155         
156         self.testAC3DImport()
157                 
158     def parse_obj(self, value):
159         if self.kids:
160             while not self.kids[-1]:
161                 self.kids.pop()
162                 self.dad = self.dad.dad
163             self.kids[-1] -= 1
164         new = Obj(value)
165         new.dad = self.dad
166         new.name = value
167         self.objlist.append(new)
168
169     def parse_kids(self, value):
170         kids = int(value)
171         if kids:
172             self.kids.append(kids)
173             self.dad = self.objlist[-1]
174         self.objlist[-1].kids = kids
175
176     def parse_name(self, value):
177         name = value.split('"')[1]
178         self.objlist[-1].name = name
179
180     def parse_data(self, value):
181         data = self.lines[self.i].strip()
182         self.objlist[-1].data = data
183
184     def parse_tex(self, value):
185         texture = value.split('"')[1]
186         self.objlist[-1].tex = texture
187
188     def parse_texrep(self, trash):
189         trep = self.lines[self.i - 1]
190         trep = trep.split()
191         trep = [float(trep[1]), float(trep[2])]
192         self.objlist[-1].texrep = trep
193         self.objlist[-1].texoff = [0, 0]
194
195     def parse_texoff(self, trash):
196         toff = self.lines[self.i - 1]
197         toff = toff.split()
198         toff = [float(toff[1]), float(toff[2])]
199         self.objlist[-1].texoff = toff
200         
201     def parse_mat(self, value):
202         i = self.i - 1
203         lines = self.lines
204         line = lines[i].split()
205         mat_name = ''
206         mat_col = mat_spec_col = [0,0,0]
207         mat_alpha = 1
208
209         while line[0] == 'MATERIAL':
210             mat_name = line[1].split('"')[1]
211             mat_col = map(float,[line[3],line[4],line[5]])
212             mat_spec_col = map(float,[line[15],line[16],line[17]])
213             mat_alpha = float(line[-1])
214             mat_alpha = 1 - mat_alpha
215             self.mlist.append([mat_name, mat_col, mat_spec_col, mat_alpha])
216             i += 1
217             line = lines[i].split()
218
219         self.i = i
220
221     def parse_rot(self, trash):
222         i = self.i - 1
223         rot = self.lines[i].split(' ', 1)[1]
224         rot = map(float, rot.split())
225         self.objlist[-1].rot = rot
226
227     def parse_loc(self, trash):
228         i = self.i - 1
229         loc = self.lines[i].split(' ', 1)[1]
230         loc = map(float, loc.split())
231         self.objlist[-1].loc = loc
232
233     def parse_vert(self, value):
234         i = self.i
235         lines = self.lines
236         obj = self.objlist[-1]
237         vlist = obj.vlist
238         n = int(value)
239
240         while n:
241             line = lines[i].split()
242             line = map(float, line)
243             vlist.append(line)
244             n -= 1
245             i += 1
246
247         self.i = i
248
249         rot = obj.rot
250         if rot:
251             nv = len(vlist)
252             for j in range(nv):
253                 v = vlist[j]
254                 t = [0,0,0]
255                 t[0] = rot[0]*v[0] + rot[3]*v[1] + rot[6]*v[2]
256                 t[1] = rot[1]*v[0] + rot[4]*v[1] + rot[7]*v[2]
257                 t[2] = rot[2]*v[0] + rot[5]*v[1] + rot[8]*v[2]
258                 vlist[j] = t
259
260         loc = obj.loc
261         dad = obj.dad
262         while dad:
263             for j in [0, 1, 2]:
264                 loc[j] += dad.loc[j]
265             dad = dad.dad
266
267         for v in vlist:
268             for j in [0, 1, 2]:
269                 v[j] += loc[j]
270
271     def parse_surf(self, value):
272         i = self.i
273         is_smooth = 0
274         double_sided = 0
275         lines = self.lines
276         obj = self.objlist[-1]
277         matlist = obj.matlist
278         numsurf = int(value)
279
280         while numsurf:
281             flags = lines[i].split()
282             flaglow = 0
283             if len(flags[1]) > 3: flaglow = int(flags[1][3])
284             flaghigh = int(flags[1][2])
285             is_smooth = flaghigh & 1
286             twoside = flaghigh & 2
287             mat = lines[i+1].split()
288             mat = int(mat[1])
289             if not mat in matlist: matlist.append(mat)
290             refs = lines[i+2].split()
291             refs = int(refs[1])
292             i += 3
293             face = []
294             faces = []
295             fuv = []
296             rfs = refs
297
298             while rfs:
299                 line = lines[i].split()
300                 v = int(line[0])
301                 uv = [float(line[1]), float(line[2])]
302                 face.append([v, uv])
303                 rfs -= 1
304                 i += 1
305                 
306             if flaglow:
307                 while len(face) >= 2:
308                     cut = face[:2]
309                     faces.append(cut)
310                     face = face[1:]
311
312                 if flaglow == 1:
313                     face = [faces[-1][-1], faces[0][0]]
314                     faces.append(face)
315
316             else:
317                 while len(face) > 4:
318                     cut = face[:4]
319                     face = face[3:]
320                     face.insert(0, cut[0])
321                     faces.append(cut)        
322
323                 faces.append(face)
324
325             for f in faces:
326                 f.append(mat)
327                 f.append(is_smooth)
328                 f.append(twoside)
329                 self.objlist[-1].flist.append(f)
330
331             numsurf -= 1      
332
333                             
334         self.i = i
335
336     def parse_file(self):
337         i = 1
338         lines = self.lines
339         line = lines[i].split()
340
341         while line:
342             kw = ''
343             for k in self.token.keys():
344                 if line[0] == k:
345                     kw = k
346                     break
347             i += 1
348             if kw:
349                 self.i = i
350                 self.token[kw](line[1])
351                 i = self.i
352             line = lines[i].split()
353
354     def testAC3DImport(self):
355         global GROUP
356         scene = Blender.Scene.GetCurrent()
357
358         bmat = []
359         for mat in self.mlist:
360             name = mat[0]
361             m = Blender.Material.New(name)
362             m.rgbCol = (mat[1][0], mat[1][1], mat[1][2])
363             m.specCol = (mat[2][0], mat[2][1], mat[2][2])
364             m.alpha = mat[3]
365             bmat.append(m)
366
367         for obj in self.objlist:
368             if obj.type == 'world':
369                 continue
370             elif obj.type == 'group':
371                 if not GROUP: continue
372                 empty = Blender.Object.New('Empty')
373                 empty.name = obj.name
374                 scene.link(empty)
375                 if self.dads:
376                     dadobj = Blender.Object.get(self.dads.pop())
377                     dadobj.makeParent([empty])
378                 while obj.kids:
379                     self.dads.append(empty.name)
380                     obj.kids -= 1
381                 continue
382             mesh = Blender.NMesh.New()
383             if (obj.data): mesh.name = obj.data
384             mesh.hasFaceUV(1)
385
386             tex = None
387             if obj.tex != '':
388                 try:
389                     tex = Blender.Image.Load(obj.tex)
390                     # Commented because it's unnecessary:
391                     #tex.xrep = int(obj.texrep[0])
392                     #tex.yrep = int(obj.texrep[1])
393                 except:
394                     basetexname = Blender.sys.basename(obj.tex)
395                     try:
396                         obj.tex = self.importdir + '/' + basetexname
397                         tex = Blender.Image.Load(obj.tex)
398                     except:
399                         try:
400                             obj.tex = TEXDIR + basetexname
401                             tex = Blender.Image.Load(obj.tex)
402                         except:
403                             print "Couldn't load texture: %s" % basetexname
404
405             for v in obj.vlist:
406                 bvert = Blender.NMesh.Vert(v[0],v[1],v[2])
407                 mesh.verts.append(bvert)
408
409             objmat_indices = []
410             for mat in bmat:
411                 if bmat.index(mat) in obj.matlist:
412                     objmat_indices.append(bmat.index(mat))
413                     mesh.materials.append(mat)
414             for f in obj.flist:
415                 twoside = f[-1]
416                 is_smooth = f[-2]
417                 fmat = f[-3]
418                 f=f[:-3]
419                 bface = Blender.NMesh.Face()
420                 bface.smooth = is_smooth
421                 if twoside: bface.mode |= Blender.NMesh.FaceModes['TWOSIDE']
422                 if tex:
423                     bface.mode |= Blender.NMesh.FaceModes['TEX']
424                     bface.image = tex
425                 bface.materialIndex = objmat_indices.index(fmat)
426                 if obj.texoff:
427                     uoff = obj.texoff[0]
428                     voff = obj.texoff[1]
429                     urep = obj.texrep[0]
430                     vrep = obj.texrep[1]
431                     for vi in range(len(f)):
432                         f[vi][1][0] *= urep
433                         f[vi][1][1] *= vrep
434                         f[vi][1][0] += uoff
435                         f[vi][1][1] += voff
436
437                 for vi in range(len(f)):
438                     bface.v.append(mesh.verts[f[vi][0]])
439                     bface.uv.append((f[vi][1][0], f[vi][1][1]))
440                 mesh.faces.append(bface)
441
442             mesh.mode = 0
443             object = Blender.NMesh.PutRaw(mesh)
444             object.setName(obj.name)
445             object.setEuler([1.5707963,0,0]) # align ac3d w/ Blender
446             if self.dads:
447                 dadobj = Blender.Object.get(self.dads.pop())
448                 dadobj.makeParent([object])
449
450         print '...done!'
451
452 # End of class AC3DImport
453
454 def filesel_callback(filename):
455   test = AC3DImport(filename)
456
457 Blender.Window.FileSelector(filesel_callback, "Import AC3D", "*.ac")