Newer
Older
# BPyMesh.meshCalcNormals(me) # high quality normals nice for realtime engines.
texture_mapping_local = {}
material_mapping_local = {}
if me.tessface_uv_textures:
for uvlayer in me.tessface_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
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, [])
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)
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
# 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 (JCB)
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
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
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:
if not blenActionDefault:
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
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
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\tFlogEnable: %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
ob_arms[:] = []
ob_bones[:] = []
ob_cameras[:] = []
ob_lights[:] = []
ob_meshes[:] = []
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_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
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
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)
filepath = new_fbxpath + newname + '.fbx'
print('\nBatch exporting %s as...\n\t%r' % (data, filepath))
# XXX don't know what to do with this, probably do the same? (Arystan)
if batch_mode == 'GROUP': # group
# group, so objects update properly, add a dummy scene.
scene = bpy.data.scenes.new(name="FBX_Temp")