From 82bfd281f2ef3fc793cff335051d8c50ee142679 Mon Sep 17 00:00:00 2001 From: Willian Padovani Germano Date: Sun, 14 Jan 2007 18:13:47 +0000 Subject: Scripts: 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 | 332 ++++++++++++++++++++++----------- release/scripts/ac3d_import.py | 413 ++++++++++++++++++++++++++--------------- 2 files changed, 483 insertions(+), 262 deletions(-) (limited to 'release') diff --git a/release/scripts/ac3d_export.py b/release/scripts/ac3d_export.py index 2180fa1e128..80e88d933c8 100644 --- a/release/scripts/ac3d_export.py +++ b/release/scripts/ac3d_export.py @@ -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:
toggle:
- AC3D 4 mode: unset it to export without the 'crease' tag that was introduced with AC3D 4.0 and with the old material handling;
+ - global coords: transform all vertices of all meshes to global coordinates;
- 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);
- 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);
toggle is "on". Notes:
- This version is considerably faster than previous ones for large meshes;
+ This version updates:
+ - modified meshes are correctly exported, no need to apply the modifiers in Blender;
+ - correctly export each used material, be it assigned to the object or to its mesh data;
+ - exporting lines (edges) is again supported;
+ - there's a new option to choose between exporting meshes with transformed (global) coordinates or local ones;
Multiple textures per mesh are supported (mesh gets split);
- Parenting with meshes or empties as parents is converted to AC3D group -information;
+ Parents are exported as a group containing both the parent and its children;
Start mesh object names (OB: field) with "!" or "#" if you don't want them to be exported;
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;
# $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;
# -------------------------------------------------------------------------- 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: diff --git a/release/scripts/ac3d_import.py b/release/scripts/ac3d_import.py index 4dcde65fb4a..0fd5d3e5625 100644 --- a/release/scripts/ac3d_import.py +++ b/release/scripts/ac3d_import.py @@ -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:
None. Config Options:
- - 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") -- cgit v1.2.3