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
import math # math.pi
import bpy
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 not data: return 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!
else:
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:
name = increment_string(name)
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 name
def sane_obname(data):
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)
def mat4x4str(mat):
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:
Campbell Barton
committed
# possible weights are out of range
index = g.group
if index < len_groupNames:
vWeightList[i][index] = g.weight
return groupNames, vWeightList
def meshNormalizedWeights(ob, me):
Campbell Barton
committed
groupNames, vWeightList = BPyMesh_meshWeight2List(ob, me)
if not groupNames:
return [], []
for i, vWeights in enumerate(vWeightList):
tot = 0.0
for w in vWeights:
tot += w
if tot:
for j, w in enumerate(vWeights):
vWeights[j] = w / tot
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="",
object_types={'EMPTY', 'CAMERA', 'LAMP', 'ARMATURE', 'MESH'},
Campbell Barton
committed
mesh_apply_modifiers=True,
mesh_smooth_type='FACE',
ANIM_ENABLE=True,
ANIM_OPTIMIZE=True,
ANIM_OPTIMIZE_PRECISSION=6,
ANIM_ACTION_ALL=False,
Campbell Barton
committed
use_metadata=True,
):
Campbell Barton
committed
import bpy_extras.io_utils
mtx_x90 = Matrix.Rotation(math.pi / 2.0, 3, 'X')
mtx4_z90 = Matrix.Rotation(math.pi / 2.0, 4, 'Z')
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)
Campbell Barton
committed
base_dst = os.path.dirname(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
# not used yet
# self.restMatrixInv = self.restMatrix.inverted()
# self.restMatrixLocal = None # set later, need parent matrix
self.parent = None
# not public
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()
Campbell Barton
committed
def getPoseBone(self):
return self.__pose_bone
# get pose from frame.
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.
if matrixWorld:
self.matrixWorld = global_matrix * matrixWorld
else:
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:
Campbell Barton
committed
self.__anim_poselist[f] = self.matrixWorld * global_matrix.inverted()
else:
self.__anim_poselist[f] = self.blenObject.matrix_world.copy()
Loading
Loading full blame...