Updated
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()
|
57
Scripts/Animation/springmagic/UtilityFunctions.txt
Normal file
@ -0,0 +1,57 @@
|
||||
############################################
|
||||
# Utility Functions
|
||||
############################################
|
||||
|
||||
# to get object parrent
|
||||
# parent = obj.getParent()
|
||||
|
||||
# to get all parents of a joint
|
||||
# parentList = joint.getAllParents()
|
||||
|
||||
# to get root bone
|
||||
# rootBone = joint.root()
|
||||
|
||||
# to get object all children
|
||||
# children = pm.listRelatives(obj, allDescendents = 1)
|
||||
|
||||
# to make sure the selection is a mesh
|
||||
# pm.nodeType(pm.ls(sl=True, type='transform')[0].getShape()) == 'mesh'
|
||||
|
||||
# to get vertex in selection as flatten
|
||||
# pm.ls(sl=True, type='float3', flatten=True)[0]
|
||||
|
||||
# to get skin cluster
|
||||
# pm.listHistory(pm.ls(sl=True), type='skinCluster')[0]
|
||||
|
||||
# to get all influcent bone of a skin cluster
|
||||
# obj.getInfluence()
|
||||
|
||||
# About path module
|
||||
|
||||
# from pymel.util.path import path
|
||||
# filePath = 'c:/temp/test/myTestFile.txt'
|
||||
# fpPathObj = path(filePath)
|
||||
# fpPathObj
|
||||
# # Result: path('c:/temp/test/myTestFile.txt') #
|
||||
# fpPathObj.basename()
|
||||
# # Result: 'myTestFile.txt' #
|
||||
# # .name is a property which returns the same
|
||||
# fpPathObj.name
|
||||
# # Result: 'myTestFile.txt' #
|
||||
# # namebase returns fileName only w/o extension
|
||||
# fpPathObj.namebase
|
||||
# # Result: 'myTestFile' #
|
||||
# # return directory above file
|
||||
# fpPathObj.parent
|
||||
# # Result: path('c:/temp/test') #
|
||||
# # check extension
|
||||
# fpPathObj.endswith('txt')
|
||||
# # Result: True #
|
||||
# # check existance
|
||||
# fpPathObj.exists()
|
||||
# # Result: True #
|
||||
# # check to see if folder type
|
||||
# fpPathObj.parent.isdir()
|
||||
# # Result: True #
|
||||
# fpPathObj.parent.parent.name
|
||||
# # Result: 'temp' #
|
11
Scripts/Animation/springmagic/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
# from springmagic.main import main
|
||||
|
||||
__version__ = "3.5a"
|
||||
|
||||
def version():
|
||||
"""
|
||||
Return the current version of the Spring Magic
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return __version__
|
942
Scripts/Animation/springmagic/core.py
Normal file
@ -0,0 +1,942 @@
|
||||
# - * - 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)
|
||||
|
119
Scripts/Animation/springmagic/decorators.py
Normal file
@ -0,0 +1,119 @@
|
||||
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/Shelf.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
Scripts/Animation/springmagic/icons/Title.png
Normal file
After Width: | Height: | Size: 4.3 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 |
BIN
Scripts/Animation/springmagic/icons/removeCapsule.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
Scripts/Animation/springmagic/icons/spring.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Scripts/Animation/springmagic/icons/update.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
Scripts/Animation/springmagic/icons/vimeo.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
Scripts/Animation/springmagic/icons/wechat_pay.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
Scripts/Animation/springmagic/icons/weightBone.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
Scripts/Animation/springmagic/icons/wind.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
Scripts/Animation/springmagic/icons/youtube.png
Normal file
After Width: | Height: | Size: 18 KiB |
12
Scripts/Animation/springmagic/main.py
Normal file
@ -0,0 +1,12 @@
|
||||
import Animation.springmagic as springmagic
|
||||
import Animation.springmagic.ui as ui
|
||||
|
||||
def main(*args, **kwargs):
|
||||
|
||||
widget = ui.SpringMagicWidget()
|
||||
widget.show()
|
||||
|
||||
# if __name__ == "__main__":
|
||||
|
||||
# with springmagic.app():
|
||||
# springmagic.main()
|
34
Scripts/Animation/springmagic/mkDevTools.py
Normal file
@ -0,0 +1,34 @@
|
||||
from os import path
|
||||
import compileall
|
||||
import sys, types
|
||||
|
||||
def recompile(modulename):
|
||||
"""Recompile the given module, its directory's contents!"""
|
||||
myScriptPath = sys.modules[modulename.__name__].__path__[0]
|
||||
if path.isdir(myScriptPath):
|
||||
compileall.compile_dir(myScriptPath, force=True)
|
||||
|
||||
def reload_module(modulename):
|
||||
"""Reload the given module and all children"""
|
||||
|
||||
# Get a reference to each loaded module
|
||||
loaded_modules = dict([(key, value) for key, value in sys.modules.items()
|
||||
if key.startswith(modulename.__name__) and
|
||||
isinstance(value, types.ModuleType)])
|
||||
|
||||
# Delete references to these loaded modules from sys.modules
|
||||
for key in loaded_modules:
|
||||
del sys.modules[key]
|
||||
|
||||
# Load each of the modules again
|
||||
# Make old modules share state with new modules
|
||||
for key in loaded_modules:
|
||||
print 're-loading %s' % key
|
||||
newmodule = __import__(key)
|
||||
oldmodule = loaded_modules[key]
|
||||
oldmodule.__dict__.clear()
|
||||
oldmodule.__dict__.update(newmodule.__dict__)
|
||||
|
||||
def refresh(modulename):
|
||||
recompile(modulename)
|
||||
reload_module(modulename)
|
27
Scripts/Animation/springmagic/springMagic.py
Normal file
@ -0,0 +1,27 @@
|
||||
import sys
|
||||
import os
|
||||
import inspect
|
||||
|
||||
# Add SprinMagic path to PYTHON_PATH
|
||||
script_name = inspect.getframeinfo(inspect.currentframe()).filename
|
||||
script_path = os.path.dirname(os.path.abspath(script_name))
|
||||
path_name = os.path.dirname(script_path)
|
||||
|
||||
if os.path.exists(path_name) and path_name not in sys.path:
|
||||
sys.path.append(path_name)
|
||||
|
||||
# Import SpringMagic module
|
||||
import springmagic
|
||||
import springmagic.mkDevTools as dev
|
||||
|
||||
# Recompile SpringMagic module if modification has been made
|
||||
# dev.refresh(springmagic)
|
||||
|
||||
# Launch SpringMagic
|
||||
springmagic.main()
|
||||
|
||||
# Unload SpringMagic module
|
||||
del(springmagic)
|
||||
|
||||
# Remove SprinMagic path from PYTHON_PATH
|
||||
sys.path.remove(path_name)
|
1931
Scripts/Animation/springmagic/springMagic.ui
Normal file
2085
Scripts/Animation/springmagic/springMagic_chn.ui
Normal file
1931
Scripts/Animation/springmagic/springMagic_eng.ui
Normal file
2028
Scripts/Animation/springmagic/springMagic_jpn.ui
Normal file
274
Scripts/Animation/springmagic/springMath.py
Normal file
@ -0,0 +1,274 @@
|
||||
import math
|
||||
import pymel.core as pm
|
||||
import pymel.core.datatypes as dt
|
||||
|
||||
def sigmoid(x):
|
||||
return 1 / (1 + math.exp(-x))
|
||||
|
||||
def distance(a, b):
|
||||
return (b - a).length()
|
||||
|
||||
def lerp_vec(a, b, t):
|
||||
return (a * (1 - t)) + (b * t)
|
||||
|
||||
def dist_to_plane(pt, n, d):
|
||||
return n.dot(pt) - (d / n.dot(n))
|
||||
|
||||
def dist_to_line(a, b, p):
|
||||
ap = p - a
|
||||
ab = b - a
|
||||
result = a + ((ap.dot(ab) / ab.dot(ab)) * ab)
|
||||
return distance(result, p)
|
||||
|
||||
def is_same_side_of_plane(pt, test_pt, n, d):
|
||||
d1 = math.copysign(1, dist_to_plane(pt, n, d))
|
||||
d2 = math.copysign(1, dist_to_plane(test_pt, n, d))
|
||||
|
||||
# print(pt, test_pt, d1, d2)
|
||||
return d1 * d2 == 1.0
|
||||
|
||||
def proj_pt_to_plane(pt, n, d):
|
||||
t = n.dot(pt) - d
|
||||
return (pt - (n * t))
|
||||
|
||||
def pt_in_sphere(pt, c, r):
|
||||
return (pt - c).length() <= r
|
||||
|
||||
def pt_in_cylinder(pt, p, q, r):
|
||||
n = (q - p).normal()
|
||||
d = n.dot(p)
|
||||
|
||||
if not is_same_side_of_plane(pt, (p + q) / 2.0, n, d):
|
||||
return False
|
||||
|
||||
n = (q - p).normal()
|
||||
d = n.dot(q)
|
||||
|
||||
if not is_same_side_of_plane(pt, (p + q) / 2.0, n, d):
|
||||
return False
|
||||
|
||||
proj_pt = proj_pt_to_plane(pt, n, d)
|
||||
# logging("proj_pt", proj_pt)
|
||||
# logging("q", q)
|
||||
# logging("distance(proj_pt, q)", distance(proj_pt, q))
|
||||
|
||||
return distance(proj_pt, q) <= r
|
||||
|
||||
def segment_sphere_isect(sa, sb, c, r):
|
||||
NotFound = (False, None)
|
||||
|
||||
p = sa
|
||||
d = (sb - sa).normal()
|
||||
|
||||
m = p - c
|
||||
b = m.dot(d)
|
||||
c = m.dot(m) - r * r
|
||||
|
||||
if c > 0.0 and b > 0.0:
|
||||
return NotFound
|
||||
|
||||
discr = b * b - c
|
||||
if discr < 0.0:
|
||||
return NotFound
|
||||
|
||||
t = -b - math.sqrt(discr)
|
||||
if t < 0.0:
|
||||
return NotFound
|
||||
|
||||
dist = distance(sa, sb)
|
||||
q = p + d * t
|
||||
return ((t >= 0 and t <= dist), q)
|
||||
|
||||
def segment_cylinder_isect(sa, sb, p, q, r):
|
||||
SM_EPSILON = 1e-6
|
||||
d = q - p
|
||||
m = sa - p
|
||||
n = sb - sa
|
||||
md = m.dot(d)
|
||||
nd = n.dot(d)
|
||||
dd = d.dot(d)
|
||||
|
||||
NotFound = (False, None)
|
||||
if md < 0 and md + nd < 0:
|
||||
return NotFound
|
||||
|
||||
if md > dd and md + nd > dd:
|
||||
return NotFound
|
||||
|
||||
nn = n.dot(n)
|
||||
mn = m.dot(n)
|
||||
|
||||
a = dd * nn - nd * nd
|
||||
k = m.dot(m) - r * r
|
||||
c = dd * k - md * md
|
||||
|
||||
if abs(a) < SM_EPSILON:
|
||||
if c > 0:
|
||||
return NotFound
|
||||
if md < 0:
|
||||
t = -mn / nn
|
||||
elif md > dd:
|
||||
t = (nd - mn) / nn
|
||||
else:
|
||||
t = 0
|
||||
return (True, lerp_vec(sa, sb, t))
|
||||
|
||||
b = dd * mn - nd * md
|
||||
discr = b * b - a * c
|
||||
if discr < 0:
|
||||
return NotFound
|
||||
|
||||
t = (-b - math.sqrt(discr)) / a
|
||||
if t < 0.0 or t > 1.0:
|
||||
return NotFound
|
||||
if (md + t * nd < 0.0):
|
||||
if nd <= 0.0:
|
||||
return NotFound
|
||||
t = -md / nd
|
||||
return (k + 2 * t * (mn + t * nn) <= 0.0, lerp_vec(sa, sb, t))
|
||||
elif md + t * nd > dd:
|
||||
if nd >= 0.0:
|
||||
return NotFound
|
||||
t = (dd - md) / nd
|
||||
return (k + dd - 2 * md + t * (2 * (mn - nd) + t * nn) <= 0.0, lerp_vec(sa, sb, t))
|
||||
|
||||
return (True, lerp_vec(sa, sb, t))
|
||||
|
||||
def pt_in_capsule(pt, p, q, r):
|
||||
return pt_in_cylinder(pt, p, q, r) or pt_in_sphere(pt, p, r) or pt_in_sphere(pt, q, r)
|
||||
|
||||
def segment_capsule_isect(sa, sb, p, q, r):
|
||||
# sa = dt.Vector()
|
||||
# ray start point pos vector
|
||||
# sb = dt.Vector()
|
||||
# ray end point pos vector
|
||||
# p = dt.Vector()
|
||||
# capsle one sphere tip pos
|
||||
# q = dt.Vector()
|
||||
# capsle another sphere tip pos
|
||||
# r = float
|
||||
# radio of capsle sphere
|
||||
|
||||
if pt_in_capsule(sa, p, q, r):
|
||||
if pt_in_capsule(sb, p, q, r):
|
||||
# both inside. extend sb to get intersection
|
||||
newb = sa + (sb - sa).normal() * 200.0
|
||||
sa, sb = newb, sa
|
||||
else:
|
||||
sb, sa = sa, sb
|
||||
|
||||
# d = (sb - sa).normal()
|
||||
|
||||
i1 = segment_sphere_isect(sa, sb, p, r)
|
||||
i2 = segment_sphere_isect(sa, sb, q, r)
|
||||
i3 = segment_cylinder_isect(sa, sb, p, q, r)
|
||||
|
||||
dist = float('inf')
|
||||
closest_pt = None
|
||||
hit = False
|
||||
hitCylinder = False
|
||||
|
||||
for i in [i1, i2, i3]:
|
||||
|
||||
if i[0]:
|
||||
hit = True
|
||||
pt = i[1]
|
||||
|
||||
if distance(sa, pt) < dist:
|
||||
closest_pt = pt
|
||||
|
||||
dist = min(dist, distance(sa, pt))
|
||||
# draw_locator(i1[2], 'i1')
|
||||
|
||||
return (hit, closest_pt, hitCylinder)
|
||||
|
||||
def checkCollision(cur_pos, pre_pos, capsuleLst, isRevert):
|
||||
# calculate collision with all the capsule in scene
|
||||
if isRevert:
|
||||
sa = cur_pos
|
||||
sb = pre_pos
|
||||
else:
|
||||
sb = cur_pos
|
||||
sa = pre_pos
|
||||
|
||||
isHited = False
|
||||
closest_pt_dict = {}
|
||||
|
||||
for obj in capsuleLst:
|
||||
objChildren = pm.listRelatives(obj, children=1, type='transform')
|
||||
p = objChildren[0].getTranslation(space='world')
|
||||
q = objChildren[1].getTranslation(space='world')
|
||||
r = obj.getAttr('scaleZ') * 1
|
||||
|
||||
hit, closest_pt, hitCylinder = segment_capsule_isect(sa, sb, p, q, r)
|
||||
|
||||
if hit:
|
||||
isHited = True
|
||||
closest_pt_dict[obj.name()] = [obj, closest_pt]
|
||||
# drawDebug_box(closest_pt)
|
||||
|
||||
if isHited:
|
||||
pt_length = 9999
|
||||
closest_pt = None
|
||||
col_obj = None
|
||||
|
||||
for pt in closest_pt_dict.keys():
|
||||
lLength = (closest_pt_dict[pt][1] - pre_pos).length()
|
||||
|
||||
if lLength < pt_length:
|
||||
pt_length = lLength
|
||||
closest_pt = closest_pt_dict[pt][1]
|
||||
col_obj = closest_pt_dict[pt][0]
|
||||
|
||||
# return col pt and col_body speed
|
||||
return closest_pt, col_obj, hitCylinder
|
||||
else:
|
||||
return None, None, None
|
||||
|
||||
def ckeckPointInTri(pos, pa, pb, pc):
|
||||
ra = math.acos(((pa - pos).normal()).dot((pb - pos).normal()))
|
||||
ra = dt.degrees(ra)
|
||||
rb = math.acos(((pb - pos).normal()).dot((pc - pos).normal()))
|
||||
rb = dt.degrees(rb)
|
||||
rc = math.acos(((pc - pos).normal()).dot((pa - pos).normal()))
|
||||
rc = dt.degrees(rc)
|
||||
|
||||
return (abs(ra + rb + rc) > 359)
|
||||
|
||||
def getVertexPositions(obj):
|
||||
vertex_positions_list = []
|
||||
|
||||
for vertex in obj.vtx:
|
||||
vertex_positions_list.append(vertex.getPosition(space='world'))
|
||||
|
||||
return vertex_positions_list
|
||||
|
||||
def checkPlaneCollision(objPos, childPos, colPlane):
|
||||
|
||||
v_coords = getVertexPositions(colPlane)
|
||||
|
||||
collision_plane_matrix = pm.xform(colPlane, worldSpace=1, matrix=1, q=1)
|
||||
n = dt.Vector(collision_plane_matrix[4:7]) # Y axis direction of plane
|
||||
q = v_coords[1]
|
||||
d = n.dot(q)
|
||||
|
||||
# get obj distance to plane
|
||||
toPlaneDistance = dist_to_plane(objPos, n, d)
|
||||
toPlaneDistance_child = dist_to_plane(childPos, n, d)
|
||||
|
||||
# child projection position on plane
|
||||
projectPos_child = proj_pt_to_plane(childPos, n, d)
|
||||
|
||||
inPlane = False
|
||||
|
||||
if ckeckPointInTri(projectPos_child, v_coords[0], v_coords[1], v_coords[2]):
|
||||
inPlane = True
|
||||
elif ckeckPointInTri(projectPos_child, v_coords[3], v_coords[1], v_coords[2]):
|
||||
inPlane = True
|
||||
|
||||
# bone above plane and bone child under plane and child project point on plane
|
||||
# means has collision with plane
|
||||
if (toPlaneDistance > 0) and (toPlaneDistance_child < 0) and inPlane:
|
||||
return projectPos_child
|
||||
else:
|
||||
return None
|
476
Scripts/Animation/springmagic/ui.py
Normal file
@ -0,0 +1,476 @@
|
||||
import os
|
||||
import time
|
||||
import inspect
|
||||
import webbrowser
|
||||
try:
|
||||
import urllib.request as urllib
|
||||
except:
|
||||
import urllib2 as urllib
|
||||
import random
|
||||
import datetime
|
||||
import maya.mel as mel
|
||||
import pymel.core as pm
|
||||
|
||||
import Animation.springmagic.core as core
|
||||
|
||||
from shutil import copyfile
|
||||
|
||||
kSpringMagicVersion = 30500
|
||||
|
||||
scriptName = inspect.getframeinfo(inspect.currentframe()).filename
|
||||
scriptPath = os.path.dirname(os.path.abspath(scriptName))
|
||||
|
||||
# Parameter Initialization
|
||||
ui_file = scriptPath + os.sep + 'springMagic.ui'
|
||||
|
||||
# Constants
|
||||
kPaypalLink = r'https://www.paypal.me/Yanbin'
|
||||
kLinkedinLink = r'https://ca.linkedin.com/in/baiyanbin'
|
||||
kVimeoLink = r''
|
||||
kBilibiliLink = r'https://animbai.com/zh/2017/10/14/skintools-tutorials/'
|
||||
kYoutubeLink = r'https://animbai.com/2017/10/14/skintools-tutorials/'
|
||||
|
||||
kUpdateLink = r'https://animbai.com/category/download/'
|
||||
kVersionCheckLink = r'http://animbai.com/skintoolsver/'
|
||||
kOldPersonalLink = r'http://www.scriptspot.com/3ds-max/scripts/spring-magic'
|
||||
|
||||
def widgetPath(windowName, widgetNames):
|
||||
"""
|
||||
@param windowName: Window instance name to search
|
||||
@param widgetNames: list of names to search for
|
||||
"""
|
||||
returnDict = {}
|
||||
mayaWidgetList = pm.lsUI(dumpWidgets=True)
|
||||
|
||||
for widget in widgetNames:
|
||||
for mayaWidget in mayaWidgetList:
|
||||
if windowName in mayaWidget:
|
||||
if mayaWidget.endswith(widget):
|
||||
returnDict[widget] = mayaWidget
|
||||
|
||||
return returnDict
|
||||
|
||||
class SpringMagicWidget():
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.init()
|
||||
|
||||
def init(self):
|
||||
try:
|
||||
pm.deleteUI(self.ui)
|
||||
except:
|
||||
pass
|
||||
|
||||
# title = pm.window(pm.loadUI(ui_file = ui_file))
|
||||
|
||||
self.ui = pm.loadUI(f=ui_file)
|
||||
|
||||
ui_widget_list = [
|
||||
'donatePayPal_button',
|
||||
'main_progressBar',
|
||||
'main_processLabel',
|
||||
'main_textEdit',
|
||||
'main_lang_id',
|
||||
'spring_language_list',
|
||||
'springSpring_lineEdit',
|
||||
'springSubs_lineEdit',
|
||||
'springXspring_lineEdit',
|
||||
'springTension_lineEdit',
|
||||
'springExtend_lineEdit',
|
||||
'springInertia_lineEdit',
|
||||
'springSubDiv_lineEdit',
|
||||
'springLoop_checkBox',
|
||||
'springPoseMatch_checkBox',
|
||||
'springClearSubFrame_checkBox',
|
||||
'springFrom_lineEdit',
|
||||
'springEnd_lineEdit',
|
||||
'springActive_radioButton',
|
||||
'springFrom_radioButton',
|
||||
# 'springUpAxis_comboBox',
|
||||
'springApply_Button',
|
||||
'springCapsule_checkBox',
|
||||
'springFastMove_checkBox',
|
||||
'springFloor_checkBox',
|
||||
'springFloor_lineEdit',
|
||||
'springBindPose_button',
|
||||
'springStraight_button',
|
||||
'springCopy_button',
|
||||
'springPaste_button',
|
||||
# 'donateBitcoin_lineEdit',
|
||||
'miscUpdate_pushButton',
|
||||
'springAddBody_Button',
|
||||
'springClearBody_Button',
|
||||
'springAddPlane_Button',
|
||||
'springAddWindCmd',
|
||||
'springBind_Button',
|
||||
'springBake_Button',
|
||||
'shelf_button',
|
||||
'link_pushButton',
|
||||
'vimeo_pushButton',
|
||||
'bilibili_pushButton',
|
||||
'language_button',
|
||||
'statusbar',
|
||||
'springWind_Button']
|
||||
|
||||
self.uiObjects = widgetPath(self.ui, ui_widget_list)
|
||||
|
||||
# Main UI
|
||||
self.main_progressBar = pm.progressBar(self.uiObjects['main_progressBar'], edit=True)
|
||||
self.main_processLabel = pm.text(self.uiObjects['main_processLabel'], edit=True)
|
||||
self.main_lineEdit = pm.ui.PyUI(self.uiObjects['main_textEdit'], edit=True)
|
||||
self.lang_id = pm.text(self.uiObjects['main_lang_id'], edit=True)
|
||||
|
||||
self.language_list = pm.textScrollList(self.uiObjects['spring_language_list'], edit=True, selectCommand=self.languageSelectedCmd, visible=False)
|
||||
|
||||
self.spring_lineEdit = pm.textField(self.uiObjects['springSpring_lineEdit'], edit=True, changeCommand=self.springRatioChangeCmd)
|
||||
self.subs_lineEdit = pm.textField(self.uiObjects['springSubs_lineEdit'], edit=True)
|
||||
self.Xspring_lineEdit = pm.textField(self.uiObjects['springXspring_lineEdit'], edit=True, changeCommand=self.twistChangeCmd)
|
||||
self.tension_lineEdit = pm.textField(self.uiObjects['springTension_lineEdit'], edit=True, changeCommand=self.tensionChangeCmd)
|
||||
self.extend_lineEdit = pm.textField(self.uiObjects['springExtend_lineEdit'], edit=True, changeCommand=self.extendChangeCmd)
|
||||
self.inertia_lineEdit = pm.textField(self.uiObjects['springInertia_lineEdit'], edit=True, changeCommand=self.inertiaChangeCmd)
|
||||
self.sub_division_lineEdit = pm.textField(self.uiObjects['springSubDiv_lineEdit'], edit=True, changeCommand=self.subDivChangeCmd)
|
||||
self.loop_checkBox = pm.checkBox(self.uiObjects['springLoop_checkBox'], edit=True)
|
||||
self.pose_match_checkBox = pm.checkBox(self.uiObjects['springPoseMatch_checkBox'], edit=True)
|
||||
self.clear_subframe_checkBox = pm.checkBox(self.uiObjects['springClearSubFrame_checkBox'], edit=True)
|
||||
self.from_lineEdit = pm.textField(self.uiObjects['springFrom_lineEdit'], edit=True)
|
||||
self.end_lineEdit = pm.textField(self.uiObjects['springEnd_lineEdit'], edit=True)
|
||||
self.active_radioButton = pm.radioButton(self.uiObjects['springActive_radioButton'], edit=True)
|
||||
self.from_radioButton = pm.radioButton(self.uiObjects['springFrom_radioButton'], edit=True)
|
||||
# self.upAxis_comboBox = pm.optionMenu(self.uiObjects['springUpAxis_comboBox'], edit=True)
|
||||
self.apply_button = pm.button(self.uiObjects['springApply_Button'], edit=True, command=self.applyCmd)
|
||||
self.add_body_button = pm.button(self.uiObjects['springAddBody_Button'], edit=True, command=self.addBodyCmd)
|
||||
self.clear_body_button = pm.button(self.uiObjects['springClearBody_Button'], edit=True, command=self.clearBodyCmd)
|
||||
self.add_plane_button = pm.button(self.uiObjects['springAddPlane_Button'], edit=True, command=self.createColPlaneCmd)
|
||||
self.wind_button = pm.button(self.uiObjects['springWind_Button'], edit=True, command=self.addWindCmd)
|
||||
self.bind_button = pm.button(self.uiObjects['springBind_Button'], edit=True, command=self.bindControlsCmd)
|
||||
self.bake_button = pm.button(self.uiObjects['springBake_Button'], edit=True, command=self.clearBindCmd)
|
||||
self.shelf_button = pm.button(self.uiObjects['shelf_button'], edit=True, command=self.goShelfCmd)
|
||||
self.link_button = pm.button(self.uiObjects['link_pushButton'], edit=True, command=self.linkinCmd)
|
||||
self.vimeo_button = pm.button(self.uiObjects['vimeo_pushButton'], edit=True, command=self.youtubeCmd)
|
||||
self.bilibili_button = pm.button(self.uiObjects['bilibili_pushButton'], edit=True, command=self.bilibiliCmd)
|
||||
self.language_button = pm.button(self.uiObjects['language_button'], edit=True, command=self.languageCmd)
|
||||
|
||||
self.collision_checkBox = pm.checkBox(self.uiObjects['springCapsule_checkBox'], edit=True)
|
||||
self.fast_move_checkBox = pm.checkBox(self.uiObjects['springFastMove_checkBox'], edit=True)
|
||||
self.floor_checkBox = pm.checkBox(self.uiObjects['springFloor_checkBox'], edit=True)
|
||||
self.floor_lineEdit = pm.textField(self.uiObjects['springFloor_lineEdit'], edit=True, changeCommand=self.twistChangeCmd)
|
||||
|
||||
self.bind_pose_button = pm.button(self.uiObjects['springBindPose_button'], edit=True, command=self.setCmd)
|
||||
self.straight_button = pm.button(self.uiObjects['springStraight_button'], edit=True, command=self.straightCmd)
|
||||
self.copy_button = pm.button(self.uiObjects['springCopy_button'], edit=True, command=self.copyCmd)
|
||||
self.paste_button = pm.button(self.uiObjects['springPaste_button'], edit=True, command=self.pasteCmd)
|
||||
|
||||
# self.statusbar = pm.button(self.uiObjects['statusbar'], edit=True, menuItemCommand=self.testCmd)
|
||||
|
||||
# donate UI
|
||||
# self.donate_bitcoin_lineEdit = pm.textField(self.uiObjects['donateBitcoin_lineEdit'], edit=True, text=kBitcoin)
|
||||
|
||||
self.misc_update_button = pm.button(self.uiObjects['miscUpdate_pushButton'], edit=True, command=self.updatePageCmd)
|
||||
|
||||
# SpringMagic_mainWindow11|centralwidget|miscUpdate_pushButton
|
||||
# SpringMagic_mainWindow11|centralwidget|miscUpdate_pushButton
|
||||
# 'miscUpdate_pushButton': ui.Button('SpringMagic_mainWindow11|centralwidget|miscUpdate_pushButton')
|
||||
# miscUpdate_button = pm.button( centralwidget + '|miscUpdate_pushButton', edit = True )
|
||||
|
||||
pm.button(self.uiObjects['donatePayPal_button'], edit=True, command=self.donatePayPalCmd)
|
||||
|
||||
self.spam_word = ['', '', '', '', '']
|
||||
|
||||
def show(self):
|
||||
|
||||
pm.showWindow(self.ui)
|
||||
|
||||
self.checkUpdate()
|
||||
|
||||
def progression_callback(self, progression):
|
||||
pm.progressBar(self.main_progressBar, edit=True, progress=progression)
|
||||
|
||||
#############################################
|
||||
# Buttons callbacks
|
||||
############################################
|
||||
|
||||
def showSpam(self, *args):
|
||||
sWord = self.spam_word[random.randint(0, 4)]
|
||||
# print as unicode
|
||||
pm.text(self.main_processLabel, edit=True, label=str(sWord))
|
||||
|
||||
def linkinCmd(self, *args):
|
||||
# open my linked in page :)
|
||||
url = kLinkedinLink
|
||||
|
||||
webbrowser.open(url, new=2)
|
||||
|
||||
def pasteCmd(self, *args):
|
||||
core.pasteBonePose()
|
||||
|
||||
def setCmd(self, *args):
|
||||
|
||||
picked_bones = pm.ls(sl=1, type='joint')
|
||||
|
||||
if picked_bones:
|
||||
self.apply_button.setEnable(False)
|
||||
|
||||
core.bindPose()
|
||||
|
||||
# Select only the joints
|
||||
pm.select(picked_bones)
|
||||
|
||||
self.apply_button.setEnable(True)
|
||||
|
||||
def straightCmd(self, *args):
|
||||
|
||||
picked_bones = pm.ls(sl=1, type='joint')
|
||||
|
||||
if picked_bones:
|
||||
self.apply_button.setEnable(False)
|
||||
|
||||
for bone in picked_bones:
|
||||
core.straightBonePose(bone)
|
||||
|
||||
# Select only the joints
|
||||
pm.select(picked_bones)
|
||||
|
||||
self.apply_button.setEnable(True)
|
||||
|
||||
def applyCmd(self, *args):
|
||||
picked_transforms = pm.ls(sl=1, type='transform')
|
||||
|
||||
if picked_transforms:
|
||||
self.apply_button.setEnable(False)
|
||||
|
||||
pm.text(self.main_processLabel, edit=True, label='Calculating Bone Spring... (Esc to cancel)')
|
||||
|
||||
springRatio = 1 - float(self.spring_lineEdit.getText())
|
||||
twistRatio = 1 - float(self.Xspring_lineEdit.getText())
|
||||
isLoop = bool(self.loop_checkBox.getValue())
|
||||
isPoseMatch = bool(self.pose_match_checkBox.getValue())
|
||||
isFastMove = self.fast_move_checkBox.getValue()
|
||||
isCollision = self.collision_checkBox.getValue()
|
||||
|
||||
subDiv = 1.0
|
||||
if isCollision:
|
||||
subDiv = float(self.sub_division_lineEdit.getText())
|
||||
|
||||
# get frame range
|
||||
if self.active_radioButton.getSelect():
|
||||
startFrame = int(pm.playbackOptions(q=1, minTime=1))
|
||||
endFrame = int(pm.playbackOptions(q=1, maxTime=1))
|
||||
else:
|
||||
startFrame = int(self.from_lineEdit.getText())
|
||||
endFrame = int(self.end_lineEdit.getText())
|
||||
|
||||
tension = float(self.tension_lineEdit.getText())
|
||||
inertia = float(self.inertia_lineEdit.getText())
|
||||
extend = float(self.extend_lineEdit.getText())
|
||||
|
||||
wipeSubFrame = self.clear_subframe_checkBox.getValue()
|
||||
|
||||
spring = core.Spring(springRatio, twistRatio, tension, extend, inertia)
|
||||
springMagic = core.SpringMagic(startFrame, endFrame, subDiv, isLoop, isPoseMatch, isCollision, isFastMove, wipeSubFrame)
|
||||
|
||||
startTime = datetime.datetime.now()
|
||||
|
||||
try:
|
||||
core.startCompute(spring, springMagic, self.progression_callback)
|
||||
|
||||
deltaTime = (datetime.datetime.now() - startTime)
|
||||
|
||||
pm.text(self.main_processLabel, edit=True, label="Spring Calculation Time: {0}s".format(deltaTime.seconds))
|
||||
|
||||
except ValueError as exception:
|
||||
pm.text(self.main_processLabel, edit=True, label='Process aborted')
|
||||
pm.warning(exception)
|
||||
|
||||
# Select only the joints
|
||||
pm.select(picked_transforms)
|
||||
|
||||
pm.progressBar(self.main_progressBar, edit=True, progress=0)
|
||||
|
||||
self.apply_button.setEnable(True)
|
||||
|
||||
def copyCmd(self, *args):
|
||||
core.copyBonePose()
|
||||
|
||||
def webCmd(self, *args):
|
||||
# open my linked in page :)
|
||||
webbrowser.open(kOldPersonalLink, new=2)
|
||||
|
||||
def twistChangeCmd(self, *args):
|
||||
self.limitTextEditValue(self.Xspring_lineEdit, defaultValue=0.7)
|
||||
|
||||
def extendChangeCmd(self, *args):
|
||||
self.limitTextEditValue(self.extend_lineEdit, defaultValue=0.0)
|
||||
|
||||
def inertiaChangeCmd(self, *args):
|
||||
self.limitTextEditValue(self.inertia_lineEdit, defaultValue=0.0)
|
||||
|
||||
def springRatioChangeCmd(self, *args):
|
||||
self.limitTextEditValue(self.spring_lineEdit, defaultValue=0.7)
|
||||
|
||||
def tensionChangeCmd(self, *args):
|
||||
self.limitTextEditValue(self.tension_lineEdit, defaultValue=0.5)
|
||||
|
||||
def subDivChangeCmd(self, *args):
|
||||
# self.limitTextEditValue(self.sub_division_lineEdit, defaultValue=1)
|
||||
pass
|
||||
|
||||
def addWindCmd(self, *args):
|
||||
core.addWindObj()
|
||||
|
||||
def addBodyCmd(self, *args):
|
||||
core.addCapsuleBody()
|
||||
|
||||
def createColPlaneCmd(self, *args):
|
||||
core.createCollisionPlane()
|
||||
|
||||
def removeBodyCmd(self, *args):
|
||||
core.removeBody(clear=False)
|
||||
|
||||
def clearBodyCmd(self, *args):
|
||||
core.removeBody(clear=True)
|
||||
|
||||
def bindControlsCmd(self, *args):
|
||||
core.bindControls()
|
||||
|
||||
def clearBindCmd(self, *args):
|
||||
|
||||
# get frame range
|
||||
if self.active_radioButton.getSelect():
|
||||
startFrame = int(pm.playbackOptions(q=1, minTime=1))
|
||||
endFrame = int(pm.playbackOptions(q=1, maxTime=1))
|
||||
else:
|
||||
startFrame = int(self.from_lineEdit.getText())
|
||||
endFrame = int(self.end_lineEdit.getText())
|
||||
|
||||
core.clearBind(startFrame, endFrame)
|
||||
|
||||
def goShelfCmd(self, *args):
|
||||
parentTab = mel.eval('''global string $gShelfTopLevel;string $shelves = `tabLayout -q -selectTab $gShelfTopLevel`;''')
|
||||
imageTitlePath = scriptPath + os.sep + "icons" + os.sep + "Title.png"
|
||||
# commandLine = "execfile(r'{0}\\springMagic.py')".format(self.scriptPath)
|
||||
commandLine = "try:\n\timport springmagic\n\tspringmagic.main()\nexcept:\n\texecfile(r'{0}\springMagic.py')".format(scriptPath)
|
||||
|
||||
pm.shelfButton(commandRepeatable=True, image1=imageTitlePath, label="Spring Magic", parent=parentTab, command=commandLine)
|
||||
|
||||
def languageCmd(self, *args):
|
||||
self.language_list.setVisible(not self.language_list.getVisible())
|
||||
|
||||
def languageSelectedCmd(self, *args):
|
||||
self.language_list.setVisible(False)
|
||||
self.applyLanguage(int(self.language_list.getSelectIndexedItem()[0]))
|
||||
|
||||
def bilibiliCmd(self, *args):
|
||||
try:
|
||||
webbrowser.open(kBilibiliLink, new=2)
|
||||
except:
|
||||
pass
|
||||
|
||||
def youtubeCmd(self, *args):
|
||||
try:
|
||||
webbrowser.open(kYoutubeLink, new=2)
|
||||
except:
|
||||
pass
|
||||
|
||||
def vimeoCmd(self, *args):
|
||||
# try:
|
||||
# webbrowser.open(kVimeoLink, new=2)
|
||||
# except:
|
||||
# pass
|
||||
pass
|
||||
|
||||
def donatePayPalCmd(self, *args):
|
||||
try:
|
||||
webbrowser.open(kPaypalLink, new=2)
|
||||
except:
|
||||
pass
|
||||
|
||||
def updatePageCmd(self, *args):
|
||||
try:
|
||||
webbrowser.open(kUpdateLink, new=2)
|
||||
except:
|
||||
pass
|
||||
|
||||
def applyLanguage(self, lanId):
|
||||
lanDict = {1: '_chn', 2: '_eng', 3: '_jpn'}
|
||||
|
||||
if lanId in lanDict.keys():
|
||||
# get new language ui file path
|
||||
new_ui_file = scriptPath + os.sep + os.path.basename(ui_file).split('.')[0] + lanDict[lanId] + '.' + os.path.basename(ui_file).split('.')[1]
|
||||
copyfile(new_ui_file, ui_file)
|
||||
|
||||
# Reload interface
|
||||
self.init()
|
||||
self.show()
|
||||
|
||||
def detectMayaLanguage(self):
|
||||
mayaLan = None
|
||||
try:
|
||||
mayaLan = os.environ['MAYA_UI_LANGUAGE']
|
||||
except:
|
||||
import locale
|
||||
mayaLan = locale.getdefaultlocale()[0]
|
||||
|
||||
lanDict = {'zh_CN': 1, 'en_US': 2, 'ja_JP': 3}
|
||||
self.applyLanguage(lanDict[mayaLan])
|
||||
|
||||
def printTextEdit(self, textEdit, inputString):
|
||||
ctime = time.ctime()
|
||||
ptime = ctime.split(' ')
|
||||
inputString = ptime[3] + ' - ' + inputString
|
||||
pm.scrollField(textEdit, edit=True, insertionPosition=0, insertText=inputString + '\n')
|
||||
|
||||
def checkUpdate(self):
|
||||
|
||||
self.misc_update_button.setVisible(0)
|
||||
|
||||
page_content = None
|
||||
|
||||
site = kVersionCheckLink
|
||||
hdr = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
|
||||
'Accept-Encoding': 'none',
|
||||
'Accept-Language': 'en-US,en;q=0.8',
|
||||
'Connection': 'keep-alive'}
|
||||
|
||||
req = urllib.Request(site, headers=hdr)
|
||||
|
||||
try:
|
||||
page = urllib.urlopen(req, timeout=5)
|
||||
page_content = page.read()
|
||||
except:
|
||||
print('checkUpdate failed')
|
||||
|
||||
if page_content:
|
||||
page_content = page_content.decode('utf-8')
|
||||
if len(page_content.split('|springMagic|')) > 1:
|
||||
new_kSpringMagicVersion = int(page_content.split('|springMagic|')[1])
|
||||
|
||||
if new_kSpringMagicVersion > kSpringMagicVersion:
|
||||
self.misc_update_button.setVisible(1)
|
||||
|
||||
self.spam_word = []
|
||||
|
||||
prefix = '|spam'
|
||||
suffix = '|'
|
||||
|
||||
if self.lang_id.getLabel() == 'chn':
|
||||
suffix = 'chn|'
|
||||
|
||||
self.spam_word = [page_content.split(prefix + str(i) + suffix)[1] for i in range(1, 6)]
|
||||
else:
|
||||
pm.text(self.main_processLabel, edit=True, label='Check update failed, try later.')
|
||||
|
||||
self.showSpam()
|
||||
|
||||
def limitTextEditValue(self, ui_object, minValue=0, maxValue=1, roundF=2, defaultValue=0):
|
||||
value = 0
|
||||
|
||||
try:
|
||||
value = float(ui_object.getText())
|
||||
value = round(value, roundF)
|
||||
value = max(min(maxValue, value), minValue)
|
||||
except:
|
||||
value = defaultValue
|
||||
|
||||
ui_object.setText(str(value))
|
50
Scripts/Animation/springmagic/utility.py
Normal file
@ -0,0 +1,50 @@
|
||||
import pymel.core as pm
|
||||
import pymel.core.datatypes as dt
|
||||
|
||||
##########################
|
||||
# Usefull function
|
||||
##########################
|
||||
|
||||
def clamp(n, minn, maxn):
|
||||
return max(min(maxn, n), minn)
|
||||
|
||||
def get_node(name):
|
||||
node_list = pm.ls(name)
|
||||
node = None
|
||||
|
||||
if node_list:
|
||||
node = node_list[0]
|
||||
|
||||
return node
|
||||
|
||||
def get_matrix(obj):
|
||||
return pm.xform(obj, worldSpace=1, matrix=1, q=1)
|
||||
|
||||
def frange(start, stop=None, step=None):
|
||||
# if set start=0.0 and step = 1.0 if not specified
|
||||
start = float(start)
|
||||
|
||||
if stop is None:
|
||||
stop = start + 0.0
|
||||
start = 0.0
|
||||
|
||||
if step is None:
|
||||
step = 1.0
|
||||
|
||||
# print("start = ", start, "stop = ", stop, "step = ", step)
|
||||
|
||||
count = 0
|
||||
while True:
|
||||
temp = float(start + count * step)
|
||||
if step > 0 and temp >= stop:
|
||||
break
|
||||
elif step < 0 and temp <= stop:
|
||||
break
|
||||
yield temp
|
||||
count += 1
|
||||
|
||||
def get_translation(n):
|
||||
return dt.Vector(pm.xform(n, worldSpace=1, translation=1, query=1))
|
||||
|
||||
def get_rotation(n):
|
||||
return pm.xform(n, worldSpace=1, rotation=1, query=1)
|
18
Scripts/Animation/springmagic/安装说明.txt
Normal file
@ -0,0 +1,18 @@
|
||||
可以用 2 种方法来安装 spring magic Maya 版本
|
||||
|
||||
A:
|
||||
1. 解压缩 springmagic.zip 至任意目录, 但保持所有文件在 "springmagic" 目录下 ( 不要改变大小写 )
|
||||
2. 在 Maya 里运行如下 Python 命令, 会出现工具界面 ( 注意保留前面的小写 r )
|
||||
execfile(r'你的路径\springmagic\springMagic.py')
|
||||
例如
|
||||
execfile(r'D:\myScript\springmagic\springMagic.py')
|
||||
3. 用工具界面右上方的创建快捷按钮功能,在书签栏创建一个快捷按钮,方便下次使用
|
||||
|
||||
B:
|
||||
1. 解压缩 springmagic.zip 并复制 "springmagic" 目录到位于 Windows 用户路径下的 Maya 脚本目录
|
||||
例如
|
||||
"C:\Users\你的用户名\Documents\maya\scripts"
|
||||
2. 在 Maya 里运行如下 Python 命令, 会出现工具界面
|
||||
import springmagic
|
||||
springmagic.main()
|
||||
3. 用工具界面右上方的创建快捷按钮功能,在书签栏创建一个快捷按钮,方便下次使用
|