Newer
Older
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
Campbell Barton
committed
if 'ARMATURE' in object_types:
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.blenMaterialList = mats
my_mesh.blenTextures = list(texture_mapping_local.keys())
Campbell Barton
committed
# sort the name so we get predictable output, some items may be NULL
Campbell Barton
committed
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_meshes.append(my_mesh)
# not forgetting to free dupli_list
if ob_base.dupli_list:
Campbell Barton
committed
if 'ARMATURE' in object_types:
# 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':
# 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
if ob.animation_data:
my_arm.blenAction = ob.animation_data.action
else:
my_arm.blenAction = None
my_arm.blenActionList = []
# fbxName, blenderObject, my_bones, blenderActions
#ob_arms[i] = fbxArmObName, ob, arm_my_bones, (ob.action, [])
if use_armature_deform_only:
# tag non deforming bones that have no deforming children
deform_map = dict.fromkeys(my_arm.blenData.bones, False)
for bone in my_arm.blenData.bones:
if bone.use_deform:
deform_map[bone] = True
# tag all parents, even ones that are not deform since their child _is_
for parent in bone.parent_recursive:
deform_map[parent] = True
for bone in my_arm.blenData.bones:
if use_armature_deform_only:
# if this bone doesnt deform, and none of its children deform, skip it!
if not deform_map[bone]:
continue
my_bone = my_bone_class(bone, my_arm)
my_arm.fbxBones.append(my_bone)
ob_bones.append(my_bone)
if use_armature_deform_only:
del deform_map
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
# 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
bpy.data.objects.tag(False)
# 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]
Campbell Barton
committed
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()
; Object definitions
;------------------------------------------------------------------
Definitions: {
Campbell Barton
committed
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
del bone_deformer_count
Campbell Barton
committed
ObjectType: "Model" {
Count: %i
Campbell Barton
committed
camera_count +
len(ob_meshes) +
len(ob_lights) +
len(ob_cameras) +
len(ob_arms) +
len(ob_null) +
Campbell Barton
committed
ObjectType: "Geometry" {
Count: %i
}''' % len(ob_meshes))
if materials:
Campbell Barton
committed
ObjectType: "Material" {
Count: %i
}''' % len(materials))
if textures:
Campbell Barton
committed
ObjectType: "Texture" {
Count: %i
}''' % len(textures)) # add 1 for an empty tex
Campbell Barton
committed
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:
tmp += 1
# Add subdeformers
for my_bone in ob_bones:
tmp += len(my_bone.blenMeshes)
if tmp:
Campbell Barton
committed
ObjectType: "Deformer" {
Count: %i
}''' % tmp)
del tmp
# Bind pose is essential for XNA if the 'MESH' is included,
# but could be removed now?
Campbell Barton
committed
ObjectType: "Pose" {
Count: 1
}''')
if groups:
Campbell Barton
committed
ObjectType: "GroupSelection" {
Count: %i
}''' % len(groups))
Campbell Barton
committed
ObjectType: "GlobalSettings" {
Count: 1
}
}''')
; Object properties
;------------------------------------------------------------------
Objects: {''')
if 'CAMERA' in object_types:
# To comply with other FBX FILES
write_camera_switch()
for my_null in ob_null:
write_null(my_null)
# XNA requires the armature to be a Limb (JCB)
# Note, 2.58 and previous wrote these as normal empties and it worked mostly (except for XNA)
for my_arm in ob_arms:
write_null(my_arm, fbxType="Limb", fbxTypeFlags="Skeleton")
for my_cam in ob_cameras:
write_camera(my_cam)
for my_light in ob_lights:
write_light(my_light)
for my_mesh in ob_meshes:
write_mesh(my_mesh)
#for bonename, bone, obname, me, armob in ob_bones:
for my_bone in ob_bones:
write_bone(my_bone)
if 'CAMERA' in object_types:
write_camera_default()
for matname, (mat, tex) in materials:
write_material(matname, mat) # We only need to have a material per image pair, but no need to write any image info into the material (dumb fbx standard)
# each texture uses a video, odd
for texname, tex in textures:
write_video(texname, tex)
i = 0
for texname, tex in textures:
write_texture(texname, tex, i)
i += 1
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
for groupname, group in groups:
write_group(groupname)
# NOTE - c4d and motionbuilder dont need normalized weights, but deep-exploration 5 does and (max?) do.
# Write armature modifiers
# TODO - add another MODEL? - because of this skin definition.
for my_mesh in ob_meshes:
if my_mesh.fbxArm:
write_deformer_skin(my_mesh.fbxName)
# Get normalized weights for temorary use
if my_mesh.fbxBoneParent:
weights = None
else:
weights = meshNormalizedWeights(my_mesh.blenObject, my_mesh.blenData)
#for bonename, bone, obname, bone_mesh, armob in ob_bones:
for my_bone in ob_bones:
if me in iter(my_bone.blenMeshes.values()):
write_sub_deformer_skin(my_mesh, my_bone, weights)
# Write pose is really weird, only needed when an armature and mesh are used together
# each by themselves do not need pose data. For now only pose meshes and bones
# Bind pose is essential for XNA if the 'MESH' is included (JCB)
Campbell Barton
committed
Pose: "Pose::BIND_POSES", "BindPose" {
Type: "BindPose"
Version: 100
Properties60: {
}
NbPoseNodes: ''')
for fbxName, matrix in pose_items:
fw('\n\t\tPoseNode: {')
fw('\n\t\t\tNode: "Model::%s"' % fbxName)
fw('\n\t\t\tMatrix: %s' % mat4x4str(matrix if matrix else Matrix()))
fw('\n\t\t}')
# Finish Writing Objects
# Write global settings
Campbell Barton
committed
GlobalSettings: {
Version: 1000
Properties60: {
Property: "UpAxis", "int", "",1
Property: "UpAxisSign", "int", "",1
Property: "FrontAxis", "int", "",2
Property: "FrontAxisSign", "int", "",1
Property: "CoordAxis", "int", "",0
Property: "CoordAxisSign", "int", "",1
Property: "UnitScaleFactor", "double", "",1
}
}
''')
; Object relations
;------------------------------------------------------------------
Relations: {''')
# Nulls are likely to cause problems for XNA
for my_null in ob_null:
fw('\n\tModel: "Model::%s", "Null" {\n\t}' % my_null.fbxName)
# Armature must be a Limb for XNA
# Note, 2.58 and previous wrote these as normal empties and it worked mostly (except for XNA)
for my_arm in ob_arms:
fw('\n\tModel: "Model::%s", "Limb" {\n\t}' % my_arm.fbxName)
for my_mesh in ob_meshes:
fw('\n\tModel: "Model::%s", "Mesh" {\n\t}' % my_mesh.fbxName)
# TODO - limbs can have the same name for multiple armatures, should prefix.
#for bonename, bone, obname, me, armob in ob_bones:
for my_bone in ob_bones:
fw('\n\tModel: "Model::%s", "Limb" {\n\t}' % my_bone.fbxName)
for my_cam in ob_cameras:
fw('\n\tModel: "Model::%s", "Camera" {\n\t}' % my_cam.fbxName)
for my_light in ob_lights:
fw('\n\tModel: "Model::%s", "Light" {\n\t}' % my_light.fbxName)
Campbell Barton
committed
Model: "Model::Producer Perspective", "Camera" {
}
Model: "Model::Producer Top", "Camera" {
}
Model: "Model::Producer Bottom", "Camera" {
}
Model: "Model::Producer Front", "Camera" {
}
Model: "Model::Producer Back", "Camera" {
}
Model: "Model::Producer Right", "Camera" {
}
Model: "Model::Producer Left", "Camera" {
}
Model: "Model::Camera Switcher", "CameraSwitcher" {
}''')
for matname, (mat, tex) in materials:
fw('\n\tMaterial: "Material::%s", "" {\n\t}' % matname)
if textures:
for texname, tex in textures:
fw('\n\tTexture: "Texture::%s", "TextureVideoClip" {\n\t}' % texname)
for texname, tex in textures:
fw('\n\tVideo: "Video::%s", "Clip" {\n\t}' % texname)
# deformers - modifiers
for my_mesh in ob_meshes:
if my_mesh.fbxArm:
fw('\n\tDeformer: "Deformer::Skin %s", "Skin" {\n\t}' % my_mesh.fbxName)
#for bonename, bone, obname, me, armob in ob_bones:
for my_bone in ob_bones:
for fbxMeshObName in my_bone.blenMeshes: # .keys() - fbxMeshObName
# is this bone effecting a mesh?
fw('\n\tDeformer: "SubDeformer::Cluster %s %s", "Cluster" {\n\t}' % (fbxMeshObName, my_bone.fbxName))
# This should be at the end
# fw('\n\tPose: "Pose::BIND_POSES", "BindPose" {\n\t}')
for groupname, group in groups:
fw('\n\tGroupSelection: "GroupSelection::%s", "Default" {\n\t}' % groupname)
; Object connections
;------------------------------------------------------------------
Connections: {''')
# NOTE - The FBX SDK does not care about the order but some importers DO!
# for instance, defining the material->mesh connection
# before the mesh->parent crashes cinema4d
for ob_generic in ob_all_typegroups: # all blender 'Object's we support
for my_ob in ob_generic:
# for deformed meshes, don't have any parents or they can get twice transformed.
if my_ob.fbxParent and (not my_ob.fbxArm):
fw('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_ob.fbxName, my_ob.fbxParent.fbxName))
else:
fw('\n\tConnect: "OO", "Model::%s", "Model::Scene"' % my_ob.fbxName)
if materials:
for my_mesh in ob_meshes:
# Connect all materials to all objects, not good form but ok for now.
for mat, tex in my_mesh.blenMaterials:
mat_name = mat.name if mat else None
tex_name = tex.name if tex else None
fw('\n\tConnect: "OO", "Material::%s", "Model::%s"' % (sane_name_mapping_mat[mat_name, tex_name], my_mesh.fbxName))
if textures:
for my_mesh in ob_meshes:
if my_mesh.blenTextures:
# fw('\n\tConnect: "OO", "Texture::_empty_", "Model::%s"' % my_mesh.fbxName)
for tex in my_mesh.blenTextures:
if tex:
fw('\n\tConnect: "OO", "Texture::%s", "Model::%s"' % (sane_name_mapping_tex[tex.name], my_mesh.fbxName))
for texname, tex in textures:
fw('\n\tConnect: "OO", "Video::%s", "Texture::%s"' % (texname, texname))
if 'MESH' in object_types:
for my_mesh in ob_meshes:
if my_mesh.fbxArm:
fw('\n\tConnect: "OO", "Deformer::Skin %s", "Model::%s"' % (my_mesh.fbxName, my_mesh.fbxName))
for my_bone in ob_bones:
for fbxMeshObName in my_bone.blenMeshes: # .keys()
fw('\n\tConnect: "OO", "SubDeformer::Cluster %s %s", "Deformer::Skin %s"' % (fbxMeshObName, my_bone.fbxName, fbxMeshObName))
# limbs -> deformers
for my_bone in ob_bones:
for fbxMeshObName in my_bone.blenMeshes: # .keys()
fw('\n\tConnect: "OO", "Model::%s", "SubDeformer::Cluster %s %s"' % (my_bone.fbxName, fbxMeshObName, my_bone.fbxName))
#for bonename, bone, obname, me, armob in ob_bones:
for my_bone in ob_bones:
# Always parent to armature now
if my_bone.parent:
fw('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_bone.fbxName, my_bone.parent.fbxName))
else:
# the armature object is written as an empty and all root level bones connect to it
fw('\n\tConnect: "OO", "Model::%s", "Model::%s"' % (my_bone.fbxName, my_bone.fbxArm.fbxName))
# groups
if groups:
for ob_generic in ob_all_typegroups:
for ob_base in ob_generic:
for fbxGroupName in ob_base.fbxGroupNames:
fw('\n\tConnect: "OO", "Model::%s", "GroupSelection::%s"' % (ob_base.fbxName, fbxGroupName))
# I think the following always duplicates the armature connection because it is also in ob_all_typegroups above! (JCB)
# for my_arm in ob_arms:
# fw('\n\tConnect: "OO", "Model::%s", "Model::Scene"' % my_arm.fbxName)
# Needed for scene footer as well as animation
render = scene.render
# from the FBX sdk
#define KTIME_ONE_SECOND KTime (K_LONGLONG(46186158000))
def fbx_time(t):
# 0.5 + val is the same as rounding.
return int(0.5 + ((t / fps) * 46186158000))
fps = float(render.fps)
start = scene.frame_start
end = scene.frame_end
if end < start:
# comment the following line, otherwise we dont get the pose
# if start==end: use_anim = False
# animations for these object types
ob_anim_lists = ob_bones, ob_meshes, ob_null, ob_cameras, ob_lights, ob_arms
if use_anim and [tmp for tmp in ob_anim_lists if tmp]:
frame_orig = scene.frame_current
if use_anim_optimize:
ANIM_OPTIMIZE_PRECISSION_FLOAT = 0.1 ** anim_optimize_precision
# default action, when no actions are avaioable
tmp_actions = []
blenActionDefault = None
action_lastcompat = None
# instead of tagging
tagged_actions = []
# get the current action first so we can use it if we only export one action (JCB)
for my_arm in ob_arms:
blenActionDefault = my_arm.blenAction
if blenActionDefault:
break
if use_anim_action_all:
tmp_actions = bpy.data.actions[:]
elif not use_default_take:
if blenActionDefault:
# Export the current action (JCB)
tmp_actions.append(blenActionDefault)
if tmp_actions:
# find which actions are compatible with the armatures
tmp_act_count = 0
for my_arm in ob_arms:
arm_bone_names = set([my_bone.blenName for my_bone in my_arm.fbxBones])
for action in tmp_actions:
if arm_bone_names.intersection(action_bone_names(my_arm.blenObject, action)): # at least one channel matches.
my_arm.blenActionList.append(action)
tagged_actions.append(action.name)
tmp_act_count += 1
# in case there are no actions applied to armatures
# for example, when a user deletes the current action.
action_lastcompat = action
if tmp_act_count:
# unlikely to ever happen but if no actions applied to armatures, just use the last compatible armature.
if not blenActionDefault:
blenActionDefault = action_lastcompat
del action_lastcompat
if use_default_take:
tmp_actions.insert(0, None) # None is the default action
;Takes and animation section
;----------------------------------------------------
Takes: {''')
if blenActionDefault and not use_default_take:
fw('\n\tCurrent: "%s"' % sane_takename(blenActionDefault))
else:
for blenAction in tmp_actions:
# we have tagged all actious that are used be selected armatures
if blenAction:
if blenAction.name in tagged_actions:
print('\taction: "%s" exporting...' % blenAction.name)
else:
print('\taction: "%s" has no armature using it, skipping' % blenAction.name)
continue
if blenAction is None:
# Warning, this only accounts for tmp_actions being [None]
take_name = "Default Take"
act_start = start
act_end = end
else:
# use existing name
take_name = sane_name_mapping_take.get(blenAction.name)
if take_name is None:
take_name = sane_takename(blenAction)
act_start, act_end = blenAction.frame_range
act_start = int(act_start)
act_end = int(act_end)
# Set the action active
for my_arm in ob_arms:
if my_arm.blenObject.animation_data and blenAction in my_arm.blenActionList:
my_arm.blenObject.animation_data.action = blenAction
# Use the action name as the take name and the take filename (JCB)
fw('\n\tTake: "%s" {' % take_name)
fw('\n\t\tFileName: "%s.tak"' % take_name.replace(" ", "_"))
fw('\n\t\tLocalTime: %i,%i' % (fbx_time(act_start - 1), fbx_time(act_end - 1))) # ??? - not sure why this is needed
fw('\n\t\tReferenceTime: %i,%i' % (fbx_time(act_start - 1), fbx_time(act_end - 1))) # ??? - not sure why this is needed
Campbell Barton
committed
;Models animation
;----------------------------------------------------''')
# set pose data for all bones
# do this here in case the action changes
'''
for my_bone in ob_bones:
my_bone.flushAnimData()
'''
i = act_start
while i <= act_end:
scene.frame_set(i)
for ob_generic in ob_anim_lists:
for my_ob in ob_generic:
#Blender.Window.RedrawAll()
if ob_generic == ob_meshes and my_ob.fbxArm:
# We cant animate armature meshes!
my_ob.setPoseFrame(i, fake=True)
else:
my_ob.setPoseFrame(i)
i += 1
#for bonename, bone, obname, me, armob in ob_bones:
for ob_generic in (ob_bones, ob_meshes, ob_null, ob_cameras, ob_lights, ob_arms):
for my_ob in ob_generic:
if ob_generic == ob_meshes and my_ob.fbxArm:
# do nothing,
pass
else:
fw('\n\t\tModel: "Model::%s" {' % my_ob.fbxName) # ??? - not sure why this is needed
fw('\n\t\t\tVersion: 1.1')
fw('\n\t\t\tChannel: "Transform" {')
context_bone_anim_mats = [(my_ob.getAnimParRelMatrix(frame), my_ob.getAnimParRelMatrixRot(frame)) for frame in range(act_start, act_end + 1)]
# ----------------
# ----------------
for TX_LAYER, TX_CHAN in enumerate('TRS'): # transform, rotate, scale
if TX_CHAN == 'T':
context_bone_anim_vecs = [mtx[0].to_translation() for mtx in context_bone_anim_mats]
elif TX_CHAN == 'S':
context_bone_anim_vecs = [mtx[0].to_scale() for mtx in context_bone_anim_mats]
elif TX_CHAN == 'R':
# Was....
# elif TX_CHAN=='R': context_bone_anim_vecs = [mtx[1].to_euler() for mtx in context_bone_anim_mats]
#
# ...but we need to use the previous euler for compatible conversion.
context_bone_anim_vecs = []
prev_eul = None
for mtx in context_bone_anim_mats:
if prev_eul:
prev_eul = mtx[1].to_euler('XYZ', prev_eul)
else:
prev_eul = mtx[1].to_euler()
context_bone_anim_vecs.append(tuple_rad_to_deg(prev_eul))
fw('\n\t\t\t\tChannel: "%s" {' % TX_CHAN) # translation
for i in range(3):
# Loop on each axis of the bone
fw('\n\t\t\t\t\tChannel: "%s" {' % ('XYZ'[i])) # translation
fw('\n\t\t\t\t\t\tDefault: %.15f' % context_bone_anim_vecs[0][i])
fw('\n\t\t\t\t\t\tKeyVer: 4005')
if not use_anim_optimize:
# Just write all frames, simple but in-eficient
fw('\n\t\t\t\t\t\tKeyCount: %i' % (1 + act_end - act_start))
fw('\n\t\t\t\t\t\tKey: ')
frame = act_start
while frame <= act_end:
if frame != act_start:
# Curve types are 'C,n' for constant, 'L' for linear
# C,n is for bezier? - linear is best for now so we can do simple keyframe removal
fw('\n\t\t\t\t\t\t\t%i,%.15f,L' % (fbx_time(frame - 1), context_bone_anim_vecs[frame - act_start][i]))
frame += 1
else:
# remove unneeded keys, j is the frame, needed when some frames are removed.
context_bone_anim_keys = [(vec[i], j) for j, vec in enumerate(context_bone_anim_vecs)]
# last frame to fisrt frame, missing 1 frame on either side.
# removeing in a backwards loop is faster
#for j in xrange( (act_end-act_start)-1, 0, -1 ):
# j = (act_end-act_start)-1
j = len(context_bone_anim_keys) - 2
while j > 0 and len(context_bone_anim_keys) > 2:
# print j, len(context_bone_anim_keys)
# Is this key the same as the ones next to it?
# co-linear horizontal...
if abs(context_bone_anim_keys[j][0] - context_bone_anim_keys[j - 1][0]) < ANIM_OPTIMIZE_PRECISSION_FLOAT and \
abs(context_bone_anim_keys[j][0] - context_bone_anim_keys[j + 1][0]) < ANIM_OPTIMIZE_PRECISSION_FLOAT:
del context_bone_anim_keys[j]
else:
frame_range = float(context_bone_anim_keys[j + 1][1] - context_bone_anim_keys[j - 1][1])
frame_range_fac1 = (context_bone_anim_keys[j + 1][1] - context_bone_anim_keys[j][1]) / frame_range
frame_range_fac2 = 1.0 - frame_range_fac1
if abs(((context_bone_anim_keys[j - 1][0] * frame_range_fac1 + context_bone_anim_keys[j + 1][0] * frame_range_fac2)) - context_bone_anim_keys[j][0]) < ANIM_OPTIMIZE_PRECISSION_FLOAT:
del context_bone_anim_keys[j]
else:
j -= 1
# keep the index below the list length
if j > len(context_bone_anim_keys) - 2:
j = len(context_bone_anim_keys) - 2
if len(context_bone_anim_keys) == 2 and context_bone_anim_keys[0][0] == context_bone_anim_keys[1][0]:
# This axis has no moton, its okay to skip KeyCount and Keys in this case
# pass
# better write one, otherwise we loose poses with no animation
fw('\n\t\t\t\t\t\tKeyCount: 1')
fw('\n\t\t\t\t\t\tKey: ')
fw('\n\t\t\t\t\t\t\t%i,%.15f,L' % (fbx_time(start), context_bone_anim_keys[0][0]))
else:
# We only need to write these if there is at least one
fw('\n\t\t\t\t\t\tKeyCount: %i' % len(context_bone_anim_keys))
fw('\n\t\t\t\t\t\tKey: ')
for val, frame in context_bone_anim_keys:
if frame != context_bone_anim_keys[0][1]: # not the first
# frame is already one less then blenders frame
fw('\n\t\t\t\t\t\t\t%i,%.15f,L' % (fbx_time(frame), val))
if i == 0:
elif i == 1:
elif i == 2:
fw('\n\t\t\t\t\t}')
fw('\n\t\t\t\t\tLayerType: %i' % (TX_LAYER + 1))
fw('\n\t\t\t\t}')
# ---------------
# end the take
# end action loop. set original actions
# do this after every loop in case actions effect eachother.
for my_arm in ob_arms:
if my_arm.blenObject.animation_data:
my_arm.blenObject.animation_data.action = my_arm.blenAction
scene.frame_set(frame_orig)
else:
# no animation
fw('\n;Takes and animation section')
fw('\n;----------------------------------------------------')
fw('\n')
fw('\nTakes: {')
fw('\n\tCurrent: ""')
fw('\n}')
# write meshes animation
#for obname, ob, mtx, me, mats, arm, armname in ob_meshes:
# Clear mesh data Only when writing with modifiers applied
for me in meshes_to_clear:
bpy.data.meshes.remove(me)
# --------------------------- Footer
if world:
m = world.mist_settings
has_mist = m.use_mist
mist_intense = m.intensity
mist_start = m.start
mist_end = m.depth
world_hor = world.horizon_color
else:
world_hor = 0, 0, 0
fw('\n;Version 5 settings')
fw('\n;------------------------------------------------------------------')
fw('\n')
fw('\nVersion5: {')
fw('\n\tAmbientRenderSettings: {')
fw('\n\t\tVersion: 101')
fw('\n\t\tAmbientLightColor: %.1f,%.1f,%.1f,0' % tuple(world_amb))
fw('\n\t}')
fw('\n\tFogOptions: {')
fw('\n\t\tFogEnable: %i' % has_mist)
fw('\n\t\tFogMode: 0')
fw('\n\t\tFogDensity: %.3f' % mist_intense)
fw('\n\t\tFogStart: %.3f' % mist_start)
fw('\n\t\tFogEnd: %.3f' % mist_end)
fw('\n\t\tFogColor: %.1f,%.1f,%.1f,1' % tuple(world_hor))
fw('\n\t}')
fw('\n\tSettings: {')
fw('\n\t\tFrameRate: "%i"' % int(fps))
fw('\n\t\tTimeFormat: 1')
fw('\n\t\tSnapOnFrames: 0')
fw('\n\t\tReferenceTimeIndex: -1')
fw('\n\t\tTimeLineStartTime: %i' % fbx_time(start - 1))
fw('\n\t\tTimeLineStopTime: %i' % fbx_time(end - 1))
fw('\n\t}')
fw('\n\tRendererSetting: {')
fw('\n\t\tDefaultCamera: "Producer Perspective"')
fw('\n\t\tDefaultViewingMode: 0')
fw('\n\t}')
fw('\n}')
fw('\n')
# XXX, shouldnt be global!
for mapping in (sane_name_mapping_ob,
sane_name_mapping_ob_unique,
sane_name_mapping_mat,
sane_name_mapping_tex,
sane_name_mapping_take,
sane_name_mapping_group,
):
mapping.clear()
del mapping
del ob_arms[:]
del ob_bones[:]
del ob_cameras[:]
del ob_lights[:]
del ob_meshes[:]
del ob_null[:]
file.close()
# copy all collected files.
Campbell Barton
committed
bpy_extras.io_utils.path_reference_copy(copy_set)
print('export finished in %.4f sec.' % (time.clock() - start_time))
return {'FINISHED'}
# defaults for applications, currently only unity but could add others.
def defaults_unity3d():
return dict(global_matrix=Matrix.Rotation(-math.pi / 2.0, 4, 'X'),
use_selection=False,
object_types={'ARMATURE', 'EMPTY', 'MESH'},
use_mesh_modifiers=True,
use_armature_deform_only=True,
use_anim=True,
use_anim_optimize=False,
use_anim_action_all=True,
batch_mode='OFF',
use_default_take=True,
)
filepath="",
use_selection=False,
batch_mode='OFF',
use_batch_own_dir=False,
**kwargs
):
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
if batch_mode == 'OFF':
kwargs_mod = kwargs.copy()
if use_selection:
kwargs_mod["context_objects"] = context.selected_objects
else:
kwargs_mod["context_objects"] = context.scene.objects
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
return save_single(operator, context.scene, filepath, **kwargs_mod)
else:
fbxpath = filepath
prefix = os.path.basename(fbxpath)
if prefix:
fbxpath = os.path.dirname(fbxpath)
if not fbxpath.endswith(os.sep):
fbxpath += os.sep
if batch_mode == 'GROUP':
data_seq = bpy.data.groups
else:
data_seq = bpy.data.scenes
# call this function within a loop with BATCH_ENABLE == False
# no scene switching done at the moment.
# orig_sce = context.scene
new_fbxpath = fbxpath # own dir option modifies, we need to keep an original
for data in data_seq: # scene or group
newname = prefix + bpy.path.clean_name(data.name)
if use_batch_own_dir:
new_fbxpath = fbxpath + newname + os.sep
# path may already exist
# TODO - might exist but be a file. unlikely but should probably account for it.
if not os.path.exists(new_fbxpath):
os.makedirs(new_fbxpath)