2022-11-04 18:45:37 +01:00
|
|
|
import bpy
|
|
|
|
from mathutils import Vector
|
|
|
|
from . import utils
|
|
|
|
|
|
|
|
class GPTB_OT_create_follow_path_curve(bpy.types.Operator):
|
|
|
|
bl_idname = "object.create_follow_path_curve"
|
|
|
|
bl_label = "Create Follow Path Curve"
|
|
|
|
bl_description = "Create curve and add follow path constraint\
|
|
|
|
\n(remove location offset from object if any)"
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
|
|
|
return context.object
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
ob = context.object
|
|
|
|
# settings = context.scene.anim_cycle_settings
|
|
|
|
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
|
|
|
|
|
|
|
## For bones
|
|
|
|
# root_name = fn.get_root_name(context=context)
|
|
|
|
# root = ob.pose.bones.get(root_name)
|
|
|
|
# if not root:
|
|
|
|
# self.report({'ERROR'}, f'posebone {root_name} not found in armature {ob.name} check addon preferences to change name')
|
|
|
|
# return {"CANCELLED"}
|
|
|
|
|
|
|
|
## create curve at bone position
|
|
|
|
# loc = ob.matrix_world @ root.matrix.to_translation()
|
|
|
|
# root_axis_vec = fn.get_direction_vector_from_enum(settings.forward_axis)
|
|
|
|
## get real world direction of the root
|
|
|
|
# world_forward = (root.matrix @ root_axis_vec) - root.matrix.to_translation()
|
|
|
|
|
|
|
|
loc = ob.matrix_world.to_translation()
|
|
|
|
|
|
|
|
## X global
|
|
|
|
# TODO: Set direction orientation in view space (UP, LEFT, RIGHT, DOWN)
|
|
|
|
direction = Vector((1,0,0))
|
|
|
|
curve = utils.create_curve(location=loc,
|
|
|
|
direction=direction.normalized() * 2,
|
|
|
|
name='curve_path',
|
|
|
|
context=context)
|
|
|
|
|
|
|
|
utils.create_follow_path_constraint(ob, curve)
|
|
|
|
|
|
|
|
## reset location to remove offset
|
|
|
|
ob.location = (0,0,0)
|
|
|
|
# ob.keyframe_insert('location')
|
|
|
|
ob.rotation_euler = (0,0,0)
|
|
|
|
# ob.keyframe_insert('rotation_euler')
|
|
|
|
|
|
|
|
# refresh evaluation so constraint shows up correctly
|
|
|
|
bpy.context.scene.frame_set(bpy.context.scene.frame_current)
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
2022-11-09 18:59:22 +01:00
|
|
|
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}')
|
|
|
|
|
2024-11-11 15:35:39 +01:00
|
|
|
elif obj.type == 'GREASEPENCIL':
|
2024-11-11 15:47:33 +01:00
|
|
|
bpy.ops.object.mode_set(mode='PAINT_GREASE_PENCIL', toggle=False)
|
2022-11-09 18:59:22 +01:00
|
|
|
|
|
|
|
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"}
|
2022-11-04 18:45:37 +01:00
|
|
|
|
|
|
|
classes = (
|
|
|
|
GPTB_OT_create_follow_path_curve,
|
2022-11-09 18:59:22 +01:00
|
|
|
GPTB_OT_edit_curve,
|
|
|
|
GPTB_OT_remove_follow_path,
|
|
|
|
GPTB_OT_go_to_object,
|
|
|
|
GPTB_OT_object_from_curve,
|
2022-11-04 18:45:37 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
def register():
|
|
|
|
for cls in classes:
|
|
|
|
bpy.utils.register_class(cls)
|
|
|
|
|
|
|
|
def unregister():
|
|
|
|
for cls in reversed(classes):
|
|
|
|
bpy.utils.unregister_class(cls)
|