FBX IO: add support for import & export of camera focal length animation.

Requested in T54050, usually would not add new stuff to FBX but this
looked like totally needed for compo needs...
This commit is contained in:
Bastien Montagne 2018-02-19 17:27:05 +01:00
parent 7e18281d2f
commit b890f0d7e8
Notes: blender-bot 2023-02-14 06:11:19 +01:00
Referenced by issue blender/blender#54050: Alembic: Camera properties (focal length, focus distance, ...) animation not importing
Referenced by issue blender/blender#54050, Alembic: Camera properties (focal length, focus distance, ...) animation not importing
3 changed files with 52 additions and 4 deletions

View File

@ -1896,8 +1896,9 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
ACNW(ob_obj.key, 'LCL_SCALING', force_key, force_sek, scale))
p_rots[ob_obj] = rot
animdata_shapes = OrderedDict()
force_key = (simplify_fac == 0.0)
animdata_shapes = OrderedDict()
for me, (me_key, _shapes_key, shapes) in scene_data.data_deformers_shape.items():
# Ignore absolute shape keys for now!
if not me.shape_keys.use_relative:
@ -1908,6 +1909,12 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
acnode.add_group(me_key, shape.name, shape.name, (shape.name,))
animdata_shapes[channel_key] = (acnode, me, shape)
animdata_cameras = OrderedDict()
for cam_obj, cam_key in scene_data.data_cameras.items():
cam = cam_obj.bdata.data
acnode = AnimationCurveNodeWrapper(cam_key, 'CAMERA_FOCAL', force_key, force_sek, (cam.lens,))
animdata_cameras[cam_key] = (acnode, cam)
currframe = f_start
while currframe <= f_end:
real_currframe = currframe - f_start if start_zero else currframe
@ -1927,6 +1934,8 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
ob_obj.dupli_list_clear()
for anim_shape, me, shape in animdata_shapes.values():
anim_shape.add_keyframe(real_currframe, (shape.value * 100.0,))
for anim_camera, camera in animdata_cameras.values():
anim_camera.add_keyframe(real_currframe, (camera.lens,))
currframe += bake_step
scene.frame_set(back_currframe, 0.0)
@ -1958,6 +1967,18 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
anim_data = animations[elem_key] = ("dummy_unused_key", OrderedDict())
anim_data[1][fbx_group] = (group_key, group, fbx_gname)
# And cameras' lens keys.
for cam_key, (anim_camera, camera) in animdata_cameras.items():
final_keys = OrderedDict()
anim_camera.simplify(simplify_fac, bake_step, force_keep)
if not anim_camera:
continue
for elem_key, group_key, group, fbx_group, fbx_gname in anim_camera.get_final_data(scene, ref_id, force_keep):
anim_data = animations.get(elem_key)
if anim_data is None:
anim_data = animations[elem_key] = ("dummy_unused_key", OrderedDict())
anim_data[1][fbx_group] = (group_key, group, fbx_gname)
astack_key = get_blender_anim_stack_key(scene, ref_id)
alayer_key = get_blender_anim_layer_key(scene, ref_id)
name = (get_blenderID_name(ref_id) if ref_id else scene.name).encode()

View File

@ -729,6 +729,7 @@ class AnimationCurveNodeWrapper:
'LCL_ROTATION': ("Lcl Rotation", "R", ("X", "Y", "Z")),
'LCL_SCALING': ("Lcl Scaling", "S", ("X", "Y", "Z")),
'SHAPE_KEY': ("DeformPercent", "DeformPercent", ("DeformPercent",)),
'CAMERA_FOCAL': ("FocalLength", "FocalLength", ("FocalLength",)),
}
def __init__(self, elem_key, kind, force_keying, force_startend_keying, default_values=...):

View File

@ -557,7 +557,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
'Bake' loc/rot/scale into the action,
taking any pre_ and post_ matrix into account to transform from fbx into blender space.
"""
from bpy.types import Object, PoseBone, ShapeKey, Material
from bpy.types import Object, PoseBone, ShapeKey, Material, Camera
from itertools import chain
fbx_curves = []
@ -577,6 +577,8 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
props = [("diffuse_color", 3, grpname or "Diffuse Color")]
elif isinstance(item, ShapeKey):
props = [(item.path_from_id("value"), 1, "Key")]
elif isinstance(item, Camera):
props = [(item.path_from_id("lens"), 1, "Camera")]
else: # Object or PoseBone:
if item.is_bone:
bl_obj = item.bl_obj.pose.bones[item.bl_bone]
@ -624,6 +626,17 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset):
for fc, v in zip(blen_curves, (value,)):
fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR'
elif isinstance(item, Camera):
for frame, values in blen_read_animations_curves_iter(fbx_curves, anim_offset, 0, fps):
value = 0.0
for v, (fbxprop, channel, _fbx_acdata) in values:
assert(fbxprop == b'FocalLength')
assert(channel == 0)
value = v
for fc, v in zip(blen_curves, (value,)):
fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR'
else: # Object or PoseBone:
if item.is_bone:
bl_obj = item.bl_obj.pose.bones[item.bl_bone]
@ -686,7 +699,7 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_o
Only the first found action is linked to objects, more complex setups are not handled,
it's up to user to reproduce them!
"""
from bpy.types import ShapeKey, Material
from bpy.types import ShapeKey, Material, Camera
actions = {}
for as_uuid, ((fbx_asdata, _blen_data), alayers) in stacks.items():
@ -698,6 +711,8 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_o
id_data = item
elif isinstance(item, ShapeKey):
id_data = item.id_data
elif isinstance(item, Camera):
id_data = item
else:
id_data = item.bl_obj
# XXX Ignore rigged mesh animations - those are a nightmare to handle, see note about it in
@ -2838,6 +2853,13 @@ def load(operator, context, filepath="",
if keyblocks is None:
continue
items += [(kb, lnk_prop) for kb in keyblocks]
elif lnk_prop == b'FocalLength': # Camera lens.
from bpy.types import Camera
fbx_item = fbx_table_nodes.get(n_uuid, None)
if fbx_item is None or not isinstance(fbx_item[1], Camera):
continue
cam = fbx_item[1]
items.append((cam, lnk_prop))
elif lnk_prop == b'DiffuseColor':
from bpy.types import Material
fbx_item = fbx_table_nodes.get(n_uuid, None)
@ -2874,7 +2896,11 @@ def load(operator, context, filepath="",
continue
# Note this is an infamous simplification of the compound props stuff,
# seems to be standard naming but we'll probably have to be smarter to handle more exotic files?
channel = {b'd|X': 0, b'd|Y': 1, b'd|Z': 2, b'd|DeformPercent': 0}.get(acn_ctype.props[3], None)
channel = {
b'd|X': 0, b'd|Y': 1, b'd|Z': 2,
b'd|DeformPercent': 0,
b'd|FocalLength': 0
}.get(acn_ctype.props[3], None)
if channel is None:
continue
curvenodes[acn_uuid][ac_uuid] = (fbx_acitem, channel)