from .logger import * from .package_level import * from .f_curve_animatable import * from .armature import * from .material import * import bpy import math from mathutils import Vector import shutil # output related constants MAX_VERTEX_ELEMENTS = 65535 MAX_VERTEX_ELEMENTS_32Bit = 16777216 COMPRESS_MATRIX_INDICES = True # this is True for .babylon exporter & False for TOB # used in Mesh & Node constructors, defined in BABYLON.AbstractMesh BILLBOARDMODE_NONE = 0 #BILLBOARDMODE_X = 1 #BILLBOARDMODE_Y = 2 #BILLBOARDMODE_Z = 4 BILLBOARDMODE_ALL = 7 # used in Mesh constructor, defined in BABYLON.PhysicsEngine SPHERE_IMPOSTER = 1 BOX_IMPOSTER = 2 #PLANE_IMPOSTER = 3 MESH_IMPOSTER = 4 CAPSULE_IMPOSTER = 5 CONE_IMPOSTER = 6 CYLINDER_IMPOSTER = 7 CONVEX_HULL_IMPOSTER = 8 #=============================================================================== class Mesh(FCurveAnimatable): def __init__(self, object, scene, startFace, forcedParent, nameID, exporter): self.name = object.name + str(nameID) Logger.log('processing begun of mesh: ' + self.name) self.define_animations(object, True, True, True) #Should animations be done when forcedParent self.isVisible = not object.hide_render self.isEnabled = not object.data.loadDisabled useFlatShading = scene.export_flatshadeScene or object.data.useFlatShading self.checkCollisions = object.data.checkCollisions self.receiveShadows = object.data.receiveShadows self.castShadows = object.data.castShadows self.freezeWorldMatrix = object.data.freezeWorldMatrix self.layer = getLayer(object) # used only for lights with 'This Layer Only' checked, not exported self.tags = object.data.tags # hasSkeleton detection & skeletonID determination hasSkeleton = False objArmature = None # if there's an armature, this will be the one! if len(object.vertex_groups) > 0 and not object.data.ignoreSkeleton: objArmature = object.find_armature() if objArmature != None: hasSkeleton = True # used to get bone index, since could be skipping IK bones skeleton = exporter.get_skeleton(objArmature.name) i = 0 for obj in scene.objects: if obj.type == "ARMATURE": if obj == objArmature: self.skeletonId = i break else: i += 1 # determine Position, rotation, & scaling if forcedParent is None: # Use local matrix locMatrix = object.matrix_local if objArmature != None: # unless the armature is the parent if object.parent and object.parent == objArmature: locMatrix = object.matrix_world * object.parent.matrix_world.inverted() loc, rot, scale = locMatrix.decompose() self.position = loc if object.rotation_mode == 'QUATERNION': self.rotationQuaternion = rot else: self.rotation = scale_vector(rot.to_euler('XYZ'), -1) self.scaling = scale else: # use defaults when not None self.position = Vector((0, 0, 0)) self.rotation = scale_vector(Vector((0, 0, 0)), 1) # isn't scaling 0's by 1 same as 0? self.scaling = Vector((1, 1, 1)) # ensure no unapplied rotation or scale, when there is an armature self.hasUnappliedTransforms = (self.scaling.x != 1 or self.scaling.y != 1 or self.scaling.z != 1 or (hasattr(self, 'rotation' ) and (self.rotation .x != 0 or self.rotation .y != 0 or self.rotation .z != 0)) or (hasattr(self, 'rotationQuaternion') and (self.rotationQuaternion.x != 0 or self.rotationQuaternion.y != 0 or self.rotationQuaternion.z != 0 or self.rotationQuaternion.w != 1)) ) # determine parent & dataName if forcedParent is None: self.dataName = object.data.name # used to support shared vertex instances in later passed if object.parent and object.parent.type != 'ARMATURE': self.parentId = object.parent.name else: self.dataName = self.name self.parentId = forcedParent.name # Get if this will be an instance of another, before processing materials, to avoid multi-bakes sourceMesh = exporter.getSourceMeshInstance(self.dataName) if sourceMesh is not None: #need to make sure rotation mode matches, since value initially copied in InstancedMesh constructor if hasattr(sourceMesh, 'rotationQuaternion'): instRot = None instRotq = rot else: instRot = scale_vector(rot.to_euler('XYZ'), -1) instRotq = None instance = MeshInstance(self, instRot, instRotq) sourceMesh.instances.append(instance) Logger.log('mesh is an instance of : ' + sourceMesh.name + '. Processing halted.', 2) return else: self.instances = [] # Physics if object.rigid_body != None: shape_items = {'SPHERE' : SPHERE_IMPOSTER, 'BOX' : BOX_IMPOSTER, 'MESH' : MESH_IMPOSTER, 'CAPSULE' : CAPSULE_IMPOSTER, 'CONE' : CONE_IMPOSTER, 'CYLINDER' : CYLINDER_IMPOSTER, 'CONVEX_HULL': CONVEX_HULL_IMPOSTER} shape_type = shape_items[object.rigid_body.collision_shape] self.physicsImpostor = shape_type mass = object.rigid_body.mass if mass < 0.005: mass = 0 self.physicsMass = mass self.physicsFriction = object.rigid_body.friction self.physicsRestitution = object.rigid_body.restitution # process all of the materials required maxVerts = MAX_VERTEX_ELEMENTS # change for multi-materials recipe = BakingRecipe(object) self.billboardMode = BILLBOARDMODE_ALL if recipe.isBillboard else BILLBOARDMODE_NONE if recipe.needsBaking: if recipe.multipleRenders: Logger.warn('Mixing of Cycles & Blender Render in same mesh not supported. No materials exported.', 2) else: bakedMat = BakedMaterial(exporter, object, recipe) exporter.materials.append(bakedMat) self.materialId = bakedMat.name else: bjs_material_slots = [] for slot in object.material_slots: # None will be returned when either the first encounter or must be unique due to baked textures material = exporter.getMaterial(slot.name) if (material != None): Logger.log('registered as also a user of material: ' + slot.name, 2) else: material = StdMaterial(slot, exporter, object) exporter.materials.append(material) bjs_material_slots.append(material) if len(bjs_material_slots) == 1: self.materialId = bjs_material_slots[0].name elif len(bjs_material_slots) > 1: multimat = MultiMaterial(bjs_material_slots, len(exporter.multiMaterials), exporter.nameSpace) self.materialId = multimat.name exporter.multiMaterials.append(multimat) maxVerts = MAX_VERTEX_ELEMENTS_32Bit else: Logger.warn('No materials have been assigned: ', 2) # Get mesh mesh = object.to_mesh(scene, True, 'PREVIEW') # Triangulate mesh if required Mesh.mesh_triangulate(mesh) # Getting vertices and indices self.positions = [] self.normals = [] self.uvs = [] # not always used self.uvs2 = [] # not always used self.colors = [] # not always used self.indices = [] self.subMeshes = [] hasUV = len(mesh.tessface_uv_textures) > 0 if hasUV: which = len(mesh.tessface_uv_textures) - 1 if recipe.needsBaking else 0 UVmap = mesh.tessface_uv_textures[which].data hasUV2 = len(mesh.tessface_uv_textures) > 1 and not recipe.needsBaking if hasUV2: UV2map = mesh.tessface_uv_textures[1].data hasVertexColor = len(mesh.vertex_colors) > 0 if hasVertexColor: Colormap = mesh.tessface_vertex_colors.active.data if hasSkeleton: weightsPerVertex = [] indicesPerVertex = [] influenceCounts = [0, 0, 0, 0, 0, 0, 0, 0, 0] # 9, so accessed orign 1; 0 used for all those greater than 8 totalInfluencers = 0 highestInfluenceObserved = 0 # used tracking of vertices as they are received alreadySavedVertices = [] vertices_Normals = [] vertices_UVs = [] vertices_UV2s = [] vertices_Colors = [] vertices_indices = [] vertices_sk_weights = [] vertices_sk_indices = [] self.offsetFace = 0 for v in range(0, len(mesh.vertices)): alreadySavedVertices.append(False) vertices_Normals.append([]) vertices_UVs.append([]) vertices_UV2s.append([]) vertices_Colors.append([]) vertices_indices.append([]) vertices_sk_weights.append([]) vertices_sk_indices.append([]) materialsCount = 1 if recipe.needsBaking else max(1, len(object.material_slots)) verticesCount = 0 indicesCount = 0 for materialIndex in range(materialsCount): if self.offsetFace != 0: break subMeshVerticesStart = verticesCount subMeshIndexStart = indicesCount for faceIndex in range(startFace, len(mesh.tessfaces)): # For each face face = mesh.tessfaces[faceIndex] if face.material_index != materialIndex and not recipe.needsBaking: continue if verticesCount + 3 > maxVerts: self.offsetFace = faceIndex break for v in range(3): # For each vertex in face vertex_index = face.vertices[v] vertex = mesh.vertices[vertex_index] position = vertex.co normal = face.normal if useFlatShading else vertex.normal #skeletons if hasSkeleton: matricesWeights = [] matricesIndices = [] # Getting influences for group in vertex.groups: index = group.group weight = group.weight for bone in objArmature.pose.bones: if object.vertex_groups[index].name == bone.name: matricesWeights.append(weight) matricesIndices.append(skeleton.get_index_of_bone(bone.name)) # Texture coordinates if hasUV: vertex_UV = UVmap[face.index].uv[v] if hasUV2: vertex_UV2 = UV2map[face.index].uv[v] # Vertex color if hasVertexColor: if v == 0: vertex_Color = Colormap[face.index].color1 if v == 1: vertex_Color = Colormap[face.index].color2 if v == 2: vertex_Color = Colormap[face.index].color3 # Check if the current vertex is already saved alreadySaved = alreadySavedVertices[vertex_index] and not useFlatShading if alreadySaved: alreadySaved = False # UV index_UV = 0 for savedIndex in vertices_indices[vertex_index]: vNormal = vertices_Normals[vertex_index][index_UV] if not same_vertex(normal, vNormal): continue; if hasUV: vUV = vertices_UVs[vertex_index][index_UV] if not same_array(vertex_UV, vUV): continue if hasUV2: vUV2 = vertices_UV2s[vertex_index][index_UV] if not same_array(vertex_UV2, vUV2): continue if hasVertexColor: vColor = vertices_Colors[vertex_index][index_UV] if vColor.r != vertex_Color.r or vColor.g != vertex_Color.g or vColor.b != vertex_Color.b: continue if hasSkeleton: vSkWeight = vertices_sk_weights[vertex_index] vSkIndices = vertices_sk_indices[vertex_index] if not same_array(vSkWeight[index_UV], matricesWeights) or not same_array(vSkIndices[index_UV], matricesIndices): continue if vertices_indices[vertex_index][index_UV] >= subMeshVerticesStart: alreadySaved = True break index_UV += 1 if (alreadySaved): # Reuse vertex index = vertices_indices[vertex_index][index_UV] else: # Export new one index = verticesCount alreadySavedVertices[vertex_index] = True vertices_Normals[vertex_index].append(normal) self.normals.append(normal) if hasUV: vertices_UVs[vertex_index].append(vertex_UV) self.uvs.append(vertex_UV[0]) self.uvs.append(vertex_UV[1]) if hasUV2: vertices_UV2s[vertex_index].append(vertex_UV2) self.uvs2.append(vertex_UV2[0]) self.uvs2.append(vertex_UV2[1]) if hasVertexColor: vertices_Colors[vertex_index].append(vertex_Color) self.colors.append(vertex_Color.r) self.colors.append(vertex_Color.g) self.colors.append(vertex_Color.b) self.colors.append(1.0) if hasSkeleton: vertices_sk_weights[vertex_index].append(matricesWeights) vertices_sk_indices[vertex_index].append(matricesIndices) nInfluencers = len(matricesWeights) totalInfluencers += nInfluencers if nInfluencers <= 8: influenceCounts[nInfluencers] += 1 else: influenceCounts[0] += 1 highestInfluenceObserved = nInfluencers if nInfluencers > highestInfluenceObserved else highestInfluenceObserved weightsPerVertex.append(matricesWeights) indicesPerVertex.append(matricesIndices) vertices_indices[vertex_index].append(index) self.positions.append(position) verticesCount += 1 self.indices.append(index) indicesCount += 1 self.subMeshes.append(SubMesh(materialIndex, subMeshVerticesStart, subMeshIndexStart, verticesCount - subMeshVerticesStart, indicesCount - subMeshIndexStart)) if verticesCount > MAX_VERTEX_ELEMENTS: Logger.warn('Due to multi-materials / Shapekeys & this meshes size, 32bit indices must be used. This may not run on all hardware.', 2) BakedMaterial.meshBakingClean(object) Logger.log('num positions : ' + str(len(self.positions)), 2) Logger.log('num normals : ' + str(len(self.normals )), 2) Logger.log('num uvs : ' + str(len(self.uvs )), 2) Logger.log('num uvs2 : ' + str(len(self.uvs2 )), 2) Logger.log('num colors : ' + str(len(self.colors )), 2) Logger.log('num indices : ' + str(len(self.indices )), 2) if hasSkeleton: Logger.log('Skeleton stats: ', 2) self.toFixedInfluencers(weightsPerVertex, indicesPerVertex, object.data.maxInfluencers, highestInfluenceObserved) if (COMPRESS_MATRIX_INDICES): self.skeletonIndices = Mesh.packSkeletonIndices(self.skeletonIndices) if (self.numBoneInfluencers > 4): self.skeletonIndicesExtra = Mesh.packSkeletonIndices(self.skeletonIndicesExtra) Logger.log('Total Influencers: ' + format_f(totalInfluencers), 3) Logger.log('Avg # of influencers per vertex: ' + format_f(totalInfluencers / len(self.positions)), 3) Logger.log('Highest # of influencers observed: ' + str(highestInfluenceObserved) + ', num vertices with this: ' + format_int(influenceCounts[highestInfluenceObserved if highestInfluenceObserved < 9 else 0]), 3) Logger.log('exported as ' + str(self.numBoneInfluencers) + ' influencers', 3) nWeights = len(self.skeletonWeights) + (len(self.skeletonWeightsExtra) if hasattr(self, 'skeletonWeightsExtra') else 0) Logger.log('num skeletonWeights and skeletonIndices: ' + str(nWeights), 3) numZeroAreaFaces = self.find_zero_area_faces() if numZeroAreaFaces > 0: Logger.warn('# of 0 area faces found: ' + str(numZeroAreaFaces), 2) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def find_zero_area_faces(self): nFaces = int(len(self.indices) / 3) nZeroAreaFaces = 0 for f in range(0, nFaces): faceOffset = f * 3 p1 = self.positions[self.indices[faceOffset ]] p2 = self.positions[self.indices[faceOffset + 1]] p3 = self.positions[self.indices[faceOffset + 2]] if same_vertex(p1, p2) or same_vertex(p1, p3) or same_vertex(p2, p3): nZeroAreaFaces += 1 return nZeroAreaFaces # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @staticmethod def mesh_triangulate(mesh): try: import bmesh bm = bmesh.new() bm.from_mesh(mesh) bmesh.ops.triangulate(bm, faces = bm.faces) bm.to_mesh(mesh) mesh.calc_tessface() bm.free() except: pass # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def toFixedInfluencers(self, weightsPerVertex, indicesPerVertex, maxInfluencers, highestObserved): if (maxInfluencers > 8 or maxInfluencers < 1): maxInfluencers = 8 Logger.warn('Maximum # of influencers invalid, set to 8', 3) self.numBoneInfluencers = maxInfluencers if maxInfluencers < highestObserved else highestObserved needExtras = self.numBoneInfluencers > 4 maxInfluencersExceeded = 0 fixedWeights = [] fixedIndices = [] fixedWeightsExtra = [] fixedIndicesExtra = [] for i in range(len(weightsPerVertex)): weights = weightsPerVertex[i] indices = indicesPerVertex[i] nInfluencers = len(weights) if (nInfluencers > self.numBoneInfluencers): maxInfluencersExceeded += 1 Mesh.sortByDescendingInfluence(weights, indices) for j in range(4): fixedWeights.append(weights[j] if nInfluencers > j else 0.0) fixedIndices.append(indices[j] if nInfluencers > j else 0 ) if needExtras: for j in range(4, 8): fixedWeightsExtra.append(weights[j] if nInfluencers > j else 0.0) fixedIndicesExtra.append(indices[j] if nInfluencers > j else 0 ) self.skeletonWeights = fixedWeights self.skeletonIndices = fixedIndices if needExtras: self.skeletonWeightsExtra = fixedWeightsExtra self.skeletonIndicesExtra = fixedIndicesExtra if maxInfluencersExceeded > 0: Logger.warn('Maximum # of influencers exceeded for ' + format_int(maxInfluencersExceeded) + ' vertices, extras ignored', 3) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # sorts one set of weights & indices by descending weight, by reference # not shown to help with MakeHuman, but did not hurt. In just so it is not lost for future. @staticmethod def sortByDescendingInfluence(weights, indices): notSorted = True while(notSorted): notSorted = False for idx in range(1, len(weights)): if weights[idx - 1] < weights[idx]: tmp = weights[idx] weights[idx ] = weights[idx - 1] weights[idx - 1] = tmp tmp = indices[idx] indices[idx ] = indices[idx - 1] indices[idx - 1] = tmp notSorted = True # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # assume that toFixedInfluencers has already run, which ensures indices length is a multiple of 4 @staticmethod def packSkeletonIndices(indices): compressedIndices = [] for i in range(math.floor(len(indices) / 4)): idx = i * 4 matricesIndicesCompressed = indices[idx ] matricesIndicesCompressed += indices[idx + 1] << 8 matricesIndicesCompressed += indices[idx + 2] << 16 matricesIndicesCompressed += indices[idx + 3] << 24 compressedIndices.append(matricesIndicesCompressed) return compressedIndices # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def to_scene_file(self, file_handler): file_handler.write('{') write_string(file_handler, 'name', self.name, True) write_string(file_handler, 'id', self.name) if hasattr(self, 'parentId'): write_string(file_handler, 'parentId', self.parentId) if hasattr(self, 'materialId'): write_string(file_handler, 'materialId', self.materialId) write_int(file_handler, 'billboardMode', self.billboardMode) write_vector(file_handler, 'position', self.position) if hasattr(self, "rotationQuaternion"): write_quaternion(file_handler, 'rotationQuaternion', self.rotationQuaternion) else: write_vector(file_handler, 'rotation', self.rotation) write_vector(file_handler, 'scaling', self.scaling) write_bool(file_handler, 'isVisible', self.isVisible) write_bool(file_handler, 'freezeWorldMatrix', self.freezeWorldMatrix) write_bool(file_handler, 'isEnabled', self.isEnabled) write_bool(file_handler, 'checkCollisions', self.checkCollisions) write_bool(file_handler, 'receiveShadows', self.receiveShadows) write_string(file_handler, 'tags', self.tags) if hasattr(self, 'physicsImpostor'): write_int(file_handler, 'physicsImpostor', self.physicsImpostor) write_float(file_handler, 'physicsMass', self.physicsMass) write_float(file_handler, 'physicsFriction', self.physicsFriction) write_float(file_handler, 'physicsRestitution', self.physicsRestitution) # Geometry if hasattr(self, 'skeletonId'): write_int(file_handler, 'skeletonId', self.skeletonId) write_int(file_handler, 'numBoneInfluencers', self.numBoneInfluencers) write_vector_array(file_handler, 'positions', self.positions) write_vector_array(file_handler, 'normals' , self.normals ) if len(self.uvs) > 0: write_array(file_handler, 'uvs', self.uvs) if len(self.uvs2) > 0: write_array(file_handler, 'uvs2', self.uvs2) if len(self.colors) > 0: write_array(file_handler, 'colors', self.colors) if hasattr(self, 'skeletonWeights'): write_array(file_handler, 'matricesWeights', self.skeletonWeights) write_array(file_handler, 'matricesIndices', self.skeletonIndices) if hasattr(self, 'skeletonWeightsExtra'): write_array(file_handler, 'matricesWeightsExtra', self.skeletonWeightsExtra) write_array(file_handler, 'matricesIndicesExtra', self.skeletonIndicesExtra) write_array(file_handler, 'indices', self.indices) # Sub meshes file_handler.write('\n,"subMeshes":[') first = True for subMesh in self.subMeshes: if first == False: file_handler.write(',') subMesh.to_scene_file(file_handler) first = False file_handler.write(']') super().to_scene_file(file_handler) # Animations # Instances first = True file_handler.write('\n,"instances":[') for instance in self.instances: if first == False: file_handler.write(',') instance.to_scene_file(file_handler) first = False file_handler.write(']') # Close mesh file_handler.write('}\n') self.alreadyExported = True #=============================================================================== class MeshInstance: def __init__(self, instancedMesh, rotation, rotationQuaternion): self.name = instancedMesh.name if hasattr(instancedMesh, 'parentId'): self.parentId = instancedMesh.parentId self.position = instancedMesh.position if rotation is not None: self.rotation = rotation if rotationQuaternion is not None: self.rotationQuaternion = rotationQuaternion self.scaling = instancedMesh.scaling self.freezeWorldMatrix = instancedMesh.freezeWorldMatrix # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def to_scene_file(self, file_handler): file_handler.write('{') write_string(file_handler, 'name', self.name, True) if hasattr(self, 'parentId'): write_string(file_handler, 'parentId', self.parentId) write_vector(file_handler, 'position', self.position) if hasattr(self, 'rotation'): write_vector(file_handler, 'rotation', self.rotation) else: write_quaternion(file_handler, 'rotationQuaternion', self.rotationQuaternion) write_vector(file_handler, 'scaling', self.scaling) # freeze World Matrix currently ignored for instances write_bool(file_handler, 'freezeWorldMatrix', self.freezeWorldMatrix) file_handler.write('}') #=============================================================================== class Node(FCurveAnimatable): def __init__(self, node): Logger.log('processing begun of node: ' + node.name) self.define_animations(node, True, True, True) #Should animations be done when forcedParent self.name = node.name if node.parent and node.parent.type != 'ARMATURE': self.parentId = node.parent.name loc, rot, scale = node.matrix_local.decompose() self.position = loc if node.rotation_mode == 'QUATERNION': self.rotationQuaternion = rot else: self.rotation = scale_vector(rot.to_euler('XYZ'), -1) self.scaling = scale self.isVisible = False self.isEnabled = True self.checkCollisions = False self.billboardMode = BILLBOARDMODE_NONE self.castShadows = False self.receiveShadows = False self.tags = '' self.layer = -1 # nodes do not have layers attribute # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def to_scene_file(self, file_handler): file_handler.write('{') write_string(file_handler, 'name', self.name, True) write_string(file_handler, 'id', self.name) if hasattr(self, 'parentId'): write_string(file_handler, 'parentId', self.parentId) write_vector(file_handler, 'position', self.position) if hasattr(self, "rotationQuaternion"): write_quaternion(file_handler, "rotationQuaternion", self.rotationQuaternion) else: write_vector(file_handler, 'rotation', self.rotation) write_vector(file_handler, 'scaling', self.scaling) write_bool(file_handler, 'isVisible', self.isVisible) write_bool(file_handler, 'isEnabled', self.isEnabled) write_bool(file_handler, 'checkCollisions', self.checkCollisions) write_int(file_handler, 'billboardMode', self.billboardMode) write_bool(file_handler, 'receiveShadows', self.receiveShadows) write_string(file_handler, 'tags', self.tags) super().to_scene_file(file_handler) # Animations file_handler.write('}') #=============================================================================== class SubMesh: def __init__(self, materialIndex, verticesStart, indexStart, verticesCount, indexCount): self.materialIndex = materialIndex self.verticesStart = verticesStart self.indexStart = indexStart self.verticesCount = verticesCount self.indexCount = indexCount # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def to_scene_file(self, file_handler): file_handler.write('{') write_int(file_handler, 'materialIndex', self.materialIndex, True) write_int(file_handler, 'verticesStart', self.verticesStart) write_int(file_handler, 'verticesCount', self.verticesCount) write_int(file_handler, 'indexStart' , self.indexStart) write_int(file_handler, 'indexCount' , self.indexCount) file_handler.write('}') #=============================================================================== bpy.types.Mesh.autoAnimate = bpy.props.BoolProperty( name='Auto launch animations', description='', default = False ) bpy.types.Mesh.useFlatShading = bpy.props.BoolProperty( name='Use Flat Shading', description='Use face normals. Increases vertices.', default = False ) bpy.types.Mesh.checkCollisions = bpy.props.BoolProperty( name='Check Collisions', description='Indicates mesh should be checked that it does not run into anything.', default = False ) bpy.types.Mesh.castShadows = bpy.props.BoolProperty( name='Cast Shadows', description='', default = False ) bpy.types.Mesh.receiveShadows = bpy.props.BoolProperty( name='Receive Shadows', description='', default = False ) bpy.types.Mesh.tags = bpy.props.StringProperty( name='Tags', description='Add meta-data to mesh (space delimited for multiples)', default = '' ) # not currently in use bpy.types.Mesh.forceBaking = bpy.props.BoolProperty( name='Combine Multi-textures / resize', description='Also good to adjust single texture\'s size /compression.', default = False ) # not currently in use bpy.types.Mesh.usePNG = bpy.props.BoolProperty( name='Need Alpha', description='Saved as PNG when alpha is required, else JPG.', default = False ) bpy.types.Mesh.bakeSize = bpy.props.IntProperty( name='Texture Size', description='Final dimensions of texture(s). Not required to be a power of 2, but recommended.', default = 1024 ) bpy.types.Mesh.bakeQuality = bpy.props.IntProperty( name='Quality 1-100', description='For JPG: The trade-off between Quality - File size(100 highest quality)\nFor PNG: amount of time spent for compression', default = 50, min = 1, max = 100 ) bpy.types.Mesh.materialNameSpace = bpy.props.StringProperty( name='Name Space', description='Prefix to use for materials for sharing across .blends.', default = DEFAULT_MATERIAL_NAMESPACE ) bpy.types.Mesh.maxSimultaneousLights = bpy.props.IntProperty( name='Max Simultaneous Lights 0 - 32', description='BJS property set on each material of this mesh.\nSet higher for more complex lighting.\nSet lower for armatures on mobile', default = 4, min = 0, max = 32 ) bpy.types.Mesh.checkReadyOnlyOnce = bpy.props.BoolProperty( name='Check Ready Only Once', description='When checked better CPU utilization. Advanced user option.', default = False ) bpy.types.Mesh.freezeWorldMatrix = bpy.props.BoolProperty( name='Freeze World Matrix', description='Indicate the position, rotation, & scale do not change for performance reasons', default = False ) bpy.types.Mesh.loadDisabled = bpy.props.BoolProperty( name='Load Disabled', description='Indicate this mesh & children should not be active until enabled by code.', default = False ) bpy.types.Mesh.attachedSound = bpy.props.StringProperty( name='Sound', description='', default = '' ) bpy.types.Mesh.loopSound = bpy.props.BoolProperty( name='Loop sound', description='', default = True ) bpy.types.Mesh.autoPlaySound = bpy.props.BoolProperty( name='Auto play sound', description='', default = True ) bpy.types.Mesh.maxSoundDistance = bpy.props.FloatProperty( name='Max sound distance', description='', default = 100 ) bpy.types.Mesh.ignoreSkeleton = bpy.props.BoolProperty( name='Ignore', description='Do not export assignment to a skeleton', default = False ) bpy.types.Mesh.maxInfluencers = bpy.props.IntProperty( name='Max bone Influencers / Vertex', description='When fewer than this are observed, the lower value is used.', default = 8, min = 1, max = 8 ) #=============================================================================== class MeshPanel(bpy.types.Panel): bl_label = get_title() bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = 'data' @classmethod def poll(cls, context): ob = context.object return ob is not None and isinstance(ob.data, bpy.types.Mesh) def draw(self, context): ob = context.object layout = self.layout row = layout.row() row.prop(ob.data, 'useFlatShading') row.prop(ob.data, 'checkCollisions') row = layout.row() row.prop(ob.data, 'castShadows') row.prop(ob.data, 'receiveShadows') row = layout.row() row.prop(ob.data, 'freezeWorldMatrix') row.prop(ob.data, 'loadDisabled') layout.prop(ob.data, 'autoAnimate') layout.prop(ob.data, 'tags') box = layout.box() box.label(text='Skeleton:') box.prop(ob.data, 'ignoreSkeleton') row = box.row() row.enabled = not ob.data.ignoreSkeleton row.prop(ob.data, 'maxInfluencers') box = layout.box() box.label('Materials') box.prop(ob.data, 'materialNameSpace') box.prop(ob.data, 'maxSimultaneousLights') box.prop(ob.data, 'checkReadyOnlyOnce') box = layout.box() box.label(text='Procedural Texture / Cycles Baking') # box.prop(ob.data, 'forceBaking') # box.prop(ob.data, 'usePNG') box.prop(ob.data, 'bakeSize') box.prop(ob.data, 'bakeQuality') box = layout.box() box.prop(ob.data, 'attachedSound') row = box.row() row.prop(ob.data, 'autoPlaySound') row.prop(ob.data, 'loopSound') box.prop(ob.data, 'maxSoundDistance')