auto_walk/OP_setup_curve_path.py

284 lines
9.9 KiB
Python
Raw Normal View History

2021-04-05 01:35:12 +02:00
import bpy, re
from . import fn
2021-04-06 18:30:25 +02:00
## 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"
2021-04-05 01:35:12 +02:00
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
2022-03-29 18:46:33 +02:00
prefs = fn.get_addon_prefs()
2021-04-05 01:35:12 +02:00
ob = context.object
2022-03-31 17:07:04 +02:00
settings = context.scene.anim_cycle_settings
2021-04-05 01:35:12 +02:00
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
2022-03-31 17:07:04 +02:00
root_name = fn.get_root_name(context=context)
root = ob.pose.bones.get(root_name)
2021-04-05 01:35:12 +02:00
if not root:
2022-03-31 17:07:04 +02:00
self.report({'ERROR'}, f'posebone {root_name} not found in armature {ob.name} check addon preferences to change name')
2021-04-05 01:35:12 +02:00
return {"CANCELLED"}
2022-03-29 18:46:33 +02:00
## create curve at bone position
2021-04-05 01:35:12 +02:00
loc = ob.matrix_world @ root.matrix.to_translation()
2022-03-31 17:07:04 +02:00
root_axis_vec = fn.get_direction_vector_from_enum(settings.forward_axis)
2021-04-05 01:35:12 +02:00
2022-03-31 17:07:04 +02:00
# get real world direction of the root
world_forward = (root.matrix @ root_axis_vec) - root.matrix.to_translation()
2021-04-05 01:35:12 +02:00
2022-03-31 17:07:04 +02:00
curve = fn.generate_curve(location=loc, direction=world_forward.normalized() * 10, name='curve_path', context=context)
2021-04-05 01:35:12 +02:00
2022-03-31 17:07:04 +02:00
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')
2022-03-31 17:07:04 +02:00
# refresh evaluation so constraint shows up correctly
bpy.context.scene.frame_set(bpy.context.scene.frame_current)
2021-04-05 01:35:12 +02:00
return {"FINISHED"}
class AW_OT_create_follow_path(bpy.types.Operator):
bl_idname = "autowalk.create_follow_path"
2022-04-01 12:12:05 +02:00
bl_label = "Create Follow Path Constraint"
bl_description = "Create follow path targeting curve chosen in dedicated field"
2021-04-06 18:30:25 +02:00
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
2021-04-06 18:30:25 +02:00
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)
2022-03-31 17:07:04 +02:00
if isinstance(err, str):
self.report({'ERROR'}, err)
return {"CANCELLED"}
2021-04-06 18:30:25 +02:00
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"
2021-04-05 01:35:12 +02:00
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()
2021-04-06 18:30:25 +02:00
if err:
self.report({err[0]}, err[1])
if err[0] == 'ERROR':
return {"CANCELLED"}
2021-04-05 01:35:12 +02:00
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"}
2021-04-05 01:35:12 +02:00
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
2021-04-05 01:35:12 +02:00
)
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()