This commit is contained in:
Jeffreytsai1004 2025-01-14 03:07:45 +08:00
parent de7ea0e64f
commit 44e6f1d1a6
29 changed files with 1271 additions and 0 deletions

View 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+
![preview](./preview.PNG)
![preview](./preview1.PNG)

Binary file not shown.

View 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!")

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View 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)

View 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

View File

@ -0,0 +1,2 @@
import springmagic
springmagic.main()

View 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__

View 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)

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB