FBX IO: Fix animation import when the newer opt-in FBX_KTIME is used #105019
@ -23,6 +23,7 @@ from . import encode_bin, data_types
|
||||
|
||||
# "Constants"
|
||||
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_SCENEINFO_VERSION = 100
|
||||
FBX_TEMPLATES_VERSION = 100
|
||||
@ -54,7 +55,19 @@ FBX_ANIM_KEY_VERSION = 4008
|
||||
FBX_NAME_CLASS_SEP = b"\x00\x01"
|
||||
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.
|
||||
@ -216,7 +229,7 @@ UNITS = {
|
||||
"degree": 360.0,
|
||||
"radian": math.pi * 2.0,
|
||||
"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_EDGE,
|
||||
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
|
||||
@ -803,9 +806,8 @@ def blen_read_invalid_animation_curve(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):
|
||||
from .fbx_utils import FBX_KTIME
|
||||
timefac = fps / FBX_KTIME
|
||||
def _convert_fbx_time_to_blender_time(key_times, blen_start_offset, fbx_start_offset, fps, fbx_ktime):
|
||||
timefac = fps / fbx_ktime
|
||||
|
||||
# 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.
|
||||
@ -838,19 +840,21 @@ def blen_read_animation_curve(fbx_curve):
|
||||
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.
|
||||
Linear interpolation is currently assumed.
|
||||
|
||||
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
|
||||
each pair has the same keyframe times.
|
||||
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)
|
||||
|
||||
# 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()
|
||||
|
||||
|
||||
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,
|
||||
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})
|
||||
blen_curve = blen_curves[channel]
|
||||
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):
|
||||
deform_values = shape_key_deforms.setdefault(item, [])
|
||||
@ -960,7 +965,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
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.
|
||||
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
|
||||
# if necessary after reading all animations.
|
||||
@ -981,7 +986,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
# Remap the imported values from FBX to Blender.
|
||||
values = values / 1000.0
|
||||
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:
|
||||
transform_data = item.fbx_transform_data
|
||||
@ -1042,10 +1047,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
|
||||
# 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.
|
||||
Only the first found action is linked to objects, more complex setups are not handled,
|
||||
@ -1092,7 +1097,7 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_o
|
||||
id_data.animation_data.action = action
|
||||
# And actually populate the action!
|
||||
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
|
||||
# range until the animated range fits and has extra room to be decreased or increased further.
|
||||
@ -3620,6 +3625,21 @@ def load(operator, context, filepath="",
|
||||
|
||||
# Animation!
|
||||
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_alayer = fbx_template_get((b'AnimationLayer', b'FbxAnimLayer'))
|
||||
stacks = {}
|
||||
@ -3733,7 +3753,8 @@ def load(operator, context, filepath="",
|
||||
curvenodes[acn_uuid][ac_uuid] = (fbx_acitem, channel)
|
||||
|
||||
# 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 _
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user