Add curve follow path creation and management
2.1.0 - added: 3 actions buttons: - create curve with follow path and go into curve edit - go back to object - got to curve edit (if follow path constraint exists with a curve target) - if follow path exists, button to remove constraint
This commit is contained in:
		
							parent
							
								
									336d1b264c
								
							
						
					
					
						commit
						a021535b3f
					
				| @ -1,5 +1,13 @@ | |||||||
| # Changelog | # Changelog | ||||||
| 
 | 
 | ||||||
|  | 2.1.0 | ||||||
|  | 
 | ||||||
|  | - added: 3 actions buttons: | ||||||
|  |   - create curve with follow path and go into curve edit | ||||||
|  |   - go back to object | ||||||
|  |   - got to curve edit (if follow path constraint exists with a curve target) | ||||||
|  |   - if follow path exists, button to remove constraint | ||||||
|  | 
 | ||||||
| 2.0.11 | 2.0.11 | ||||||
| 
 | 
 | ||||||
| - fix: prefix set by project environment | - fix: prefix set by project environment | ||||||
|  | |||||||
| @ -54,9 +54,156 @@ class GPTB_OT_create_follow_path_curve(bpy.types.Operator): | |||||||
|         bpy.context.scene.frame_set(bpy.context.scene.frame_current) |         bpy.context.scene.frame_set(bpy.context.scene.frame_current) | ||||||
|         return {"FINISHED"} |         return {"FINISHED"} | ||||||
| 
 | 
 | ||||||
|  | class GPTB_OT_edit_curve(bpy.types.Operator): | ||||||
|  |     bl_idname = "object.edit_curve" | ||||||
|  |     bl_label = "Edit Curve" | ||||||
|  |     bl_description = "Edit curve used as follow path constraint" | ||||||
|  |     bl_options = {"REGISTER", "INTERNAL", "UNDO"} | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def poll(cls, context): | ||||||
|  |         return context.object | ||||||
|  | 
 | ||||||
|  |     def execute(self, context): | ||||||
|  |         ob = context.object | ||||||
|  |         curve = next((c.target for c in ob.constraints if c.type == 'FOLLOW_PATH' and c.target), None) | ||||||
|  | 
 | ||||||
|  |         if curve is None: | ||||||
|  |             self.report({"ERROR"}, 'No follow path curve found') | ||||||
|  |             return {"CANCELLED"} | ||||||
|  |          | ||||||
|  |         # Object mode, set curve as active, go Edit | ||||||
|  |         utils.go_edit_mode(curve) | ||||||
|  |         # curve context.mode -> EDIT_CURVE | ||||||
|  |         # b.id_data.select_set(False) | ||||||
|  |         ob.select_set(False) | ||||||
|  |         return {"FINISHED"} | ||||||
|  | 
 | ||||||
|  | class GPTB_OT_remove_follow_path(bpy.types.Operator): | ||||||
|  |     bl_idname = "object.remove_follow_path" | ||||||
|  |     bl_label = "Remove Follow Path Constraint" | ||||||
|  |     bl_description = "Remove follow path on object" | ||||||
|  |     bl_options = {"REGISTER", "UNDO"} | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def poll(cls, context): | ||||||
|  |         return context.object | ||||||
|  | 
 | ||||||
|  |     def execute(self, context): | ||||||
|  |         ob = context.object | ||||||
|  |         const = next((c for c in ob.constraints if c.type == 'FOLLOW_PATH'), None) | ||||||
|  |         if not const: | ||||||
|  |             self.report({'ERROR'}, f'No follow path constraint on "{ob.name}" found') | ||||||
|  |             return {"CANCELLED"} | ||||||
|  |          | ||||||
|  |         # store position | ||||||
|  |         mat = ob.matrix_world.copy() | ||||||
|  |          | ||||||
|  |         ob.constraints.remove(const) | ||||||
|  |          | ||||||
|  |         # restore position | ||||||
|  |         ob.matrix_world = mat | ||||||
|  | 
 | ||||||
|  |         self.report({'INFO'}, f'Removed follow_path constraint on "{ob.name}"') | ||||||
|  |         # Also remove offset action ? maybe give the choice | ||||||
|  |         return {"FINISHED"} | ||||||
|  | 
 | ||||||
|  | class GPTB_OT_go_to_object(bpy.types.Operator): | ||||||
|  |     bl_idname = "object.go_to_object" | ||||||
|  |     bl_label = "Go To Object" | ||||||
|  |     bl_description = "Go to object in pose mode" | ||||||
|  |     bl_options = {"REGISTER", "INTERNAL"} | ||||||
|  |      | ||||||
|  |     obj_name : bpy.props.StringProperty(options={'SKIP_SAVE'}) | ||||||
|  | 
 | ||||||
|  |     def execute(self, context): | ||||||
|  |         obj = context.scene.objects.get(self.obj_name) | ||||||
|  |         if not obj: | ||||||
|  |             self.report({'ERROR'}, f'Could not find object {self.obj_name} in scene objects') | ||||||
|  |             return {"CANCELLED"} | ||||||
|  | 
 | ||||||
|  |         bpy.ops.object.mode_set(mode='OBJECT', toggle=False) | ||||||
|  |          | ||||||
|  |         for ob in context.scene.objects: | ||||||
|  |             ob.select_set(False) | ||||||
|  |          | ||||||
|  |         # Set active | ||||||
|  |         obj.select_set(True) | ||||||
|  |         context.view_layer.objects.active = obj | ||||||
|  |          | ||||||
|  |         if obj.type == 'ARMATURE': | ||||||
|  |             bpy.ops.object.mode_set(mode='POSE', toggle=False) | ||||||
|  |             self.report({'INFO'}, f'Back to pose mode, {obj.name}') | ||||||
|  |          | ||||||
|  |         elif obj.type == 'GPENCIL': | ||||||
|  |             bpy.ops.object.mode_set(mode='PAINT_GPENCIL', toggle=False) | ||||||
|  |          | ||||||
|  |         else: | ||||||
|  |             self.report({'INFO'}, f'Back to object mode, {obj.name}') | ||||||
|  | 
 | ||||||
|  |         return {"FINISHED"} | ||||||
|  | 
 | ||||||
|  | class GPTB_OT_object_from_curve(bpy.types.Operator): | ||||||
|  |     bl_idname = "object.object_from_curve" | ||||||
|  |     bl_label = "Back To Following Object" | ||||||
|  |     bl_description = "Go on following object from current curve" | ||||||
|  |     bl_options = {"REGISTER", "INTERNAL", "UNDO"} | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def poll(cls, context): | ||||||
|  |         return context.object and context.object.type == 'CURVE' | ||||||
|  | 
 | ||||||
|  |     def invoke(self, context, event): | ||||||
|  |         curve = context.object | ||||||
|  |         self.objects = [] | ||||||
|  |         for o in context.scene.objects: | ||||||
|  |              | ||||||
|  |             if o.type != 'ARMATURE': | ||||||
|  |                 for c in o.constraints: | ||||||
|  |                     if c.type == 'FOLLOW_PATH' and c.target and c.target == curve: | ||||||
|  |                         self.objects.append(o) | ||||||
|  |             else: | ||||||
|  |                 for pb in o.pose.bones: | ||||||
|  |                     for c in pb.constraints: | ||||||
|  |                         if c.type == 'FOLLOW_PATH' and c.target and c.target == curve: | ||||||
|  |                             self.objects.append(o) | ||||||
|  |                             break | ||||||
|  |          | ||||||
|  |         if not self.objects: | ||||||
|  |             self.report({'ERROR'}, 'No object following current curve found') | ||||||
|  |             return {"CANCELLED"} | ||||||
|  |          | ||||||
|  |         curve.select_set(False) | ||||||
|  |         if len(self.objects) > 1: | ||||||
|  |             return context.window_manager.invoke_props_popup(self, event) # execute on change | ||||||
|  | 
 | ||||||
|  |         # set pose mode on only object available | ||||||
|  |         obj = self.objects[0] | ||||||
|  |         bpy.ops.object.go_to_object(obj_name=obj.name) | ||||||
|  |         # bpy.ops.object.mode_set(mode='OBJECT', toggle=False) | ||||||
|  |         # for ob in context.scene.objects: | ||||||
|  |         #     ob.select_set(False) | ||||||
|  |         # obj.select_set(True) | ||||||
|  |         # context.view_layer.objects.active = obj | ||||||
|  |         # bpy.ops.object.mode_set(mode='POSE', toggle=False) | ||||||
|  |         # self.report({'INFO'}, f'Back to pose mode {obj.name} (constraint on {pb.name})') | ||||||
|  | 
 | ||||||
|  |         return self.execute(context) | ||||||
|  | 
 | ||||||
|  |     def draw(self, context): | ||||||
|  |         layout = self.layout | ||||||
|  |         for obj in self.objects: | ||||||
|  |             layout.operator('object.go_to_object', text=obj.name, icon='OBJECT_DATA').obj_name = obj.name | ||||||
|  | 
 | ||||||
|  |     def execute(self, context): | ||||||
|  |         return {"FINISHED"} | ||||||
| 
 | 
 | ||||||
| classes = ( | classes = ( | ||||||
|     GPTB_OT_create_follow_path_curve, |     GPTB_OT_create_follow_path_curve, | ||||||
|  |     GPTB_OT_edit_curve, | ||||||
|  |     GPTB_OT_remove_follow_path, | ||||||
|  |     GPTB_OT_go_to_object, | ||||||
|  |     GPTB_OT_object_from_curve, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| def register(): | def register(): | ||||||
|  | |||||||
| @ -398,7 +398,7 @@ class GPTB_OT_toggle_mute_animation(bpy.types.Operator): | |||||||
|             if self.mode == 'CAMERA' and o.type != 'CAMERA': |             if self.mode == 'CAMERA' and o.type != 'CAMERA': | ||||||
|                 continue |                 continue | ||||||
|              |              | ||||||
|             # mute attribute aniamtion for GP and cameras |             # mute attribute animation for GP and cameras | ||||||
|             if o.type in ('GPENCIL', 'CAMERA') and o.data.animation_data: |             if o.type in ('GPENCIL', 'CAMERA') and o.data.animation_data: | ||||||
|                 gp_act = o.data.animation_data.action |                 gp_act = o.data.animation_data.action | ||||||
|                 if gp_act: |                 if gp_act: | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								UI_tools.py
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								UI_tools.py
									
									
									
									
									
								
							| @ -224,7 +224,19 @@ class GPTB_PT_anim_manager(Panel): | |||||||
| 
 | 
 | ||||||
|         ## Follow curve path |         ## Follow curve path | ||||||
|         row = col.row(align=True) |         row = col.row(align=True) | ||||||
|         row.operator('object.create_follow_path_curve', text='Create Curve', icon='CURVE_BEZCURVE') |         # row.operator('object.create_follow_path_curve', text='Create Curve', icon='CURVE_BEZCURVE') | ||||||
|  |          | ||||||
|  |         if context.object and context.object.type == 'CURVE' and context.mode in ('OBJECT', 'EDIT_CURVE'): | ||||||
|  |             row.operator('object.object_from_curve', text='Back To Object', icon='LOOP_BACK') | ||||||
|  |          | ||||||
|  |         elif (follow_const := context.object.constraints.get('Follow Path')) and follow_const.target: | ||||||
|  |             row.operator('object.edit_curve', text='Edit Curve', icon='OUTLINER_DATA_CURVE') | ||||||
|  |             row.operator('object.remove_follow_path', text='', icon='X') | ||||||
|  |             col.label(text=f'{context.object.name} -> {follow_const.target.name}', icon='CON_FOLLOWPATH') | ||||||
|  | 
 | ||||||
|  |         else: | ||||||
|  |             col.operator('object.create_follow_path_curve', text='Create Curve', icon='CURVE_BEZCURVE') | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|         ## This can go in an extra category... |         ## This can go in an extra category... | ||||||
|         col = layout.column() |         col = layout.column() | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user