## ***** BEGIN GPL LICENSE BLOCK ***** # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # ***** END GPL LICENCE BLOCK ***** # # # REQUIRED OPTIONS - # - Make Normals Consistent # - Remove Doubles # ********************************** bl_info = { "name": "ASCII Scene Exporter", "author": "Richard Bartlett, MCampagnini", "version": (1, 4, 1), "blender": (2, 5, 9), "api": 35622, "location": "File > Export > ASCII Scene Export(.ase)", "description": "ASCII Scene Export(.ase)", "warning": "", "wiki_url": "", "tracker_url": "", "category": "Import-Export" } import os import bpy import math import time #TODO: aseFloat = lambda x: '''{0: 0.4f}'''.format(x) scale = 1.0 #OPTIONS remove_doubles = True import bpy from bpy.props import * ############## # ERROR LIST # ############## class Error(Exception): pass class NoMaterialError(Error): # No material assigned to a non-collision object. def __init__(self, objName): self.msg = 'No material assigned to object ' + objName print(self.msg) class NoTextureError(Error): # No texture image assigned to a non-collision object. def __init__(self, objName): self.msg = 'No texture image assigned to object ' + objName print(self.msg) class DuplicateTextureError(Error): # No texture image assigned to a non-collision object. def __init__(self, objName): self.msg = 'Duplicate texture image found on object ' + objName ################ # HEADER BLOCK # ################ class cHeader: def __init__(self): self.comment = "Blender 2.59 Ascii Scene Exporter" def __repr__(self): return '''*3DSMAX_ASCIIEXPORT\t200\n*COMMENT "v{0}"\n'''.format(self.comment) #################### # SCENE INFO BLOCK # #################### class cScene: def __init__(self): self.filename = bpy.data.filepath self.firstframe = 0 self.lastframe = 100 self.framespeed = 30 self.ticksperframe = 160 self.backgroundstatic = ''.join([aseFloat(x) for x in [0.0, 0.0, 0.0]]) self.ambientstatic = ''.join([aseFloat(x) for x in [0.0, 0.0, 0.0]]) def __repr__(self): return '''*SCENE {{\n\t*SCENE_FILENAME "{0}"\ \n\t*SCENE_FIRSTFRAME {1}\ \n\t*SCENE_LASTFRAME {2}\ \n\t*SCENE_FRAMESPEED {3}\ \n\t*SCENE_TICKSPERFxRAME {4}\ \n\t*SCENE_BACKGROUND_STATIC {5}\ \n\t*SCENE_AMBIENT_STATIC {6}\ \n}}\n'''.format(self.filename, self.firstframe, self.lastframe, self.framespeed, self.ticksperframe, self.backgroundstatic, self.ambientstatic) ####################### # MATERIAL INFO BLOCK # ####################### class cMaterialList: def __init__(self): self.dump = '' self.multiMat = False self.material_list = [] # list of all materials used in selected objects self.materials = '' # material data # get all materials in all non-collision objects, add them to the material_list (no duplicates) for object in bpy.context.selected_objects: # apply object transformations bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN') bpy.ops.object.transform_apply(scale = True) bpy.ops.object.transform_apply(location=True) bpy.ops.object.transform_apply(rotation=True) if isColliderObject(self, object): continue for slot in object.material_slots: if self.material_list.count(slot.material) == 0: self.material_list.append(slot.material) # determine if the scene uses multiple materials if len(self.material_list) == 0: raise NoMaterialError(object.name) elif len(self.material_list) > 1: self.multiMat = True # get the first object that is not a collision model for obj in bpy.context.selected_objects: if isColliderObject(self, obj) == False: bpy.context.scene.objects.active = obj break object = bpy.context.active_object # Initialize base material if self.multiMat == False: self.materials = str(cMaterial(self.material_list[0])) elif self.multiMat == True: self.materials = str(cMultiMaterial(self.material_list)) self.dump = '''*MATERIAL_LIST {{\ \n\t*MATERIAL_COUNT 1\ \n\t*MATERIAL 0 {{\ {0} \n\t}}\ \n}}'''.format(self.materials) def __repr__(self): return self.dump class cMultiMaterial: def __init__(self, material_list): slot = material_list[0] self.material_list = material_list self.dump = '' self.matDump = '' self.name = slot.name self.numSubMtls = len(material_list) self.matClass = 'Multi/Sub-Object' self.ambient = ''.join([aseFloat(x) for x in [0.0, 0.0, 0.0]]) self.diffuse = ''.join([aseFloat(x) for x in slot.diffuse_color]) self.specular = ''.join([aseFloat(x) for x in slot.specular_color]) self.shine = aseFloat(slot.specular_hardness / 511) self.shinestrength = aseFloat(slot.specular_intensity) self.transparency = aseFloat(slot.translucency * slot.alpha) self.wiresize = aseFloat(1.0) # Build SubMaterials for index, slot in enumerate(self.material_list): self.matDump += '''\n\t\t*SUBMATERIAL {0} {{\ {1} \n\t\t}}'''.format(index, cMaterial(slot)) # Material Definition self.dump += '''\n\t\t*MATERIAL_NAME "{0}"\ \n\t\t*MATERIAL_CLASS {1}\ \n\t\t*MATERIAL_AMBIENT {2}\ \n\t\t*MATERIAL_DIFFUSE {3}\ \n\t\t*MATERIAL_SPECULAR {4}\ \n\t\t*MATERIAL_SHINE {5}\ \n\t\t*MATERIAL_SHINESTRENGTH {6}\ \n\t\t*MATERIAL_TRANSPARENCY {7}\ \n\t\t*MATERIAL_WIRESIZE {8}\ \n\t\t*NUMSUBMTLS {9}\ {10}'''.format(self.name, self.matClass, self.ambient, self.diffuse, self.specular, self.shine, self.shinestrength, self.transparency, self.wiresize, self.numSubMtls, self.matDump) def __repr__(self): return self.dump class cMaterial: def __init__(self, slot): self.dump = '' self.name = slot.name self.matClass = 'Standard' self.ambient = ''.join([aseFloat(x) for x in [0.0, 0.0, 0.0]]) self.diffuse = ''.join([aseFloat(x) for x in slot.diffuse_color]) self.specular = ''.join([aseFloat(x) for x in slot.specular_color]) self.shine = aseFloat(slot.specular_hardness / 511) self.shinestrength = aseFloat(slot.specular_intensity) self.transparency = aseFloat(slot.translucency * slot.alpha) self.wiresize = aseFloat(1.0) # Material Definition self.shading = str(slot.specular_shader).capitalize() self.xpfalloff = aseFloat(0.0) self.xptype = 'Filter' self.falloff = 'In' self.soften = False self.diffusemap = cDiffusemap(slot.texture_slots[0]) self.submtls = [] self.selfillum = aseFloat(slot.emit) self.dump = '''\n\t\t*MATERIAL_NAME "{0}"\ \n\t\t*MATERIAL_CLASS {1}\ \n\t\t*MATERIAL_AMBIENT {2}\ \n\t\t*MATERIAL_DIFFUSE {3}\ \n\t\t*MATERIAL_SPECULAR {4}\ \n\t\t*MATERIAL_SHINE {5}\ \n\t\t*MATERIAL_SHINESTRENGTH {6}\ \n\t\t*MATERIAL_TRANSPARENCY {7}\ \n\t\t*MATERIAL_WIRESIZE {8}\ \n\t\t*MATERIAL_SHADING {9}\ \n\t\t*MATERIAL_XP_FALLOFF {10}\ \n\t\t*MATERIAL_SELFILLUM {11}\ \n\t\t*MATERIAL_FALLOFF {12}\ \n\t\t*MATERIAL_XP_TYPE {13}\ \n\t{14}'''.format(self.name, self.matClass, self.ambient, self.diffuse, self.specular, self.shine, self.shinestrength, self.transparency, self.wiresize, self.shading, self.xpfalloff, self.selfillum, self.falloff, self.xptype, self.diffdump()) def diffdump(self): for x in [self.diffusemap]: return x def __repr__(self): return self.dump class cDiffusemap: def __init__(self, slot): import os self.dump = '' self.name = slot.name self.subno = 1 self.amount = aseFloat(1.0) if slot.texture.type == 'IMAGE': self.mapclass = 'Bitmap' self.bitmap = slot.texture.image.filepath if slot.texture.image.has_data: pass else: self.bitmap = '\\\\base\\' + self.bitmap.replace('/','\\') else: self.mapclass = 'None' self.bitmap = 'None' self.type = 'Screen' self.uoffset = aseFloat(0.0) self.voffset = aseFloat(0.0) self.utiling = aseFloat(1.0) self.vtiling = aseFloat(1.0) self.angle = aseFloat(0.0) self.blur = aseFloat(1.0) self.bluroffset = aseFloat(0.0) self.noiseamt = aseFloat(1.0) self.noisesize = aseFloat(1.0) self.noiselevel = 1 self.noisephase = aseFloat(0.0) self.bitmapfilter = 'Pyramidal' self.dump = '''\t*MAP_DIFFUSE {{\ \n\t\t\t\t*MAP_NAME "{0}"\ \n\t\t\t\t*MAP_CLASS "{1}"\ \n\t\t\t\t*MAP_SUBNO {2}\ \n\t\t\t\t*MAP_AMOUNT {3}\ \n\t\t\t\t*BITMAP "{4}"\ \n\t\t\t\t*MAP_TYPE {5}\ \n\t\t\t\t*UVW_U_OFFSET {6}\ \n\t\t\t\t*UVW_V_OFFSET {7}\ \n\t\t\t\t*UVW_U_TILING {8}\ \n\t\t\t\t*UVW_V_TILING {9}\ \n\t\t\t\t*UVW_ANGLE {10}\ \n\t\t\t\t*UVW_BLUR {11}\ \n\t\t\t\t*UVW_BLUR_OFFSET {12}\ \n\t\t\t\t*UVW_NOUSE_AMT {13}\ \n\t\t\t\t*UVW_NOISE_SIZE {14}\ \n\t\t\t\t*UVW_NOISE_LEVEL {15}\ \n\t\t\t\t*UVW_NOISE_PHASE {16}\ \n\t\t\t\t*BITMAP_FILTER {17}\ \n\t\t\t}}\ '''.format(self.name, self.mapclass, self.subno, self.amount, self.bitmap, self.type, self.uoffset, self.voffset, self.utiling, self.vtiling, self.angle, self.blur, self.bluroffset, self.noiseamt, self.noisesize, self.noiselevel, self.noisephase, self.bitmapfilter) def __repr__(self): return self.dump class cGeomObjList: def __init__(self): self.geolist = [] if len(bpy.context.selected_objects) > 0: for object in bpy.context.selected_objects: if object.type == 'MESH': geoobj = cGeomObject(object) self.geolist.append(geoobj) def dump(self): temp = '' for x in self.geolist: temp = temp + str(x) return temp def __repr__(self): return self.dump() class cGeomObject: def __init__(self, object): bpy.context.scene.objects.active = object self.name = object.name self.prop_motionblur = 0 self.prop_castshadow = 1 self.prop_recvshadow = 1 self.material_ref = 0 self.nodetm = cNodeTM(object) self.mesh = cMesh(object) self.dump = '''\n*GEOMOBJECT {{\n\t*NODE_NAME "{0}"\n{1}\n{2}\n\t*PROP_MOTIONBLUR {3}\n\t*PROP_CASTSHADOW {4}\n\t*PROP_RECVSHADOW {5}\n\t*MATERIAL_REF {6}\n}}'''.format(self.name, self.nodetm, self.mesh, self.prop_motionblur, self.prop_castshadow, self.prop_recvshadow, self.material_ref) def __repr__(self): return self.dump class cNodeTM: def __init__(self, object): self.name = object.name self.inherit_pos = '0 0 0' self.inherit_rot = '0 0 0' self.inherit_scl = '0 0 0' self.tm_row0 = '1.0000 0.0000 0.0000' self.tm_row1 = '0.0000 1.0000 0.0000' self.tm_row2 = '0.0000 0.0000 1.0000' self.tm_row3 = '0.0000 0.0000 0.0000' self.tm_pos = '0.0000 0.0000 0.0000' self.tm_rotaxis = '0.0000 0.0000 0.0000' self.tm_rotangle = '0.0000' self.tm_scale = '1.0000 1.0000 1.0000' self.tm_scaleaxis = '0.0000 0.0000 0.0000' self.tm_scaleaxisang = '0.0000' self.dump = '''\t*NODE_TM {{\ \n\t\t*NODE_NAME "{0}"\ \n\t\t*INHERIT_POS {1}\ \n\t\t*INHERIT_ROT {2}\ \n\t\t*INHERIT_SCL {3}\ \n\t\t*TM_ROW0 {4}\ \n\t\t*TM_ROW1 {5}\ \n\t\t*TM_ROW2 {6}\ \n\t\t*TM_ROW3 {7}\ \n\t\t*TM_POS {8}\ \n\t\t*TM_ROTAXIS {9}\ \n\t\t*TM_ROTANGLE {10}\ \n\t\t*TM_SCALE {11}\ \n\t\t*TM_SCALEAXIS {12}\ \n\t\t*TM_SCALEAXISANG {13}\ \n\t}}'''.format(self.name, self.inherit_pos, self.inherit_rot, self.inherit_scl, self.tm_row0, self.tm_row1, self.tm_row2, self.tm_row3, self.tm_pos, self.tm_rotaxis, self.tm_rotangle, self.tm_scale, self.tm_scaleaxis, self.tm_scaleaxisang) def __repr__(self): return self.dump class cMesh: def __init__(self, object): bpy.context.scene.objects.active = object print('Gathering data for ' + str(bpy.context.scene.objects.active)) bpy.ops.mesh.reveal if isColliderObject(self, object) == False: object.data.uv_textures.active_index = 0 object.data.uv_texture_stencil_index = 0 self.tvertlist = cTVertlist(object) self.numtvertex = self.tvertlist.length self.numtvfaces = len(object.data.uv_texture_stencil.data) self.tfacelist = cTFacelist(self.numtvfaces) self.uvmapchannels = self.uvdump(object) # OUTPUT self.tvertlist_str = '\n\t\t*MESH_TVERTLIST ' + str(self.tvertlist) self.numtvertex_str = '\n\t\t*MESH_NUMTVERTEX ' + str(self.numtvertex) self.numtvfaces_str = '\n\t\t*MESH_NUMTVFACES ' + str(self.numtvfaces) self.tfacelist_str = '\n\t\t*MESH_TFACELIST ' + str(self.tfacelist) else: self.tvertlist_str = '' self.numtvertex_str = '' self.numtvfaces_str = '' self.tfacelist_str = '' self.uvmapchannels = '' self.timevalue = '0' self.numvertex = len(object.data.vertices) self.numfaces = len(object.data.faces) self.vertlist = cVertlist(object) self.facelist = cFacelist(object) if len(object.data.vertex_colors) > 0: self.cvertlist = cCVertlist(object) self.numcvertex = self.cvertlist.length self.numcvfaces = len(object.data.vertex_colors[0].data) self.cfacelist = cCFacelist(self.numcvfaces) # change them into strings now self.cvertlist = '\n{0}'.format(self.cvertlist) self.numcvertex = '\n\t\t*MESH_NUMCVERTEX {0}'.format(self.numcvertex) self.numcvfaces = '\n\t\t*MESH_NUMCVFACES {0}'.format(self.numcvfaces) self.cfacelist = '\n{0}'.format(self.cfacelist) else: self.cvertlist = '' self.numcvertex = '' self.numcvfaces = '' self.cfacelist = '' self.normals = cNormallist(object) # get uv layer names for specified object def getUVLayerNames(self, object): self.uvLayerNames = [] obj = object.data for uv in obj.uv_textures.keys(): self.uvLayerNames.append(str(uv)) def uvdump(self, object): self.mappingchannels = '' # if there is more than 1 uv layer if isColliderObject(self, object) == False: self.getUVLayerNames(object) if len(self.uvLayerNames) > 1: # save uv actives active_uv = object.data.uv_textures.active_index obj = object.data activeUV = 0 for uvname in self.uvLayerNames: if activeUV == 0: activeUV += 1 continue obj.uv_textures.active_index = activeUV obj.uv_texture_stencil_index = activeUV self.uvm_tvertlist = cTVertlist(object) self.uvm_numtvertex = self.uvm_tvertlist.length self.uvm_numtvfaces = len(object.data.uv_texture_stencil.data) self.uvm_tfacelist = cTFacelist(self.uvm_numtvfaces) if len(object.data.vertex_colors) > 0: self.uvm_cvertlist = cCVertlist(object) self.uvm_numcvertex = self.uvm_cvertlist.length self.uvm_numcvfaces = len(object.data.vertex_colors[0].data) self.uvm_cfacelist = cCFacelist(self.uvm_numcvfaces) # self.uvm_cvertlist = '\n{0}'.format(self.uvm_cvertlist) self.uvm_numcvertex = '\n\t\t*MESH_NUMCVERTEX {0}'.format(self.uvm_numcvertex) self.uvm_numcvfaces = '\n\t\t*MESH_NUMCVFACES {0}'.format(self.uvm_numcvfaces) self.uvm_cfacelist = '\n{0}'.format(self.uvm_cfacelist) else: self.uvm_numcvertex = '' self.uvm_numcvfaces = '' self.uvm_cvertlist = '' self.uvm_cfacelist = '' # print extra mapping channels self.mappingchannels += '''\n\t\t*MESH_MAPPINGCHANNEL {0} {{\n\t\t\t*MESH_NUMTVERTEX {1}\n\t\t\t*MESH_TVERTLIST {2}\n\t\t*MESH_NUMTVFACES {3}\n\t\t*MESH_TFACELIST {4}{5}{6}{7}{8}\n\t\t}}'''.format(str(activeUV+1), self.uvm_numtvertex, self.uvm_tvertlist, self.uvm_numtvfaces, self.uvm_tfacelist, self.uvm_numcvertex, self.uvm_cvertlist, self.uvm_numcvfaces, self.uvm_cfacelist) activeUV = activeUV + 1 # restore uv actives object.data.uv_textures.active_index = active_uv return self.mappingchannels # UV textures go AFTER MESH_FACE_LIST # MESH_NUMTVERTEX, MESH_TVERTLIST, MESH_NUMTVFACES, MESH_TFACELIST def __repr__(self): temp = '''\t*MESH {{\n\t\t*TIMEVALUE {0}\n\t\t*MESH_NUMVERTEX {1}\n\t\t*MESH_NUMFACES {2}\n\t\t*MESH_VERTEX_LIST {3}\n\t\t*MESH_FACE_LIST {4}{5}{6}{7}{8}{9}{10}{11}{12}{13}\n{14}\n\t}}'''.format(self.timevalue, self.numvertex, self.numfaces, self.vertlist, self.facelist, self.numtvertex_str, self.tvertlist_str, self.numtvfaces_str, self.tfacelist_str, self.numcvertex, self.cvertlist, self.numcvfaces, self.cfacelist, self.uvmapchannels, self.normals) return temp class cVertlist: def __init__(self, object): print('\tVerts data for ' + object.name) self.vertlist = [] for data in object.data.vertices: temp = cVert(data.index, data.co.to_tuple(4)) self.vertlist.append(temp) def dump(self): temp = '' for x in self.vertlist: temp += str(x) return temp def __repr__(self): return '''{{\n{0}\t\t}}'''.format(self.dump()) class cVert: def __init__(self, index, coord): global scale self.index = index self.x = aseFloat(coord[0] * scale) self.y = aseFloat(coord[1] * scale) self.z = aseFloat(coord[2] * scale) def __repr__(self): return '''\t\t\t*MESH_VERTEX {0} {1} {2} {3}\n'''.format(self.index, self.x, self.y, self.z) class cFacelist: def __init__(self, object): self.facelist = [] collider = isColliderObject(self, object) sgID = 0 if collider == False: smoothing_groups = defineSmoothing(self, object) for face in object.data.faces: self.matid = face.material_index if collider == False: for group in smoothing_groups: if group.count(face.index) == 0: continue else: #TODO: Compress sg's index = smoothing_groups.index(group) sgID = index%32 break temp = '''\t\t\t*MESH_FACE {0}: A: {1} B: {2} C: {3} AB: 0 BC: 0 CA: 0 *MESH_SMOOTHING {4} *MESH_MTLID {5}\n'''.format(face.index, face.vertices[0], face.vertices[1], face.vertices[2], sgID, self.matid) self.facelist.append(temp) def dump(self): temp = '' for x in self.facelist: temp = temp + str(x) return temp def __repr__(self): return '''{{\n{0}\t\t}}'''.format(self.dump()) class cTVertlist: def __init__(self, object): self.vertlist = [] for index, face in enumerate(object.data.uv_texture_stencil.data): temp = cTVert((index * 3), face.uv1.to_tuple(4)) self.vertlist.append(temp) temp = cTVert((index * 3)+1, face.uv2.to_tuple(4)) self.vertlist.append(temp) temp = cTVert((index * 3)+2, face.uv3.to_tuple(4)) self.vertlist.append(temp) self.length = len(self.vertlist) def dump(self): temp = '' for x in self.vertlist: temp += str(x) return temp def __repr__(self): return '''{{\n{0}\t\t}}'''.format(self.dump()) class cTVert: def __init__(self, index, coord): self.index = index self.u = aseFloat(coord[0]) self.v = aseFloat(coord[1]) def __repr__(self): return '''\t\t\t*MESH_TVERT {0} {1} {2} 0.0000\n'''.format(self.index, self.u, self.v) class cTFacelist: def __init__(self, facecount): self.facelist = [] for data in range(facecount): temp = cTFace(data) self.facelist.append(temp) def dump(self): temp = '' for x in self.facelist: temp = temp + str(x) return temp def __repr__(self): return '''{{\n{0}\t\t}}'''.format(self.dump()) class cTFace: def __init__(self, x): self.index = x self.vertices = [] self.vertices.append(x*3) self.vertices.append((x*3)+1) self.vertices.append((x*3)+2) def __repr__(self): return '''\t\t\t*MESH_TFACE {0} {1} {2} {3}\n'''.format(self.index, self.vertices[0], self.vertices[1], self.vertices[2]) class cCVertlist: def __init__(self, object): temp = [] if len(object.data.vertex_colors) > 0: for face in object.data.vertex_colors[0].data: temp.append(face.color1) temp.append(face.color2) temp.append(face.color3) self.vertlist = [] for index, data in enumerate(temp): self.vertlist.append(cCVert(index, data)) self.length = len(self.vertlist) def dump(self): temp = '' for x in self.vertlist: temp = temp + str(x) return temp def __repr__(self): return '''\t\t*MESH_CVERTLIST {{\n{0}\t\t}}'''.format(self.dump()) class cCVert: def __init__(self, index, data): self.index = index self.r = aseFloat(data[0]) self.g = aseFloat(data[1]) self.b = aseFloat(data[2]) def __repr__(self): return '''\t\t\t*MESH_VERTCOL {0} {1} {2} {3}\n'''.format(self.index, self.r, self.g, self.b) class cCFacelist: def __init__(self, facecount): temp = [0 for x in range(facecount)] self.facelist = [] for index, data in enumerate(temp): self.facelist.append(cCFace(index, data)) def dump(self): temp = '' for x in self.facelist: temp = temp + str(x) return temp def __repr__(self): return '''\t\t*MESH_CFACELIST {{\n{0}\t\t}}'''.format(self.dump()) class cCFace: def __init__(self, index, data): self.index = index self.vertices = [] self.vertices.append(index*3) self.vertices.append((index*3)+1) self.vertices.append((index*3)+2) def __repr__(self): return '''\t\t\t*MESH_CFACE {0} {1} {2} {3}\n'''.format(self.index, self.vertices[0], self.vertices[1], self.vertices[2]) class cNormallist: def __init__(self, object): self.normallist = [] for face in object.data.faces: self.normallist.append(cNormal(face, object)) def dump(self): temp = '' for x in self.normallist: temp = temp + str(x) return temp def __repr__(self): return '''\t\t*MESH_NORMALS {{\n{0}\t\t}}'''.format(self.dump()) class cNormal: def __init__(self, face, object): self.faceindex = face.index self.facenormal = [aseFloat(x) for x in face.normal.to_tuple(4)] self.vertnormals = [] for x in face.vertices: self.vertnormals.append([x, [aseFloat(y) for y in object.data.vertices[x].normal.to_tuple(4)]]) def __repr__(self): return '''\t\t\t*MESH_FACENORMAL {0} {1} {2} {3}\n\t\t\t\t*MESH_VERTEXNORMAL {4} {5} {6} {7}\n\t\t\t\t*MESH_VERTEXNORMAL {8} {9} {10} {11}\n\t\t\t\t*MESH_VERTEXNORMAL {12} {13} {14} {15}\n'''.format(self.faceindex, self.facenormal[0], self.facenormal[1], self.facenormal[2], self.vertnormals[0][0], self.vertnormals[0][1][0], self.vertnormals[0][1][1], self.vertnormals[0][1][2], self.vertnormals[1][0], self.vertnormals[1][1][0], self.vertnormals[1][1][1], self.vertnormals[1][1][2], self.vertnormals[2][0], self.vertnormals[2][1][0], self.vertnormals[2][1][1], self.vertnormals[2][1][2]) # Return a list of the selected vertices def getSelectedVerts(self): selected_verts = [] # Update mesh data bpy.ops.object.editmode_toggle() bpy.ops.object.editmode_toggle() _mode = bpy.context.scene.objects.active.mode bpy.ops.object.mode_set(mode='EDIT') object = bpy.context.scene.objects.active for vert in object.data.vertices: if vert.select == True: selected_verts.append(vert) bpy.ops.object.mode_set(mode=_mode) return selected_verts # Return a list of the selected edges def getSelectedEdges(self): selected_edges = [] # Update mesh data bpy.ops.object.editmode_toggle() bpy.ops.object.editmode_toggle() _mode = bpy.context.scene.objects.active.mode bpy.ops.object.mode_set(mode='EDIT') object = bpy.context.scene.objects.active for edge in object.data.edges: if edge.select == True: selected_edges.append(edge) bpy.ops.object.mode_set(mode=_mode) return selected_edges # Return a list of the selected faces, if index=True, return face index list def getSelectedFaces(self, index=False): selected_faces = [] # Update mesh data bpy.ops.object.editmode_toggle() bpy.ops.object.editmode_toggle() _mode = bpy.context.scene.objects.active.mode bpy.ops.object.mode_set(mode='EDIT') object = bpy.context.scene.objects.active for face in object.data.faces: if face.select == True: if index==False: selected_faces.append(face) else: selected_faces.append(face.index) bpy.ops.object.mode_set(mode=_mode) return selected_faces # Get smoothing groups def defineSmoothing(self, object): seam_edge_list = [] sharp_edge_list = [] #bpy.context.scene.objects.active = object _mode = bpy.context.scene.objects.active.mode bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='DESELECT') setSelMode(self, 'EDGE') # Get seams and clear them bpy.ops.object.mode_set(mode='OBJECT') for edge in object.data.edges: if edge.use_seam: seam_edge_list.append(edge.index) edge.select = True bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.mark_seam(clear=True) bpy.ops.mesh.select_all(action='DESELECT') # Get sharp edges, convert them to seams bpy.ops.object.mode_set(mode='OBJECT') for edge in object.data.edges: if edge.use_edge_sharp: sharp_edge_list.append(edge) edge.select = True bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.mark_seam() bpy.ops.mesh.select_all(action='DESELECT') ##### BEGIN ############################################################### print('Beginning Smooth Groups') smoothing_groups = [] face_list = [] mode = getSelMode(self, False) setSelMode(self, 'FACE') for face in object.data.faces: face_list.append(face.index) while len(face_list) > 0: bpy.ops.object.mode_set(mode='OBJECT') object.data.faces[face_list[0]].select = True bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_linked(limit=True) # TODO - update when API is updated selected_faces = getSelectedFaces(self, True) smoothing_groups.append(selected_faces) for face_index in selected_faces: face_list.remove(face_index) bpy.ops.mesh.select_all(action='DESELECT') setSelMode(self, mode, False) ##### END ################################################################# # Clear seams created by sharp edges bpy.ops.object.mode_set(mode='OBJECT') for edge in object.data.edges: if edge.use_seam: seam_edge_list.append(edge.index) edge.select = True bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.mark_seam(clear=True) bpy.ops.mesh.select_all(action='DESELECT') # Restore original uv seams bpy.ops.object.mode_set(mode='OBJECT') for edge_index in seam_edge_list: object.data.edges[edge_index].select = True bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.mark_seam() print(str(len(smoothing_groups)) + ' smoothing groups found.') return smoothing_groups def getAdjacent(self, smoothing_groups, object, id): loopVerts = [] deselected = [] adjacent = [] index = 0 # Deselect all bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='DESELECT') # Select all faces in the current smoothing group setSelMode(self, 'FACE') bpy.ops.object.mode_set(mode='OBJECT') for i, group in enumerate(smoothing_groups): if smoothing_groups[i][0].count(id): index = i break for face in smoothing_groups[index][1]: object.data.faces[face].select = True bpy.ops.mesh.region_to_loop() # Get vertices in the loop for vert in object.data.faces.vertices: if vert.select: loopVerts.append(vert) # Select all of the other not in this smoothing group # Select all faces in the group, then invert selection setSelMode(self, 'FACE') bpy.ops.object.mode_set(mode='OBJECT') for face in smoothing_groups[index][1]: object.data.faces[face].select = True bpy.ops.mesh.select_all(action='INVERT') for face in object.data.faces: if face.select: for v in face.vertices: if loopVerts.count(v): if not adjacent.count(face.index): adjacent.append(face.index) if len(adjacent): return adjacent else: return 0 # Return a list of the selected vertices def getSelectedVerts(self): selected_verts = [] # Update mesh data bpy.ops.object.editmode_toggle() bpy.ops.object.editmode_toggle() _mode = bpy.context.scene.objects.active.mode bpy.ops.object.mode_set(mode='EDIT') object = bpy.context.scene.objects.active for vert in object.data.vertices: if vert.select == True: selected_verts.append(vert) bpy.ops.object.mode_set(mode=_mode) return selected_verts # Return current mode (OBJECT, EDIT) def getMode(self): object = bpy.context.active_object if object.type == 'MESH': return object.mode # Set mode def setMode(self, set_mode): object = bpy.context.active_object if object.type == 'MESH': bpy.ops.object.mode_set(mode=set_mode) return True else: return False # Get selection mode def getSelMode(self, default=True): if default: if bpy.context.tool_settings.mesh_select_mode[0] == True: return 'VERT' elif bpy.context.tool_settings.mesh_select_mode[1] == True: return 'EDGE' elif bpy.context.tool_settings.mesh_select_mode[2] == True: return 'FACE' return False else: mode = [] for value in bpy.context.tool_settings.mesh_select_mode: mode.append(value) return mode # Get selection mode def setSelMode(self, mode, default=True): if default: if mode == 'VERT': bpy.context.tool_settings.mesh_select_mode = [True, False, False] elif mode == 'EDGE': bpy.context.tool_settings.mesh_select_mode = [False, True, False] elif mode == 'FACE': bpy.context.tool_settings.mesh_select_mode = [False, False, True] else: return False else: bpy.context.tool_settings.mesh_select_mode = mode return True # Return a list of the selected edges def getSelectedEdges(self): selected_edges = [] # Update mesh data bpy.ops.object.editmode_toggle() bpy.ops.object.editmode_toggle() _mode = bpy.context.scene.objects.active.mode bpy.ops.object.mode_set(mode='EDIT') object = bpy.context.scene.objects.active for edge in object.data.edges: if edge.select == True: selected_edges.append(edge) bpy.ops.object.mode_set(mode=_mode) return selected_edges # Return a list of the selected faces, if index=True, return face index list def getSelectedFaces(self, index=False): selected_faces = [] # Update mesh data bpy.ops.object.editmode_toggle() bpy.ops.object.editmode_toggle() _mode = bpy.context.scene.objects.active.mode bpy.ops.object.mode_set(mode='EDIT') object = bpy.context.scene.objects.active for face in object.data.faces: if face.select == True: if index==False: selected_faces.append(face) else: selected_faces.append(face.index) bpy.ops.object.mode_set(mode=_mode) return selected_faces # Check if the mesh is a collider # Return True if collision model, else: false def isColliderObject(self, object): colliderPrefixes = ['UCX_', 'UBX_', 'USX_'] for prefix in colliderPrefixes: if object.name.find(str(prefix)) >= 0: return True return False # Get selection mode def setSelMode(self, mode, default=True): if default: if mode == 'VERT': bpy.context.tool_settings.mesh_select_mode = [True, False, False] elif mode == 'EDGE': bpy.context.tool_settings.mesh_select_mode = [False, True, False] elif mode == 'FACE': bpy.context.tool_settings.mesh_select_mode = [False, False, True] else: return False else: bpy.context.tool_settings.mesh_select_mode = mode return True # Combine gathered data def gatherData(self): scn = bpy.context.scene global auto_tri global auto_normals for object in bpy.context.selected_objects: if object.type != 'MESH': continue bpy.context.scene.objects.active = object bpy.ops.object.mode_set(mode='OBJECT') print('Consistent normals: ' + str(auto_normals)) if auto_normals: bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.normals_make_consistent() print('Remove doubles: ' + str(remove_doubles)) if remove_doubles: bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.remove_doubles() print('Auto triangles: ' + str(auto_tri)) if auto_tri: bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.quads_convert_to_tris() bpy.ops.object.mode_set(mode='OBJECT') header = cHeader() scene = cScene() matlist = cMaterialList() geolist = cGeomObjList() return '{0}{1}{2}{3}'.format(header,scene,matlist, geolist) def exportASE(filename, data): print('Writing', filename) try: file = open(filename, 'w') except IOError: print('Error: The file could not be written to. Aborting.') else: file.write(data) file.close() from bpy.props import * class EXPORT_OT_asel(bpy.types.Operator): '''Load an Ascii Scene Export File''' bl_idname = "export_scene.ase" bl_label = "Export ASE" filepath = StringProperty(name="File Path", description="File path used for exporting the ASE file", maxlen= 1024, default= "") ASE_SCALE = FloatProperty(name="Scale", description="Object scaling factor (default: 16)", min=0.01, max=1000.0, soft_min=0.01, soft_max=1000.0, default=16.0) AUTO_TRI = BoolProperty(name = "Auto Tris", default=True ) AUTO_NORMALS = BoolProperty(name = "Recalculate Normals", default=True ) def execute(self, context): import os global scale global auto_tri global auto_normals start = time.clock() for object in bpy.context.selected_objects: bpy.context.scene.objects.active = object bpy.ops.object.mode_set(mode='OBJECT') if isColliderObject(self, object) == False: if object.type == 'MESH': if len(object.material_slots) == 0: print(object.name + ' has no material. Aborting.') return {'CANCELLED'} if object.data.uv_texture_stencil_index == -1: print(object.name + ' is not uv mapped. Aborting.') return {'CANCELLED'} scale = self.properties.ASE_SCALE auto_tri = self.properties.AUTO_TRI auto_normals = self.properties.AUTO_NORMALS print('Calculating data for ' + object.name + '. Please wait.') model = gatherData(self) exportASE(self.properties.filepath, model) lapse = (time.clock() - start) print( 'Completed in ' + str(lapse) + ' seconds.') return {'FINISHED'} def invoke(self, context, event): wm = context.window_manager # fixed for 2.56? Katsbits.com (via Nic B) # original wm.add_fileselect(self) wm.fileselect_add(self) return {'RUNNING_MODAL'} # Set filepath to save the ASE to def menu_func(self, context): # append .ase to filepath if os.path.splitext(bpy.data.filepath)[0] == "": default_path = "default.ase" else: default_path = os.path.splitext(bpy.data.filepath)[0] + ".ase" self.layout.operator(EXPORT_OT_asel.bl_idname, text="Ascii Scene Export (.ase)").filepath = default_path def register(): bpy.utils.register_module(__name__) bpy.types.INFO_MT_file_export.append(menu_func) def unregister(): bpy.utils.unregister_module(__name__) bpy.types.INFO_MT_file_export.remove(menu_func) if __name__ == '__main__': register()