no need for cycle and better snap curve
1.5.0 - changed: no need to have a cycle on fcurve to bake keys anymore - changed: snap curve does not create a curve copy - added: allow to directly snap selected curve (`ctrl + Click` to keep shrinkwarp modfifier, need to apply to affect object) - fixed: error when going in curve edit from object modemaster
parent
578e0d7266
commit
c98bb520b8
|
@ -1,5 +1,12 @@
|
|||
# Changelog
|
||||
|
||||
1.5.0
|
||||
|
||||
- changed: no need to have a cycle on fcurve to bake keys anymore
|
||||
- changed: snap curve does not create a curve copy
|
||||
- added: allow to directly snap selected curve (`ctrl + Click` to keep shrinkwarp modfifier, need to apply to affect object)
|
||||
- fixed: error when going in curve edit from object mode
|
||||
|
||||
1.4.4
|
||||
|
||||
- changed: default start to 100
|
||||
|
|
|
@ -36,22 +36,18 @@ def bake_cycle(on_selection=True, end=None):
|
|||
last = max(all_keys) # int(max(all_keys))
|
||||
offset = last - first
|
||||
|
||||
for fcu in act.fcurves:
|
||||
|
||||
## if a curve is not cycled don't touch
|
||||
if not [m for m in fcu.modifiers if m.type == 'CYCLES']:
|
||||
ct_no_cycle += 1
|
||||
continue
|
||||
|
||||
if debug: print(fcu.data_path, 'has cycle')
|
||||
for fcu in fn.get_only_pose_keyable_fcurves(obj, action=act):
|
||||
#-# old -- filter only on fcurve that have a cycle modifier (maybe as an option)
|
||||
# if not [m for m in fcu.modifiers if m.type == 'CYCLES']:
|
||||
# ct_no_cycle += 1
|
||||
# continue
|
||||
|
||||
#-# only on location :
|
||||
# if not fcu.data_path.endswith('.location'):
|
||||
# continue
|
||||
|
||||
# prop = fcu.data_path.split('.')[-1]
|
||||
|
||||
b_name = fcu.data_path.split('"')[1]
|
||||
if debug: print(b_name, 'has cycle')
|
||||
|
||||
pb = obj.pose.bones.get(b_name)
|
||||
if not pb:
|
||||
print(f'{b_name} is invalid')
|
||||
|
|
|
@ -104,7 +104,7 @@ class AW_OT_remove_follow_path(bpy.types.Operator):
|
|||
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_description = "Snap curve to ground determine in field"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
|
@ -119,6 +119,34 @@ class AW_OT_snap_curve_to_ground(bpy.types.Operator):
|
|||
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"
|
||||
|
@ -130,6 +158,7 @@ class AW_OT_edit_curve(bpy.types.Operator):
|
|||
return context.object and context.object.type == 'ARMATURE'
|
||||
|
||||
def execute(self, context):
|
||||
ob = context.object
|
||||
b = context.active_pose_bone
|
||||
curve = None
|
||||
|
||||
|
@ -139,7 +168,7 @@ class AW_OT_edit_curve(bpy.types.Operator):
|
|||
|
||||
# get from 'root' bone
|
||||
if not curve:
|
||||
curve, _const = fn.get_follow_curve_from_armature(context.object)
|
||||
curve, _const = fn.get_follow_curve_from_armature(ob)
|
||||
if isinstance(curve, str):
|
||||
self.report({curve}, _const)
|
||||
if curve == 'ERROR':
|
||||
|
@ -150,7 +179,9 @@ class AW_OT_edit_curve(bpy.types.Operator):
|
|||
# curve context.mode -> EDIT_CURVE
|
||||
|
||||
# Deselect armature object
|
||||
b.id_data.select_set(False)
|
||||
|
||||
# b.id_data.select_set(False)
|
||||
ob.select_set(False)
|
||||
return {"FINISHED"}
|
||||
|
||||
class AW_OT_go_to_object(bpy.types.Operator):
|
||||
|
@ -235,6 +266,7 @@ 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
|
||||
|
|
|
@ -4,7 +4,7 @@ bl_info = {
|
|||
"name": "Auto Walk",
|
||||
"description": "Develop a walk/run cycles along a curve and pin feets",
|
||||
"author": "Samuel Bernou",
|
||||
"version": (1, 4, 4),
|
||||
"version": (1, 5, 0),
|
||||
"blender": (3, 0, 0),
|
||||
"location": "View3D",
|
||||
"warning": "",
|
||||
|
|
170
fn.py
170
fn.py
|
@ -389,66 +389,98 @@ def create_follow_path_constraint(ob, curve, follow_curve=True):
|
|||
const.use_curve_follow = True
|
||||
return curve, const
|
||||
|
||||
def snap_curve():
|
||||
def shrinkwrap_on_object(source, target, apply=True):
|
||||
# shrinkwrap or cast on ground
|
||||
mod = source.modifiers.new('Shrinkwrap', 'SHRINKWRAP')
|
||||
# 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
|
||||
mod.target = target
|
||||
|
||||
if apply:
|
||||
# Apply and decimate
|
||||
switch = False
|
||||
if bpy.context.mode == 'EDIT_CURVE':
|
||||
switch = True
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.ops.object.modifier_apply({'object': source}, modifier="Shrinkwrap", report=False)
|
||||
if switch:
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
|
||||
def snap_curve(create_copy=False):
|
||||
obj = bpy.context.object
|
||||
gnd = get_gnd()
|
||||
if not gnd:
|
||||
return
|
||||
|
||||
curve = const = None
|
||||
if obj.type == 'ARMATURE':
|
||||
curve, const = get_follow_curve_from_armature(obj)
|
||||
|
||||
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')
|
||||
|
||||
# get curve from field
|
||||
if not curve:
|
||||
curve, const = create_follow_path_constraint(obj, to_follow)
|
||||
if isinstance(curve, str):
|
||||
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')
|
||||
|
||||
gnd = get_gnd()
|
||||
if not gnd:
|
||||
return
|
||||
|
||||
|
||||
curve_act = None
|
||||
anim_frame = None
|
||||
|
||||
# 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
|
||||
if create_copy:
|
||||
# get curve from field
|
||||
if not curve and not to_follow:
|
||||
return ('ERROR', f'No curve pointed by "Path" filed')
|
||||
|
||||
if not curve:
|
||||
curve, const = create_follow_path_constraint(obj, to_follow)
|
||||
if isinstance(curve, str):
|
||||
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')
|
||||
|
||||
# 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
|
||||
# delete old snap
|
||||
bpy.data.objects.remove(curve)
|
||||
# assign old curve as main one
|
||||
curve = org_curve
|
||||
|
||||
# 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)
|
||||
|
||||
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'
|
||||
if curve_act:
|
||||
nc.data.animation_data_create()
|
||||
nc.data.animation_data.action = curve_act
|
||||
if anim_frame:
|
||||
nc.data.path_duration = anim_frame
|
||||
if org_curve:
|
||||
const.target = org_curve
|
||||
|
||||
curve.users_collection[0].objects.link(nc)
|
||||
# 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
|
||||
# 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'
|
||||
|
||||
if curve_act:
|
||||
nc.data.animation_data_create()
|
||||
nc.data.animation_data.action = curve_act
|
||||
if anim_frame:
|
||||
nc.data.path_duration = anim_frame
|
||||
|
||||
curve.users_collection[0].objects.link(nc)
|
||||
|
||||
else:
|
||||
if not curve:
|
||||
return ('ERROR', 'Path not found')
|
||||
nc = curve
|
||||
|
||||
## If object mode is Curve subdivide it (TODO if nurbs needs conversion)
|
||||
#-# subdivide the curve (if curve is not nurbs)
|
||||
|
@ -458,18 +490,7 @@ def snap_curve():
|
|||
# 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')
|
||||
# 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
|
||||
mod.target = gnd
|
||||
|
||||
# Apply and decimate
|
||||
bpy.ops.object.modifier_apply({'object': nc}, modifier="Shrinkwrap", report=False)
|
||||
shrinkwrap_on_object(nc, gnd)
|
||||
bpy.context.scene.anim_cycle_settings.path_to_follow = nc
|
||||
# return 0, nc
|
||||
|
||||
|
@ -646,10 +667,11 @@ def remove_all_cycles_modifier(ob=None):
|
|||
print(f'Remove cyclic modifiers on {ct} fcurve(s)')
|
||||
return ct
|
||||
|
||||
def create_cycle_modifiers(ob=None):
|
||||
ob = ob or bpy.context.object
|
||||
def get_only_pose_keyable_fcurves(ob, action=None):
|
||||
'''Can action providing another action (must be for the same object)'''
|
||||
|
||||
# skip bones that are on protected layers ?
|
||||
act = action or ob.animation_data.action
|
||||
## skip bones that are on protected layers ?
|
||||
# protected = [i for i, l in enumerate(ob.data.layers_protected) if l]
|
||||
# for b in ob.data.bones:
|
||||
# if b.use_deform: # don't affect deform bones
|
||||
|
@ -657,24 +679,34 @@ def create_cycle_modifiers(ob=None):
|
|||
## b_layers = [i for i, l in enumerate(b.layers) if l]
|
||||
|
||||
name_list = [b.name for b in ob.data.bones] # if not b.use_deform (too limiting)
|
||||
|
||||
fcus = []
|
||||
re_prefix = re.compile(r'^(mch|def|org|vis|fld|ctp)[\._-]', flags=re.I)
|
||||
ct = 0
|
||||
for fc in ob.animation_data.action.fcurves:
|
||||
if [m for m in fc.modifiers if m.type == 'CYCLES']:
|
||||
# skip already existing modifier
|
||||
continue
|
||||
if not '"' in fc.data_path:
|
||||
continue
|
||||
for fc in act.fcurves:
|
||||
# skip offset
|
||||
if fc.data_path.endswith('.offset') and 'constraint' in fc.data_path:
|
||||
continue
|
||||
|
||||
# skip fcus that are not bones
|
||||
if not '"' in fc.data_path:
|
||||
continue
|
||||
|
||||
b_name = fc.data_path.split('"')[1]
|
||||
if b_name not in name_list:
|
||||
continue
|
||||
|
||||
if re_prefix.match(b_name):
|
||||
continue
|
||||
fcus.append(fc)
|
||||
|
||||
return fcus
|
||||
|
||||
if b_name not in name_list:
|
||||
def create_cycle_modifiers(ob=None):
|
||||
ob = ob or bpy.context.object
|
||||
ct = 0
|
||||
keyable_fcurves = get_only_pose_keyable_fcurves(ob)
|
||||
for fc in keyable_fcurves:
|
||||
if [m for m in fc.modifiers if m.type == 'CYCLES']:
|
||||
# skip if already existing modifier
|
||||
continue
|
||||
# print(f'Adding cycle modifier {fc.data_path}')
|
||||
_m = fc.modifiers.new(type='CYCLES')
|
||||
|
|
|
@ -66,7 +66,13 @@ class AW_PT_walk_cycle_anim_panel(bpy.types.Panel):
|
|||
box.prop_search(settings, "gnd", context.scene, "objects")
|
||||
|
||||
row = box.row()
|
||||
row.operator('autowalk.snap_curve_to_ground', text='Snap curve to ground', icon='SNAP_ON')
|
||||
if ob and ob.type == 'ARMATURE':
|
||||
row.operator('autowalk.snap_curve_to_ground', text='Snap Curve To Ground', icon='SNAP_ON')
|
||||
elif ob and ob.type == 'CURVE':
|
||||
row.operator('autowalk.snap_selected_curve', text='Snap Selected Curve To Ground', icon='SNAP_ON')
|
||||
else:
|
||||
row.label(text='Select curve or armature to snap', icon='INFO')
|
||||
|
||||
row.active = bool(settings.gnd)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue