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