diff --git a/release/scripts/export_cal3d.py b/release/scripts/export_cal3d.py index 0ac395b82fe..6ed51923c24 100644 --- a/release/scripts/export_cal3d.py +++ b/release/scripts/export_cal3d.py @@ -89,7 +89,6 @@ example. The parameters are the same as below. LODS = 0 # Scale the model (not supported by Soya). -SCALE = 0.04 # See also BASE_MATRIX below, if you want to rotate/scale/translate the model at # the exportation. @@ -99,13 +98,13 @@ SCALE = 0.04 # The script should be quite re-useable for writing another Blender animation exporter. # Most of the hell of it is to deal with Blender's head-tail-roll bone's definition. -import sys, os, os.path, struct, math, string +import math import Blender import BPyMesh import BPySys import BPyArmature import BPyObject - +import bpy def best_armature_root(armature): ''' @@ -259,25 +258,35 @@ BASE_MATRIX = None # Cal3D data structures CAL3D_VERSION = 910 -MATERIALS = {} +MATERIALS = {} # keys are (mat.name, img.name) class Cal3DMaterial(object): __slots__ = 'amb', 'diff', 'spec', 'shininess', 'maps_filenames', 'id' - def __init__(self, map_filename = None): - self.amb = (255,255,255,255) - self.diff = (255,255,255,255) - self.spec = (255,255,255,255) - self.shininess = 1.0 + def __init__(self, blend_world, blend_material, blend_images): - if map_filename: - map_filename = map_filename.split('\\')[-1].split('/')[-1] - self.maps_filenames = [map_filename] + # Material Settings + if blend_world: amb = [ int(c*255) for c in blend_world.amb ] + else: amb = [0,0,0] # Default value + + if blend_material: + self.amb = tuple([int(c*blend_material.amb) for c in amb] + [255]) + self.diff = tuple([int(c*255) for c in blend_material.rgbCol] + [int(blend_material.alpha*255)]) + self.spec = tuple([int(c*255) for c in blend_material.rgbCol] + [int(blend_material.alpha*255)]) + self.shininess = (float(blend_material.hard)-1)/5.10 else: - self.maps_filenames = [] + self.amb = tuple(amb + [255]) + self.diff = (255,255,255,255) + self.spec = (255,255,255,255) + self.shininess = 1.0 + + self.maps_filenames = [] + for image in blend_images: + if image: + self.maps_filenames.append( image.filename.split('\\')[-1].split('/')[-1] ) self.id = len(MATERIALS) - MATERIALS[map_filename] = self - + MATERIALS[blend_material, blend_images] = self + # new xml format def writeCal3D(self, file): file.write('\n') @@ -286,144 +295,170 @@ class Cal3DMaterial(object): file.write('\t%i %i %i %i\n' % self.amb) file.write('\t%i %i %i %i\n' % self.diff) file.write('\t%i %i %i %i\n' % self.spec) - file.write('\t%i\n' % self.shininess) + file.write('\t%.6f\n' % self.shininess) for map_filename in self.maps_filenames: file.write('\t%s\n' % map_filename) - + file.write('\n') class Cal3DMesh(object): - __slots__ = 'name', 'submeshes' - def __init__(self, ob, blend_mesh): + __slots__ = 'name', 'submeshes', 'matrix', 'matrix_normal' + def __init__(self, ob, blend_mesh, blend_world): self.name = ob.name self.submeshes = [] - matrix = ob.matrixWorld - matrix_no = matrix.copy().rotationPart() + BPyMesh.meshCalcNormals(blend_mesh) + + self.matrix = ob.matrixWorld + self.matrix_normal = self.matrix.copy().rotationPart() + #if BASE_MATRIX: # matrix = matrix_multiply(BASE_MATRIX, matrix) - faces = list(blend_mesh.faces) - while faces: - image = faces[0].image - image_filename = image and image.filename - material = MATERIALS.get(image_filename) or Cal3DMaterial(image_filename) - outputuv = len(material.maps_filenames) > 0 + face_groups = {} + blend_materials = blend_mesh.materials + uvlayers = () + mat = None # incase we have no materials + if blend_mesh.faceUV: + uvlayers = blend_mesh.getUVLayerNames() + if len(uvlayers) == 1: + for f in blend_mesh.faces: + image = (f.image,) # bit in a tuple so we can match multi UV code + if blend_materials: mat = blend_materials[f.mat] # if no materials, mat will always be None + face_groups.setdefault( (mat,image), (mat,image,[]) )[2].append( f ) + else: + # Multi UV's + face_multi_images = [[] for i in xrange(len(blend_mesh.faces))] + face_multi_uvs = [[[] for i in xrange(len(f)) ] for f in blend_mesh.faces] + for uvlayer in uvlayers: + blend_mesh.activeUVLayer = uvlayer + for i, f in enumerate(blend_mesh.faces): + face_multi_images[i].append(f.image) + if f.image: + for j, uv in enumerate(f.uv): + face_multi_uvs[i][j].append( tuple(uv) ) + + # Convert UV's to tuples so they can be compared with eachother + # when creating new verts + for fuv in face_multi_uvs: + for i, uv in enumerate(fuv): + fuv[i] = tuple(uv) + + for i, f in enumerate(blend_mesh.faces): + image = tuple(face_multi_images[i]) + if blend_materials: mat = blend_materials[f.mat] + face_groups.setdefault( (mat,image), (mat,image,[]) )[2].append( f ) + else: + # No UV's + for f in blend_mesh.faces: + if blend_materials: mat = blend_materials[f.mat] + face_groups.setdefault( (mat,()), (mat,(),[]) )[2].append( f ) + + for blend_material, blend_images, faces in face_groups.itervalues(): + + try: material = MATERIALS[blend_material, blend_images] + except: material = MATERIALS[blend_material, blend_images] = Cal3DMaterial(blend_world, blend_material, blend_images) - # TODO add material color support here submesh = Cal3DSubMesh(self, material, len(self.submeshes)) self.submeshes.append(submesh) - vertices = {} - for face in faces[:]: - if (face.image and face.image.filename) == image_filename: - faces.remove(face) - - if not face.smooth: - normal = face.no * matrix_no - normal.normalize() - - face_vertices = [] - face_v = face.v - for i, blend_vert in enumerate(face_v): - vertex = vertices.get(blend_vert.index) - if not vertex: - coord = blend_vert.co * matrix - - if face.smooth: - normal = blend_vert.no * matrix_no - normal.normalize() - - vertex = vertices[blend_vert.index] = Cal3DVertex(coord, normal, len(submesh.vertices)) - submesh.vertices.append(vertex) - - influences = blend_mesh.getVertexInfluences(blend_vert.index) - # should this really be a warning? (well currently enabled, - # because blender has some bugs where it doesn't return - # influences in python api though they are set, and because - # cal3d<=0.9.1 had bugs where objects without influences - # aren't drawn. - if not influences: - print 'A vertex of object "%s" has no influences.\n(This occurs on objects placed in an invisible layer, you can fix it by using a single layer)' % ob.name - - # sum of influences is not always 1.0 in Blender ?!?! - sum = 0.0 - for bone_name, weight in influences: - sum += weight - - for bone_name, weight in influences: - if bone_name not in BONES: - print 'Couldnt find bone "%s" which influences object "%s"' % (bone_name, ob.name) - continue - if weight: - vertex.influences.append(Cal3DInfluence(BONES[bone_name], weight / sum)) - - elif not face.smooth: - # We cannot share vertex for non-smooth faces, since Cal3D does not - # support vertex sharing for 2 vertices with different normals. - # => we must clone the vertex. - - old_vertex = vertex - vertex = Cal3DVertex(vertex.loc, normal, len(submesh.vertices)) - submesh.vertices.append(vertex) - - vertex.cloned_from = old_vertex - vertex.influences = old_vertex.influences - old_vertex.clones.append(vertex) - - if blend_mesh.faceUV: - uv = [face.uv[i][0], 1.0 - face.uv[i][1]] - if not vertex.maps: - if outputuv: vertex.maps.append(Cal3DMap(*uv)) - elif (vertex.maps[0].u != uv[0]) or (vertex.maps[0].v != uv[1]): - # This vertex can be shared for Blender, but not for Cal3D !!! - # Cal3D does not support vertex sharing for 2 vertices with - # different UV texture coodinates. - # => we must clone the vertex. - - for clone in vertex.clones: - if (clone.maps[0].u == uv[0]) and (clone.maps[0].v == uv[1]): - vertex = clone - break - else: # Not yet cloned... - old_vertex = vertex - vertex = Cal3DVertex(vertex.loc, vertex.normal, len(submesh.vertices)) - submesh.vertices.append(vertex) - - vertex.cloned_from = old_vertex - vertex.influences = old_vertex.influences - if outputuv: vertex.maps.append(Cal3DMap(*uv)) - old_vertex.clones.append(vertex) - - face_vertices.append(vertex) - - # Split faces with more than 3 vertices - for i in xrange(1, len(face.v) - 1): - submesh.faces.append(Cal3DFace(face_vertices[0], face_vertices[i], face_vertices[i + 1])) - # Computes LODs info - if LODS: - submesh.compute_lods() + # Check weather we need to write UVs, dont do it if theres no image + # Multilayer UV's have alredy checked that they have images when + # building face_multi_uvs + if len(uvlayers) == 1: + if blend_images == (None,): + write_single_layer_uvs = False + else: + write_single_layer_uvs = True + + + for face in faces: + + if not face.smooth: + normal = face.no + + face_vertices = [] + face_v = face.v + + + if len(uvlayers)>1: + for i, blend_vert in enumerate(face_v): + if face.smooth: normal = blend_vert.no + vertex = submesh.getVertex(blend_mesh, blend_vert.index, blend_vert.co, normal, face_multi_uvs[face.index][i]) + face_vertices.append(vertex) + + elif len(uvlayers)==1: + if write_single_layer_uvs: + face_uv = face.uv + + for i, blend_vert in enumerate(face_v): + if face.smooth: normal = blend_vert.no + if write_single_layer_uvs: uvs = (tuple(face_uv[i]),) + else: uvs = () + + vertex = submesh.getVertex(blend_mesh, blend_vert.index, blend_vert.co, normal, uvs ) + face_vertices.append(vertex) + else: + # No UVs + for i, blend_vert in enumerate(face_v): + if face.smooth: normal = blend_vert.no + vertex = submesh.getVertex(blend_mesh, blend_vert.index, blend_vert.co, normal, () ) + face_vertices.append(vertex) + + + # Split faces with more than 3 vertices + for i in xrange(1, len(face) - 1): + submesh.faces.append(Cal3DFace(face_vertices[0], face_vertices[i], face_vertices[i + 1])) def writeCal3D(self, file): file.write('\n') file.write('
\n' % CAL3D_VERSION) file.write('\n' % len(self.submeshes)) for submesh in self.submeshes: - submesh.writeCal3D(file) + submesh.writeCal3D(file, self.matrix, self.matrix_normal) file.write('\n') + class Cal3DSubMesh(object): - __slots__ = 'material', 'vertices', 'faces', 'nb_lodsteps', 'springs', 'id' + __slots__ = 'material', 'vertices', 'vert_mapping', 'vert_count', 'faces', 'nb_lodsteps', 'springs', 'id' def __init__(self, mesh, material, id): self.material = material self.vertices = [] + self.vert_mapping = {} # map original indicies to local + self.vert_count = 0 self.faces = [] self.nb_lodsteps = 0 self.springs = [] self.id = id + def getVertex(self, blend_mesh, blend_index, loc, normal, maps): + index_map = self.vert_mapping.get(blend_index) + if index_map == None: + self.vert_mapping[blend_index] = self.vert_count + vertex = Cal3DVertex(loc, normal, maps, blend_mesh.getVertexInfluences(blend_index)) + self.vertices.append([vertex]) + self.vert_count +=1 + return vertex + else: + vertex_list = self.vertices[index_map] + + for v in vertex_list: + #print "TEST", v.normal, normal + if v.normal == normal and\ + v.maps == maps: + print "reuseing" + return v + + # No match, add a new vert + # Use the first verts influences + vertex = Cal3DVertex(coord, normal, uv, vertex_list[0].influences) + vertex_list.append(vertex) + self.vert_count +=1 + return vertex + + def compute_lods(self): """Computes LODs info for Cal3D (there's no Blender related stuff here).""" @@ -525,52 +560,85 @@ class Cal3DSubMesh(object): for i in xrange(len(new_vertices)): new_vertices[i].id = i self.vertices = new_vertices - def writeCal3D(self, file): + def writeCal3D(self, file, matrix, matrix_normal): + file.write('\t\n' % \ (self.nb_lodsteps, len(self.springs), len(self.material.maps_filenames))) - for item in self.vertices: item.writeCal3D(file) - for item in self.springs: item.writeCal3D(file) - for item in self.faces: item.writeCal3D(file) + i = 0 + for v in self.vertices: + for item in v: + item.id = i + item.writeCal3D(file, matrix, matrix_normal) + i += 1 + + for item in self.springs: + item.writeCal3D(file) + for item in self.faces: + item.writeCal3D(file) file.write('\t\n') class Cal3DVertex(object): __slots__ = 'loc','normal','collapse_to','face_collapse_count','maps','influences','weight','cloned_from','clones','id' - def __init__(self, loc, normal, id): + def __init__(self, loc, normal, maps, blend_influences): self.loc = loc self.normal = normal self.collapse_to = None self.face_collapse_count = 0 - self.maps = [] - self.influences = [] + self.maps = maps self.weight = None self.cloned_from = None self.clones = [] - self.id = id + self.id = -1 + + self.influences = [] + # should this really be a warning? (well currently enabled, + # because blender has some bugs where it doesn't return + # influences in python api though they are set, and because + # cal3d<=0.9.1 had bugs where objects without influences + # aren't drawn. + #if not blend_influences: + # print 'A vertex of object "%s" has no influences.\n(This occurs on objects placed in an invisible layer, you can fix it by using a single layer)' % ob.name + + # sum of influences is not always 1.0 in Blender ?!?! + sum = 0.0 + for bone_name, weight in blend_influences: + sum += weight + + for bone_name, weight in blend_influences: + bone = BONES.get(bone_name) + if not bone: # keys + # print 'Couldnt find bone "%s" which influences object "%s"' % (bone_name, ob.name) + continue + + if weight: + self.influences.append(Cal3DInfluence(BONES[bone_name], weight / sum)) - def writeCal3D(self, file): + + def writeCal3D(self, file, matrix, matrix_normal): if self.collapse_to: collapse_id = self.collapse_to.id else: collapse_id = -1 file.write('\t\t\n' % \ (self.id, len(self.influences))) - file.write('\t\t\t%.6f %.6f %.6f\n' % (self.loc[0], self.loc[1], self.loc[2])) - file.write('\t\t\t%.6f %.6f %.6f\n' % \ - (self.normal[0], self.normal[1], self.normal[2])) + file.write('\t\t\t%.6f %.6f %.6f\n' % tuple(self.loc*matrix)) + file.write('\t\t\t%.6f %.6f %.6f\n' % tuple( (self.normal*matrix_normal).normalize() )) if collapse_id != -1: file.write('\t\t\t%i\n' % collapse_id) file.write('\t\t\t%i\n' % \ self.face_collapse_count) - for item in self.maps: - item.writeCal3D(file) + for uv in self.maps: + # we cant have more UV's then our materials image maps + # check for this + file.write('\t\t\t%.6f %.6f\n' % uv) for item in self.influences: item.writeCal3D(file) @@ -579,16 +647,6 @@ class Cal3DVertex(object): file.write('\t\t\t%.6f\n' % len(self.weight)) file.write('\t\t\n') - -class Cal3DMap(object): - __slots__ = 'u', 'v' - def __init__(self, u, v): - self.u = u - self.v = v - - def writeCal3D(self, file): - file.write('\t\t\t%.6f %.6f\n' % (self.u, self.v)) - class Cal3DInfluence(object): __slots__ = 'bone', 'weight' def __init__(self, bone, weight): @@ -794,19 +852,19 @@ def export_cal3d(filename, PREF_SCALE=0.1, PREF_BAKE_MOTION = True, PREF_ACT_ACT #if EXPORT_FOR_SOYA: # global BASE_MATRIX # BASE_MATRIX = matrix_rotate_x(-math.pi / 2.0) - # Get the scene - - scene = Blender.Scene.GetCurrent() + # Get the sce + sce = bpy.data.scenes.active + blend_world = sce.world # ---- Export skeleton (armature) ---------------------------------------- skeleton = Cal3DSkeleton() - blender_armature = [ob for ob in scene.objects.context if ob.type == 'Armature'] + blender_armature = [ob for ob in sce.objects.context if ob.type == 'Armature'] if len(blender_armature) > 1: print "Found multiple armatures! using ",armatures[0].name if blender_armature: blender_armature = blender_armature[0] else: # Try find a meshes armature - for ob in scene.objects.context: + for ob in sce.objects.context: blender_armature = BPyObject.getObjectArmature(ob) if blender_armature: break @@ -823,13 +881,12 @@ def export_cal3d(filename, PREF_SCALE=0.1, PREF_BAKE_MOTION = True, PREF_ACT_ACT # ---- Export Mesh data --------------------------------------------------- meshes = [] - for ob in scene.objects.context: + for ob in sce.objects.context: if ob.type != 'Mesh': continue blend_mesh = ob.getData(mesh=1) - BPyMesh.meshCalcNormals(blend_mesh) if not blend_mesh.faces: continue - meshes.append( Cal3DMesh(ob, blend_mesh) ) + meshes.append( Cal3DMesh(ob, blend_mesh, blend_world) ) # ---- Export animations -------------------------------------------------- backup_action = blender_armature.action @@ -967,7 +1024,7 @@ def export_cal3d(filename, PREF_SCALE=0.1, PREF_BAKE_MOTION = True, PREF_ACT_ACT cfg = open((filename), "wb") cfg.write('# Cal3D model exported from Blender with export_cal3d.py\n') - if SCALE != 1.0: cfg.write('scale=%.6f\n' % PREF_SCALE) + if PREF_SCALE != 1.0: cfg.write('scale=%.6f\n' % PREF_SCALE) fname = file_only_noext + '.xsf' file = open( base_only + fname, "wb") @@ -997,10 +1054,8 @@ def export_cal3d(filename, PREF_SCALE=0.1, PREF_BAKE_MOTION = True, PREF_ACT_ACT materials = MATERIALS.values() materials.sort(key = lambda a: a.id) for material in materials: - if material.maps_filenames: - fname = new_name(material.maps_filenames[0].split('\\')[-1].split('/')[-1], '.xrf') - else: - fname = new_name('plain', '.xrf') + # Just number materials, its less trouble + fname = new_name(str(material.id), '.xrf') file = open(base_only + fname, "wb") material.writeCal3D(file) @@ -1023,7 +1078,7 @@ def export_cal3d_ui(filename): PREF_ACT_ACTION_ONLY= Blender.Draw.Create(1) block = [\ - ('Scale: ', PREF_SCALE, 0.01, 100, "The scale to set in the Cal3d .cfg file"),\ + ('Scale: ', PREF_SCALE, 0.01, 100, "The scale to set in the Cal3d .cfg file (unsupported by soya)"),\ ('Baked Motion', PREF_BAKE_MOTION, 'use final pose position instead of ipo keyframes (IK and constraint support)'),\ ('Active Action', PREF_ACT_ACTION_ONLY, 'Only export the active action applied to this armature, otherwise export all'),\ ] @@ -1031,11 +1086,13 @@ def export_cal3d_ui(filename): if not Blender.Draw.PupBlock("Cal3D Options", block): return + Blender.Window.WaitCursor(1) export_cal3d(filename, 1.0/PREF_SCALE.val, PREF_BAKE_MOTION.val, PREF_ACT_ACTION_ONLY.val) + Blender.Window.WaitCursor(0) -#import os +import os if __name__ == '__main__': - Blender.Window.FileSelector(export_cal3d_ui, "Cal3D Export", Blender.Get('filename').replace('.blend', '.cfg')) - #export_cal3d('/test' + '.cfg') - #os.system('cd /; wine /cal3d_miniviewer.exe /test.cfg') + #Blender.Window.FileSelector(export_cal3d_ui, "Cal3D Export", Blender.Get('filename').replace('.blend', '.cfg')) + export_cal3d('/test' + '.cfg') + os.system('cd /; wine /cal3d_miniviewer.exe /test.cfg')