Compare commits
3 Commits
release-v1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cf75f21f4 | |||
| 52ac5cf5a6 | |||
| 9f30e905d7 |
2
.gitignore
vendored
@@ -1,5 +1,7 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*/__pycache__/
|
||||
__pycache__/*
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
|
||||
BIN
2023/icons/QuadRemesher.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
2023/icons/creaseplus.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
2023/icons/springmagic.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
2
2023/scripts/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -0,0 +1,8 @@
|
||||
1. Unzip all files
|
||||
|
||||
2. copy "springmagic" folder into Maya scripts path, i.e.
|
||||
"C:\Users\YOUR_USER_NAME\Documents\maya\scripts"
|
||||
|
||||
3. Start Maya and run command below in command bar with Python way
|
||||
import springmagic
|
||||
springmagic.main()
|
||||
@@ -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' #
|
||||
12
2023/scripts/animation_tools/springmagic/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
__version__ = "3.5a"
|
||||
|
||||
from springmagic.main import main
|
||||
|
||||
|
||||
def version():
|
||||
"""
|
||||
Return the current version of the Spring Magic
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return __version__
|
||||
976
2023/scripts/animation_tools/springmagic/core.py
Normal file
@@ -0,0 +1,976 @@
|
||||
# - * - 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
|
||||
|
||||
from . import decorators
|
||||
from . import springMath
|
||||
|
||||
from .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 = [[x] + [y for y in pm.listRelatives(x, allDescendents=True) if y in selected_ctrls][::-1]
|
||||
for x in 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 = [
|
||||
([x] if x.getParent() else []) + [y for y in pm.listRelatives(x, allDescendents=True) if y in objs][::-1] for x
|
||||
in 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 list(spring_data_dict.values()):
|
||||
|
||||
if not SpringMagicMaya.isInterrupted():
|
||||
spring_data.keyframe_child_proxy()
|
||||
|
||||
progression = next(progression_generator)
|
||||
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 list(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 list(
|
||||
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 = next(progression_generator)
|
||||
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 list(spring_data_dict.values())]
|
||||
|
||||
# Deactivate all pairblend otherwise bake doesn't work with animation layers
|
||||
for spring_data in list(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 list(SM_boneTransformDict.keys()):
|
||||
logging.debug(SM_boneTransformDict[obj][0])
|
||||
|
||||
obj.setTranslation(SM_boneTransformDict[obj][0])
|
||||
obj.setRotation(SM_boneTransformDict[obj][1])
|
||||
|
||||
|
||||
def preCheckCollision(objPos, objLength, capsuleList):
|
||||
# print('objPos:' + str(objPos))
|
||||
# print('objLength:' + str(objLength))
|
||||
|
||||
# pre check bone length compare with collision body radius
|
||||
# will improve performance if bone is far from capsule
|
||||
for capsule in capsuleList:
|
||||
capsule_children_list = pm.listRelatives(capsule, children=1, type='transform')
|
||||
|
||||
p = capsule_children_list[0].getTranslation(space='world')
|
||||
q = capsule_children_list[1].getTranslation(space='world')
|
||||
r = capsule.getAttr('scaleZ')
|
||||
|
||||
bone_to_capsule_distance = springMath.dist_to_line(p, q, objPos)
|
||||
|
||||
# print('p:' + str(p))
|
||||
# print('q:' + str(q))
|
||||
# print('r:' + str(r))
|
||||
# print('boneToCapsuleDistance:' + str(bone_to_capsule_distance))
|
||||
|
||||
# means close enough to have a hit change
|
||||
if bone_to_capsule_distance < objLength + r:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def repeatMoveToPlane(obj, objPos, objTarget, colPlane, times):
|
||||
# Y axis direction of plane
|
||||
n = dt.Vector(get_matrix(colPlane)[4:7])
|
||||
q = get_translation(colPlane)
|
||||
d = n.dot(q)
|
||||
|
||||
# for i in range(times):
|
||||
# pt = objPos
|
||||
# obj.setTranslation(proj_pt_to_plane(pt, n, d), space='world')
|
||||
# if (i + 1) != times:
|
||||
# obj.setTranslation(get_translation(objTarget), space='world')
|
||||
pt = objPos
|
||||
outPos = springMath.proj_pt_to_plane(pt, n, d)
|
||||
|
||||
return outPos
|
||||
|
||||
|
||||
def setWireShading(obj, tmp):
|
||||
obj.getShape().overrideEnabled.set(True)
|
||||
obj.getShape().overrideShading.set(False)
|
||||
|
||||
if tmp:
|
||||
obj.getShape().overrideDisplayType.set(1)
|
||||
|
||||
|
||||
def addCapsuleSphereConstraint(sphereObj):
|
||||
# create a locator and make sphere follow it
|
||||
locator = pm.spaceLocator(name=sphereObj.name() + '_locator' + kCapsuleNameSuffix)
|
||||
|
||||
locator.setTranslation(sphereObj.getTranslation())
|
||||
locator.setRotation(sphereObj.getRotation())
|
||||
locator.getShape().setAttr('visibility', False)
|
||||
|
||||
pm.parentConstraint(locator, sphereObj)
|
||||
|
||||
return locator
|
||||
|
||||
|
||||
def createCapsuleGeometry(size):
|
||||
# create geometry
|
||||
cylinder, cylinder_history = pm.cylinder(radius=size, sections=8, heightRatio=3)
|
||||
pm.rename(cylinder.name(), cylinder.name() + kCapsuleNameSuffix)
|
||||
|
||||
sphereA, sphereA_history = pm.sphere(radius=size, endSweep=180, sections=4)
|
||||
pm.rename(sphereA.name(), sphereA.name() + kCapsuleNameSuffix)
|
||||
|
||||
sphereB, sphereB_history = pm.sphere(radius=size, endSweep=180, sections=4)
|
||||
pm.rename(sphereB.name(), sphereB.name() + kCapsuleNameSuffix)
|
||||
|
||||
# set to wireframe shader
|
||||
setWireShading(cylinder, False)
|
||||
setWireShading(sphereA, True)
|
||||
setWireShading(sphereB, True)
|
||||
|
||||
# build a capsule with geometry
|
||||
cylinder.setAttr('rotateZ', 90)
|
||||
sphereA.setAttr('translateY', -1.5 * size)
|
||||
sphereB.setAttr('rotateZ', 180)
|
||||
sphereB.setAttr('translateY', 1.5 * size)
|
||||
|
||||
# add constrain
|
||||
locatorA = addCapsuleSphereConstraint(sphereA)
|
||||
locatorB = addCapsuleSphereConstraint(sphereB)
|
||||
|
||||
pm.parent(locatorA, cylinder)
|
||||
pm.parent(locatorB, cylinder)
|
||||
|
||||
pm.parent(sphereA, cylinder)
|
||||
pm.parent(sphereB, cylinder)
|
||||
|
||||
sphereA.setAttr('inheritsTransform', False)
|
||||
sphereB.setAttr('inheritsTransform', False)
|
||||
|
||||
pm.connectAttr(cylinder.scaleY, (sphereA_history.name() + '.radius'))
|
||||
pm.connectAttr(cylinder.scaleY, (sphereB_history.name() + '.radius'))
|
||||
pm.connectAttr(cylinder.scaleY, cylinder.scaleZ)
|
||||
|
||||
return cylinder
|
||||
|
||||
|
||||
def getCapsule(getAll):
|
||||
if getAll:
|
||||
nurbsTransLst = pm.ls(type='transform')
|
||||
else:
|
||||
nurbsTransLst = pm.ls(sl=True)
|
||||
|
||||
nurbsSurfaceLst = []
|
||||
for obj in nurbsTransLst:
|
||||
if obj.getShape() and (pm.nodeType(obj.getShape()) == 'nurbsSurface'):
|
||||
nurbsSurfaceLst.append(obj)
|
||||
|
||||
cylinderLst = []
|
||||
for obj in nurbsTransLst:
|
||||
if 'ylinder' in obj.name() and kCapsuleNameSuffix in obj.name():
|
||||
cylinderLst.append(obj)
|
||||
|
||||
return cylinderLst
|
||||
|
||||
|
||||
def addCapsuleBody():
|
||||
# create capsule body for collision
|
||||
# place capsule at ori point of nothing selected in scene
|
||||
# place capsule match with object position and rotation if select scene object
|
||||
collisionBoneList = []
|
||||
objs = pm.ls(sl=True)
|
||||
|
||||
for obj in objs:
|
||||
children = pm.listRelatives(obj, children=1)
|
||||
|
||||
# only add capsule to the obj which has child
|
||||
if children:
|
||||
collisionBoneList.append([obj, children[0]])
|
||||
|
||||
if collisionBoneList:
|
||||
for couple in collisionBoneList:
|
||||
baseBone = couple[0]
|
||||
endBone = couple[1]
|
||||
capsule = createCapsuleGeometry(1)
|
||||
|
||||
pm.parent(capsule, baseBone)
|
||||
# match capsule to bone
|
||||
endBoneTrans = endBone.getTranslation()
|
||||
capsule.setTranslation(endBoneTrans * 0.5)
|
||||
capsule.setAttr('scaleX', endBoneTrans[0] / 3)
|
||||
capsule.setAttr('scaleY', endBoneTrans[0] / 3)
|
||||
cns = pm.aimConstraint(endBone, capsule, aimVector=[1, 0, 0])
|
||||
pm.delete(cns)
|
||||
|
||||
else:
|
||||
capsule = createCapsuleGeometry(1)
|
||||
capsule.setAttr('scaleX', 10)
|
||||
capsule.setAttr('scaleY', 10)
|
||||
pm.select(clear=1)
|
||||
121
2023/scripts/animation_tools/springmagic/decorators.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import maya.mel as mel
|
||||
import maya.cmds as cmds
|
||||
|
||||
from functools import wraps
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Decorators
|
||||
# -----------------------------------------------------------------------------
|
||||
def viewportOff(func):
|
||||
"""
|
||||
Decorator - turn off Maya display while func is running.
|
||||
if func will fail, the error will be raised after.
|
||||
"""
|
||||
@wraps(func)
|
||||
def wrap(*args, **kwargs):
|
||||
|
||||
# Turn $gMainPane Off:
|
||||
mel.eval("paneLayout -e -manage false $gMainPane")
|
||||
|
||||
# Decorator will try/except running the function.
|
||||
# But it will always turn on the viewport at the end.
|
||||
# In case the function failed, it will prevent leaving maya viewport off.
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception:
|
||||
raise # will raise original error
|
||||
finally:
|
||||
mel.eval("paneLayout -e -manage true $gMainPane")
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
class gShowProgress(object):
|
||||
"""
|
||||
Function decorator to show the user (progress) feedback.
|
||||
@usage
|
||||
|
||||
import time
|
||||
@gShowProgress(end=10)
|
||||
def createCubes():
|
||||
for i in range(10):
|
||||
time.sleep(1)
|
||||
if createCubes.isInterrupted(): break
|
||||
iCube = cmds.polyCube(w=1,h=1,d=1)
|
||||
cmds.move(i,i*.2,0,iCube)
|
||||
createCubes.step()
|
||||
"""
|
||||
|
||||
def __init__(self, status='Busy...', start=0, end=100, interruptable=True):
|
||||
import maya.mel
|
||||
|
||||
self.mStartValue = start
|
||||
self.mEndValue = end
|
||||
self.mStatus = status
|
||||
self.mInterruptable = interruptable
|
||||
self.mMainProgressBar = maya.mel.eval('$tmp = $gMainProgressBar')
|
||||
|
||||
def step(self, inValue=1):
|
||||
"""Increase step
|
||||
@param inValue (int) Step value"""
|
||||
cmds.progressBar(self.mMainProgressBar, edit=True, step=inValue)
|
||||
|
||||
def progress(self, inValue):
|
||||
"""Set progression value
|
||||
@param inValue (int) Progress value"""
|
||||
cmds.progressBar(self.mMainProgressBar, edit=True, progress=inValue)
|
||||
|
||||
def isInterrupted(self):
|
||||
"""Check if the user has interrupted the progress
|
||||
@return (boolean)"""
|
||||
return cmds.progressBar(self.mMainProgressBar, query=True, isCancelled=True)
|
||||
|
||||
def start(self):
|
||||
"""Start progress"""
|
||||
cmds.waitCursor(state=True)
|
||||
cmds.progressBar(
|
||||
self.mMainProgressBar,
|
||||
edit=True,
|
||||
beginProgress=True,
|
||||
isInterruptable=self.mInterruptable,
|
||||
status=self.mStatus,
|
||||
minValue=self.mStartValue,
|
||||
maxValue=self.mEndValue)
|
||||
cmds.refresh()
|
||||
|
||||
def end(self):
|
||||
"""Mark the progress as ended"""
|
||||
cmds.progressBar(self.mMainProgressBar, edit=True, endProgress=True)
|
||||
cmds.waitCursor(state=False)
|
||||
|
||||
def __call__(self, inFunction):
|
||||
"""
|
||||
Override call method
|
||||
@param inFunction (function) Original function
|
||||
@return (function) Wrapped function
|
||||
@description
|
||||
If there are decorator arguments, __call__() is only called once,
|
||||
as part of the decoration process! You can only give it a single argument,
|
||||
which is the function object.
|
||||
"""
|
||||
def wrapped_f(*args, **kwargs):
|
||||
# Start progress
|
||||
self.start()
|
||||
# Call original function
|
||||
inFunction(*args, **kwargs)
|
||||
# End progress
|
||||
self.end()
|
||||
|
||||
# Add special methods to the wrapped function
|
||||
wrapped_f.step = self.step
|
||||
wrapped_f.progress = self.progress
|
||||
wrapped_f.isInterrupted = self.isInterrupted
|
||||
|
||||
# Copy over attributes
|
||||
wrapped_f.__doc__ = inFunction.__doc__
|
||||
wrapped_f.__name__ = inFunction.__name__
|
||||
wrapped_f.__module__ = inFunction.__module__
|
||||
|
||||
# Return wrapped function
|
||||
return wrapped_f
|
||||
75
2023/scripts/animation_tools/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
2023/scripts/animation_tools/springmagic/icons/China Flag.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/Shelf.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/Title.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/addCapsule.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/addPlane.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/bilibili.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/bitcoin.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/clearCapsule.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/ctrl_bake.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/ctrl_bind.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/donut.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/english.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/info.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/japanese.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/language.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/linkedin.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/paypal.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/redo.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/removeCapsule.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/spring.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/update.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/vimeo.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/weightBone.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/wind.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
2023/scripts/animation_tools/springmagic/icons/youtube.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
13
2023/scripts/animation_tools/springmagic/main.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from .import ui as ui
|
||||
|
||||
|
||||
def main(*args, **kwargs):
|
||||
widget = ui.SpringMagicWidget()
|
||||
widget.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import springmagic
|
||||
|
||||
with springmagic.app():
|
||||
springmagic.main()
|
||||
36
2023/scripts/animation_tools/springmagic/mkDevTools.py
Normal file
@@ -0,0 +1,36 @@
|
||||
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 list(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)
|
||||
28
2023/scripts/animation_tools/springmagic/springMagic.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import sys
|
||||
import os
|
||||
import inspect
|
||||
|
||||
|
||||
def main():
|
||||
# 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
|
||||
|
||||
# Recompile SpringMagic module if modification has been made
|
||||
|
||||
from . import main as app
|
||||
from . import mkDevTools as dev
|
||||
|
||||
import springmagic
|
||||
dev.refresh(springmagic)
|
||||
# Launch SpringMagic
|
||||
app.main()
|
||||
|
||||
# Remove SprinMagic path from PYTHON_PATH
|
||||
sys.path.remove(path_name)
|
||||
1664
2023/scripts/animation_tools/springmagic/springMagic.ui
Normal file
1821
2023/scripts/animation_tools/springmagic/springMagic_chn.ui
Normal file
1664
2023/scripts/animation_tools/springmagic/springMagic_eng.ui
Normal file
1760
2023/scripts/animation_tools/springmagic/springMagic_jpn.ui
Normal file
291
2023/scripts/animation_tools/springmagic/springMath.py
Normal file
@@ -0,0 +1,291 @@
|
||||
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 list(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
|
||||
460
2023/scripts/animation_tools/springmagic/ui.py
Normal file
@@ -0,0 +1,460 @@
|
||||
import os
|
||||
import time
|
||||
import inspect
|
||||
import webbrowser
|
||||
import urllib.request, urllib.error, urllib.parse
|
||||
import random
|
||||
import datetime
|
||||
import maya.mel as mel
|
||||
import pymel.core as pm
|
||||
|
||||
from . import 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
|
||||
kVimeoLink = r''
|
||||
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 Exception:
|
||||
pass
|
||||
|
||||
# title = pm.window(pm.loadUI(ui_file = ui_file))
|
||||
|
||||
self.ui = pm.loadUI(f=ui_file)
|
||||
|
||||
ui_widget_list = [
|
||||
'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',
|
||||
'vimeo_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.vimeo_button = pm.button(self.uiObjects['vimeo_pushButton'], edit=True, command=self.youtubeCmd)
|
||||
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)
|
||||
|
||||
self.misc_update_button = pm.button(self.uiObjects['miscUpdate_pushButton'], edit=True,
|
||||
command=self.updatePageCmd)
|
||||
|
||||
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\
|
||||
kwargs = {"edit": True}
|
||||
sWord = sWord if isinstance(sWord, str) else str(sWord, "utf8", errors="ignore")
|
||||
kwargs.setdefault("label", sWord)
|
||||
|
||||
pm.text(self.main_processLabel, **kwargs)
|
||||
|
||||
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 = "import springmagic\nspringmagic.main()"
|
||||
|
||||
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 youtubeCmd(self, *args):
|
||||
try:
|
||||
webbrowser.open(kYoutubeLink, new=2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def vimeoCmd(self, *args):
|
||||
# try:
|
||||
# webbrowser.open(kVimeoLink, new=2)
|
||||
# except Exception:
|
||||
# pass
|
||||
pass
|
||||
|
||||
def updatePageCmd(self, *args):
|
||||
try:
|
||||
webbrowser.open(kUpdateLink, new=2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def applyLanguage(self, lanId):
|
||||
lanDict = {1: '_chn', 2: '_eng', 3: '_jpn'}
|
||||
|
||||
if lanId in list(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 Exception:
|
||||
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.Request(site, headers=hdr)
|
||||
|
||||
try:
|
||||
page = urllib.request.urlopen(req, timeout=5)
|
||||
page_content = page.read()
|
||||
except Exception:
|
||||
print('checkUpdate failed')
|
||||
|
||||
if page_content:
|
||||
if isinstance(page_content, bytes):
|
||||
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 Exception:
|
||||
value = defaultValue
|
||||
|
||||
ui_object.setText(str(value))
|
||||
56
2023/scripts/animation_tools/springmagic/utility.py
Normal file
@@ -0,0 +1,56 @@
|
||||
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)
|
||||
13
2023/scripts/animation_tools/springmagic/安装说明.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
1. 解压缩 springmagic.zip 并复制 "springmagic" 目录至位于 Windows 用户路径下的 Maya 脚本目录
|
||||
例如
|
||||
"C:\Users\你的用户名\Documents\maya\scripts"
|
||||
2. 启动 Maya,在 Maya 里运行如下 Python 命令, 会出现工具界面
|
||||
import springmagic
|
||||
springmagic.main()
|
||||
3. 用工具界面右上方的创建快捷按钮功能,在书签栏创建一个快捷按钮,方便下次使用
|
||||
|
||||
|
||||
复制这个
|
||||
|
||||
import springmagic
|
||||
springmagic.main()
|
||||
@@ -24,17 +24,16 @@ from . import ModIt_CSS
|
||||
|
||||
|
||||
##_____________________________________________PATH
|
||||
MODIT_DIR = os.path.dirname(os.path.abspath(__file__)).replace('\\', '/')
|
||||
USERAPPDIR = mc.internalVar(userAppDir=True)
|
||||
VERSION = mc.about(v=True)
|
||||
# Get ModIt's actual location
|
||||
ModItDir = os.path.dirname(os.path.abspath(__file__))
|
||||
IconsPathThemeClassic = os.path.join(ModItDir, 'Icons/Theme_Classic/')
|
||||
ToolPath = os.path.join(ModItDir, 'Tools/')
|
||||
PreferencePath = os.path.join(ModItDir, 'Preferences/')
|
||||
PlugInsPath = os.path.join(USERAPPDIR, VERSION+'/plug-ins')
|
||||
PrefIcons = os.path.join(USERAPPDIR, VERSION+'/prefs/icons')
|
||||
UserScriptFolder = os.path.join(USERAPPDIR, VERSION+'/scripts')
|
||||
RessourcePath = os.path.join(ModItDir, 'Ressources/')
|
||||
IconsPathThemeClassic = os.path.join(MODIT_DIR, 'Icons/Theme_Classic/').replace('\\', '/')
|
||||
ToolPath = os.path.join(MODIT_DIR, 'Tools/').replace('\\', '/')
|
||||
PreferencePath = os.path.join(MODIT_DIR, 'Preferences/').replace('\\', '/')
|
||||
PlugInsPath = os.path.join(USERAPPDIR, VERSION+'/plug-ins').replace('\\', '/')
|
||||
PrefIcons = os.path.join(USERAPPDIR, VERSION+'/prefs/icons').replace('\\', '/')
|
||||
UserScriptFolder = os.path.join(USERAPPDIR, VERSION+'/scripts').replace('\\', '/')
|
||||
RessourcePath = os.path.join(MODIT_DIR, 'Ressources/').replace('\\', '/')
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"MULTISIZEVALUE": 81.0}
|
||||
{"MULTISIZEVALUE": 0.5}
|
||||
@@ -1 +1 @@
|
||||
{"TAB_OPEN": 0}
|
||||
{"TAB_OPEN": 1}
|
||||
@@ -1,2 +1,2 @@
|
||||
[General]
|
||||
windowGeometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\x1\x9a\0\0\0\xd6\0\0\x2\xef\0\0\x3\x89\0\0\x1\x9b\0\0\0\xf5\0\0\x2\xee\0\0\x3\x88\0\0\0\0\0\0\0\0\a\x80\0\0\x1\x9b\0\0\0\xf5\0\0\x2\xee\0\0\x3\x88)
|
||||
windowGeometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\x2\b\0\0\0\x9c\0\0\x3]\0\0\x2\xe6\0\0\x2\t\0\0\0\xbb\0\0\x3\\\0\0\x2\xe5\0\0\0\0\0\0\0\0\a\x80\0\0\x2\t\0\0\0\xbb\0\0\x3\\\0\0\x2\xe5)
|
||||
|
||||
@@ -9,5 +9,6 @@ General modeling utilities
|
||||
from .batchextrusion import show_batch_extrusion_ui
|
||||
|
||||
__all__ = [
|
||||
'show_batch_extrusion_ui'
|
||||
'show_batch_extrusion_ui',
|
||||
'creaseplus'
|
||||
]
|
||||
|
||||
1153
2023/scripts/modeling_tools/creaseplus/CreasePlus.mel
Normal file
BIN
2023/scripts/modeling_tools/creaseplus/CreasePlus_Doc.png
Normal file
|
After Width: | Height: | Size: 476 KiB |
452
2023/scripts/modeling_tools/creaseplus/CreasePlus_Extension1.mel
Normal file
@@ -0,0 +1,452 @@
|
||||
//AUTHOR : BAIDHIR HIDAIR © 2017.
|
||||
//don't modify , don't distribute.
|
||||
|
||||
//PP
|
||||
|
||||
if(!`polyCreaseCtx -ex "spWeightCtx"`){
|
||||
polyCreaseCtx -es 1 -r 1 "spWeightCtx";
|
||||
}
|
||||
///////////////////////////////////////////////////// LOCAL REALM
|
||||
|
||||
proc float spgetmaxf(float $valz[]){
|
||||
|
||||
int $tmp2 = $valz[0];
|
||||
|
||||
for($j = 0; $j < size($valz); $j++){
|
||||
|
||||
if($tmp2 < $valz[$j]){
|
||||
$tmp2 = $valz[$j];
|
||||
}
|
||||
}
|
||||
|
||||
return $tmp2;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////// GLOBAL REALM /////////////////////////////////////
|
||||
|
||||
global proc spSmartLvl(){
|
||||
|
||||
string $edges[] = `filterExpand -ex 1 -sm 32`;
|
||||
|
||||
|
||||
if(!`size $edges`){error;}
|
||||
|
||||
polyOptions -dce 0;
|
||||
|
||||
string $op[] = `listRelatives -p -f $edges`;
|
||||
|
||||
setAttr ($op[0] + ".osdSmoothTriangles") 1;
|
||||
|
||||
float $Wval[] = `polyCrease -q -v ($op[0] + ".e[*]")`;
|
||||
|
||||
float $higherW = `spgetmaxf $Wval`;
|
||||
|
||||
int $smtLvl = (int) `ceil $higherW`;
|
||||
|
||||
if($smtLvl < 1){$smtLvl = 1;}
|
||||
|
||||
setAttr ($op[0] + ".smoothLevel") ($smtLvl + 1);
|
||||
|
||||
}
|
||||
|
||||
global proc spFastCrease(){
|
||||
|
||||
if(size(`filterExpand -ex 1 -sm 12`)){
|
||||
polySelectConstraint -m 0 -dis; polySelectConstraint -m 3 -t 0x8000 -sm 1; polySelectConstraint -m 0 -dis;
|
||||
}
|
||||
|
||||
string $edges[] = `filterExpand -ex 1 -sm 32`;
|
||||
|
||||
if(!`size $edges`){ error "You must select edge components.\n";}
|
||||
|
||||
polyOptions -dce 0;
|
||||
|
||||
string $op[] = `listRelatives -p -f $edges`;
|
||||
|
||||
|
||||
setAttr ($op[0] + ".osdSmoothTriangles") 1;
|
||||
|
||||
setAttr ($op[0] + ".displaySmoothMesh") 2;
|
||||
|
||||
setToolTo "spWeightCtx";
|
||||
|
||||
scriptJob -cu 1 -ro 1 -e "PostToolChanged" "spSmartLvl";
|
||||
}
|
||||
|
||||
|
||||
global proc spNoCrease(){// Remove Crease / Weights
|
||||
|
||||
string $op[] = eval("listRelatives -p -f `eval(\"listRelatives -p -f `polyListComponentConversion -tv`\")`");
|
||||
|
||||
if(!`size $op`){ error "Select objects or components.\n"; }
|
||||
|
||||
string $opShape[] = `listRelatives -c -f $op`;
|
||||
|
||||
if(size(`filterExpand -ex 1 -sm 12`)){
|
||||
|
||||
polyOptions -dce 1;
|
||||
|
||||
for($i in $op){
|
||||
polyCrease -op 2 $i;
|
||||
setAttr ($opShape[0] + ".displaySmoothMesh") 2;
|
||||
}
|
||||
|
||||
select -r $op;
|
||||
}else{
|
||||
|
||||
polyOptions -dce 0;
|
||||
|
||||
polyCrease -op 1 `ls -sl -fl`;
|
||||
|
||||
setAttr ($opShape[0] + ".displaySmoothMesh") 2;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
global proc spSmoothOs(){ // Final Smooth
|
||||
|
||||
string $op[] = eval("listRelatives -p -f `eval(\"listRelatives -p -f `polyListComponentConversion -tv`\")`");
|
||||
|
||||
if(!`size $op`){error "You must select Object(s).\n";}
|
||||
|
||||
string $opShape[] = `listRelatives -c -f $op`;
|
||||
|
||||
polyOptions -dce 0; LowQualityDisplay;
|
||||
|
||||
for($i in $op){
|
||||
|
||||
|
||||
float $Wval[] = `polyCrease -q -v ($i + ".e[*]")`;
|
||||
|
||||
float $higherW = `spgetmaxf $Wval`;
|
||||
|
||||
int $smtLvl = (int) `ceil $higherW`;
|
||||
if($smtLvl < 1 || $smtLvl > 5){$smtLvl = 1;}
|
||||
|
||||
|
||||
string $Node[0] =`polySmooth $i`;
|
||||
|
||||
|
||||
string $ud[] = `listAttr -ud $i`;
|
||||
|
||||
for($j in $ud){
|
||||
deleteAttr -at $j $i;
|
||||
}
|
||||
|
||||
|
||||
addAttr -k 1 -ln "smoothLevel" -at "short" -dv ($smtLvl+1) -hnv 1 -min 0 -max 5 $i;
|
||||
|
||||
connectAttr ($i + ".smoothLevel") ($Node[0] + ".divisions");
|
||||
|
||||
}
|
||||
|
||||
select -r $op;
|
||||
|
||||
}
|
||||
|
||||
global proc smoothSg(){ // Smooth Based on Smoothing Groups
|
||||
|
||||
string $op[] = eval("listRelatives -p -f `eval(\"listRelatives -p -f `polyListComponentConversion -tv`\")`");
|
||||
|
||||
if(!`size $op`){error "You must select Objects.\n";}
|
||||
|
||||
|
||||
LowQualityDisplay;
|
||||
|
||||
|
||||
for($i in $op){
|
||||
|
||||
|
||||
polySelectConstraint -m 0 -dis; polySelectConstraint -m 3 -t 0x8000 -sm 1; polySelectConstraint -m 0 -dis;
|
||||
|
||||
if(size(`ls -sl -fl`)){polyCrease -op 2 $i; polyCrease -v 5.0 `ls -sl -fl`; }
|
||||
|
||||
|
||||
string $Node1[] = `polySmooth $i`;
|
||||
//basediv
|
||||
|
||||
polyCrease -op 2 $i;
|
||||
|
||||
string $ud[] = `listAttr -ud $i`;
|
||||
|
||||
for($j in $ud){
|
||||
deleteAttr -at $j $i;
|
||||
}
|
||||
|
||||
|
||||
addAttr -k 1 -ln "baseDiv" -at "short" -dv 2 -hnv 1 -min 0 -max 5 $i;
|
||||
|
||||
connectAttr ($i + ".baseDiv") ($Node1[0] + ".divisions");
|
||||
|
||||
string $Node2[] =`polySmooth $i`;
|
||||
//smoothdiv
|
||||
|
||||
|
||||
addAttr -k 1 -ln "smoothDiv" -at "short" -dv 1 -hnv 1 -min 0 -max 5 $i;
|
||||
|
||||
connectAttr ($i + ".smoothDiv") ($Node2[0] + ".divisions");
|
||||
}
|
||||
|
||||
select -r $op;
|
||||
}
|
||||
|
||||
global proc spCreasePreset(int $power){// Prefab of the creasing command
|
||||
|
||||
string $op[] = eval("listRelatives -p -f `eval(\"listRelatives -p -f `polyListComponentConversion -tv`\")`");
|
||||
|
||||
if(!`size $op`){error "Select Objects or components.\n"; }
|
||||
|
||||
polyOptions -dce 0;
|
||||
|
||||
|
||||
if(size(`filterExpand -ex 1 -sm 12`)){ // if objects are selected instead
|
||||
|
||||
for($i in $op){
|
||||
|
||||
polySelectConstraint -m 0 -dis; polySelectConstraint -m 3 -t 0x8000 -sm 1; polySelectConstraint -m 0 -dis;
|
||||
|
||||
if(!size(`ls -sl -fl`)){continue;}
|
||||
|
||||
|
||||
if($power == 1){
|
||||
polyCrease -op 2 $i;
|
||||
polyCrease -v 2.0;
|
||||
}else if($power == 2){
|
||||
polyCrease -op 2 $i;
|
||||
polyCrease -v 3.3;
|
||||
}else if($power == 3){
|
||||
polyCrease -op 2 $i;
|
||||
polyCrease -v 4.0;
|
||||
}else{
|
||||
error ("argument " + $power + " has no behavior, use 1, 2, 3 instead.\n");
|
||||
}
|
||||
|
||||
string $shape[0] = `listRelatives -c -f $i`;
|
||||
|
||||
setAttr ($shape[0] + ".osdSmoothTriangles") 1;
|
||||
|
||||
float $Wval[] = `polyCrease -q -v ($i + ".e[*]")`;
|
||||
|
||||
float $higherW = `spgetmaxf $Wval`;
|
||||
|
||||
int $smtLvl = (int) `ceil $higherW`;
|
||||
|
||||
if($smtLvl < 1){$smtLvl = 1;}
|
||||
|
||||
setAttr ($shape[0] + ".smoothLevel") ($smtLvl + 1);
|
||||
}
|
||||
select -r $op; HighQualityDisplay;
|
||||
}else{
|
||||
//components
|
||||
|
||||
if($power == 1){
|
||||
polyCrease -v 2.0;
|
||||
}else if($power == 2){
|
||||
polyCrease -v 3.0;
|
||||
}else if($power == 3){
|
||||
polyCrease -v 4.0;
|
||||
}else{
|
||||
error ("argument " + $power + " has no behavior, use 1, 2, 3 instead.\n");
|
||||
}
|
||||
|
||||
string $shape[0] = `listRelatives -c -f $op[0]`;
|
||||
|
||||
setAttr ($shape[0] + ".osdSmoothTriangles") 1;
|
||||
|
||||
float $Wval[] = `polyCrease -q -v ($op[0] + ".e[*]")`;
|
||||
|
||||
float $higherW = `spgetmaxf $Wval`;
|
||||
|
||||
int $smtLvl = (int) `ceil $higherW`;
|
||||
|
||||
if($smtLvl < 1){$smtLvl = 1;}
|
||||
|
||||
setAttr ($shape[0] + ".smoothLevel") ($smtLvl + 1);
|
||||
|
||||
|
||||
hilite; HighQualityDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
global proc spLevel(int $op){ //Utility for Levels of Weight and SubD
|
||||
|
||||
string $ops[] = eval("listRelatives -p -f `polyListComponentConversion -tv`");
|
||||
|
||||
if(!`size $ops`){error;}
|
||||
|
||||
string $edges[] = `filterExpand -ex 1 -sm 32`;
|
||||
|
||||
int $itr;
|
||||
|
||||
polyOptions -dce 0;
|
||||
|
||||
if($op == 1){//Lower Rez
|
||||
|
||||
for($i in $ops){
|
||||
|
||||
int $lvl = `getAttr ($i + ".smoothLevel")`;
|
||||
|
||||
setAttr ($i + ".smoothLevel ") ($lvl-1);
|
||||
|
||||
}
|
||||
|
||||
select -r $ops; HighQualityDisplay;
|
||||
|
||||
}else if($op == 2){//Higher Rez
|
||||
|
||||
for($i in $ops){
|
||||
|
||||
int $lvl = `getAttr ($i + ".smoothLevel")`;
|
||||
|
||||
setAttr ($i + ".smoothLevel ") ($lvl+1);
|
||||
}
|
||||
|
||||
select -r $ops; HighQualityDisplay;
|
||||
|
||||
}else if($op == 3){ //Lower Weight
|
||||
|
||||
for($i in $ops){
|
||||
|
||||
setAttr ($i + ".osdSmoothTriangles") 1;
|
||||
|
||||
float $Wval[] = `polyCrease -q -v ($i + ".e[*]")`;
|
||||
|
||||
float $higherW = `spgetmaxf $Wval`;
|
||||
|
||||
int $smtLvl = (int) `ceil $higherW`;
|
||||
|
||||
if($smtLvl < 1){$smtLvl = 1;}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
for($j in $edges){
|
||||
polyCrease -v ($Wval[$itr] - 1) $j;
|
||||
$itr++;
|
||||
}
|
||||
|
||||
setAttr ($i + ".smoothLevel") ($smtLvl);
|
||||
|
||||
}
|
||||
|
||||
select -r $ops; HighQualityDisplay;
|
||||
|
||||
}else if($op == 4){ // Higher Weight
|
||||
|
||||
for($i in $ops){
|
||||
|
||||
setAttr ($i + ".osdSmoothTriangles") 1;
|
||||
|
||||
float $Wval[] = `polyCrease -q -v ($i + ".e[*]")`;
|
||||
|
||||
float $higherW = `spgetmaxf $Wval`;
|
||||
|
||||
int $smtLvl = (int) `ceil $higherW`;
|
||||
|
||||
if($smtLvl < 1){$smtLvl = 1;}
|
||||
|
||||
|
||||
|
||||
for($j in $edges){
|
||||
polyCrease -v ($Wval[$itr] + 1) $j;
|
||||
$itr++;
|
||||
}
|
||||
|
||||
setAttr ($i + ".smoothLevel") ($smtLvl + 2);
|
||||
}
|
||||
|
||||
select -r $ops; HighQualityDisplay;
|
||||
|
||||
}else{
|
||||
error ($op + " is not an option, Try with 1, 2, 3 , 4 instead.\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
global proc spPhysicalCrease(){// physical Crease
|
||||
|
||||
global int $cp_maya_v1;
|
||||
|
||||
string $op[] = eval("listRelatives -p -f `eval(\"listRelatives -p -f `polyListComponentConversion -tv`\")`");
|
||||
|
||||
string $ControlNode[];
|
||||
|
||||
if(`currentCtx` == "cpCtx"){
|
||||
if(`cpIsBvlOp $op[0]`){
|
||||
select -r $op[0]; cpAttrSwitch; return;
|
||||
}
|
||||
}
|
||||
|
||||
if(size(`filterExpand -ex 1 -sm 12`)){
|
||||
|
||||
for($i in $op){
|
||||
|
||||
|
||||
select -r $i;
|
||||
|
||||
polySelectConstraint -m 0 -dis; polySelectConstraint -m 3 -t 0x8000 -sm 1; polySelectConstraint -m 0 -dis;
|
||||
|
||||
if($cp_maya_v1 > 2016){
|
||||
$ControlNode = `polyBevel3 -af 1 -oaf 0 -c 0 -sg 1 -sn 1 -sa 180 -o 0`;
|
||||
|
||||
}else{
|
||||
$ControlNode = `polyBevel3 -af 1 -oaf 0 -sg 1 -fn 1 -sa 180 -o 0`;
|
||||
|
||||
}
|
||||
|
||||
|
||||
string $ud[] = `listAttr -ud $i`;
|
||||
|
||||
for($j in $ud){
|
||||
deleteAttr -at $j $i;
|
||||
}
|
||||
|
||||
addAttr -ln "hOffset" -k 1 -at "doubleLinear" -hnv 1 -min 0 -dv 0.1 $i;
|
||||
|
||||
connectAttr ($i + ".hOffset") ($ControlNode[0] + ".offset");
|
||||
|
||||
addAttr -ln "hDivisions" -k 1 -at "long" -hnv 1 -min 0 -dv 1 $i;
|
||||
|
||||
connectAttr ($i + ".hDivisions") ($ControlNode[0] + ".segments");
|
||||
|
||||
}
|
||||
|
||||
select -r $op; cpAttrSwitch;
|
||||
|
||||
}else{
|
||||
if($cp_maya_v1 > 2016){
|
||||
$ControlNode = `polyBevel3 -af 1 -oaf 0 -c 0 -sg 1 -sn 1 -sa 180 -o 0`;
|
||||
|
||||
}else{
|
||||
$ControlNode = `polyBevel3 -af 1 -oaf 0 -sg 1 -fn 1 -sa 180 -o 0`;
|
||||
|
||||
}
|
||||
|
||||
string $ud[] = `listAttr -ud $op[0]`;
|
||||
|
||||
for($j in $ud){
|
||||
deleteAttr -at $j $op[0];
|
||||
}
|
||||
|
||||
addAttr -ln "hOffset" -k 1 -at "doubleLinear" -hnv 1 -min 0 -dv 0.1 $op[0];
|
||||
|
||||
connectAttr ($op[0] + ".hOffset") ($ControlNode[0] + ".offset");
|
||||
|
||||
addAttr -ln "hDivisions" -k 1 -at "long" -hnv 1 -min 0 -dv 1 $op[0];
|
||||
|
||||
connectAttr ($op[0] + ".hDivisions") ($ControlNode[0] + ".segments");
|
||||
|
||||
select -r $op; cpAttrSwitch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
global proc spShowCreaseEd(){
|
||||
python "from maya.app.general import creaseSetEditor; creaseSetEditor.showCreaseSetEditor()";
|
||||
}
|
||||
31
2023/scripts/modeling_tools/creaseplus/__init__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
CreasePlus MEL Tool Wrapper
|
||||
Provides Python interface to launch the CreasePlus MEL tool
|
||||
"""
|
||||
|
||||
import maya.cmds as cmds
|
||||
import maya.mel as mel
|
||||
import os
|
||||
|
||||
|
||||
def start():
|
||||
"""Launch CreasePlus MEL tool"""
|
||||
# Get the directory containing this script
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
icon_dir = os.path.join(script_dir, 'icons').replace('\\', '/')
|
||||
|
||||
# Set global MEL variable for icon path
|
||||
mel.eval(f'global string $cp_icon_path = "{icon_dir}/"')
|
||||
|
||||
# Source the MEL script
|
||||
mel_script = os.path.join(script_dir, 'CreasePlus.mel').replace('\\', '/')
|
||||
|
||||
try:
|
||||
mel.eval(f'source "{mel_script}"')
|
||||
mel.eval('cpUi')
|
||||
print(f"CreasePlus: UI launched successfully")
|
||||
except Exception as e:
|
||||
print(f"CreasePlus: Error launching UI: {e}")
|
||||
46
2023/scripts/modeling_tools/creaseplus/cp_(read)commands.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
cpAttrSwitch;
|
||||
//Use to toggle between attributs, select object first.
|
||||
cpDisplayBool;
|
||||
//Perform boolean with operands display
|
||||
cpKeepBool;
|
||||
//perform boolean preserving the operands
|
||||
cpHbevel;
|
||||
//perform hBevel
|
||||
cpMirror;
|
||||
//Mirrors the mesh
|
||||
cpPanelBool;
|
||||
//Performs panel boolean : creates panel cutting/carving with operands
|
||||
cpMeshSlicer;
|
||||
//slice mesh with curve, select curve and mesh first.
|
||||
cpTglBoolv;
|
||||
//Toggle visibility of the boolean objects
|
||||
cpHedgeSel;
|
||||
//Select Hard edges of the selected objects
|
||||
cpShapeShifter;
|
||||
//triggers ShapeShifter if found on the disk
|
||||
cpGoz;
|
||||
//Send meshes to Zbrush, with nGon cleanup
|
||||
cpQsmooth;
|
||||
//Apply a 30 degree smoothing to selected objects are components
|
||||
cpHardDisplay;
|
||||
//Toggles display of hard edges in realtime
|
||||
cpmakeUV;
|
||||
//make UV based on hard edges
|
||||
cpCurveCham;
|
||||
//Perform curve bevel (must draw two curve point around a corner before use)
|
||||
cpTransferBevel;
|
||||
//transfer bevel settings from one object to the others
|
||||
cpCleanAttrs;
|
||||
//kills custom attributes of the objects
|
||||
cpBakThatNod;
|
||||
//Bake selected Node from the channel Box(select object then node)
|
||||
cpInstanceBool;
|
||||
// perform Instance bool
|
||||
|
||||
cpAttachCurve;
|
||||
cpCurveBool;
|
||||
cpCloseCurve;
|
||||
cpCurveMultiply;
|
||||
|
||||
cpUi;
|
||||
//Calls Crease+ 's UI
|
||||
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_bevel.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_bool.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_curve_attach.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_curve_bevel.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_curve_bool.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_curve_close.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_curve_draw.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_eye.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_goz.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_hard_display.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_keep_bool.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_mesh_slicer.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_mirror.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_panelbool.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_quicksmooth.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_sel_hard.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/cp_ss.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/sp_crease.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/sp_nocrease.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/sp_smooth.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2023/scripts/modeling_tools/creaseplus/icons/sp_weight_tool.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -21,6 +21,30 @@ import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
# Set MAYA_MODULE_PATH environment variable for external tools
|
||||
try:
|
||||
_current_file = os.path.abspath(__file__)
|
||||
_script_dir = os.path.dirname(_current_file)
|
||||
_project_root = os.path.dirname(os.path.dirname(_script_dir))
|
||||
_modules_dir = os.path.join(_project_root, 'modules')
|
||||
|
||||
if os.path.exists(_modules_dir):
|
||||
_modules_dir_normalized = os.path.normpath(_modules_dir)
|
||||
_current_module_path = os.environ.get('MAYA_MODULE_PATH', '')
|
||||
|
||||
# Check if already in path
|
||||
_paths = [p.strip() for p in _current_module_path.split(os.pathsep) if p.strip()]
|
||||
_normalized_paths = [os.path.normpath(p) for p in _paths]
|
||||
|
||||
if _modules_dir_normalized not in _normalized_paths:
|
||||
if _current_module_path:
|
||||
os.environ['MAYA_MODULE_PATH'] = f"{_modules_dir_normalized}{os.pathsep}{_current_module_path}"
|
||||
else:
|
||||
os.environ['MAYA_MODULE_PATH'] = _modules_dir_normalized
|
||||
print(f"[Tool] MAYA_MODULE_PATH set to: {_modules_dir_normalized}")
|
||||
except Exception as e:
|
||||
print(f"[Tool] Warning: Could not set MAYA_MODULE_PATH: {e}")
|
||||
|
||||
# Silently try to open default commandPort to avoid startup error if it's already in use
|
||||
try:
|
||||
mel.eval('catchQuiet("commandPort -securityWarning -name \\"commandportDefault\\";");')
|
||||
@@ -51,12 +75,14 @@ TOOL_CONFIG = {
|
||||
{'name': 'gs_curvetools', 'path': 'modeling_tools/gs_curvetools/icons'},
|
||||
{'name': 'gs_toolbox', 'path': 'modeling_tools/gs_toolbox/icons'},
|
||||
{'name': 'modit', 'path': 'modeling_tools/ModIt/Icons/Theme_Classic'},
|
||||
{'name': 'creaseplus', 'path': 'modeling_tools/creaseplus/icons'},
|
||||
{'name': 'ngskintools2', 'path': 'rigging_tools/ngskintools2/ui/images'},
|
||||
{'name': 'mgpicker', 'path': 'animation_tools/mgpicker/MGPicker_Program/Icons'},
|
||||
{'name': 'atools', 'path': 'animation_tools/atools/img'},
|
||||
{'name': 'dwpicker', 'path': 'animation_tools/dwpicker/icons'},
|
||||
{'name': 'studiolibrary', 'path': 'animation_tools/studiolibrary/studiolibrary/resource/icons'},
|
||||
{'name': 'studiolibrary_maya', 'path': 'animation_tools/studiolibrary/studiolibrarymaya/icons'},
|
||||
{'name': 'springmagic', 'path': 'animation_tools/springmagic/icons'},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -350,20 +376,20 @@ def load_tool_plugins():
|
||||
|
||||
_tool_log(f"[Tool] Processing plugin: {plugin_name}")
|
||||
|
||||
# 首先检查配置的 path 参数
|
||||
# First check the configured path parameter
|
||||
plugin_path = None
|
||||
if plugin_rel_path:
|
||||
# 构建完整路径:script_dir/path/plugin_name
|
||||
# Build the full path: script_dir/path/plugin_name
|
||||
full_path = _norm_path(os.path.join(script_dir, plugin_rel_path, plugin_name))
|
||||
if os.path.exists(full_path):
|
||||
plugin_path = full_path
|
||||
_tool_log(f"[Tool] Found plugin at configured path: {plugin_path}")
|
||||
|
||||
# 如果配置路径没找到,搜索环境变量路径
|
||||
# If the configured path is not found, search the environment variable path.
|
||||
if not plugin_path:
|
||||
plugin_path = _find_file_in_paths(plugin_name, os.environ.get(env_var, '').split(os.pathsep))
|
||||
|
||||
# 最后尝试脚本目录和父级 plug-ins 文件夹
|
||||
# Finally, try the script directory and the parent plug-ins folder.
|
||||
if not plugin_path:
|
||||
search_dirs = [
|
||||
script_dir,
|
||||
@@ -452,10 +478,10 @@ def load_tool_scripts():
|
||||
def load_project_modules():
|
||||
"""从 modules 目录加载 .mod 文件定义的插件"""
|
||||
try:
|
||||
# 获取当前 Maya 版本
|
||||
# Get current Maya version
|
||||
maya_version = int(cmds.about(version=True).split()[0])
|
||||
|
||||
# 获取项目根目录
|
||||
# Get project root directory
|
||||
script_dir = _get_script_dir()
|
||||
project_root = os.path.dirname(os.path.dirname(script_dir))
|
||||
modules_dir = _norm_path(os.path.join(project_root, "modules"))
|
||||
@@ -464,7 +490,7 @@ def load_project_modules():
|
||||
_tool_log(f"[Tool] Modules directory not found: {modules_dir}")
|
||||
return
|
||||
|
||||
# 查找所有 .mod 文件
|
||||
# Find all .mod files
|
||||
mod_files = [f for f in os.listdir(modules_dir) if f.endswith('.mod')]
|
||||
|
||||
if not mod_files:
|
||||
@@ -473,7 +499,7 @@ def load_project_modules():
|
||||
|
||||
_tool_print(f"[Tool] Found {len(mod_files)} module(s): {', '.join(mod_files)}")
|
||||
|
||||
# 解析 .mod 文件并加载插件
|
||||
# Parse .mod files and load plugins.
|
||||
plugins_loaded = 0
|
||||
for mod_file in mod_files:
|
||||
mod_path = os.path.join(modules_dir, mod_file)
|
||||
@@ -486,36 +512,48 @@ def load_project_modules():
|
||||
with open(mod_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
|
||||
# 跳过注释和空行
|
||||
|
||||
# Skip comments and empty lines
|
||||
if not line or line.startswith('#') or line.startswith('//'):
|
||||
continue
|
||||
|
||||
# 解析模块定义行(以 + 开头)
|
||||
|
||||
# Parse module definition lines (starting with +)
|
||||
if line.startswith('+'):
|
||||
parts = line.split()
|
||||
in_correct_version = False
|
||||
current_module_root = None
|
||||
|
||||
if len(parts) >= 5:
|
||||
# 检查版本匹配
|
||||
|
||||
if len(parts) >= 4:
|
||||
# Check for version restrictions.
|
||||
has_version_requirement = False
|
||||
for part in parts:
|
||||
if part.startswith('MAYAVERSION:'):
|
||||
has_version_requirement = True
|
||||
required_version = int(part.split(':')[1])
|
||||
if required_version == maya_version:
|
||||
in_correct_version = True
|
||||
# 获取模块根路径(最后一个参数)
|
||||
# Get the module root path (last parameter)
|
||||
current_module_root = parts[-1]
|
||||
if current_module_root.startswith('..'):
|
||||
current_module_root = _norm_path(os.path.join(modules_dir, current_module_root))
|
||||
_tool_log(f"[Tool] Version matched for Maya {maya_version}: {current_module_root}")
|
||||
break
|
||||
|
||||
# 只处理匹配版本的 MAYA_PLUG_IN_PATH
|
||||
|
||||
# If there are no version restrictions, it works for all versions.
|
||||
if not has_version_requirement:
|
||||
in_correct_version = True
|
||||
current_module_root = parts[-1]
|
||||
if current_module_root.startswith('..'):
|
||||
current_module_root = _norm_path(os.path.join(modules_dir, current_module_root))
|
||||
_tool_log(f"[Tool] Module without version restriction loaded: {current_module_root}")
|
||||
|
||||
# Only process MAYA_PLUG_IN_PATH that matches the version
|
||||
elif line.startswith('MAYA_PLUG_IN_PATH') and in_correct_version and current_module_root:
|
||||
if '+:=' in line or '+=' in line:
|
||||
plugin_path_relative = line.split('=')[-1].strip()
|
||||
|
||||
plugins_dir = _norm_path(os.path.join(current_module_root, plugin_path_relative))
|
||||
|
||||
|
||||
if os.path.exists(plugins_dir):
|
||||
_tool_log(f"[Tool] Checking plugin dir: {plugins_dir}")
|
||||
@@ -525,7 +563,7 @@ def load_project_modules():
|
||||
plugin_name = os.path.splitext(plugin_file)[0]
|
||||
plugin_full_path = os.path.join(plugins_dir, plugin_file)
|
||||
|
||||
# 检查是否已加载
|
||||
# Check if it has been loaded
|
||||
if plugin_name in _LOADED_PLUGINS:
|
||||
continue
|
||||
|
||||
@@ -598,11 +636,11 @@ def initialize_tool():
|
||||
if not _check_maya_version():
|
||||
print("[Tool] Warning: Running on unsupported Maya version")
|
||||
|
||||
# 先设置图标路径,这样 shelf 加载时就能找到图标
|
||||
# Set icon paths first, so shelves can find icons
|
||||
setup_icon_paths()
|
||||
|
||||
# Load project modules
|
||||
load_project_modules()
|
||||
load_project_modules() # Automatic loading using Maya standard module system
|
||||
|
||||
# Setup command port
|
||||
setup_command_port()
|
||||
|
||||
@@ -285,5 +285,40 @@ global proc shelf_Nexus_Animation () {
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
;
|
||||
shelfButton
|
||||
-enableCommandRepeat 1
|
||||
-flexibleWidthType 3
|
||||
-flexibleWidthValue 32
|
||||
-enable 1
|
||||
-width 35
|
||||
-height 34
|
||||
-manage 1
|
||||
-visible 1
|
||||
-preventOverride 0
|
||||
-annotation "Spring Magic - Calculate bone chain animation with spring physics"
|
||||
-enableBackground 0
|
||||
-backgroundColor 0 0 0
|
||||
-highlightColor 0.321569 0.521569 0.65098
|
||||
-align "center"
|
||||
-label "SpringMagic"
|
||||
-labelOffset 0
|
||||
-rotation 0
|
||||
-flipX 0
|
||||
-flipY 0
|
||||
-useAlpha 1
|
||||
-font "plainLabelFont"
|
||||
-imageOverlayLabel "Spring"
|
||||
-overlayLabelColor 0.8 0.8 0.8
|
||||
-overlayLabelBackColor 0 0 0 0.5
|
||||
-image "springmagic.png"
|
||||
-image1 "springmagic.png"
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "import springmagic\nspringmagic.main()"
|
||||
-sourceType "python"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ global proc shelf_Nexus_Modeling () {
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "import gs_curvetools.main as gs_curvetools_main; from importlib import reload; reload(gs_curvetools_main); gs_curvetools_main.reset(); del gs_curvetools_main"
|
||||
-command "import gs_curvetools.core.utils as gs_curvetools_utils; gs_curvetools_utils.reset_ui(); del gs_curvetools_utils"
|
||||
-sourceType "python"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
@@ -209,7 +209,7 @@ global proc shelf_Nexus_Modeling () {
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "import gs_curvetools.main as gs_curvetools_main; from importlib import reload; reload(gs_curvetools_main); gs_curvetools_main.stop(); del gs_curvetools_main"
|
||||
-command "import gs_curvetools.core.utils as gs_curvetools_utils; gs_curvetools_utils.stop_ui(); del gs_curvetools_utils"
|
||||
-sourceType "python"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
@@ -294,101 +294,31 @@ global proc shelf_Nexus_Modeling () {
|
||||
-manage 1
|
||||
-visible 1
|
||||
-preventOverride 0
|
||||
-annotation "Instant Tie - Easy way to create tie and rope"
|
||||
-annotation "CreasePlus - Advanced hard surface modeling tool"
|
||||
-enableBackground 0
|
||||
-backgroundColor 0 0 0
|
||||
-highlightColor 0.321569 0.521569 0.65098
|
||||
-align "center"
|
||||
-label "InstantTie"
|
||||
-label "CreasePlus"
|
||||
-labelOffset 0
|
||||
-rotation 0
|
||||
-flipX 0
|
||||
-flipY 0
|
||||
-useAlpha 1
|
||||
-font "plainLabelFont"
|
||||
-imageOverlayLabel "Tie"
|
||||
-imageOverlayLabel "Crease+"
|
||||
-overlayLabelColor 0.8 0.8 0.8
|
||||
-overlayLabelBackColor 0 0 0 0.5
|
||||
-image "instant_tie.png"
|
||||
-image1 "instant_tie.png"
|
||||
-image "creaseplus.png"
|
||||
-image1 "creaseplus.png"
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "from modeling_tools import instant_tie\ninstant_tie.InstantTie()"
|
||||
-command "from modeling_tools import creaseplus\ncreaseplus.start()"
|
||||
-sourceType "python"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
;
|
||||
shelfButton
|
||||
-enableCommandRepeat 1
|
||||
-flexibleWidthType 3
|
||||
-flexibleWidthValue 32
|
||||
-enable 1
|
||||
-width 35
|
||||
-height 34
|
||||
-manage 1
|
||||
-visible 1
|
||||
-preventOverride 0
|
||||
-annotation "Corner Killer - Fixing corner topology"
|
||||
-enableBackground 0
|
||||
-backgroundColor 0 0 0
|
||||
-highlightColor 0.321569 0.521569 0.65098
|
||||
-align "center"
|
||||
-label "CornerKiller"
|
||||
-labelOffset 0
|
||||
-rotation 0
|
||||
-flipX 0
|
||||
-flipY 0
|
||||
-useAlpha 1
|
||||
-font "plainLabelFont"
|
||||
-imageOverlayLabel "CK"
|
||||
-overlayLabelColor 0.8 0.8 0.8
|
||||
-overlayLabelBackColor 0 0 0 0.5
|
||||
-image "cornerkiller.png"
|
||||
-image1 "cornerkiller.png"
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "from modeling_tools import cornerkiller\ncornerkiller.cornerKiller()"
|
||||
-sourceType "python"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
;
|
||||
shelfButton
|
||||
-enableCommandRepeat 1
|
||||
-flexibleWidthType 3
|
||||
-flexibleWidthValue 32
|
||||
-enable 1
|
||||
-width 35
|
||||
-height 34
|
||||
-manage 1
|
||||
-visible 1
|
||||
-preventOverride 0
|
||||
-annotation "Flatten Model By UV - Select a mesh first, then click to flatten by UV"
|
||||
-enableBackground 0
|
||||
-backgroundColor 0 0 0
|
||||
-highlightColor 0.321569 0.521569 0.65098
|
||||
-align "center"
|
||||
-label "FlattenUV"
|
||||
-labelOffset 0
|
||||
-rotation 0
|
||||
-flipX 0
|
||||
-flipY 0
|
||||
-useAlpha 1
|
||||
-font "plainLabelFont"
|
||||
-imageOverlayLabel "Flat"
|
||||
-overlayLabelColor 0.8 0.8 0.8
|
||||
-overlayLabelBackColor 0 0 0 0.5
|
||||
-image "flattenmodelbyuv.png"
|
||||
-image1 "flattenmodelbyuv.png"
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "source \"modeling_tools/flattenmodelbyuv.mel\";\nflattenModelbyUV();"
|
||||
-sourceType "mel"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
;
|
||||
shelfButton
|
||||
-enableCommandRepeat 1
|
||||
-flexibleWidthType 3
|
||||
@@ -459,6 +389,111 @@ global proc shelf_Nexus_Modeling () {
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
;
|
||||
shelfButton
|
||||
-enableCommandRepeat 1
|
||||
-flexibleWidthType 3
|
||||
-flexibleWidthValue 32
|
||||
-enable 1
|
||||
-width 35
|
||||
-height 34
|
||||
-manage 1
|
||||
-visible 1
|
||||
-preventOverride 0
|
||||
-annotation "Flatten Model By UV - Select a mesh first, then click to flatten by UV"
|
||||
-enableBackground 0
|
||||
-backgroundColor 0 0 0
|
||||
-highlightColor 0.321569 0.521569 0.65098
|
||||
-align "center"
|
||||
-label "FlattenUV"
|
||||
-labelOffset 0
|
||||
-rotation 0
|
||||
-flipX 0
|
||||
-flipY 0
|
||||
-useAlpha 1
|
||||
-font "plainLabelFont"
|
||||
-imageOverlayLabel "Flat"
|
||||
-overlayLabelColor 0.8 0.8 0.8
|
||||
-overlayLabelBackColor 0 0 0 0.5
|
||||
-image "flattenmodelbyuv.png"
|
||||
-image1 "flattenmodelbyuv.png"
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "source \"modeling_tools/flattenmodelbyuv.mel\";\nflattenModelbyUV();"
|
||||
-sourceType "mel"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
;
|
||||
shelfButton
|
||||
-enableCommandRepeat 1
|
||||
-flexibleWidthType 3
|
||||
-flexibleWidthValue 32
|
||||
-enable 1
|
||||
-width 35
|
||||
-height 34
|
||||
-manage 1
|
||||
-visible 1
|
||||
-preventOverride 0
|
||||
-annotation "Instant Tie - Easy way to create tie and rope"
|
||||
-enableBackground 0
|
||||
-backgroundColor 0 0 0
|
||||
-highlightColor 0.321569 0.521569 0.65098
|
||||
-align "center"
|
||||
-label "InstantTie"
|
||||
-labelOffset 0
|
||||
-rotation 0
|
||||
-flipX 0
|
||||
-flipY 0
|
||||
-useAlpha 1
|
||||
-font "plainLabelFont"
|
||||
-imageOverlayLabel "Tie"
|
||||
-overlayLabelColor 0.8 0.8 0.8
|
||||
-overlayLabelBackColor 0 0 0 0.5
|
||||
-image "instant_tie.png"
|
||||
-image1 "instant_tie.png"
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "from modeling_tools import instant_tie\ninstant_tie.InstantTie()"
|
||||
-sourceType "python"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
;
|
||||
shelfButton
|
||||
-enableCommandRepeat 1
|
||||
-flexibleWidthType 3
|
||||
-flexibleWidthValue 32
|
||||
-enable 1
|
||||
-width 35
|
||||
-height 34
|
||||
-manage 1
|
||||
-visible 1
|
||||
-preventOverride 0
|
||||
-annotation "Corner Killer - Fixing corner topology"
|
||||
-enableBackground 0
|
||||
-backgroundColor 0 0 0
|
||||
-highlightColor 0.321569 0.521569 0.65098
|
||||
-align "center"
|
||||
-label "CornerKiller"
|
||||
-labelOffset 0
|
||||
-rotation 0
|
||||
-flipX 0
|
||||
-flipY 0
|
||||
-useAlpha 1
|
||||
-font "plainLabelFont"
|
||||
-imageOverlayLabel "CK"
|
||||
-overlayLabelColor 0.8 0.8 0.8
|
||||
-overlayLabelBackColor 0 0 0 0.5
|
||||
-image "cornerkiller.png"
|
||||
-image1 "cornerkiller.png"
|
||||
-style "iconOnly"
|
||||
-marginWidth 0
|
||||
-marginHeight 1
|
||||
-command "from modeling_tools import cornerkiller\ncornerkiller.cornerKiller()"
|
||||
-sourceType "python"
|
||||
-commandRepeatable 1
|
||||
-flat 1
|
||||
;
|
||||
shelfButton
|
||||
-enableCommandRepeat 1
|
||||
-flexibleWidthType 3
|
||||
|
||||
BIN
2024/icons/QuadRemesher.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
2024/icons/creaseplus.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
2024/icons/springmagic.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
2
2024/scripts/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -0,0 +1,8 @@
|
||||
1. Unzip all files
|
||||
|
||||
2. copy "springmagic" folder into Maya scripts path, i.e.
|
||||
"C:\Users\YOUR_USER_NAME\Documents\maya\scripts"
|
||||
|
||||
3. Start Maya and run command below in command bar with Python way
|
||||
import springmagic
|
||||
springmagic.main()
|
||||
@@ -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' #
|
||||
12
2024/scripts/animation_tools/springmagic/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
__version__ = "3.5a"
|
||||
|
||||
from springmagic.main import main
|
||||
|
||||
|
||||
def version():
|
||||
"""
|
||||
Return the current version of the Spring Magic
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return __version__
|
||||
976
2024/scripts/animation_tools/springmagic/core.py
Normal file
@@ -0,0 +1,976 @@
|
||||
# - * - 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
|
||||
|
||||
from . import decorators
|
||||
from . import springMath
|
||||
|
||||
from .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 = [[x] + [y for y in pm.listRelatives(x, allDescendents=True) if y in selected_ctrls][::-1]
|
||||
for x in 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 = [
|
||||
([x] if x.getParent() else []) + [y for y in pm.listRelatives(x, allDescendents=True) if y in objs][::-1] for x
|
||||
in 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 list(spring_data_dict.values()):
|
||||
|
||||
if not SpringMagicMaya.isInterrupted():
|
||||
spring_data.keyframe_child_proxy()
|
||||
|
||||
progression = next(progression_generator)
|
||||
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 list(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 list(
|
||||
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 = next(progression_generator)
|
||||
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 list(spring_data_dict.values())]
|
||||
|
||||
# Deactivate all pairblend otherwise bake doesn't work with animation layers
|
||||
for spring_data in list(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 list(SM_boneTransformDict.keys()):
|
||||
logging.debug(SM_boneTransformDict[obj][0])
|
||||
|
||||
obj.setTranslation(SM_boneTransformDict[obj][0])
|
||||
obj.setRotation(SM_boneTransformDict[obj][1])
|
||||
|
||||
|
||||
def preCheckCollision(objPos, objLength, capsuleList):
|
||||
# print('objPos:' + str(objPos))
|
||||
# print('objLength:' + str(objLength))
|
||||
|
||||
# pre check bone length compare with collision body radius
|
||||
# will improve performance if bone is far from capsule
|
||||
for capsule in capsuleList:
|
||||
capsule_children_list = pm.listRelatives(capsule, children=1, type='transform')
|
||||
|
||||
p = capsule_children_list[0].getTranslation(space='world')
|
||||
q = capsule_children_list[1].getTranslation(space='world')
|
||||
r = capsule.getAttr('scaleZ')
|
||||
|
||||
bone_to_capsule_distance = springMath.dist_to_line(p, q, objPos)
|
||||
|
||||
# print('p:' + str(p))
|
||||
# print('q:' + str(q))
|
||||
# print('r:' + str(r))
|
||||
# print('boneToCapsuleDistance:' + str(bone_to_capsule_distance))
|
||||
|
||||
# means close enough to have a hit change
|
||||
if bone_to_capsule_distance < objLength + r:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def repeatMoveToPlane(obj, objPos, objTarget, colPlane, times):
|
||||
# Y axis direction of plane
|
||||
n = dt.Vector(get_matrix(colPlane)[4:7])
|
||||
q = get_translation(colPlane)
|
||||
d = n.dot(q)
|
||||
|
||||
# for i in range(times):
|
||||
# pt = objPos
|
||||
# obj.setTranslation(proj_pt_to_plane(pt, n, d), space='world')
|
||||
# if (i + 1) != times:
|
||||
# obj.setTranslation(get_translation(objTarget), space='world')
|
||||
pt = objPos
|
||||
outPos = springMath.proj_pt_to_plane(pt, n, d)
|
||||
|
||||
return outPos
|
||||
|
||||
|
||||
def setWireShading(obj, tmp):
|
||||
obj.getShape().overrideEnabled.set(True)
|
||||
obj.getShape().overrideShading.set(False)
|
||||
|
||||
if tmp:
|
||||
obj.getShape().overrideDisplayType.set(1)
|
||||
|
||||
|
||||
def addCapsuleSphereConstraint(sphereObj):
|
||||
# create a locator and make sphere follow it
|
||||
locator = pm.spaceLocator(name=sphereObj.name() + '_locator' + kCapsuleNameSuffix)
|
||||
|
||||
locator.setTranslation(sphereObj.getTranslation())
|
||||
locator.setRotation(sphereObj.getRotation())
|
||||
locator.getShape().setAttr('visibility', False)
|
||||
|
||||
pm.parentConstraint(locator, sphereObj)
|
||||
|
||||
return locator
|
||||
|
||||
|
||||
def createCapsuleGeometry(size):
|
||||
# create geometry
|
||||
cylinder, cylinder_history = pm.cylinder(radius=size, sections=8, heightRatio=3)
|
||||
pm.rename(cylinder.name(), cylinder.name() + kCapsuleNameSuffix)
|
||||
|
||||
sphereA, sphereA_history = pm.sphere(radius=size, endSweep=180, sections=4)
|
||||
pm.rename(sphereA.name(), sphereA.name() + kCapsuleNameSuffix)
|
||||
|
||||
sphereB, sphereB_history = pm.sphere(radius=size, endSweep=180, sections=4)
|
||||
pm.rename(sphereB.name(), sphereB.name() + kCapsuleNameSuffix)
|
||||
|
||||
# set to wireframe shader
|
||||
setWireShading(cylinder, False)
|
||||
setWireShading(sphereA, True)
|
||||
setWireShading(sphereB, True)
|
||||
|
||||
# build a capsule with geometry
|
||||
cylinder.setAttr('rotateZ', 90)
|
||||
sphereA.setAttr('translateY', -1.5 * size)
|
||||
sphereB.setAttr('rotateZ', 180)
|
||||
sphereB.setAttr('translateY', 1.5 * size)
|
||||
|
||||
# add constrain
|
||||
locatorA = addCapsuleSphereConstraint(sphereA)
|
||||
locatorB = addCapsuleSphereConstraint(sphereB)
|
||||
|
||||
pm.parent(locatorA, cylinder)
|
||||
pm.parent(locatorB, cylinder)
|
||||
|
||||
pm.parent(sphereA, cylinder)
|
||||
pm.parent(sphereB, cylinder)
|
||||
|
||||
sphereA.setAttr('inheritsTransform', False)
|
||||
sphereB.setAttr('inheritsTransform', False)
|
||||
|
||||
pm.connectAttr(cylinder.scaleY, (sphereA_history.name() + '.radius'))
|
||||
pm.connectAttr(cylinder.scaleY, (sphereB_history.name() + '.radius'))
|
||||
pm.connectAttr(cylinder.scaleY, cylinder.scaleZ)
|
||||
|
||||
return cylinder
|
||||
|
||||
|
||||
def getCapsule(getAll):
|
||||
if getAll:
|
||||
nurbsTransLst = pm.ls(type='transform')
|
||||
else:
|
||||
nurbsTransLst = pm.ls(sl=True)
|
||||
|
||||
nurbsSurfaceLst = []
|
||||
for obj in nurbsTransLst:
|
||||
if obj.getShape() and (pm.nodeType(obj.getShape()) == 'nurbsSurface'):
|
||||
nurbsSurfaceLst.append(obj)
|
||||
|
||||
cylinderLst = []
|
||||
for obj in nurbsTransLst:
|
||||
if 'ylinder' in obj.name() and kCapsuleNameSuffix in obj.name():
|
||||
cylinderLst.append(obj)
|
||||
|
||||
return cylinderLst
|
||||
|
||||
|
||||
def addCapsuleBody():
|
||||
# create capsule body for collision
|
||||
# place capsule at ori point of nothing selected in scene
|
||||
# place capsule match with object position and rotation if select scene object
|
||||
collisionBoneList = []
|
||||
objs = pm.ls(sl=True)
|
||||
|
||||
for obj in objs:
|
||||
children = pm.listRelatives(obj, children=1)
|
||||
|
||||
# only add capsule to the obj which has child
|
||||
if children:
|
||||
collisionBoneList.append([obj, children[0]])
|
||||
|
||||
if collisionBoneList:
|
||||
for couple in collisionBoneList:
|
||||
baseBone = couple[0]
|
||||
endBone = couple[1]
|
||||
capsule = createCapsuleGeometry(1)
|
||||
|
||||
pm.parent(capsule, baseBone)
|
||||
# match capsule to bone
|
||||
endBoneTrans = endBone.getTranslation()
|
||||
capsule.setTranslation(endBoneTrans * 0.5)
|
||||
capsule.setAttr('scaleX', endBoneTrans[0] / 3)
|
||||
capsule.setAttr('scaleY', endBoneTrans[0] / 3)
|
||||
cns = pm.aimConstraint(endBone, capsule, aimVector=[1, 0, 0])
|
||||
pm.delete(cns)
|
||||
|
||||
else:
|
||||
capsule = createCapsuleGeometry(1)
|
||||
capsule.setAttr('scaleX', 10)
|
||||
capsule.setAttr('scaleY', 10)
|
||||
pm.select(clear=1)
|
||||
121
2024/scripts/animation_tools/springmagic/decorators.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import maya.mel as mel
|
||||
import maya.cmds as cmds
|
||||
|
||||
from functools import wraps
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Decorators
|
||||
# -----------------------------------------------------------------------------
|
||||
def viewportOff(func):
|
||||
"""
|
||||
Decorator - turn off Maya display while func is running.
|
||||
if func will fail, the error will be raised after.
|
||||
"""
|
||||
@wraps(func)
|
||||
def wrap(*args, **kwargs):
|
||||
|
||||
# Turn $gMainPane Off:
|
||||
mel.eval("paneLayout -e -manage false $gMainPane")
|
||||
|
||||
# Decorator will try/except running the function.
|
||||
# But it will always turn on the viewport at the end.
|
||||
# In case the function failed, it will prevent leaving maya viewport off.
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception:
|
||||
raise # will raise original error
|
||||
finally:
|
||||
mel.eval("paneLayout -e -manage true $gMainPane")
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
class gShowProgress(object):
|
||||
"""
|
||||
Function decorator to show the user (progress) feedback.
|
||||
@usage
|
||||
|
||||
import time
|
||||
@gShowProgress(end=10)
|
||||
def createCubes():
|
||||
for i in range(10):
|
||||
time.sleep(1)
|
||||
if createCubes.isInterrupted(): break
|
||||
iCube = cmds.polyCube(w=1,h=1,d=1)
|
||||
cmds.move(i,i*.2,0,iCube)
|
||||
createCubes.step()
|
||||
"""
|
||||
|
||||
def __init__(self, status='Busy...', start=0, end=100, interruptable=True):
|
||||
import maya.mel
|
||||
|
||||
self.mStartValue = start
|
||||
self.mEndValue = end
|
||||
self.mStatus = status
|
||||
self.mInterruptable = interruptable
|
||||
self.mMainProgressBar = maya.mel.eval('$tmp = $gMainProgressBar')
|
||||
|
||||
def step(self, inValue=1):
|
||||
"""Increase step
|
||||
@param inValue (int) Step value"""
|
||||
cmds.progressBar(self.mMainProgressBar, edit=True, step=inValue)
|
||||
|
||||
def progress(self, inValue):
|
||||
"""Set progression value
|
||||
@param inValue (int) Progress value"""
|
||||
cmds.progressBar(self.mMainProgressBar, edit=True, progress=inValue)
|
||||
|
||||
def isInterrupted(self):
|
||||
"""Check if the user has interrupted the progress
|
||||
@return (boolean)"""
|
||||
return cmds.progressBar(self.mMainProgressBar, query=True, isCancelled=True)
|
||||
|
||||
def start(self):
|
||||
"""Start progress"""
|
||||
cmds.waitCursor(state=True)
|
||||
cmds.progressBar(
|
||||
self.mMainProgressBar,
|
||||
edit=True,
|
||||
beginProgress=True,
|
||||
isInterruptable=self.mInterruptable,
|
||||
status=self.mStatus,
|
||||
minValue=self.mStartValue,
|
||||
maxValue=self.mEndValue)
|
||||
cmds.refresh()
|
||||
|
||||
def end(self):
|
||||
"""Mark the progress as ended"""
|
||||
cmds.progressBar(self.mMainProgressBar, edit=True, endProgress=True)
|
||||
cmds.waitCursor(state=False)
|
||||
|
||||
def __call__(self, inFunction):
|
||||
"""
|
||||
Override call method
|
||||
@param inFunction (function) Original function
|
||||
@return (function) Wrapped function
|
||||
@description
|
||||
If there are decorator arguments, __call__() is only called once,
|
||||
as part of the decoration process! You can only give it a single argument,
|
||||
which is the function object.
|
||||
"""
|
||||
def wrapped_f(*args, **kwargs):
|
||||
# Start progress
|
||||
self.start()
|
||||
# Call original function
|
||||
inFunction(*args, **kwargs)
|
||||
# End progress
|
||||
self.end()
|
||||
|
||||
# Add special methods to the wrapped function
|
||||
wrapped_f.step = self.step
|
||||
wrapped_f.progress = self.progress
|
||||
wrapped_f.isInterrupted = self.isInterrupted
|
||||
|
||||
# Copy over attributes
|
||||
wrapped_f.__doc__ = inFunction.__doc__
|
||||
wrapped_f.__name__ = inFunction.__name__
|
||||
wrapped_f.__module__ = inFunction.__module__
|
||||
|
||||
# Return wrapped function
|
||||
return wrapped_f
|
||||
75
2024/scripts/animation_tools/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
2024/scripts/animation_tools/springmagic/icons/China Flag.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
2024/scripts/animation_tools/springmagic/icons/Shelf.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
2024/scripts/animation_tools/springmagic/icons/Title.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
2024/scripts/animation_tools/springmagic/icons/addCapsule.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2024/scripts/animation_tools/springmagic/icons/addPlane.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
2024/scripts/animation_tools/springmagic/icons/ali_pay.png
Normal file
|
After Width: | Height: | Size: 31 KiB |