glTF exporter: Basic SK driver export (driven by armature animation)
This commit is contained in:
parent
76fc4142b5
commit
f505743b2f
|
@ -15,7 +15,7 @@
|
|||
bl_info = {
|
||||
'name': 'glTF 2.0 format',
|
||||
'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
|
||||
"version": (1, 1, 25),
|
||||
"version": (1, 1, 26),
|
||||
'blender': (2, 81, 6),
|
||||
'location': 'File > Import-Export',
|
||||
'description': 'Import-Export as glTF 2.0',
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
Loading…
Reference in New Issue