diff --git a/anim_offset/support.py b/anim_offset/support.py index 452a04c..efb0230 100644 --- a/anim_offset/support.py +++ b/anim_offset/support.py @@ -84,10 +84,12 @@ def magnet_handlers(scene): for obj in selected_objects: action = getattr(obj.animation_data, 'action', None) + fcurves = utils.curve.get_fcurves_for_action(action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] - for fcurve in getattr(action, 'fcurves', list()): + for fcurve in fcurves_list: if fcurve.data_path.endswith("rotation_mode"): - continue #added exception + continue magnet(context, obj, fcurve) return @@ -103,10 +105,10 @@ def magnet(context, obj, fcurve): return if getattr(fcurve.group, 'name', None) == 'animaide': - return # we don't want to select keys on reference fcurves + return blends_action = bpy.data.actions.get('animaide') - blends_curves = getattr(blends_action, 'fcurves', None) + blends_curves = utils.curve.get_fcurves_for_action(blends_action) delta_y = get_delta(context, obj, fcurve) @@ -116,9 +118,12 @@ def magnet(context, obj, fcurve): factor = 1 elif scene.frame_start <= k.co.x <= scene.frame_end: factor = 1 - elif blends_curves is not None and len(blends_curves) > 0: - blends_curve = blends_curves[0] - factor = blends_curve.evaluate(k.co.x) + elif utils.curve.get_fcurve_count(blends_curves) > 0: + blends_curve = utils.curve.get_first_fcurve(blends_curves) + if blends_curve: + factor = blends_curve.evaluate(k.co.x) + else: + factor = 0 else: factor = 0 @@ -162,11 +167,12 @@ def get_delta(context, obj, fcurve): def add_blends(): """Add a curve with 4 control pints to an action called 'animaide' that would act as a mask for anim_offset""" action = utils.set_animaide_action() - fcurves = getattr(action, 'fcurves', None) - if len(fcurves) == 0: + fcurves = utils.curve.get_fcurves_for_action(action) + if utils.curve.get_fcurve_count(fcurves) == 0: return utils.curve.new('Magnet', 4) else: - return action.fcurves[0] + first = utils.curve.get_first_fcurve(fcurves) + return first if first else utils.curve.new('Magnet', 4) def remove_mask(context): @@ -174,11 +180,13 @@ def remove_mask(context): anim_offset = context.scene.animaide.anim_offset blends_action = bpy.data.actions.get('animaide') - blends_curves = getattr(blends_action, 'fcurves', None) + blends_curves = utils.curve.get_fcurves_for_action(blends_action) anim_offset.mask_in_use = False - if blends_curves is not None and len(blends_curves) > 0: - blends_curves.remove(blends_curves[0]) + if blends_curves is not None and utils.curve.get_fcurve_count(blends_curves) > 0: + first = utils.curve.get_first_fcurve(blends_curves) + if first and blends_curves: + blends_curves.remove(first) # reset_timeline_mask(context) return @@ -189,10 +197,12 @@ def set_blend_values(context): scene = context.scene blends_action = bpy.data.actions.get('animaide') - blends_curves = getattr(blends_action, 'fcurves', None) + blends_curves = utils.curve.get_fcurves_for_action(blends_action) - if blends_curves is not None: - blend_curve = blends_curves[0] + if blends_curves is not None and utils.curve.get_fcurve_count(blends_curves) > 0: + blend_curve = utils.curve.get_first_fcurve(blends_curves) + if blend_curve is None: + return keys = blend_curve.keyframe_points left_blend = scene.frame_preview_start @@ -239,8 +249,10 @@ def add_keys(context): for obj in selected_objects: action = getattr(obj.animation_data, 'action', None) + fcurves = utils.curve.get_fcurves_for_action(action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] - for fcurve in getattr(action, 'fcurves', list()): + for fcurve in fcurves_list: if fcurve.lock: return @@ -327,19 +339,18 @@ def poll(context): def get_anim_offset_globals(context, obj): - """Get global values for the anim_offset""" - anim = obj.animation_data if anim is None: return - if anim.action.fcurves is None: + + fcurves = utils.curve.get_fcurves_for_action(anim.action) + if fcurves is None: return - fcurves = obj.animation_data.action.fcurves - curves = {} + fcurves_list = utils.curve.get_fcurves_list(fcurves) - for fcurve_index, fcurve in fcurves.items(): + for fcurve in fcurves_list: if fcurve.lock is True: continue @@ -349,8 +360,6 @@ def get_anim_offset_globals(context, obj): values = {'x': cur_frame, 'y': cur_frame_y} - curves[fcurve_index]['current_frame'] = values + curves[fcurve.data_path] = {'current_frame': values} global_values[obj.name] = curves - - diff --git a/curve_tools/support.py b/curve_tools/support.py index 3a691e7..50c1438 100755 --- a/curve_tools/support.py +++ b/curve_tools/support.py @@ -20,6 +20,7 @@ ''' import math +import random import bpy # from utils.key import global_values, on_current_frame, get_selected_neigbors, \ @@ -49,12 +50,13 @@ def add_noise(fcurve, strength=0.4, scale=1, phase=0): def add_shear_curve(): """Add a curve with 4 control pints to an action called 'animaide' that would act as a mask for anim_offset""" action = utils.set_animaide_action() - fcurves = getattr(action, 'fcurves', None) + fcurves = utils.curve.get_fcurves_for_action(action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] # fcurves = utils.curve.get_all_fcurves(obj) - if len(fcurves) == 0: + if len(fcurves_list) == 0: return utils.curve.new('Shear', 2, key_interp='VECTOR') else: - return fcurves[0] + return fcurves_list[0] def set_shear_values(left, right): @@ -62,10 +64,11 @@ def set_shear_values(left, right): # scene = context.scene action = bpy.data.actions.get('animaide') - curves = getattr(action, 'fcurves', None) + curves = utils.curve.get_fcurves_for_action(action) + curves_list = utils.curve.get_fcurves_list(curves) if curves else [] - if curves is not None: - curve = curves[0] + if curves_list: + curve = curves_list[0] keys = curve.keyframe_points # left_blend = scene.frame_preview_start @@ -91,7 +94,8 @@ def shear(self, direction): else: direction = 0 shear_action = utils.set_animaide_action() - shear_curves = getattr(shear_action, 'fcurves', None) + shear_curves = utils.curve.get_fcurves_for_action(shear_action) + shear_curves_list = utils.curve.get_fcurves_list(shear_curves) if shear_curves else [] shear_curve = add_shear_curve() local_y = self.right_neighbor['y'] - self.left_neighbor['y'] set_shear_values(self.left_neighbor['x'], self.right_neighbor['x']) @@ -105,7 +109,8 @@ def shear(self, direction): k = self.fcurve.keyframe_points[index] k.co_ui.y = self.original_keys[index]['y'] + shear_curve.evaluate(k.co.x) * factor - shear_curves.remove(shear_curve) + if shear_curves_list: + shear_curves_list[0].remove(shear_curve) def set_min_max_values(self, context): @@ -141,10 +146,15 @@ def to_execute(self, context, function, *args): if not action: continue - self.fcurves = action.fcurves + self.fcurves = utils.curve.get_fcurves_for_action(action) + fcurves_list = utils.curve.get_fcurves_list(self.fcurves) if self.fcurves else [] for fcurve_i in global_fcurves: - self.fcurve = self.fcurves[fcurve_i] + # Access by index - need to handle Blender 5.0 compatibility + if fcurve_i < len(fcurves_list): + self.fcurve = fcurves_list[fcurve_i] + else: + continue self.global_fcurve = global_fcurves[fcurve_i] @@ -194,8 +204,14 @@ def reset_original(): if not action: continue + fcurves = utils.curve.get_fcurves_for_action(action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] + for fcurve_i in global_fcurves: - fcurve = action.fcurves[fcurve_i] + if fcurve_i < len(fcurves_list): + fcurve = fcurves_list[fcurve_i] + else: + continue global_fcurve = global_fcurves[fcurve_i] @@ -223,6 +239,58 @@ def reset_original(): return +# Function to determine if an fcurve is locked +def is_fcurve_locked(obj, fcurve): + if fcurve.lock or fcurve.mute: + return True + if not obj: + return False + print(fcurve.data_path) + + # Check if the fcurve is related to a pose bone + if fcurve.data_path.startswith("pose.bones"): + bone_name = fcurve.data_path.split('"')[1] # Extract the bone name + bone = obj.pose.bones.get(bone_name) + + if not bone: + return False + + # Determine the specific property (location, rotation, scale) + if "location" in fcurve.data_path: + if fcurve.array_index < len(bone.lock_location): + return bone.lock_location[fcurve.array_index] + elif "rotation_quaternion" in fcurve.data_path: + if fcurve.array_index == 0: + return bone.lock_rotation_w # Quaternion W component + elif fcurve.array_index - 1 < len(bone.lock_rotation): + return bone.lock_rotation[fcurve.array_index - 1] # Quaternion X, Y, Z components + elif "rotation_euler" in fcurve.data_path: + if fcurve.array_index < len(bone.lock_rotation): + return bone.lock_rotation[fcurve.array_index] # Euler X, Y, Z components + elif "scale" in fcurve.data_path: + if fcurve.array_index < len(bone.lock_scale): + return bone.lock_scale[fcurve.array_index] + else: + # For object level properties + if fcurve.data_path.startswith("location"): + if fcurve.array_index < len(obj.lock_location): + return obj.lock_location[fcurve.array_index] + elif "rotation_quaternion" in fcurve.data_path: + if fcurve.array_index == 0: + return obj.lock_rotation_w # Quaternion W component + elif fcurve.array_index - 1 < len(obj.lock_rotation): + return obj.lock_rotation[fcurve.array_index - 1] # Quaternion X, Y, Z components + elif "rotation_euler" in fcurve.data_path: + if fcurve.array_index < len(obj.lock_rotation): + return obj.lock_rotation[fcurve.array_index] # Euler X, Y, Z components + elif fcurve.data_path.startswith("scale"): + if fcurve.array_index < len(obj.lock_scale): + return obj.lock_scale[fcurve.array_index] + + return False + + + def get_globals(context): """Gets all the global values needed to work with the curve_tools""" @@ -250,18 +318,27 @@ def get_globals(context): continue action_type = action_data['type'] - fcurves = action.fcurves + fcurves = utils.curve.get_fcurves_for_action(action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] - if not fcurves: + if not fcurves_list: continue global_curve_data = {} # Allows selection in dope sheet to work in graph editor - for fcurve_index, fcurve in fcurves.items(): - fcurve.select = True + for fcurve_index, fcurve in enumerate(fcurves_list): + locked = is_fcurve_locked(obj, fcurve) + if not locked: + fcurve.select = True + else: + fcurve.select = False + for keyframe in fcurve.keyframe_points: + keyframe.select_control_point = False + keyframe.select_left_handle = False + keyframe.select_right_handle = False - for fcurve_index, fcurve in fcurves.items(): + for fcurve_index, fcurve in enumerate(fcurves_list): if not utils.curve.valid_fcurve(context, obj, fcurve, action_type=action_type): continue @@ -465,5 +542,3 @@ def linear_y(key, left_neighbor, right_neighbor): adjacent = key.co_ui.x - left_neighbor['x'] oposite = tangent * adjacent return left_neighbor['y'] + oposite - - diff --git a/key_manager/ops.py b/key_manager/ops.py index c754b63..e01920d 100755 --- a/key_manager/ops.py +++ b/key_manager/ops.py @@ -373,8 +373,10 @@ def execute(self, context): objects = context.selected_objects for obj in objects: action = obj.animation_data.action + fcurves = utils.curve.get_fcurves_for_action(action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] - for curve in action.fcurves: + for curve in fcurves_list: if curve.select is not True: continue noise = curve.modifiers.new('NOISE') @@ -398,7 +400,7 @@ def poll(cls, context): def execute(self, context): obj = context.object - fcurves = obj.animation_data.action.fcurves + fcurves = utils.curve.get_fcurves_for_action(obj.animation_data.action) utils.curve.create_path(context, fcurves) diff --git a/key_manager/support.py b/key_manager/support.py index 22f7263..99c7276 100755 --- a/key_manager/support.py +++ b/key_manager/support.py @@ -47,9 +47,10 @@ def set_type(context, key_type): some_selected_key = utils.key.some_selected_key(context, obj) - fcurves = obj.animation_data.action.fcurves + fcurves = utils.curve.get_fcurves_for_action(obj.animation_data.action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] - for fcurve in fcurves: + for fcurve in fcurves_list: if not utils.curve.valid_fcurve(context, obj, fcurve): continue @@ -133,9 +134,10 @@ def can_move(l_limit, r_limit, most_l, most_r): if not utils.curve.valid_obj(context, obj): continue - fcurves = obj.animation_data.action.fcurves + fcurves = utils.curve.get_fcurves_for_action(obj.animation_data.action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] - for fcurve in fcurves: + for fcurve in fcurves_list: if not utils.curve.valid_fcurve(context, obj, fcurve): continue @@ -215,9 +217,10 @@ def displace_keys(anchor_frame): some_selected_key = utils.key.some_selected_key(context, obj) - fcurves = obj.animation_data.action.fcurves + fcurves = utils.curve.get_fcurves_for_action(obj.animation_data.action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] - for fcurve in fcurves: + for fcurve in fcurves_list: if not utils.curve.valid_fcurve(context, obj, fcurve): continue @@ -252,8 +255,10 @@ def set_handles_type(context, act_on='SELECTION', handle_type='NONE', check_ui=T continue action = obj.animation_data.action + fcurves = utils.curve.get_fcurves_for_action(action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] - for fcurve in action.fcurves: + for fcurve in fcurves_list: if not utils.curve.valid_fcurve(context, obj, fcurve, check_ui=check_ui): continue @@ -277,7 +282,7 @@ def set_handles_type(context, act_on='SELECTION', handle_type='NONE', check_ui=T handle_type=handle_type) elif act_on == 'ALL': - for index, key in fcurve.keyframe_points.items(): + for key in fcurve.keyframe_points: # set_handles_interp(context, interp=key_tweak.interp) handle_type_asignment(key, **kwargs) elif act_on == 'FIRST': @@ -305,9 +310,10 @@ def select_key_parts(context, left=False, right=False, point=False): if not utils.curve.valid_obj(context, obj): continue - fcurves = obj.animation_data.action.fcurves + fcurves = utils.curve.get_fcurves_for_action(obj.animation_data.action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] - for fcurve_index, fcurve in fcurves.items(): + for fcurve in fcurves_list: if not utils.curve.valid_fcurve(context, obj, fcurve): continue @@ -359,8 +365,10 @@ def set_handles_interp(context, act_on='SELECTION', interp='NONE', easing='NONE' continue action = obj.animation_data.action + fcurves = utils.curve.get_fcurves_for_action(action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] - for fcurve in action.fcurves: + for fcurve in fcurves_list: if not utils.curve.valid_fcurve(context, obj, fcurve, check_ui=check_ui): continue @@ -372,7 +380,7 @@ def set_handles_interp(context, act_on='SELECTION', interp='NONE', easing='NONE' assign_interp(key, interp, easing, strength) if act_on == 'ALL': - for index, key in fcurve.keyframe_points.items(): + for key in fcurve.keyframe_points: assign_interp(key, interp, easing, strength) fcurve.keyframe_points.sort() @@ -472,8 +480,10 @@ def select_by_type(context, kind, selection=True): continue action = obj.animation_data.action + fcurves = utils.curve.get_fcurves_for_action(action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] - for fcurve in action.fcurves: + for fcurve in fcurves_list: if not utils.curve.valid_fcurve(context, obj, fcurve): continue @@ -482,7 +492,7 @@ def select_by_type(context, kind, selection=True): keys = fcurve.keyframe_points - for index, key in keys.items(): + for key in keys: if key.type == kind: # key.select_control_point = selection handle_buttons(context, key, selection, selection, selection) diff --git a/prefe.py b/prefe.py index 8ee0042..9b13a33 100644 --- a/prefe.py +++ b/prefe.py @@ -33,7 +33,8 @@ def add_key_manager_header(): global key_manager_pref for cls in key_manager.ui.header_classes: bpy.utils.register_class(cls) - bpy.types.TIME_MT_editor_menus.append(key_manager.ui.draw_key_manager) + if bpy.app.version < (5, 0, 0): + bpy.types.TIME_MT_editor_menus.append(key_manager.ui.draw_key_manager) bpy.types.DOPESHEET_MT_editor_menus.append(key_manager.ui.draw_key_manager) bpy.types.GRAPH_MT_editor_menus.append(key_manager.ui.draw_key_manager) key_manager_pref = 'HEADERS' @@ -43,7 +44,8 @@ def remove_key_manager_header(): global key_manager_pref for cls in key_manager.ui.header_classes: bpy.utils.unregister_class(cls) - bpy.types.TIME_MT_editor_menus.remove(key_manager.ui.draw_key_manager) + if bpy.app.version < (5, 0, 0): + bpy.types.TIME_MT_editor_menus.remove(key_manager.ui.draw_key_manager) bpy.types.DOPESHEET_MT_editor_menus.remove(key_manager.ui.draw_key_manager) bpy.types.GRAPH_MT_editor_menus.remove(key_manager.ui.draw_key_manager) key_manager_pref = '' @@ -79,7 +81,8 @@ def remove_anim_offset_panel(): def add_anim_offset_header(): global anim_offset_pref - bpy.types.TIME_MT_editor_menus.append(anim_offset.ui.draw_anim_offset) + if bpy.app.version < (5, 0, 0): + bpy.types.TIME_MT_editor_menus.append(anim_offset.ui.draw_anim_offset) bpy.types.DOPESHEET_MT_editor_menus.append(anim_offset.ui.draw_anim_offset_mask) bpy.types.GRAPH_MT_editor_menus.append(anim_offset.ui.draw_anim_offset_mask) anim_offset_pref = 'HEADERS' @@ -87,7 +90,8 @@ def add_anim_offset_header(): def remove_anim_offset_header(): global anim_offset_pref - bpy.types.TIME_MT_editor_menus.remove(anim_offset.ui.draw_anim_offset) + if bpy.app.version < (5, 0, 0): + bpy.types.TIME_MT_editor_menus.remove(anim_offset.ui.draw_anim_offset) bpy.types.GRAPH_MT_editor_menus.remove(anim_offset.ui.draw_anim_offset_mask) bpy.types.DOPESHEET_MT_editor_menus.remove(anim_offset.ui.draw_anim_offset_mask) anim_offset_pref = '' diff --git a/utils/curve.py b/utils/curve.py index e39aa45..3fa7e9a 100755 --- a/utils/curve.py +++ b/utils/curve.py @@ -27,6 +27,88 @@ user_scene_range = {} +def _get_or_create_default_slot_layer_strip(action): + if not hasattr(action, 'slots'): + return None + + if len(action.slots) == 0: + return None + + slot = action.slots[0] + + if not hasattr(action, 'layers') or len(action.layers) == 0: + layer = action.layers.new() + else: + layer = action.layers[0] + + if not hasattr(layer, 'strips') or len(layer.strips) == 0: + strip = layer.strips.new(type='KEYFRAME') + else: + strip = layer.strips[0] + + return slot, strip + + +def get_fcurves_for_action(action): + if action is None: + return None + + fcurves = getattr(action, 'fcurves', None) + if fcurves is not None: + return fcurves + + if bpy.app.version[0] >= 5: + result = _get_or_create_default_slot_layer_strip(action) + if result: + slot, strip = result + channelbag = strip.channelbag(slot, ensure=True) + return channelbag.fcurves if channelbag else None + + return None + + +def get_fcurves_list(fcurves_collection): + if fcurves_collection is None: + return [] + + try: + return list(fcurves_collection) + except TypeError: + try: + return [f for _, f in fcurves_collection.items()] + except (TypeError, AttributeError): + return [] + + +def get_fcurve_count(fcurves_collection): + if fcurves_collection is None: + return 0 + + try: + return len(fcurves_collection) + except TypeError: + return len(get_fcurves_list(fcurves_collection)) + + +def get_first_fcurve(fcurves_collection): + if fcurves_collection is None: + return None + + try: + return fcurves_collection[0] + except (TypeError, KeyError, IndexError): + fcurves_list = get_fcurves_list(fcurves_collection) + return fcurves_list[0] if fcurves_list else None + + +def new_fcurve(fcurves, data_path, index, group_name_value): + """Create new fcurve with correct parameter name for Blender version""" + if bpy.app.version < (5, 0, 0): + return fcurves.new(data_path=data_path, index=index, action_group=group_name_value) + else: + return fcurves.new(data_path=data_path, index=index, group_name=group_name_value) + + def add_curve3d(context, name, key_amount=0): curve_data = bpy.data.curves.new(name, 'CURVE') spline = curve_data.splines.new('BEZIER') @@ -38,11 +120,13 @@ def add_curve3d(context, name, key_amount=0): def new(action_group_name, keys_to_add, key_interp='AUTO_CLAMPED', color=(1, 1, 1)): - """Adds and fcuve in the 'animaide' action""" - action = utils.set_animaide_action() + fcurves = get_fcurves_for_action(action) + + if fcurves is None: + return None - blends_curve = action.fcurves.new(data_path=group_name, index=0, action_group=action_group_name) + blends_curve = new_fcurve(fcurves, data_path=group_name, index=0, group_name_value=action_group_name) blends_curve.color_mode = 'CUSTOM' blends_curve.color = color @@ -53,13 +137,10 @@ def new(action_group_name, keys_to_add, key_interp='AUTO_CLAMPED', color=(1, 1, k.handle_left_type = key_interp k.handle_right_type = key_interp - # bpy.ops.transform.translate(value=(0, 0, 0)) - blends_curve.lock = True blends_curve.select = True blends_curve.keyframe_points.sort() blends_curve.keyframe_points.handles_recalc() - # utils.key.update_keyframe_points(context) return blends_curve @@ -140,40 +221,38 @@ def get_selected(fcurves): def get_all_fcurves(obj): trans_action = obj.animation_data.action - trans_fcurves = getattr(trans_action, 'fcurves', None) - if trans_fcurves: - trans_fcurves = trans_fcurves.items() - else: - trans_fcurves = [] + trans_fcurves = get_fcurves_for_action(trans_action) + trans_fcurves = list(trans_fcurves) if trans_fcurves else [] if obj.type != 'ARMATURE': shapes_action = obj.data.shape_keys.animation_data.action - shapes_fcurves = getattr(shapes_action, 'fcurves', None) - if shapes_fcurves: - shapes_fcurves = shapes_fcurves.items() - else: - shapes_fcurves = [] + shapes_fcurves = get_fcurves_for_action(shapes_action) + shapes_fcurves = list(shapes_fcurves) if shapes_fcurves else [] return trans_fcurves + shapes_fcurves else: return trans_fcurves def remove_helpers(objects): - """Remove the all the helper curves that have been added to an object action""" - for obj in objects: action = obj.animation_data.action + fcurves = get_fcurves_for_action(action) - for fcurve in action.fcurves: # delete the first of the clones left + if fcurves is None: + continue + + fcurves_list = get_fcurves_list(fcurves) + for fcurve in fcurves_list: if getattr(fcurve.group, 'name', None) == group_name: - action.fcurves.remove(fcurve) + fcurves.remove(fcurve) def get_slope(fcurve): """Gets the slope of a curve at a specific range""" selected_keys = utils.key.get_selected_index(fcurve) first_key, last_key = utils.key.first_and_last_selected(fcurve, selected_keys) - slope = (first_key.co.y**2 - last_key.co.y**2) / (first_key.co.x**2 - last_key.co.x**2) + slope = (first_key.co.y**2 - last_key.co.y**2) / \ + (first_key.co.x**2 - last_key.co.x**2) return slope @@ -186,10 +265,12 @@ def add_cycle(fcurve, before='MIRROR', after='MIRROR'): def duplicate(fcurve, selected_keys=True, before='NONE', after='NONE', lock=False): - """Duploicates an fcurve""" - action = fcurve.id_data - index = len(action.fcurves) + fcurves = get_fcurves_for_action(action) + if fcurves is None: + return None + + index = get_fcurve_count(fcurves) if selected_keys: selected_keys = get_selected(fcurve) @@ -198,7 +279,7 @@ def duplicate(fcurve, selected_keys=True, before='NONE', after='NONE', lock=Fals clone_name = '%s.%d.clone' % (fcurve.data_path, fcurve.array_index) - dup = action.fcurves.new(data_path=clone_name, index=index, action_group=group_name) + dup = new_fcurve(fcurves, data_path=clone_name, index=index, group_name_value=group_name) dup.keyframe_points.add(len(selected_keys)) dup.color_mode = 'CUSTOM' dup.color = (0, 0, 0) @@ -206,8 +287,12 @@ def duplicate(fcurve, selected_keys=True, before='NONE', after='NONE', lock=Fals dup.lock = lock dup.select = False - action.groups[group_name].lock = lock - action.groups[group_name].color_set = 'THEME10' + # Blender < 5.0 has action groups; Blender 5.0+ uses slots/layers system + if bpy.app.version < (5, 0, 0): + action = fcurve.id_data + if hasattr(action, 'groups') and group_name in action.groups: + action.groups[group_name].lock = lock + action.groups[group_name].color_set = 'THEME10' for i, (index, key) in enumerate(selected_keys): dup.keyframe_points[i].co = key.co @@ -216,7 +301,6 @@ def duplicate(fcurve, selected_keys=True, before='NONE', after='NONE', lock=Fals dup.keyframe_points.sort() dup.keyframe_points.handles_recalc() - return dup @@ -228,7 +312,7 @@ def duplicate_from_data(fcurves, global_fcurve, new_data_path, before='NONE', af every_key = global_fcurve['every_key'] original_keys = global_fcurve['original_keys'] - dup = fcurves.new(data_path=new_data_path, index=index, action_group=group_name) + dup = new_fcurve(fcurves, data_path=new_data_path, index=index, group_name_value=group_name) dup.keyframe_points.add(len(every_key)) dup.color_mode = 'CUSTOM' dup.color = (0, 0, 0) @@ -236,9 +320,12 @@ def duplicate_from_data(fcurves, global_fcurve, new_data_path, before='NONE', af dup.lock = lock dup.select = False - action = fcurves.id_data - action.groups[group_name].lock = lock - action.groups[group_name].color_set = 'THEME10' + # Blender < 5.0 has action groups; Blender 5.0+ uses slots/layers system + if bpy.app.version < (5, 0, 0): + action = fcurves.id_data + if hasattr(action, 'groups') and group_name in action.groups: + action.groups[group_name].lock = lock + action.groups[group_name].color_set = 'THEME10' i = 0 @@ -257,14 +344,17 @@ def duplicate_from_data(fcurves, global_fcurve, new_data_path, before='NONE', af def add_clone(objects, cycle_before='NONE', cycle_after="NONE", selected_keys=False): - """Create an fcurve clone""" - for obj in objects: - fcurves = obj.animation_data.action.fcurves + action = obj.animation_data.action + fcurves = get_fcurves_for_action(action) + + if fcurves is None: + continue - for fcurve in fcurves: + fcurves_list = get_fcurves_list(fcurves) + for fcurve in fcurves_list: if getattr(fcurve.group, 'name', None) == group_name: - continue # we don't want to add to the list the helper curves we have created + continue if fcurve.hide or not fcurve.select: continue @@ -276,59 +366,66 @@ def add_clone(objects, cycle_before='NONE', cycle_after="NONE", selected_keys=Fa def remove_clone(objects): - """Removes an fcurve clone""" - for obj in objects: action = obj.animation_data.action + fcurves = get_fcurves_for_action(action) + + if fcurves is None: + continue animaide = bpy.context.scene.animaide aclones = animaide.clone_data.clones clones_n = len(aclones) - blender_n = len(action.fcurves) - clones_n + fcurves_list = get_fcurves_list(fcurves) + blender_n = len(fcurves_list) - clones_n for n in range(clones_n): - maybe_clone = action.fcurves[blender_n] # delete the first of the clones left - if 'clone' in maybe_clone.data_path: - clone = maybe_clone - action.fcurves.remove(clone) - aclones.remove(0) + if blender_n + n < len(fcurves_list): + maybe_clone = fcurves_list[blender_n + n] + if 'clone' in maybe_clone.data_path: + fcurves.remove(maybe_clone) + aclones.remove(0) def move_clone(objects): - """Moves clone fcurve in time""" - for obj in objects: action = obj.animation_data.action + fcurves = get_fcurves_for_action(action) + + if fcurves is None: + continue + + fcurves_list = get_fcurves_list(fcurves) animaide = bpy.context.scene.animaide aclone_data = animaide.clone_data aclones = aclone_data.clones move_factor = aclone_data.move_factor + for aclone in aclones: - clone = action.fcurves[aclone.fcurve.index] - fcurve = action.fcurves[aclone.original_fcurve.index] - selected_keys = utils.key.get_selected_index(fcurve) - key1, key2 = utils.key.first_and_last_selected(fcurve, selected_keys) - amount = abs(key2.co.x - key1.co.x) - for key in clone.keyframe_points: - key.co.x = key.co.x + (amount * move_factor) + if aclone.fcurve.index < len(fcurves_list) and aclone.original_fcurve.index < len(fcurves_list): + clone = fcurves_list[aclone.fcurve.index] + fcurve = fcurves_list[aclone.original_fcurve.index] + selected_keys = utils.key.get_selected_index(fcurve) + key1, key2 = utils.key.first_and_last_selected(fcurve, selected_keys) + amount = abs(key2.co.x - key1.co.x) + for key in clone.keyframe_points: + key.co.x = key.co.x + (amount * move_factor) - clone.keyframe_points.sort() - clone.keyframe_points.handles_recalc() + clone.keyframe_points.sort() + clone.keyframe_points.handles_recalc() - utils.key.attach_selection_to_fcurve(fcurve, clone, is_gradual=False) + utils.key.attach_selection_to_fcurve( + fcurve, clone, is_gradual=False) - fcurve.keyframe_points.sort() - fcurve.keyframe_points.handles_recalc() + fcurve.keyframe_points.sort() + fcurve.keyframe_points.handles_recalc() def valid_anim(obj): - anim = obj.animation_data action = getattr(anim, 'action', None) - fcurves = getattr(action, 'fcurves', None) - - return fcurves + return get_fcurves_for_action(action) is not None def valid_obj(context, obj, check_ui=True): @@ -346,11 +443,21 @@ def valid_obj(context, obj, check_ui=True): return True +def are_frames_selected_in_fcurve(fcurve): + for keyframe in fcurve.keyframe_points: + if keyframe.select_control_point: + return True + return False + + def valid_fcurve(context, obj, fcurve, action_type='transfrom_action', check_ui=True): if not fcurve: return False + if not are_frames_selected_in_fcurve(fcurve): + return False + try: if action_type == 'transfrom_action': prop = obj.path_resolve(fcurve.data_path) @@ -406,12 +513,20 @@ def valid_fcurve(context, obj, fcurve, action_type='transfrom_action', check_ui= if bone.hide: return False + if bpy.app.version[0] >= 5: + # Blender 5.0+: use pose bone selection state + pose_bone = obj.pose.bones.get(bone_name) if bone else None + bone_selected = pose_bone.select if pose_bone else False + else: + # Blender < 5.0: data-bone had selection properties + bone_selected = bone.select + if context.area.type == 'VIEW_3D': - if not bone.select: + if not bone_selected: return False else: only_selected = context.space_data.dopesheet.show_only_selected - if only_selected and not bone.select: + if only_selected and not bone_selected: return False # if getattr(fcurve.group, 'name', None) == utils.curve.group_name: diff --git a/utils/general.py b/utils/general.py index 480ad03..224ab67 100644 --- a/utils/general.py +++ b/utils/general.py @@ -208,7 +208,11 @@ def set_bar_color(): graph_color = bpy.context.preferences.themes[0].graph_editor.space.header[:] nla_color = bpy.context.preferences.themes[0].nla_editor.space.header[:] - h = bpy.context.preferences.themes[0].graph_editor.preview_range + # preview_range was removed in Blender 5.0, use space header color instead + if bpy.app.version < (5, 0, 0): + h = bpy.context.preferences.themes[0].graph_editor.preview_range + else: + h = bpy.context.preferences.themes[0].graph_editor.space.header highlight = (h[0]*.9, h[1]*.9, h[2]*.9, 1) bpy.context.preferences.use_preferences_save = False bpy.context.preferences.themes[0].dopesheet_editor.space.header = highlight diff --git a/utils/key.py b/utils/key.py index 03aeda6..3ec2b9d 100755 --- a/utils/key.py +++ b/utils/key.py @@ -177,9 +177,10 @@ def selected_bounding_box(context, objects, keys_selected=True): if not utils.curve.valid_obj(context, obj): continue - fcurves = obj.animation_data.action.fcurves + fcurves = utils.curve.get_fcurves_for_action(obj.animation_data.action) + fcurves_list = utils.curve.get_fcurves_list(fcurves) if fcurves else [] - for fcurve in fcurves: + for fcurve in fcurves_list: if not utils.curve.valid_fcurve(context, obj, fcurve): continue if keys_selected: