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"} 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 == 'GREASEPENCIL': bpy.ops.object.mode_set(mode='PAINT_GREASE_PENCIL', 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(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls)