fix BGE bug #8668: Behavior of os.getcwd() is not consistent between operating systems
[blender-staging.git] / release / scripts / ac3d_export.py
1 #!BPY
2
3 """ Registration info for Blender menus:
4 Name: 'AC3D (.ac)...'
5 Blender: 243
6 Group: 'Export'
7 Tip: 'Export selected meshes to AC3D (.ac) format'
8 """
9
10 __author__ = "Willian P. Germano"
11 __url__ = ("blender", "blenderartists.org", "AC3D's homepage, http://www.ac3d.org",
12         "PLib 3d gaming lib, http://plib.sf.net")
13 __version__ = "2.44 2007-05-05"
14
15 __bpydoc__ = """\
16 This script exports selected Blender meshes to AC3D's .ac file format.
17
18 AC3D is a simple 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 3.x).
21
22 Supported:<br>
23     UV-textured meshes with hierarchy (grouping) information.
24
25 Missing:<br>
26     The 'url' tag, specific to AC3D.  It is easy to add by hand to the exported
27 file, if needed.
28
29 Known issues:<br>
30     The ambient and emit data we can retrieve from Blender are single values,
31 that this script copies to R, G, B, giving shades of gray.<br>
32     Loose edges (lines) receive the first material found in the mesh, if any, or a default white material.<br>
33     In AC3D 4 "compatibility mode":<br>
34     - shininess of materials is taken from the shader specularity value in Blender, mapped from [0.0, 2.0] to [0, 128];<br>
35     - crease angle is exported, but in Blender it is limited to [1, 80], since there are other more powerful ways to control surface smoothing.  In AC3D 4.0 crease's range is [0.0, 180.0];
36
37 Config Options:<br>
38     toggle:<br>
39     - AC3D 4 mode: unset it to export without the 'crease' tag that was
40 introduced with AC3D 4.0 and with the old material handling;<br>
41     - global coords: transform all vertices of all meshes to global coordinates;<br>
42     - skip data: set it if you don't want mesh names (ME:, not OB: field)
43 to be exported as strings for AC's "data" tags (19 chars max);<br>
44     - rgb mirror color can be exported as ambient and/or emissive if needed,
45 since Blender handles these differently;<br>
46     - default mat: a default (white) material is added if some mesh was
47 left without mats -- it's better to always add your own materials;<br>
48     - no split: don't split meshes (see above);<br>
49     - set texture dir: override the actual textures path with a given default
50 path (or simply export the texture names, without dir info, if the path is
51 empty);<br>
52     - per face 1 or 2 sided: override the "Double Sided" button that defines this behavior per whole mesh in favor of the UV Face Select mode "twosided" per face atribute;<br>
53     - only selected: only consider selected objects when looking for meshes
54 to export (read notes below about tokens, too);<br>
55     strings:<br>
56     - export dir: default dir to export to;<br>
57     - texture dir: override textures path with this path if 'set texture dir'
58 toggle is "on".
59
60 Notes:<br>
61         This version updates:<br>
62     - modified meshes are correctly exported, no need to apply the modifiers in Blender;<br>
63     - correctly export each used material, be it assigned to the object or to its mesh data;<br>
64     - exporting lines (edges) is again supported; color comes from first material found in the mesh, if any, or a default white one.<br>
65     - there's a new option to choose between exporting meshes with transformed (global) coordinates or local ones;<br>
66     Multiple textures per mesh are supported (mesh gets split);<br>
67         Parents are exported as a group containing both the parent and its children;<br>
68     Start mesh object names (OB: field) with "!" or "#" if you don't want them to be exported;<br>
69     Start mesh object names (OB: field) with "=" or "$" to prevent them from being split (meshes with multiple textures or both textured and non textured faces are split unless this trick is used or the "no split" option is set.
70 """
71
72 # $Id$
73 #
74 # --------------------------------------------------------------------------
75 # AC3DExport version 2.44
76 # Program versions: Blender 2.42+ and AC3Db files (means version 0xb)
77 # new: updated for new Blender version and Mesh module; supports lines (edges) again;
78 # option to export vertices transformed to global coordinates or not; now the modified
79 # (by existing mesh modifiers) mesh is exported; materials are properly exported, no
80 # matter if each of them is linked to the mesh or to the object. New (2.43.1): loose
81 # edges use color of first material found in the mesh, if any.
82 # --------------------------------------------------------------------------
83 # Thanks: Steve Baker for discussions and inspiration; for testing, bug
84 # reports, suggestions, patches: David Megginson, Filippo di Natale,
85 # Franz Melchior, Campbell Barton, Josh Babcock, Ralf Gerlich, Stewart Andreason.
86 # --------------------------------------------------------------------------
87 # ***** BEGIN GPL LICENSE BLOCK *****
88 #
89 # Copyright (C) 2004-2007: Willian P. Germano, wgermano _at_ ig.com.br
90 #
91 # This program is free software; you can redistribute it and/or
92 # modify it under the terms of the GNU General Public License
93 # as published by the Free Software Foundation; either version 2
94 # of the License, or (at your option) any later version.
95 #
96 # This program is distributed in the hope that it will be useful,
97 # but WITHOUT ANY WARRANTY; without even the implied warranty of
98 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
99 # GNU General Public License for more details.
100 #
101 # You should have received a copy of the GNU General Public License
102 # along with this program; if not, write to the Free Software Foundation,
103 # --------------------------------------------------------------------------
104
105 import Blender
106 from Blender import Object, Mesh, Material, Image, Mathutils, Registry
107 from Blender import sys as bsys
108
109 # Globals
110 REPORT_DATA = {
111         'main': [],
112         'errors': [],
113         'warns': [],
114         'nosplit': [],
115         'noexport': []
116 }
117 TOKENS_DONT_EXPORT = ['!', '#']
118 TOKENS_DONT_SPLIT  = ['=', '$']
119
120 MATIDX_ERROR = 0
121
122 # flags:
123 LOOSE = Mesh.EdgeFlags['LOOSE']
124 FACE_TWOSIDED = Mesh.FaceModes['TWOSIDE']
125 MESH_TWOSIDED = Mesh.Modes['TWOSIDED']
126
127 REG_KEY = 'ac3d_export'
128
129 # config options:
130 GLOBAL_COORDS = True
131 SKIP_DATA = False
132 MIRCOL_AS_AMB = False
133 MIRCOL_AS_EMIS = False
134 ADD_DEFAULT_MAT = True
135 SET_TEX_DIR = True
136 TEX_DIR = ''
137 AC3D_4 = True # export crease value, compatible with AC3D 4 loaders
138 NO_SPLIT = False
139 ONLY_SELECTED = True
140 EXPORT_DIR = ''
141 PER_FACE_1_OR_2_SIDED = True
142
143 tooltips = {
144         'GLOBAL_COORDS': "transform all vertices of all meshes to global coordinates",
145         'SKIP_DATA': "don't export mesh names as data fields",
146         'MIRCOL_AS_AMB': "export mirror color as ambient color",
147         'MIRCOL_AS_EMIS': "export mirror color as emissive color",
148         'ADD_DEFAULT_MAT': "always add a default white material",
149         'SET_TEX_DIR': "don't export default texture paths (edit also \"tex dir\")",
150         'EXPORT_DIR': "default / last folder used to export .ac files to",
151         'TEX_DIR': "(see \"set tex dir\") dir to prepend to all exported texture names (leave empty for no dir)",
152         'AC3D_4': "compatibility mode, adds 'crease' tag and slightly better material support",
153         'NO_SPLIT': "don't split meshes with multiple textures (or both textured and non textured polygons)",
154         'ONLY_SELECTED': "export only selected objects",
155         'PER_FACE_1_OR_2_SIDED': "override \"Double Sided\" button in favor of per face \"twosided\" attribute (UV Face Select mode)"
156 }
157
158 def update_RegistryInfo():
159         d = {}
160         d['SKIP_DATA'] = SKIP_DATA
161         d['MIRCOL_AS_AMB'] = MIRCOL_AS_AMB
162         d['MIRCOL_AS_EMIS'] = MIRCOL_AS_EMIS
163         d['ADD_DEFAULT_MAT'] = ADD_DEFAULT_MAT
164         d['SET_TEX_DIR'] = SET_TEX_DIR
165         d['TEX_DIR'] = TEX_DIR
166         d['AC3D_4'] = AC3D_4
167         d['NO_SPLIT'] = NO_SPLIT
168         d['EXPORT_DIR'] = EXPORT_DIR
169         d['ONLY_SELECTED'] = ONLY_SELECTED
170         d['PER_FACE_1_OR_2_SIDED'] = PER_FACE_1_OR_2_SIDED
171         d['tooltips'] = tooltips
172         d['GLOBAL_COORDS'] = GLOBAL_COORDS
173         Registry.SetKey(REG_KEY, d, True)
174
175 # Looking for a saved key in Blender.Registry dict:
176 rd = Registry.GetKey(REG_KEY, True)
177
178 if rd:
179         try:
180                 AC3D_4 = rd['AC3D_4']
181                 SKIP_DATA = rd['SKIP_DATA']
182                 MIRCOL_AS_AMB = rd['MIRCOL_AS_AMB']
183                 MIRCOL_AS_EMIS = rd['MIRCOL_AS_EMIS']
184                 ADD_DEFAULT_MAT = rd['ADD_DEFAULT_MAT']
185                 SET_TEX_DIR = rd['SET_TEX_DIR']
186                 TEX_DIR = rd['TEX_DIR']
187                 EXPORT_DIR = rd['EXPORT_DIR']
188                 ONLY_SELECTED = rd['ONLY_SELECTED']
189                 NO_SPLIT = rd['NO_SPLIT']
190                 PER_FACE_1_OR_2_SIDED = rd['PER_FACE_1_OR_2_SIDED']
191                 GLOBAL_COORDS = rd['GLOBAL_COORDS']
192         except KeyError: update_RegistryInfo()
193
194 else:
195         update_RegistryInfo()
196
197 VERBOSE = True
198 CONFIRM_OVERWRITE = True
199
200 # check General scripts config key for default behaviors
201 rd = Registry.GetKey('General', True)
202 if rd:
203         try:
204                 VERBOSE = rd['verbose']
205                 CONFIRM_OVERWRITE = rd['confirm_overwrite']
206         except: pass
207
208
209 # The default material to be used when necessary (see ADD_DEFAULT_MAT)
210 DEFAULT_MAT = \
211 'MATERIAL "DefaultWhite" rgb 1 1 1  amb 1 1 1  emis 0 0 0  \
212 spec 0.5 0.5 0.5  shi 64  trans 0'
213
214 # This transformation aligns Blender and AC3D coordinate systems:
215 BLEND_TO_AC3D_MATRIX = Mathutils.Matrix([1,0,0,0], [0,0,-1,0], [0,1,0,0], [0,0,0,1])
216
217 def Round_s(f):
218         "Round to default precision and turn value to a string"
219         r = round(f,6) # precision set to 10e-06
220         if r == int(r):
221                 return str(int(r))
222         else:
223                 return str(r)
224  
225 def transform_verts(verts, m):
226         vecs = []
227         for v in verts:
228                 x, y, z = v.co
229                 vec = Mathutils.Vector([x, y, z, 1])
230                 vecs.append(vec*m)
231         return vecs
232
233 def get_loose_edges(mesh):
234         loose = LOOSE
235         return [e for e in mesh.edges if e.flag & loose]
236
237 # ---
238
239 # meshes with more than one texture assigned
240 # are split and saved as these foomeshes
241 class FooMesh:
242
243         class FooVert:
244                 def __init__(self, v):
245                         self.v = v
246                         self.index = 0
247
248         class FooFace:
249                 def __init__(self, foomesh, f):
250                         self.f = f
251                         foov = foomesh.FooVert
252                         self.v = [foov(f.v[0]), foov(f.v[1])]
253                         len_fv = len(f.v)
254                         if len_fv > 2 and f.v[2]:
255                                 self.v.append(foov(f.v[2]))
256                                 if len_fv > 3 and f.v[3]: self.v.append(foov(f.v[3]))
257
258                 def __getattr__(self, attr):
259                         if attr == 'v': return self.v
260                         return getattr(self.f, attr)
261
262                 def __len__(self):
263                         return len(self.f)
264
265         def __init__(self, tex, faces, mesh):
266                 self.name = mesh.name
267                 self.mesh = mesh
268                 self.looseEdges = []
269                 self.faceUV = mesh.faceUV
270                 self.degr = mesh.degr
271                 vidxs = [0]*len(mesh.verts)
272                 foofaces = []
273                 for f in faces:
274                         foofaces.append(self.FooFace(self, f))
275                         for v in f.v:
276                                 if v: vidxs[v.index] = 1
277                 i = 0
278                 fooverts = []
279                 for v in mesh.verts:
280                         if vidxs[v.index]:
281                                 fooverts.append(v)
282                                 vidxs[v.index] = i
283                                 i += 1
284                 for f in foofaces:
285                         for v in f.v:
286                                 if v: v.index = vidxs[v.v.index]
287                 self.faces = foofaces
288                 self.verts = fooverts
289
290
291 class AC3DExport: # the ac3d exporter part
292
293         def __init__(self, scene_objects, file):
294
295                 global ARG, SKIP_DATA, ADD_DEFAULT_MAT, DEFAULT_MAT
296
297                 header = 'AC3Db'
298                 self.file = file
299                 self.buf = ''
300                 self.mbuf = []
301                 self.mlist = []
302                 world_kids = 0
303                 parents_list = self.parents_list = []
304                 kids_dict = self.kids_dict = {}
305                 objs = []
306                 exp_objs = self.exp_objs = []
307                 tree = {}
308
309                 file.write(header+'\n')
310
311                 objs = \
312                         [o for o in scene_objects if o.type in ['Mesh', 'Empty']]
313
314                 # create a tree from parents to children objects
315
316                 for obj in objs[:]:
317                         parent = obj.parent
318                         lineage = [obj]
319
320                         while parent:
321                                 parents_list.append(parent.name)
322                                 obj = parent
323                                 parent = parent.getParent()
324                                 lineage.insert(0, obj)
325
326                         d = tree
327                         for i in xrange(len(lineage)):
328                                 lname = lineage[i].getType()[:2] + lineage[i].name
329                                 if lname not in d.keys():
330                                         d[lname] = {}
331                                 d = d[lname]
332
333                 # traverse the tree to get an ordered list of names of objects to export
334                 self.traverse_dict(tree)
335
336                 world_kids = len(tree.keys())
337
338                 # get list of objects to export, start writing the .ac file
339
340                 objlist = [Object.Get(name) for name in exp_objs]
341
342                 meshlist = [o for o in objlist if o.type == 'Mesh']
343
344                 # create a temporary mesh to hold actual (modified) mesh data
345                 TMP_mesh = Mesh.New('tmp_for_ac_export')
346
347                 # write materials
348
349                 self.MATERIALS(meshlist, TMP_mesh)
350                 mbuf = self.mbuf
351                 if not mbuf or ADD_DEFAULT_MAT:
352                         mbuf.insert(0, "%s\n" % DEFAULT_MAT)
353                 mbuf = "".join(mbuf)
354                 file.write(mbuf)
355
356                 file.write('OBJECT world\nkids %s\n' % world_kids)
357
358                 # write the objects
359
360                 for obj in objlist:
361                         self.obj = obj
362
363                         objtype = obj.type
364                         objname = obj.name
365                         kidsnum = kids_dict[objname]
366
367                         # A parent plus its children are exported as a group.
368                         # If the parent is a mesh, its rot and loc are exported as the
369                         # group rot and loc and the mesh (w/o rot and loc) is added to the group.
370                         if kidsnum:
371                                 self.OBJECT('group')
372                                 self.name(objname)
373                                 if objtype == 'Mesh':
374                                         kidsnum += 1
375                                 if not GLOBAL_COORDS:
376                                         localmatrix = obj.getMatrix('localspace')
377                                         if not obj.getParent():
378                                                 localmatrix *= BLEND_TO_AC3D_MATRIX
379                                         self.rot(localmatrix.rotationPart()) 
380                                         self.loc(localmatrix.translationPart())
381                                 self.kids(kidsnum)
382
383                         if objtype == 'Mesh':
384                                 mesh = TMP_mesh # temporary mesh to hold actual (modified) mesh data
385                                 mesh.getFromObject(objname)
386                                 self.mesh = mesh
387                                 if mesh.faceUV:
388                                         meshes = self.split_mesh(mesh)
389                                 else:
390                                         meshes = [mesh]
391                                 if len(meshes) > 1:
392                                         if NO_SPLIT or self.dont_split(objname):
393                                                 self.export_mesh(mesh, ob)
394                                                 REPORT_DATA['nosplit'].append(objname)
395                                         else:
396                                                 self.OBJECT('group')
397                                                 self.name(objname)
398                                                 self.kids(len(meshes))
399                                                 counter = 0
400                                                 for me in meshes:
401                                                         self.export_mesh(me, obj,
402                                                                 name = '%s_%s' % (obj.name, counter), foomesh = True)
403                                                         self.kids()
404                                                         counter += 1
405                                 else:
406                                         self.export_mesh(mesh, obj)
407                                         self.kids()
408
409
410         def traverse_dict(self, d):
411                 kids_dict = self.kids_dict
412                 exp_objs = self.exp_objs
413                 keys = d.keys()
414                 keys.sort() # sort for predictable output
415                 keys.reverse()
416                 for k in keys:
417                         objname = k[2:]
418                         klen = len(d[k])
419                         kids_dict[objname] = klen
420                         if self.dont_export(objname):
421                                 d.pop(k)
422                                 parent = Object.Get(objname).getParent()
423                                 if parent: kids_dict[parent.name] -= 1
424                                 REPORT_DATA['noexport'].append(objname)
425                                 continue
426                         if klen:
427                                 self.traverse_dict(d[k])
428                                 exp_objs.insert(0, objname)
429                         else:
430                                 if k.find('Em', 0) == 0: # Empty w/o children
431                                         d.pop(k)
432                                         parent = Object.Get(objname).getParent()
433                                         if parent: kids_dict[parent.name] -= 1
434                                 else:
435                                         exp_objs.insert(0, objname)
436
437         def dont_export(self, name): # if name starts with '!' or '#'
438                 length = len(name)
439                 if length >= 1:
440                         if name[0] in TOKENS_DONT_EXPORT: # '!' or '#' doubled (escaped): export
441                                 if length > 1 and name[1] == name[0]:
442                                         return 0
443                                 return 1
444
445         def dont_split(self, name): # if name starts with '=' or '$'
446                 length = len(name)
447                 if length >= 1:
448                         if name[0] in TOKENS_DONT_SPLIT: # '=' or '$' doubled (escaped): split
449                                 if length > 1 and name[1] == name[0]:
450                                         return 0
451                                 return 1
452
453         def split_mesh(self, mesh):
454                 tex_dict = {0:[]}
455                 for f in mesh.faces:
456                         if f.image:
457                                 if not f.image.name in tex_dict: tex_dict[f.image.name] = []
458                                 tex_dict[f.image.name].append(f)
459                         else: tex_dict[0].append(f)
460                 keys = tex_dict.keys()
461                 len_keys = len(keys)
462                 if not tex_dict[0]:
463                         len_keys -= 1
464                         tex_dict.pop(0)
465                         keys.remove(0)
466                 elif len_keys > 1:
467                         lines = []
468                         anyimgkey = [k for k in keys if k != 0][0]
469                         for f in tex_dict[0]:
470                                 if len(f.v) < 3:
471                                         lines.append(f)
472                         if len(tex_dict[0]) == len(lines):
473                                 for l in lines:
474                                         tex_dict[anyimgkey].append(l)
475                                 len_keys -= 1
476                                 tex_dict.pop(0)
477                 if len_keys > 1:
478                         foo_meshes = []
479                         for k in keys:
480                                 faces = tex_dict[k]
481                                 foo_meshes.append(FooMesh(k, faces, mesh))
482                         foo_meshes[0].edges = get_loose_edges(mesh)
483                         return foo_meshes
484                 return [mesh]
485
486         def export_mesh(self, mesh, obj, name = None, foomesh = False):
487                 file = self.file
488                 self.OBJECT('poly')
489                 if not name: name = obj.name
490                 self.name(name)
491                 if not SKIP_DATA:
492                         meshname = obj.getData(name_only = True)
493                         self.data(len(meshname), meshname)
494                 if mesh.faceUV:
495                         texline = self.texture(mesh.faces)
496                         if texline: file.write(texline)
497                 if AC3D_4:
498                         self.crease(mesh.degr)
499
500                 # If exporting using local coordinates, children object coordinates should not be
501                 # transformed to ac3d's coordinate system, since that will be accounted for in
502                 # their topmost parents (the parents w/o parents) transformations.
503                 if not GLOBAL_COORDS:
504                         # We hold parents in a list, so they also don't get transformed,
505                         # because for each parent we create an ac3d group to hold both the
506                         # parent and its children.
507                         if obj.name not in self.parents_list:
508                                 localmatrix = obj.getMatrix('localspace')
509                                 if not obj.getParent():
510                                         localmatrix *= BLEND_TO_AC3D_MATRIX
511                                 self.rot(localmatrix.rotationPart())
512                                 self.loc(localmatrix.translationPart())
513                         matrix = None
514                 else:
515                         matrix = obj.getMatrix() * BLEND_TO_AC3D_MATRIX
516
517                 self.numvert(mesh.verts, matrix)
518                 self.numsurf(mesh, foomesh)
519
520         def MATERIALS(self, meshlist, me):
521                 for meobj in meshlist:
522                         me.getFromObject(meobj)
523                         mats = me.materials
524                         mbuf = []
525                         mlist = self.mlist
526                         for m in mats:
527                                 if not m: continue
528                                 name = m.name
529                                 if name not in mlist:
530                                         mlist.append(name)
531                                         M = Material.Get(name)
532                                         material = 'MATERIAL "%s"' % name
533                                         mirCol = "%s %s %s" % (Round_s(M.mirCol[0]), Round_s(M.mirCol[1]),
534                                                 Round_s(M.mirCol[2]))
535                                         rgb = "rgb %s %s %s" % (Round_s(M.R), Round_s(M.G), Round_s(M.B))
536                                         ambval = Round_s(M.amb)
537                                         amb = "amb %s %s %s" % (ambval, ambval, ambval)
538                                         spec = "spec %s %s %s" % (Round_s(M.specCol[0]),
539                                                  Round_s(M.specCol[1]), Round_s(M.specCol[2]))
540                                         if AC3D_4:
541                                                 emit = Round_s(M.emit)
542                                                 emis = "emis %s %s %s" % (emit, emit, emit)
543                                                 shival = int(M.spec * 64)
544                                         else:
545                                                 emis = "emis 0 0 0"
546                                                 shival = 72
547                                         shi = "shi %s" % shival
548                                         trans = "trans %s" % (Round_s(1 - M.alpha))
549                                         if MIRCOL_AS_AMB:
550                                                 amb = "amb %s" % mirCol 
551                                         if MIRCOL_AS_EMIS:
552                                                 emis = "emis %s" % mirCol
553                                         mbuf.append("%s %s %s %s %s %s %s\n" \
554                                                 % (material, rgb, amb, emis, spec, shi, trans))
555                         self.mlist = mlist
556                         self.mbuf.append("".join(mbuf))
557
558         def OBJECT(self, type):
559                 self.file.write('OBJECT %s\n' % type)
560
561         def name(self, name):
562                 if name[0] in TOKENS_DONT_EXPORT or name[0] in TOKENS_DONT_SPLIT:
563                         if len(name) > 1: name = name[1:]
564                 self.file.write('name "%s"\n' % name)
565
566         def kids(self, num = 0):
567                 self.file.write('kids %s\n' % num)
568
569         def data(self, num, str):
570                 self.file.write('data %s\n%s\n' % (num, str))
571
572         def texture(self, faces):
573                 tex = ""
574                 for f in faces:
575                         if f.image:
576                                 tex = f.image.name
577                                 break
578                 if tex:
579                         image = Image.Get(tex)
580                         texfname = image.filename
581                         if SET_TEX_DIR:
582                                 texfname = bsys.basename(texfname)
583                                 if TEX_DIR:
584                                         texfname = bsys.join(TEX_DIR, texfname)
585                         buf = 'texture "%s"\n' % texfname
586                         xrep = image.xrep
587                         yrep = image.yrep
588                         buf += 'texrep %s %s\n' % (xrep, yrep)
589                         self.file.write(buf)
590
591         def rot(self, matrix):
592                 rot = ''
593                 not_I = 0 # not identity
594                 matstr = []
595                 for i in [0, 1, 2]:
596                         r = map(Round_s, matrix[i])
597                         not_I += (r[0] != '0')+(r[1] != '0')+(r[2] != '0')
598                         not_I -= (r[i] == '1')
599                         for j in [0, 1, 2]:
600                                 matstr.append(' %s' % r[j])
601                 if not_I: # no need to write identity
602                         self.file.write('rot%s\n' % "".join(matstr))
603                                 
604         def loc(self, loc):
605                 loc = map(Round_s, loc)
606                 if loc != ['0', '0', '0']: # no need to write default
607                         self.file.write('loc %s %s %s\n' % (loc[0], loc[1], loc[2]))
608
609         def crease(self, crease):
610                 self.file.write('crease %f\n' % crease)
611
612         def numvert(self, verts, matrix):
613                 file = self.file
614                 nvstr = []
615                 nvstr.append("numvert %s\n" % len(verts))
616
617                 if matrix:
618                         verts = transform_verts(verts, matrix)
619                         for v in verts:
620                                 v = map (Round_s, v)
621                                 nvstr.append("%s %s %s\n" % (v[0], v[1], v[2]))
622                 else:
623                         for v in verts:
624                                 v = map(Round_s, v.co)
625                                 nvstr.append("%s %s %s\n" % (v[0], v[1], v[2]))
626
627                 file.write("".join(nvstr))
628
629         def numsurf(self, mesh, foomesh = False):
630
631                 global MATIDX_ERROR
632
633                 # local vars are faster and so better in tight loops
634                 lc_ADD_DEFAULT_MAT = ADD_DEFAULT_MAT
635                 lc_MATIDX_ERROR = MATIDX_ERROR
636                 lc_PER_FACE_1_OR_2_SIDED = PER_FACE_1_OR_2_SIDED
637                 lc_FACE_TWOSIDED = FACE_TWOSIDED
638                 lc_MESH_TWOSIDED = MESH_TWOSIDED
639
640                 faces = mesh.faces
641                 hasFaceUV = mesh.faceUV
642                 if foomesh:
643                         looseEdges = mesh.looseEdges
644                 else:
645                         looseEdges = get_loose_edges(mesh)
646
647                 file = self.file
648  
649                 file.write("numsurf %s\n" % (len(faces) + len(looseEdges)))
650
651                 if not foomesh: verts = list(self.mesh.verts)
652
653                 materials = self.mesh.materials
654                 mlist = self.mlist
655                 matidx_error_reported = False
656                 objmats = []
657                 for omat in materials:
658                         if omat: objmats.append(omat.name)
659                         else: objmats.append(None)
660                 for f in faces:
661                         if not objmats:
662                                 m_idx = 0
663                         elif objmats[f.mat] in mlist:
664                                 m_idx = mlist.index(objmats[f.mat])
665                         else:
666                                 if not lc_MATIDX_ERROR:
667                                         rdat = REPORT_DATA['warns']
668                                         rdat.append("Object %s" % self.obj.name)
669                                         rdat.append("has at least one material *index* assigned but not")
670                                         rdat.append("defined (not linked to an existing material).")
671                                         rdat.append("Result: some faces may be exported with a wrong color.")
672                                         rdat.append("You can assign materials in the Edit Buttons window (F9).")
673                                 elif not matidx_error_reported:
674                                         midxmsg = "- Same for object %s." % self.obj.name
675                                         REPORT_DATA['warns'].append(midxmsg)
676                                 lc_MATIDX_ERROR += 1
677                                 matidx_error_reported = True
678                                 m_idx = 0
679                                 if lc_ADD_DEFAULT_MAT: m_idx -= 1
680                         refs = len(f)
681                         flaglow = 0 # polygon
682                         if lc_PER_FACE_1_OR_2_SIDED and hasFaceUV: # per face attribute
683                                 two_side = f.mode & lc_FACE_TWOSIDED
684                         else: # global, for the whole mesh
685                                 two_side = self.mesh.mode & lc_MESH_TWOSIDED
686                         two_side = (two_side > 0) << 1
687                         flaghigh = f.smooth | two_side
688                         surfstr = "SURF 0x%d%d\n" % (flaghigh, flaglow)
689                         if lc_ADD_DEFAULT_MAT and objmats: m_idx += 1
690                         matstr = "mat %s\n" % m_idx
691                         refstr = "refs %s\n" % refs
692                         u, v, vi = 0, 0, 0
693                         fvstr = []
694                         if foomesh:
695                                 for vert in f.v:
696                                         fvstr.append(str(vert.index))
697                                         if hasFaceUV:
698                                                 u = f.uv[vi][0]
699                                                 v = f.uv[vi][1]
700                                                 vi += 1
701                                         fvstr.append(" %s %s\n" % (u, v))
702                         else:
703                                 for vert in f.v:
704                                         fvstr.append(str(verts.index(vert)))
705                                         if hasFaceUV:
706                                                 u = f.uv[vi][0]
707                                                 v = f.uv[vi][1]
708                                                 vi += 1
709                                         fvstr.append(" %s %s\n" % (u, v))
710
711                         fvstr = "".join(fvstr)
712
713                         file.write("%s%s%s%s" % (surfstr, matstr, refstr, fvstr))
714
715                 # material for loose edges
716                 edges_mat = 0 # default to first material
717                 for omat in objmats: # but look for a material from this mesh
718                         if omat in mlist:
719                                 edges_mat = mlist.index(omat)
720                                 if lc_ADD_DEFAULT_MAT: edges_mat += 1
721                                 break
722
723                 for e in looseEdges:
724                         fvstr = []
725                         #flaglow = 2 # 1 = closed line, 2 = line
726                         #flaghigh = 0
727                         #surfstr = "SURF 0x%d%d\n" % (flaghigh, flaglow)
728                         surfstr = "SURF 0x02\n"
729
730                         fvstr.append("%d 0 0\n" % verts.index(e.v1))
731                         fvstr.append("%d 0 0\n" % verts.index(e.v2))
732                         fvstr = "".join(fvstr)
733
734                         matstr = "mat %d\n" % edges_mat # for now, use first material 
735                         refstr = "refs 2\n" # 2 verts
736
737                         file.write("%s%s%s%s" % (surfstr, matstr, refstr, fvstr))
738
739                 MATIDX_ERROR = lc_MATIDX_ERROR
740
741 # End of Class AC3DExport
742
743 from Blender.Window import FileSelector
744
745 def report_data():
746         global VERBOSE
747
748         if not VERBOSE: return
749
750         d = REPORT_DATA
751         msgs = {
752                 '0main': '%s\nExporting meshes to AC3D format' % str(19*'-'),
753                 '1warns': 'Warnings',
754                 '2errors': 'Errors',
755                 '3nosplit': 'Not split (because name starts with "=" or "$")',
756                 '4noexport': 'Not exported (because name starts with "!" or "#")'
757         }
758         if NO_SPLIT:
759                 l = msgs['3nosplit']
760                 l = "%s (because OPTION NO_SPLIT is set)" % l.split('(')[0] 
761                 msgs['3nosplit'] = l
762         keys = msgs.keys()
763         keys.sort()
764         for k in keys:
765                 msgk = msgs[k]
766                 msg = '\n'.join(d[k[1:]])
767                 if msg:
768                         print '\n-%s:' % msgk
769                         print msg
770
771 # File Selector callback:
772 def fs_callback(filename):
773         global EXPORT_DIR, OBJS, CONFIRM_OVERWRITE, VERBOSE
774
775         if not filename.endswith('.ac'): filename = '%s.ac' % filename
776
777         if bsys.exists(filename) and CONFIRM_OVERWRITE:
778                 if Blender.Draw.PupMenu('OVERWRITE?%t|File exists') != 1:
779                         return
780
781         Blender.Window.WaitCursor(1)
782         starttime = bsys.time()
783
784         export_dir = bsys.dirname(filename)
785         if export_dir != EXPORT_DIR:
786                 EXPORT_DIR = export_dir
787                 update_RegistryInfo()
788
789         try:
790                 file = open(filename, 'w')
791         except IOError, (errno, strerror):
792                 error = "IOError #%s: %s" % (errno, strerror)
793                 REPORT_DATA['errors'].append("Saving failed - %s." % error)
794                 error_msg = "Couldn't save file!%%t|%s" % error
795                 Blender.Draw.PupMenu(error_msg)
796                 return
797
798         try:
799                 test = AC3DExport(OBJS, file)
800         except:
801                 file.close()
802                 raise
803         else:
804                 file.close()
805                 endtime = bsys.time() - starttime
806                 REPORT_DATA['main'].append("Done. Saved to: %s" % filename)
807                 REPORT_DATA['main'].append("Data exported in %.3f seconds." % endtime)
808
809         if VERBOSE: report_data()
810         Blender.Window.WaitCursor(0)
811
812
813 # -- End of definitions
814
815 scn = Blender.Scene.GetCurrent()
816
817 if ONLY_SELECTED:
818         OBJS = list(scn.objects.context)
819 else:
820         OBJS = list(scn.objects)
821
822 if not OBJS:
823         Blender.Draw.PupMenu('ERROR: no objects selected')
824 else:
825         fname = bsys.makename(ext=".ac")
826         if EXPORT_DIR:
827                 fname = bsys.join(EXPORT_DIR, bsys.basename(fname))
828         FileSelector(fs_callback, "Export AC3D", fname)