remove ground feet and some bugfixes

1.3.2

- removed: ground feet (added initial support for override compatibility)
- fixed: error with key and jump
master
Pullusb 2022-04-21 14:25:43 +02:00
parent cd23f50c55
commit 072a483188
7 changed files with 101 additions and 55 deletions

View File

@ -1,10 +1,14 @@
# Changelog # Changelog
1.3.2
- removed: ground feet (added initial support for override compatibility wip)
- fixed: error with key and jump
1.3.1 1.3.1
- added: button to remove follow path constraint - added: button to remove follow path constraint
1.3.0 1.3.0
- changed: rename addon: `unfold anim cycle` >> `auto walk` - changed: rename addon: `unfold anim cycle` >> `auto walk`

View File

@ -184,7 +184,7 @@ def step_path():
class AW_OT_bake_cycle_and_step(bpy.types.Operator): class AW_OT_bake_cycle_and_step(bpy.types.Operator):
bl_idname = "autowalk.bake_cycle_and_step" bl_idname = "autowalk.bake_cycle_and_step"
bl_label = "Bake key and step path " bl_label = "Bake key and step path"
bl_description = "Bake the key and step the animation path according to those key\ bl_description = "Bake the key and step the animation path according to those key\
\n(duplicate to a new 'baked' action)" \n(duplicate to a new 'baked' action)"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@ -488,7 +488,7 @@ class AW_OT_step_back_actions(bpy.types.Operator):
if a == context.object.animation_data.action: if a == context.object.animation_data.action:
layout.label(text=f'(current) >> {a.name}', icon='ACTION') layout.label(text=f'(current) >> {a.name}', icon='ACTION')
continue continue
layout.operator('AW_OT_set_action', text=a.name, icon='ACTION').act_name = a.name layout.operator('autowalk.set_action', text=a.name, icon='ACTION').act_name = a.name
def execute(self, context): def execute(self, context):
return {"FINISHED"} return {"FINISHED"}

View File

@ -1,6 +1,7 @@
import bpy import bpy
import bmesh import re
from mathutils import Vector from mathutils import Vector
from . import fn
def raycast_from_loc_to_obj(src, tgt, direction=None, dg=None): def raycast_from_loc_to_obj(src, tgt, direction=None, dg=None):
@ -55,6 +56,12 @@ def worldspace_move_posebone(b, vec, is_target=False):
to_space='POSE') to_space='POSE')
return target_vec return target_vec
def set_subsurf_viewport(ob, show=True):
for m in ob.modifiers:
# if m.type in ('SUBSURF', 'TRIANGULATE'):
if m.type == 'SUBSURF':
m.show_viewport = show
def snap_foot(pb, gnd): def snap_foot(pb, gnd):
'''Get posebone and ground to touch''' '''Get posebone and ground to touch'''
@ -63,29 +70,69 @@ def snap_foot(pb, gnd):
print('arm: ', arm) print('arm: ', arm)
# find tip bone : # find tip bone :
tip = [p for p in pb.children_recursive if p.name.startswith('DEF')][-1] # tip = [p for p in pb.children_recursive if p.name.startswith('DEF')][-1]
def_name = f'DEF.{pb.name}'
tip = arm.pose.bones.get(def_name)
## guess fallbacks
if not tip:
## Regex for potential separator variance
# "." dot separator is not literal anymore : DEF.foot\.L
parttern = f'DEF.{re.escape(pb.name)}'
for pb in arm.pose.bones:
if re.search(parttern, pb.name):
tip = pb
break
if not tip:
## name proximity
best_ratio = 0
for pb in arm.pose.bones:
if (ratio := fn.fuzzy_match_ratio(pb.name, def_name)) > best_ratio:
tip = pb # assign tip
best_ratio = ratio
if best_ratio < 0.85:
# no bones name is close enough
return ('ERROR', f'no bone name is close enough of "{def_name}" (best was "{tip}" with close-ratio {best_ratio})')
print('tip: ', tip) print('tip: ', tip)
# get deformed object VG (find skinned mesh) # get deformed object VG (find skinned mesh)
ob = None ob = None
for o in arm.users_collection[0].all_objects:
for o in arm.proxy_collection.instance_collection.all_objects:
if o.type != 'MESH': if o.type != 'MESH':
continue continue
if not 'body' in o.name: # might be not needed if filter on vertex group
continue
if not o.vertex_groups.get(tip.name):
# has vertex_group pointing to the bone
continue
for m in o.modifiers: for m in o.modifiers:
if m.type == 'ARMATURE': if m.type == 'ARMATURE':
# print(o.name, m.object) # print(o.name, m.object)
if m.object == arm.proxy: # if point to orignal rig if m.object == arm: # if point to orignal rig
## here we want body, not body_deform
if not 'body' in o.name:
continue
if '_deform' in o.name:
continue
ob = o ob = o
break break
## find object with old proxy method
# for o in arm.proxy_collection.instance_collection.all_objects:
# if o.type != 'MESH':
# continue
# for m in o.modifiers:
# if m.type == 'ARMATURE':
# # print(o.name, m.object)
# if m.object == arm.proxy: # if point to orignal rig
# ## here we want body, not body_deform
# if not 'body' in o.name:
# continue
# if '_deform' in o.name:
# continue
# ob = o
# break
if not ob: if not ob:
print('ERROR', 'no skinned mesh found') return ('ERROR', 'no skinned mesh found')
return
print('check skinning of', ob.name) print('check skinning of', ob.name)
### MESH baking ### MESH baking
@ -119,10 +166,7 @@ def snap_foot(pb, gnd):
#-# Get Vertices position for a specific vertex group if over weight limit #-# Get Vertices position for a specific vertex group if over weight limit
#-# (Does not work if a subdivision modifier is on) #-# (Does not work if a subdivision modifier is on)
for m in ob.modifiers: set_subsurf_viewport(ob, show=False)
# if m.type in ('SUBSURF', 'TRIANGULATE'):
if m.type == 'SUBSURF':
m.show_viewport = False
dg = bpy.context.evaluated_depsgraph_get() dg = bpy.context.evaluated_depsgraph_get()
obeval = ob.evaluated_get(dg) #.copy() obeval = ob.evaluated_get(dg) #.copy()
@ -142,14 +186,21 @@ def snap_foot(pb, gnd):
vg = obeval.vertex_groups[tip.name] vg = obeval.vertex_groups[tip.name]
world_co = [] world_co = []
for idx, vert in enumerate(bake_mesh.vertices): for idx, vert in enumerate(bake_mesh.vertices):
print('idx, vert: ', idx, vert)
grp_indexes = [i.group for i in vert.groups] grp_indexes = [i.group for i in vert.groups]
print('grp_indexes: ', grp_indexes)
print('vg.index in grp_indexes: ', vg.index in grp_indexes)
if vg.index in grp_indexes:
print(':: vg.weight(idx) > 0.5: ', vg.weight(idx) > 0.5)
# print()
if vg.index in grp_indexes and vg.weight(idx) > 0.5: if vg.index in grp_indexes and vg.weight(idx) > 0.5:
ct +=1 ct +=1
world_co.append(ob.matrix_world @ vert.co) world_co.append(ob.matrix_world @ vert.co)
if not ct: if not ct:
print('ERROR', 'No vertices found') set_subsurf_viewport(ob)
return obeval.to_mesh_clear()
return ('ERROR', 'No vertices found')
#-# # list comprehension #-# # list comprehension
# verts = [vert for vid, vert in enumerate(bake_mesh.vertices) \ # verts = [vert for vid, vert in enumerate(bake_mesh.vertices) \
@ -184,8 +235,10 @@ def snap_foot(pb, gnd):
# empty_at(contact, size=0.2) # empty_at(contact, size=0.2)
if not dists and not updists: if not dists and not updists:
print('ERROR', 'raycast could not found contact') set_subsurf_viewport(ob)
return obeval.to_mesh_clear()
return ('ERROR', 'raycast could not found contact')
# move bones by the minimal amount. # move bones by the minimal amount.
if updists: if updists:
@ -200,14 +253,8 @@ def snap_foot(pb, gnd):
worldspace_move_posebone(pb, vec) worldspace_move_posebone(pb, vec)
print('INFO', f'move down by {move}') print('INFO', f'move down by {move}')
## restore ## restore
set_subsurf_viewport(ob)
for m in ob.modifiers:
if m.type == 'SUBSURF':
# if m.type in ('SUBSURF', 'TRIANGULATE'):
m.show_viewport = True
obeval.to_mesh_clear() obeval.to_mesh_clear()
@ -233,9 +280,9 @@ def snap_feet():
for pb in bpy.context.selected_pose_bones: for pb in bpy.context.selected_pose_bones:
## find the foot bones. ## find the foot bones.
if '_shoe' in pb.name: if 'foot' in pb.name:
# get pb lowest surface deformed point # get pb lowest surface deformed point
snap_foot(pb, gnd) return snap_foot(pb, gnd)
class AW_OT_contact_to_ground(bpy.types.Operator): class AW_OT_contact_to_ground(bpy.types.Operator):
bl_idname = "autowalk.contact_to_ground" bl_idname = "autowalk.contact_to_ground"

View File

@ -82,7 +82,7 @@ class AW_OT_world_space_paste_next(bpy.types.Operator):
self.report({'WARNING'}, 'No next frame to jump on') self.report({'WARNING'}, 'No next frame to jump on')
return {'FINISHED'} return {'FINISHED'}
context.scene.frame_current = new_frame context.scene.frame_current = int(new_frame)
return {"FINISHED"} return {"FINISHED"}
class AW_OT_world_space_paste_next_frame(bpy.types.Operator): class AW_OT_world_space_paste_next_frame(bpy.types.Operator):

View File

@ -28,27 +28,19 @@ Sidebar > Anim > unfold anim cycle
### TODO ### TODO
- Better A-B position setup:
- auto-retime animation to prioritize speed over base cycle velocity
- create nurb path instead of curve
- Smoothing keys after last freezed to avoid too much gap "pose click".
<!--
### DONE
- auto-determine foot bone to use for distance reference
- Expose methods to go back in action history
- pin feets: - pin feets:
- iterate in reverse in keys when pinning so last foot position is correct - iterate in reverse in keys when pinning so last foot position is correct
- create intermediate keys (at each frame when necessary) to prevent lateral sliding on curved path - create intermediate keys (at each frame when necessary) to prevent lateral sliding on curved path
(maybe expose as an option... not needed if path is straight for example... or auto detect if path is full straight at the moment of pin ops) (maybe expose as an option... not needed if path is straight for example... or auto detect if path is full straight at the moment of pin ops)
- Expose methods to go back in action history
*things to consider*:
- Expose foot ?
- Store path animation on a separate action (but that mean NLA hadto be used every time)
Bonus:
- Use position A-B method to generate curve (with retimed animation to prioritize speed over fidelity)
- auto-determine foot bone to use for distance reference
- create nurb path instead of curve
- Smoothing keys after last freezed to avoid too much gap "pose click".
<!--
### DONE
- align curve to root - align curve to root
- Put curve forward motion on bones modifier's offset value as negative time offset (instead of using curve ) - Put curve forward motion on bones modifier's offset value as negative time offset (instead of using curve )
--> -->

View File

@ -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, 3, 1), "version": (1, 3, 2),
"blender": (3, 0, 0), "blender": (3, 0, 0),
"location": "View3D", "location": "View3D",
"warning": "", "warning": "",

View File

@ -94,10 +94,10 @@ class AW_PT_walk_cycle_anim_panel(bpy.types.Panel):
row = col.row(align=True) row = col.row(align=True)
row.prop(settings, "start_frame", text='Start') row.prop(settings, "start_frame", text='Start')
row.prop(settings, "end_frame", text='End') row.prop(settings, "end_frame", text='End')
col.operator('autowalk.animate_path', text='Animate Forward Motion', icon='ANIM') col.operator('autowalk.animate_path', text='Move On Curve', icon='ANIM') # Animate Forward Motion
row=col.row() row=col.row()
row.operator('autowalk.adjust_animation_length', text='Adjust Forward Speed', icon='MOD_TIME') row.operator('autowalk.adjust_animation_length', text='Adjust Speed', icon='MOD_TIME') # Adjust Forward Speed
## Bake cycle (on selected) ## Bake cycle (on selected)
box = layout.box() box = layout.box()
@ -127,13 +127,16 @@ class AW_PT_anim_tools_panel(bpy.types.Panel):
bl_space_type = "VIEW_3D" bl_space_type = "VIEW_3D"
bl_region_type = "UI" bl_region_type = "UI"
bl_category = "Walk" bl_category = "Walk"
bl_label = "Walk Tools" bl_label = "Pin Tools"
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.operator('autowalk.contact_to_ground', text='Ground selected feet', icon='SNAP_OFF') ## FIXME: need to fix vertices get
# layout.operator('autowalk.contact_to_ground', text='Ground selected feet', icon='SNAP_OFF')
row = layout.row() row = layout.row()
row.operator('autowalk.world_space_copy', text='Copy Pose', icon='COPYDOWN') row.operator('autowalk.world_space_copy', text='Copy Pose', icon='COPYDOWN')
# row.operator('autowalk.world_space_paste', text='Paste', icon='PASTEDOWN') # row.operator('autowalk.world_space_paste', text='Paste', icon='PASTEDOWN')
# row = layout.row(align=False) # row = layout.row(align=False)