import bpy from . import fn ## step 2 : Auto animate the path (with feet selection) and modal to adjust speed def anim_path_from_y_translate(): print(fn.helper()) obj = bpy.context.object if obj.type != 'ARMATURE': return ('ERROR', 'active is not an armature type') # found curve through constraint b = bpy.context.active_pose_bone if not 'shoe' in b.bone.name: return ('ERROR', 'No "shoe" in active bone name\n-> Select foot that has the most reliable contact') curve = None if bpy.context.scene.anim_cycle_settings.path_to_follow: curve = bpy.context.scene.anim_cycle_settings.path_to_follow # if curve is not defined try to track it from constraints on armature if not curve: curve, _const = fn.get_follow_curve_from_armature(obj) if isinstance(curve, str): return curve, _const act = fn.get_obj_action(obj) if not act: return ('ERROR', f'No action active on {obj.name}') # use original action as ref if '_expanded' in act.name: base_act_name = act.name.split('_expanded')[0] base_act = bpy.data.action.get(base_act_name) if base_act: act = base_act print(f'Using for {base_act_name} as reference') else: print(f'No base action found (searching for {base_act_name})') # CHANGE - retiré le int de la frame # keyframes = [int(k.co[0]) for fcu in act.fcurves for k in fcu.keyframe_points] ## calculate offset from bones locy_fcu = None for fcu in act.fcurves: if fcu.data_path.split('"')[1] != b.bone.name: continue if fcu.data_path.split('.')[-1] == 'location' and fcu.array_index == 1: locy_fcu = fcu if not locy_fcu: return ('ERROR', 'Current bone, location.y animation not found') start = None end = None ## based on extreme for k in locy_fcu.keyframe_points: # if k.select_control_point: # based on selection if k.type == 'EXTREME': # using extreme keys. if not start: start = k end = k else: if start is not None: ## means back to other frame type after passed breaskdown we stop break if not start: return ('ERROR', f"No extreme marked frame was found on bone {b.bone.name}.{['x','y','z'][locy_fcu.array_index]}") if start == end: return ('ERROR', 'Seems like only one key was marked as extreme ! Need at least two chained') start_frame = start.co.x start_val = start.co.y end_frame = end.co.x end_val = end.co.y move_frame = end_frame - start_frame # Y positive value (forward) -> move_val = abs(start_val - end_val) print('move_val: ', move_val) length = fn.get_curve_length(curve) steps = length / move_val frame_duration = steps * move_frame ### Clear keyframe before creating new ones # curve.data.animation_data_clear() # too much.. delete only eval_time if curve.data.animation_data and curve.data.animation_data.action: for fcu in curve.data.animation_data.action.fcurves: if fcu.data_path == 'eval_time': curve.data.animation_data.action.fcurves.remove(fcu) break anim_frame = bpy.context.scene.anim_cycle_settings.start_frame curve.data.path_duration = frame_duration curve.data.eval_time = 0 curve.data.keyframe_insert('eval_time', frame=anim_frame)# , options={'INSERTKEY_AVAILABLE'} curve.data.eval_time = frame_duration curve.data.keyframe_insert('eval_time', frame=anim_frame + frame_duration) ## all to linear (will be set to CONSTANT at the moment of sampling) for fcu in curve.data.animation_data.action.fcurves: if fcu.data_path == 'eval_time': for k in fcu.keyframe_points: k.interpolation = 'LINEAR' ## set all to constant # for k in t_fcu.keyframe_points: # k.interpolation = 'CONSTANT' print('end of set_follow_path_anim') class UAC_OT_animate_path(bpy.types.Operator): bl_idname = "anim.animate_path" bl_label = "Animate Path" bl_description = "Select the most representative 'in contact' feet of the cycle" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return context.object and context.object.type == 'ARMATURE' # def invoke(self, context, event): # self.shift = event.shift # return self.execute(context) def execute(self, context): # TODO clear previous animation (keys) if there is any err = anim_path_from_y_translate() if err: self.report({err[0]}, err[1]) if err[0] == 'ERROR': return {"CANCELLED"} return {"FINISHED"} class UAC_OT_adjust_animation_length(bpy.types.Operator): bl_idname = "anim.adjust_animation_length" bl_label = "Adjust Anim speed" bl_description = "Adjust speed\nOnce pressed, move up/down to move animation path last key value" bl_options = {"REGISTER"} # , "UNDO" @classmethod def poll(cls, context): return context.object and context.object.type in ('ARMATURE', 'CURVE') val : bpy.props.FloatProperty(name='End key value') def invoke(self, context, event): # check animation data of curve # self.pref = fn.get_addon_prefs() curve = bpy.context.scene.anim_cycle_settings.path_to_follow if not curve: if context.object.type != 'ARMATURE': self.report({'ERROR'}, 'no curve targeted in "Path" field') return {"CANCELLED"} curve, _const = fn.get_follow_curve_from_armature(context.object) if isinstance(curve, str): self.report({curve}, _const) return {"CANCELLED"} self.act = fn.get_obj_action(curve.data) if not self.act: self.report({'ERROR'}, f'No action on {curve.name} data') return {"CANCELLED"} # if '_expanded' in self.act.name: # self.report({'WARNING'}, f'Action is expanded') self.fcu = None for fcu in self.act.fcurves: if fcu.data_path == 'eval_time': self.fcu = fcu break if not self.fcu or not len(self.fcu.keyframe_points): self.report({'ERROR'}, f'No eval_time animated on {curve.name} data action (or no keys)') return {"CANCELLED"} if len(self.fcu.keyframe_points) > 2: self.report({'WARNING'}, f'{curve.name} eval_time has {len(self.fcu.keyframe_points)} keyframe (should just have 2 to redefine speed)') self.k = self.fcu.keyframe_points[-1] self.val = self.init_ky = self.k.co.y self.init_my = event.mouse_y context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} def modal(self, context, event): # added reduction factor self.val = self.init_ky + ((event.mouse_y - self.init_my) * 0.1) offset = self.val - self.init_ky display_text = f"Path animation end key value {self.val:.3f}, offset {offset:.3f}" context.area.header_text_set(display_text) self.k.co.y = self.val if event.type == 'LEFTMOUSE' and event.value == "PRESS": context.area.header_text_set(None) self.execute(context) return {"FINISHED"} if event.type in ('RIGHTMOUSE', 'ESC') and event.value == "PRESS": self.k.co.y = self.init_ky context.area.header_text_set(None) return {"CANCELLED"} if event.type in ('MIDDLEMOUSE', 'SPACE'): # Mmaybe not mid mouse ? return {'PASS_THROUGH'} return {"RUNNING_MODAL"} def execute(self, context): self.k.co.y = self.val return {"FINISHED"} # def draw(self, context): # layout = self.layout # layout.prop(self, "val") classes=( UAC_OT_animate_path, UAC_OT_adjust_animation_length, ) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls)