246 lines
8.6 KiB
Python
246 lines
8.6 KiB
Python
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)
|
|
|
|
# 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_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):
|
|
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(context.object)
|
|
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)
|
|
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_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() |