Scripts:
authorWillian Padovani Germano <wpgermano@gmail.com>
Sun, 14 Jan 2007 18:13:47 +0000 (18:13 +0000)
committerWillian Padovani Germano <wpgermano@gmail.com>
Sun, 14 Jan 2007 18:13:47 +0000 (18:13 +0000)
Misc updates to the ac3d importer and exporter:
- use Mesh instead of NMesh;
- properly export modified data and materials from either ob or obdata (thanks for mesh.getFromObject :) );
- option to export local rot and loc info;
- better import / export of hierarchies;
- + tiny updates here and there to support old or weird .ac files.

release/scripts/ac3d_export.py
release/scripts/ac3d_import.py

index 2180fa1e1288dbfe14b8fb526640c60d102e5cc3..80e88d933c8e4c206e1e10526aa9c0a85082c3be 100644 (file)
@@ -2,7 +2,7 @@
 
 """ Registration info for Blender menus:
 Name: 'AC3D (.ac)...'
-Blender: 236
+Blender: 242
 Group: 'Export'
 Tip: 'Export selected meshes to AC3D (.ac) format'
 """
@@ -10,7 +10,7 @@ Tip: 'Export selected meshes to AC3D (.ac) format'
 __author__ = "Willian P. Germano"
 __url__ = ("blender", "elysiun", "AC3D's homepage, http://www.ac3d.org",
        "PLib 3d gaming lib, http://plib.sf.net")
-__version__ = "2.41a 2006-06-16"
+__version__ = "2.43 2007-01-14"
 
 __bpydoc__ = """\
 This script exports selected Blender meshes to AC3D's .ac file format.
@@ -37,6 +37,7 @@ Config Options:<br>
     toggle:<br>
     - AC3D 4 mode: unset it to export without the 'crease' tag that was
 introduced with AC3D 4.0 and with the old material handling;<br>
+    - global coords: transform all vertices of all meshes to global coordinates;<br>
     - skip data: set it if you don't want mesh names (ME:, not OB: field)
 to be exported as strings for AC's "data" tags (19 chars max);<br>
     - rgb mirror color can be exported as ambient and/or emissive if needed,
@@ -56,10 +57,13 @@ to export (read notes below about tokens, too);<br>
 toggle is "on".
 
 Notes:<br>
-    This version is considerably faster than previous ones for large meshes;<br>
+       This version updates:<br>
+    - modified meshes are correctly exported, no need to apply the modifiers in Blender;<br>
+    - correctly export each used material, be it assigned to the object or to its mesh data;<br>
+    - exporting lines (edges) is again supported;<br>
+    - there's a new option to choose between exporting meshes with transformed (global) coordinates or local ones;<br>
     Multiple textures per mesh are supported (mesh gets split);<br>
-    Parenting with meshes or empties as parents is converted to AC3D group
-information;<br>
+       Parents are exported as a group containing both the parent and its children;<br>
     Start mesh object names (OB: field) with "!" or "#" if you don't want them to be exported;<br>
     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.
 """
@@ -67,17 +71,20 @@ information;<br>
 # $Id$
 #
 # --------------------------------------------------------------------------
-# AC3DExport version 2.41
-# Program versions: Blender 2.36+ and AC3Db files (means version 0xb)
-# new: faster, supports multiple textures per object and parenting is
-# properly exported as group info, adapted to work with the Config Editor
+# AC3DExport version 2.43
+# Program versions: Blender 2.42+ and AC3Db files (means version 0xb)
+# new: updated for new Blender version and Mesh module; supports lines (edges) again;
+# option to export vertices transformed to global coordinates or not; now the modified
+# (by existing mesh modifiers) mesh is exported; materials are properly exported, no
+# matter if each of them is linked to the mesh or to the object.
 # --------------------------------------------------------------------------
 # Thanks: Steve Baker for discussions and inspiration; for testing, bug
-# reports, suggestions: David Megginson, Filippo di Natale, Franz Melchior
+# reports, suggestions, patches: David Megginson, Filippo di Natale,
+# Franz Melchior, Campbell Barton, Josh Babcock, Ralf Gerlich
 # --------------------------------------------------------------------------
 # ***** BEGIN GPL LICENSE BLOCK *****
 #
-# Copyright (C) 2004: Willian P. Germano, wgermano _at_ ig.com.br
+# Copyright (C) 2004-2007: Willian P. Germano, wgermano _at_ ig.com.br
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -94,8 +101,8 @@ information;<br>
 # --------------------------------------------------------------------------
 
 import Blender
+from Blender import Object, Mesh, Material, Image, Mathutils, Registry
 from Blender import sys as bsys
-from Blender import Mathutils
 
 # Globals
 ERROR_MSG = '' # popup error msg
@@ -108,11 +115,18 @@ REPORT_DATA = {
 }
 TOKENS_DONT_EXPORT = ['!', '#']
 TOKENS_DONT_SPLIT  = ['=', '$']
-MATIDX_ERROR = False
+
+MATIDX_ERROR = 0
+
+# flags:
+LOOSE = Mesh.EdgeFlags['LOOSE']
+FACE_TWOSIDED = Mesh.FaceModes['TWOSIDE']
+MESH_TWOSIDED = Mesh.Modes['TWOSIDED']
 
 REG_KEY = 'ac3d_export'
 
 # config options:
+GLOBAL_COORDS = True
 SKIP_DATA = False
 MIRCOL_AS_AMB = False
 MIRCOL_AS_EMIS = False
@@ -126,6 +140,7 @@ EXPORT_DIR = ''
 PER_FACE_1_OR_2_SIDED = True
 
 tooltips = {
+       'GLOBAL_COORDS': "transform all vertices of all meshes to global coordinates",
        'SKIP_DATA': "don't export mesh names as data fields",
        'MIRCOL_AS_AMB': "export mirror color as ambient color",
        'MIRCOL_AS_EMIS': "export mirror color as emissive color",
@@ -153,10 +168,11 @@ def update_RegistryInfo():
        d['ONLY_SELECTED'] = ONLY_SELECTED
        d['PER_FACE_1_OR_2_SIDED'] = PER_FACE_1_OR_2_SIDED
        d['tooltips'] = tooltips
-       Blender.Registry.SetKey(REG_KEY, d, True)
+       d['GLOBAL_COORDS'] = GLOBAL_COORDS
+       Registry.SetKey(REG_KEY, d, True)
 
 # Looking for a saved key in Blender.Registry dict:
-rd = Blender.Registry.GetKey(REG_KEY, True)
+rd = Registry.GetKey(REG_KEY, True)
 
 if rd:
        try:
@@ -171,6 +187,7 @@ if rd:
                ONLY_SELECTED = rd['ONLY_SELECTED']
                NO_SPLIT = rd['NO_SPLIT']
                PER_FACE_1_OR_2_SIDED = rd['PER_FACE_1_OR_2_SIDED']
+               GLOBAL_COORDS = rd['GLOBAL_COORDS']
        except KeyError: update_RegistryInfo()
 
 else:
@@ -180,7 +197,7 @@ VERBOSE = True
 CONFIRM_OVERWRITE = True
 
 # check General scripts config key for default behaviors
-rd = Blender.Registry.GetKey('General', True)
+rd = Registry.GetKey('General', True)
 if rd:
        try:
                VERBOSE = rd['verbose']
@@ -194,9 +211,10 @@ DEFAULT_MAT = \
 spec 0.5 0.5 0.5  shi 64  trans 0'
 
 # This transformation aligns Blender and AC3D coordinate systems:
-acmatrix = Mathutils.Matrix([1,0,0,0], [0,0,-1,0], [0,1,0,0], [0,0,0,1])
+BLEND_TO_AC3D_MATRIX = Mathutils.Matrix([1,0,0,0], [0,0,-1,0], [0,1,0,0], [0,0,0,1])
 
-def Round(f):
+def Round_s(f):
+       "Round to default precision and turn value to a string"
        r = round(f,6) # precision set to 10e-06
        if r == int(r):
                return str(int(r))
@@ -206,11 +224,15 @@ def Round(f):
 def transform_verts(verts, m):
        vecs = []
        for v in verts:
-               vec = Mathutils.Vector([v[0],v[1],v[2], 1])
-               #vecs.append(Mathutils.VecMultMat(vec, m))
+               x, y, z = v.co
+               vec = Mathutils.Vector([x, y, z, 1])
                vecs.append(vec*m)
        return vecs
 
+def get_loose_edges(mesh):
+       loose = LOOSE
+       return [e for e in mesh.edges if e.flag & loose]
+
 # ---
 
 # meshes with more than one texture assigned
@@ -242,29 +264,27 @@ class FooMesh:
        def __init__(self, tex, faces, mesh):
                self.name = mesh.name
                self.mesh = mesh
-               self.faces = []
-               self.verts = verts = []
+               self.looseEdges = []
+               self.faceUV = mesh.faceUV
+               self.degr = mesh.degr
                vidxs = [0]*len(mesh.verts)
-               faces2 = [0]*len(faces)
+               foofaces = []
                for f in faces:
-                       self.faces.append(self.FooFace(self, f))
+                       foofaces.append(self.FooFace(self, f))
                        for v in f.v:
                                if v: vidxs[v.index] = 1
                i = 0
+               fooverts = []
                for v in mesh.verts:
                        if vidxs[v.index]:
-                               verts.append(v)
+                               fooverts.append(v)
                                vidxs[v.index] = i
                                i += 1
-               for f in self.faces:
+               for f in foofaces:
                        for v in f.v:
                                if v: v.index = vidxs[v.v.index]
-
-       def hasFaceUV(self):
-               return self.mesh.hasFaceUV()
-
-       def getMaxSmoothAngle(self):
-               return self.mesh.getMaxSmoothAngle()
+               self.faces = foofaces
+               self.verts = fooverts
 
 
 class AC3DExport: # the ac3d exporter part
@@ -272,15 +292,14 @@ class AC3DExport: # the ac3d exporter part
        def __init__(self, scene_objects, filename):
 
                global ARG, SKIP_DATA, ADD_DEFAULT_MAT, DEFAULT_MAT
-               global ERROR_MSG, MATIDX_ERROR
-
-               MATIDX_ERROR = 0
+               global ERROR_MSG
 
                header = 'AC3Db'
                self.buf = ''
                self.mbuf = ''
                self.mlist = []
                world_kids = 0
+               parents_list = self.parents_list = []
                kids_dict = self.kids_dict = {}
                objs = []
                exp_objs = self.exp_objs = []
@@ -299,37 +318,50 @@ class AC3DExport: # the ac3d exporter part
                objs = \
                        [o for o in scene_objects if o.type in ['Mesh', 'Empty']]
 
+               # create a tree from parents to children objects
+
                for obj in objs[:]:
                        parent = obj.parent
-                       list = [obj]
+                       lineage = [obj]
 
                        while parent:
+                               parents_list.append(parent.name)
                                obj = parent
                                parent = parent.getParent()
-                               list.insert(0, obj)
+                               lineage.insert(0, obj)
 
-                       dict = tree
-                       for i in xrange(len(list)):
-                               lname = list[i].getType()[:2] + list[i].name
-                               if lname not in dict.keys():
-                                       dict[lname] = {}
-                               dict = dict[lname]
+                       d = tree
+                       for i in xrange(len(lineage)):
+                               lname = lineage[i].getType()[:2] + lineage[i].name
+                               if lname not in d.keys():
+                                       d[lname] = {}
+                               d = d[lname]
 
+               # traverse the tree to get an ordered list of names of objects to export
                self.traverse_dict(tree)
 
                world_kids = len(tree.keys())
 
-               objlist = [Blender.Object.Get(name) for name in exp_objs]
+               # get list of objects to export, start writing the .ac file
+
+               objlist = [Object.Get(name) for name in exp_objs]
 
                meshlist = [o for o in objlist if o.type == 'Mesh']
 
-               self.MATERIALS(meshlist)
+               # create a temporary mesh to hold actual (modified) mesh data
+               TMP_mesh = Mesh.New('tmp_for_ac_export')
+
+               # write materials
+
+               self.MATERIALS(meshlist, TMP_mesh)
                if not self.mbuf or ADD_DEFAULT_MAT:
-                       self.mbuf = DEFAULT_MAT + '\n' + self.mbuf
+                       self.mbuf = "%s\n%s" % (DEFAULT_MAT, self.mbuf)
                file.write(self.mbuf)
 
                file.write('OBJECT world\nkids %s\n' % world_kids)
 
+               # write the objects
+
                for obj in objlist:
                        self.obj = obj
 
@@ -337,21 +369,33 @@ class AC3DExport: # the ac3d exporter part
                        objname = obj.name
                        kidsnum = kids_dict[objname]
 
+                       # A parent plus its children are exported as a group.
+                       # If the parent is a mesh, its rot and loc are exported as the
+                       # group rot and loc and the mesh (w/o rot and loc) is added to the group.
                        if kidsnum:
                                self.OBJECT('group')
-                               parent_is_mesh = 0
+                               self.name(objname)
                                if objtype == 'Mesh':
                                        kidsnum += 1
-                                       parent_is_mesh = 1
-                               self.name(objname)
+                               if not GLOBAL_COORDS:
+                                       localmatrix = obj.getMatrix('localspace')
+                                       if not obj.getParent():
+                                               localmatrix *= BLEND_TO_AC3D_MATRIX
+                                       self.rot(localmatrix.rotationPart()) 
+                                       self.loc(localmatrix.translationPart())
                                self.kids(kidsnum)
 
                        if objtype == 'Mesh':
-                               mesh = self.mesh = obj.getData()
-                               meshes = self.split_mesh(mesh)
+                               mesh = TMP_mesh # temporary mesh to hold actual (modified) mesh data
+                               mesh.getFromObject(objname)
+                               self.mesh = mesh
+                               if mesh.faceUV:
+                                       meshes = self.split_mesh(mesh)
+                               else:
+                                       meshes = [mesh]
                                if len(meshes) > 1:
                                        if NO_SPLIT or self.dont_split(objname):
-                                               self.export_mesh(mesh, obj)
+                                               self.export_mesh(mesh, ob)
                                                REPORT_DATA['nosplit'].append(objname)
                                        else:
                                                self.OBJECT('group')
@@ -370,27 +414,27 @@ class AC3DExport: # the ac3d exporter part
                file.close()
                REPORT_DATA['main'].append("Done. Saved to: %s" % filename)
 
-       def traverse_dict(self, dict):
+       def traverse_dict(self, d):
                kids_dict = self.kids_dict
                exp_objs = self.exp_objs
-               keys = dict.keys()
+               keys = d.keys()
                for k in keys:
                        objname = k[2:]
-                       klen = len(dict[k])
+                       klen = len(d[k])
                        kids_dict[objname] = klen
                        if self.dont_export(objname):
-                               dict.pop(k)
-                               parent = Blender.Object.Get(objname).getParent()
+                               d.pop(k)
+                               parent = Object.Get(objname).getParent()
                                if parent: kids_dict[parent.name] -= 1
                                REPORT_DATA['noexport'].append(objname)
                                continue
                        if klen:
-                               self.traverse_dict(dict[k])
+                               self.traverse_dict(d[k])
                                exp_objs.insert(0, objname)
                        else:
                                if k.find('Em', 0) == 0: # Empty w/o children
-                                       dict.pop(k)
-                                       parent = Blender.Object.Get(objname).getParent()
+                                       d.pop(k)
+                                       parent = Object.Get(objname).getParent()
                                        if parent: kids_dict[parent.name] -= 1
                                else:
                                        exp_objs.insert(0, objname)
@@ -440,6 +484,7 @@ class AC3DExport: # the ac3d exporter part
                        for k in keys:
                                faces = tex_dict[k]
                                foo_meshes.append(FooMesh(k, faces, mesh))
+                       foo_meshes[0].edges = get_loose_edges(mesh)
                        return foo_meshes
                return [mesh]
 
@@ -449,17 +494,37 @@ class AC3DExport: # the ac3d exporter part
                if not name: name = obj.name
                self.name(name)
                if not SKIP_DATA:
-                       self.data(len(mesh.name), mesh.name)
-               texline = self.texture(mesh.faces)
-               if texline: file.write(texline)
+                       meshname = obj.getData(name_only = True)
+                       self.data(len(meshname), meshname)
+               if mesh.faceUV:
+                       texline = self.texture(mesh.faces)
+                       if texline: file.write(texline)
                if AC3D_4:
-                       self.crease(mesh.getMaxSmoothAngle())
-               self.numvert(mesh.verts, obj.getMatrix())
-               self.numsurf(mesh.faces, mesh.hasFaceUV(), foomesh)
-
-       def MATERIALS(self, meshlist):
+                       self.crease(mesh.degr)
+
+               # If exporting using local coordinates, children object coordinates should not be
+               # transformed to ac3d's coordinate system, since that will be accounted for in
+               # their topmost parents (the parents w/o parents) transformations.
+               if not GLOBAL_COORDS:
+                       # We hold parents in a list, so they also don't get transformed,
+                       # because for each parent we create an ac3d group to hold both the
+                       # parent and its children.
+                       if obj.name not in self.parents_list:
+                               localmatrix = obj.getMatrix('localspace')
+                               if not obj.getParent():
+                                       localmatrix *= BLEND_TO_AC3D_MATRIX
+                               self.rot(localmatrix.rotationPart())
+                               self.loc(localmatrix.translationPart())
+                       matrix = None
+               else:
+                       matrix = obj.getMatrix() * BLEND_TO_AC3D_MATRIX
+
+               self.numvert(mesh.verts, matrix)
+               self.numsurf(mesh, foomesh)
+
+       def MATERIALS(self, meshlist, me):
                for meobj in meshlist:
-                       me = meobj.getData()
+                       me.getFromObject(meobj)
                        mat = me.materials
                        mbuf = []
                        mlist = self.mlist
@@ -469,23 +534,24 @@ class AC3DExport: # the ac3d exporter part
                                        mlist.index(name)
                                except ValueError:
                                        mlist.append(name)
-                                       M = Blender.Material.Get(name)
+                                       M = Material.Get(name)
                                        material = 'MATERIAL "%s"' % name
-                                       mirCol = "%s %s %s" % (Round(M.mirCol[0]), Round(M.mirCol[1]),
-                                               Round(M.mirCol[2]))
-                                       rgb = "rgb %s %s %s" % (Round(M.R), Round(M.G), Round(M.B))
-                                       amb = "amb %s %s %s" % (Round(M.amb), Round(M.amb), Round(M.amb))
-                                       spec = "spec %s %s %s" % (Round(M.specCol[0]),
-                                                Round(M.specCol[1]), Round(M.specCol[2]))
+                                       mirCol = "%s %s %s" % (Round_s(M.mirCol[0]), Round_s(M.mirCol[1]),
+                                               Round_s(M.mirCol[2]))
+                                       rgb = "rgb %s %s %s" % (Round_s(M.R), Round_s(M.G), Round_s(M.B))
+                                       ambval = Round_s(M.amb)
+                                       amb = "amb %s %s %s" % (ambval, ambval, ambval)
+                                       spec = "spec %s %s %s" % (Round_s(M.specCol[0]),
+                                                Round_s(M.specCol[1]), Round_s(M.specCol[2]))
                                        if AC3D_4:
-                                               emit = Round(M.emit)
+                                               emit = Round_s(M.emit)
                                                emis = "emis %s %s %s" % (emit, emit, emit)
                                                shival = int(M.spec * 64)
                                        else:
                                                emis = "emis 0 0 0"
                                                shival = 72
                                        shi = "shi %s" % shival
-                                       trans = "trans %s" % (Round(1 - M.alpha))
+                                       trans = "trans %s" % (Round_s(1 - M.alpha))
                                        if MIRCOL_AS_AMB:
                                                amb = "amb %s" % mirCol 
                                        if MIRCOL_AS_EMIS:
@@ -516,12 +582,12 @@ class AC3DExport: # the ac3d exporter part
                                tex = f.image.name
                                break
                if tex:
-                       image = Blender.Image.Get(tex)
+                       image = Image.Get(tex)
                        texfname = image.filename
                        if SET_TEX_DIR:
-                               texfname = Blender.sys.basename(texfname)
+                               texfname = bsys.basename(texfname)
                                if TEX_DIR:
-                                       texfname = Blender.sys.join(TEX_DIR, texfname)
+                                       texfname = bsys.join(TEX_DIR, texfname)
                        buf = 'texture "%s"\n' % texfname
                        xrep = image.xrep
                        yrep = image.yrep
@@ -530,41 +596,65 @@ class AC3DExport: # the ac3d exporter part
 
        def rot(self, matrix):
                rot = ''
-               not_I = 0
+               not_I = 0 # not identity
+               matstr = []
                for i in [0, 1, 2]:
-                       r = map(Round, matrix[i])
-                       not_I += (r[0] != '0.0')+(r[1] != '0.0')+(r[2] != '0.0')
-                       not_I -= (r[i] == '1.0')
+                       r = map(Round_s, matrix[i])
+                       not_I += (r[0] != '0')+(r[1] != '0')+(r[2] != '0')
+                       not_I -= (r[i] == '1')
                        for j in [0, 1, 2]:
-                               rot = "%s %s" % (rot, r[j])
-               if not_I:
-                       self.file.write('rot %s\n' % rot.strip())
+                               matstr.append(' %s' % r[j])
+               if not_I: # no need to write identity
+                       self.file.write('rot%s\n' % "".join(matstr))
                                
        def loc(self, loc):
-               loc = map(Round, loc)
-               if loc[0] or loc[1] or loc[2]:
+               loc = map(Round_s, loc)
+               if loc != ['0', '0', '0']: # no need to write default
                        self.file.write('loc %s %s %s\n' % (loc[0], loc[1], loc[2]))
 
        def crease(self, crease):
-               self.file.write('crease %s\n' % crease)
+               self.file.write('crease %f\n' % crease)
 
        def numvert(self, verts, matrix):
                file = self.file
-               file.write("numvert %s\n" % len(verts))
-               m = matrix * acmatrix
-               verts = transform_verts(verts, m)
-               for v in verts:
-                       v0, v1, v2 = Round(v[0]), Round(v[1]), Round(v[2])
-                       file.write("%s %s %s\n" % (v0, v1, v2))
+               nvstr = []
+               nvstr.append("numvert %s\n" % len(verts))
+
+               if matrix:
+                       verts = transform_verts(verts, matrix)
+                       for v in verts:
+                               v = map (Round_s, v)
+                               nvstr.append("%s %s %s\n" % (v[0], v[1], v[2]))
+               else:
+                       for v in verts:
+                               v = map(Round_s, v.co)
+                               nvstr.append("%s %s %s\n" % (v[0], v[1], v[2]))
+
+               file.write("".join(nvstr))
+
+       def numsurf(self, mesh, foomesh = False):
+
+               global MATIDX_ERROR
+
+               # local vars are faster and so better in tight loops
+               lc_ADD_DEFAULT_MAT = ADD_DEFAULT_MAT
+               lc_MATIDX_ERROR = MATIDX_ERROR
+               lc_PER_FACE_1_OR_2_SIDED = PER_FACE_1_OR_2_SIDED
+               lc_FACE_TWOSIDED = FACE_TWOSIDED
+               lc_MESH_TWOSIDED = MESH_TWOSIDED
+
+               faces = mesh.faces
+               hasFaceUV = mesh.faceUV
+               if foomesh:
+                       looseEdges = mesh.looseEdges
+               else:
+                       looseEdges = get_loose_edges(mesh)
 
-       def numsurf(self, faces, hasFaceUV, foomesh = False):
-
-               global ADD_DEFAULT_MAT, MATIDX_ERROR
                file = self.file
  
-               file.write("numsurf %s\n" % len(faces))
+               file.write("numsurf %s\n" % (len(faces) + len(looseEdges)))
 
-               if not foomesh: verts = self.mesh.verts
+               if not foomesh: verts = list(self.mesh.verts)
 
                mlist = self.mlist
                omlist = {}
@@ -573,33 +663,33 @@ class AC3DExport: # the ac3d exporter part
                for i in range(len(objmats)):
                        objmats[i] = objmats[i].name
                for f in faces:
-                       m_idx = f.materialIndex
+                       m_idx = f.mat
                        try:
                                m_idx = mlist.index(objmats[m_idx])
                        except IndexError:
-                               if not MATIDX_ERROR:
+                               if not lc_MATIDX_ERROR:
                                        rdat = REPORT_DATA['warns']
                                        rdat.append("Object %s" % self.obj.name)
                                        rdat.append("has at least one material *index* assigned but not")
                                        rdat.append("defined (not linked to an existing material).")
                                        rdat.append("Result: some faces may be exported with a wrong color.")
-                                       rdat.append("You can link materials in the Edit Buttons window (F9).")
+                                       rdat.append("You can assign materials in the Edit Buttons window (F9).")
                                elif not matidx_error_told:
                                        midxmsg = "- Same for object %s." % self.obj.name
                                        REPORT_DATA['warns'].append(midxmsg)
-                               MATIDX_ERROR += 1
+                               lc_MATIDX_ERROR += 1
                                matidx_error_told = 1
                                m_idx = 0
                        refs = len(f)
-                       flaglow = (refs == 2) << 1
-                       if PER_FACE_1_OR_2_SIDED: # per face attribute
-                               two_side = f.mode & Blender.NMesh.FaceModes['TWOSIDE']
+                       flaglow = 0 # polygon
+                       if lc_PER_FACE_1_OR_2_SIDED and hasFaceUV: # per face attribute
+                               two_side = f.mode & lc_FACE_TWOSIDED
                        else: # global, for the whole mesh
-                               two_side = self.mesh.mode & Blender.NMesh.Modes['TWOSIDED']
+                               two_side = self.mesh.mode & lc_MESH_TWOSIDED
                        two_side = (two_side > 0) << 1
                        flaghigh = f.smooth | two_side
                        surfstr = "SURF 0x%d%d\n" % (flaghigh, flaglow)
-                       if ADD_DEFAULT_MAT and objmats: m_idx += 1
+                       if lc_ADD_DEFAULT_MAT and objmats: m_idx += 1
                        matstr = "mat %s\n" % m_idx
                        refstr = "refs %s\n" % refs
                        u, v, vi = 0, 0, 0
@@ -625,6 +715,24 @@ class AC3DExport: # the ac3d exporter part
 
                        file.write("%s%s%s%s" % (surfstr, matstr, refstr, fvstr))
 
+               for e in looseEdges:
+                       fvstr = []
+                       #flaglow = 2 # 1 = closed line, 2 = line
+                       #flaghigh = 0
+                       #surfstr = "SURF 0x%d%d\n" % (flaghigh, flaglow)
+                       surfstr = "SURF 0x02\n"
+
+                       fvstr.append("%d 0 0\n" % verts.index(e.v1))
+                       fvstr.append("%d 0 0\n" % verts.index(e.v2))
+                       fvstr = "".join(fvstr)
+
+                       matstr = "mat 0\n" # for now, use first material 
+                       refstr = "refs 2\n" # 2 verts
+
+                       file.write("%s%s%s%s" % (surfstr, matstr, refstr, fvstr))
+
+               MATIDX_ERROR = lc_MATIDX_ERROR
+
 # End of Class AC3DExport
 
 from Blender.Window import FileSelector
@@ -687,7 +795,9 @@ def fs_callback(filename):
 
 
 # -- End of definitions
+
 scn = Blender.Scene.GetCurrent()
+
 if ONLY_SELECTED:
        OBJS = list(scn.objects.context)
 else:
index 4dcde65fb4af78a9b17c3abe18d78b89385edba4..0fd5d3e5625ead84d2ee4b4dcfa47991419c79d6 100644 (file)
@@ -2,7 +2,7 @@
 
 """ Registration info for Blender menus:
 Name: 'AC3D (.ac)...'
-Blender: 236
+Blender: 242
 Group: 'Import'
 Tip: 'Import an AC3D (.ac) file.'
 """
@@ -10,7 +10,7 @@ Tip: 'Import an AC3D (.ac) file.'
 __author__ = "Willian P. Germano"
 __url__ = ("blender", "elysiun", "AC3D's homepage, http://www.ac3d.org",
        "PLib 3d gaming lib, http://plib.sf.net")
-__version__ = "2.36a 2005-12-04"
+__version__ = "2.43 2007-01-14"
 
 __bpydoc__ = """\
 This script imports AC3D models into Blender.
@@ -29,8 +29,6 @@ Known issues:<br>
     None.
 
 Config Options:<br>
-    - group (toggle): if "on", grouped objects in the .ac file are parented to
-Empties.
     - textures dir (string): if non blank, when imported texture paths are
 wrong in the .ac file, Blender will also look for them at this dir.
 
@@ -43,13 +41,13 @@ users can configure (see config options above).
 # $Id$
 #
 # --------------------------------------------------------------------------
-# AC3DImport version 2.36a Dec 04, 2005
-# Program versions: Blender 2.36+ and AC3Db files (means version 0xb)
-# changed: fixed a bug: error on 1 vertex "closed" polylines
+# AC3DImport version 2.43 Jan 04, 2007
+# Program versions: Blender 2.43 and AC3Db files (means version 0xb)
+# changed: updated for new Blender version, Mesh module
 # --------------------------------------------------------------------------
 # ***** BEGIN GPL LICENSE BLOCK *****
 #
-# Copyright (C) 2005: Willian P. Germano, wgermano _at_ ig.com.br
+# Copyright (C) 2004-2007: Willian P. Germano, wgermano _at_ ig.com.br
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -74,33 +72,30 @@ users can configure (see config options above).
 #  fixing. Avoiding or triangulating concave n-gons in AC3D is a simple way to
 #  avoid problems.
 
+from math import radians
+
 import Blender
-from Blender import Registry
+from Blender import Scene, Object, Mesh, Lamp, Registry, sys as bsys, Window, Image, Material
 from Blender.sys import dirsep
+from Blender.Mathutils import Vector, Matrix, Euler
 
 # Default folder for AC3D textures, to override wrong paths, change to your
 # liking or leave as "":
 TEXTURES_DIR = ""
 
-# Set 'GROUP' to True to make Blender group imported objects using Empties,
-# to reproduce the object hierarchy in the .ac file
-GROUP = False
-
 tooltips = {
-       'TEXTURES_DIR': 'additional dir to look for missing textures',
-       'GROUP': 'mimick grouping information by parenting grouped meshes to empties'
+       'TEXTURES_DIR': 'additional dir to look for missing textures'
 }
 
 def update_registry():
-       global GROUP, TEXTURES_DIR
-       rd = dict([('GROUP', GROUP), ('TEXTURES_DIR', TEXTURES_DIR)])
+       global TEXTURES_DIR
+       rd = dict([('TEXTURES_DIR', TEXTURES_DIR)])
        Registry.SetKey('ac3d_import', rd, True)
 
 rd = Registry.GetKey('ac3d_import', True)
 
 if rd:
        TEXTURES_DIR = rd['TEXTURES_DIR']
-       GROUP = rd['GROUP']
 else: update_registry()
 
 if TEXTURES_DIR:
@@ -119,11 +114,31 @@ if rd:
        
 errmsg = ""
 
+# Matrix to align ac3d's coordinate system with Blender's one,
+# it's a -90 degrees rotation around the x axis:
+AC_TO_BLEND_MATRIX = Matrix([1, 0, 0], [0, 0, 1], [0, -1, 0])
+
+AC_WORLD = 0
+AC_GROUP = 1
+AC_POLY = 2
+AC_LIGHT = 3
+AC_OB_TYPES = {
+       'world': AC_WORLD,
+       'group': AC_GROUP,
+       'poly':  AC_POLY,
+       'light':  AC_LIGHT
+       }
 
 def inform(msg):
        global VERBOSE
        if VERBOSE: print msg
 
+def euler_in_radians(eul):
+       "Used while there's a bug in the BPY API"
+       eul.x = radians(eul.x)
+       eul.y = radians(eul.y)
+       eul.z = radians(eul.z)
+       return eul
 
 class Obj:
        
@@ -135,23 +150,31 @@ class Obj:
                self.tex = ''
                self.texrep = [1,1]
                self.texoff = None
-               self.loc = [0, 0, 0]
+               self.loc = []
                self.rot = []
+               self.size = []
                self.crease = 30
                self.vlist = []
-               self.flist = []
+               self.flist_cfg = []
+               self.flist_v = []
+               self.flist_uv = []
+               self.elist = []
                self.matlist = []
                self.kids = 0
 
+               self.bl_obj = None # the actual Blender object created from this data
+
 class AC3DImport:
 
        def __init__(self, filename):
 
                global errmsg
 
+               self.scene = Scene.GetCurrent()
+
                self.i = 0
                errmsg = ''
-               self.importdir = Blender.sys.dirname(filename)
+               self.importdir = bsys.dirname(filename)
                try:
                        file = open(filename, 'r')
                except IOError, (errno, strerror):
@@ -187,8 +210,7 @@ class AC3DImport:
 
                self.objlist = []
                self.mlist = []
-               self.dads = []
-               self.kids = []
+               self.kidsnumlist = []
                self.dad = None
 
                self.lines = file.readlines()
@@ -199,12 +221,12 @@ class AC3DImport:
                self.testAC3DImport()
                                
        def parse_obj(self, value):
-               if self.kids:
-                       while not self.kids[-1]:
-                               self.kids.pop()
+               if self.kidsnumlist:
+                       while not self.kidsnumlist[-1]:
+                               self.kidsnumlist.pop()
                                self.dad = self.dad.dad
-                       self.kids[-1] -= 1
-               new = Obj(value)
+                       self.kidsnumlist[-1] -= 1
+               new = Obj(AC_OB_TYPES[value])
                new.dad = self.dad
                new.name = value
                self.objlist.append(new)
@@ -212,7 +234,7 @@ class AC3DImport:
        def parse_kids(self, value):
                kids = int(value)
                if kids:
-                       self.kids.append(kids)
+                       self.kidsnumlist.append(kids)
                        self.dad = self.objlist[-1]
                self.objlist[-1].kids = kids
 
@@ -269,23 +291,24 @@ class AC3DImport:
 
        def parse_rot(self, trash):
                i = self.i - 1
+               ob = self.objlist[-1]
                rot = self.lines[i].split(' ', 1)[1]
                rot = map(float, rot.split())
-               self.objlist[-1].rot = rot
+               matrix = Matrix(rot[:3], rot[3:6], rot[6:])
+               ob.rot = matrix
+               size = matrix.scalePart() # vector
+               ob.size = size
 
        def parse_loc(self, trash):
                i = self.i - 1
                loc = self.lines[i].split(' ', 1)[1]
                loc = map(float, loc.split())
-               self.objlist[-1].loc = loc
+               self.objlist[-1].loc = Vector(loc)
 
        def parse_crease(self, value):
                # AC3D: range is [0.0, 180.0]; Blender: [1, 80]
-               try:
-                       value = int(value)
-               except ValueError:
-                       value = int(float(value)) # duh
-               self.objlist[-1].crease = value
+               value = float(value)
+               self.objlist[-1].crease = int(value)
 
        def parse_vert(self, value):
                i = self.i
@@ -303,28 +326,6 @@ class AC3DImport:
 
                self.i = i
 
-               rot = obj.rot
-               if rot:
-                       nv = len(vlist)
-                       for j in range(nv):
-                               v = vlist[j]
-                               t = [0,0,0]
-                               t[0] = rot[0]*v[0] + rot[3]*v[1] + rot[6]*v[2]
-                               t[1] = rot[1]*v[0] + rot[4]*v[1] + rot[7]*v[2]
-                               t[2] = rot[2]*v[0] + rot[5]*v[1] + rot[8]*v[2]
-                               vlist[j] = t
-
-               loc = obj.loc
-               dad = obj.dad
-               while dad:
-                       for j in [0, 1, 2]:
-                               loc[j] += dad.loc[j]
-                       dad = dad.dad
-
-               for v in vlist:
-                       for j in [0, 1, 2]:
-                               v[j] += loc[j]
-
        def parse_surf(self, value):
                i = self.i
                is_smooth = 0
@@ -333,6 +334,7 @@ class AC3DImport:
                obj = self.objlist[-1]
                matlist = obj.matlist
                numsurf = int(value)
+               NUMSURF = numsurf
 
                while numsurf:
                        flags = lines[i].split()
@@ -349,45 +351,51 @@ class AC3DImport:
                        i += 3
                        face = []
                        faces = []
+                       edges = []
                        fuv = []
+                       fuvs = []
                        rfs = refs
 
                        while rfs:
                                line = lines[i].split()
                                v = int(line[0])
                                uv = [float(line[1]), float(line[2])]
-                               face.append([v, uv])
+                               face.append(v)
+                               fuv.append(Vector(uv))
                                rfs -= 1
                                i += 1
                                
-                       if flaglow:
+                       if flaglow: # it's a line or closed line, not a polygon
                                while len(face) >= 2:
                                        cut = face[:2]
-                                       faces.append(cut)
+                                       edges.append(cut)
                                        face = face[1:]
 
-                               if flaglow == 1 and faces:
-                                       face = [faces[-1][-1], faces[0][0]]
-                                       faces.append(face)
+                               if flaglow == 1 and edges:
+                                       face = [edges[-1][-1], edges[0][0]]
+                                       edges.append(face)
 
                        else:
                                while len(face) > 4:
                                        cut = face[:4]
+                                       cutuv = fuv[:4]
                                        face = face[3:]
+                                       fuv = fuv[3:]
                                        face.insert(0, cut[0])
-                                       faces.append(cut)        
+                                       fuv.insert(0, cutuv[0])
+                                       faces.append(cut)
+                                       fuvs.append(cutuv)
 
                                faces.append(face)
+                               fuvs.append(fuv)
 
-                       for f in faces:
-                               f.append(mat)
-                               f.append(is_smooth)
-                               f.append(twoside)
-                               self.objlist[-1].flist.append(f)
+                       obj.flist_cfg.extend([[mat, is_smooth, twoside]] * len(faces))
+                       obj.flist_v.extend(faces)
+                       obj.flist_uv.extend(fuvs)
+                       obj.elist.extend(edges) # loose edges
 
                        numsurf -= 1      
 
-                                                       
                self.i = i
 
        def parse_file(self):
@@ -408,14 +416,86 @@ class AC3DImport:
                                i = self.i
                        line = lines[i].split()
 
+       # for each group of meshes we try to find one that can be used as
+       # parent of the group in Blender.
+       # If not found, we can use an Empty as parent.
+       def found_parent(self, groupname, olist):
+               l = [o for o in olist if o.type == AC_POLY \
+                               and not o.kids and not o.rot and not o.loc]
+               if l:
+                       if len(l) > 1:
+                               for o in l:
+                                       if o.name == groupname:
+                                               return o
+                       return l[0]
+               return None
+
+       def build_hierarchy(self):
+               blmatrix = AC_TO_BLEND_MATRIX
+
+               olist = self.objlist[1:]
+               olist.reverse()
+
+               newlist = []
+
+               for o in olist:
+                       kids = o.kids
+                       if kids:
+                               children = newlist[-kids:]
+                               newlist = newlist[:-kids]
+                               if o.type == AC_GROUP:
+                                       parent = self.found_parent(o.name, children)
+                                       if parent:
+                                               children.remove(parent)
+                                               o.bl_obj = parent.bl_obj
+                                       else: # not found, use an empty
+                                               empty = Object.New('Empty', o.name)
+                                               self.scene.link(empty)
+                                               empty.select(True)
+                                               o.bl_obj = empty
+
+                               bl_children = [c.bl_obj for c in children]
+                               o.bl_obj.makeParent(bl_children, 0, 1)
+                               for child in children:
+                                       if child.loc:
+                                               child.bl_obj.setLocation(child.loc)
+                                       if child.rot:
+                                               eul = euler_in_radians(child.rot.toEuler())
+                                               child.bl_obj.setEuler(eul)
+                                       if child.size:
+                                               child.bl_obj.size = child.size
+
+                       newlist.append(o)
+
+               for o in newlist: # newlist now only has objs w/o parents
+                       blob = o.bl_obj
+                       if o.loc:
+                               blob.setLocation(o.loc * blmatrix)
+                       if o.size:
+                               o.bl_obj.size = o.size
+                       if not o.rot:
+                               blob.setEuler([1.5707963267948966, 0, 0])
+                       else:
+                               matrix = o.rot * blmatrix
+                               eul = euler_in_radians(matrix.toEuler())
+                               blob.setEuler(eul)
+
        def testAC3DImport(self):
-               global GROUP
-               scene = Blender.Scene.GetCurrent()
+
+               FACE_TWOSIDE = Mesh.FaceModes['TWOSIDE']
+               FACE_TEX = Mesh.FaceModes['TEX']
+               MESH_AUTOSMOOTH = Mesh.Modes['AUTOSMOOTH']
+
+               scene = self.scene
+
+               bl_images = {} # loaded texture images
+
+               objlist = self.objlist[1:] # skip 'world'
 
                bmat = []
                for mat in self.mlist:
                        name = mat[0]
-                       m = Blender.Material.New(name)
+                       m = Material.New(name)
                        m.rgbCol = (mat[1][0], mat[1][1], mat[1][2])
                        m.amb = mat[2]
                        m.emit = mat[3]
@@ -424,102 +504,133 @@ class AC3DImport:
                        m.alpha = mat[6]
                        bmat.append(m)
 
-               for obj in self.objlist:
-                       if obj.type == 'world':
+               obj_idx = 0 # index of current obj in loop
+               for obj in objlist:
+                       if obj.type == AC_GROUP:
                                continue
-                       elif obj.type == 'group':
-                               if not GROUP: continue
-                               empty = Blender.Object.New('Empty')
-                               empty.name = obj.name
-                               scene.link(empty)
-                               if self.dads:
-                                       dadobj = Blender.Object.get(self.dads.pop())
-                                       dadobj.makeParent([empty])
-                               while obj.kids:
-                                       self.dads.append(empty.name)
-                                       obj.kids -= 1
+                       elif obj.type == AC_LIGHT:
+                               light = Lamp.New('Lamp')
+                               object = scene.objects.new(light, obj.name)
+                               object.select(True)
+                               obj.bl_obj = object
+                               if obj.data:
+                                       light.name = obj.data
                                continue
-                       mesh = Blender.NMesh.New()
-                       if obj.data: mesh.name = obj.data
-                       mesh.setMaxSmoothAngle(obj.crease) # will clamp to [1, 80]
-                       mesh.hasFaceUV(1)
 
-                       tex = None
-                       if obj.tex != '':
-                               try:
-                                       tex = Blender.Image.Load(obj.tex)
-                                       # Commented because it's unnecessary:
-                                       #tex.xrep = int(obj.texrep[0])
-                                       #tex.yrep = int(obj.texrep[1])
-                               except:
-                                       basetexname = Blender.sys.basename(obj.tex)
-                                       try:
-                                               obj.tex = self.importdir + '/' + basetexname
-                                               tex = Blender.Image.Load(obj.tex)
-                                       except:
-                                               try:
-                                                       obj.tex = TEXTURES_DIR + basetexname
-                                                       tex = Blender.Image.Load(obj.tex)
-                                               except:
-                                                       inform("Couldn't load texture: %s" % basetexname)
+                       # type AC_POLY:
 
-                       for v in obj.vlist:
-                               bvert = Blender.NMesh.Vert(v[0],v[1],v[2])
-                               mesh.verts.append(bvert)
+                       # old .ac files used empty meshes as groups, convert to a real ac group
+                       if not obj.vlist:
+                               obj.type = AC_GROUP
+                               continue
+
+                       mesh = Mesh.New()
+                       object = scene.objects.new(mesh, obj.name)
+                       object.select(True)
+                       obj.bl_obj = object
+                       if obj.data: mesh.name = obj.data
+                       mesh.degr = obj.crease # will auto clamp to [1, 80]
+
+                       mesh.verts.extend(obj.vlist)
 
                        objmat_indices = []
                        for mat in bmat:
                                if bmat.index(mat) in obj.matlist:
                                        objmat_indices.append(bmat.index(mat))
-                                       mesh.materials.append(mat)
-                       for f in obj.flist:
-                               twoside = f[-1]
-                               is_smooth = f[-2]
-                               fmat = f[-3]
-                               f=f[:-3]
-                               bface = Blender.NMesh.Face()
+                                       mesh.materials += [mat]
+
+                       for e in obj.elist:
+                               mesh.edges.extend(e)
+
+                       mesh.faces.extend(obj.flist_v)
+
+                       # checking if the .ac file had duplicate faces (Blender ignores them):
+                       if len(mesh.faces) != len(obj.flist_v):
+                               # it has, ugh. Let's clean the uv list:
+                               lenfl = len(obj.flist_v)
+                               flist = obj.flist_v
+                               uvlist = obj.flist_uv
+                               cfglist = obj.flist_cfg
+                               for f in flist:
+                                       f.sort()
+                               for fi in range(lenfl - 1):
+                                       if flist[fi] in flist[fi+1:]:
+                                               uvlist.pop(fi)
+                                               cfglist.pop(fi)
+
+                       if obj.flist_v: mesh.faceUV = True
+
+                       img = None
+                       tex = None
+                       if obj.tex != '' and mesh.faceUV:
+                               baseimgname = bsys.basename(obj.tex)
+                               if obj.tex in bl_images.keys():
+                                       img = bl_images[obj.txt]
+                                       tex = bl_textures[img]
+                               else:
+                                       try:
+                                               img = Image.Load(obj.tex)
+                                               # Commented because it's unnecessary:
+                                               #img.xrep = int(obj.texrep[0])
+                                               #img.yrep = int(obj.texrep[1])
+                                       except:
+                                               try:
+                                                       obj.tex = self.importdir + '/' + baseimgname
+                                                       img = Image.Load(obj.tex)
+                                               except:
+                                                       try:
+                                                               obj.tex = TEXTURES_DIR + baseimgname
+                                                               img = Image.Load(obj.tex)
+                                                       except:
+                                                               inform("Couldn't load texture: %s" % baseimgname)
+                                       if img:
+                                               bl_images[obj.tex] = img
+
+                       i = 0
+                       for f in obj.flist_cfg:
+                               fmat = f[0]
+                               is_smooth = f[1]
+                               twoside = f[2]
+                               bface = mesh.faces[i]
                                bface.smooth = is_smooth
-                               if twoside: bface.mode |= Blender.NMesh.FaceModes['TWOSIDE']
-                               if tex:
-                                       bface.mode |= Blender.NMesh.FaceModes['TEX']
-                                       bface.image = tex
-                               bface.materialIndex = objmat_indices.index(fmat)
+                               if twoside: bface.mode |= FACE_TWOSIDE
+                               if img:
+                                       bface.mode |= FACE_TEX
+                                       bface.image = img
+                               bface.mat = objmat_indices.index(fmat)
+                               fuv = obj.flist_uv[i]
                                if obj.texoff:
                                        uoff = obj.texoff[0]
                                        voff = obj.texoff[1]
                                        urep = obj.texrep[0]
                                        vrep = obj.texrep[1]
-                                       for vi in range(len(f)):
-                                               f[vi][1][0] *= urep
-                                               f[vi][1][1] *= vrep
-                                               f[vi][1][0] += uoff
-                                               f[vi][1][1] += voff
-
-                               for vi in range(len(f)):
-                                       bface.v.append(mesh.verts[f[vi][0]])
-                                       bface.uv.append((f[vi][1][0], f[vi][1][1]))
-                               #mesh.faces.append(bface)
-                               # quick hack, will switch from NMesh to Mesh later:
-                               if len(bface.v) > 1: mesh.addFace(bface)
-
-                       mesh.mode = 0
-                       object = Blender.NMesh.PutRaw(mesh)
-                       object.setName(obj.name)
-                       object.setEuler([1.5707963,0,0]) # align ac3d w/ Blender
-                       if self.dads:
-                               dadobj = Blender.Object.get(self.dads.pop())
-                               dadobj.makeParent([object])
+                                       for uv in fuv:
+                                               uv[0] *= urep
+                                               uv[1] *= vrep
+                                               uv[0] += uoff
+                                               uv[1] += voff
+
+                               mesh.faces[i].uv = fuv
+
+                               i += 1
+
+                       mesh.mode = MESH_AUTOSMOOTH
+
+                       obj_idx += 1
+
+               self.build_hierarchy()
+               scene.update()
 
 # End of class AC3DImport
 
 def filesel_callback(filename):
 
-       inform("Trying to import AC3D model(s) from %s ..." % filename)
-       Blender.Window.WaitCursor(1)
-       starttime = Blender.sys.time()
+       inform("\nTrying to import AC3D model(s) from:\n%s ..." % filename)
+       Window.WaitCursor(1)
+       starttime = bsys.time()
        test = AC3DImport(filename)
-       Blender.Window.WaitCursor(0)
-       endtime = Blender.sys.time() - starttime
-       inform('... done!  Data imported in %.3f seconds.\n' % endtime)
+       Window.WaitCursor(0)
+       endtime = bsys.time() - starttime
+       inform('Done! Data imported in %.3f seconds.\n' % endtime)
 
-Blender.Window.FileSelector(filesel_callback, "Import AC3D", "*.ac")
+Window.FileSelector(filesel_callback, "Import AC3D", "*.ac")