Skip to content
Snippets Groups Projects
export_fbx.py 112 KiB
Newer Older
# ##### 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

# Script copyright (C) Campbell Barton

"""
This script is an exporter to the FBX file format.

http://wiki.blender.org/index.php/Scripts/Manual/Export/autodesk_fbx
"""

import os
import time
from mathutils import Vector, Matrix
# I guess FBX uses degrees instead of radians (Arystan).
# Call this function just before writing to FBX.
# 180 / math.pi == 57.295779513
def tuple_rad_to_deg(eul):
    return eul[0] * 57.295779513, eul[1] * 57.295779513, eul[2] * 57.295779513

# Used to add the scene name into the filepath without using odd chars
sane_name_mapping_ob = {}
sane_name_mapping_ob_unique = set()
sane_name_mapping_mat = {}
sane_name_mapping_tex = {}
sane_name_mapping_take = {}
sane_name_mapping_group = {}

# Make sure reserved names are not used
sane_name_mapping_ob['Scene'] = 'Scene_'
sane_name_mapping_ob_unique.add('Scene_')
def increment_string(t):
    name = t
    num = ''
    while name and name[-1].isdigit():
        num = name[-1] + num
        name = name[:-1]
    if num:
        return '%s%d' % (name, int(num) + 1)
    else:
        return name + '_0'
# todo - Disallow the name 'Scene' - it will bugger things up.
def sane_name(data, dct, unique_set=None):
    if type(data) == tuple:  # materials are paired up with images
        data, other = data
        use_other = True
    else:
        other = None
        use_other = False

    name = data.name if data else None
    orig_name = name

    if other:
        orig_name_other = other.name
        name = '%s #%s' % (name, orig_name_other)
    else:
        orig_name_other = None

    # dont cache, only ever call once for each data type now,
    # so as to avoid namespace collision between types - like with objects <-> bones
    #try:		return dct[name]
    #except:		pass

    if not name:
        name = 'unnamed'  # blank string, ASKING FOR TROUBLE!
        name = bpy.path.clean_name(name)  # use our own
    name_unique = dct.values() if unique_set is None else unique_set

    while name in name_unique:
    if use_other:  # even if other is None - orig_name_other will be a string or None
        dct[orig_name, orig_name_other] = name
    else:
        dct[orig_name] = name

    if unique_set is not None:
        unique_set.add(name)

    return sane_name(data, sane_name_mapping_ob, sane_name_mapping_ob_unique)


def sane_matname(data):
    return sane_name(data, sane_name_mapping_mat)


def sane_texname(data):
    return sane_name(data, sane_name_mapping_tex)


def sane_takename(data):
    return sane_name(data, sane_name_mapping_take)


def sane_groupname(data):
    return sane_name(data, sane_name_mapping_group)
    return '%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f,%.15f' % tuple([f for v in mat for f in v])


# ob must be OB_MESH
def BPyMesh_meshWeight2List(ob, me):
    ''' Takes a mesh and return its group names and a list of lists, one list per vertex.
    aligning the each vert list with the group names, each list contains float value for the weight.
    These 2 lists can be modified and then used with list2MeshWeight to apply the changes.
    '''

    # Clear the vert group.
    groupNames = [g.name for g in ob.vertex_groups]
    len_groupNames = len(groupNames)

    if not len_groupNames:
        # no verts? return a vert aligned empty list
        return [[] for i in range(len(me.vertices))], []
    else:
        vWeightList = [[0.0] * len_groupNames for i in range(len(me.vertices))]

    for i, v in enumerate(me.vertices):
        for g in v.groups:
            # possible weights are out of range
            index = g.group
            if index < len_groupNames:
                vWeightList[i][index] = g.weight
def meshNormalizedWeights(ob, me):
    groupNames, vWeightList = BPyMesh_meshWeight2List(ob, me)

    for i, vWeights in enumerate(vWeightList):
        tot = 0.0
        for w in vWeights:

        if tot:
            for j, w in enumerate(vWeights):

    return groupNames, vWeightList

header_comment = \
'''; FBX 6.1.0 project file
; Created by Blender FBX Exporter
; for support mail: ideasman42@gmail.com
; ----------------------------------------------------

'''

# This func can be called with just the filepath
def save_single(operator, scene, filepath="",
        global_matrix=None,
        context_objects=None,
        object_types={'EMPTY', 'CAMERA', 'LAMP', 'ARMATURE', 'MESH'},
        use_anim=True,
        use_anim_optimize=True,
        anim_optimize_precision=6,
    # Only used for camera and lamp rotations
    mtx_x90 = Matrix.Rotation(math.pi / 2.0, 3, 'X')
    # Used for mesh and armature rotations
    mtx4_z90 = Matrix.Rotation(math.pi / 2.0, 4, 'Z')
    # Rotation does not work for XNA animations.  I do not know why but they end up a mess! (JCB)
    if use_rotate_workaround:
        # Set rotation to Matrix Identity for XNA (JCB)
        mtx4_z90.identity()
    if global_matrix is None:
        global_matrix = Matrix()

    # Use this for working out paths relative to the export location
    base_src = os.path.dirname(bpy.data.filepath)

    # collect images to copy
    copy_set = set()

    # ----------------------------------------------
    # storage classes
    class my_bone_class(object):
        __slots__ = ("blenName",
                     "blenBone",
                     "blenMeshes",
                     "restMatrix",
                     "parent",
                     "blenName",
                     "fbxName",
                     "fbxArm",
                     "__pose_bone",
                     "__anim_poselist")

        def __init__(self, blenBone, fbxArm):
            # This is so 2 armatures dont have naming conflicts since FBX bones use object namespace
            self.fbxName = sane_obname(blenBone)

            self.blenName = blenBone.name
            self.blenBone = blenBone
            self.blenMeshes = {}					# fbxMeshObName : mesh
            self.fbxArm = fbxArm
            self.restMatrix = blenBone.matrix_local
            # self.restMatrixInv =	self.restMatrix.inverted()
            # self.restMatrixLocal =	None # set later, need parent matrix

            pose = fbxArm.blenObject.pose
            self.__pose_bone = pose.bones[self.blenName]

            # store a list if matricies here, (poseMatrix, head, tail)
            # {frame:posematrix, frame:posematrix, ...}
            self.__anim_poselist = {}

        '''
        def calcRestMatrixLocal(self):
            if self.parent:
                self.restMatrixLocal = self.restMatrix * self.parent.restMatrix.inverted()
            else:
                self.restMatrixLocal = self.restMatrix.copy()
        '''
        def setPoseFrame(self, f):
            # cache pose info here, frame must be set beforehand

            # Didnt end up needing head or tail, if we do - here it is.
            '''
            self.__anim_poselist[f] = (\
                self.__pose_bone.poseMatrix.copy(),\
                self.__pose_bone.head.copy(),\
                self.__pose_bone.tail.copy() )
            self.__anim_poselist[f] = self.__pose_bone.matrix.copy()
        def getPoseBone(self):
            return self.__pose_bone
        def getPoseMatrix(self, f):  # ----------------------------------------------
            return self.__anim_poselist[f]
        '''
        def getPoseHead(self, f):
            #return self.__pose_bone.head.copy()
            return self.__anim_poselist[f][1].copy()
        def getPoseTail(self, f):
            #return self.__pose_bone.tail.copy()
            return self.__anim_poselist[f][2].copy()
        '''
        # end

        def getAnimParRelMatrix(self, frame):
            #arm_mat = self.fbxArm.matrixWorld
            #arm_mat = self.fbxArm.parRelMatrix()
            if not self.parent:
                #return mtx4_z90 * (self.getPoseMatrix(frame) * arm_mat) # dont apply arm matrix anymore
                return self.getPoseMatrix(frame) * mtx4_z90
            else:
                #return (mtx4_z90 * ((self.getPoseMatrix(frame) * arm_mat)))  *  (mtx4_z90 * (self.parent.getPoseMatrix(frame) * arm_mat)).inverted()
                return (self.parent.getPoseMatrix(frame) * mtx4_z90).inverted() * ((self.getPoseMatrix(frame)) * mtx4_z90)

        # we need thes because cameras and lights modified rotations
        def getAnimParRelMatrixRot(self, frame):
            return self.getAnimParRelMatrix(frame)

        def flushAnimData(self):
            self.__anim_poselist.clear()

    class my_object_generic(object):
        __slots__ = ("fbxName",
                     "blenObject",
                     "blenData",
                     "origData",
                     "blenTextures",
                     "blenMaterials",
                     "blenMaterialList",
                     "blenAction",
                     "blenActionList",
                     "fbxGroupNames",
                     "fbxParent",
                     "fbxBoneParent",
                     "fbxBones",
                     "fbxArm",
                     "matrixWorld",
                     "__anim_poselist",
                     )

        # Other settings can be applied for each type - mesh, armature etc.
        def __init__(self, ob, matrixWorld=None):
            self.fbxName = sane_obname(ob)
            self.blenObject = ob
            self.fbxGroupNames = []
            self.fbxParent = None  # set later on IF the parent is in the selection.
            self.fbxArm = None
                self.matrixWorld = global_matrix * matrixWorld
                self.matrixWorld = global_matrix * ob.matrix_world

            self.__anim_poselist = {}  # we should only access this

        def parRelMatrix(self):
            if self.fbxParent:
                return self.fbxParent.matrixWorld.inverted() * self.matrixWorld
            else:
                return self.matrixWorld

        def setPoseFrame(self, f, fake=False):
            if fake:
                self.__anim_poselist[f] = self.matrixWorld * global_matrix.inverted()
                self.__anim_poselist[f] = self.blenObject.matrix_world.copy()

        def getAnimParRelMatrix(self, frame):
            if self.fbxParent:
                #return (self.__anim_poselist[frame] * self.fbxParent.__anim_poselist[frame].inverted() ) * global_matrix
                return (global_matrix * self.fbxParent.__anim_poselist[frame]).inverted() * (global_matrix * self.__anim_poselist[frame])
                return global_matrix * self.__anim_poselist[frame]

        def getAnimParRelMatrixRot(self, frame):
            obj_type = self.blenObject.type
            if self.fbxParent:
                matrix_rot = ((global_matrix * self.fbxParent.__anim_poselist[frame]).inverted() * (global_matrix * self.__anim_poselist[frame])).to_3x3()
                matrix_rot = (global_matrix * self.__anim_poselist[frame]).to_3x3()
                matrix_rot = matrix_rot * mtx_x90
                y = matrix_rot * Vector((0.0, 1.0, 0.0))
                matrix_rot = Matrix.Rotation(math.pi / 2.0, 3, y) * matrix_rot

            return matrix_rot

    # ----------------------------------------------

    print('\nFBX export starting... %r' % filepath)
    start_time = time.clock()
    try:
        file = open(filepath, "w", encoding="utf8", newline="\n")
        import traceback
        traceback.print_exc()
        operator.report({'ERROR'}, "Could'nt open file %r" % filepath)
        return {'CANCELLED'}
Campbell Barton's avatar
Campbell Barton committed
    # convenience
    fw = file.write

    # scene = context.scene  # now passed as an arg instead of context
    world = scene.world

    # ---------------------------- Write the header first
Campbell Barton's avatar
Campbell Barton committed
    fw(header_comment)
        curtime = time.localtime()[0:6]
    else:
Campbell Barton's avatar
Campbell Barton committed
    fw(
	FBXHeaderVersion: 1003
	FBXVersion: 6100
	CreationTimeStamp:  {
		Version: 1000
		Year: %.4i
		Month: %.2i
		Day: %.2i
		Hour: %.2i
		Minute: %.2i
		Second: %.2i
		Millisecond: 0
	}
	Creator: "FBX SDK/FBX Plugins build 20070228"
	OtherFlags:  {
		FlagPLE: 0
	}
Campbell Barton's avatar
Campbell Barton committed
    fw('\nCreationTime: "%.4i-%.2i-%.2i %.2i:%.2i:%.2i:000"' % curtime)
    fw('\nCreator: "Blender version %s"' % bpy.app.version_string)
    pose_items = []  # list of (fbxName, matrix) to write pose data for, easier to collect allong the way

    # --------------- funcs for exporting
    def object_tx(ob, loc, matrix, matrix_mod=None):
        '''
        Matrix mod is so armature objects can modify their bone matricies
        '''
        if isinstance(ob, bpy.types.Bone):

            # we know we have a matrix
            # matrix = mtx4_z90 * (ob.matrix['ARMATURESPACE'] * matrix_mod)
            matrix = ob.matrix_local * mtx4_z90  # dont apply armature matrix anymore

            parent = ob.parent
            if parent:
                #par_matrix = mtx4_z90 * (parent.matrix['ARMATURESPACE'] * matrix_mod)
                par_matrix = parent.matrix_local * mtx4_z90  # dont apply armature matrix anymore
                matrix = par_matrix.inverted() * matrix

            loc, rot, scale = matrix.decompose()
            matrix_rot = rot.to_matrix()
            rot = tuple(rot.to_euler())  # quat -> euler
                
            # Essential for XNA to use the original matrix not rotated nor scaled (JCB)
            if use_rotate_workaround:
                matrix = ob.matrix_local
            
        else:
            # This is bad because we need the parent relative matrix from the fbx parent (if we have one), dont use anymore
            #if ob and not matrix: matrix = ob.matrix_world * global_matrix
            if ob and not matrix:
                raise Exception("error: this should never happen!")

            if matrix:
                loc, rot, scale = matrix.decompose()
                matrix_rot = rot.to_matrix()

                # Lamps need to be rotated
                    matrix_rot = matrix_rot * mtx_x90
                elif ob and ob.type == 'CAMERA':
                    y = matrix_rot * Vector((0.0, 1.0, 0.0))
                    matrix_rot = Matrix.Rotation(math.pi / 2.0, 3, y) * matrix_rot
                # else do nothing.

                loc = tuple(loc)
                rot = tuple(matrix_rot.to_euler())
                scale = tuple(scale)
            else:
                if not loc:
                    loc = 0.0, 0.0, 0.0
                scale = 1.0, 1.0, 1.0
                rot = 0.0, 0.0, 0.0

        return loc, rot, scale, matrix, matrix_rot

    def write_object_tx(ob, loc, matrix, matrix_mod=None):
        '''
        We have loc to set the location if non blender objects that have a location

        matrix_mod is only used for bones at the moment
        '''
        loc, rot, scale, matrix, matrix_rot = object_tx(ob, loc, matrix, matrix_mod)

Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\t\tProperty: "Lcl Translation", "Lcl Translation", "A+",%.15f,%.15f,%.15f' % loc)
        fw('\n\t\t\tProperty: "Lcl Rotation", "Lcl Rotation", "A+",%.15f,%.15f,%.15f' % tuple_rad_to_deg(rot))
        fw('\n\t\t\tProperty: "Lcl Scaling", "Lcl Scaling", "A+",%.15f,%.15f,%.15f' % scale)
        return loc, rot, scale, matrix, matrix_rot

    def get_constraints(ob=None):
        # Set variables to their defaults.
        constraint_values = {"loc_min": (0.0, 0.0, 0.0),
                             "loc_max": (0.0, 0.0, 0.0),
                             "loc_limit": (0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
                             "rot_min": (0.0, 0.0, 0.0),
                             "rot_max": (0.0, 0.0, 0.0),
                             "rot_limit": (0.0, 0.0, 0.0),
                             "sca_min": (1.0, 1.0, 1.0),
                             "sca_max": (1.0, 1.0, 1.0),
                             "sca_limit": (0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
                            }

        # Iterate through the list of constraints for this object to get the information in a format which is compatible with the FBX format.
        if ob is not None:
            for constraint in ob.constraints:
                if constraint.type == 'LIMIT_LOCATION':
                    constraint_values["loc_min"] = constraint.min_x, constraint.min_y, constraint.min_z
                    constraint_values["loc_max"] = constraint.max_x, constraint.max_y, constraint.max_z
                    constraint_values["loc_limit"] = constraint.use_min_x, constraint.use_min_y, constraint.use_min_z, constraint.use_max_x, constraint.use_max_y, constraint.use_max_z
                elif constraint.type == 'LIMIT_ROTATION':
                    constraint_values["rot_min"] = math.degrees(constraint.min_x), math.degrees(constraint.min_y), math.degrees(constraint.min_z)
                    constraint_values["rot_max"] = math.degrees(constraint.max_x), math.degrees(constraint.max_y), math.degrees(constraint.max_z)
                    constraint_values["rot_limit"] = constraint.use_limit_x, constraint.use_limit_y, constraint.use_limit_z
                elif constraint.type == 'LIMIT_SCALE':
                    constraint_values["sca_min"] = constraint.min_x, constraint.min_y, constraint.min_z
                    constraint_values["sca_max"] = constraint.max_x, constraint.max_y, constraint.max_z
                    constraint_values["sca_limit"] = constraint.use_min_x, constraint.use_min_y, constraint.use_min_z, constraint.use_max_x, constraint.use_max_y, constraint.use_max_z

        # incase bad values are assigned.
        assert(len(constraint_values) == 9)

        return constraint_values

    def write_object_props(ob=None, loc=None, matrix=None, matrix_mod=None, pose_bone=None):
        # Check if a pose exists for this object and set the constraint soruce accordingly. (Poses only exsit if the object is a bone.)
        if pose_bone:
            constraints = get_constraints(pose_bone)
        else:
            constraints = get_constraints(ob)

        # if the type is 0 its an empty otherwise its a mesh
        # only difference at the moment is one has a color
Campbell Barton's avatar
Campbell Barton committed
        fw('''
		Properties60:  {
			Property: "QuaternionInterpolate", "bool", "",0
			Property: "Visibility", "Visibility", "A+",1''')

        loc, rot, scale, matrix, matrix_rot = write_object_tx(ob, loc, matrix, matrix_mod)

        # Rotation order, note, for FBX files Iv loaded normal order is 1
        # setting to zero.
        # eEULER_XYZ = 0
        # eEULER_XZY
        # eEULER_YZX
        # eEULER_YXZ
        # eEULER_ZXY
        # eEULER_ZYX
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\t\tProperty: "RotationOffset", "Vector3D", "",0,0,0'
           '\n\t\t\tProperty: "RotationPivot", "Vector3D", "",0,0,0'
           '\n\t\t\tProperty: "ScalingOffset", "Vector3D", "",0,0,0'
           '\n\t\t\tProperty: "ScalingPivot", "Vector3D", "",0,0,0'
           '\n\t\t\tProperty: "TranslationActive", "bool", "",0'
           )

        fw('\n\t\t\tProperty: "TranslationMin", "Vector3D", "",%.15g,%.15g,%.15g' % constraints["loc_min"])
        fw('\n\t\t\tProperty: "TranslationMax", "Vector3D", "",%.15g,%.15g,%.15g' % constraints["loc_max"])
        fw('\n\t\t\tProperty: "TranslationMinX", "bool", "",%d' % constraints["loc_limit"][0])
        fw('\n\t\t\tProperty: "TranslationMinY", "bool", "",%d' % constraints["loc_limit"][1])
        fw('\n\t\t\tProperty: "TranslationMinZ", "bool", "",%d' % constraints["loc_limit"][2])
        fw('\n\t\t\tProperty: "TranslationMaxX", "bool", "",%d' % constraints["loc_limit"][3])
        fw('\n\t\t\tProperty: "TranslationMaxY", "bool", "",%d' % constraints["loc_limit"][4])
        fw('\n\t\t\tProperty: "TranslationMaxZ", "bool", "",%d' % constraints["loc_limit"][5])

        fw('\n\t\t\tProperty: "RotationOrder", "enum", "",0'
           '\n\t\t\tProperty: "RotationSpaceForLimitOnly", "bool", "",0'
           '\n\t\t\tProperty: "AxisLen", "double", "",10'
           '\n\t\t\tProperty: "PreRotation", "Vector3D", "",0,0,0'
           '\n\t\t\tProperty: "PostRotation", "Vector3D", "",0,0,0'
           '\n\t\t\tProperty: "RotationActive", "bool", "",0'
           )

        fw('\n\t\t\tProperty: "RotationMin", "Vector3D", "",%.15g,%.15g,%.15g' % constraints["rot_min"])
        fw('\n\t\t\tProperty: "RotationMax", "Vector3D", "",%.15g,%.15g,%.15g' % constraints["rot_max"])
        fw('\n\t\t\tProperty: "RotationMinX", "bool", "",%d' % constraints["rot_limit"][0])
        fw('\n\t\t\tProperty: "RotationMinY", "bool", "",%d' % constraints["rot_limit"][1])
        fw('\n\t\t\tProperty: "RotationMinZ", "bool", "",%d' % constraints["rot_limit"][2])
        fw('\n\t\t\tProperty: "RotationMaxX", "bool", "",%d' % constraints["rot_limit"][0])
        fw('\n\t\t\tProperty: "RotationMaxY", "bool", "",%d' % constraints["rot_limit"][1])
        fw('\n\t\t\tProperty: "RotationMaxZ", "bool", "",%d' % constraints["rot_limit"][2])

        fw('\n\t\t\tProperty: "RotationStiffnessX", "double", "",0'
           '\n\t\t\tProperty: "RotationStiffnessY", "double", "",0'
           '\n\t\t\tProperty: "RotationStiffnessZ", "double", "",0'
           '\n\t\t\tProperty: "MinDampRangeX", "double", "",0'
           '\n\t\t\tProperty: "MinDampRangeY", "double", "",0'
           '\n\t\t\tProperty: "MinDampRangeZ", "double", "",0'
           '\n\t\t\tProperty: "MaxDampRangeX", "double", "",0'
           '\n\t\t\tProperty: "MaxDampRangeY", "double", "",0'
           '\n\t\t\tProperty: "MaxDampRangeZ", "double", "",0'
           '\n\t\t\tProperty: "MinDampStrengthX", "double", "",0'
           '\n\t\t\tProperty: "MinDampStrengthY", "double", "",0'
           '\n\t\t\tProperty: "MinDampStrengthZ", "double", "",0'
           '\n\t\t\tProperty: "MaxDampStrengthX", "double", "",0'
           '\n\t\t\tProperty: "MaxDampStrengthY", "double", "",0'
           '\n\t\t\tProperty: "MaxDampStrengthZ", "double", "",0'
           '\n\t\t\tProperty: "PreferedAngleX", "double", "",0'
           '\n\t\t\tProperty: "PreferedAngleY", "double", "",0'
           '\n\t\t\tProperty: "PreferedAngleZ", "double", "",0'
           '\n\t\t\tProperty: "InheritType", "enum", "",0'
           '\n\t\t\tProperty: "ScalingActive", "bool", "",0'
           )

        fw('\n\t\t\tProperty: "ScalingMin", "Vector3D", "",%.15g,%.15g,%.15g' % constraints["sca_min"])
        fw('\n\t\t\tProperty: "ScalingMax", "Vector3D", "",%.15g,%.15g,%.15g' % constraints["sca_max"])
        fw('\n\t\t\tProperty: "ScalingMinX", "bool", "",%d' % constraints["sca_limit"][0])
        fw('\n\t\t\tProperty: "ScalingMinY", "bool", "",%d' % constraints["sca_limit"][1])
        fw('\n\t\t\tProperty: "ScalingMinZ", "bool", "",%d' % constraints["sca_limit"][2])
        fw('\n\t\t\tProperty: "ScalingMaxX", "bool", "",%d' % constraints["sca_limit"][3])
        fw('\n\t\t\tProperty: "ScalingMaxY", "bool", "",%d' % constraints["sca_limit"][4])
        fw('\n\t\t\tProperty: "ScalingMaxZ", "bool", "",%d' % constraints["sca_limit"][5])

        fw('\n\t\t\tProperty: "GeometricTranslation", "Vector3D", "",0,0,0'
           '\n\t\t\tProperty: "GeometricRotation", "Vector3D", "",0,0,0'
           '\n\t\t\tProperty: "GeometricScaling", "Vector3D", "",1,1,1'
           '\n\t\t\tProperty: "LookAtProperty", "object", ""'
           '\n\t\t\tProperty: "UpVectorProperty", "object", ""'
           '\n\t\t\tProperty: "Show", "bool", "",1'
           '\n\t\t\tProperty: "NegativePercentShapeSupport", "bool", "",1'
           '\n\t\t\tProperty: "DefaultAttributeIndex", "int", "",0'
           )
        if ob and not isinstance(ob, bpy.types.Bone):
            # Only mesh objects have color
Campbell Barton's avatar
Campbell Barton committed
            fw('\n\t\t\tProperty: "Color", "Color", "A",0.8,0.8,0.8'
               '\n\t\t\tProperty: "Size", "double", "",100'
               '\n\t\t\tProperty: "Look", "enum", "",1'
               )

        return loc, rot, scale, matrix, matrix_rot

    # -------------------------------------------- Armatures
    #def write_bone(bone, name, matrix_mod):
    def write_bone(my_bone):
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\tModel: "Model::%s", "Limb" {' % my_bone.fbxName)
        fw('\n\t\tVersion: 232')

        #poseMatrix = write_object_props(my_bone.blenBone, None, None, my_bone.fbxArm.parRelMatrix())[3]
        poseMatrix = write_object_props(my_bone.blenBone, pose_bone=my_bone.getPoseBone())[3]  # dont apply bone matricies anymore
        pose_items.append((my_bone.fbxName, poseMatrix))
Campbell Barton's avatar
Campbell Barton committed
        # fw('\n\t\t\tProperty: "Size", "double", "",%.6f' % ((my_bone.blenData.head['ARMATURESPACE'] - my_bone.blenData.tail['ARMATURESPACE']) * my_bone.fbxArm.parRelMatrix()).length)
        fw('\n\t\t\tProperty: "Size", "double", "",1')

        #((my_bone.blenData.head['ARMATURESPACE'] * my_bone.fbxArm.matrixWorld) - (my_bone.blenData.tail['ARMATURESPACE'] * my_bone.fbxArm.parRelMatrix())).length)

        """
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\t\tProperty: "LimbLength", "double", "",%.6f' %\
            ((my_bone.blenBone.head['ARMATURESPACE'] - my_bone.blenBone.tail['ARMATURESPACE']) * my_bone.fbxArm.parRelMatrix()).length)
        """

Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\t\tProperty: "LimbLength", "double", "",%.6f' %
           (my_bone.blenBone.head_local - my_bone.blenBone.tail_local).length)

        #fw('\n\t\t\tProperty: "LimbLength", "double", "",1')
        fw('\n\t\t\tProperty: "Color", "ColorRGB", "",0.8,0.8,0.8'
           '\n\t\t\tProperty: "Color", "Color", "A",0.8,0.8,0.8'
           '\n\t\t}'
           '\n\t\tMultiLayer: 0'
           '\n\t\tMultiTake: 1'
           '\n\t\tShading: Y'
           '\n\t\tCulling: "CullingOff"'
           '\n\t\tTypeFlags: "Skeleton"'
           '\n\t}'
           )
Campbell Barton's avatar
Campbell Barton committed
        fw('''
	Model: "Model::Camera Switcher", "CameraSwitcher" {
		Version: 232''')
Campbell Barton's avatar
Campbell Barton committed
        fw('''
			Property: "Color", "Color", "A",0.8,0.8,0.8
			Property: "Camera Index", "Integer", "A+",100
		}
		MultiLayer: 0
		MultiTake: 1
		Hidden: "True"
		Shading: W
		Culling: "CullingOff"
		Version: 101
		Name: "Model::Camera Switcher"
		CameraId: 0
		CameraName: 100
		CameraIndexName:
	}''')

    def write_camera_dummy(name, loc, near, far, proj_type, up):
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\tModel: "Model::%s", "Camera" {' % name)
        fw('\n\t\tVersion: 232')
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\t\tProperty: "Color", "Color", "A",0.8,0.8,0.8'
           '\n\t\t\tProperty: "Roll", "Roll", "A+",0'
           '\n\t\t\tProperty: "FieldOfView", "FieldOfView", "A+",40'
           '\n\t\t\tProperty: "FieldOfViewX", "FieldOfView", "A+",1'
           '\n\t\t\tProperty: "FieldOfViewY", "FieldOfView", "A+",1'
           '\n\t\t\tProperty: "OpticalCenterX", "Real", "A+",0'
           '\n\t\t\tProperty: "OpticalCenterY", "Real", "A+",0'
           '\n\t\t\tProperty: "BackgroundColor", "Color", "A+",0.63,0.63,0.63'
           '\n\t\t\tProperty: "TurnTable", "Real", "A+",0'
           '\n\t\t\tProperty: "DisplayTurnTableIcon", "bool", "",1'
           '\n\t\t\tProperty: "Motion Blur Intensity", "Real", "A+",1'
           '\n\t\t\tProperty: "UseMotionBlur", "bool", "",0'
           '\n\t\t\tProperty: "UseRealTimeMotionBlur", "bool", "",1'
           '\n\t\t\tProperty: "ResolutionMode", "enum", "",0'
           '\n\t\t\tProperty: "ApertureMode", "enum", "",2'
           '\n\t\t\tProperty: "GateFit", "enum", "",0'
           '\n\t\t\tProperty: "FocalLength", "Real", "A+",21.3544940948486'
           '\n\t\t\tProperty: "CameraFormat", "enum", "",0'
           '\n\t\t\tProperty: "AspectW", "double", "",320'
           '\n\t\t\tProperty: "AspectH", "double", "",200'
           '\n\t\t\tProperty: "PixelAspectRatio", "double", "",1'
           '\n\t\t\tProperty: "UseFrameColor", "bool", "",0'
           '\n\t\t\tProperty: "FrameColor", "ColorRGB", "",0.3,0.3,0.3'
           '\n\t\t\tProperty: "ShowName", "bool", "",1'
           '\n\t\t\tProperty: "ShowGrid", "bool", "",1'
           '\n\t\t\tProperty: "ShowOpticalCenter", "bool", "",0'
           '\n\t\t\tProperty: "ShowAzimut", "bool", "",1'
           '\n\t\t\tProperty: "ShowTimeCode", "bool", "",0'
           )

        fw('\n\t\t\tProperty: "NearPlane", "double", "",%.6f' % near)
        fw('\n\t\t\tProperty: "FarPlane", "double", "",%.6f' % far)

        fw('\n\t\t\tProperty: "FilmWidth", "double", "",0.816'
           '\n\t\t\tProperty: "FilmHeight", "double", "",0.612'
           '\n\t\t\tProperty: "FilmAspectRatio", "double", "",1.33333333333333'
           '\n\t\t\tProperty: "FilmSqueezeRatio", "double", "",1'
           '\n\t\t\tProperty: "FilmFormatIndex", "enum", "",4'
           '\n\t\t\tProperty: "ViewFrustum", "bool", "",1'
           '\n\t\t\tProperty: "ViewFrustumNearFarPlane", "bool", "",0'
           '\n\t\t\tProperty: "ViewFrustumBackPlaneMode", "enum", "",2'
           '\n\t\t\tProperty: "BackPlaneDistance", "double", "",100'
           '\n\t\t\tProperty: "BackPlaneDistanceMode", "enum", "",0'
           '\n\t\t\tProperty: "ViewCameraToLookAt", "bool", "",1'
           '\n\t\t\tProperty: "LockMode", "bool", "",0'
           '\n\t\t\tProperty: "LockInterestNavigation", "bool", "",0'
           '\n\t\t\tProperty: "FitImage", "bool", "",0'
           '\n\t\t\tProperty: "Crop", "bool", "",0'
           '\n\t\t\tProperty: "Center", "bool", "",1'
           '\n\t\t\tProperty: "KeepRatio", "bool", "",1'
           '\n\t\t\tProperty: "BackgroundMode", "enum", "",0'
           '\n\t\t\tProperty: "BackgroundAlphaTreshold", "double", "",0.5'
           '\n\t\t\tProperty: "ForegroundTransparent", "bool", "",1'
           '\n\t\t\tProperty: "DisplaySafeArea", "bool", "",0'
           '\n\t\t\tProperty: "SafeAreaDisplayStyle", "enum", "",1'
           '\n\t\t\tProperty: "SafeAreaAspectRatio", "double", "",1.33333333333333'
           '\n\t\t\tProperty: "Use2DMagnifierZoom", "bool", "",0'
           '\n\t\t\tProperty: "2D Magnifier Zoom", "Real", "A+",100'
           '\n\t\t\tProperty: "2D Magnifier X", "Real", "A+",50'
           '\n\t\t\tProperty: "2D Magnifier Y", "Real", "A+",50'
           )

        fw('\n\t\t\tProperty: "CameraProjectionType", "enum", "",%i' % proj_type)

        fw('\n\t\t\tProperty: "UseRealTimeDOFAndAA", "bool", "",0'
           '\n\t\t\tProperty: "UseDepthOfField", "bool", "",0'
           '\n\t\t\tProperty: "FocusSource", "enum", "",0'
           '\n\t\t\tProperty: "FocusAngle", "double", "",3.5'
           '\n\t\t\tProperty: "FocusDistance", "double", "",200'
           '\n\t\t\tProperty: "UseAntialiasing", "bool", "",0'
           '\n\t\t\tProperty: "AntialiasingIntensity", "double", "",0.77777'
           '\n\t\t\tProperty: "UseAccumulationBuffer", "bool", "",0'
           '\n\t\t\tProperty: "FrameSamplingCount", "int", "",7'
           '\n\t\t}'
           '\n\t\tMultiLayer: 0'
           '\n\t\tMultiTake: 0'
           '\n\t\tHidden: "True"'
           '\n\t\tShading: Y'
           '\n\t\tCulling: "CullingOff"'
           '\n\t\tTypeFlags: "Camera"'
           '\n\t\tGeometryVersion: 124'
           )

        fw('\n\t\tPosition: %.6f,%.6f,%.6f' % loc)
        fw('\n\t\tUp: %i,%i,%i' % up)

        fw('\n\t\tLookAt: 0,0,0'
           '\n\t\tShowInfoOnMoving: 1'
           '\n\t\tShowAudio: 0'
           '\n\t\tAudioColor: 0,1,0'
           '\n\t\tCameraOrthoZoom: 1'
           '\n\t}'
           )

    def write_camera_default():
        # This sucks but to match FBX converter its easier to
        # write the cameras though they are not needed.
        write_camera_dummy('Producer Perspective', (0, 71.3, 287.5), 10, 4000, 0, (0, 1, 0))
        write_camera_dummy('Producer Top', (0, 4000, 0), 1, 30000, 1, (0, 0, -1))
        write_camera_dummy('Producer Bottom', (0, -4000, 0), 1, 30000, 1, (0, 0, -1))
        write_camera_dummy('Producer Front', (0, 0, 4000), 1, 30000, 1, (0, 1, 0))
        write_camera_dummy('Producer Back', (0, 0, -4000), 1, 30000, 1, (0, 1, 0))
        write_camera_dummy('Producer Right', (4000, 0, 0), 1, 30000, 1, (0, 1, 0))
        write_camera_dummy('Producer Left', (-4000, 0, 0), 1, 30000, 1, (0, 1, 0))

    def write_camera(my_cam):
        '''
        Write a blender camera
        '''
        render = scene.render
        width = render.resolution_x
        height = render.resolution_y
        aspect = width / height
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\tModel: "Model::%s", "Camera" {' % my_cam.fbxName)
        fw('\n\t\tVersion: 232')
        loc, rot, scale, matrix, matrix_rot = write_object_props(my_cam.blenObject, None, my_cam.parRelMatrix())

Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\t\tProperty: "Roll", "Roll", "A+",0')
        fw('\n\t\t\tProperty: "FieldOfView", "FieldOfView", "A+",%.6f' % math.degrees(data.angle))
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\t\tProperty: "FieldOfViewX", "FieldOfView", "A+",1'
           '\n\t\t\tProperty: "FieldOfViewY", "FieldOfView", "A+",1'
           )
Campbell Barton's avatar
Campbell Barton committed
        # fw('\n\t\t\tProperty: "FocalLength", "Real", "A+",14.0323972702026')
        fw('\n\t\t\tProperty: "OpticalCenterX", "Real", "A+",%.6f' % data.shift_x)  # not sure if this is in the correct units?
        fw('\n\t\t\tProperty: "OpticalCenterY", "Real", "A+",%.6f' % data.shift_y)  # ditto
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\t\tProperty: "BackgroundColor", "Color", "A+",0,0,0'
           '\n\t\t\tProperty: "TurnTable", "Real", "A+",0'
           '\n\t\t\tProperty: "DisplayTurnTableIcon", "bool", "",1'
           '\n\t\t\tProperty: "Motion Blur Intensity", "Real", "A+",1'
           '\n\t\t\tProperty: "UseMotionBlur", "bool", "",0'
           '\n\t\t\tProperty: "UseRealTimeMotionBlur", "bool", "",1'
           '\n\t\t\tProperty: "ResolutionMode", "enum", "",0'
           '\n\t\t\tProperty: "ApertureMode", "enum", "",2'
           '\n\t\t\tProperty: "GateFit", "enum", "",2'
           '\n\t\t\tProperty: "CameraFormat", "enum", "",0'
           )
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\t\tProperty: "AspectW", "double", "",%i' % width)
        fw('\n\t\t\tProperty: "AspectH", "double", "",%i' % height)

        '''Camera aspect ratio modes.
            0 If the ratio mode is eWINDOW_SIZE, both width and height values aren't relevant.
            1 If the ratio mode is eFIXED_RATIO, the height value is set to 1.0 and the width value is relative to the height value.
            2 If the ratio mode is eFIXED_RESOLUTION, both width and height values are in pixels.
            3 If the ratio mode is eFIXED_WIDTH, the width value is in pixels and the height value is relative to the width value.
            4 If the ratio mode is eFIXED_HEIGHT, the height value is in pixels and the width value is relative to the height value.

        Definition at line 234 of file kfbxcamera.h. '''

Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\t\tProperty: "PixelAspectRatio", "double", "",2'
           '\n\t\t\tProperty: "UseFrameColor", "bool", "",0'
           '\n\t\t\tProperty: "FrameColor", "ColorRGB", "",0.3,0.3,0.3'
           '\n\t\t\tProperty: "ShowName", "bool", "",1'
           '\n\t\t\tProperty: "ShowGrid", "bool", "",1'
           '\n\t\t\tProperty: "ShowOpticalCenter", "bool", "",0'
           '\n\t\t\tProperty: "ShowAzimut", "bool", "",1'
           '\n\t\t\tProperty: "ShowTimeCode", "bool", "",0'
           )

        fw('\n\t\t\tProperty: "NearPlane", "double", "",%.6f' % data.clip_start)
        fw('\n\t\t\tProperty: "FarPlane", "double", "",%.6f' % data.clip_end)

        fw('\n\t\t\tProperty: "FilmWidth", "double", "",1.0'
           '\n\t\t\tProperty: "FilmHeight", "double", "",1.0'
           )

        fw('\n\t\t\tProperty: "FilmAspectRatio", "double", "",%.6f' % aspect)

        fw('\n\t\t\tProperty: "FilmSqueezeRatio", "double", "",1'
           '\n\t\t\tProperty: "FilmFormatIndex", "enum", "",0'
           '\n\t\t\tProperty: "ViewFrustum", "bool", "",1'
           '\n\t\t\tProperty: "ViewFrustumNearFarPlane", "bool", "",0'
           '\n\t\t\tProperty: "ViewFrustumBackPlaneMode", "enum", "",2'
           '\n\t\t\tProperty: "BackPlaneDistance", "double", "",100'
           '\n\t\t\tProperty: "BackPlaneDistanceMode", "enum", "",0'
           '\n\t\t\tProperty: "ViewCameraToLookAt", "bool", "",1'
           '\n\t\t\tProperty: "LockMode", "bool", "",0'
           '\n\t\t\tProperty: "LockInterestNavigation", "bool", "",0'
           '\n\t\t\tProperty: "FitImage", "bool", "",0'
           '\n\t\t\tProperty: "Crop", "bool", "",0'
           '\n\t\t\tProperty: "Center", "bool", "",1'
           '\n\t\t\tProperty: "KeepRatio", "bool", "",1'
           '\n\t\t\tProperty: "BackgroundMode", "enum", "",0'
           '\n\t\t\tProperty: "BackgroundAlphaTreshold", "double", "",0.5'
           '\n\t\t\tProperty: "ForegroundTransparent", "bool", "",1'
           '\n\t\t\tProperty: "DisplaySafeArea", "bool", "",0'
           '\n\t\t\tProperty: "SafeAreaDisplayStyle", "enum", "",1'
           )

        fw('\n\t\t\tProperty: "SafeAreaAspectRatio", "double", "",%.6f' % aspect)

        fw('\n\t\t\tProperty: "Use2DMagnifierZoom", "bool", "",0'
           '\n\t\t\tProperty: "2D Magnifier Zoom", "Real", "A+",100'
           '\n\t\t\tProperty: "2D Magnifier X", "Real", "A+",50'
           '\n\t\t\tProperty: "2D Magnifier Y", "Real", "A+",50'
           '\n\t\t\tProperty: "CameraProjectionType", "enum", "",0'
           '\n\t\t\tProperty: "UseRealTimeDOFAndAA", "bool", "",0'
           '\n\t\t\tProperty: "UseDepthOfField", "bool", "",0'
           '\n\t\t\tProperty: "FocusSource", "enum", "",0'
           '\n\t\t\tProperty: "FocusAngle", "double", "",3.5'
           '\n\t\t\tProperty: "FocusDistance", "double", "",200'
           '\n\t\t\tProperty: "UseAntialiasing", "bool", "",0'
           '\n\t\t\tProperty: "AntialiasingIntensity", "double", "",0.77777'
           '\n\t\t\tProperty: "UseAccumulationBuffer", "bool", "",0'
           '\n\t\t\tProperty: "FrameSamplingCount", "int", "",7'
           )

        fw('\n\t\t}')

        fw('\n\t\tMultiLayer: 0'
           '\n\t\tMultiTake: 0'
           '\n\t\tShading: Y'
           '\n\t\tCulling: "CullingOff"'
           '\n\t\tTypeFlags: "Camera"'
           '\n\t\tGeometryVersion: 124'
           )

        fw('\n\t\tPosition: %.6f,%.6f,%.6f' % loc)
        fw('\n\t\tUp: %.6f,%.6f,%.6f' % (matrix_rot * Vector((0.0, 1.0, 0.0)))[:])
        fw('\n\t\tLookAt: %.6f,%.6f,%.6f' % (matrix_rot * Vector((0.0, 0.0, -1.0)))[:])

        #fw('\n\t\tUp: 0,0,0' )
        #fw('\n\t\tLookAt: 0,0,0' )

        fw('\n\t\tShowInfoOnMoving: 1')
        fw('\n\t\tShowAudio: 0')
        fw('\n\t\tAudioColor: 0,1,0')
        fw('\n\t\tCameraOrthoZoom: 1')
        fw('\n\t}')

    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: "Intensity", "Intensity", "A+",%.2f' % (min(light.energy * 100.0, 200.0)))  # clamp below 200
Campbell Barton's avatar
Campbell Barton committed
            fw('\n\t\t\tProperty: "Cone angle", "Cone angle", "A+",%.2f' % math.degrees(light.spot_size))
        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' % (min(light.energy * 100.0, 200.0)))  # clamp below 200

        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", ""')
        fw('\n\t\t\tProperty: "DecayType", "enum", "",0')
        fw('\n\t\t\tProperty: "DecayStart", "double", "",%.2f' % light.distance)

        fw('\n\t\t\tProperty: "EnableNearAttenuation", "bool", "",0'
           '\n\t\t\tProperty: "NearAttenuationStart", "double", "",0'
           '\n\t\t\tProperty: "NearAttenuationEnd", "double", "",0'
           '\n\t\t\tProperty: "EnableFarAttenuation", "bool", "",0'
           '\n\t\t\tProperty: "FarAttenuationStart", "double", "",0'
           '\n\t\t\tProperty: "FarAttenuationEnd", "double", "",0'
           )

        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 = world_amb

            mat_dif = mat.diffuse_intensity
            mat_amb = mat.ambient
            mat_hard = (float(mat.specular_hardness) - 1.0) / 5.10
            mat_spec = mat.specular_intensity / 2.0
            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_cols = mat_cold = 0.8, 0.8, 0.8
            # mat_colm
            mat_dif = 1.0
            mat_amb = 0.5
            mat_hard = 20.0
            mat_spec = 0.2
            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", "",80.0')
            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)
            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)
            fname_strip = bpy.path.basename(bpy.path.abspath(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
	}''')

    def write_deformer_skin(obname):
        '''
        Each mesh has its own deformer
        '''
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 spesific 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])

        if my_mesh.fbxParent:
            # TODO FIXME, this case is broken in some cases. skinned meshes just shouldnt have parents where possible!
            m = (my_mesh.matrixWorld.inverted() * my_bone.fbxArm.matrixWorld.copy() * my_bone.restMatrix) * mtx4_z90
        else:
            # Yes! this is it...  - but dosnt work when the mesh is a.
            m = (my_mesh.matrixWorld.inverted() * my_bone.fbxArm.matrixWorld.copy() * my_bone.restMatrix) * mtx4_z90

        #m = mtx4_z90 * my_bone.restMatrix
        matstr = mat4x4str(m)
        matstr_i = mat4x4str(m.inverted())
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\tTransform: %s' % matstr_i)  # THIS IS __NOT__ THE GLOBAL MATRIX AS DOCUMENTED :/
        fw('\n\t\tTransformLink: %s' % matstr)
        fw('\n\t}')

    def write_mesh(my_mesh):

        me = my_mesh.blenData

        # if there are non NULL materials on this mesh
        do_materials = bool(my_mesh.blenMaterials)
        do_textures = bool(my_mesh.blenTextures)
        do_uvs = bool(me.uv_textures)

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.
        me_vertices = me.vertices[:]
        me_edges = me.edges[:] if use_mesh_edges else ()
        me_faces = me.faces[:]

        poseMatrix = write_object_props(my_mesh.blenObject, None, my_mesh.parRelMatrix())[3]
        pose_items.append((my_mesh.fbxName, poseMatrix))

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: ')
Campbell Barton's avatar
Campbell Barton committed
                fw('%.6f,%.6f,%.6f' % v.co[:])
Campbell Barton's avatar
Campbell Barton committed
                    fw('\n\t\t')
Campbell Barton's avatar
Campbell Barton committed
                fw(',%.6f,%.6f,%.6f' % v.co[:])
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\tPolygonVertexIndex: ')
        for f in me_faces:
            fi = f.vertices[:]

            # last index XORd w. -1 indicates end of face
            if i == -1:
                if len(fi) == 3:
Campbell Barton's avatar
Campbell Barton committed
                    fw('%i,%i,%i' % (fi[0], fi[1], fi[2] ^ -1))
Campbell Barton's avatar
Campbell Barton committed
                    fw('%i,%i,%i,%i' % (fi[0], fi[1], fi[2], fi[3] ^ -1))
Campbell Barton's avatar
Campbell Barton committed
                    fw('\n\t\t')
Campbell Barton's avatar
Campbell Barton committed
                    fw(',%i,%i,%i' % (fi[0], fi[1], fi[2] ^ -1))
Campbell Barton's avatar
Campbell Barton committed
                    fw(',%i,%i,%i,%i' % (fi[0], fi[1], fi[2], fi[3] ^ -1))
            i += 1

        # write loose edges as faces.
        for ed in me_edges:
            if ed.is_loose:
                ed_val = ed.vertices[:]
                ed_val = ed_val[0], ed_val[-1] ^ -1

Campbell Barton's avatar
Campbell Barton committed
                    fw('%i,%i' % ed_val)
Campbell Barton's avatar
Campbell Barton committed
                        fw('\n\t\t')
Campbell Barton's avatar
Campbell Barton committed
                    fw(',%i,%i' % ed_val)
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\tEdges: ')
Campbell Barton's avatar
Campbell Barton committed
                    fw('%i,%i' % (ed.vertices[0], ed.vertices[1]))
Campbell Barton's avatar
Campbell Barton committed
                        fw('\n\t\t')
Campbell Barton's avatar
Campbell Barton committed
                    fw(',%i,%i' % (ed.vertices[0], ed.vertices[1]))
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\tGeometryVersion: 124')
Campbell Barton's avatar
Campbell Barton committed
        fw('''
		LayerElementNormal: 0 {
			Version: 101
			Name: ""
			MappingInformationType: "ByVertice"
			ReferenceInformationType: "Direct"
			Normals: ''')
Campbell Barton's avatar
Campbell Barton committed
                fw('%.15f,%.15f,%.15f' % v.normal[:])
Campbell Barton's avatar
Campbell Barton committed
                    fw('\n\t\t\t ')
Campbell Barton's avatar
Campbell Barton committed
                fw(',%.15f,%.15f,%.15f' % v.normal[:])
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\t}')
Campbell Barton's avatar
Campbell Barton committed
            fw('''
		LayerElementSmoothing: 0 {
			Version: 102
			Name: ""
			MappingInformationType: "ByPolygon"
			ReferenceInformationType: "Direct"
			Smoothing: ''')
Campbell Barton's avatar
Campbell Barton committed
                    fw('%i' % f.use_smooth)
Campbell Barton's avatar
Campbell Barton committed
                        fw('\n\t\t\t ')
Campbell Barton's avatar
Campbell Barton committed
                    fw(',%i' % f.use_smooth)
Campbell Barton's avatar
Campbell Barton committed
            fw('\n\t\t}')
Campbell Barton's avatar
Campbell Barton committed
            fw('''
		LayerElementSmoothing: 0 {
			Version: 101
			Name: ""
			MappingInformationType: "ByEdge"
			ReferenceInformationType: "Direct"
			Smoothing: ''')
Campbell Barton's avatar
Campbell Barton committed
                    fw('%i' % (ed.use_edge_sharp))
Campbell Barton's avatar
Campbell Barton committed
                        fw('\n\t\t\t ')
Campbell Barton's avatar
Campbell Barton committed
                    fw(',%i' % ed.use_edge_sharp)
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 = []
        if len(me.vertex_colors):
            collayers = me.vertex_colors
            for colindex, collayer in enumerate(collayers):
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\tLayerElementColor: %i {' % colindex)
                fw('\n\t\t\tVersion: 101')
                fw('\n\t\t\tName: "%s"' % collayer.name)
Campbell Barton's avatar
Campbell Barton committed
                fw('''
			MappingInformationType: "ByPolygonVertex"
			ReferenceInformationType: "IndexToDirect"
			Colors: ''')
                ii = 0  # Count how many Colors we write

                for fi, cf in enumerate(collayer.data):
                    if len(me_faces[fi].vertices) == 4:
                        colors = cf.color1[:], cf.color2[:], cf.color3[:], cf.color4[:]
                    else:
                        colors = cf.color1[:], cf.color2[:], cf.color3[:]

                    for col in colors:
Campbell Barton's avatar
Campbell Barton committed
                            fw('%.4f,%.4f,%.4f,1' % col)
Campbell Barton's avatar
Campbell Barton committed
                                fw('\n\t\t\t\t')
Campbell Barton's avatar
Campbell Barton committed
                            fw(',%.4f,%.4f,%.4f,1' % col)
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\t\tColorIndex: ')
Campbell Barton's avatar
Campbell Barton committed
                        fw('%i' % j)
Campbell Barton's avatar
Campbell Barton committed
                            fw('\n\t\t\t\t')
Campbell Barton's avatar
Campbell Barton committed
                        fw(',%i' % j)
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\t}')

        # Write UV and texture layers.
        uvlayers = []
        if do_uvs:
            uvlayers = me.uv_textures
            for uvindex, uvlayer in enumerate(me.uv_textures):
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\tLayerElementUV: %i {' % uvindex)
                fw('\n\t\t\tVersion: 101')
                fw('\n\t\t\tName: "%s"' % uvlayer.name)
Campbell Barton's avatar
Campbell Barton committed
                fw('''
			MappingInformationType: "ByPolygonVertex"
			ReferenceInformationType: "IndexToDirect"
			UV: ''')
                ii = 0  # Count how many UVs we write

                for uf in uvlayer.data:
                    # workaround, since uf.uv iteration is wrong atm
                    for uv in uf.uv:
Campbell Barton's avatar
Campbell Barton committed
                            fw('%.6f,%.6f' % uv[:])
Campbell Barton's avatar
Campbell Barton committed
                                fw('\n\t\t\t ')
Campbell Barton's avatar
Campbell Barton committed
                            fw(',%.6f,%.6f' % uv[:])
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\t\tUVIndex: ')
Campbell Barton's avatar
Campbell Barton committed
                        fw('%i' % j)
Campbell Barton's avatar
Campbell Barton committed
                            fw('\n\t\t\t\t')
Campbell Barton's avatar
Campbell Barton committed
                        fw(',%i' % j)
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\t}')
Campbell Barton's avatar
Campbell Barton committed
                    fw('\n\t\tLayerElementTexture: %i {' % uvindex)
                    fw('\n\t\t\tVersion: 101')
                    fw('\n\t\t\tName: "%s"' % uvlayer.name)
Campbell Barton's avatar
Campbell Barton committed
                        fw('\n\t\t\tMappingInformationType: "AllSame"')
Campbell Barton's avatar
Campbell Barton committed
                        fw('\n\t\t\tMappingInformationType: "ByPolygon"')
Campbell Barton's avatar
Campbell Barton committed
                    fw('\n\t\t\tReferenceInformationType: "IndexToDirect"')
                    fw('\n\t\t\tBlendMode: "Translucent"')
                    fw('\n\t\t\tTextureAlpha: 1')
                    fw('\n\t\t\tTextureId: ')
Campbell Barton's avatar
Campbell Barton committed
                        fw('0')
                        texture_mapping_local = {None: -1}
                        for tex in my_mesh.blenTextures:
Campbell Barton's avatar
Campbell Barton committed
                                fw('%s' % texture_mapping_local[img_key])
Campbell Barton's avatar
Campbell Barton committed
                                    fw('\n			 ')
Campbell Barton's avatar
Campbell Barton committed
                                fw(',%s' % texture_mapping_local[img_key])
Campbell Barton's avatar
Campbell Barton committed
                    fw('''
		LayerElementTexture: 0 {
			Version: 101
			Name: ""
			MappingInformationType: "NoMappingInformation"
			ReferenceInformationType: "IndexToDirect"
			BlendMode: "Translucent"
			TextureAlpha: 1
			TextureId: ''')
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\t}')
Campbell Barton's avatar
Campbell Barton committed
            fw('\n\t\tLayerElementMaterial: 0 {')
            fw('\n\t\t\tVersion: 101')
            fw('\n\t\t\tName: ""')
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\t\tMappingInformationType: "AllSame"')
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\t\tMappingInformationType: "ByPolygon"')
Campbell Barton's avatar
Campbell Barton committed
            fw('\n\t\t\tReferenceInformationType: "IndexToDirect"')
            fw('\n\t\t\tMaterials: ')
Campbell Barton's avatar
Campbell Barton committed
                fw('0')
            else:
                # Build a material mapping for this
                material_mapping_local = {}  # local-mat & tex : global index.

                for j, mat_tex_pair in enumerate(my_mesh.blenMaterials):
                    material_mapping_local[mat_tex_pair] = j

                mats = my_mesh.blenMaterialList

                if me.uv_textures.active:
                    uv_faces = me.uv_textures.active.data
                else:
                    uv_faces = [None] * len(me_faces)

                for f, uf in zip(me_faces, uv_faces):
                    try:
                        mat = mats[f.material_index]
                    except:
                        mat = None
                    if do_uvs:
                        tex = uf.image  # WARNING - MULTI UV LAYER IMAGES NOT SUPPORTED :/
                    else:
                        tex = None
Campbell Barton's avatar
Campbell Barton committed
                        fw('%s' % material_mapping_local[mat, tex])  # None for mat or tex is ok
Campbell Barton's avatar
Campbell Barton committed
                            fw('\n\t\t\t\t')
Campbell Barton's avatar
Campbell Barton committed
                        fw(',%s' % material_mapping_local[mat, tex])
Campbell Barton's avatar
Campbell Barton committed
            fw('\n\t\t}')
Campbell Barton's avatar
Campbell Barton committed
        fw('''
		Layer: 0 {
			Version: 100
			LayerElement:  {
				Type: "LayerElementNormal"
				TypedIndex: 0
			}''')
Campbell Barton's avatar
Campbell Barton committed
            fw('''
			LayerElement:  {
				Type: "LayerElementMaterial"
				TypedIndex: 0
			}''')
Campbell Barton's avatar
Campbell Barton committed
            fw('''
			LayerElement:  {
				Type: "LayerElementSmoothing"
				TypedIndex: 0
			}''')
Campbell Barton's avatar
Campbell Barton committed
            fw('''
			LayerElement:  {
				Type: "LayerElementTexture"
				TypedIndex: 0
			}''')
Campbell Barton's avatar
Campbell Barton committed
            fw('''
			LayerElement:  {
				Type: "LayerElementColor"
				TypedIndex: 0
			}''')
Campbell Barton's avatar
Campbell Barton committed
            fw('''
			LayerElement:  {
				Type: "LayerElementUV"
				TypedIndex: 0
			}''')
Campbell Barton's avatar
Campbell Barton committed
        fw('\n\t\t}')

        if len(uvlayers) > 1:
            for i in range(1, len(uvlayers)):

Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\tLayer: %i {' % i)
                fw('\n\t\t\tVersion: 100')
Campbell Barton's avatar
Campbell Barton committed
                fw('''
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\t\t\tTypedIndex: %i' % i)
                fw('\n\t\t\t}')
Campbell Barton's avatar
Campbell Barton committed
                    fw('''
			LayerElement:  {
				Type: "LayerElementTexture"''')
Campbell Barton's avatar
Campbell Barton committed
                    fw('\n\t\t\t\tTypedIndex: %i' % i)
                    fw('\n\t\t\t}')
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\t}')

        if len(collayers) > 1:
            # Take into account any UV layers
            layer_offset = 0
            if uvlayers:
                layer_offset = len(uvlayers) - 1
            for i in range(layer_offset, len(collayers) + layer_offset):
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\tLayer: %i {' % i)
                fw('\n\t\t\tVersion: 100')
Campbell Barton's avatar
Campbell Barton committed
                fw('''
			LayerElement:  {
				Type: "LayerElementColor"''')
Campbell Barton's avatar
Campbell Barton committed
                fw('\n\t\t\t\tTypedIndex: %i' % i)
                fw('\n\t\t\t}')
                fw('\n\t\t}')
        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 = {}  # (mat, image) keys, should be a set()
    textures = {}  # should be a set()
Campbell Barton's avatar
Campbell Barton committed
    tmp_ob_type = None  # incase 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_mapping_local = {}
                    material_mapping_local = {}
                    if me.uv_textures:
                        for uvlayer in me.uv_textures:
                            for f, uf in zip(me.faces, uvlayer.data):
                                tex = uf.image
                                textures[tex] = texture_mapping_local[tex] = None

                                try:
                                    mat = mats[f.material_index]
                                except:
                                    mat = None
                                materials[mat, tex] = material_mapping_local[mat, tex] = None  # should use sets, wait for blender 2.5

                    else:
                        for mat in mats:
                            # 2.44 use mat.lib too for uniqueness
                            materials[mat, None] = material_mapping_local[mat, None] = None
                        else:
                            materials[None, None] = None

                        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_mapping_local.keys())
                    my_mesh.blenTextures = list(texture_mapping_local.keys())
                    # 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, [])

        for bone in my_arm.blenData.bones:
            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)
        if my_mesh.fbxArm:
            for my_arm in ob_arms:
                if my_arm.blenObject == my_mesh.fbxArm:
                    my_mesh.fbxArm = my_arm
                    break

        for my_bone in ob_bones:

            # The mesh uses this bones armature!
            if my_bone.fbxArm == my_mesh.fbxArm:
                if my_bone.blenBone.use_deform:
                    my_bone.blenMeshes[my_mesh.fbxName] = me

                # parent bone: replace bone names with our class instances
                # my_mesh.fbxBoneParent is None or a blender bone name initialy, replacing if the names match.
                if my_mesh.fbxBoneParent == my_bone.blenName:
                    my_mesh.fbxBoneParent = my_bone

    bone_deformer_count = 0  # count how many bones deform a mesh
    my_bone_blenParent = None
    for my_bone in ob_bones:
        my_bone_blenParent = my_bone.blenBone.parent
        if my_bone_blenParent:
            for my_bone_parent in ob_bones:
                # Note 2.45rc2 you can compare bones normally
                if my_bone_blenParent.name == my_bone_parent.blenName and my_bone.fbxArm == my_bone_parent.fbxArm:
                    my_bone.parent = my_bone_parent
                    break

        # Not used at the moment
        # my_bone.calcRestMatrixLocal()
        bone_deformer_count += len(my_bone.blenMeshes)

    del my_bone_blenParent

    # Build blenObject -> fbxObject mapping
    # this is needed for groups as well as fbxParenting

    # using a list of object names for tagging (Arystan)

    tmp_obmapping = {}
    for ob_generic in ob_all_typegroups:
        for ob_base in ob_generic:
            ob_base.blenObject.tag = True
            tmp_obmapping[ob_base.blenObject] = ob_base

    # Build Groups from objects we export
    for blenGroup in bpy.data.groups:
        fbxGroupName = None
        for ob in blenGroup.objects:
            if ob.tag:
                if fbxGroupName is None:
                    fbxGroupName = sane_groupname(blenGroup)
                    groups.append((fbxGroupName, blenGroup))

                tmp_obmapping[ob].fbxGroupNames.append(fbxGroupName)  # also adds to the objects fbxGroupNames
    groups.sort()  # not really needed

    # Assign parents using this mapping
    for ob_generic in ob_all_typegroups:
        for my_ob in ob_generic:
            parent = my_ob.blenObject.parent
            if parent and parent.tag:  # does it exist and is it in the mapping
                my_ob.fbxParent = tmp_obmapping[parent]

    del tmp_obmapping
    # Finished finding groups we use
    
    # == WRITE OBJECTS TO THE FILE ==
    # == From now on we are building the FBX file from the information collected above (JCB)
    materials = [(sane_matname(mat_tex_pair), mat_tex_pair) for mat_tex_pair in materials.keys()]
    textures = [(sane_texname(tex), tex) for tex in textures.keys()  if tex]
    materials.sort(key=lambda m: m[0])  # sort by name
    textures.sort(key=lambda m: m[0])
    camera_count = 8 if 'CAMERA' in object_types else 0

    # sanity checks
    try:
        assert(not (ob_meshes and ('MESH' not in object_types)))
        assert(not (materials and ('MESH' not in object_types)))
        assert(not (textures and ('MESH' not in object_types)))
        assert(not (textures and ('MESH' not in object_types)))

        assert(not (ob_lights and ('LAMP' not in object_types)))

        assert(not (ob_cameras and ('CAMERA' not in object_types)))
    except AssertionError:
        import traceback
        traceback.print_exc()

Campbell Barton's avatar
Campbell Barton committed
    fw('''

; Object definitions
;------------------------------------------------------------------

Definitions:  {
	Version: 100
	Count: %i''' % (
        1 + camera_count +
        len(ob_meshes) +
        len(ob_lights) +
        len(ob_cameras) +
        len(ob_arms) +
        len(ob_null) +
        len(ob_bones) +
        bone_deformer_count +
        len(materials) +
        (len(textures) * 2)))  # add 1 for global settings
Campbell Barton's avatar
Campbell Barton committed
    fw('''
        camera_count +
        len(ob_meshes) +
        len(ob_lights) +
        len(ob_cameras) +
        len(ob_arms) +
        len(ob_null) +
        len(ob_bones)))
Campbell Barton's avatar
Campbell Barton committed
    fw('''
	ObjectType: "Geometry" {
		Count: %i
	}''' % len(ob_meshes))
Campbell Barton's avatar
Campbell Barton committed
        fw('''
	ObjectType: "Material" {
		Count: %i
	}''' % len(materials))
Campbell Barton's avatar
Campbell Barton committed
        fw('''
	ObjectType: "Texture" {
		Count: %i
	}''' % len(textures))  # add 1 for an empty tex
Campbell Barton's avatar
Campbell Barton committed
        fw('''
	ObjectType: "Video" {
		Count: %i
	}''' % len(textures))  # add 1 for an empty tex

    tmp = 0
    # Add deformer nodes
    for my_mesh in ob_meshes:
        if my_mesh.fbxArm:

    # Add subdeformers
    for my_bone in ob_bones:
        tmp += len(my_bone.blenMeshes)

    if tmp:
Campbell Barton's avatar
Campbell Barton committed
        fw('''
    # Bind pose is essential for XNA if the 'MESH' is included (JCB)
Campbell Barton's avatar
Campbell Barton committed
    fw('''
Campbell Barton's avatar
Campbell Barton committed
        fw('''
	ObjectType: "GroupSelection" {
		Count: %i
	}''' % len(groups))
Campbell Barton's avatar
Campbell Barton committed
    fw('''
Campbell Barton's avatar
Campbell Barton committed
    fw('''

; Object properties
;------------------------------------------------------------------
Loading
Loading full blame...