Update
17
Scripts/Animation/MotionCapHelper/README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# MotionCapHelper
|
||||||
|
maya simple plugin for animator
|
||||||
|
|
||||||
|
# main feature:主要功能
|
||||||
|
animation curve smooth:平滑动画曲线
|
||||||
|
frame align:在参考物体的帧时间k选择物体的帧,并删除其他帧。(对齐帧)
|
||||||
|
regular expression:根据正则表达式过滤物体名,并创建(多个)显示层
|
||||||
|
animation rebuild:删除范围内外帧,偏移选中物体帧,手动提取关键帧,快速烘焙动画到另一个物体,fk转换ik,钉住世界位置,快速烘焙父子约束效果。
|
||||||
|
|
||||||
|
都是非常简单的功能,其中平滑曲线的代码来自https://blog.csdn.net/lulongfei172006/article/details/51493273/
|
||||||
|
|
||||||
|
# support maya version:支持版本
|
||||||
|
maya 2018+
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|

|
BIN
Scripts/Animation/MotionCapHelper/Thumbs.db
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import maya.cmds as cmds
|
||||||
|
objs = cmds.ls(selection = True,type = "dagNode",long = True)
|
||||||
|
|
||||||
|
selectobjflag = True
|
||||||
|
selectmultiobjflag = False
|
||||||
|
if len(objs) == 0:
|
||||||
|
selectobjflag = False
|
||||||
|
else:
|
||||||
|
if len(objs) != 1:
|
||||||
|
selectmultiobjflag = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if objs == None:
|
||||||
|
selectobjflag = False
|
||||||
|
|
||||||
|
curves = cmds.keyframe(query = True , selected = True , name = True)
|
||||||
|
print(type(curves))
|
||||||
|
selectkeyflag = True
|
||||||
|
if curves == None:
|
||||||
|
selectkeyflag = False
|
||||||
|
|
||||||
|
if selectobjflag:
|
||||||
|
if selectkeyflag:
|
||||||
|
nodes = set()
|
||||||
|
for cv in curves:
|
||||||
|
node = cmds.listConnections(cv)[0]
|
||||||
|
nodes.add(node)
|
||||||
|
cmds.select(clear = True)
|
||||||
|
cmds.select(nodes)
|
||||||
|
print("TRACE BACK OBJS FROM KEY: " + str(nodes))
|
||||||
|
else:
|
||||||
|
obj = cmds.ls(objs[0])[0]
|
||||||
|
|
||||||
|
layer = cmds.ls(cmds.listConnections(obj,t = "displayLayer"))
|
||||||
|
if len(layer) == 0:
|
||||||
|
print("this obj has no layer,or you have selected multiple objs.")
|
||||||
|
elif cmds.editDisplayLayerGlobals(cdl = True,q = True) == "defaultLayer":
|
||||||
|
print("no display layer selected,trace back won\'t work.")
|
||||||
|
else:
|
||||||
|
cmds.editDisplayLayerGlobals(cdl = layer[0])
|
||||||
|
|
||||||
|
if selectmultiobjflag:
|
||||||
|
print("TRACE BACK LAYER FROM OBJ: " + layer[0] + "BUT WITH MULTIPLE OBJS SELECTED,SO RESULE IS UNEXPECTED.")
|
||||||
|
else:
|
||||||
|
print("TRACE BACK LAYER FROM OBJ: " + layer[0] )
|
||||||
|
else :
|
||||||
|
print("no objs selected!")
|
BIN
Scripts/Animation/MotionCapHelper/preview.PNG
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
Scripts/Animation/MotionCapHelper/preview1.PNG
Normal file
After Width: | Height: | Size: 12 KiB |
5
Scripts/Animation/springmagic/HowToCompile.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
If you changed the source code, you have to recompile SpringMagic project
|
||||||
|
|
||||||
|
Uncomment this line in file springMagic.py
|
||||||
|
# Recompile SpringMagic module if modification has been made
|
||||||
|
dev.refresh(springmagic)
|
30
Scripts/Animation/springmagic/HowToInstall.txt
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
1. Unzip all files into same folder
|
||||||
|
|
||||||
|
2. Run Maya
|
||||||
|
|
||||||
|
3. Run command below in command bar with Python way
|
||||||
|
execfile(r'the folder path\springMagic.py')
|
||||||
|
example:
|
||||||
|
execfile(r'C:\Users\Chris\Scripts\springMagic.py')
|
||||||
|
|
||||||
|
keep the 'r' in front of your path to avoid IO Error may cause
|
||||||
|
|
||||||
|
Tested under Maya 2011 and above, you may need to install Pymel for Maya 2010 or elder build by yourself
|
||||||
|
|
||||||
|
Known Issue:
|
||||||
|
1. Error: Syntax error
|
||||||
|
Please check if you run the command in Python mode, you can click "mel" word to toggle between mel and python
|
||||||
|
|
||||||
|
2. # Error: ImportError: No module named pymel.core #
|
||||||
|
That means you are using old version of MAYA and have no Pymel installed, please download and install Pymel follow this page:
|
||||||
|
http://download.autodesk.com/us/maya/2011help/PyMel/install.html
|
||||||
|
|
||||||
|
3. Error: IOError: file <maya console> line 1: 2
|
||||||
|
It mean your path has some error when you run the execfile command, please double check what you typed
|
||||||
|
|
||||||
|
|
||||||
|
for more details visit my site
|
||||||
|
www.animbai.com
|
||||||
|
|
||||||
|
Yanbin
|
||||||
|
2017.10
|
2
Scripts/Animation/springmagic/Howto.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import springmagic
|
||||||
|
springmagic.main()
|
12
Scripts/Animation/springmagic/__init__.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# from springmagic.main import main
|
||||||
|
|
||||||
|
__version__ = "3.5a"
|
||||||
|
|
||||||
|
|
||||||
|
def version():
|
||||||
|
"""
|
||||||
|
Return the current version of the Spring Magic
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return __version__
|
961
Scripts/Animation/springmagic/core.py
Normal file
@ -0,0 +1,961 @@
|
|||||||
|
# - * - coding: utf - 8 - * -
|
||||||
|
# PEP8 formatting
|
||||||
|
|
||||||
|
#####################################################################################
|
||||||
|
#
|
||||||
|
# Spring Magic for Maya
|
||||||
|
#
|
||||||
|
# Calculate bone chain animation by settings, support collisions and wind force
|
||||||
|
# Can work with rigging controller as well
|
||||||
|
#
|
||||||
|
# Need pringMagic.ui file to work with
|
||||||
|
# This script need also icon file support, which should be put in same folder
|
||||||
|
#
|
||||||
|
# feel free to mail me redtank@outlook.com for any bug or issue
|
||||||
|
#
|
||||||
|
# Yanbin Bai
|
||||||
|
# 2021.02
|
||||||
|
#
|
||||||
|
#####################################################################################
|
||||||
|
|
||||||
|
import math
|
||||||
|
import logging
|
||||||
|
# import copy
|
||||||
|
|
||||||
|
import pymel.core as pm
|
||||||
|
import pymel.core.datatypes as dt
|
||||||
|
import maya.cmds as cmds
|
||||||
|
|
||||||
|
import Animation.springmagic.decorators as decorators
|
||||||
|
import Animation.springmagic.springMath as springMath
|
||||||
|
|
||||||
|
from Animation.springmagic.utility import *
|
||||||
|
|
||||||
|
from itertools import cycle
|
||||||
|
from itertools import chain
|
||||||
|
from weakref import WeakValueDictionary
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# spring magic
|
||||||
|
####################################
|
||||||
|
|
||||||
|
kWindObjectName = 'spring_wind'
|
||||||
|
kSpringProxySuffix = '_SpringProxy'
|
||||||
|
kCollisionPlaneSuffix = '_SpringColPlane'
|
||||||
|
kCapsuleNameSuffix = '_collision_capsule'
|
||||||
|
kNullSuffix = '_SpringNull'
|
||||||
|
# kTwistNullSuffix = '_SpringTwistNull'
|
||||||
|
|
||||||
|
|
||||||
|
class Spring:
|
||||||
|
|
||||||
|
def __init__(self, ratio=0.5, twistRatio=0.0, tension=0.0, extend=0.0, inertia=0.0):
|
||||||
|
|
||||||
|
self.ratio = ratio
|
||||||
|
self.twist_ratio = twistRatio
|
||||||
|
self.tension = tension
|
||||||
|
self.extend = extend
|
||||||
|
self.inertia = inertia
|
||||||
|
|
||||||
|
class SpringMagic:
|
||||||
|
|
||||||
|
def __init__(self, startFrame, endFrame, subDiv=1.0, isLoop=False, isPoseMatch=False, isCollision=False, isFastMove=False, wipeSubframe=True):
|
||||||
|
|
||||||
|
self.start_frame = startFrame
|
||||||
|
self.end_frame = endFrame
|
||||||
|
|
||||||
|
self.sub_div = subDiv
|
||||||
|
self.is_loop = isLoop
|
||||||
|
self.is_pose_match = isPoseMatch
|
||||||
|
self.is_fast_move = isFastMove
|
||||||
|
self.wipe_subframe = wipeSubframe
|
||||||
|
|
||||||
|
self.is_collision = isCollision
|
||||||
|
self.collision_planes_list = None
|
||||||
|
|
||||||
|
self.wind = None
|
||||||
|
|
||||||
|
class SpringData:
|
||||||
|
|
||||||
|
cur_position_locator = None
|
||||||
|
prev_position_locator = None
|
||||||
|
prev_grand_child_position_locator = None
|
||||||
|
|
||||||
|
_instances = WeakValueDictionary()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def Count(self):
|
||||||
|
return len(self._instances)
|
||||||
|
|
||||||
|
def __init__(self, springMagic, spring, transform, child, grand_child, grand_parent):
|
||||||
|
# self.current_child_position
|
||||||
|
|
||||||
|
self._instances[id(self)] = self
|
||||||
|
|
||||||
|
self.springMagic = springMagic
|
||||||
|
self.spring = spring
|
||||||
|
|
||||||
|
self.parent = transform
|
||||||
|
self.child = child
|
||||||
|
self.grand_child = grand_child
|
||||||
|
self.grand_parent = grand_parent
|
||||||
|
|
||||||
|
self.child_position = get_translation(child)
|
||||||
|
self.grand_child_position = get_translation(grand_child) if grand_child else None
|
||||||
|
|
||||||
|
self.previous_child_position = self.child_position
|
||||||
|
|
||||||
|
self.rotation = get_rotation(transform)
|
||||||
|
self.up_vector = get_matrix(transform)[4:7]
|
||||||
|
|
||||||
|
transform_pos = get_translation(transform)
|
||||||
|
self.bone_length = springMath.distance(transform_pos, self.child_position)
|
||||||
|
|
||||||
|
self.has_child_collide = False
|
||||||
|
self.has_plane_collide = False
|
||||||
|
|
||||||
|
# create temporary locators use for aim constraint
|
||||||
|
if not SpringData.cur_position_locator:
|
||||||
|
SpringData.cur_position_locator = pm.spaceLocator(name='cur_position_locator')
|
||||||
|
|
||||||
|
if not SpringData.prev_position_locator:
|
||||||
|
SpringData.prev_position_locator = pm.spaceLocator(name='prev_position_locator')
|
||||||
|
|
||||||
|
if not SpringData.prev_grand_child_position_locator:
|
||||||
|
SpringData.prev_grand_child_position_locator = pm.spaceLocator(name='prev_grand_child_position_locator')
|
||||||
|
|
||||||
|
# Weight attribute to de/activate the aim constraint in pose match mode
|
||||||
|
self.pairblend_weight_attribute = None
|
||||||
|
self.aim_constraint = None
|
||||||
|
|
||||||
|
self.__create_child_proxy()
|
||||||
|
self.__prepare_animation_key()
|
||||||
|
self.__create_aim_constraint()
|
||||||
|
self.__init_pairblend_weight()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
|
||||||
|
if self.Count == 0:
|
||||||
|
# print('Last Counter object deleted')
|
||||||
|
|
||||||
|
# delete temporary locators (useful, it's delete constraints at the same time)
|
||||||
|
pm.delete(SpringData.cur_position_locator, SpringData.prev_position_locator, SpringData.prev_grand_child_position_locator)
|
||||||
|
|
||||||
|
SpringData.cur_position_locator = None
|
||||||
|
SpringData.prev_position_locator = None
|
||||||
|
SpringData.prev_grand_child_position_locator = None
|
||||||
|
|
||||||
|
# remove all spring nulls, add recursive incase name spaces
|
||||||
|
# pm.delete(pm.ls('*' + kNullSuffix + '*', recursive=1))
|
||||||
|
else:
|
||||||
|
# print(self.Count, 'Counter objects remaining')
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self.child_proxy:
|
||||||
|
# remove spring nulls, add recursive incase name spaces
|
||||||
|
pm.delete(pm.ls('*' + self.child_proxy + '*', recursive=1))
|
||||||
|
|
||||||
|
def update(self, has_collision, has_hit_plane, child_pos_corrected):
|
||||||
|
# Update current transform with the new values
|
||||||
|
self.child_position = get_translation(self.child)
|
||||||
|
self.grand_child_position = get_translation(self.grand_child) if self.grand_child else None
|
||||||
|
self.previous_child_position = child_pos_corrected
|
||||||
|
|
||||||
|
self.rotation = get_rotation(self.parent)
|
||||||
|
self.up_vector = get_matrix(self.parent)[4:7]
|
||||||
|
|
||||||
|
self.has_child_collide = has_collision
|
||||||
|
self.has_plane_collide = has_hit_plane
|
||||||
|
|
||||||
|
def __create_child_proxy(self):
|
||||||
|
# create a null at child pos, then parent to obj parent for calculation
|
||||||
|
child_proxy_locator_name = self.parent.name() + kNullSuffix
|
||||||
|
child_proxy_list = pm.ls(child_proxy_locator_name)
|
||||||
|
|
||||||
|
if not child_proxy_list:
|
||||||
|
self.child_proxy = pm.spaceLocator(name=child_proxy_locator_name)
|
||||||
|
else:
|
||||||
|
self.child_proxy = child_proxy_list[0]
|
||||||
|
|
||||||
|
self.child_proxy.getShape().setAttr('visibility', False)
|
||||||
|
|
||||||
|
pm.parent(self.child_proxy, self.parent.getParent())
|
||||||
|
# pm.parent(child_proxy, self.grand_parent)
|
||||||
|
|
||||||
|
if not self.springMagic.is_pose_match:
|
||||||
|
self.child_proxy.setTranslation(self.child.getTranslation(space='world'), space='world')
|
||||||
|
self.child_proxy.setRotation(self.child.getRotation(space='world'), space='world')
|
||||||
|
|
||||||
|
def __prepare_animation_key(self):
|
||||||
|
if not self.springMagic.is_pose_match:
|
||||||
|
# remove exists keys
|
||||||
|
pm.cutKey(self.parent, time=(self.springMagic.start_frame, self.springMagic.end_frame + 0.99999))
|
||||||
|
pm.cutKey(self.child, time=(self.springMagic.start_frame, self.springMagic.end_frame + 0.99999))
|
||||||
|
|
||||||
|
# set key
|
||||||
|
pm.setKeyframe(self.parent, attribute='rotate')
|
||||||
|
|
||||||
|
if self.spring.extend != 0.0:
|
||||||
|
pm.setKeyframe(self.child, attribute='tx')
|
||||||
|
|
||||||
|
def __create_aim_constraint(self):
|
||||||
|
# Create a constraint per transform to speed up computation, not active yet (weight=0)
|
||||||
|
self.aim_constraint = pm.aimConstraint(SpringData.cur_position_locator, SpringData.prev_position_locator, SpringData.prev_grand_child_position_locator, self.parent, aimVector=[1, 0, 0], upVector=[0, 1, 0], maintainOffset=False, weight=0)
|
||||||
|
|
||||||
|
def __init_pairblend_weight(self):
|
||||||
|
# if transform rotation has no animation, set a key at start frame to force the creation of a pairblend when the aim constraint is created
|
||||||
|
for rotation_input in ['rx', 'ry', 'rz']:
|
||||||
|
rotation_connection = pm.listConnections(self.parent + '.' + rotation_input, d=False, s=True)
|
||||||
|
|
||||||
|
if not rotation_connection:
|
||||||
|
pm.setKeyframe(self.parent, attribute=rotation_input)
|
||||||
|
|
||||||
|
pairblends = pm.listConnections(self.parent, type="pairBlend", destination=True, skipConversionNodes=True)
|
||||||
|
|
||||||
|
# Find the pairblend connected to the aim constraint
|
||||||
|
for pairblend in pairblends:
|
||||||
|
|
||||||
|
connected_constraint_list = cmds.listConnections(pairblend.name(), type='constraint', destination=False)
|
||||||
|
|
||||||
|
if self.aim_constraint.name() in connected_constraint_list:
|
||||||
|
|
||||||
|
# Get pairblend weight connected attribute
|
||||||
|
# return [u'joint2.blendAim1')]
|
||||||
|
weight_attribute_list = cmds.listConnections(pairblend + '.weight', d=False, s=True, p=True)
|
||||||
|
|
||||||
|
if weight_attribute_list:
|
||||||
|
self.pairblend_weight_attribute = weight_attribute_list[0]
|
||||||
|
|
||||||
|
def set_pairblend_weight(self, blend_value):
|
||||||
|
if self.pairblend_weight_attribute:
|
||||||
|
pm.setAttr(self.pairblend_weight_attribute, blend_value)
|
||||||
|
|
||||||
|
def keyframe_child_proxy(self):
|
||||||
|
|
||||||
|
if self.child_proxy:
|
||||||
|
# Deactivate pairblend weight
|
||||||
|
# Aim constraint weight set to 0 is not enough, it paratizes the process
|
||||||
|
self.set_pairblend_weight(0.0)
|
||||||
|
|
||||||
|
self.child_proxy.setTranslation(self.child.getTranslation(space='world'), space='world')
|
||||||
|
pm.setKeyframe(self.child_proxy, attribute='translate')
|
||||||
|
self.child_proxy.setRotation(self.child.getRotation(space='world'), space='world')
|
||||||
|
pm.setKeyframe(self.child_proxy, attribute='rotate')
|
||||||
|
|
||||||
|
self.set_pairblend_weight(1.0)
|
||||||
|
|
||||||
|
def apply_inertia(self, currentChildPosition):
|
||||||
|
ratio = self.spring.ratio / self.springMagic.sub_div
|
||||||
|
inertia_offset = [0.0, 0.0, 0.0]
|
||||||
|
|
||||||
|
if self.spring.inertia > 0.0:
|
||||||
|
bone_ref_loc_offset_dir = currentChildPosition - self.child_position
|
||||||
|
bone_ref_loc_offset_distance = ((bone_ref_loc_offset_dir) * (1 - ratio) * (1 - self.spring.inertia)).length()
|
||||||
|
|
||||||
|
inertia_offset = bone_ref_loc_offset_dir.normal() * (bone_ref_loc_offset_distance / self.springMagic.sub_div)
|
||||||
|
|
||||||
|
# apply mass
|
||||||
|
force_direction = self.child_position - self.previous_child_position
|
||||||
|
force_distance = force_direction.length() * self.spring.inertia
|
||||||
|
|
||||||
|
# offset position
|
||||||
|
inertia_offset += force_direction.normal() * (force_distance / self.springMagic.sub_div)
|
||||||
|
|
||||||
|
return inertia_offset
|
||||||
|
|
||||||
|
def apply_wind(self, frame):
|
||||||
|
wind_offset = [0.0, 0.0, 0.0]
|
||||||
|
|
||||||
|
if self.springMagic.wind:
|
||||||
|
wind_max_force = self.springMagic.wind.getAttr('MaxForce')
|
||||||
|
wind_min_force = self.springMagic.wind.getAttr('MinForce')
|
||||||
|
wind_frequency = self.springMagic.wind.getAttr('Frequency')
|
||||||
|
|
||||||
|
mid_force = (wind_max_force + wind_min_force) / 2
|
||||||
|
|
||||||
|
# get source x - axis direction in world space
|
||||||
|
wind_direction = get_matrix(self.springMagic.wind)[:3]
|
||||||
|
# sDirection = sObj.getMatrix()[0][:3]
|
||||||
|
wind_direction = dt.Vector(wind_direction[0], wind_direction[1], wind_direction[2]).normal()
|
||||||
|
wind_distance = math.sin(frame * wind_frequency) * (wind_max_force - wind_min_force) + mid_force
|
||||||
|
|
||||||
|
# offset position
|
||||||
|
wind_offset = wind_direction.normal() * wind_distance
|
||||||
|
|
||||||
|
return wind_offset
|
||||||
|
|
||||||
|
def detect_collision(self, new_obj_pos, new_child_pos, capsule_list):
|
||||||
|
col_pre = col_cur = None
|
||||||
|
|
||||||
|
child_pos_corrected = self.child_position
|
||||||
|
|
||||||
|
if self.springMagic.is_collision and capsule_list:
|
||||||
|
|
||||||
|
if preCheckCollision(new_obj_pos, self.bone_length, capsule_list):
|
||||||
|
|
||||||
|
# check collision from previous pos to cur pos
|
||||||
|
col_pre, col_body_pre, hitCylinder_pre = springMath.checkCollision(new_child_pos, self.child_position, capsule_list, True)
|
||||||
|
|
||||||
|
# check collision from cur pos to previous pos
|
||||||
|
col_cur, col_body_cur, hitCylinder_cur = springMath.checkCollision(new_child_pos, self.child_position, capsule_list, False)
|
||||||
|
|
||||||
|
if col_pre and (col_cur is None):
|
||||||
|
new_child_pos = col_pre
|
||||||
|
elif col_cur and (col_pre is None):
|
||||||
|
child_pos_corrected = col_cur
|
||||||
|
elif col_pre and col_cur:
|
||||||
|
|
||||||
|
# move cur child pose to closest out point if both pre and cur pos are already inside of col body
|
||||||
|
# if distance(col_pre, new_child_pos) < distance(col_cur, new_child_pos):
|
||||||
|
mid_point = (self.child_position + new_child_pos) / 2
|
||||||
|
|
||||||
|
if springMath.distance(col_pre, mid_point) < springMath.distance(col_cur, mid_point):
|
||||||
|
new_child_pos = col_pre
|
||||||
|
else:
|
||||||
|
new_child_pos = col_cur
|
||||||
|
|
||||||
|
if self.springMagic.is_fast_move:
|
||||||
|
child_pos_corrected = new_child_pos
|
||||||
|
|
||||||
|
# # draw debug locator
|
||||||
|
# if col_pre and col_cur:
|
||||||
|
# locator1 = pm.spaceLocator(name=obj.name() + '_col_pre_locator_' + str(i))
|
||||||
|
# locator1.setTranslation(col_pre)
|
||||||
|
# locator1 = pm.spaceLocator(name=obj.name() + '_col_cur_locator_' + str(i))
|
||||||
|
# locator1.setTranslation(col_cur)
|
||||||
|
|
||||||
|
return True if col_pre or col_cur else False, new_child_pos, child_pos_corrected
|
||||||
|
|
||||||
|
def detect_plane_hit(self, new_obj_pos, new_child_pos, grand_parent_has_plane_collision):
|
||||||
|
has_hit_plane = False
|
||||||
|
|
||||||
|
if self.springMagic.is_collision and self.springMagic.collision_planes_list[0]:
|
||||||
|
collision_plane = self.springMagic.collision_planes_list[0]
|
||||||
|
has_plane_collision = springMath.checkPlaneCollision(new_obj_pos, new_child_pos, collision_plane)
|
||||||
|
|
||||||
|
if has_plane_collision or grand_parent_has_plane_collision:
|
||||||
|
new_child_pos = repeatMoveToPlane(self.parent, new_child_pos, self.child, collision_plane, 3)
|
||||||
|
has_hit_plane = True
|
||||||
|
|
||||||
|
return has_hit_plane, new_child_pos
|
||||||
|
|
||||||
|
# calculate upvector by interpolation y axis for twist
|
||||||
|
def compute_up_vector(self):
|
||||||
|
twist_ratio = self.spring.twist_ratio / self.springMagic.sub_div
|
||||||
|
|
||||||
|
cur_obj_yAxis = get_matrix(self.child_proxy)[4:7]
|
||||||
|
prev_up_vector = dt.Vector(self.up_vector[0], self.up_vector[1], self.up_vector[2]).normal()
|
||||||
|
cur_up_vector = dt.Vector(cur_obj_yAxis[0], cur_obj_yAxis[1], cur_obj_yAxis[2]).normal()
|
||||||
|
|
||||||
|
up_vector = (prev_up_vector * (1 - twist_ratio)) + (cur_up_vector * twist_ratio)
|
||||||
|
|
||||||
|
return up_vector
|
||||||
|
|
||||||
|
def aim_by_ratio(self, upVector, newChildPos, childPosCorrected):
|
||||||
|
ratio = self.spring.ratio / self.springMagic.sub_div
|
||||||
|
tension = self.spring.tension / (1.0 / (springMath.sigmoid(1 - self.springMagic.sub_div) + 0.5))
|
||||||
|
|
||||||
|
# print("obj: " + str(self.parent.name()))
|
||||||
|
# print("newChildPos: " + str(newChildPos))
|
||||||
|
# print("childPosCorrected: " + str(childPosCorrected))
|
||||||
|
# print("grand_child_position: " + str(self.grand_child_position))
|
||||||
|
# print("upVector: " + str(upVector))
|
||||||
|
# print("ratio: " + str(ratio))
|
||||||
|
# print("tension: " + str(tension))
|
||||||
|
|
||||||
|
SpringData.cur_position_locator.setTranslation(newChildPos)
|
||||||
|
SpringData.prev_position_locator.setTranslation(childPosCorrected)
|
||||||
|
|
||||||
|
pm.aimConstraint(self.parent, e=True, worldUpVector=upVector)
|
||||||
|
|
||||||
|
pm.aimConstraint(SpringData.cur_position_locator, self.parent, e=True, w=ratio)
|
||||||
|
pm.aimConstraint(SpringData.prev_position_locator, self.parent, e=True, w=1 - ratio)
|
||||||
|
|
||||||
|
if self.has_child_collide and self.grand_child_position and tension != 0:
|
||||||
|
SpringData.prev_grand_child_position_locator.setTranslation(self.grand_child_position)
|
||||||
|
pm.aimConstraint(SpringData.prev_grand_child_position_locator, self.parent, e=True, w=(1 - ratio) * tension)
|
||||||
|
|
||||||
|
pm.setKeyframe(self.parent, attribute='rotate')
|
||||||
|
|
||||||
|
pm.aimConstraint(SpringData.cur_position_locator, SpringData.prev_position_locator, SpringData.prev_grand_child_position_locator, self.parent, e=True, w=0.0)
|
||||||
|
|
||||||
|
def extend_bone(self, childPosCorrected):
|
||||||
|
if self.spring.extend != 0.0:
|
||||||
|
child_translation = self.child.getTranslation()
|
||||||
|
# get length between bone pos and child pos
|
||||||
|
x2 = (childPosCorrected - get_translation(self.parent)).length()
|
||||||
|
x3 = (self.bone_length * (1 - self.spring.extend)) + (x2 * self.spring.extend)
|
||||||
|
self.child.setTranslation([x3, child_translation[1], child_translation[2]])
|
||||||
|
pm.setKeyframe(self.child, attribute='tx')
|
||||||
|
# else:
|
||||||
|
# self.child.setTranslation([self.bone_length, child_translation[1], child_translation[2]])
|
||||||
|
|
||||||
|
def createCollisionPlane():
|
||||||
|
|
||||||
|
# remove exist plane
|
||||||
|
collision_plane = get_node('*' + kCollisionPlaneSuffix + '*')
|
||||||
|
|
||||||
|
if collision_plane:
|
||||||
|
pm.delete(collision_plane)
|
||||||
|
|
||||||
|
collision_plane = pm.polyPlane(name="the" + kCollisionPlaneSuffix, sx=1, sy=1, w=10, h=10, ch=1)[0]
|
||||||
|
|
||||||
|
# one side display
|
||||||
|
pm.setAttr(collision_plane.doubleSided, False)
|
||||||
|
|
||||||
|
# lock scale
|
||||||
|
pm.setAttr(collision_plane.sx, lock=True)
|
||||||
|
pm.setAttr(collision_plane.sy, lock=True)
|
||||||
|
pm.setAttr(collision_plane.sz, lock=True)
|
||||||
|
|
||||||
|
pm.select(collision_plane)
|
||||||
|
|
||||||
|
|
||||||
|
def removeBody(clear=False):
|
||||||
|
cylinder_list = getCapsule(clear)
|
||||||
|
|
||||||
|
pm.delete(cylinder_list)
|
||||||
|
|
||||||
|
collision_plane = get_node('*' + kCollisionPlaneSuffix + '*')
|
||||||
|
|
||||||
|
if collision_plane:
|
||||||
|
pm.delete(collision_plane)
|
||||||
|
|
||||||
|
|
||||||
|
def addWindObj():
|
||||||
|
windCone = pm.cone(name=kWindObjectName)[0]
|
||||||
|
|
||||||
|
windCone.setScale([5, 5, 5])
|
||||||
|
|
||||||
|
pm.delete(windCone, constructionHistory=1)
|
||||||
|
|
||||||
|
# add wind attr
|
||||||
|
pm.addAttr(windCone, longName='MaxForce', attributeType='float')
|
||||||
|
pm.setAttr(windCone.name() + '.MaxForce', 1, e=1, keyable=1)
|
||||||
|
pm.addAttr(windCone, longName='MinForce', attributeType='float')
|
||||||
|
pm.setAttr(windCone.name() + '.MinForce', 0.5, e=1, keyable=1)
|
||||||
|
pm.addAttr(windCone, longName='Frequency', attributeType='float')
|
||||||
|
pm.setAttr(windCone.name() + '.Frequency', 1, e=1, keyable=1)
|
||||||
|
# pm.addAttr(windCone, longName='Wave', attributeType='float')
|
||||||
|
# pm.setAttr(windCone.name() + '.Wave', 0.5, e=1, keyable=1)
|
||||||
|
|
||||||
|
setWireShading(windCone, False)
|
||||||
|
|
||||||
|
pm.makeIdentity(apply=True)
|
||||||
|
windCone.setRotation([0, 0, 90])
|
||||||
|
|
||||||
|
|
||||||
|
def bindControls(linked_chains=False):
|
||||||
|
selected_ctrls = pm.ls(sl=True)
|
||||||
|
pm.select(clear=True)
|
||||||
|
|
||||||
|
# The chains are linked, we can sort them
|
||||||
|
if linked_chains:
|
||||||
|
# Create list for every ctrls chains
|
||||||
|
# ie [[ctrl1, ctrl1.1, ctrl1.2], [ctrl2, ctrl2.1, ctrl2.2, ctrl2.3]]
|
||||||
|
all_ctrls_descendants_list = pm.listRelatives(selected_ctrls, allDescendents=True)
|
||||||
|
top_hierarchy_ctrls_list = [x for x in selected_ctrls if x not in all_ctrls_descendants_list]
|
||||||
|
|
||||||
|
ctrls_chains_list = map(lambda x: [x] + [y for y in pm.listRelatives(x, allDescendents=True) if y in selected_ctrls][::-1], top_hierarchy_ctrls_list)
|
||||||
|
# No sorting possible because the controlers have no lineage
|
||||||
|
else:
|
||||||
|
ctrls_chains_list = [selected_ctrls]
|
||||||
|
|
||||||
|
proxy_joint_chain_list = []
|
||||||
|
|
||||||
|
for ctrls_list in ctrls_chains_list:
|
||||||
|
|
||||||
|
proxy_joint_list = []
|
||||||
|
|
||||||
|
for ctrl in ctrls_list:
|
||||||
|
# create proxy joint in ctrl world position
|
||||||
|
ctrl_position = pm.xform(ctrl, worldSpace=1, rp=1, q=1)
|
||||||
|
|
||||||
|
proxyJoint = pm.joint(name=ctrl.name() + kSpringProxySuffix, position=ctrl_position, radius=0.2, roo='xyz')
|
||||||
|
proxy_joint_list.append(proxyJoint)
|
||||||
|
|
||||||
|
for joint in proxy_joint_list:
|
||||||
|
# set joint orientation
|
||||||
|
pm.joint(joint, edit=1, orientJoint='xyz', zeroScaleOrient=True)
|
||||||
|
|
||||||
|
# Straight bones alignment
|
||||||
|
joint.setRotation([0, 0, 0])
|
||||||
|
joint.setAttr('rotateAxis', [0, 0, 0])
|
||||||
|
joint.setAttr('jointOrient', [0, 0, 0])
|
||||||
|
|
||||||
|
# Free rotation (move rotation values to joint orient values)
|
||||||
|
# pm.makeIdentity(proxy_joint_list[idx], apply=True, t=False, r=True, s=False, pn=True)
|
||||||
|
|
||||||
|
if proxy_joint_list:
|
||||||
|
# parent root proxy joint to control parent
|
||||||
|
pm.parent(proxy_joint_list[0], ctrls_list[0].getParent())
|
||||||
|
|
||||||
|
# Necessary to start a new joint chain
|
||||||
|
pm.select(clear=True)
|
||||||
|
|
||||||
|
proxy_joint_chain_list += [proxy_joint_list]
|
||||||
|
|
||||||
|
for idx, joint in enumerate(proxy_joint_list[:-1]):
|
||||||
|
# orient joint chain
|
||||||
|
cns = pm.aimConstraint(ctrls_list[idx + 1], proxy_joint_list[idx], aimVector=[1, 0, 0], upVector=[0, 0, 0], worldUpVector=[0, 1, 0], skip='x')
|
||||||
|
pm.delete(cns)
|
||||||
|
|
||||||
|
for idx, joint in enumerate(proxy_joint_list):
|
||||||
|
pm.parentConstraint(proxy_joint_list[idx], ctrls_list[idx], maintainOffset=True)
|
||||||
|
|
||||||
|
pm.select(proxy_joint_chain_list)
|
||||||
|
|
||||||
|
|
||||||
|
def clearBind(startFrame, endFrame):
|
||||||
|
proxyJointLst = pm.ls(sl=True)
|
||||||
|
pm.select(d=True)
|
||||||
|
|
||||||
|
ctrlList = []
|
||||||
|
|
||||||
|
for bone in proxyJointLst:
|
||||||
|
ctrl = pm.ls(bone.name().split(kSpringProxySuffix)[0])[0]
|
||||||
|
ctrlList.append(ctrl)
|
||||||
|
|
||||||
|
if ctrlList:
|
||||||
|
pm.bakeResults(*ctrlList, t=(startFrame, endFrame))
|
||||||
|
|
||||||
|
pm.delete(proxyJointLst)
|
||||||
|
|
||||||
|
|
||||||
|
def bindPose():
|
||||||
|
pm.runtime.GoToBindPose()
|
||||||
|
|
||||||
|
|
||||||
|
# Prepare all information to call SpringMagicMaya function
|
||||||
|
def startCompute(spring, springMagic, progression_callback=None):
|
||||||
|
|
||||||
|
autokeyframe_state = cmds.autoKeyframe(query=True, state=True)
|
||||||
|
cmds.autoKeyframe(state=False)
|
||||||
|
|
||||||
|
# get selection obj
|
||||||
|
objs = pm.ls(sl=True)
|
||||||
|
|
||||||
|
# check objects validity
|
||||||
|
for obj in objs:
|
||||||
|
# has duplicate name obj
|
||||||
|
nameCntErr = (len(pm.ls(obj.name())) > 1)
|
||||||
|
|
||||||
|
# is a duplicate obj
|
||||||
|
nameValidErr = (obj.name().find('|') > 0)
|
||||||
|
|
||||||
|
if nameCntErr or nameValidErr:
|
||||||
|
raise ValueError(obj.name() + ' has duplicate name object! Stopped!')
|
||||||
|
|
||||||
|
obj_translation = obj.getTranslation()
|
||||||
|
|
||||||
|
if (obj_translation[0] < 0 or abs(obj_translation[1]) > 0.001 or abs(obj_translation[2]) > 0.001) and obj.getParent() and (obj.getParent() in objs):
|
||||||
|
pm.warning(obj.getParent().name() + "'s X axis not point to child! May get broken result!")
|
||||||
|
|
||||||
|
# Search for collision objects
|
||||||
|
if springMagic.is_collision:
|
||||||
|
springMagic.collision_planes_list = [get_node('*' + kCollisionPlaneSuffix + '*')]
|
||||||
|
|
||||||
|
# Search for a wind object
|
||||||
|
if pm.ls(kWindObjectName):
|
||||||
|
springMagic.wind = pm.ls(kWindObjectName)[0]
|
||||||
|
|
||||||
|
SpringMagicMaya(objs, spring, springMagic, progression_callback)
|
||||||
|
|
||||||
|
cmds.autoKeyframe(state=autokeyframe_state)
|
||||||
|
|
||||||
|
|
||||||
|
# @decorators.viewportOff
|
||||||
|
@decorators.gShowProgress(status="SpringMagic does his magic")
|
||||||
|
def SpringMagicMaya(objs, spring, springMagic, progression_callback=None):
|
||||||
|
# on each frame go through all objs and do:
|
||||||
|
# 1. make a vectorA from current obj position to previous child position
|
||||||
|
# 2. make a vectorB from current obj position to current child position
|
||||||
|
# 3. calculate the angle between two vectors
|
||||||
|
# 4. rotate the obj towards vectorA base on spring value
|
||||||
|
|
||||||
|
start_frame = springMagic.start_frame
|
||||||
|
end_frame = springMagic.end_frame
|
||||||
|
sub_div = springMagic.sub_div
|
||||||
|
|
||||||
|
# remove all spring nulls, add recursive incase name spaces
|
||||||
|
pm.delete(pm.ls('*' + kNullSuffix + '*', recursive=True))
|
||||||
|
|
||||||
|
# get all capsules in scene
|
||||||
|
capsule_list = getCapsule(True) if springMagic.is_collision else None
|
||||||
|
|
||||||
|
if progression_callback:
|
||||||
|
progression_callback(0)
|
||||||
|
|
||||||
|
# Save object previous frame information in a ordered dict
|
||||||
|
spring_data_dict = OrderedDict()
|
||||||
|
|
||||||
|
# Initialize data on the first frame
|
||||||
|
pm.currentTime(start_frame, edit=True)
|
||||||
|
|
||||||
|
# Create a list of objects chains
|
||||||
|
# ie [[nt.Joint(u'joint1'), nt.Joint(u'joint2'), nt.Joint(u'joint4')], [nt.Joint(u'joint7'), nt.Joint(u'joint8'), nt.Joint(u'joint10')]]
|
||||||
|
all_joints_descendants_list = pm.listRelatives(objs, allDescendents=True, type='transform')
|
||||||
|
top_hierarchy_joints_list = [x for x in objs if x not in all_joints_descendants_list]
|
||||||
|
|
||||||
|
# transforms_chains_list = map(lambda x: [x] + [y for y in pm.listRelatives(x, allDescendents=True) if y in objs][::-1], top_hierarchy_joints_list)
|
||||||
|
|
||||||
|
# Deal with the specific case of root bone with no parent.
|
||||||
|
# The root bone is considered the driver, so we remove it from the calculation.
|
||||||
|
transforms_chains_list = map(lambda x: ([x] if x.getParent() else []) + [y for y in pm.listRelatives(x, allDescendents=True) if y in objs][::-1], top_hierarchy_joints_list)
|
||||||
|
|
||||||
|
# Remove empty lists
|
||||||
|
transforms_chains_list = [x for x in transforms_chains_list if x != []]
|
||||||
|
|
||||||
|
# Create progression bar generator values
|
||||||
|
number_of_progession_step = 0
|
||||||
|
|
||||||
|
if springMagic.is_pose_match:
|
||||||
|
number_of_progession_step += end_frame - start_frame + 1
|
||||||
|
|
||||||
|
if springMagic.is_loop:
|
||||||
|
# Doesn't process the first frame on the first loop
|
||||||
|
number_of_progession_step += ((end_frame - start_frame) * 2 + 1) * sub_div
|
||||||
|
else:
|
||||||
|
# Doesn't process the first frame
|
||||||
|
number_of_progession_step += (end_frame - start_frame) * sub_div
|
||||||
|
|
||||||
|
progression_increment = 100.0 / number_of_progession_step
|
||||||
|
progression_generator = frange(progression_increment, 100.0 + progression_increment, progression_increment)
|
||||||
|
|
||||||
|
# Create spring data for each transforms at start frame
|
||||||
|
for transforms_chain in transforms_chains_list:
|
||||||
|
|
||||||
|
if SpringMagicMaya.isInterrupted():
|
||||||
|
break
|
||||||
|
|
||||||
|
transforms_cycle = cycle(transforms_chain)
|
||||||
|
|
||||||
|
# Prime the pump
|
||||||
|
parent = first_transform = next(transforms_cycle)
|
||||||
|
grand_parent = parent.getParent()
|
||||||
|
child = next(transforms_cycle)
|
||||||
|
grand_child = next(transforms_cycle)
|
||||||
|
|
||||||
|
# skip end bone
|
||||||
|
for transform in transforms_chain[:-1]:
|
||||||
|
|
||||||
|
if SpringMagicMaya.isInterrupted():
|
||||||
|
break
|
||||||
|
|
||||||
|
# End of cycle iteration
|
||||||
|
if grand_child == first_transform:
|
||||||
|
grand_child = None
|
||||||
|
|
||||||
|
spring_data_dict[parent.name()] = SpringData(springMagic, spring, parent, child, grand_child, grand_parent)
|
||||||
|
|
||||||
|
grand_parent, parent, child, grand_child = parent, child, grand_child, next(transforms_cycle)
|
||||||
|
|
||||||
|
# Save joints position over timeline
|
||||||
|
# Parse timeline just one time
|
||||||
|
if springMagic.is_pose_match:
|
||||||
|
for frame in range(0, end_frame - start_frame + 1):
|
||||||
|
|
||||||
|
if SpringMagicMaya.isInterrupted():
|
||||||
|
break
|
||||||
|
|
||||||
|
pm.currentTime(start_frame + frame, edit=True)
|
||||||
|
|
||||||
|
for spring_data in spring_data_dict.values():
|
||||||
|
|
||||||
|
if not SpringMagicMaya.isInterrupted():
|
||||||
|
spring_data.keyframe_child_proxy()
|
||||||
|
|
||||||
|
progression = progression_generator.next()
|
||||||
|
progression = clamp(progression, 0, 100)
|
||||||
|
|
||||||
|
if progression_callback:
|
||||||
|
progression_callback(progression)
|
||||||
|
|
||||||
|
SpringMagicMaya.progress(progression)
|
||||||
|
|
||||||
|
# Generate frame index
|
||||||
|
# Skip first frame on first calculation pass
|
||||||
|
frame_increment = 1.0 / sub_div
|
||||||
|
frame_generator = frange(frame_increment, end_frame - start_frame + frame_increment, frame_increment)
|
||||||
|
|
||||||
|
# On second calculation pass compute first frame
|
||||||
|
if springMagic.is_loop:
|
||||||
|
frame_generator = chain(frame_generator, frange(0, end_frame - start_frame + frame_increment, frame_increment))
|
||||||
|
|
||||||
|
for frame in frame_generator:
|
||||||
|
|
||||||
|
# print('Frame: ' + str(frame))
|
||||||
|
|
||||||
|
if SpringMagicMaya.isInterrupted():
|
||||||
|
break
|
||||||
|
|
||||||
|
pm.currentTime(start_frame + frame, edit=True)
|
||||||
|
|
||||||
|
for previous_frame_spring_data in spring_data_dict.values():
|
||||||
|
|
||||||
|
if SpringMagicMaya.isInterrupted():
|
||||||
|
break
|
||||||
|
|
||||||
|
grand_parent_spring_data = None
|
||||||
|
if previous_frame_spring_data.grand_parent and previous_frame_spring_data.grand_parent.name() in spring_data_dict.keys():
|
||||||
|
grand_parent_spring_data = spring_data_dict[previous_frame_spring_data.grand_parent.name()]
|
||||||
|
|
||||||
|
# get current position of parent and child
|
||||||
|
parent_pos = get_translation(previous_frame_spring_data.parent)
|
||||||
|
|
||||||
|
# print("obj: " + str(previous_frame_spring_data.parent.name()))
|
||||||
|
|
||||||
|
new_child_pos = get_translation(previous_frame_spring_data.child_proxy)
|
||||||
|
|
||||||
|
# Apply inertia
|
||||||
|
new_child_pos += previous_frame_spring_data.apply_inertia(new_child_pos)
|
||||||
|
|
||||||
|
# apply wind
|
||||||
|
new_child_pos += previous_frame_spring_data.apply_wind(start_frame + frame)
|
||||||
|
|
||||||
|
# detect collision
|
||||||
|
has_collision, new_child_pos, child_pos_corrected = previous_frame_spring_data.detect_collision(parent_pos, new_child_pos, capsule_list)
|
||||||
|
|
||||||
|
# detect plane collision
|
||||||
|
grand_parent_has_plane_collision = False
|
||||||
|
if grand_parent_spring_data:
|
||||||
|
grand_parent_has_plane_collision = grand_parent_spring_data.has_plane_collide
|
||||||
|
|
||||||
|
has_hit_plane, new_child_pos = previous_frame_spring_data.detect_plane_hit(parent_pos, new_child_pos, grand_parent_has_plane_collision)
|
||||||
|
|
||||||
|
# calculate upvector by interpolation y axis for twist
|
||||||
|
up_vector = previous_frame_spring_data.compute_up_vector()
|
||||||
|
|
||||||
|
# apply aim constraint to do actual rotation
|
||||||
|
previous_frame_spring_data.aim_by_ratio(up_vector, new_child_pos, child_pos_corrected)
|
||||||
|
|
||||||
|
# Extend bone if needed (update child translation)
|
||||||
|
previous_frame_spring_data.extend_bone(child_pos_corrected)
|
||||||
|
|
||||||
|
# Update current transform with the new values
|
||||||
|
previous_frame_spring_data.update(has_collision, has_hit_plane, child_pos_corrected)
|
||||||
|
|
||||||
|
# Update the grand parent has_child_collide value
|
||||||
|
if grand_parent_spring_data:
|
||||||
|
grand_parent_spring_data.has_child_collide = has_collision
|
||||||
|
|
||||||
|
progression = progression_generator.next()
|
||||||
|
progression = clamp(progression, 0, 100)
|
||||||
|
|
||||||
|
if progression_callback:
|
||||||
|
progression_callback(progression)
|
||||||
|
|
||||||
|
SpringMagicMaya.progress(progression)
|
||||||
|
|
||||||
|
# bake result on frame
|
||||||
|
if springMagic.wipe_subframe and not SpringMagicMaya.isInterrupted():
|
||||||
|
transform_to_bake_list = [spring_data.parent for spring_data in spring_data_dict.values()]
|
||||||
|
|
||||||
|
# Deactivate all pairblend otherwise bake doesn't work with animation layers
|
||||||
|
for spring_data in spring_data_dict.values():
|
||||||
|
spring_data.set_pairblend_weight(0.0)
|
||||||
|
|
||||||
|
bakeAnim(transform_to_bake_list, start_frame, end_frame)
|
||||||
|
|
||||||
|
|
||||||
|
def bakeAnim(objList, startFrame, endFrame):
|
||||||
|
pm.bakeResults(
|
||||||
|
objList,
|
||||||
|
t=(startFrame, endFrame),
|
||||||
|
sampleBy=1,
|
||||||
|
disableImplicitControl=False,
|
||||||
|
preserveOutsideKeys=True,
|
||||||
|
sparseAnimCurveBake=False,
|
||||||
|
removeBakedAttributeFromLayer=False,
|
||||||
|
bakeOnOverrideLayer=False,
|
||||||
|
minimizeRotation=True,
|
||||||
|
shape=False,
|
||||||
|
simulation=False)
|
||||||
|
|
||||||
|
|
||||||
|
SM_boneTransformDict = {}
|
||||||
|
|
||||||
|
|
||||||
|
def copyBonePose():
|
||||||
|
global SM_boneTransformDict
|
||||||
|
|
||||||
|
for obj in pm.ls(sl=True):
|
||||||
|
SM_boneTransformDict[obj] = [obj.getTranslation(), obj.getRotation()]
|
||||||
|
|
||||||
|
|
||||||
|
def pasteBonePose():
|
||||||
|
global SM_boneTransformDict
|
||||||
|
|
||||||
|
for obj in pm.ls(sl=True):
|
||||||
|
if obj in SM_boneTransformDict.keys():
|
||||||
|
|
||||||
|
logging.debug(SM_boneTransformDict[obj][0])
|
||||||
|
|
||||||
|
obj.setTranslation(SM_boneTransformDict[obj][0])
|
||||||
|
obj.setRotation(SM_boneTransformDict[obj][1])
|
||||||
|
|
||||||
|
|
||||||
|
def preCheckCollision(objPos, objLength, capsuleList):
|
||||||
|
|
||||||
|
# print('objPos:' + str(objPos))
|
||||||
|
# print('objLength:' + str(objLength))
|
||||||
|
|
||||||
|
# pre check bone length compare with collision body radius
|
||||||
|
# will improve performance if bone is far from capsule
|
||||||
|
for capsule in capsuleList:
|
||||||
|
capsule_children_list = pm.listRelatives(capsule, children=1, type='transform')
|
||||||
|
|
||||||
|
p = capsule_children_list[0].getTranslation(space='world')
|
||||||
|
q = capsule_children_list[1].getTranslation(space='world')
|
||||||
|
r = capsule.getAttr('scaleZ')
|
||||||
|
|
||||||
|
bone_to_capsule_distance = springMath.dist_to_line(p, q, objPos)
|
||||||
|
|
||||||
|
# print('p:' + str(p))
|
||||||
|
# print('q:' + str(q))
|
||||||
|
# print('r:' + str(r))
|
||||||
|
# print('boneToCapsuleDistance:' + str(bone_to_capsule_distance))
|
||||||
|
|
||||||
|
# means close enough to have a hit change
|
||||||
|
if bone_to_capsule_distance < objLength + r:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def repeatMoveToPlane(obj, objPos, objTarget, colPlane, times):
|
||||||
|
# Y axis direction of plane
|
||||||
|
n = dt.Vector(get_matrix(colPlane)[4:7])
|
||||||
|
q = get_translation(colPlane)
|
||||||
|
d = n.dot(q)
|
||||||
|
|
||||||
|
# for i in range(times):
|
||||||
|
# pt = objPos
|
||||||
|
# obj.setTranslation(proj_pt_to_plane(pt, n, d), space='world')
|
||||||
|
# if (i + 1) != times:
|
||||||
|
# obj.setTranslation(get_translation(objTarget), space='world')
|
||||||
|
pt = objPos
|
||||||
|
outPos = springMath.proj_pt_to_plane(pt, n, d)
|
||||||
|
|
||||||
|
return outPos
|
||||||
|
|
||||||
|
|
||||||
|
def setWireShading(obj, tmp):
|
||||||
|
obj.getShape().overrideEnabled.set(True)
|
||||||
|
obj.getShape().overrideShading.set(False)
|
||||||
|
|
||||||
|
if tmp:
|
||||||
|
obj.getShape().overrideDisplayType.set(1)
|
||||||
|
|
||||||
|
|
||||||
|
def addCapsuleSphereConstraint(sphereObj):
|
||||||
|
# create a locator and make sphere follow it
|
||||||
|
locator = pm.spaceLocator(name=sphereObj.name() + '_locator' + kCapsuleNameSuffix)
|
||||||
|
|
||||||
|
locator.setTranslation(sphereObj.getTranslation())
|
||||||
|
locator.setRotation(sphereObj.getRotation())
|
||||||
|
locator.getShape().setAttr('visibility', False)
|
||||||
|
|
||||||
|
pm.parentConstraint(locator, sphereObj)
|
||||||
|
|
||||||
|
return locator
|
||||||
|
|
||||||
|
|
||||||
|
def createCapsuleGeometry(size):
|
||||||
|
# create geometry
|
||||||
|
cylinder, cylinder_history = pm.cylinder(radius=size, sections=8, heightRatio=3)
|
||||||
|
pm.rename(cylinder.name(), cylinder.name() + kCapsuleNameSuffix)
|
||||||
|
|
||||||
|
sphereA, sphereA_history = pm.sphere(radius=size, endSweep=180, sections=4)
|
||||||
|
pm.rename(sphereA.name(), sphereA.name() + kCapsuleNameSuffix)
|
||||||
|
|
||||||
|
sphereB, sphereB_history = pm.sphere(radius=size, endSweep=180, sections=4)
|
||||||
|
pm.rename(sphereB.name(), sphereB.name() + kCapsuleNameSuffix)
|
||||||
|
|
||||||
|
# set to wireframe shader
|
||||||
|
setWireShading(cylinder, False)
|
||||||
|
setWireShading(sphereA, True)
|
||||||
|
setWireShading(sphereB, True)
|
||||||
|
|
||||||
|
# build a capsule with geometry
|
||||||
|
cylinder.setAttr('rotateZ', 90)
|
||||||
|
sphereA.setAttr('translateY', -1.5 * size)
|
||||||
|
sphereB.setAttr('rotateZ', 180)
|
||||||
|
sphereB.setAttr('translateY', 1.5 * size)
|
||||||
|
|
||||||
|
# add constrain
|
||||||
|
locatorA = addCapsuleSphereConstraint(sphereA)
|
||||||
|
locatorB = addCapsuleSphereConstraint(sphereB)
|
||||||
|
|
||||||
|
pm.parent(locatorA, cylinder)
|
||||||
|
pm.parent(locatorB, cylinder)
|
||||||
|
|
||||||
|
pm.parent(sphereA, cylinder)
|
||||||
|
pm.parent(sphereB, cylinder)
|
||||||
|
|
||||||
|
sphereA.setAttr('inheritsTransform', False)
|
||||||
|
sphereB.setAttr('inheritsTransform', False)
|
||||||
|
|
||||||
|
pm.connectAttr(cylinder.scaleY, (sphereA_history.name() + '.radius'))
|
||||||
|
pm.connectAttr(cylinder.scaleY, (sphereB_history.name() + '.radius'))
|
||||||
|
pm.connectAttr(cylinder.scaleY, cylinder.scaleZ)
|
||||||
|
|
||||||
|
return cylinder
|
||||||
|
|
||||||
|
|
||||||
|
def getCapsule(getAll):
|
||||||
|
if getAll:
|
||||||
|
nurbsTransLst = pm.ls(type='transform')
|
||||||
|
else:
|
||||||
|
nurbsTransLst = pm.ls(sl=True)
|
||||||
|
|
||||||
|
nurbsSurfaceLst = []
|
||||||
|
for obj in nurbsTransLst:
|
||||||
|
if obj.getShape() and (pm.nodeType(obj.getShape()) == 'nurbsSurface'):
|
||||||
|
nurbsSurfaceLst.append(obj)
|
||||||
|
|
||||||
|
cylinderLst = []
|
||||||
|
for obj in nurbsTransLst:
|
||||||
|
if 'ylinder' in obj.name() and kCapsuleNameSuffix in obj.name():
|
||||||
|
cylinderLst.append(obj)
|
||||||
|
|
||||||
|
return cylinderLst
|
||||||
|
|
||||||
|
|
||||||
|
def addCapsuleBody():
|
||||||
|
# create capsule body for collision
|
||||||
|
# place capsule at ori point of nothing selected in scene
|
||||||
|
# place capsule match with object position and rotation if select scene object
|
||||||
|
collisionBoneList = []
|
||||||
|
objs = pm.ls(sl=True)
|
||||||
|
|
||||||
|
for obj in objs:
|
||||||
|
children = pm.listRelatives(obj, children=1)
|
||||||
|
|
||||||
|
# only add capsule to the obj which has child
|
||||||
|
if children:
|
||||||
|
collisionBoneList.append([obj, children[0]])
|
||||||
|
|
||||||
|
if collisionBoneList:
|
||||||
|
for couple in collisionBoneList:
|
||||||
|
baseBone = couple[0]
|
||||||
|
endBone = couple[1]
|
||||||
|
capsule = createCapsuleGeometry(1)
|
||||||
|
|
||||||
|
pm.parent(capsule, baseBone)
|
||||||
|
# match capsule to bone
|
||||||
|
endBoneTrans = endBone.getTranslation()
|
||||||
|
capsule.setTranslation(endBoneTrans * 0.5)
|
||||||
|
capsule.setAttr('scaleX', endBoneTrans[0] / 3)
|
||||||
|
capsule.setAttr('scaleY', endBoneTrans[0] / 3)
|
||||||
|
cns = pm.aimConstraint(endBone, capsule, aimVector=[1, 0, 0])
|
||||||
|
pm.delete(cns)
|
||||||
|
|
||||||
|
else:
|
||||||
|
capsule = createCapsuleGeometry(1)
|
||||||
|
capsule.setAttr('scaleX', 10)
|
||||||
|
capsule.setAttr('scaleY', 10)
|
||||||
|
pm.select(clear=1)
|
||||||
|
|
121
Scripts/Animation/springmagic/decorators.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import maya.mel as mel
|
||||||
|
import maya.cmds as cmds
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Decorators
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
def viewportOff(func):
|
||||||
|
"""
|
||||||
|
Decorator - turn off Maya display while func is running.
|
||||||
|
if func will fail, the error will be raised after.
|
||||||
|
"""
|
||||||
|
@wraps(func)
|
||||||
|
def wrap(*args, **kwargs):
|
||||||
|
|
||||||
|
# Turn $gMainPane Off:
|
||||||
|
mel.eval("paneLayout -e -manage false $gMainPane")
|
||||||
|
|
||||||
|
# Decorator will try/except running the function.
|
||||||
|
# But it will always turn on the viewport at the end.
|
||||||
|
# In case the function failed, it will prevent leaving maya viewport off.
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except Exception:
|
||||||
|
raise # will raise original error
|
||||||
|
finally:
|
||||||
|
mel.eval("paneLayout -e -manage true $gMainPane")
|
||||||
|
|
||||||
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
|
class gShowProgress(object):
|
||||||
|
"""
|
||||||
|
Function decorator to show the user (progress) feedback.
|
||||||
|
@usage
|
||||||
|
|
||||||
|
import time
|
||||||
|
@gShowProgress(end=10)
|
||||||
|
def createCubes():
|
||||||
|
for i in range(10):
|
||||||
|
time.sleep(1)
|
||||||
|
if createCubes.isInterrupted(): break
|
||||||
|
iCube = cmds.polyCube(w=1,h=1,d=1)
|
||||||
|
cmds.move(i,i*.2,0,iCube)
|
||||||
|
createCubes.step()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, status='Busy...', start=0, end=100, interruptable=True):
|
||||||
|
import maya.mel
|
||||||
|
|
||||||
|
self.mStartValue = start
|
||||||
|
self.mEndValue = end
|
||||||
|
self.mStatus = status
|
||||||
|
self.mInterruptable = interruptable
|
||||||
|
self.mMainProgressBar = maya.mel.eval('$tmp = $gMainProgressBar')
|
||||||
|
|
||||||
|
def step(self, inValue=1):
|
||||||
|
"""Increase step
|
||||||
|
@param inValue (int) Step value"""
|
||||||
|
cmds.progressBar(self.mMainProgressBar, edit=True, step=inValue)
|
||||||
|
|
||||||
|
def progress(self, inValue):
|
||||||
|
"""Set progression value
|
||||||
|
@param inValue (int) Progress value"""
|
||||||
|
cmds.progressBar(self.mMainProgressBar, edit=True, progress=inValue)
|
||||||
|
|
||||||
|
def isInterrupted(self):
|
||||||
|
"""Check if the user has interrupted the progress
|
||||||
|
@return (boolean)"""
|
||||||
|
return cmds.progressBar(self.mMainProgressBar, query=True, isCancelled=True)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Start progress"""
|
||||||
|
cmds.waitCursor(state=True)
|
||||||
|
cmds.progressBar(
|
||||||
|
self.mMainProgressBar,
|
||||||
|
edit=True,
|
||||||
|
beginProgress=True,
|
||||||
|
isInterruptable=self.mInterruptable,
|
||||||
|
status=self.mStatus,
|
||||||
|
minValue=self.mStartValue,
|
||||||
|
maxValue=self.mEndValue)
|
||||||
|
cmds.refresh()
|
||||||
|
|
||||||
|
def end(self):
|
||||||
|
"""Mark the progress as ended"""
|
||||||
|
cmds.progressBar(self.mMainProgressBar, edit=True, endProgress=True)
|
||||||
|
cmds.waitCursor(state=False)
|
||||||
|
|
||||||
|
def __call__(self, inFunction):
|
||||||
|
"""
|
||||||
|
Override call method
|
||||||
|
@param inFunction (function) Original function
|
||||||
|
@return (function) Wrapped function
|
||||||
|
@description
|
||||||
|
If there are decorator arguments, __call__() is only called once,
|
||||||
|
as part of the decoration process! You can only give it a single argument,
|
||||||
|
which is the function object.
|
||||||
|
"""
|
||||||
|
def wrapped_f(*args, **kwargs):
|
||||||
|
# Start progress
|
||||||
|
self.start()
|
||||||
|
# Call original function
|
||||||
|
inFunction(*args, **kwargs)
|
||||||
|
# End progress
|
||||||
|
self.end()
|
||||||
|
|
||||||
|
# Add special methods to the wrapped function
|
||||||
|
wrapped_f.step = self.step
|
||||||
|
wrapped_f.progress = self.progress
|
||||||
|
wrapped_f.isInterrupted = self.isInterrupted
|
||||||
|
|
||||||
|
# Copy over attributes
|
||||||
|
wrapped_f.__doc__ = inFunction.__doc__
|
||||||
|
wrapped_f.__name__ = inFunction.__name__
|
||||||
|
wrapped_f.__module__ = inFunction.__module__
|
||||||
|
|
||||||
|
# Return wrapped function
|
||||||
|
return wrapped_f
|
75
Scripts/Animation/springmagic/history.txt
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#####################################################################################
|
||||||
|
#
|
||||||
|
# Spring Magic for Maya
|
||||||
|
#
|
||||||
|
# Calculate bone chain animation by settings, support collisions and wind force
|
||||||
|
# Can work with rigging controller as well
|
||||||
|
#
|
||||||
|
# Need pringMagic.ui file to work with
|
||||||
|
# This script need also icon file support, which should be put in same folder
|
||||||
|
#
|
||||||
|
# feel free to mail me redtank@outlook.com for any bug or issue
|
||||||
|
#
|
||||||
|
# Yanbin Bai
|
||||||
|
# 2021.02
|
||||||
|
#
|
||||||
|
#####################################################################################
|
||||||
|
|
||||||
|
3.5a
|
||||||
|
- Fix bug tension calculation introduced in the 3.5 (Benoit Degand)
|
||||||
|
- Fix bug inertia calculation introduced in the 3.5 (Benoit Degand)
|
||||||
|
- Clarify code splitting source code in class and methods (Benoit Degand)
|
||||||
|
|
||||||
|
3.5
|
||||||
|
- Apply PEP8 coding format (Benoit Degand)
|
||||||
|
- Add possiblity to cancel the operation (Esc) (Benoit Degand)
|
||||||
|
- Increase speed (x2), avoiding locators and aim constraints intensive creation/deletion (Benoit Degand)
|
||||||
|
- Fragment source code in several files (Benoit Degand)
|
||||||
|
- Pose Match default off
|
||||||
|
|
||||||
|
3.4b
|
||||||
|
- fix collision bug
|
||||||
|
|
||||||
|
3.4a
|
||||||
|
- fix wind bug
|
||||||
|
|
||||||
|
3.4
|
||||||
|
- add plane collision
|
||||||
|
- add pose match
|
||||||
|
|
||||||
|
3.3
|
||||||
|
- add inertia effect
|
||||||
|
|
||||||
|
3.2
|
||||||
|
- fix wind effect cannot set key issue
|
||||||
|
|
||||||
|
3.1
|
||||||
|
- add bind controller
|
||||||
|
- add wind
|
||||||
|
- add flex setting
|
||||||
|
- improve performance
|
||||||
|
- fix twist bug
|
||||||
|
- add capsule icon
|
||||||
|
- seperate skinTools to spring magic and skin magic
|
||||||
|
|
||||||
|
3.0
|
||||||
|
- re-write spring magic to improve performance
|
||||||
|
- add capsule collision for spring magic
|
||||||
|
- add donate page
|
||||||
|
|
||||||
|
2.7.8
|
||||||
|
- fix script stop working issue cause by highend3d.com changed their web page
|
||||||
|
|
||||||
|
2.7.7
|
||||||
|
- add time out for update checking in case of network issue
|
||||||
|
|
||||||
|
2.7.6
|
||||||
|
- fix spring magic calculation issue on MAYA 2016
|
||||||
|
- update UI for MAYA 2016
|
||||||
|
Thanks for all the help from Nobuyuki Kobayashi nobuyuki@unity3d.com
|
||||||
|
|
||||||
|
2.7.5
|
||||||
|
- add floor collision to spring magic
|
||||||
|
|
||||||
|
2.7
|
||||||
|
- Add spring magic
|
BIN
Scripts/Animation/springmagic/icons/China Flag.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
Scripts/Animation/springmagic/icons/addCapsule.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
Scripts/Animation/springmagic/icons/addPlane.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
Scripts/Animation/springmagic/icons/ali_pay.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
Scripts/Animation/springmagic/icons/bilibili.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
Scripts/Animation/springmagic/icons/bitcoin.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
Scripts/Animation/springmagic/icons/clearCapsule.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
Scripts/Animation/springmagic/icons/ctrl_bake.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
Scripts/Animation/springmagic/icons/ctrl_bind.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
Scripts/Animation/springmagic/icons/donut.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Scripts/Animation/springmagic/icons/english.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
Scripts/Animation/springmagic/icons/info.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
Scripts/Animation/springmagic/icons/japanese.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
Scripts/Animation/springmagic/icons/language.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
Scripts/Animation/springmagic/icons/linkedin.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
Scripts/Animation/springmagic/icons/paypal.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
Scripts/Animation/springmagic/icons/redo.png
Normal file
After Width: | Height: | Size: 4.2 KiB |