Skip to content
Snippets Groups Projects
export_fbx.py 116 KiB
Newer Older
  • Learn to ignore specific revisions
  •     def write_light(my_light):
            light = my_light.blenObject.data
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tModel: "Model::%s", "Light" {' % my_light.fbxName)
            fw('\n\t\tVersion: 232')
    
    
            write_object_props(my_light.blenObject, None, my_light.parRelMatrix())
    
            # Why are these values here twice?????? - oh well, follow the holy sdk's output
    
            # Blender light types match FBX's, funny coincidence, we just need to
            # be sure that all unsupported types are made into a point light
            #ePOINT,
            #eDIRECTIONAL
            #eSPOT
            light_type_items = {'POINT': 0, 'SUN': 1, 'SPOT': 2, 'HEMI': 3, 'AREA': 4}
            light_type = light_type_items[light.type]
    
    
            if light_type > 2:
                light_type = 1  # hemi and area lights become directional
    
                do_light = not (light.use_diffuse or light.use_specular)
                do_shadow = False
    
                do_light = not (light.use_only_shadow or (not light.use_diffuse and not light.use_specular))
    
                do_shadow = (light.shadow_method in {'RAY_SHADOW', 'BUFFER_SHADOW'})
    
    Campbell Barton's avatar
    Campbell Barton committed
            # scale = abs(global_matrix.to_scale()[0])  # scale is always uniform in this case  #  UNUSED
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\t\tProperty: "LightType", "enum", "",%i' % light_type)
            fw('\n\t\t\tProperty: "CastLightOnObject", "bool", "",1')
            fw('\n\t\t\tProperty: "DrawVolumetricLight", "bool", "",1')
            fw('\n\t\t\tProperty: "DrawGroundProjection", "bool", "",1')
            fw('\n\t\t\tProperty: "DrawFrontFacingVolumetricLight", "bool", "",0')
            fw('\n\t\t\tProperty: "GoboProperty", "object", ""')
            fw('\n\t\t\tProperty: "Color", "Color", "A+",1,1,1')
    
                fw('\n\t\t\tProperty: "OuterAngle", "Number", "A+",%.2f' %
                   math.degrees(light.spot_size))
                fw('\n\t\t\tProperty: "InnerAngle", "Number", "A+",%.2f' %
                   (math.degrees(light.spot_size) - math.degrees(light.spot_size) * light.spot_blend))
    
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\t\tProperty: "Fog", "Fog", "A+",50')
            fw('\n\t\t\tProperty: "Color", "Color", "A",%.2f,%.2f,%.2f' % tuple(light.color))
    
            fw('\n\t\t\tProperty: "Intensity", "Intensity", "A+",%.2f' % (light.energy * 100.0))
    
    Campbell Barton's avatar
    Campbell Barton committed
    
            fw('\n\t\t\tProperty: "Fog", "Fog", "A+",50')
            fw('\n\t\t\tProperty: "LightType", "enum", "",%i' % light_type)
            fw('\n\t\t\tProperty: "CastLightOnObject", "bool", "",%i' % do_light)
            fw('\n\t\t\tProperty: "DrawGroundProjection", "bool", "",1')
            fw('\n\t\t\tProperty: "DrawFrontFacingVolumetricLight", "bool", "",0')
            fw('\n\t\t\tProperty: "DrawVolumetricLight", "bool", "",1')
            fw('\n\t\t\tProperty: "GoboProperty", "object", ""')
    
            if light.type in {'SPOT', 'POINT'}:
                if light.falloff_type == 'CONSTANT':
                    fw('\n\t\t\tProperty: "DecayType", "enum", "",0')
                if light.falloff_type == 'INVERSE_LINEAR':
                    fw('\n\t\t\tProperty: "DecayType", "enum", "",1')
                    fw('\n\t\t\tProperty: "EnableFarAttenuation", "bool", "",1')
                    fw('\n\t\t\tProperty: "FarAttenuationEnd", "double", "",%.2f' % (light.distance * 2.0))
                if light.falloff_type == 'INVERSE_SQUARE':
                    fw('\n\t\t\tProperty: "DecayType", "enum", "",2')
                    fw('\n\t\t\tProperty: "EnableFarAttenuation", "bool", "",1')
                    fw('\n\t\t\tProperty: "FarAttenuationEnd", "double", "",%.2f' % (light.distance * 2.0))
    
    Campbell Barton's avatar
    Campbell Barton committed
    
            fw('\n\t\t\tProperty: "CastShadows", "bool", "",%i' % do_shadow)
            fw('\n\t\t\tProperty: "ShadowColor", "ColorRGBA", "",0,0,0,1')
            fw('\n\t\t}')
    
            fw('\n\t\tMultiLayer: 0'
               '\n\t\tMultiTake: 0'
               '\n\t\tShading: Y'
               '\n\t\tCulling: "CullingOff"'
               '\n\t\tTypeFlags: "Light"'
               '\n\t\tGeometryVersion: 124'
               '\n\t}'
               )
    
    
        # matrixOnly is not used at the moment
    
        def write_null(my_null=None, fbxName=None, fbxType="Null", fbxTypeFlags="Null"):
    
            if not fbxName:
                fbxName = my_null.fbxName
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tModel: "Model::%s", "%s" {' % (fbxName, fbxType))
            fw('\n\t\tVersion: 232')
    
            if my_null:
                poseMatrix = write_object_props(my_null.blenObject, None, my_null.parRelMatrix())[3]
            else:
                poseMatrix = write_object_props()[3]
    
    
            pose_items.append((fbxName, poseMatrix))
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\t}'
               '\n\t\tMultiLayer: 0'
               '\n\t\tMultiTake: 1'
               '\n\t\tShading: Y'
               '\n\t\tCulling: "CullingOff"'
               )
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\tTypeFlags: "%s"' % fbxTypeFlags)
            fw('\n\t}')
    
        if world:
            world_amb = world.ambient_color[:]
        else:
            world_amb = 0.0, 0.0, 0.0  # default value
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tMaterial: "Material::%s", "" {' % matname)
    
    
            # Todo, add more material Properties.
            if mat:
                mat_cold = tuple(mat.diffuse_color)
                mat_cols = tuple(mat.specular_color)
                #mat_colm = tuple(mat.mirCol) # we wont use the mirror color
    
                mat_colamb = 1.0, 1.0, 1.0
    
    
                mat_dif = mat.diffuse_intensity
                mat_amb = mat.ambient
    
                mat_hard = ((float(mat.specular_hardness) - 1.0) / 510.0) * 128.0
                mat_spec = mat.specular_intensity
    
                mat_alpha = mat.alpha
                mat_emit = mat.emit
                mat_shadeless = mat.use_shadeless
                if mat_shadeless:
                    mat_shader = 'Lambert'
                else:
                    if mat.diffuse_shader == 'LAMBERT':
                        mat_shader = 'Lambert'
                    else:
                        mat_shader = 'Phong'
            else:
    
                mat_cold = 0.8, 0.8, 0.8
                mat_cols = 1.0, 1.0, 1.0
                mat_colamb = 1.0, 1.0, 1.0
    
                mat_dif = 0.8
                mat_amb = 1.0
                mat_hard = 12.3
                mat_spec = 0.5
    
                mat_alpha = 1.0
                mat_emit = 0.0
                mat_shadeless = False
                mat_shader = 'Phong'
    
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\tVersion: 102')
            fw('\n\t\tShadingModel: "%s"' % mat_shader.lower())
            fw('\n\t\tMultiLayer: 0')
    
            fw('\n\t\tProperties60:  {')
            fw('\n\t\t\tProperty: "ShadingModel", "KString", "", "%s"' % mat_shader)
            fw('\n\t\t\tProperty: "MultiLayer", "bool", "",0')
            fw('\n\t\t\tProperty: "EmissiveColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_cold)  # emit and diffuse color are he same in blender
            fw('\n\t\t\tProperty: "EmissiveFactor", "double", "",%.4f' % mat_emit)
    
            fw('\n\t\t\tProperty: "AmbientColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_colamb)
            fw('\n\t\t\tProperty: "AmbientFactor", "double", "",%.4f' % mat_amb)
            fw('\n\t\t\tProperty: "DiffuseColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_cold)
            fw('\n\t\t\tProperty: "DiffuseFactor", "double", "",%.4f' % mat_dif)
            fw('\n\t\t\tProperty: "Bump", "Vector3D", "",0,0,0')
            fw('\n\t\t\tProperty: "TransparentColor", "ColorRGB", "",1,1,1')
            fw('\n\t\t\tProperty: "TransparencyFactor", "double", "",%.4f' % (1.0 - mat_alpha))
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\t\t\tProperty: "SpecularColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_cols)
                fw('\n\t\t\tProperty: "SpecularFactor", "double", "",%.4f' % mat_spec)
    
                fw('\n\t\t\tProperty: "ShininessExponent", "double", "",%.1f' % mat_hard)
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\t\t\tProperty: "ReflectionColor", "ColorRGB", "",0,0,0')
                fw('\n\t\t\tProperty: "ReflectionFactor", "double", "",1')
            fw('\n\t\t\tProperty: "Emissive", "ColorRGB", "",0,0,0')
            fw('\n\t\t\tProperty: "Ambient", "ColorRGB", "",%.1f,%.1f,%.1f' % mat_colamb)
            fw('\n\t\t\tProperty: "Diffuse", "ColorRGB", "",%.1f,%.1f,%.1f' % mat_cold)
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\t\t\tProperty: "Specular", "ColorRGB", "",%.1f,%.1f,%.1f' % mat_cols)
                fw('\n\t\t\tProperty: "Shininess", "double", "",%.1f' % mat_hard)
            fw('\n\t\t\tProperty: "Opacity", "double", "",%.1f' % mat_alpha)
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\t\t\tProperty: "Reflectivity", "double", "",0')
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\t}')
            fw('\n\t}')
    
    
        # tex is an Image (Arystan)
        def write_video(texname, tex):
            # Same as texture really!
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tVideo: "Video::%s", "Clip" {' % texname)
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    		Type: "Clip"
    		Properties60:  {
    			Property: "FrameRate", "double", "",0
    			Property: "LastFrame", "int", "",0
    			Property: "Width", "int", "",0
    			Property: "Height", "int", "",0''')
    
                fname_rel = bpy_extras.io_utils.path_reference(tex.filepath, base_src, base_dst, path_mode, "", copy_set, tex.library)
    
                fname_strip = bpy.path.basename(fname_rel)
    
                fname_strip = fname_rel = ""
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\t\tProperty: "Path", "charptr", "", "%s"' % fname_strip)
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    			Property: "StartFrame", "int", "",0
    			Property: "StopFrame", "int", "",0
    			Property: "PlaySpeed", "double", "",1
    			Property: "Offset", "KTime", "",0
    			Property: "InterlaceMode", "enum", "",0
    			Property: "FreeRunning", "bool", "",0
    			Property: "Loop", "bool", "",0
    			Property: "AccessMode", "enum", "",0
    		}
    		UseMipMap: 0''')
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\tFilename: "%s"' % fname_strip)
            fw('\n\t\tRelativeFilename: "%s"' % fname_rel)  # make relative
            fw('\n\t}')
    
    
        def write_texture(texname, tex, num):
            # if tex is None then this is a dummy tex
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tTexture: "Texture::%s", "TextureVideoClip" {' % texname)
            fw('\n\t\tType: "TextureVideoClip"')
            fw('\n\t\tVersion: 202')
    
            # TODO, rare case _empty_ exists as a name.
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\tTextureName: "Texture::%s"' % texname)
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    		Properties60:  {
    			Property: "Translation", "Vector", "A+",0,0,0
    			Property: "Rotation", "Vector", "A+",0,0,0
    			Property: "Scaling", "Vector", "A+",1,1,1''')
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\t\tProperty: "Texture alpha", "Number", "A+",%i' % num)
    
    
            # WrapModeU/V 0==rep, 1==clamp, TODO add support
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    			Property: "TextureTypeUse", "enum", "",0
    			Property: "CurrentTextureBlendMode", "enum", "",1
    			Property: "UseMaterial", "bool", "",0
    			Property: "UseMipMap", "bool", "",0
    			Property: "CurrentMappingType", "enum", "",0
    			Property: "UVSwap", "bool", "",0''')
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\t\tProperty: "WrapModeU", "enum", "",%i' % tex.use_clamp_x)
            fw('\n\t\t\tProperty: "WrapModeV", "enum", "",%i' % tex.use_clamp_y)
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    			Property: "TextureRotationPivot", "Vector3D", "",0,0,0
    			Property: "TextureScalingPivot", "Vector3D", "",0,0,0
    			Property: "VideoProperty", "object", ""
    		}''')
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\tMedia: "Video::%s"' % texname)
    
                fname_rel = bpy_extras.io_utils.path_reference(tex.filepath, base_src, base_dst, path_mode, "", copy_set, tex.library)
                fname_strip = bpy.path.basename(fname_rel)
    
                fname_strip = fname_rel = ""
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\tFileName: "%s"' % fname_strip)
            fw('\n\t\tRelativeFilename: "%s"' % fname_rel)  # need some make relative command
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    		ModelUVTranslation: 0,0
    		ModelUVScaling: 1,1
    		Texture_Alpha_Source: "None"
    		Cropping: 0,0,0,0
    	}''')
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tDeformer: "Deformer::Skin %s", "Skin" {' % obname)
            fw('''
    
    		Version: 100
    		MultiLayer: 0
    		Type: "Skin"
    		Properties60:  {
    		}
    		Link_DeformAcuracy: 50
    	}''')
    
    
        # in the example was 'Bip01 L Thigh_2'
        def write_sub_deformer_skin(my_mesh, my_bone, weights):
    
    
            Each subdeformer is specific to a mesh, but the bone it links to can be used by many sub-deformers
    
            So the SubDeformer needs the mesh-object name as a prefix to make it unique
    
            Its possible that there is no matching vgroup in this mesh, in that case no verts are in the subdeformer,
            a but silly but dosnt really matter
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tDeformer: "SubDeformer::Cluster %s %s", "Cluster" {' % (my_mesh.fbxName, my_bone.fbxName))
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    		Version: 100
    		MultiLayer: 0
    		Type: "Cluster"
    		Properties60:  {
    			Property: "SrcModel", "object", ""
    			Property: "SrcModelReference", "object", ""
    		}
    		UserData: "", ""''')
    
    
            # Support for bone parents
            if my_mesh.fbxBoneParent:
                if my_mesh.fbxBoneParent == my_bone:
                    # TODO - this is a bit lazy, we could have a simple write loop
                    # for this case because all weights are 1.0 but for now this is ok
                    # Parent Bones arent used all that much anyway.
                    vgroup_data = [(j, 1.0) for j in range(len(my_mesh.blenData.vertices))]
                else:
                    # This bone is not a parent of this mesh object, no weights
                    vgroup_data = []
    
            else:
                # Normal weight painted mesh
                if my_bone.blenName in weights[0]:
    
                    group_index = weights[0].index(my_bone.blenName)
                    vgroup_data = [(j, weight[group_index]) for j, weight in enumerate(weights[1]) if weight[group_index]]
                else:
                    vgroup_data = []
    
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\tIndexes: ')
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('%i' % vg[0])
    
    Campbell Barton's avatar
    Campbell Barton committed
                        fw('\n\t\t')
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw(',%i' % vg[0])
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\tWeights: ')
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('%.8f' % vg[1])
    
    Campbell Barton's avatar
    Campbell Barton committed
                        fw('\n\t\t')
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw(',%.8f' % vg[1])
    
            # Set TransformLink to the global transform of the bone and Transform
            # equal to the mesh's transform in bone space.
            # http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/why-the-values-return-by-fbxcluster-gettransformmatrix-x-not-same-with-the-value-in-ascii-fbx-file/
    
            global_bone_matrix = (my_bone.fbxArm.matrixWorld * my_bone.restMatrix) * mtx4_z90
            global_mesh_matrix = my_mesh.matrixWorld
            transform_matrix = (global_bone_matrix.inverted() * global_mesh_matrix)
    
            global_bone_matrix_string = mat4x4str(global_bone_matrix )
            transform_matrix_string = mat4x4str(transform_matrix )
    
            fw('\n\t\tTransform: %s' % transform_matrix_string)
            fw('\n\t\tTransformLink: %s' % global_bone_matrix_string)
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t}')
    
            # if there are non None materials on this mesh
            do_materials = bool([m for m in my_mesh.blenMaterials if m is not None])
            do_textures = bool([t for t in my_mesh.blenTextures if t is not None])
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            do_uvs = bool(me.uv_layers)
    
            do_shapekeys = (my_mesh.blenObject.type == 'MESH' and
                            my_mesh.blenObject.data.shape_keys and
                            len(my_mesh.blenObject.data.vertices) == len(me.vertices))
    
            # print(len(my_mesh.blenObject.data.vertices), len(me.vertices))  # XXX does not work when org obj is no mesh!
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tModel: "Model::%s", "Mesh" {' % my_mesh.fbxName)
            fw('\n\t\tVersion: 232')  # newline is added in write_object_props
    
    
            # convert into lists once.
    
            poseMatrix = write_object_props(my_mesh.blenObject, None, my_mesh.parRelMatrix())[3]
    
    
            # Calculate the global transform for the mesh in the bind pose the same way we do
            # in write_sub_deformer_skin
            globalMeshBindPose = my_mesh.matrixWorld * mtx4_z90
            pose_items.append((my_mesh.fbxName, globalMeshBindPose))
    
            if do_shapekeys:
                for kb in my_mesh.blenObject.data.shape_keys.key_blocks[1:]:
                    fw('\n\t\t\tProperty: "%s", "Number", "AN",0' % kb.name)
    
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\t}')
    
            fw('\n\t\tMultiLayer: 0'
               '\n\t\tMultiTake: 1'
               '\n\t\tShading: Y'
               '\n\t\tCulling: "CullingOff"'
               )
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\tVertices: ')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            _nchunk = 12  # Number of coordinates per line.
            t_co = [None] * len(me.vertices) * 3
            me.vertices.foreach_get("co", t_co)
            fw(',\n\t\t          '.join(','.join('%.6f' % co for co in chunk) for chunk in grouper_exact(t_co, _nchunk)))
            del t_co
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\tPolygonVertexIndex: ')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            _nchunk = 32  # Number of indices per line.
            # A bit more complicated, as we have to ^-1 last index of each loop.
            # NOTE: Here we assume that loops order matches polygons order!
            t_vi = [None] * len(me.loops)
            me.loops.foreach_get("vertex_index", t_vi)
            t_ls = [None] * len(me.polygons)
            me.polygons.foreach_get("loop_start", t_ls)
            if t_ls != sorted(t_ls):
                print("Error: polygons and loops orders do not match!")
            for ls in t_ls:
                t_vi[ls - 1] ^= -1
            prep = ',\n\t\t                    '
            fw(prep.join(','.join('%i' % vi for vi in chunk) for chunk in grouper_exact(t_vi, _nchunk)))
            del t_vi
            del t_ls
    
            if use_mesh_edges:
                t_vi = [None] * len(me.edges) * 2
                me.edges.foreach_get("vertices", t_vi)
    
                # write loose edges as faces.
                t_el = [None] * len(me.edges)
                me.edges.foreach_get("is_loose", t_el)
                num_lose = sum(t_el)
                if num_lose != 0:
                    it_el = ((vi ^ -1) if (idx % 2) else vi for idx, vi in enumerate(t_vi) if t_el[idx // 2])
                    if (len(me.loops)):
                        fw(prep)
                    fw(prep.join(','.join('%i' % vi for vi in chunk) for chunk in grouper_exact(it_el, _nchunk)))
    
                fw('\n\t\tEdges: ')
                fw(',\n\t\t       '.join(','.join('%i' % vi for vi in chunk) for chunk in grouper_exact(t_vi, _nchunk)))
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\tGeometryVersion: 124')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            _nchunk = 12  # Number of coordinates per line.
            t_vn = [None] * len(me.loops) * 3
            me.calc_normals_split()
            # NOTE: Here we assume that loops order matches polygons order!
            me.loops.foreach_get("normal", t_vn)
            fw('\n\t\tLayerElementNormal: 0 {'
               '\n\t\t\tVersion: 101'
               '\n\t\t\tName: ""'
               '\n\t\t\tMappingInformationType: "ByPolygonVertex"'
               '\n\t\t\tReferenceInformationType: "Direct"'  # We could save some space with IndexToDirect here too...
               '\n\t\t\tNormals: ')
            fw(',\n\t\t\t         '.join(','.join('%.6f' % n for n in chunk) for chunk in grouper_exact(t_vn, _nchunk)))
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            del t_vn
            me.free_normals_split()
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            _nchunk = 64  # Number of bool per line.
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                t_ps = [None] * len(me.polygons)
                me.polygons.foreach_get("use_smooth", t_ps)
                fw('\n\t\tLayerElementSmoothing: 0 {'
                   '\n\t\t\tVersion: 102'
                   '\n\t\t\tName: ""'
                   '\n\t\t\tMappingInformationType: "ByPolygon"'
                   '\n\t\t\tReferenceInformationType: "Direct"'
                   '\n\t\t\tSmoothing: ')
                fw(',\n\t\t\t           '.join(','.join('%d' % b for b in chunk) for chunk in grouper_exact(t_ps, _nchunk)))
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                t_es = [None] * len(me.edges)
                me.edges.foreach_get("use_edge_sharp", t_es)
                fw('\n\t\tLayerElementSmoothing: 0 {'
                   '\n\t\t\tVersion: 101'
                   '\n\t\t\tName: ""'
                   '\n\t\t\tMappingInformationType: "ByEdge"'
                   '\n\t\t\tReferenceInformationType: "Direct"'
                   '\n\t\t\tSmoothing: ')
                fw(',\n\t\t\t           '
                   ''.join(','.join('%d' % (not b) for b in chunk) for chunk in grouper_exact(t_es, _nchunk)))
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\t\t}')
    
            elif mesh_smooth_type == 'OFF':
                pass
            else:
                raise Exception("invalid mesh_smooth_type: %r" % mesh_smooth_type)
    
    
            # Write VertexColor Layers
            # note, no programs seem to use this info :/
            collayers = []
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            if len(me.vertex_colors):
                collayers = me.vertex_colors
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                col2idx = None
                _nchunk = 4  # Number of colors per line
                _nchunk_idx = 64  # Number of color indices per line
    
                for colindex, collayer in enumerate(collayers):
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    collayer.data.foreach_get("color", t_lc)
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    fw('\n\t\tLayerElementColor: %i {'
                       '\n\t\t\tVersion: 101'
                       '\n\t\t\tName: "%s"'
                       '\n\t\t\tMappingInformationType: "ByPolygonVertex"'
                       '\n\t\t\tReferenceInformationType: "IndexToDirect"'
                       '\n\t\t\tColors: ' % (colindex, collayer.name))
    
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    fw(',\n\t\t\t        '.join(','.join('%.6f,%.6f,%.6f,1' % c for c in chunk)
                                                for chunk in grouper_exact(col2idx, _nchunk)))
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('\n\t\t\tColorIndex: ')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    col2idx = {col: idx for idx, col in enumerate(col2idx)}
                    fw(',\n\t\t\t            '
    
                       ''.join(','.join('%d' % col2idx[c] for c in chunk) for chunk in grouper_exact(lc, _nchunk_idx)))
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('\n\t\t}')
    
    
            # Write UV and texture layers.
            uvlayers = []
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            uvtextures = []
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                uvlayers = me.uv_layers
    
                uvtextures = me.uv_textures
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                t_pi = None
                uv2idx = None
                tex2idx = None
                _nchunk = 6  # Number of UVs per line
                _nchunk_idx = 64  # Number of UV indices per line
                if do_textures:
                    is_tex_unique = len(my_mesh.blenTextures) == 1
                    tex2idx = {None: -1}
                    tex2idx.update({tex: i for i, tex in enumerate(my_mesh.blenTextures)})
    
                for uvindex, (uvlayer, uvtexture) in enumerate(zip(uvlayers, uvtextures)):
                    uvlayer.data.foreach_get("uv", t_uv)
    
                    uvco = tuple(zip(*[iter(t_uv)] * 2))
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    fw('\n\t\tLayerElementUV: %d {'
                       '\n\t\t\tVersion: 101'
                       '\n\t\t\tName: "%s"'
                       '\n\t\t\tMappingInformationType: "ByPolygonVertex"'
                       '\n\t\t\tReferenceInformationType: "IndexToDirect"'
                       '\n\t\t\tUV: ' % (uvindex, uvlayer.name))
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    fw(',\n\t\t\t    '
                       ''.join(','.join('%.6f,%.6f' % uv for uv in chunk) for chunk in grouper_exact(uv2idx, _nchunk)))
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('\n\t\t\tUVIndex: ')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    uv2idx = {uv: idx for idx, uv in enumerate(uv2idx)}
                    fw(',\n\t\t\t         '
    
                       ''.join(','.join('%d' % uv2idx[uv] for uv in chunk) for chunk in grouper_exact(uvco, _nchunk_idx)))
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('\n\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                        fw('\n\t\tLayerElementTexture: %d {'
                           '\n\t\t\tVersion: 101'
                           '\n\t\t\tName: "%s"' 
                           '\n\t\t\tMappingInformationType: "%s"'
                           '\n\t\t\tReferenceInformationType: "IndexToDirect"'
                           '\n\t\t\tBlendMode: "Translucent"'
                           '\n\t\t\tTextureAlpha: 1'
                           '\n\t\t\tTextureId: '
                           % (uvindex, uvlayer.name, ('AllSame' if is_tex_unique else 'ByPolygon')))
                        if is_tex_unique:
    
    Campbell Barton's avatar
    Campbell Barton committed
                            fw('0')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                            t_pi = (d.image for d in uvtexture.data)  # Can't use foreach_get here :(
                            fw(',\n\t\t\t           '.join(','.join('%d' % tex2idx[i] for i in chunk)
                                                           for chunk in grouper_exact(t_pi, _nchunk_idx)))
    
                        fw('\n\t\t}')
                if not do_textures:
                    fw('\n\t\tLayerElementTexture: 0 {'
                       '\n\t\t\tVersion: 101'
                       '\n\t\t\tName: ""'
                       '\n\t\t\tMappingInformationType: "NoMappingInformation"'
                       '\n\t\t\tReferenceInformationType: "IndexToDirect"'
                       '\n\t\t\tBlendMode: "Translucent"'
                       '\n\t\t\tTextureAlpha: 1'
                       '\n\t\t\tTextureId: '
                       '\n\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                is_mat_unique = len(my_mesh.blenMaterials) == 1
                fw('\n\t\tLayerElementMaterial: 0 {'
                   '\n\t\t\tVersion: 101'
                   '\n\t\t\tName: ""'
                   '\n\t\t\tMappingInformationType: "%s"'
                   '\n\t\t\tReferenceInformationType: "IndexToDirect"'
                   '\n\t\t\tMaterials: ' % ('AllSame' if is_mat_unique else 'ByPolygon',))
                if is_mat_unique:
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('0')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    _nchunk = 64  # Number of material indices per line
    
                    # Build a material mapping for this
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    mat2idx = {mt: i for i, mt in enumerate(my_mesh.blenMaterials)}  # (local-mat, tex) -> global index.
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    if me.uv_textures.active and do_uvs:
                        poly_tex = me.uv_textures.active.data
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                        poly_tex = [None] * len(me.polygons)
                    _it_mat = (mats[p.material_index] for p in me.polygons)
                    _it_tex = (pt.image if pt else None for pt in poly_tex)  # WARNING - MULTI UV LAYER IMAGES NOT SUPPORTED
                    t_mti = (mat2idx[m, t] for m, t in zip(_it_mat, _it_tex))
                    fw(',\n\t\t\t           '
                       ''.join(','.join('%d' % i for i in chunk) for chunk in grouper_exact(t_mti, _nchunk)))
    
    Campbell Barton's avatar
    Campbell Barton committed
                fw('\n\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            fw('\n\t\tLayer: 0 {'
               '\n\t\t\tVersion: 100'
               '\n\t\t\tLayerElement:  {'
               '\n\t\t\t\tType: "LayerElementNormal"'
               '\n\t\t\t\tTypedIndex: 0'
               '\n\t\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                fw('\n\t\t\tLayerElement:  {'
                   '\n\t\t\t\tType: "LayerElementSmoothing"'
                   '\n\t\t\t\tTypedIndex: 0'
                   '\n\t\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                fw('\n\t\t\tLayerElement:  {'
                   '\n\t\t\t\tType: "LayerElementColor"'
                   '\n\t\t\t\tTypedIndex: 0'
                   '\n\t\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                fw('\n\t\t\tLayerElement:  {'
                   '\n\t\t\t\tType: "LayerElementUV"'
                   '\n\t\t\t\tTypedIndex: 0'
                   '\n\t\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            # Always write this
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                fw('\n\t\t\tLayerElement:  {'
                   '\n\t\t\t\tType: "LayerElementTexture"'
                   '\n\t\t\t\tTypedIndex: 0'
                   '\n\t\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            if do_materials:
                fw('\n\t\t\tLayerElement:  {'
                   '\n\t\t\t\tType: "LayerElementMaterial"'
                   '\n\t\t\t\tTypedIndex: 0'
                   '\n\t\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            fw('\n\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            if len(uvlayers) > 1:
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    fw('\n\t\tLayer: %d {'
                       '\n\t\t\tVersion: 100'
                       '\n\t\t\tLayerElement:  {'
                       '\n\t\t\t\tType: "LayerElementUV"'
                       '\n\t\t\t\tTypedIndex: %d'
                       '\n\t\t\t}' % (i, i))
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                        fw('\n\t\t\tLayerElement:  {'
                           '\n\t\t\t\tType: "LayerElementTexture"'
                           '\n\t\t\t\tTypedIndex: %d'
                           '\n\t\t\t}' % i)
    
                    else:
                        fw('\n\t\t\tLayerElement:  {'
                           '\n\t\t\t\tType: "LayerElementTexture"'
                           '\n\t\t\t\tTypedIndex: 0'
                           '\n\t\t\t}')
    
    Campbell Barton's avatar
    Campbell Barton committed
                    fw('\n\t\t}')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            # XXX Col layers are written before UV ones above, why adding them after UV here???
            #     And why this offset based on len(UV layers) - 1???
            #     I have the feeling some indices are wrong here!
            #     --mont29
    
            if len(collayers) > 1:
                # Take into account any UV layers
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                layer_offset = len(uvlayers) - 1 if uvlayers else 0
    
                for i in range(layer_offset, len(collayers) + layer_offset):
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    fw('\n\t\tLayer: %d {'
                       '\n\t\t\tVersion: 100'
                       '\n\t\t\tLayerElement:  {'
                       '\n\t\t\t\tType: "LayerElementColor"'
                       '\n\t\t\t\tTypedIndex: %d'
                       '\n\t\t\t}'
                       '\n\t\t}' % (i, i))
    
            if do_shapekeys:
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                # Not sure this works really good...
                #     Aren't key's co already relative if set as such?
                #     Also, does not handle custom relative option for each key...
                # --mont29
                import operator
    
                key_blocks = my_mesh.blenObject.data.shape_keys.key_blocks[:]
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                t_sk_basis = [None] * len(me.vertices) * 3
                t_sk = [None] * len(me.vertices) * 3
                key_blocks[0].data.foreach_get("co", t_sk_basis)
                _nchunk = 4  # Number of delta coordinates per line
                _nchunk_idx = 32  # Number of vert indices per line
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                for kb in key_blocks[1:]:
                    kb.data.foreach_get("co", t_sk)
                    _dcos = tuple(zip(*[map(operator.sub, t_sk, t_sk_basis)] * 3))
                    verts = tuple(i for i, dco in enumerate(_dcos) if sum(map(operator.pow, dco, (2, 2, 2))) > 3e-12)
                    dcos = (_dcos[i] for i in verts)
                    fw('\n\t\tShape: "%s" {'
                       '\n\t\t\tIndexes: ' % kb.name)
                    fw(',\n\t\t\t         '
                       ''.join(','.join('%d' % i for i in chunk) for chunk in grouper_exact(verts, _nchunk_idx)))
    
    
                    fw('\n\t\t\tVertices: ')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    fw(',\n\t\t\t          '
                       ''.join(','.join('%.6f,%.6f,%.6f' % c for c in chunk) for chunk in grouper_exact(dcos, _nchunk)))
    
                    # all zero, why? - campbell
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    # Would need to recompute them I guess... and I assume those are supposed to be delta as well?
    
                    fw('\n\t\t\tNormals: ')
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    fw(',\n\t\t\t         '
                       ''.join(','.join('0,0,0' for c in chunk) for chunk in grouper_exact(range(len(verts)), _nchunk)))
    
                    fw('\n\t\t}')
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\t}')
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('\n\tGroupSelection: "GroupSelection::%s", "Default" {' % name)
    
    Campbell Barton's avatar
    Campbell Barton committed
            fw('''
    
    		Properties60:  {
    			Property: "MultiLayer", "bool", "",0
    			Property: "Pickable", "bool", "",1
    			Property: "Transformable", "bool", "",1
    			Property: "Show", "bool", "",1
    		}
    		MultiLayer: 0
    	}''')
    
    
        # add meshes here to clear because they are not used anywhere.
        meshes_to_clear = []
    
        ob_meshes = []
        ob_lights = []
        ob_cameras = []
        # in fbx we export bones as children of the mesh
        # armatures not a part of a mesh, will be added to ob_arms
        ob_bones = []
        ob_arms = []
    
    
        # List of types that have blender objects (not bones)
        ob_all_typegroups = [ob_meshes, ob_lights, ob_cameras, ob_arms, ob_null]
    
    
        groups = []  # blender groups, only add ones that have objects in the selections
    
        materials = set()  # (mat, image) items
        textures = set()
    
        tmp_ob_type = None  # in case no objects are exported, so as not to raise an error
    
    Campbell Barton's avatar
    Campbell Barton committed
    ## XXX
    
            # This is needed so applying modifiers dosnt apply the armature deformation, its also needed
            # ...so mesh objects return their rest worldspace matrix when bone-parents are exported as weighted meshes.
            # set every armature to its rest, backup the original values so we done mess up the scene
            ob_arms_orig_rest = [arm.pose_position for arm in bpy.data.armatures]
    
            for arm in bpy.data.armatures:
                arm.pose_position = 'REST'
    
            if ob_arms_orig_rest:
                for ob_base in bpy.data.objects:
                    if ob_base.type == 'ARMATURE':
    
                        ob_base.update_tag()
    
    
                # This causes the makeDisplayList command to effect the mesh
                scene.frame_set(scene.frame_current)
    
    
        for ob_base in context_objects:
    
            if ob_base.parent and ob_base.parent.dupli_type in {'VERTS', 'FACES'}:
    
            obs = [(ob_base, ob_base.matrix_world.copy())]
    
            if ob_base.dupli_type != 'NONE':
    
                ob_base.dupli_list_create(scene)
    
                obs = [(dob.object, dob.matrix.copy()) for dob in ob_base.dupli_list]
    
    
            for ob, mtx in obs:
                tmp_ob_type = ob.type
                if tmp_ob_type == 'CAMERA':
    
                        ob_cameras.append(my_object_generic(ob, mtx))
                elif tmp_ob_type == 'LAMP':
    
                        ob_lights.append(my_object_generic(ob, mtx))
                elif tmp_ob_type == 'ARMATURE':
    
                        # TODO - armatures dont work in dupligroups!
    
                        if ob not in ob_arms:
                            ob_arms.append(ob)
    
                        # ob_arms.append(ob) # replace later. was "ob_arms.append(sane_obname(ob), ob)"
                elif tmp_ob_type == 'EMPTY':
    
                        ob_null.append(my_object_generic(ob, mtx))
    
                    origData = True
                    if tmp_ob_type != 'MESH':
    
                            me = ob.to_mesh(scene, True, 'PREVIEW')
    
                            mats = me.materials
                            origData = False
                    else:
                        # Mesh Type!
    
                            me = ob.to_mesh(scene, True, 'PREVIEW')
    
    
                            # print ob, me, me.getVertGroupNames()
    
                            origData = False
                            mats = me.materials
                        else:
                            me = ob.data
    
                            mats = me.materials
    
    # 						# Support object colors
    # 						tmp_colbits = ob.colbits
    # 						if tmp_colbits:
    # 							tmp_ob_mats = ob.getMaterials(1) # 1 so we get None's too.
    # 							for i in xrange(16):
    # 								if tmp_colbits & (1<<i):
    # 									mats[i] = tmp_ob_mats[i]
    # 							del tmp_ob_mats
    # 						del tmp_colbits
    
                    if me:
    
    # 					# This WILL modify meshes in blender if use_mesh_modifiers is disabled.
    
    # 					# so strictly this is bad. but only in rare cases would it have negative results
    # 					# say with dupliverts the objects would rotate a bit differently
    # 					if EXP_MESH_HQ_NORMALS:
    # 						BPyMesh.meshCalcNormals(me) # high quality normals nice for realtime engines.
    
    
                        texture_set_local = set()
                        material_set_local = set()
                        if me.uv_textures:
                            for uvlayer in me.uv_textures:
                                for p, p_uv in zip(me.polygons, uvlayer.data):
                                    tex = p_uv.image
                                    texture_set_local.add(tex)
                                    mat = mats[p.material_index]
    
                                    # Should not be needed anymore.
                                    #try:
                                        #mat = mats[p.material_index]
                                    #except:
                                        #mat = None
    
                                    material_set_local.add((mat, tex))
    
    
                        else:
                            for mat in mats:
                                # 2.44 use mat.lib too for uniqueness
    
                                material_set_local.add((mat, None))
    
                        textures |= texture_set_local
                        materials |= material_set_local
    
                            armob = ob.find_armature()
                            blenParentBoneName = None
    
                            # parent bone - special case
                            if (not armob) and ob.parent and ob.parent.type == 'ARMATURE' and \
                                    ob.parent_type == 'BONE':
                                armob = ob.parent
                                blenParentBoneName = ob.parent_bone
    
                            if armob and armob not in ob_arms:
                                ob_arms.append(armob)
    
                            # Warning for scaled, mesh objects with armatures
                            if abs(ob.scale[0] - 1.0) > 0.05 or abs(ob.scale[1] - 1.0) > 0.05 or abs(ob.scale[1] - 1.0) > 0.05:
    
                                operator.report({'WARNING'}, "Object '%s' has a scale of (%.3f, %.3f, %.3f), " \
                                                             "Armature deformation will not work as expected " \
                                                             "(apply Scale to fix)" % ((ob.name,) + tuple(ob.scale)))
    
    
                        else:
                            blenParentBoneName = armob = None
    
                        my_mesh = my_object_generic(ob, mtx)
    
                        my_mesh.blenData = me
                        my_mesh.origData = origData
    
                        my_mesh.blenMaterials = list(material_set_local)
    
                        my_mesh.blenTextures = list(texture_set_local)
    
                        # sort the name so we get predictable output, some items may be NULL
    
                        my_mesh.blenMaterials.sort(key=lambda m: (getattr(m[0], "name", ""), getattr(m[1], "name", "")))
                        my_mesh.blenTextures.sort(key=lambda m: getattr(m, "name", ""))
    
    
                        # if only 1 null texture then empty the list
                        if len(my_mesh.blenTextures) == 1 and my_mesh.blenTextures[0] is None:
                            my_mesh.blenTextures = []
    
    
                        my_mesh.fbxArm = armob  # replace with my_object_generic armature instance later
                        my_mesh.fbxBoneParent = blenParentBoneName  # replace with my_bone instance later
    
                ob_base.dupli_list_clear()
    
            # now we have the meshes, restore the rest arm position
            for i, arm in enumerate(bpy.data.armatures):
                arm.pose_position = ob_arms_orig_rest[i]
    
            if ob_arms_orig_rest:
                for ob_base in bpy.data.objects:
                    if ob_base.type == 'ARMATURE':
    
                        ob_base.update_tag()
    
                # This causes the makeDisplayList command to effect the mesh
                scene.frame_set(scene.frame_current)
    
    
        del tmp_ob_type, context_objects
    
    
        # now we have collected all armatures, add bones
        for i, ob in enumerate(ob_arms):
    
            ob_arms[i] = my_arm = my_object_generic(ob)
    
    
            my_arm.fbxBones = []
            my_arm.blenData = ob.data
    
                my_arm.blenAction = ob.animation_data.action
    
    
            # fbxName, blenderObject, my_bones, blenderActions
            #ob_arms[i] = fbxArmObName, ob, arm_my_bones, (ob.action, [])
    
    
            if use_armature_deform_only:
                # tag non deforming bones that have no deforming children
                deform_map = dict.fromkeys(my_arm.blenData.bones, False)
                for bone in my_arm.blenData.bones:
                    if bone.use_deform:
                        deform_map[bone] = True
                        # tag all parents, even ones that are not deform since their child _is_
                        for parent in bone.parent_recursive:
                            deform_map[parent] = True
    
    
            for bone in my_arm.blenData.bones:
    
    
                if use_armature_deform_only:
                    # if this bone doesnt deform, and none of its children deform, skip it!
                    if not deform_map[bone]:
                        continue
    
    
                my_bone = my_bone_class(bone, my_arm)
    
                my_arm.fbxBones.append(my_bone)
                ob_bones.append(my_bone)
    
        # add the meshes to the bones and replace the meshes armature with own armature class
        #for obname, ob, mtx, me, mats, arm, armname in ob_meshes:
        for my_mesh in ob_meshes:
            # Replace
            # ...this could be sped up with dictionary mapping but its unlikely for
            # it ever to be a bottleneck - (would need 100+ meshes using armatures)