1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
|
# SPDX-License-Identifier: GPL-2.0-or-later
# This file defines a set of methods that are useful for various
# Relative Keying Set (RKS) related operations, such as: callbacks
# for polling, iterator callbacks, and also generate callbacks.
# All of these can be used in conjunction with the others.
__all__ = (
"path_add_property",
"RKS_POLL_selected_objects",
"RKS_POLL_selected_bones",
"RKS_POLL_selected_items",
"RKS_ITER_selected_objects",
"RKS_ITER_selected_bones",
"RKS_ITER_selected_item",
"RKS_GEN_available",
"RKS_GEN_location",
"RKS_GEN_rotation",
"RKS_GEN_scaling",
"RKS_GEN_bendy_bones",
)
import bpy
###########################
# General Utilities
# Append the specified property name on the the existing path
def path_add_property(path, prop):
if path:
return path + "." + prop
else:
return prop
###########################
# Poll Callbacks
# selected objects (active object must be in object mode)
def RKS_POLL_selected_objects(_ksi, context):
ob = context.active_object
if ob:
return ob.mode == 'OBJECT'
else:
return bool(context.selected_objects)
# selected bones
def RKS_POLL_selected_bones(_ksi, context):
# we must be in Pose Mode, and there must be some bones selected
ob = context.active_object
if ob and ob.mode == 'POSE':
if context.active_pose_bone or context.selected_pose_bones:
return True
# nothing selected
return False
# selected bones or objects
def RKS_POLL_selected_items(ksi, context):
return (RKS_POLL_selected_bones(ksi, context) or
RKS_POLL_selected_objects(ksi, context))
###########################
# Iterator Callbacks
# All selected objects or pose bones, depending on which we've got.
def RKS_ITER_selected_item(ksi, context, ks):
ob = context.active_object
if ob and ob.mode == 'POSE':
for bone in context.selected_pose_bones:
ksi.generate(context, ks, bone)
else:
for ob in context.selected_objects:
ksi.generate(context, ks, ob)
# All selected objects only.
def RKS_ITER_selected_objects(ksi, context, ks):
for ob in context.selected_objects:
ksi.generate(context, ks, ob)
# All selected bones only.
def RKS_ITER_selected_bones(ksi, context, ks):
for bone in context.selected_pose_bones:
ksi.generate(context, ks, bone)
###########################
# Generate Callbacks
# 'Available' F-Curves
def RKS_GEN_available(_ksi, _context, ks, data):
# try to get the animation data associated with the closest
# ID-block to the data (neither of which may exist/be easy to find)
id_block = data.id_data
adt = getattr(id_block, "animation_data", None)
# there must also be an active action...
if adt is None or adt.action is None:
return
# if we haven't got an ID-block as 'data', try to restrict
# paths added to only those which branch off from here
# i.e. for bones
if id_block != data:
basePath = data.path_from_id()
else:
basePath = None # this is not needed...
# for each F-Curve, include a path to key it
# NOTE: we don't need to set the group settings here
for fcu in adt.action.fcurves:
if basePath:
if basePath in fcu.data_path:
ks.paths.add(id_block, fcu.data_path, index=fcu.array_index)
else:
ks.paths.add(id_block, fcu.data_path, index=fcu.array_index)
# ------
# get ID block and based ID path for transform generators
# private function
def get_transform_generators_base_info(data):
# ID-block for the data
id_block = data.id_data
# get base path and grouping method/name
if isinstance(data, bpy.types.ID):
# no path in this case
path = ""
# transform data on ID-blocks directly should get grouped under a
# hardcoded label ("Object Transforms") so that they get grouped
# consistently when keyframed directly
grouping = "Object Transforms"
else:
# get the path to the ID-block
path = data.path_from_id()
# try to use the name of the data element to group the F-Curve
# else fallback on the KeyingSet name
grouping = getattr(data, "name", None)
# return the ID-block and the path
return id_block, path, grouping
# Location
def RKS_GEN_location(_ksi, _context, ks, data):
# get id-block and path info
id_block, base_path, grouping = get_transform_generators_base_info(data)
# add the property name to the base path
path = path_add_property(base_path, "location")
# add Keying Set entry for this...
if grouping:
ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
else:
ks.paths.add(id_block, path)
# Rotation
def RKS_GEN_rotation(_ksi, _context, ks, data):
# get id-block and path info
id_block, base_path, grouping = get_transform_generators_base_info(data)
# add the property name to the base path
# rotation mode affects the property used
if data.rotation_mode == 'QUATERNION':
path = path_add_property(base_path, "rotation_quaternion")
elif data.rotation_mode == 'AXIS_ANGLE':
path = path_add_property(base_path, "rotation_axis_angle")
else:
path = path_add_property(base_path, "rotation_euler")
# add Keying Set entry for this...
if grouping:
ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
else:
ks.paths.add(id_block, path)
# Scaling
def RKS_GEN_scaling(_ksi, _context, ks, data):
# get id-block and path info
id_block, base_path, grouping = get_transform_generators_base_info(data)
# add the property name to the base path
path = path_add_property(base_path, "scale")
# add Keying Set entry for this...
if grouping:
ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
else:
ks.paths.add(id_block, path)
# Custom Properties
def RKS_GEN_custom_props(_ksi, _context, ks, data):
# get id-block and path info
id_block, base_path, grouping = get_transform_generators_base_info(data)
# Only some RNA types can be animated.
prop_type_compat = {bpy.types.BoolProperty,
bpy.types.IntProperty,
bpy.types.FloatProperty}
# When working with a pose, 'id_block' is the armature object (which should
# get the animation data), whereas 'data' is the bone being keyed.
for cprop_name in data.keys():
# ignore special "_RNA_UI" used for UI editing
if cprop_name == "_RNA_UI":
continue
prop_path = '["%s"]' % bpy.utils.escape_identifier(cprop_name)
try:
rna_property = data.path_resolve(prop_path, False)
except ValueError:
# Can technically happen, but there is no known case.
continue
if rna_property is None:
# In this case the property cannot be converted to an
# FCurve-compatible value, so we can't keyframe it anyways.
continue
if rna_property.rna_type not in prop_type_compat:
continue
path = "%s%s" % (base_path, prop_path)
if grouping:
ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
else:
ks.paths.add(id_block, path)
# ------
# Property identifiers for Bendy Bones
bbone_property_ids = (
"bbone_curveinx",
"bbone_curveiny",
"bbone_curveoutx",
"bbone_curveouty",
"bbone_rollin",
"bbone_rollout",
"bbone_scalein",
"bbone_scaleout",
# NOTE: These are in the nested bone struct
# Do it this way to force them to be included
# in whatever actions are being keyed here
"bone.bbone_in",
"bone.bbone_out",
)
# Add Keying Set entries for bendy bones
def RKS_GEN_bendy_bones(_ksi, _context, ks, data):
# get id-block and path info
# NOTE: This assumes that we're dealing with a bone here...
id_block, base_path, grouping = get_transform_generators_base_info(data)
# for each of the bendy bone properties, add a Keying Set entry for it...
for propname in bbone_property_ids:
# add the property name to the base path
path = path_add_property(base_path, propname)
# add Keying Set entry for this...
if grouping:
ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
else:
ks.paths.add(id_block, path)
|