Skip to content

Instantly share code, notes, and snippets.

@green224
Created June 7, 2019 18:27
Show Gist options
  • Save green224/ac02d61c5d0cf9446529b8b456c21775 to your computer and use it in GitHub Desktop.
Save green224/ac02d61c5d0cf9446529b8b456c21775 to your computer and use it in GitHub Desktop.
Blenderでアニメーション付きモデルをちゃんとFBX出力するアドオン
"""
アニメーションを全BakeしてFBX出力をするアドオン。
そのままのFBX出力では、スケーリングしたボーンの子ボーンを
Constraintsで回転させた場合に、正常なモーションを出力できない。
Bakeしようにも、デフォルトのBake処理は1アニメーションずつしかBakeできず、
Bake時にConstraintsを削除する必要があるため、手作業で全Bakeするのはとても大変。
このアドオンでは、それらのBake処理から出力までを一括して行う。
使い方
1.File->Export Baked Anim FBX を選択する
2.出力先ファイルを指定する
3.FBXが出力される。
出力過程で作業状態に変更が加わってしまうので、引き続き作業を行う場合は、1回Undoをする
注意
・出力対象のアニメーションは、NLAトラックのみ。
Actionは出力されないので、出力したいアニメーションはNLAトラック化すること。
・ミュート状態のNLAトラックは出力されない仕様なので、
出力したいNLAトラックのミュート状態はOFFにしておくこと。
・ActionのBake機能は、0未満の時刻のFrameをBakeすることができない。
したがって、NLAトラックのActionExtentsに指定するFrameは0以上の値にしておくこと。
・ActionのBake範囲は、キーが打ってある範囲に限定される。
したがってModifierでループアニメーション化している場合でも、必要であれば
Bakeしたい長さの終わり部分にダミーのキーを打っておくこと。
"""
import bpy
import math
import os
from bpy.props import IntProperty, FloatProperty, EnumProperty
from bpy.props import FloatVectorProperty, StringProperty
# プラグインに関する情報
bl_info = {
"name" : "Baked FBX Exporter",
"author" : "Shu",
"version" : (0,1),
"blender" : (2, 7, 9),
"location" : "File menu > Export Baked FBX",
"description" : "Export FBX with baked animation",
"warning" : "",
"wiki_url" : "",
"tracker_url" : "",
"category" : "Import-Export"
}
# メニューを登録する関数
def menu_func(self, context):
self.layout.operator( ExpBakedFBXOpe.bl_idname )
# プラグインをインストールしたときの処理
def register():
bpy.utils.register_class( ExpBakedFBXOpe )
bpy.types.INFO_MT_file.append( menu_func )
# プラグインをアンインストールしたときの処理
def unregister():
bpy.utils.unregister_class( ExpBakedFBXOpe )
bpy.types.INFO_MT_file.remove( menu_func )
# メニュー項目オペレータ
class ExpBakedFBXOpe(bpy.types.Operator):
bl_idname = "object.exp_baked_fbx"
bl_label = "Export Baked Anim FBX"
bl_options = {'REGISTER', 'UNDO'}
filepath = StringProperty(subtype="FILE_PATH")
filename = StringProperty()
directory = StringProperty(subtype="FILE_PATH")
def execute(self, context):
if export( context, self.filepath ): return {'FINISHED'}
"""
self.report(
{'INFO'},
"[FilePath] %s, [FileName] %s, [Directory] %s"
% (self.filepath, self.filename, self.directory)
)
"""
return {'CANCELLED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
# 出力処理本体
def export( context, file_name ):
print("[ExportFBX] Begin")
# 対象Armatureを取得する。1Armatureにだけ対応している。
# (そもそも複数ArmatureはUnityで再生できない)
tgt_armature = None
for obj in bpy.data.objects:
if not obj :continue
if obj.type != 'ARMATURE':continue
tgt_armature = obj
break
if not tgt_armature:
report('WARNING','There is no armature')
return False
if not tgt_armature.animation_data:
report('WARNING','There is no animation')
return False
if not tgt_armature.animation_data.nla_tracks:
report('WARNING','There is no NLA tracks')
return False
# オリジナルArmatureのモードおよび、NLAトラックのミュート状態を記憶しておく
bpy.context.scene.objects.active = tgt_armature
old_mode = bpy.context.object.mode
bpy.ops.object.mode_set(mode='POSE')
old_tracks_mute = []
for track in tgt_armature.animation_data.nla_tracks: old_tracks_mute.append(track.mute)
# 全アクションを列挙
actions = [ a for a in bpy.data.actions if bool(a) & a.use_fake_user ]
for action_idx, action in enumerate(actions):
# Bakeする際にArmatureが破壊されるので、複製する
bpy.context.scene.objects.active = tgt_armature
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
bpy.context.scene.objects.active = tgt_armature
tgt_armature.select = True
# FBX出力時にConstraintsが残っていると、結局正常でないアニメーションが出力されてしまうので
# Constraintsが削除されたArmatureを使用する必要がある。
# したがって最後のArmatureだけは、複製せずにBakeを行い、そのままFBX出力に使用する。
if action_idx == len(actions)-1:
dup_armature = tgt_armature
else:
bpy.ops.object.duplicate_move()
dup_armature = bpy.context.scene.objects.active
dup_armature.animation_data.action = action
# Bakeする前にNLAトラックを全ミュートにする
bpy.ops.object.mode_set(mode='POSE')
for track in dup_armature.animation_data.nla_tracks: track.mute = True
# キーのないボーンのTransformをデフォルト状態にしておく
bpy.ops.pose.select_all(action='SELECT')
bpy.ops.pose.transforms_clear()
# アクションのキーフレーム長を計算
firstFrame = 9999999
lastFrame = -9999999
for fcu in action.fcurves:
for keyframe in fcu.keyframe_points:
x, y = keyframe.co
k = math.ceil(x)
if k < firstFrame : firstFrame = k
if k > lastFrame : lastFrame = k
# ActionをBake
bpy.context.scene.update()
bpy.ops.nla.bake(
frame_start=firstFrame,
frame_end=lastFrame,
only_selected=True,
visual_keying=True,
clear_constraints=True,
use_current_action=True,
bake_types={'POSE'}
)
# Bake用の仮Armatureを削除
if action_idx != len(actions)-1:
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.delete( use_global=True )
bpy.context.scene.update()
print("Action: {}, First frame: {}, Second frame: {}".format(action.name, firstFrame, lastFrame))
# オリジナルArmatureのモードおよびNLAトラックのミュート状態を復元する
bpy.context.scene.objects.active = tgt_armature
bpy.ops.object.mode_set(mode=old_mode)
for i,track in enumerate(tgt_armature.animation_data.nla_tracks):
track.mute = old_tracks_mute[i]
bpy.context.scene.update()
# FBX出力
mdl_filepath = bpy.data.filepath
mdl_directory = os.path.dirname( mdl_filepath )
fbx_filepath = os.path.join( mdl_directory, file_name )
bpy.ops.export_scene.fbx(
filepath=fbx_filepath,
check_existing=False,
axis_up='Y',
axis_forward='-Z',
filter_glob="*.fbx",
version='BIN7400',
use_selection=False,
global_scale=1.0,
bake_space_transform=True,
object_types={'MESH', 'ARMATURE'},
use_mesh_modifiers=True,
mesh_smooth_type='OFF',
use_mesh_edges=False,
use_tspace=False,
use_custom_props=False,
add_leaf_bones=False,
primary_bone_axis='Y',
secondary_bone_axis='X',
use_armature_deform_only=True,
bake_anim=True,
bake_anim_use_all_bones=True,
bake_anim_use_nla_strips=True,
bake_anim_use_all_actions=False,
bake_anim_step=1.0,
bake_anim_simplify_factor=1.0,
use_anim=True,
use_anim_action_all=True,
use_default_take=True,
use_anim_optimize=True,
anim_optimize_precision=6.0,
path_mode='AUTO',
embed_textures=False,
batch_mode='OFF',
use_batch_own_dir=True,
use_metadata=True
)
print("[ExportFBX] Complete")
return True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment