import bpy, re from . import fn ## step 1 : Create the curve and/or follow path constraint, snap to ground class AW_OT_create_curve_path(bpy.types.Operator): bl_idname = "autowalk.create_curve_path" bl_label = "Create Curve" bl_description = "Create curve and add follow path constraint" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return context.object and context.object.type == 'ARMATURE' def execute(self, context): # use root (or other specified bone) to find where to put the curve prefs = fn.get_addon_prefs() ob = context.object settings = context.scene.anim_cycle_settings bpy.ops.object.mode_set(mode='OBJECT', toggle=False) 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() curve = fn.generate_curve(location=loc, direction=world_forward.normalized() * 10, name='curve_path', context=context) settings.path_to_follow = curve fn.create_follow_path_constraint(ob, curve) # reset location to remove offset root.location = (0,0,0) root.keyframe_insert('location') root.rotation_euler = (0,0,0) root.keyframe_insert('rotation_euler') # refresh evaluation so constraint shows up correctly bpy.context.scene.frame_set(bpy.context.scene.frame_current) return {"FINISHED"} class AW_OT_create_follow_path(bpy.types.Operator): bl_idname = "autowalk.create_follow_path" bl_label = "Create Follow Path Constraint" bl_description = "Create follow path targeting curve chosen in dedicated field" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return context.object and context.object.type == 'ARMATURE' \ and context.scene.anim_cycle_settings.path_to_follow def execute(self, context): ob = context.object curve = context.scene.anim_cycle_settings.path_to_follow err, const = fn.create_follow_path_constraint(ob, curve) if isinstance(err, str): self.report({'ERROR'}, err) return {"CANCELLED"} return {"FINISHED"} class AW_OT_remove_follow_path(bpy.types.Operator): bl_idname = "autowalk.remove_follow_path" bl_label = "Remove Follow Path Constraint" bl_description = "Remove follow path on target bone" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return context.object and context.object.type == 'ARMATURE' def execute(self, context): ob = context.object # curve = context.scene.anim_cycle_settings.path_to_follow tgt_bone = fn.get_addon_prefs().tgt_bone bone = ob.pose.bones.get(tgt_bone) if not bone: self.report({'ERROR'}, f'No bone "{tgt_bone}" found') return {"CANCELLED"} const = next((c for c in bone.constraints if c.type == 'FOLLOW_PATH'), None) if not const: self.report({'ERROR'}, f'No follow path constraint on "{tgt_bone}" found') return {"CANCELLED"} bone.constraints.remove(const) self.report({'INFO'}, f'removed follow_path constraint on bone "{tgt_bone}"') # Also remove offset action ? myabe give the choice return {"FINISHED"} class AW_OT_snap_curve_to_ground(bpy.types.Operator): bl_idname = "autowalk.snap_curve_to_ground" bl_label = "Snap Curve" bl_description = "Snap curve to ground determine in field" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return context.object and context.object.type == 'ARMATURE' def execute(self, context): err = fn.snap_curve() if err: self.report({err[0]}, err[1]) if err[0] == 'ERROR': return {"CANCELLED"} return {"FINISHED"} class AW_OT_snap_selected_curve(bpy.types.Operator): bl_idname = "autowalk.snap_selected_curve" bl_label = "Snap Selected Curve" bl_description = "Snap selected curve to ground\ \nCtrl + Click : Not apply Shrinkwarp modifier (Apply manually)" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): return context.object and context.object.type == 'CURVE' def invoke(self, context, event): self.apply = not event.ctrl # don't apply if ctrl is pressd return self.execute(context) def execute(self, context): ob = context.object # bpy.ops.object.mode_set(mode='OBJECT', toggle=False) ground = fn.get_gnd() if not ground: self.report({'ERROR'}, 'Need to specify ground (in curve options) or name an object in scene "Ground"') return {"CANCELLED"} fn.shrinkwrap_on_object(ob, ground, apply=self.apply) if self.apply: self.report({'INFO'}, 'ShrinkWrap Modifier need to be applyed manually') return {"FINISHED"} class AW_OT_edit_curve(bpy.types.Operator): bl_idname = "autowalk.edit_curve" bl_label = "Edit Curve" bl_description = "Edit curve used as constraint for foot" bl_options = {"REGISTER", "INTERNAL", "UNDO"} @classmethod def poll(cls, context): return context.object and context.object.type == 'ARMATURE' def execute(self, context): ob = context.object b = context.active_pose_bone curve = None # test with selected bone if b and b.constraints: curve = next((c.target for c in b.constraints if c.type == 'FOLLOW_PATH' and c.target), None) # get from 'root' bone if not curve: curve, _const = fn.get_follow_curve_from_armature(ob) if isinstance(curve, str): self.report({curve}, _const) if curve == 'ERROR': return {"CANCELLED"} # set mode to object set curve as active and go Edit fn.go_edit_mode(curve) # curve context.mode -> EDIT_CURVE # Deselect armature object # b.id_data.select_set(False) ob.select_set(False) return {"FINISHED"} class AW_OT_go_to_object(bpy.types.Operator): bl_idname = "autowalk.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) ## deselect all armatures (or all objects) for ob in context.scene.objects: if ob.type == 'ARMATURE': 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}') return {"FINISHED"} class AW_OT_object_from_curve(bpy.types.Operator): bl_idname = "autowalk.object_from_curve" bl_label = "Back To Armature" bl_description = "Go in armature pose mode 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): self.armatures = [] curve = context.object for o in context.scene.objects: if o.type != 'ARMATURE': continue for pb in o.pose.bones: for c in pb.constraints: if c.type == 'FOLLOW_PATH' and c.target and c.target == curve: self.armatures.append((o, pb)) break if not self.armatures: self.report({'ERROR'}, 'No armature using this curve found') return {"CANCELLED"} curve.select_set(False) if len(self.armatures) > 1: return context.window_manager.invoke_props_popup(self, event) # execute on change # set pose mode on only object available obj, pb = self.armatures[0] bpy.ops.object.mode_set(mode='OBJECT', toggle=False) for ob in context.scene.objects: if ob.type == 'ARMATURE': 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 arm, pb in self.armatures: layout.operator('autowalk.go_to_object', text=f'{arm.name} ({pb.name})', icon='OUTLINER_OB_ARMATURE').obj_name = arm.name def execute(self, context): return {"FINISHED"} classes=( AW_OT_create_curve_path, AW_OT_create_follow_path, AW_OT_remove_follow_path, AW_OT_snap_curve_to_ground, AW_OT_snap_selected_curve, AW_OT_edit_curve, AW_OT_go_to_object, AW_OT_object_from_curve, # use set_choice_id is used to set an index in object_from_curve pop up menu ) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) # if __name__ == "__main__": # register()