Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c415fa8
remove timeline support
augmero Dec 20, 2025
e440d95
add fcurve action slots / strips / layers / channelbags
augmero Dec 20, 2025
50dd2c8
use new fcurve functions
augmero Dec 20, 2025
5f105c6
Refactor fcurve handling with utility functions
augmero Dec 20, 2025
e7c3b51
Refactor fcurve handling with utility functions
augmero Dec 20, 2025
f207946
Refactor fcurves handling in ops.py
augmero Dec 20, 2025
671da9a
Adjust graph editor color logic for Blender 5.0
augmero Dec 20, 2025
d923f8c
Refactor fcurve retrieval for animation data
augmero Dec 20, 2025
57e2795
Update bone selection logic for Blender versioning
augmero Dec 21, 2025
324544f
Simplify Blender version check for bone selection
augmero Dec 21, 2025
0c54f6b
updates to duplicate fcurve functions
augmero Dec 21, 2025
a5292ab
Add bounds checking for lock properties in support.py
augmero Dec 21, 2025
9bf5d82
Blender 5.1 compatibility fixes and audit
augmero Mar 31, 2026
8a5a6ad
Fix TimelineMarker IDProperty error in Blender 5.0+
augmero Mar 31, 2026
1c24eb5
Update audit doc: add TimelineMarker fix, mark all items complete
augmero Mar 31, 2026
469dbbe
Fix ActionLayers.new() missing required name parameter (Blender 5.0+)
augmero Mar 31, 2026
aa909a2
Update audit doc: document ActionLayers.new() fix, mark complete
augmero Mar 31, 2026
e0cd17b
Fix TimelineMarker IDProperty error - restore name-based marker lookup
augmero Mar 31, 2026
8cd267f
Fix shear crash and dope sheet unselected-key mutation
augmero Mar 31, 2026
318fea3
Add Randomize tool, remove debug print
augmero Mar 31, 2026
12a0a13
Randomize: apply noise on both left and right drag
augmero Mar 31, 2026
3e5fbe5
Fix key_manager Blender 5.1 breakage
augmero Mar 31, 2026
23efa0a
Code quality sweep: remove debug prints, fix shape_keys crash
augmero Mar 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"name": "AnimAide",
"description": "Helpful tools to manipulate keys on f-curves",
"author": "Ares Deveaux",
"version": (1, 0, 38),
"blender": (2, 93, 0),
"version": (1, 1, 0),
"blender": (5, 1, 0),
"location": "Graph Editor - Dope Sheet - Timeline - 3D View - sidebar and menu bar",
"warning": "This addon is still in development.",
"category": "Animation",
Expand Down Expand Up @@ -63,7 +63,6 @@ def load_post_handler(scene):
# if support.magnet_handlers in bpy.app.handlers.depsgraph_update_post:
# bpy.app.handlers.depsgraph_update_post.remove(support.magnet_handlers)
utils.remove_message()
print('init')


def register():
Expand Down
11 changes: 4 additions & 7 deletions anim_offset/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,20 @@ def modal(self, context, event):
self.leftmouse = True
elif event.value == 'RELEASE':
self.leftmouse = False
print('LEFTMOUSE')

if event.type == 'MOUSEMOVE':
print('LEFTMOUSE: ', self.leftmouse)
print('MOUSEMOVE')

if self.leftmouse:
print('leftmouse')
pass

if event.shift and self.leftmouse:
print('shift')
pass

elif event.alt and self.leftmouse:
print('alt')
pass

elif event.ctrl and self.leftmouse:
print('ctrl')
pass

elif event.type in {'RIGHTMOUSE', 'ESC'}:
return {'CANCELLED'}
Expand Down
61 changes: 35 additions & 26 deletions anim_offset/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand All @@ -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

Expand Down Expand Up @@ -162,23 +167,26 @@ 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):
"""Removes the fcurve and action that are been used as a mask for anim_offset"""

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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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


48 changes: 48 additions & 0 deletions curve_tools/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import bpy
import os
import math
import random

# import utils.general
# import utils.key
Expand Down Expand Up @@ -928,6 +929,52 @@ def execute(self, context):
return support.to_execute(self, context, self.tool, context)


class ANIMAIDE_OT_randomize(Operator, ANIMAIDE_OT):
"""Apply random value offsets to selected keys."""

bl_idname = "anim.aide_randomize"
bl_label = "Randomize"

tool_type = 'RANDOMIZE'

def tool(self, context):

if self.selected_keys:
if self.op_context == 'INVOKE_DEFAULT':
context.window.workspace.status_text_set(self.display_info)

tool = context.scene.animaide.tool
factor = utils.clamp(self.factor, self.min_value, self.max_value)

if factor != 0:
clone = utils.curve.duplicate_from_data(self.fcurves,
self.global_fcurve,
'animaide')

phase = self.animaide.tool.noise_phase + self.noise_steps
scale = self.animaide.tool.noise_scale

# Randomize phase per execution for varied results
phase = phase * random.uniform(-1, 1)

support.add_noise(clone, strength=1, scale=scale, phase=phase)

for index in self.selected_keys:
k = self.fcurve.keyframe_points[index]

delta = clone.evaluate(k.co.x) - self.original_keys[index]['y']
new_value = self.original_keys[index]['y'] + delta * factor
if tool.sticky_handles and context.area.type == 'GRAPH_EDITOR':
k.co.y = new_value
else:
k.co_ui.y = new_value

self.fcurves.remove(clone)

def execute(self, context):
return support.to_execute(self, context, self.tool, context)


class ANIMAIDE_OT_scale_left(Operator, ANIMAIDE_OT):
"""Increase or decrease the value of selected or current keys \n""" \
"""in relationship to the left neighboring one."""
Expand Down Expand Up @@ -1275,6 +1322,7 @@ def execute(self, context):
ANIMAIDE_OT_scale_right,
ANIMAIDE_OT_smooth,
ANIMAIDE_OT_wave_noise,
ANIMAIDE_OT_randomize,
ANIMAIDE_OT_time_offset,
ANIMAIDE_OT_shear_left,
ANIMAIDE_OT_shear_right,
Expand Down
3 changes: 2 additions & 1 deletion curve_tools/props.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
('TWEEN', 'Tween', 'Sets key value using neighbors as reference', '', 16),

('SMOOTH', 'Smooth', 'Smooths out fcurve keys', '', 17),
('WAVE_NOISE', 'Wave-Noise', 'add wave or random values to keys', '', 18)]
('WAVE_NOISE', 'Wave-Noise', 'add wave or random values to keys', '', 18),
('RANDOMIZE', 'Randomize', 'Apply random value offsets to selected keys', '', 19)]


menu_items_3d = [('BLEND_FRAME', 'Blend Frame', 'From current to set frames', '', 1),
Expand Down
Loading