FBX IO: Fix animation import when the newer opt-in FBX_KTIME is used #105019
@ -5,7 +5,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "FBX format",
|
"name": "FBX format",
|
||||||
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem",
|
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem",
|
||||||
"version": (5, 10, 2),
|
"version": (5, 10, 3),
|
||||||
"blender": (4, 1, 0),
|
"blender": (4, 1, 0),
|
||||||
"location": "File > Import-Export",
|
"location": "File > Import-Export",
|
||||||
"description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions",
|
"description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions",
|
||||||
|
@ -23,6 +23,7 @@ from . import encode_bin, data_types
|
|||||||
|
|
||||||
# "Constants"
|
# "Constants"
|
||||||
FBX_VERSION = 7400
|
FBX_VERSION = 7400
|
||||||
|
# 1004 adds use of "OtherFlags"->"TCDefinition" to control the FBX_KTIME opt-in in FBX version 7700.
|
||||||
FBX_HEADER_VERSION = 1003
|
FBX_HEADER_VERSION = 1003
|
||||||
FBX_SCENEINFO_VERSION = 100
|
FBX_SCENEINFO_VERSION = 100
|
||||||
FBX_TEMPLATES_VERSION = 100
|
FBX_TEMPLATES_VERSION = 100
|
||||||
@ -54,7 +55,19 @@ FBX_ANIM_KEY_VERSION = 4008
|
|||||||
FBX_NAME_CLASS_SEP = b"\x00\x01"
|
FBX_NAME_CLASS_SEP = b"\x00\x01"
|
||||||
FBX_ANIM_PROPSGROUP_NAME = "d"
|
FBX_ANIM_PROPSGROUP_NAME = "d"
|
||||||
|
|
||||||
FBX_KTIME = 46186158000 # This is the number of "ktimes" in one second (yep, precision over the nanosecond...)
|
FBX_KTIME_V7 = 46186158000 # This is the number of "ktimes" in one second (yep, precision over the nanosecond...)
|
||||||
|
# FBX 2019.5 (FBX version 7700) changed the number of "ktimes" per second, however, the new value is opt-in until FBX
|
||||||
|
# version 8000 where it will probably become opt-out.
|
||||||
|
FBX_KTIME_V8 = 141120000
|
||||||
|
# To explicitly use the V7 value in FBX versions 7700-7XXX: fbx_root->"FBXHeaderExtension"->"OtherFlags"->"TCDefinition"
|
||||||
|
# is set to 127.
|
||||||
|
# To opt in to the V8 value in FBX version 7700-7XXX: "TCDefinition" is set to 0.
|
||||||
|
FBX_TIMECODE_DEFINITION_TO_KTIME_PER_SECOND = {
|
||||||
|
0: FBX_KTIME_V8,
|
||||||
|
127: FBX_KTIME_V7,
|
||||||
|
}
|
||||||
|
# The "ktimes" per second for Blender exported FBX is constant because the exported `FBX_VERSION` is constant.
|
||||||
|
FBX_KTIME = FBX_KTIME_V8 if FBX_VERSION >= 8000 else FBX_KTIME_V7
|
||||||
|
|
||||||
|
|
||||||
MAT_CONVERT_LIGHT = Matrix.Rotation(math.pi / 2.0, 4, 'X') # Blender is -Z, FBX is -Y.
|
MAT_CONVERT_LIGHT = Matrix.Rotation(math.pi / 2.0, 4, 'X') # Blender is -Z, FBX is -Y.
|
||||||
@ -216,7 +229,7 @@ UNITS = {
|
|||||||
"degree": 360.0,
|
"degree": 360.0,
|
||||||
"radian": math.pi * 2.0,
|
"radian": math.pi * 2.0,
|
||||||
"second": 1.0, # Ref unit!
|
"second": 1.0, # Ref unit!
|
||||||
"ktime": FBX_KTIME,
|
"ktime": FBX_KTIME, # For export use only because the imported "ktimes" per second may vary.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,6 +48,9 @@ from .fbx_utils import (
|
|||||||
MESH_ATTRIBUTE_SHARP_FACE,
|
MESH_ATTRIBUTE_SHARP_FACE,
|
||||||
MESH_ATTRIBUTE_SHARP_EDGE,
|
MESH_ATTRIBUTE_SHARP_EDGE,
|
||||||
expand_shape_key_range,
|
expand_shape_key_range,
|
||||||
|
FBX_KTIME_V7,
|
||||||
|
FBX_KTIME_V8,
|
||||||
|
FBX_TIMECODE_DEFINITION_TO_KTIME_PER_SECOND,
|
||||||
)
|
)
|
||||||
|
|
||||||
LINEAR_INTERPOLATION_VALUE = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items['LINEAR'].value
|
LINEAR_INTERPOLATION_VALUE = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items['LINEAR'].value
|
||||||
@ -803,9 +806,8 @@ def blen_read_invalid_animation_curve(key_times, key_values):
|
|||||||
return key_times, key_values
|
return key_times, key_values
|
||||||
|
|
||||||
|
|
||||||
def _convert_fbx_time_to_blender_time(key_times, blen_start_offset, fbx_start_offset, fps):
|
def _convert_fbx_time_to_blender_time(key_times, blen_start_offset, fbx_start_offset, fps, fbx_ktime):
|
||||||
from .fbx_utils import FBX_KTIME
|
timefac = fps / fbx_ktime
|
||||||
timefac = fps / FBX_KTIME
|
|
||||||
|
|
||||||
# Convert from FBX timing to Blender timing.
|
# Convert from FBX timing to Blender timing.
|
||||||
# Cannot subtract in-place because key_times could be read directly from FBX and could be used by multiple Actions.
|
# Cannot subtract in-place because key_times could be read directly from FBX and could be used by multiple Actions.
|
||||||
@ -838,19 +840,21 @@ def blen_read_animation_curve(fbx_curve):
|
|||||||
return blen_read_invalid_animation_curve(key_times, key_values)
|
return blen_read_invalid_animation_curve(key_times, key_values)
|
||||||
|
|
||||||
|
|
||||||
def blen_store_keyframes(fbx_key_times, blen_fcurve, key_values, blen_start_offset, fps, fbx_start_offset=0):
|
def blen_store_keyframes(fbx_key_times, blen_fcurve, key_values, blen_start_offset, fps, fbx_ktime, fbx_start_offset=0):
|
||||||
"""Set all keyframe times and values for a newly created FCurve.
|
"""Set all keyframe times and values for a newly created FCurve.
|
||||||
Linear interpolation is currently assumed.
|
Linear interpolation is currently assumed.
|
||||||
|
|
||||||
This is a convenience function for calling blen_store_keyframes_multi with only a single fcurve and values array."""
|
This is a convenience function for calling blen_store_keyframes_multi with only a single fcurve and values array."""
|
||||||
blen_store_keyframes_multi(fbx_key_times, [(blen_fcurve, key_values)], blen_start_offset, fps, fbx_start_offset)
|
blen_store_keyframes_multi(fbx_key_times, [(blen_fcurve, key_values)], blen_start_offset, fps, fbx_ktime,
|
||||||
|
fbx_start_offset)
|
||||||
|
|
||||||
|
|
||||||
def blen_store_keyframes_multi(fbx_key_times, fcurve_and_key_values_pairs, blen_start_offset, fps, fbx_start_offset=0):
|
def blen_store_keyframes_multi(fbx_key_times, fcurve_and_key_values_pairs, blen_start_offset, fps, fbx_ktime,
|
||||||
|
fbx_start_offset=0):
|
||||||
"""Set all keyframe times and values for multiple pairs of newly created FCurves and keyframe values arrays, where
|
"""Set all keyframe times and values for multiple pairs of newly created FCurves and keyframe values arrays, where
|
||||||
each pair has the same keyframe times.
|
each pair has the same keyframe times.
|
||||||
Linear interpolation is currently assumed."""
|
Linear interpolation is currently assumed."""
|
||||||
bl_key_times = _convert_fbx_time_to_blender_time(fbx_key_times, blen_start_offset, fbx_start_offset, fps)
|
bl_key_times = _convert_fbx_time_to_blender_time(fbx_key_times, blen_start_offset, fbx_start_offset, fps, fbx_ktime)
|
||||||
num_keys = len(bl_key_times)
|
num_keys = len(bl_key_times)
|
||||||
|
|
||||||
# Compatible with C float type
|
# Compatible with C float type
|
||||||
@ -883,7 +887,8 @@ def blen_store_keyframes_multi(fbx_key_times, fcurve_and_key_values_pairs, blen_
|
|||||||
blen_fcurve.update()
|
blen_fcurve.update()
|
||||||
|
|
||||||
|
|
||||||
def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, global_scale, shape_key_deforms):
|
def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, global_scale, shape_key_deforms,
|
||||||
|
fbx_ktime):
|
||||||
"""
|
"""
|
||||||
'Bake' loc/rot/scale into the action,
|
'Bake' loc/rot/scale into the action,
|
||||||
taking any pre_ and post_ matrix into account to transform from fbx into blender space.
|
taking any pre_ and post_ matrix into account to transform from fbx into blender space.
|
||||||
@ -947,7 +952,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
|||||||
assert(channel in {0, 1, 2})
|
assert(channel in {0, 1, 2})
|
||||||
blen_curve = blen_curves[channel]
|
blen_curve = blen_curves[channel]
|
||||||
fbx_key_times, values = blen_read_animation_curve(curve)
|
fbx_key_times, values = blen_read_animation_curve(curve)
|
||||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
|
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps, fbx_ktime)
|
||||||
|
|
||||||
elif isinstance(item, ShapeKey):
|
elif isinstance(item, ShapeKey):
|
||||||
for fbxprop, channel_to_curve in fbx_curves.items():
|
for fbxprop, channel_to_curve in fbx_curves.items():
|
||||||
@ -959,7 +964,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
|||||||
fbx_key_times, values = blen_read_animation_curve(curve)
|
fbx_key_times, values = blen_read_animation_curve(curve)
|
||||||
# A fully activated shape key in FBX DeformPercent is 100.0 whereas it is 1.0 in Blender.
|
# A fully activated shape key in FBX DeformPercent is 100.0 whereas it is 1.0 in Blender.
|
||||||
values = values / 100.0
|
values = values / 100.0
|
||||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
|
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps, fbx_ktime)
|
||||||
|
|
||||||
# Store the minimum and maximum shape key values, so that the shape key's slider range can be expanded
|
# Store the minimum and maximum shape key values, so that the shape key's slider range can be expanded
|
||||||
# if necessary after reading all animations.
|
# if necessary after reading all animations.
|
||||||
@ -982,7 +987,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
|||||||
# Remap the imported values from FBX to Blender.
|
# Remap the imported values from FBX to Blender.
|
||||||
values = values / 1000.0
|
values = values / 1000.0
|
||||||
values *= global_scale
|
values *= global_scale
|
||||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
|
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps, fbx_ktime)
|
||||||
|
|
||||||
else: # Object or PoseBone:
|
else: # Object or PoseBone:
|
||||||
transform_data = item.fbx_transform_data
|
transform_data = item.fbx_transform_data
|
||||||
@ -1043,10 +1048,10 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
|||||||
|
|
||||||
# Each channel has the same keyframe times, so the combined times can be passed once along with all the curves
|
# Each channel has the same keyframe times, so the combined times can be passed once along with all the curves
|
||||||
# and values arrays.
|
# and values arrays.
|
||||||
blen_store_keyframes_multi(combined_fbx_times, zip(blen_curves, channel_values), anim_offset, fps)
|
blen_store_keyframes_multi(combined_fbx_times, zip(blen_curves, channel_values), anim_offset, fps, fbx_ktime)
|
||||||
|
|
||||||
|
|
||||||
def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_offset, global_scale):
|
def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_offset, global_scale, fbx_ktime):
|
||||||
"""
|
"""
|
||||||
Recreate an action per stack/layer/object combinations.
|
Recreate an action per stack/layer/object combinations.
|
||||||
Only the first found action is linked to objects, more complex setups are not handled,
|
Only the first found action is linked to objects, more complex setups are not handled,
|
||||||
@ -1093,7 +1098,7 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_o
|
|||||||
id_data.animation_data.action = action
|
id_data.animation_data.action = action
|
||||||
# And actually populate the action!
|
# And actually populate the action!
|
||||||
blen_read_animations_action_item(action, item, cnodes, scene.render.fps, anim_offset, global_scale,
|
blen_read_animations_action_item(action, item, cnodes, scene.render.fps, anim_offset, global_scale,
|
||||||
shape_key_values)
|
shape_key_values, fbx_ktime)
|
||||||
|
|
||||||
# If the minimum/maximum animated value is outside the slider range of the shape key, attempt to expand the slider
|
# If the minimum/maximum animated value is outside the slider range of the shape key, attempt to expand the slider
|
||||||
# range until the animated range fits and has extra room to be decreased or increased further.
|
# range until the animated range fits and has extra room to be decreased or increased further.
|
||||||
@ -3621,6 +3626,21 @@ def load(operator, context, filepath="",
|
|||||||
|
|
||||||
# Animation!
|
# Animation!
|
||||||
def _():
|
def _():
|
||||||
|
# Find the number of "ktimes" per second for this file.
|
||||||
|
# Start with the default for this FBX version.
|
||||||
|
fbx_ktime = FBX_KTIME_V8 if version >= 8000 else FBX_KTIME_V7
|
||||||
|
# Try to find the value of the nested elem_root->'FBXHeaderExtension'->'OtherFlags'->'TCDefinition' element
|
||||||
|
# and look up the "ktimes" per second for its value.
|
||||||
|
if header := elem_find_first(elem_root, b'FBXHeaderExtension'):
|
||||||
|
# The header version that added TCDefinition support is 1004.
|
||||||
|
if elem_prop_first(elem_find_first(header, b'FBXHeaderVersion'), default=0) >= 1004:
|
||||||
|
if other_flags := elem_find_first(header, b'OtherFlags'):
|
||||||
|
if timecode_definition := elem_find_first(other_flags, b'TCDefinition'):
|
||||||
|
timecode_definition_value = elem_prop_first(timecode_definition)
|
||||||
|
# If its value is unknown or missing, default to FBX_KTIME_V8.
|
||||||
|
fbx_ktime = FBX_TIMECODE_DEFINITION_TO_KTIME_PER_SECOND.get(timecode_definition_value,
|
||||||
|
FBX_KTIME_V8)
|
||||||
|
|
||||||
fbx_tmpl_astack = fbx_template_get((b'AnimationStack', b'FbxAnimStack'))
|
fbx_tmpl_astack = fbx_template_get((b'AnimationStack', b'FbxAnimStack'))
|
||||||
fbx_tmpl_alayer = fbx_template_get((b'AnimationLayer', b'FbxAnimLayer'))
|
fbx_tmpl_alayer = fbx_template_get((b'AnimationLayer', b'FbxAnimLayer'))
|
||||||
stacks = {}
|
stacks = {}
|
||||||
@ -3734,7 +3754,8 @@ def load(operator, context, filepath="",
|
|||||||
curvenodes[acn_uuid][ac_uuid] = (fbx_acitem, channel)
|
curvenodes[acn_uuid][ac_uuid] = (fbx_acitem, channel)
|
||||||
|
|
||||||
# And now that we have sorted all this, apply animations!
|
# And now that we have sorted all this, apply animations!
|
||||||
blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, settings.anim_offset, global_scale)
|
blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, settings.anim_offset, global_scale,
|
||||||
|
fbx_ktime)
|
||||||
|
|
||||||
_(); del _
|
_(); del _
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user