diff options
author | Julien Duroure <julien.duroure@gmail.com> | 2019-12-14 11:02:16 +0300 |
---|---|---|
committer | Julien Duroure <julien.duroure@gmail.com> | 2019-12-14 11:02:16 +0300 |
commit | f505743b2f9f75f53b1497796869f799aa16020e (patch) | |
tree | fbe03ab40c55f2146f4fe06c15522bf3615c839c /io_scene_gltf2/blender | |
parent | 76fc4142b518189b602ff69b4c43ebbfb1b23441 (diff) |
glTF exporter: Basic SK driver export (driven by armature animation)
Diffstat (limited to 'io_scene_gltf2/blender')
7 files changed, 225 insertions, 33 deletions
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_conversion.py b/io_scene_gltf2/blender/com/gltf2_blender_conversion.py index 88026e54..6d9a901a 100755 --- a/io_scene_gltf2/blender/com/gltf2_blender_conversion.py +++ b/io_scene_gltf2/blender/com/gltf2_blender_conversion.py @@ -81,3 +81,15 @@ def texture_transform_gltf_to_blender(texture_transform): 'scale': [scale[0], scale[1]], } +def get_target(property): + return { + "delta_location": "translation", + "delta_rotation_euler": "rotation", + "location": "translation", + "rotation_axis_angle": "rotation", + "rotation_euler": "rotation", + "rotation_quaternion": "rotation", + "scale": "scale", + "value": "weights" + }.get(property) + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py index 5fcd4ede..0b23ec85 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py @@ -27,13 +27,14 @@ def gather_animation_channel_target(channels: typing.Tuple[bpy.types.FCurve], blender_object: bpy.types.Object, bake_bone: typing.Union[str, None], bake_channel: typing.Union[str, None], + driver_obj, export_settings ) -> gltf2_io.AnimationChannelTarget: animation_channel_target = gltf2_io.AnimationChannelTarget( extensions=__gather_extensions(channels, blender_object, export_settings, bake_bone), extras=__gather_extras(channels, blender_object, export_settings, bake_bone), - node=__gather_node(channels, blender_object, export_settings, bake_bone), + node=__gather_node(channels, blender_object, export_settings, bake_bone, driver_obj), path=__gather_path(channels, blender_object, export_settings, bake_bone, bake_channel) ) @@ -66,8 +67,13 @@ def __gather_extras(channels: typing.Tuple[bpy.types.FCurve], def __gather_node(channels: typing.Tuple[bpy.types.FCurve], blender_object: bpy.types.Object, export_settings, - bake_bone: typing.Union[str, None] + bake_bone: typing.Union[str, None], + driver_obj ) -> gltf2_io.Node: + + if driver_obj is not None: + return gltf2_blender_gather_nodes.gather_node(driver_obj, None, export_settings) + if blender_object.type == "ARMATURE": # TODO: get joint from fcurve data_path and gather_joint diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py index 50918b68..199d73a9 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py @@ -23,6 +23,7 @@ from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_samplers from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_channel_target from io_scene_gltf2.blender.exp import gltf2_blender_get from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins +from io_scene_gltf2.blender.exp import gltf2_blender_gather_drivers from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions @@ -77,18 +78,41 @@ def gather_animation_channels(blender_action: bpy.types.Action, p, bake_range_start, bake_range_end, - blender_action.name) + blender_action.name, + None) channels.append(channel) + + + # Retrieve channels for drivers, if needed + drivers_to_manage = gltf2_blender_gather_drivers.get_sk_drivers(blender_object) + for obj, fcurves in drivers_to_manage: + channel = __gather_animation_channel( + fcurves, + blender_object, + export_settings, + None, + None, + bake_range_start, + bake_range_end, + blender_action.name, + obj) + channels.append(channel) + else: for channel_group in __get_channel_groups(blender_action, blender_object, export_settings): channel_group_sorted = __get_channel_group_sorted(channel_group, blender_object) if len(channel_group_sorted) == 0: # Only errors on channels, ignoring continue - channel = __gather_animation_channel(channel_group_sorted, blender_object, export_settings, None, None, bake_range_start, bake_range_end, blender_action.name) + channel = __gather_animation_channel(channel_group_sorted, blender_object, export_settings, None, None, bake_range_start, bake_range_end, blender_action.name, None) if channel is not None: channels.append(channel) + + # resetting driver caches + gltf2_blender_gather_drivers.get_sk_driver_values.reset_cache() + gltf2_blender_gather_drivers.get_sk_drivers.reset_cache() + return channels def __get_channel_group_sorted(channels: typing.Tuple[bpy.types.FCurve], blender_object: bpy.types.Object): @@ -145,7 +169,8 @@ def __gather_animation_channel(channels: typing.Tuple[bpy.types.FCurve], bake_channel: typing.Union[str, None], bake_range_start, bake_range_end, - action_name: str + action_name: str, + driver_obj ) -> typing.Union[gltf2_io.AnimationChannel, None]: if not __filter_animation_channel(channels, blender_object, export_settings): return None @@ -153,8 +178,8 @@ def __gather_animation_channel(channels: typing.Tuple[bpy.types.FCurve], animation_channel = gltf2_io.AnimationChannel( extensions=__gather_extensions(channels, blender_object, export_settings, bake_bone), extras=__gather_extras(channels, blender_object, export_settings, bake_bone), - sampler=__gather_sampler(channels, blender_object, export_settings, bake_bone, bake_channel, bake_range_start, bake_range_end, action_name), - target=__gather_target(channels, blender_object, export_settings, bake_bone, bake_channel) + sampler=__gather_sampler(channels, blender_object, export_settings, bake_bone, bake_channel, bake_range_start, bake_range_end, action_name, driver_obj), + target=__gather_target(channels, blender_object, export_settings, bake_bone, bake_channel, driver_obj) ) export_user_extensions('gather_animation_channel_hook', @@ -201,7 +226,8 @@ def __gather_sampler(channels: typing.Tuple[bpy.types.FCurve], bake_channel: typing.Union[str, None], bake_range_start, bake_range_end, - action_name + action_name, + driver_obj ) -> gltf2_io.AnimationSampler: return gltf2_blender_gather_animation_samplers.gather_animation_sampler( channels, @@ -211,6 +237,7 @@ def __gather_sampler(channels: typing.Tuple[bpy.types.FCurve], bake_range_start, bake_range_end, action_name, + driver_obj, export_settings ) @@ -219,10 +246,11 @@ def __gather_target(channels: typing.Tuple[bpy.types.FCurve], blender_object: bpy.types.Object, export_settings, bake_bone: typing.Union[str, None], - bake_channel: typing.Union[str, None] + bake_channel: typing.Union[str, None], + driver_obj ) -> gltf2_io.AnimationChannelTarget: return gltf2_blender_gather_animation_channel_target.gather_animation_channel_target( - channels, blender_object, bake_bone, bake_channel, export_settings) + channels, blender_object, bake_bone, bake_channel, driver_obj, export_settings) def __get_channel_groups(blender_action: bpy.types.Action, blender_object: bpy.types.Object, export_settings): diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py index 09df7d53..bb59b8a9 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py @@ -20,6 +20,7 @@ from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, boneca from io_scene_gltf2.blender.com import gltf2_blender_math from io_scene_gltf2.blender.exp import gltf2_blender_get from io_scene_gltf2.blender.exp import gltf2_blender_extract +from io_scene_gltf2.blender.exp.gltf2_blender_gather_drivers import get_sk_drivers, get_sk_driver_values from . import gltf2_blender_export_keys from io_scene_gltf2.io.com import gltf2_io_debug @@ -164,6 +165,13 @@ def get_bone_matrix(blender_object_if_armature: typing.Optional[bpy.types.Object matrix = pbone.matrix matrix = blender_object_if_armature.convert_space(pose_bone=pbone, matrix=matrix, from_space='POSE', to_space='LOCAL') data[frame][pbone.name] = matrix + + + # If some drivers must be evaluated, do it here, to avoid to have to change frame by frame later + drivers_to_manage = get_sk_drivers(blender_object_if_armature) + for dr_obj, dr_fcurves in drivers_to_manage: + vals = get_sk_driver_values(dr_obj, frame, dr_fcurves) + frame += step return data @@ -178,10 +186,11 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec bake_range_start, bake_range_end, action_name: str, + driver_obj, export_settings ) -> typing.List[Keyframe]: """Convert the blender action groups' fcurves to keyframes for use in glTF.""" - if bake_bone is None: + if bake_bone is None and driver_obj is None: # Find the start and end of the whole action group # Note: channels has some None items only for SK if some SK are not animated ranges = [channel.range() for channel in channels if channel is not None] @@ -197,7 +206,7 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec # Bake the animation, by evaluating the animation for all frames # TODO: maybe baking can also be done with FCurve.convert_to_samples - if blender_object_if_armature is not None: + if blender_object_if_armature is not None and driver_obj is None: if bake_bone is None: pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature, channels[0].data_path) @@ -238,9 +247,13 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec "scale": scale }[target_property] else: - # Note: channels has some None items only for SK if some SK are not animated - key.value = [c.evaluate(frame) for c in channels if c is not None] - complete_key(key, non_keyed_values) + if driver_obj is None: + # Note: channels has some None items only for SK if some SK are not animated + key.value = [c.evaluate(frame) for c in channels if c is not None] + complete_key(key, non_keyed_values) + else: + key.value = get_sk_driver_values(driver_obj, frame, channels) + complete_key(key, non_keyed_values) keyframes.append(key) frame += step else: diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py index cd237f35..4b1ebeb1 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py @@ -38,11 +38,12 @@ def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve], bake_range_start, bake_range_end, action_name: str, + driver_obj, export_settings ) -> gltf2_io.AnimationSampler: blender_object_if_armature = blender_object if blender_object.type == "ARMATURE" else None - if blender_object_if_armature is not None: + if blender_object_if_armature is not None and driver_obj is None: if bake_bone is None: pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature, channels[0].data_path) @@ -53,6 +54,7 @@ def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve], non_keyed_values = __gather_non_keyed_values(channels, blender_object, blender_object_if_armature, pose_bone_if_armature, bake_channel, + driver_obj, export_settings) @@ -60,7 +62,7 @@ def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve], extensions=__gather_extensions(channels, blender_object_if_armature, export_settings, bake_bone, bake_channel), extras=__gather_extras(channels, blender_object_if_armature, export_settings, bake_bone, bake_channel), input=__gather_input(channels, blender_object_if_armature, non_keyed_values, - bake_bone, bake_channel, bake_range_start, bake_range_end, action_name, export_settings), + bake_bone, bake_channel, bake_range_start, bake_range_end, action_name, driver_obj, export_settings), interpolation=__gather_interpolation(channels, blender_object_if_armature, export_settings, bake_bone, bake_channel), output=__gather_output(channels, blender_object.matrix_parent_inverse.copy().freeze(), blender_object_if_armature, @@ -70,6 +72,7 @@ def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve], bake_range_start, bake_range_end, action_name, + driver_obj, export_settings) ) @@ -91,16 +94,23 @@ def __gather_non_keyed_values(channels: typing.Tuple[bpy.types.FCurve], blender_object_if_armature: typing.Optional[bpy.types.Object], pose_bone_if_armature: typing.Optional[bpy.types.PoseBone], bake_channel: typing.Union[str, None], + driver_obj, export_settings ) -> typing.Tuple[typing.Optional[float]]: non_keyed_values = [] + obj = blender_object if driver_obj is None else driver_obj + # Note: channels has some None items only for SK if some SK are not animated if None not in channels: # classic case for object TRS or bone TRS # Or if all morph target are animated + if driver_obj is not None: + # driver of SK + return tuple([None] * len(channels)) + if bake_channel is None: target = channels[0].data_path.split('.')[-1] else: @@ -129,26 +139,26 @@ def __gather_non_keyed_values(channels: typing.Tuple[bpy.types.FCurve], for i in range(0, length): if bake_channel is not None: non_keyed_values.append({ - "delta_location" : blender_object.delta_location, - "delta_rotation_euler" : blender_object.delta_rotation_euler, - "location" : blender_object.location, - "rotation_axis_angle" : blender_object.rotation_axis_angle, - "rotation_euler" : blender_object.rotation_euler, - "rotation_quaternion" : blender_object.rotation_quaternion, - "scale" : blender_object.scale + "delta_location" : obj.delta_location, + "delta_rotation_euler" : obj.delta_rotation_euler, + "location" : obj.location, + "rotation_axis_angle" : obj.rotation_axis_angle, + "rotation_euler" : obj.rotation_euler, + "rotation_quaternion" : obj.rotation_quaternion, + "scale" : obj.scale }[target][i]) elif i in indices: non_keyed_values.append(None) else: if blender_object_if_armature is None: non_keyed_values.append({ - "delta_location" : blender_object.delta_location, - "delta_rotation_euler" : blender_object.delta_rotation_euler, - "location" : blender_object.location, - "rotation_axis_angle" : blender_object.rotation_axis_angle, - "rotation_euler" : blender_object.rotation_euler, - "rotation_quaternion" : blender_object.rotation_quaternion, - "scale" : blender_object.scale + "delta_location" : obj.delta_location, + "delta_rotation_euler" : obj.delta_rotation_euler, + "location" : obj.location, + "rotation_axis_angle" : obj.rotation_axis_angle, + "rotation_euler" : obj.rotation_euler, + "rotation_quaternion" : obj.rotation_quaternion, + "scale" : obj.scale }[target][i]) else: # TODO, this is not working if the action is not active (NLA case for example) @@ -171,7 +181,7 @@ def __gather_non_keyed_values(channels: typing.Tuple[bpy.types.FCurve], if object_path: shapekeys_idx = {} cpt_sk = 0 - for sk in blender_object.data.shape_keys.key_blocks: + for sk in obj.data.shape_keys.key_blocks: if sk == sk.relative_key: continue if sk.mute is True: @@ -181,7 +191,7 @@ def __gather_non_keyed_values(channels: typing.Tuple[bpy.types.FCurve], for idx_c, channel in enumerate(channels): if channel is None: - non_keyed_values.append(blender_object.data.shape_keys.key_blocks[shapekeys_idx[idx_c]].value) + non_keyed_values.append(obj.data.shape_keys.key_blocks[shapekeys_idx[idx_c]].value) else: non_keyed_values.append(None) @@ -214,6 +224,7 @@ def __gather_input(channels: typing.Tuple[bpy.types.FCurve], bake_range_start, bake_range_end, action_name, + driver_obj, export_settings ) -> gltf2_io.Accessor: """Gather the key time codes.""" @@ -225,6 +236,7 @@ def __gather_input(channels: typing.Tuple[bpy.types.FCurve], bake_range_start, bake_range_end, action_name, + driver_obj, export_settings) times = [k.seconds for k in keyframes] @@ -276,6 +288,7 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve], bake_range_start, bake_range_end, action_name, + driver_obj, export_settings ) -> gltf2_io.Accessor: """Gather the data of the keyframes.""" @@ -287,6 +300,7 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve], bake_range_start, bake_range_end, action_name, + driver_obj, export_settings) if bake_bone is not None: target_datapath = "pose.bones['" + bake_bone + "']." + bake_channel diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py index f514e913..4efd08f3 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py @@ -95,3 +95,48 @@ def bonecache(func): call_or_fetch = cached unique = cached +def skdriverdiscovercache(func): + + def reset_cache_skdriverdiscovercache(): + func.__current_armature_name = None + func.__skdriverdiscover = {} + + func.reset_cache = reset_cache_skdriverdiscovercache + + @functools.wraps(func) + def wrapper_skdriverdiscover(*args, **kwargs): + if not hasattr(func, "__current_armature_name") or func.__current_armature_name is None: + func.__current_armature_name = None + func.reset_cache() + + if args[0] != func.__current_armature_name: + result = func(*args) + func.__skdriverdiscover[args[0]] = result + func.__current_armature_name = args[0] + return result + else: + return func.__skdriverdiscover[args[0]] + return wrapper_skdriverdiscover + +def skdrivervalues(func): + + def reset_cache_skdrivervalues(): + func.__skdrivervalues = {} + + func.reset_cache = reset_cache_skdrivervalues + + @functools.wraps(func) + def wrapper_skdrivervalues(*args, **kwargs): + if not hasattr(func, "__skdrivervalues") or func.__skdrivervalues is None: + func.reset_cache() + + if args[0].name not in func.__skdrivervalues.keys(): + func.__skdrivervalues[args[0].name] = {} + if args[1] not in func.__skdrivervalues[args[0].name]: + vals = func(*args) + func.__skdrivervalues[args[0].name][args[1]] = vals + return vals + else: + return func.__skdrivervalues[args[0].name][args[1]] + return wrapper_skdrivervalues + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py new file mode 100644 index 00000000..a2ab949b --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py @@ -0,0 +1,74 @@ +# Copyright 2019 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from io_scene_gltf2.blender.exp import gltf2_blender_gather_nodes +from io_scene_gltf2.blender.com import gltf2_blender_conversion +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import skdriverdiscovercache, skdrivervalues +from io_scene_gltf2.blender.com.gltf2_blender_data_path import get_target_object_path + + +@skdriverdiscovercache +def get_sk_drivers(blender_armature): + + drivers = [] + + for child in blender_armature.children: + if not child.data: + continue + if not child.data.shape_keys: + continue + if not child.data.shape_keys.animation_data: + continue + if not child.data.shape_keys.animation_data.drivers: + continue + if len(child.data.shape_keys.animation_data.drivers) <= 0: + continue + + shapekeys_idx = {} + cpt_sk = 0 + for sk in child.data.shape_keys.key_blocks: + if sk == sk.relative_key: + continue + if sk.mute is True: + continue + shapekeys_idx[sk.name] = cpt_sk + cpt_sk += 1 + + # Note: channels will have some None items only for SK if some SK are not animated + idx_channel_mapping = [] + all_sorted_channels = [] + for sk_c in child.data.shape_keys.animation_data.drivers: + sk_name = child.data.shape_keys.path_resolve(get_target_object_path(sk_c.data_path)).name + idx = shapekeys_idx[sk_name] + idx_channel_mapping.append((shapekeys_idx[sk_name], sk_c)) + existing_idx = dict(idx_channel_mapping) + for i in range(0, cpt_sk): + if i not in existing_idx.keys(): + all_sorted_channels.append(None) + else: + all_sorted_channels.append(existing_idx[i]) + + drivers.append((child, tuple(all_sorted_channels))) + + return tuple(drivers) + +@skdrivervalues +def get_sk_driver_values(blender_object, frame, fcurves): + sk_values = [] + for f in [f for f in fcurves if f is not None]: + sk_values.append(blender_object.data.shape_keys.path_resolve(get_target_object_path(f.data_path)).value) + + return tuple(sk_values) + |