diff options
author | Joshua Leung <aligorith@gmail.com> | 2010-03-16 09:18:49 +0300 |
---|---|---|
committer | Joshua Leung <aligorith@gmail.com> | 2010-03-16 09:18:49 +0300 |
commit | ea4a987fd424de77465f1a2cd95a655ccf42fd31 (patch) | |
tree | fe49fdb0e18ce012a3a366db7bed703635e0568c /release | |
parent | 604a2b1a1879c1e0ad1f637f5edc1ab12f61c31d (diff) |
== Massive Keying Sets Recode ==
After a few days of wrong turns and learning the finer points of RNA-type-subclassing the hard way, this commit finally presents a refactored version of the Keying Sets system (now version 2) based on some requirements from Cessen.
For a more thorough discussion of this commit, see
http://sites.google.com/site/aligorith/keyingsets_2.pdf?attredirects=0&d=1
------
The main highlight of this refactor is that relative Keying Sets have now been recoded so that Python callbacks are run to generate the Keying Set's list of paths everytime the Keying Set is used (to insert or delete keyframes), allowing complex heuristics to be used to determine whether a property gets keyframed based on the current context. These checks may include checking on selection status of related entities, or transform locks.
Built-In KeyingSets have also been recoded, and moved from C and out into Python. These are now coded as Relative Keying Sets, and can to some extent serve as basis for adding new relative Keying Sets. However, these have mostly been coded in a slightly 'modular' way which may be confusing for those not so familiar with Python in general. A usable template will be added soon for more general usage.
Keyframing settings (i.e. 'visual', 'needed') can now be specified on a per-path basis now, which is especially useful for Absolute Keying Sets, where control over this is often beneficial.
Most of the places where Auto-Keyframing is performed have been tidied up for consistency. I'm sure quite a few issues still exist there, but these I'll clean up over the next few days.
Diffstat (limited to 'release')
-rw-r--r-- | release/scripts/keyingsets/keyingsets_builtins.py | 236 | ||||
-rw-r--r-- | release/scripts/keyingsets/keyingsets_utils.py | 170 | ||||
-rw-r--r-- | release/scripts/modules/bpy/utils.py | 4 |
3 files changed, 408 insertions, 2 deletions
diff --git a/release/scripts/keyingsets/keyingsets_builtins.py b/release/scripts/keyingsets/keyingsets_builtins.py new file mode 100644 index 00000000000..c5417ba3d66 --- /dev/null +++ b/release/scripts/keyingsets/keyingsets_builtins.py @@ -0,0 +1,236 @@ +# Built-In Keying Sets +# None of these Keying Sets should be removed, as these +# are needed by various parts of Blender in order for them +# to work correctly. + +import bpy +from keyingsets_utils import * + +############################### +# Built-In KeyingSets + +# Location +class BUILTIN_KSI_Location(bpy.types.KeyingSetInfo): + bl_idname = "Location" + bl_builtin = True + + # poll - use predefined callback for selected bones/objects + poll = RKS_POLL_selected_items + + # iterator - use callback for selected bones/objects + iterator = RKS_ITER_selected_item + + # generator - use callback for location + generate = RKS_GEN_location + +# Rotation +class BUILTIN_KSI_Rotation(bpy.types.KeyingSetInfo): + bl_idname = "Rotation" + bl_builtin = True + + # poll - use predefined callback for selected bones/objects + poll = RKS_POLL_selected_items + + # iterator - use callback for selected bones/objects + iterator = RKS_ITER_selected_item + + # generator - use callback for location + generate = RKS_GEN_rotation + +# Scale +class BUILTIN_KSI_Scaling(bpy.types.KeyingSetInfo): + bl_idname = "Scaling" + bl_builtin = True + + # poll - use predefined callback for selected bones/objects + poll = RKS_POLL_selected_items + + # iterator - use callback for selected bones/objects + iterator = RKS_ITER_selected_item + + # generator - use callback for location + generate = RKS_GEN_scaling + +# ------------ + +# LocRot +class BUILTIN_KSI_LocRot(bpy.types.KeyingSetInfo): + bl_idname = "LocRot" + bl_builtin = True + + # poll - use predefined callback for selected bones/objects + poll = RKS_POLL_selected_items + + # iterator - use callback for selected bones/objects + iterator = RKS_ITER_selected_item + + # generator - use callback for location + def generate(self, context, ks, data): + # location + RKS_GEN_location(self, context, ks, data) + # rotation + RKS_GEN_rotation(self, context, ks, data) + +# LocScale +class BUILTIN_KSI_LocScale(bpy.types.KeyingSetInfo): + bl_idname = "LocScale" + bl_builtin = True + + # poll - use predefined callback for selected bones/objects + poll = RKS_POLL_selected_items + + # iterator - use callback for selected bones/objects + iterator = RKS_ITER_selected_item + + # generator - use callback for location + def generate(self, context, ks, data): + # location + RKS_GEN_location(self, context, ks, data) + # scale + RKS_GEN_scaling(self, context, ks, data) + +# LocRotScale +class BUILTIN_KSI_LocRotScale(bpy.types.KeyingSetInfo): + bl_idname = "LocRotScale" + bl_builtin = True + + # poll - use predefined callback for selected bones/objects + poll = RKS_POLL_selected_items + + # iterator - use callback for selected bones/objects + iterator = RKS_ITER_selected_item + + # generator - use callback for location + def generate(self, context, ks, data): + # location + RKS_GEN_location(self, context, ks, data) + # rotation + RKS_GEN_rotation(self, context, ks, data) + # scale + RKS_GEN_scaling(self, context, ks, data) + +# RotScale +class BUILTIN_KSI_RotScale(bpy.types.KeyingSetInfo): + bl_idname = "RotScale" + bl_builtin = True + + # poll - use predefined callback for selected bones/objects + poll = RKS_POLL_selected_items + + # iterator - use callback for selected bones/objects + iterator = RKS_ITER_selected_item + + # generator - use callback for location + def generate(self, context, ks, data): + # rotation + RKS_GEN_rotation(self, context, ks, data) + # scaling + RKS_GEN_scaling(self, context, ks, data) + +# ------------ + +# Location +class BUILTIN_KSI_VisualLoc(bpy.types.KeyingSetInfo): + bl_idname = "Visual Location" + bl_builtin = True + + insertkey_visual = True + + # poll - use predefined callback for selected bones/objects + poll = RKS_POLL_selected_items + + # iterator - use callback for selected bones/objects + iterator = RKS_ITER_selected_item + + # generator - use callback for location + generate = RKS_GEN_location + +# Rotation +class BUILTIN_KSI_VisualRot(bpy.types.KeyingSetInfo): + bl_idname = "Visual Rotation" + bl_builtin = True + + insertkey_visual = True + + # poll - use predefined callback for selected bones/objects + poll = RKS_POLL_selected_items + + # iterator - use callback for selected bones/objects + iterator = RKS_ITER_selected_item + + # generator - use callback for location + generate = RKS_GEN_rotation + +# VisualLocRot +class BUILTIN_KSI_VisualLocRot(bpy.types.KeyingSetInfo): + bl_idname = "Visual LocRot" + bl_builtin = True + + insertkey_visual = True + + # poll - use predefined callback for selected bones/objects + poll = RKS_POLL_selected_items + + # iterator - use callback for selected bones/objects + iterator = RKS_ITER_selected_item + + # generator - use callback for location + def generate(self, context, ks, data): + # location + RKS_GEN_location(self, context, ks, data) + # rotation + RKS_GEN_rotation(self, context, ks, data) + +# ------------ + +# Available +class BUILTIN_KSI_Available(bpy.types.KeyingSetInfo): + bl_idname = "Available" + bl_builtin = True + + # poll - use predefined callback for selected objects + # TODO: this should really check whether the selected object (or datablock) + # has any animation data defined yet + poll = RKS_POLL_selected_objects + + # iterator - use callback for selected bones/objects + iterator = RKS_ITER_selected_item + + # generator - use callback for location + generate = RKS_GEN_available + +############################### + +classes = [ + BUILTIN_KSI_Location, + BUILTIN_KSI_Rotation, + BUILTIN_KSI_Scaling, + + BUILTIN_KSI_LocRot, + BUILTIN_KSI_LocScale, + BUILTIN_KSI_LocRotScale, + BUILTIN_KSI_RotScale, + + BUILTIN_KSI_VisualLoc, + BUILTIN_KSI_VisualRot, + BUILTIN_KSI_VisualLocRot, + + BUILTIN_KSI_Available, +] + + +def register(): + register = bpy.types.register + for cls in classes: + register(cls) + + +def unregister(): + unregister = bpy.types.unregister + for cls in classes: + unregister(cls) + +if __name__ == "__main__": + register() + +############################### diff --git a/release/scripts/keyingsets/keyingsets_utils.py b/release/scripts/keyingsets/keyingsets_utils.py new file mode 100644 index 00000000000..78e170c88f9 --- /dev/null +++ b/release/scripts/keyingsets/keyingsets_utils.py @@ -0,0 +1,170 @@ +# 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. + +import bpy + +########################### +# General Utilities + +# Append the specified property name on the the existing path +def path_add_property(path, prop): + if len(path): + return path + "." + prop; + else: + return prop; + +########################### +# Poll Callbacks + +# selected objects +def RKS_POLL_selected_objects(ksi, context): + return context.active_object or len(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 + if (context.active_object) and (context.active_object.mode == 'POSE'): + if context.active_pose_bone or len(context.select_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): + if (context.active_object) and (context.active_object.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) + +########################### +# 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 + try: + adt = id_block.animation_data + except: + # there isn't any animation data available + return; + + # there must also be an active action... + if adt.action is None: + return; + + # for each F-Curve, include an path to key it + # NOTE: we don't need to set the group settings here + for fcu in adt.action.fcurves: + ks.add_path(id_block, fcu.rna_path, array_index=fcu.array_index, entire_array=False) + +# ------ + +# get ID block and based ID path for transform generators +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 = "" + + # data on ID-blocks directly should get grouped by the KeyingSet + grouping = None; + else: + # get the path to the ID-block + path = data.path_to_id() + + try: + # try to use the name of the data element to group the F-Curve + grouping = data.name + except: + # fallback on the KeyingSet name + grouping = 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.add_path(id_block, path, grouping_method='NAMED', group_name=grouping) + else: + ks.add_path(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 == 'AXISANGLE': + 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.add_path(id_block, path, grouping_method='NAMED', group_name=grouping) + else: + ks.add_path(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.add_path(id_block, path, grouping_method='NAMED', group_name=grouping) + else: + ks.add_path(id_block, path) + +########################### +# Un-needed stuff which is here to just shut up the warnings... + +classes = [] + +def register(): + register = bpy.types.register + for cls in classes: + register(cls) + + +def unregister(): + unregister = bpy.types.unregister + for cls in classes: + unregister(cls) + +if __name__ == "__main__": + register() + +########################### diff --git a/release/scripts/modules/bpy/utils.py b/release/scripts/modules/bpy/utils.py index 7787d9ee5fe..5b0599fe25f 100644 --- a/release/scripts/modules/bpy/utils.py +++ b/release/scripts/modules/bpy/utils.py @@ -169,7 +169,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): _loaded[:] = [] for base_path in script_paths(user=False): - for path_subdir in ("ui", "op", "io", "cfg"): + for path_subdir in ("ui", "op", "io", "cfg", "keyingsets"): path = _os.path.join(base_path, path_subdir) if _os.path.isdir(path): sys_path_ensure(path) @@ -179,7 +179,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): user_path = user_script_path() if user_path: - for path_subdir in ("", "ui", "op", "io", "cfg"): + for path_subdir in ("", "ui", "op", "io", "cfg", "keyingsets"): path = _os.path.join(user_path, path_subdir) if _os.path.isdir(path): sys_path_ensure(path) |