Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Duroure <julien.duroure@gmail.com>2019-12-14 11:02:16 +0300
committerJulien Duroure <julien.duroure@gmail.com>2019-12-14 11:02:16 +0300
commitf505743b2f9f75f53b1497796869f799aa16020e (patch)
treefbe03ab40c55f2146f4fe06c15522bf3615c839c /io_scene_gltf2/blender
parent76fc4142b518189b602ff69b4c43ebbfb1b23441 (diff)
glTF exporter: Basic SK driver export (driven by armature animation)
Diffstat (limited to 'io_scene_gltf2/blender')
-rwxr-xr-xio_scene_gltf2/blender/com/gltf2_blender_conversion.py12
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py10
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py44
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py23
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py50
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py45
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py74
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)
+