auto_walk/OP_setup_curve_path.py

226 lines
7.6 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
def create_follow_path_constraint(ob, curve):
pref = fn.get_addon_prefs()
bone_name = pref.tgt_bone
root = ob.pose.bones.get(bone_name)
if not root:
return ('ERROR', f'posebone {bone_name} not found in armature {ob.name} check addon preferences to change name')
# Clear bone follow path constraint
exiting_fp_constraints = [c for c in root.constraints if c.type == 'FOLLOW_PATH']
for c in exiting_fp_constraints:
root.constraints.remove(c)
# loc = ob.matrix_world @ root.matrix.to_translation()
if root.name == 'root' and root.location != (0,0,0):
old_loc = root.location
root.location = (0,0,0)
print(f'root moved from {old_loc} to (0,0,0) to counter curve offset')
const = root.constraints.new('FOLLOW_PATH')
const.target = curve
# axis only in this case, should be in addon to prefs
const.forward_axis = 'FORWARD_X'
const.use_curve_follow = True
return curve, const
2021-04-05 01:35:12 +02:00
def snap_curve():
obj = bpy.context.object
2021-04-06 18:30:25 +02:00
curve = const = None
2021-04-05 01:35:12 +02:00
if obj.type == 'ARMATURE':
curve, const = fn.get_follow_curve_from_armature(obj)
2021-04-06 18:30:25 +02:00
to_follow = bpy.context.scene.anim_cycle_settings.path_to_follow
if not curve and not to_follow:
return ('ERROR', f'No curve pointed by "Path" filed')
2021-04-05 01:35:12 +02:00
2021-04-06 18:30:25 +02:00
# get curve from field
if not curve:
curve, const = create_follow_path_constraint(obj, to_follow)
if not curve:
return (curve, const) # those are error message
# if obj.type == 'CURVE':
# return ('ERROR', f'Select the armature related to curve {obj.name}')
# else:
# return ('ERROR', 'Not an armature object')
2021-04-05 01:35:12 +02:00
gnd = fn.get_gnd()
if not gnd:
return
2021-04-08 19:25:05 +02:00
curve_act = None
anim_frame = None
2021-04-05 01:35:12 +02:00
# if it's on a snap curve, fetch original
if '_snap' in curve.name:
org_name = re.sub(r'_snap\.?\d{0,3}$', '', curve.name)
org_curve = bpy.context.scene.objects.get(org_name)
if org_curve:
const.target = org_curve
2021-04-08 19:25:05 +02:00
# keep action
if curve.data.animation_data and curve.data.animation_data.action:
curve_act = curve.data.animation_data.action
anim_frame = curve.data.path_duration
2021-04-05 01:35:12 +02:00
# delete old snap
bpy.data.objects.remove(curve)
# assign old curve as main one
curve = org_curve
nc = curve.copy()
name = re.sub(r'\.\d{3}$', '', curve.name) + '_snap'
const.target = nc
nc.name = name
nc.data = curve.data.copy()
nc.data.name = name + '_data'
2021-04-08 19:25:05 +02:00
if curve_act:
nc.data.animation_data_create()
nc.data.animation_data.action = curve_act
if anim_frame:
nc.data.path_duration = anim_frame
2021-04-05 01:35:12 +02:00
curve.users_collection[0].objects.link(nc)
## If object mode is Curve subdivide it (TODO if nurbs needs conversion)
#-# subdivide the curve (if curve is not nurbs)
# bpy.ops.object.mode_set(mode='EDIT')
# bpy.ops.curve.select_all(action='SELECT')
# bpy.ops.curve.subdivide(number_cuts=4)
# bpy.ops.object.mode_set(mode='OBJECT')
# shrinkwrap or cast on ground
mod = nc.modifiers.new('Shrinkwrap', 'SHRINKWRAP')
2021-04-08 19:25:05 +02:00
# mod.wrap_method = 'TARGET_PROJECT'
mod.wrap_method = 'PROJECT'
mod.wrap_mode = 'ON_SURFACE'
mod.use_project_z = True
mod.use_negative_direction = True
mod.use_positive_direction = True
2021-04-05 01:35:12 +02:00
mod.target = gnd
# Apply and decimate
bpy.ops.object.modifier_apply({'object': nc}, modifier="Shrinkwrap", report=False)
2021-04-06 18:30:25 +02:00
bpy.context.scene.anim_cycle_settings.path_to_follow = nc
# return 0, nc
2021-04-05 01:35:12 +02:00
2021-04-05 01:39:27 +02:00
class UAC_OT_create_curve_path(bpy.types.Operator):
2021-04-05 01:35:12 +02:00
bl_idname = "anim.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
pref = fn.get_addon_prefs()
ob = context.object
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
bone_name = pref.tgt_bone
root = ob.pose.bones.get(bone_name)
if not root:
self.report({'ERROR'}, f'posebone {bone_name} not found in armature {ob.name} check addon preferences to change name')
return {"CANCELLED"}
loc = ob.matrix_world @ root.matrix.to_translation()
# TODO propose nurbs instead of curve
# TODO mode elegantly create the curve using data...
bpy.ops.curve.primitive_bezier_curve_add(radius=1, enter_editmode=True, align='WORLD', location=loc, scale=(1, 1, 1))
# fast straighten
print('context.object: ', context.object.name)
curve = context.object
curve.name = 'curve_path'
curve.show_in_front = True
bpy.ops.curve.handle_type_set(type='VECTOR')
bpy.ops.curve.handle_type_set(type='ALIGNED')
# offset to have start
bpy.ops.transform.translate(value=(1, 0, 0), orient_type='LOCAL',
orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='LOCAL',
constraint_axis=(True, False, False), mirror=True, use_proportional_edit=False)
context.space_data.overlay.show_curve_normals = True
context.space_data.overlay.normals_length = 0.2
2021-04-05 18:34:53 +02:00
context.scene.anim_cycle_settings.path_to_follow = curve
2021-04-05 01:35:12 +02:00
## back to objct mode ?
# bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
2021-04-06 18:30:25 +02:00
## TODO replace this part with >> create_follow_path_constraint(ob, curve)
2021-04-05 01:35:12 +02:00
if root.name == 'root' and root.location != (0,0,0):
old_loc = root.location
root.location = (0,0,0)
print(f'root moved from {old_loc} to (0,0,0) to counter curve offset')
# make follow path constraint
const = root.constraints.new('FOLLOW_PATH')
const.target = curve
# axis only in this case, should be in addon to prefs
const.forward_axis = 'FORWARD_X' # 'TRACK_NEGATIVE_Y'
const.use_curve_follow = True
return {"FINISHED"}
2021-04-06 18:30:25 +02:00
class UAC_OT_create_follow_path(bpy.types.Operator):
bl_idname = "anim.create_follow_path"
bl_label = "Create follow path constraint"
bl_description = "Create follow path targeting curve in field"
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
create_follow_path_constraint(ob, curve)
return {"FINISHED"}
2021-04-05 01:39:27 +02:00
class UAC_OT_snap_curve_to_ground(bpy.types.Operator):
2021-04-05 01:35:12 +02:00
bl_idname = "anim.snap_curve_to_ground"
bl_label = "snap_curve_to_ground"
bl_description = "snap curve"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'ARMATURE'
def execute(self, context):
2021-04-06 18:30:25 +02:00
err = snap_curve()
if err:
self.report({err[0]}, err[1])
if err[0] == 'ERROR':
return {"CANCELLED"}
2021-04-05 01:35:12 +02:00
return {"FINISHED"}
classes=(
2021-04-05 01:39:27 +02:00
UAC_OT_create_curve_path,
2021-04-06 18:30:25 +02:00
UAC_OT_create_follow_path,
2021-04-05 01:39:27 +02:00
UAC_OT_snap_curve_to_ground,
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()