fix anim path and bake - add custom axis pinning
1.7.0 - added: `Custom Pinning` Possibility to pin selectively lobc/rot x/y/z components - fixed: bug on baking, some keys can be duplicated on same frame after (auto-clean) - fixed: bug on animate path, when a channel on reference bone is not animated
This commit is contained in:
		
							parent
							
								
									b698f003d9
								
							
						
					
					
						commit
						a1d7aff92c
					
				| @ -1,5 +1,11 @@ | |||||||
| # Changelog | # Changelog | ||||||
| 
 | 
 | ||||||
|  | 1.7.0 | ||||||
|  | 
 | ||||||
|  | - added: `Custom Pinning` Possibility to pin selectively lobc/rot x/y/z components | ||||||
|  | - fixed: bug on baking, some keys can be duplicated on same frame after (auto-clean) | ||||||
|  | - fixed: bug on animate path, when a channel on reference bone is not animated | ||||||
|  | 
 | ||||||
| 1.6.1 | 1.6.1 | ||||||
| 
 | 
 | ||||||
| - changed: `on select` options using `pin feet` target selected bones instead (wuthout the option only target bones with `foot` in name) | - changed: `on select` options using `pin feet` target selected bones instead (wuthout the option only target bones with `foot` in name) | ||||||
|  | |||||||
| @ -13,11 +13,12 @@ def get_bone_transform_at_frame(b, act, frame): | |||||||
|         for i in range(3): |         for i in range(3): | ||||||
|             f = act.fcurves.find(f'pose.bones["{b.name}"].{channel}', index=i) |             f = act.fcurves.find(f'pose.bones["{b.name}"].{channel}', index=i) | ||||||
|             if not f: |             if not f: | ||||||
|                 # print(frame, channel, 'not animated ! using current value') # Dbg |                 print(frame, channel, 'Not animated ! Using current value') # Dbg | ||||||
|                 chan_list.append(getattr(b, channel)) # get current value since not animated |                 chan_list.append(getattr(b, channel)[i]) # get current value since not animated | ||||||
|                 continue |                 continue | ||||||
|             chan_list.append(f.evaluate(frame)) |             chan_list.append(f.evaluate(frame)) | ||||||
|          |          | ||||||
|  |         # print('chan_list: ', chan_list) | ||||||
|         # print(frame, b.name, channel, chan_list) # Dbg |         # print(frame, b.name, channel, chan_list) # Dbg | ||||||
|         if channel == 'rotation_euler': |         if channel == 'rotation_euler': | ||||||
|             transform[channel] = Euler(chan_list) |             transform[channel] = Euler(chan_list) | ||||||
|  | |||||||
| @ -130,7 +130,7 @@ def bake_cycle(on_selection=True, end=None): | |||||||
|         return ('ERROR', 'No fcurve with cyclic modifier found (used to determine what to bake)') |         return ('ERROR', 'No fcurve with cyclic modifier found (used to determine what to bake)') | ||||||
| 
 | 
 | ||||||
|     if not ct: |     if not ct: | ||||||
|         return ('ERROR', 'No fcurve treated (! action duplicated to _baked !)') |         return ('ERROR', 'No fcurve affected (! action duplicated to _baked !)') | ||||||
| 
 | 
 | ||||||
|     # cleaning update  |     # cleaning update  | ||||||
|     fn.update_action(act) |     fn.update_action(act) | ||||||
| @ -203,6 +203,7 @@ class AW_OT_bake_cycle_and_step(bpy.types.Operator): | |||||||
|         if not act: |         if not act: | ||||||
|             self.report({'ERROR'}, 'No Animation set on active object') |             self.report({'ERROR'}, 'No Animation set on active object') | ||||||
|             return {"CANCELLED"} |             return {"CANCELLED"} | ||||||
|  |         self.starting_action = act | ||||||
| 
 | 
 | ||||||
|         act = fn.get_origin_action(act) |         act = fn.get_origin_action(act) | ||||||
|         # all_keys = [k.co.x for fc in act.fcurves if not '.offset' in fc.data_path for k in fc.keyframe_points] |         # all_keys = [k.co.x for fc in act.fcurves if not '.offset' in fc.data_path for k in fc.keyframe_points] | ||||||
| @ -228,16 +229,28 @@ class AW_OT_bake_cycle_and_step(bpy.types.Operator): | |||||||
|         if err: |         if err: | ||||||
|             self.report({err[0]}, err[1]) |             self.report({err[0]}, err[1]) | ||||||
|             if err[0] == 'ERROR': |             if err[0] == 'ERROR': | ||||||
|  |                 # context.object.animation_data.action = self.starting_action # restore act | ||||||
|  |                 return {"CANCELLED"} | ||||||
|  |          | ||||||
|  |         ## Clean overlap | ||||||
|  |         act = fn.get_obj_action(context.object) | ||||||
|  |         print('Action name:', act.name) | ||||||
|  |         clean_error = fn.clean_fcurve(act) | ||||||
|  |         if clean_error: | ||||||
|  |             self.report({clean_error[0]}, clean_error[1]) | ||||||
|  |             if clean_error[0] == 'ERROR': | ||||||
|  |                 # context.object.animation_data.action = self.starting_action # restore act | ||||||
|                 return {"CANCELLED"} |                 return {"CANCELLED"} | ||||||
|          |          | ||||||
|         ## all followup is not needed when animating on one |         ## all followup is not needed when animating on one | ||||||
| 
 |         ## step or smooth path animation | ||||||
|         if not context.scene.anim_cycle_settings.linear: |         if not context.scene.anim_cycle_settings.linear: | ||||||
|             # CHAINED ACTION : step the path of the curve path |             # CHAINED ACTION : step the path of the curve path | ||||||
|             err = step_path() |             err = step_path() | ||||||
|             if err: |             if err: | ||||||
|                 self.report({err[0]}, err[1]) |                 self.report({err[0]}, err[1]) | ||||||
|                 if err[0] == 'ERROR': |                 if err[0] == 'ERROR': | ||||||
|  |                     # context.object.animation_data.action = self.starting_action # restore act | ||||||
|                     return {"CANCELLED"} |                     return {"CANCELLED"} | ||||||
|         else: |         else: | ||||||
|             # Delete points in curve action between first and last and go LINEAR |             # Delete points in curve action between first and last and go LINEAR | ||||||
| @ -256,8 +269,7 @@ class AW_OT_bake_cycle_and_step(bpy.types.Operator): | |||||||
|                             k.interpolation = 'LINEAR' |                             k.interpolation = 'LINEAR' | ||||||
|                         print(f'Anim path to linear : Deleted all keys ({keys_ct - 2}) on anim path except first and last') |                         print(f'Anim path to linear : Deleted all keys ({keys_ct - 2}) on anim path except first and last') | ||||||
| 
 | 
 | ||||||
|         # CHAINED ACTION pin feet ?? : Step the path of the curve path |         ## CHAINED ACTION pin feet ?? : Step the path of the curve path | ||||||
| 
 |  | ||||||
|         return {"FINISHED"} |         return {"FINISHED"} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -277,7 +289,8 @@ def pin_down_feets(): | |||||||
|     debug = fn.get_addon_prefs().debug |     debug = fn.get_addon_prefs().debug | ||||||
| 
 | 
 | ||||||
|     scn = bpy.context.scene |     scn = bpy.context.scene | ||||||
|     on_selected = scn.anim_cycle_settings.expand_on_selected_bones |     settings = scn.anim_cycle_settings | ||||||
|  |     on_selected = settings.expand_on_selected_bones | ||||||
|     # Delete current action if its not the main one |     # Delete current action if its not the main one | ||||||
|     # create a new '_pinned' one  |     # create a new '_pinned' one  | ||||||
|     act = fn.set_generated_action(obj) |     act = fn.set_generated_action(obj) | ||||||
| @ -387,7 +400,7 @@ def pin_down_feets(): | |||||||
| 
 | 
 | ||||||
|              # iterate in reverse ranges (not really necessary) |              # iterate in reverse ranges (not really necessary) | ||||||
|             for r in reversed(contact_ranges): |             for r in reversed(contact_ranges): | ||||||
|                 print(f'range: {r}') |                 if debug >= 1: print(f'range: {r}') | ||||||
|                 first = True |                 first = True | ||||||
|                 for i in range(r[0], r[1]+1)[::-1]: # start from the end of the range |                 for i in range(r[0], r[1]+1)[::-1]: # start from the end of the range | ||||||
|                 # for i in range(r[0], r[1]+1): |                 # for i in range(r[0], r[1]+1): | ||||||
| @ -399,13 +412,37 @@ def pin_down_feets(): | |||||||
|                         first = False |                         first = False | ||||||
|                         continue |                         continue | ||||||
|                      |                      | ||||||
|  |                     # TODO: don't insert non-needed keyframe in step mode ? | ||||||
|  | 
 | ||||||
|                     # print(f'Apply on {b_name} at {i}') |                     # print(f'Apply on {b_name} at {i}') | ||||||
| 
 | 
 | ||||||
|                     #-# assign previous matrix |                     #-# assign previous matrix | ||||||
|                     # pbl = pb.location.copy() |                     # pbl = pb.location.copy() | ||||||
|  |                     if not settings.custom_pin: | ||||||
|  |                         pb.matrix = bone_mat # Exact same position | ||||||
|  |                     else: | ||||||
|  |                         pbl = pb.location.copy() | ||||||
|  |                         pbr = pb.rotation_euler.copy() | ||||||
| 
 | 
 | ||||||
|                         pb.matrix = bone_mat # Exact same position |                         pb.matrix = bone_mat # Exact same position | ||||||
|                          |                          | ||||||
|  |                         # Selectively restore initial bone transform | ||||||
|  |                         # per channel according to filters | ||||||
|  |                         if not settings.pin_loc_x: | ||||||
|  |                             setattr(pb.location, 'x', getattr(pbl, 'x')) | ||||||
|  |                         if not settings.pin_loc_y: | ||||||
|  |                             setattr(pb.location, 'y', getattr(pbl, 'y')) | ||||||
|  |                         if not settings.pin_loc_z: | ||||||
|  |                             setattr(pb.location, 'z', getattr(pbl, 'z')) | ||||||
|  | 
 | ||||||
|  |                         if not settings.pin_rot_x: | ||||||
|  |                             setattr(pb.rotation_euler, 'x', getattr(pbr, 'x')) | ||||||
|  |                         if not settings.pin_rot_y: | ||||||
|  |                             setattr(pb.rotation_euler, 'y', getattr(pbr, 'y')) | ||||||
|  |                         if not settings.pin_rot_z: | ||||||
|  |                             setattr(pb.rotation_euler, 'z', getattr(pbr, 'z')) | ||||||
|  | 
 | ||||||
|  |                      | ||||||
|                     ## maybe align on a specific axis |                     ## maybe align on a specific axis | ||||||
|                     # pb.location.x = pbl.x # dont touch x either |                     # pb.location.x = pbl.x # dont touch x either | ||||||
| 
 | 
 | ||||||
| @ -421,13 +458,9 @@ def pin_down_feets(): | |||||||
|                     ## insert keyframe |                     ## insert keyframe | ||||||
|                     pb.keyframe_insert('location') |                     pb.keyframe_insert('location') | ||||||
|                      |                      | ||||||
|                     # only touched Y location |                     if not settings.custom_pin or any((settings.pin_rot_x, settings.pin_rot_y, settings.pin_rot_z)): | ||||||
|                         pb.keyframe_insert('rotation_euler') |                         pb.keyframe_insert('rotation_euler') | ||||||
| 
 | 
 | ||||||
|                     # if i == r[1]+1: # (last key) in normal |  | ||||||
|                     # if i == r[0]: # (last key) in reverse |  | ||||||
|                     #     continue |  | ||||||
| 
 |  | ||||||
|                     # k.type = 'JITTER' # 'BREAKDOWN' 'MOVING_HOLD' 'JITTER' |                     # k.type = 'JITTER' # 'BREAKDOWN' 'MOVING_HOLD' 'JITTER' | ||||||
|                     ct += 1 |                     ct += 1 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ bl_info = { | |||||||
|     "name": "Auto Walk", |     "name": "Auto Walk", | ||||||
|     "description": "Develop a walk/run cycles along a curve and pin feets", |     "description": "Develop a walk/run cycles along a curve and pin feets", | ||||||
|     "author": "Samuel Bernou", |     "author": "Samuel Bernou", | ||||||
|     "version": (1, 6, 1), |     "version": (1, 7, 0), | ||||||
|     "blender": (3, 0, 0), |     "blender": (3, 0, 0), | ||||||
|     "location": "View3D", |     "location": "View3D", | ||||||
|     "warning": "", |     "warning": "", | ||||||
|  | |||||||
							
								
								
									
										29
									
								
								fn.py
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								fn.py
									
									
									
									
									
								
							| @ -774,3 +774,32 @@ def get_x_pos_of_visible_keys(ob, act): | |||||||
|      |      | ||||||
|     return keys |     return keys | ||||||
| 
 | 
 | ||||||
|  | # def clear_duplicated_keys_in_fcurves() | ||||||
|  | def clean_fcurve(action=None): | ||||||
|  |     '''clear duplicated keys at same frame in fcurves''' | ||||||
|  | 
 | ||||||
|  |     cleaned = 0 | ||||||
|  |     problems = 0 | ||||||
|  |     if action is None: | ||||||
|  |         bpy.context.object.animation_data.action | ||||||
|  |     for fcu in action.fcurves: | ||||||
|  |         prev = None | ||||||
|  |         for k in reversed(fcu.keyframe_points): | ||||||
|  |             if not prev: | ||||||
|  |                 prev = k | ||||||
|  |                 continue | ||||||
|  |             if prev.co.x == k.co.x: | ||||||
|  |                 if prev.co.y == k.co.y: | ||||||
|  |                     print(f'autoclean: 2 idential keys at {k.co.x} ', fcu.data_path, fcu.array_index)         | ||||||
|  |                     fcu.keyframe_points.remove(prev) | ||||||
|  |                     cleaned += 1 | ||||||
|  |                 else: | ||||||
|  |                     print(f'/!\ 2 keys with different value at {k.co.x} ! : ', fcu.data_path, fcu.array_index) | ||||||
|  |                     problems += 1 | ||||||
|  |             prev = k | ||||||
|  |      | ||||||
|  |     if problems: | ||||||
|  |         return ('ERROR', f'{problems} keys are overlapping (see console)') | ||||||
|  |     if cleaned: | ||||||
|  |         return ('WARNING', f'{cleaned} keys autocleaned') | ||||||
|  | 
 | ||||||
|  | |||||||
							
								
								
									
										15
									
								
								panels.py
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								panels.py
									
									
									
									
									
								
							| @ -117,6 +117,21 @@ class AW_PT_walk_cycle_anim_panel(bpy.types.Panel): | |||||||
|         txt = 'Bake keys' if settings.linear else 'Bake keys and step path' |         txt = 'Bake keys' if settings.linear else 'Bake keys and step path' | ||||||
|         col.operator('autowalk.bake_cycle_and_step', text=txt, icon='SHAPEKEY_DATA') |         col.operator('autowalk.bake_cycle_and_step', text=txt, icon='SHAPEKEY_DATA') | ||||||
|          |          | ||||||
|  |         # Custom Axis Pinning | ||||||
|  |         subox = col.box() | ||||||
|  |         subox.prop(settings, "custom_pin", text='Custom Pinning') | ||||||
|  |         if settings.custom_pin: | ||||||
|  |             row=subox.row(align=True) | ||||||
|  |             row.label(text='Location') | ||||||
|  |             row.prop(settings, 'pin_loc_x', text='X', toggle=True) | ||||||
|  |             row.prop(settings, 'pin_loc_y', text='Y', toggle=True) | ||||||
|  |             row.prop(settings, 'pin_loc_z', text='Z', toggle=True) | ||||||
|  |             row=subox.row(align=True) | ||||||
|  |             row.label(text='Rotation') | ||||||
|  |             row.prop(settings, 'pin_rot_x', text='X', toggle=True) | ||||||
|  |             row.prop(settings, 'pin_rot_y', text='Y', toggle=True) | ||||||
|  |             row.prop(settings, 'pin_rot_z', text='Z', toggle=True) | ||||||
|  | 
 | ||||||
|         # Pin feet |         # Pin feet | ||||||
|         col.operator('autowalk.pin_feets', text='Pin feets', icon='PINNED') |         col.operator('autowalk.pin_feets', text='Pin feets', icon='PINNED') | ||||||
|          |          | ||||||
|  | |||||||
| @ -57,6 +57,25 @@ class AW_PG_settings(bpy.types.PropertyGroup) : | |||||||
|             ), |             ), | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  |     custom_pin : bpy.props.BoolProperty( | ||||||
|  |         name="Custom Pinning", description="Pin only specific axis\ | ||||||
|  |             \nElse pin all location and rotation", | ||||||
|  |         default=False, options={'HIDDEN'}) | ||||||
|  | 
 | ||||||
|  |     pin_loc_x : bpy.props.BoolProperty( | ||||||
|  |         name="Pin Loc X", description="Pin bones location X", default=True, options={'HIDDEN'}) | ||||||
|  |     pin_loc_y : bpy.props.BoolProperty( | ||||||
|  |         name="Pin Loc Y", description="Pin bones location Y", default=True, options={'HIDDEN'}) | ||||||
|  |     pin_loc_z : bpy.props.BoolProperty( | ||||||
|  |         name="Pin Loc Z", description="Pin bones location Z", default=True, options={'HIDDEN'}) | ||||||
|  |      | ||||||
|  |     pin_rot_x : bpy.props.BoolProperty( | ||||||
|  |         name="Pin Rot X", description="Pin bones rotation X", default=True, options={'HIDDEN'}) | ||||||
|  |     pin_rot_y : bpy.props.BoolProperty( | ||||||
|  |         name="Pin Rot Y", description="Pin bones rotation Y", default=True, options={'HIDDEN'}) | ||||||
|  |     pin_rot_z : bpy.props.BoolProperty( | ||||||
|  |         name="Pin Rot Z", description="Pin bones rotation Z", default=True, options={'HIDDEN'}) | ||||||
|  | 
 | ||||||
|     """ |     """ | ||||||
|     ## foot axis not needed (not always aligned with character direction) |     ## foot axis not needed (not always aligned with character direction) | ||||||
|     foot_axis : EnumProperty( |     foot_axis : EnumProperty( | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user