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
|
|
|
|
|
2022-03-31 17:07:04 +02:00
|
|
|
def create_follow_path_constraint(ob, curve, follow_curve=True):
|
2022-03-29 18:46:33 +02:00
|
|
|
prefs = fn.get_addon_prefs()
|
2022-03-31 17:07:04 +02:00
|
|
|
root_name = prefs.tgt_bone
|
|
|
|
root = ob.pose.bones.get(root_name)
|
|
|
|
|
2021-04-06 18:30:25 +02:00
|
|
|
if not root:
|
2022-03-31 17:07:04 +02:00
|
|
|
return ('ERROR', f'posebone {root_name} not found in armature {ob.name} check addon preferences to change name')
|
2021-04-06 18:30:25 +02:00
|
|
|
|
|
|
|
# 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()
|
2022-03-31 17:07:04 +02:00
|
|
|
if root.name == ('world', 'root') and root.location != (0,0,0):
|
2021-04-06 18:30:25 +02:00
|
|
|
old_loc = root.location
|
|
|
|
root.location = (0,0,0)
|
2022-03-31 17:07:04 +02:00
|
|
|
print(f'root moved from {old_loc} to (0,0,0) to counter follow curve offset')
|
2021-04-06 18:30:25 +02:00
|
|
|
|
|
|
|
const = root.constraints.new('FOLLOW_PATH')
|
|
|
|
const.target = curve
|
|
|
|
# axis only in this case, should be in addon to prefs
|
2022-03-31 17:07:04 +02:00
|
|
|
|
|
|
|
## determine which axis to use... maybe found orientation in world space from matrix_basis ?
|
|
|
|
root_world_base_direction = root.bone.matrix_local @ fn.get_direction_vector_from_enum(bpy.context.scene.anim_cycle_settings.forward_axis)
|
|
|
|
const.forward_axis = fn.orentation_track_from_vector(root_world_base_direction) # 'TRACK_NEGATIVE_Y' # bpy.context.scene.anim_cycle_settings.forward_axis # 'FORWARD_X'
|
|
|
|
print('const.forward_axis: ', const.forward_axis)
|
2021-04-06 18:30:25 +02:00
|
|
|
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)
|
2022-03-31 17:07:04 +02:00
|
|
|
if isinstance(curve, str):
|
2021-04-06 18:30:25 +02:00
|
|
|
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
|
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
|
|
|
|
|
|
|
|
create_follow_path_constraint(ob, curve)
|
|
|
|
|
|
|
|
# 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"}
|
|
|
|
|
2021-04-06 18:30:25 +02:00
|
|
|
|
|
|
|
class UAC_OT_create_follow_path(bpy.types.Operator):
|
|
|
|
bl_idname = "anim.create_follow_path"
|
2022-04-01 12:12:05 +02:00
|
|
|
bl_label = "Create Follow Path Constraint"
|
2021-04-06 18:30:25 +02:00
|
|
|
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
|
2022-03-31 17:07:04 +02:00
|
|
|
err, const = create_follow_path_constraint(ob, curve)
|
|
|
|
if isinstance(err, str):
|
|
|
|
self.report({'ERROR'}, err)
|
|
|
|
return {"CANCELLED"}
|
2021-04-06 18:30:25 +02:00
|
|
|
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"
|
2022-04-01 12:12:05 +02:00
|
|
|
bl_description = "Snap Curve"
|
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):
|
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()
|