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 constraintgpv2
parent
336d1b264c
commit
a021535b3f
|
@ -1,5 +1,13 @@
|
|||
# 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
|
||||
|
||||
- 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)
|
||||
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 = (
|
||||
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():
|
||||
|
|
|
@ -398,7 +398,7 @@ class GPTB_OT_toggle_mute_animation(bpy.types.Operator):
|
|||
if self.mode == 'CAMERA' and o.type != 'CAMERA':
|
||||
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:
|
||||
gp_act = o.data.animation_data.action
|
||||
if gp_act:
|
||||
|
|
14
UI_tools.py
14
UI_tools.py
|
@ -224,7 +224,19 @@ class GPTB_PT_anim_manager(Panel):
|
|||
|
||||
## Follow curve path
|
||||
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...
|
||||
col = layout.column()
|
||||
|
|
Loading…
Reference in New Issue