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
|
# 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
|
1.4.4
|
||||||
|
|
||||||
- changed: default start to 100
|
- 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))
|
last = max(all_keys) # int(max(all_keys))
|
||||||
offset = last - first
|
offset = last - first
|
||||||
|
|
||||||
for fcu in act.fcurves:
|
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
|
||||||
|
|
||||||
## 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')
|
|
||||||
#-# only on location :
|
#-# only on location :
|
||||||
# if not fcu.data_path.endswith('.location'):
|
# if not fcu.data_path.endswith('.location'):
|
||||||
# continue
|
# continue
|
||||||
|
|
||||||
# prop = fcu.data_path.split('.')[-1]
|
|
||||||
|
|
||||||
b_name = 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)
|
pb = obj.pose.bones.get(b_name)
|
||||||
if not pb:
|
if not pb:
|
||||||
print(f'{b_name} is invalid')
|
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):
|
class AW_OT_snap_curve_to_ground(bpy.types.Operator):
|
||||||
bl_idname = "autowalk.snap_curve_to_ground"
|
bl_idname = "autowalk.snap_curve_to_ground"
|
||||||
bl_label = "Snap Curve"
|
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"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -119,6 +119,34 @@ class AW_OT_snap_curve_to_ground(bpy.types.Operator):
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
return {"FINISHED"}
|
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):
|
class AW_OT_edit_curve(bpy.types.Operator):
|
||||||
bl_idname = "autowalk.edit_curve"
|
bl_idname = "autowalk.edit_curve"
|
||||||
bl_label = "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'
|
return context.object and context.object.type == 'ARMATURE'
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
ob = context.object
|
||||||
b = context.active_pose_bone
|
b = context.active_pose_bone
|
||||||
curve = None
|
curve = None
|
||||||
|
|
||||||
|
@ -139,7 +168,7 @@ class AW_OT_edit_curve(bpy.types.Operator):
|
||||||
|
|
||||||
# get from 'root' bone
|
# get from 'root' bone
|
||||||
if not curve:
|
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):
|
if isinstance(curve, str):
|
||||||
self.report({curve}, _const)
|
self.report({curve}, _const)
|
||||||
if curve == 'ERROR':
|
if curve == 'ERROR':
|
||||||
|
@ -150,7 +179,9 @@ class AW_OT_edit_curve(bpy.types.Operator):
|
||||||
# curve context.mode -> EDIT_CURVE
|
# curve context.mode -> EDIT_CURVE
|
||||||
|
|
||||||
# Deselect armature object
|
# Deselect armature object
|
||||||
b.id_data.select_set(False)
|
|
||||||
|
# b.id_data.select_set(False)
|
||||||
|
ob.select_set(False)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
class AW_OT_go_to_object(bpy.types.Operator):
|
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_create_follow_path,
|
||||||
AW_OT_remove_follow_path,
|
AW_OT_remove_follow_path,
|
||||||
AW_OT_snap_curve_to_ground,
|
AW_OT_snap_curve_to_ground,
|
||||||
|
AW_OT_snap_selected_curve,
|
||||||
AW_OT_edit_curve,
|
AW_OT_edit_curve,
|
||||||
AW_OT_go_to_object,
|
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
|
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",
|
"name": "Auto Walk",
|
||||||
"description": "Develop a walk/run cycles along a curve and pin feets",
|
"description": "Develop a walk/run cycles along a curve and pin feets",
|
||||||
"author": "Samuel Bernou",
|
"author": "Samuel Bernou",
|
||||||
"version": (1, 4, 4),
|
"version": (1, 5, 0),
|
||||||
"blender": (3, 0, 0),
|
"blender": (3, 0, 0),
|
||||||
"location": "View3D",
|
"location": "View3D",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
|
|
168
fn.py
168
fn.py
|
@ -389,66 +389,98 @@ def create_follow_path_constraint(ob, curve, follow_curve=True):
|
||||||
const.use_curve_follow = True
|
const.use_curve_follow = True
|
||||||
return curve, const
|
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
|
obj = bpy.context.object
|
||||||
|
gnd = get_gnd()
|
||||||
|
if not gnd:
|
||||||
|
return
|
||||||
|
|
||||||
curve = const = None
|
curve = const = None
|
||||||
if obj.type == 'ARMATURE':
|
if obj.type == 'ARMATURE':
|
||||||
curve, const = get_follow_curve_from_armature(obj)
|
curve, const = get_follow_curve_from_armature(obj)
|
||||||
|
|
||||||
to_follow = bpy.context.scene.anim_cycle_settings.path_to_follow
|
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
|
curve_act = None
|
||||||
anim_frame = None
|
anim_frame = None
|
||||||
|
|
||||||
# if it's on a snap curve, fetch original
|
if create_copy:
|
||||||
if '_snap' in curve.name:
|
# get curve from field
|
||||||
org_name = re.sub(r'_snap\.?\d{0,3}$', '', curve.name)
|
if not curve and not to_follow:
|
||||||
org_curve = bpy.context.scene.objects.get(org_name)
|
return ('ERROR', f'No curve pointed by "Path" filed')
|
||||||
|
|
||||||
if org_curve:
|
if not curve:
|
||||||
const.target = org_curve
|
curve, const = create_follow_path_constraint(obj, to_follow)
|
||||||
|
if isinstance(curve, str):
|
||||||
|
return (curve, const) # those are error message
|
||||||
|
|
||||||
# keep action
|
# if obj.type == 'CURVE':
|
||||||
if curve.data.animation_data and curve.data.animation_data.action:
|
# return ('ERROR', f'Select the armature related to curve {obj.name}')
|
||||||
curve_act = curve.data.animation_data.action
|
# else:
|
||||||
|
# return ('ERROR', 'Not an armature object')
|
||||||
|
|
||||||
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)
|
# 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
|
||||||
|
|
||||||
|
# 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)
|
## If object mode is Curve subdivide it (TODO if nurbs needs conversion)
|
||||||
#-# subdivide the curve (if curve is not nurbs)
|
#-# subdivide the curve (if curve is not nurbs)
|
||||||
|
@ -458,18 +490,7 @@ def snap_curve():
|
||||||
# bpy.ops.curve.subdivide(number_cuts=4)
|
# bpy.ops.curve.subdivide(number_cuts=4)
|
||||||
# bpy.ops.object.mode_set(mode='OBJECT')
|
# bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
# shrinkwrap or cast on ground
|
shrinkwrap_on_object(nc, gnd)
|
||||||
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)
|
|
||||||
bpy.context.scene.anim_cycle_settings.path_to_follow = nc
|
bpy.context.scene.anim_cycle_settings.path_to_follow = nc
|
||||||
# return 0, nc
|
# return 0, nc
|
||||||
|
|
||||||
|
@ -646,10 +667,11 @@ def remove_all_cycles_modifier(ob=None):
|
||||||
print(f'Remove cyclic modifiers on {ct} fcurve(s)')
|
print(f'Remove cyclic modifiers on {ct} fcurve(s)')
|
||||||
return ct
|
return ct
|
||||||
|
|
||||||
def create_cycle_modifiers(ob=None):
|
def get_only_pose_keyable_fcurves(ob, action=None):
|
||||||
ob = ob or bpy.context.object
|
'''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]
|
# protected = [i for i, l in enumerate(ob.data.layers_protected) if l]
|
||||||
# for b in ob.data.bones:
|
# for b in ob.data.bones:
|
||||||
# if b.use_deform: # don't affect deform bones
|
# if b.use_deform: # don't affect deform bones
|
||||||
|
@ -657,25 +679,35 @@ def create_cycle_modifiers(ob=None):
|
||||||
## b_layers = [i for i, l in enumerate(b.layers) if l]
|
## 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)
|
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)
|
re_prefix = re.compile(r'^(mch|def|org|vis|fld|ctp)[\._-]', flags=re.I)
|
||||||
ct = 0
|
for fc in act.fcurves:
|
||||||
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
|
|
||||||
# skip offset
|
# skip offset
|
||||||
if fc.data_path.endswith('.offset') and 'constraint' in fc.data_path:
|
if fc.data_path.endswith('.offset') and 'constraint' in fc.data_path:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
b_name = fc.data_path.split('"')[1]
|
# skip fcus that are not bones
|
||||||
if re_prefix.match(b_name):
|
if not '"' in fc.data_path:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
b_name = fc.data_path.split('"')[1]
|
||||||
if b_name not in name_list:
|
if b_name not in name_list:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if re_prefix.match(b_name):
|
||||||
|
continue
|
||||||
|
fcus.append(fc)
|
||||||
|
|
||||||
|
return fcus
|
||||||
|
|
||||||
|
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}')
|
# print(f'Adding cycle modifier {fc.data_path}')
|
||||||
_m = fc.modifiers.new(type='CYCLES')
|
_m = fc.modifiers.new(type='CYCLES')
|
||||||
ct += 1
|
ct += 1
|
||||||
|
|
|
@ -66,7 +66,13 @@ class AW_PT_walk_cycle_anim_panel(bpy.types.Panel):
|
||||||
box.prop_search(settings, "gnd", context.scene, "objects")
|
box.prop_search(settings, "gnd", context.scene, "objects")
|
||||||
|
|
||||||
row = box.row()
|
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)
|
row.active = bool(settings.gnd)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue