This commit is contained in:
2025-12-06 18:23:17 +08:00
parent 1f10abfb32
commit 9f30e905d7
259 changed files with 33763 additions and 348 deletions

BIN
2025/icons/QuadRemesher.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
2025/icons/creaseplus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
2025/icons/springmagic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

2
2025/scripts/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

View File

@@ -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()

View File

@@ -0,0 +1,57 @@
############################################
# Utility Functions
############################################
# to get object parrent
# parent = obj.getParent()
# to get all parents of a joint
# parentList = joint.getAllParents()
# to get root bone
# rootBone = joint.root()
# to get object all children
# children = pm.listRelatives(obj, allDescendents = 1)
# to make sure the selection is a mesh
# pm.nodeType(pm.ls(sl=True, type='transform')[0].getShape()) == 'mesh'
# to get vertex in selection as flatten
# pm.ls(sl=True, type='float3', flatten=True)[0]
# to get skin cluster
# pm.listHistory(pm.ls(sl=True), type='skinCluster')[0]
# to get all influcent bone of a skin cluster
# obj.getInfluence()
# About path module
# from pymel.util.path import path
# filePath = 'c:/temp/test/myTestFile.txt'
# fpPathObj = path(filePath)
# fpPathObj
# # Result: path('c:/temp/test/myTestFile.txt') #
# fpPathObj.basename()
# # Result: 'myTestFile.txt' #
# # .name is a property which returns the same
# fpPathObj.name
# # Result: 'myTestFile.txt' #
# # namebase returns fileName only w/o extension
# fpPathObj.namebase
# # Result: 'myTestFile' #
# # return directory above file
# fpPathObj.parent
# # Result: path('c:/temp/test') #
# # check extension
# fpPathObj.endswith('txt')
# # Result: True #
# # check existance
# fpPathObj.exists()
# # Result: True #
# # check to see if folder type
# fpPathObj.parent.isdir()
# # Result: True #
# fpPathObj.parent.parent.name
# # Result: 'temp' #

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

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

View File

@@ -0,0 +1,121 @@
import maya.mel as mel
import maya.cmds as cmds
from functools import wraps
# -----------------------------------------------------------------------------
# Decorators
# -----------------------------------------------------------------------------
def viewportOff(func):
"""
Decorator - turn off Maya display while func is running.
if func will fail, the error will be raised after.
"""
@wraps(func)
def wrap(*args, **kwargs):
# Turn $gMainPane Off:
mel.eval("paneLayout -e -manage false $gMainPane")
# Decorator will try/except running the function.
# But it will always turn on the viewport at the end.
# In case the function failed, it will prevent leaving maya viewport off.
try:
return func(*args, **kwargs)
except Exception:
raise # will raise original error
finally:
mel.eval("paneLayout -e -manage true $gMainPane")
return wrap
class gShowProgress(object):
"""
Function decorator to show the user (progress) feedback.
@usage
import time
@gShowProgress(end=10)
def createCubes():
for i in range(10):
time.sleep(1)
if createCubes.isInterrupted(): break
iCube = cmds.polyCube(w=1,h=1,d=1)
cmds.move(i,i*.2,0,iCube)
createCubes.step()
"""
def __init__(self, status='Busy...', start=0, end=100, interruptable=True):
import maya.mel
self.mStartValue = start
self.mEndValue = end
self.mStatus = status
self.mInterruptable = interruptable
self.mMainProgressBar = maya.mel.eval('$tmp = $gMainProgressBar')
def step(self, inValue=1):
"""Increase step
@param inValue (int) Step value"""
cmds.progressBar(self.mMainProgressBar, edit=True, step=inValue)
def progress(self, inValue):
"""Set progression value
@param inValue (int) Progress value"""
cmds.progressBar(self.mMainProgressBar, edit=True, progress=inValue)
def isInterrupted(self):
"""Check if the user has interrupted the progress
@return (boolean)"""
return cmds.progressBar(self.mMainProgressBar, query=True, isCancelled=True)
def start(self):
"""Start progress"""
cmds.waitCursor(state=True)
cmds.progressBar(
self.mMainProgressBar,
edit=True,
beginProgress=True,
isInterruptable=self.mInterruptable,
status=self.mStatus,
minValue=self.mStartValue,
maxValue=self.mEndValue)
cmds.refresh()
def end(self):
"""Mark the progress as ended"""
cmds.progressBar(self.mMainProgressBar, edit=True, endProgress=True)
cmds.waitCursor(state=False)
def __call__(self, inFunction):
"""
Override call method
@param inFunction (function) Original function
@return (function) Wrapped function
@description
If there are decorator arguments, __call__() is only called once,
as part of the decoration process! You can only give it a single argument,
which is the function object.
"""
def wrapped_f(*args, **kwargs):
# Start progress
self.start()
# Call original function
inFunction(*args, **kwargs)
# End progress
self.end()
# Add special methods to the wrapped function
wrapped_f.step = self.step
wrapped_f.progress = self.progress
wrapped_f.isInterrupted = self.isInterrupted
# Copy over attributes
wrapped_f.__doc__ = inFunction.__doc__
wrapped_f.__name__ = inFunction.__name__
wrapped_f.__module__ = inFunction.__module__
# Return wrapped function
return wrapped_f

View File

@@ -0,0 +1,75 @@
#####################################################################################
#
# Spring Magic for Maya
#
# Calculate bone chain animation by settings, support collisions and wind force
# Can work with rigging controller as well
#
# Need pringMagic.ui file to work with
# This script need also icon file support, which should be put in same folder
#
# feel free to mail me redtank@outlook.com for any bug or issue
#
# Yanbin Bai
# 2021.02
#
#####################################################################################
3.5a
- Fix bug tension calculation introduced in the 3.5 (Benoit Degand)
- Fix bug inertia calculation introduced in the 3.5 (Benoit Degand)
- Clarify code splitting source code in class and methods (Benoit Degand)
3.5
- Apply PEP8 coding format (Benoit Degand)
- Add possiblity to cancel the operation (Esc) (Benoit Degand)
- Increase speed (x2), avoiding locators and aim constraints intensive creation/deletion (Benoit Degand)
- Fragment source code in several files (Benoit Degand)
- Pose Match default off
3.4b
- fix collision bug
3.4a
- fix wind bug
3.4
- add plane collision
- add pose match
3.3
- add inertia effect
3.2
- fix wind effect cannot set key issue
3.1
- add bind controller
- add wind
- add flex setting
- improve performance
- fix twist bug
- add capsule icon
- seperate skinTools to spring magic and skin magic
3.0
- re-write spring magic to improve performance
- add capsule collision for spring magic
- add donate page
2.7.8
- fix script stop working issue cause by highend3d.com changed their web page
2.7.7
- add time out for update checking in case of network issue
2.7.6
- fix spring magic calculation issue on MAYA 2016
- update UI for MAYA 2016
Thanks for all the help from Nobuyuki Kobayashi nobuyuki@unity3d.com
2.7.5
- add floor collision to spring magic
2.7
- Add spring magic

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

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

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

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

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

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

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

View File

@@ -1,43 +1,44 @@
##GLOBAL VARIABLEs
try:
from PySide6 import QtWidgets, QtCore, QtGui
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import *
from shiboken6 import wrapInstance
except ImportError:
from PySide2 import QtWidgets, QtCore, QtGui
from shiboken2 import wrapInstance
from PySide2.QtGui import QIcon
from PySide2.QtWidgets import QWidget
import shiboken2
from PySide2 import QtWidgets, QtCore, QtGui
from maya import cmds as mc
import maya.mel as mel
import json
from .Qt import QtWidgets, QtCore, QtCompat
import os
import maya.cmds as cmds
from maya import OpenMayaUI as omui
# Special cases for different Maya versions
try:
from shiboken2 import wrapInstance
except ImportError:
from shiboken import wrapInstance
try:
from PySide2.QtGui import QIcon
from PySide2.QtWidgets import QWidget
except ImportError:
from PySide.QtGui import QIcon, QWidget
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)
IconsPathThemeClassic = os.path.join(USERAPPDIR, VERSION+'/scripts/ModIt/Icons/Theme_Classic/')
ToolPath = os.path.join(USERAPPDIR, VERSION+'/scripts/ModIt/Tools/')
PreferencePath = os.path.join(USERAPPDIR, VERSION+'/scripts/ModIt/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(USERAPPDIR, VERSION+'/scripts/ModIt/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('\\', '/')
##_____________________________________________PREFERENCES
ModItTitle = "ModIt - 3.3"
ModItTitle = "ModIt - 3.1.1"
##_____________________________________________UI

View File

@@ -1 +1 @@
{"TAB_OPEN": 0}
{"TAB_OPEN": 2}

View File

@@ -1,2 +1,2 @@
[General]
windowGeometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\x5\xce\0\0\x2\x31\0\0\a!\0\0\x4\xf1\0\0\x5\xce\0\0\x2^\0\0\a!\0\0\x4\xf1\0\0\0\0\0\0\0\0\xf\0\0\0\x5\xce\0\0\x2^\0\0\a!\0\0\x4\xf1)
windowGeometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\x4V\0\0\0q\0\0\x5\xa9\0\0\x3\x37\0\0\x4V\0\0\0\x90\0\0\x5\xa9\0\0\x3\x37\0\0\0\0\0\0\0\0\a\x80\0\0\x4V\0\0\0\x90\0\0\x5\xa9\0\0\x3\x37)

View File

@@ -9,5 +9,6 @@ General modeling utilities
from .batchextrusion import show_batch_extrusion_ui
__all__ = [
'show_batch_extrusion_ui'
'show_batch_extrusion_ui',
'creaseplus'
]

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

View 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()";
}

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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,967 @@
<!--
Tooltips are added in format:
# Widget
Tooltip
-->
<!-- main.py -->
<!-- Options Menu -->
# importCurves
Imports Curves that were Exported using the Export Button.
NOTE: Only import files that were exported using Export Curves button.
WARNING: This operation is NOT undoable!
# exportCurves
Exports selected curves into a .curves (or .ma) file. Can then be Imported using Import Curves.
# changeScaleFactor
Opens a window that controls the Scale Factor and Precision Scale.
***
Scale factor determines the initial size of the created curve and adjusts other parameters.
Scale factor is stored in a global preset, in the scene and on each curve.
Priority of scale factors Curve>Scene>Global.
Setting the appropriate scale factor before starting a project can help to have a good initial Width and size of the cards.
***
Precision Scale controls the world scale of the curve without changing the curve appearance, CV positions and other parameters.
Lowering the Precision scale (from 1.0 to 0.05 for example) helps to fix the geometry deformation on very small curves.
The default value of 0.05 should be sufficient for most cases. Values lower than 0.01 can affect performance.
***
Normalize Selected and Normalize Selected to Default will change the Scale Factor and Precision Scale of the selected curves, without changing their appearance or CV positions.
Normalize Selected to Default will reset the slider values to default (0.5 and 0.05) and Normalize Selected will use the current slider values.
# normalizeSelectedButton
Normalize selected curves to the chosen slider values.
Normalization will not change the CV positions or geometry appearance, but the selected objects will have new Scale Factor and Precision Scale applied.
# normalizeSelectedToDefaultButton
Normalize selected curves to the default slider values (0.5, 0.05).
Resets the slider values to the default.
Normalization will not change the CV positions or geometry appearance, but the selected objects will have new Scale Factor and Precision Scale applied.
# saveScaleFactorAndPrecisionScaleButton
Saves the selected slider values to the global storage and to the current scene.
New scenes will automatically have these Scale Factor and Precision Scale values.
NOTE: This will not apply the new values to already existing objects. See Normalize buttons.
# saveScaleFactorAndPrecisionScaleButtonAndClose
Saves the selected slider values to the global storage and to the current scene.
New scenes will automatically have these Scale Factor and Precision Scale values.
Closes the window after save.
NOTE: This will not apply the new values to already existing objects. See Normalize buttons.
# closePrecisionScaleWindow
Close this window without save
# globalCurveThickness
Opens a window that controls the thickness of the curves in the scene as well as the global curve thickness preset across the scenes.
# setAOSettings
Manually sets the recommended AO settings for older Maya versions.
These AO settings are needed to use the "See Through" or "Toggle Always on Top" functions.
Are applied automatically by those functions.
# setTransparencySettings
Sets recommended transparency settings for Maya viewport.
***
Simple Transparency is fast but very inaccurate render mode. Only suitable for simple, one layer hair.
Object Sorting Transparency has average performance impact and quality. Can have issues on complex multi-layered grooms.
Depth Transparency - these are the optimal settings for the highest quality of the hair cards preview. Can have performance impact on slower systems.
# convertToWarpCard
Converts selection to Warp Cards.
Compatible attributes are retained.
# convertToWarpTube
Converts selection to Warp Tubes.
Compatible attributes are retained.
# convertToExtrudeCard
Converts selection to Extrude Cards.
Compatible attributes are retained.
# convertToExtrudeTube
Converts selection to Extrude Tubes.
Compatible attributes are retained.
# duplicateUnparentCurves
Duplicates selected NURBS curves and unparents them (parents them to the world).
Original curves are not deleted.
Can be used to easily extract and export curves from GS CurveTools objects.
# syncCurveColor
Toggles the syncing of the curve color to the layer color.
# colorizedRegroup
Toggles the colorization of the regrouped layers when Regroup by Layer function is used.
# colorOnlyDiffuse
Colorize only the diffuse component of the card material.
Alpha will stay the same.
# checkerPattern
Toggles the use of the checker pattern when the Color mode is enabled.
# ignoreLastLayer
Toggles the filtering (All, Curve, Geo) and Extraction (Extract All) of the last layer. If enabled, the last layer is ignored.
NOTE: Last layer is typically used for templates, so it is ignored by default.
# ignoreTemplateCollections
Ignores the filtering (All, Curve, Geo) and Extraction (Extract All) of all the collections that have "template" in their name. Case insensitive.
# groupTemplateCollections
Collections that have "template" in their name (case insensitive) will be grouped together into "CT_Templates" group by Regroup By Layer function.
# syncOutlinerLayerVis
Syncs the outliner and layer visibility. If enabled, hiding the layer will also hide the curve groups in the outliner.
# keepCurveAttributes
If enabled, the attributes that are stored in the curve will be restored if the curve that was duplicated (on its own, without the geo) is used to create other cards or tubes.
If disabled, the attributes will be ignored and reset.
Example:
1. Create a card and change its twist value.
2. Ctrl+D and Shift+P the curve (not the curve group).
3. Click Curve Card and the twist value will be restored on a newly created card.
# boundCurvesFollowParent
Will ensure that moving a parent curve in a Bound Object (Bound Group) will also move all the child curves along with it to a new layer. Recommended to keep this enabled.
# massBindOption
Will bind selected hair clump (or geometry) to all selected "empty" curves.
# bindDuplicatesCurves
Will automatically duplicate the curves before binding them to the curve, leaving old curves behind with no edits.
# bindFlipUVs
Enabling this option will flip the UVs of the original cards before binding them to the curve.
This option will also flip the UVs back when using Unbind for better workflow.
Disabling this option will result in an old Bind/Unbind behaviour.
# unpackDeleteOriginalObject
Unpack (Shift + Click on Unbind) will delete the original Bind object.
# unpackCVMatch
Unpack (Shift + Click on Unbind) will match the CVs of the original Bind object.
# addBetweenBlendAttributes
Enables blending of the attributes when using Add Cards/Tubes or Fill functions.
# fillCreateCurvesOnly
When enabled Fill function will only create curves (not the geo).
# convertInstances
Will automatically convert instanced curves to normal curves before any other function is applied.
# replacingCurveLayerSelection
Will disable additive selection for the layers. When holding Ctrl and clicking on a new layer, old layer will be deselected automatically.
# useAutoRefineOnNewCurves
Automatically enables auto-refine on the new curves.
# flipUVsAfterMirror
Enabling this option will flip the UVs horizontally after mirroring the cards to achieve exact mirroring.
# enableTooltips
Will toggle the visibility of these tooltips you are reading right now.
# showLayerCollectionsMenu
Shows layer collections menu widget.
# importIntoANewCollection
If enabled, all the imported curves will be placed into a new "Imported Curves" layer collection.
If disabled, all the imported curves will be placed into the "Main" layer collection
# layerNumbersOnly
Layers will use only numbers if enabled.
# convertToNewLayerSystem
Converts all the layers in the scene to a new layer system that is hidden from the Channel Box Display Layers window.
Layers can still be accessed from Window->Relationship Editors->Display Layer window.
# updateLayers
Utility function that will manually update all layers. Used for if layers are correct for some reason.
# resetToDefaults
Resets every option and the GS CurveTools to the default "factory" state.
# maya2020UVFix
This function will fix any broken UVs when trying to open old scenes in Maya 2020 or 2022 or when opening scenes in 2020 and 2022 when using Maya Binary file type. This will have no effect on older versions of Maya (<2020). This bug is native to Maya and thus cant be fixed in GS CurveTools plug-in.
# mayaFixBrokenGraphs
This function will attempt to fix all the broken graphs in the scene.
Use if one of the graphs (Width, Twist or Profile) is in a broken state.
# convertBezierToNurbs
Converts the selected Bezier curves to NURBS curves.
Bezier curves are not supported by the GS CurveTools.
# maya2020TwistAttribute
This function will fix any broken cards created in Maya 2020.4 before v1.2.2 update.
# maya2020UnbindFix
This function will fix any cards that are not unbinding properly and were created before v1.2.3 update in Maya 2020.4.
# deleteAllAnimationKeys
This function will delete all the animation keys on all the curves present in the scene.
This can fix deformation issues when using duplicate or other GS CurveTools functions.
Some functions (like duplicate) will delete the keys automatically, however the keys can still cause issues.
<!-- Main Buttons -->
# warpSwitch
Advanced cards and tubes suitable for longer hair.
Additional options and controls.
Slower than Extrude (viewport performance).
Can have issues on very small scales.
# extrudeSwitch
Simple cards and tubes suitable for shorter hair and brows.
Has limited controls.
Much faster than Warp (viewport performance).
Better for small scales.
# newCard
Creates a new Card in the middle of the world. Used at the beginning of the project to create templates.
# newTube
Creates a new Tube in the middle of the world. Used at the beginning of the project to create templates.
# curveCard
Converts selected Maya curve to CurveTools Card.
# curveTube
Converts selected Maya curve to CurveTools Tube.
# gsBind
Binds selection to a single curve. Creates a Bind Group. Selection options:
1. Single "empty" curve (default Maya curve) and single combined geometry.
2. Single "empty" curve (default Maya curve) and any number of Curve Cards and Curve Tubes.
***
Shift + Click will duplicate the original curves/geo before binding it to the empty curve.
Same option is available in the Options menu (Duplicate Curves Before Bind).
# gsUnbind
Normal Click:
UnBinds geometry or Cards/Tubes from selected Bound object.
Geometry and Cards/Tubes will be placed at the origin.
***
Shift + Click:
Will UNPACK the selected Bind object in-place.
Unpack will attempt to create an in-place approximation of the cards and tubes that Bind object consists of.
Basically it will extract the geometry and create cards (or tubes) based on that geometry.
The original Bind object will be deleted in the process. Optionally, you can keep it (toggle in the options menu).
This operation is not procedural, so you will not be able to return back to the Bind object after (unlike regular UnBind).
WARNING: Unpack is not a 1 to 1 conversion. It will try its best to approximate the shape, but complex twists and bends will not be captured.
# addCards
Creates Cards in-between selected Cards based on the Add slider value.
Shift + Click: creates objects but does not blend the attributes.
Bound objects are NOT supported.
NOTE: Selection order defines the direction of added Cards if more than 2 initial Cards are selected.
# addTubes
Creates Tubes in-between selected Tubes based on the Add slider value.
Shift + Click: creates objects but does not blend the attributes.
Bound objects are NOT supported.
NOTE: Selection order defines the direction of added Tubes if more than 2 initial Tubes are selected.
# gsFill
Creates Cards/Tubes or Bound Groups in between selected Cards/Tubes or Bound Groups based on the Add slider value.
Shift + Click: creates objects but does not blend the attributes.
NOTE 1: Selection order defines the direction of added curves if more than 2 initial curves are selected.
NOTE 2: The type of Card or Tube or Bound Group is defined by the previous curve in the selection chain.
NOTE 3: Options -> Fill Creates Only Curves option will make the Fill function create only NURBS curves, but not the geo.
# gsSubdivide
Subdivides selected curve into multiple curves based on the Add slider value
Shift + Click subdivides selected curve but does not delete the original curve
# gsEdgeToCurve
Converts selected geometry edges to curves.
Multiple unconnected edge groups can be selected at the same time.
***
Key Combinations:
Shift + Click will create a curve without the curvature (first degree curve or simply a line)
# gsGeoToCurve
Opens the Geo-to-Curve UI
Geo to Curve algorithm will attempt to generate GS CurveTools cards and tubes from selected geometry.
Selected geometry should be either one-sided cards or regular tubes without caps (or both).
Multi-selection compatible, but selected geometries should be separate objects and not one combined object.
# layerCollectionsComboBox
Layer collections drop-down menu.
Allows to separate the project into different layer collections, up to 80 layers in each collection.
Has additional functionality in marking menu (Hold RMB):
***
Marking menu:
1. Clear - will delete all the cards from the current layer. Undoable operation.
2. Merge Up, Merge Down - merge all the cards from the current layer to the layer above or below it.
3. Copy, Paste - will copy all the cards from the current layer and paste them to the layer that user selects.
4. Move Up, Move Down - will rearrange the current layer collections by moving the currently selected collection up or down in the list.
5. Rename - will rename the current layer collection
# layerCollectionsPlus
Add additional layer collection after the current one.
# layerCollectionsMinus
Remove current layer collection. All the cards from the removed collection will be merged one layer UP.
# gsAllFilter
Layer filter. Controls the visibility of all objects in all layers:
Normal click will show all curves and geometry in all layers.
Shift + Click will hide all curves and geometry in all layers
Ctrl + Click will show all the curves and geometry in all layers and all collections.
Ctrl + Shift + Click will hide all curves and geometry in all layers and all collections.
# gsCurveFilter
Layer filter. Hides all geometry and shows all the curves in all layers.
Ctrl + Click will do the same thing, but for all layers and all collections.
NOTE: Holding RMB will open a marking menu with Toggle Always on Top function as well as "Auto-Hide Inactive Curves" function.
Toggle Always on Top function will toggle the Always on Top feature that will show the curve component always on top. The effect is different in different Maya versions
Auto-Hide Inactive Curves will hide all the curve components on all inactive layer collections when switching between collections.
# gsGeoFilter
Layer filter. Hides all curves and shows only geometry.
Ctrl + Click will do the same thing, but for all layers and all collections.
# colorMode
Color mode toggle. Enables colors for each layer and (optionally) UV checker material.
NOTE: Checker pattern can be disabled in the Options menu
# curveGrp0
Curve Layers that are used for organization of the cards and tubes in the scene.
Selected layer (white outline) will be used to store newly created cards.
Holding RMB will open a marking menu with all the functions of current layer.
***
Key Combinations:
Shift + Click: additively select the contents of the layers.
Ctrl + Click: exclusively select the contents of the layer.
Alt + Click: show/hide selected layer.
Ctrl + Shift: show/hide curve component on selected layer.
Ctrl + Alt: show/hide geo component for the selected layer.
Shift + Alt + Click: isolate select the layer.
Shift + Ctrl + Alt + Click: enable Always on Top for each layer (only for Maya 2022+).
***
Layer MMB Dragging:
MMB + Drag: move the contents of one layer to another layer. Combine if target layer has contents.
MMB + Shift + Drag: copy the contents of one layer to another layer. Copy and Add if target layer has contents.
# gsExtractSelected
Extracts (Duplicates) the geometry component from the selected curves:
***
Key Combinations:
Normal click will extract geometry and combine it.
Shift + Click will extract geometry as individual cards.
Ctrl + Click will extract geometry, combine it, open export menu and delete the extracted geo after export.
Shift + Ctrl click will extract geometry, open export menu and delete the extracted geo after export.
# gsExtractAll
Extracts (Duplicates) the geometry component from all layers and collections. Original layers are HIDDEN, NOT deleted:
Last Layer in the current Collection is ignored by default. Can be changed in the options.
Collections with "template" in their name (case insensitive) will be ignored by default. Can be changed in the options.
***
Key Combinations:
Normal click will extract geometry and combine it.
Shift + Click will extract geometry as individual cards grouped by layers.
Ctrl + Click will extract geometry, combine it, open export menu and delete the extracted geo after export.
Shift + Ctrl click will extract geometry, open export menu and delete the extracted geo after export.
# gsSelectCurve
Selects the curve components of the selected Curve Cards/Tubes.
NOTE: Useful during the selection in the outliner.
# gsSelectGeo
Selects the geometry component of the selected Curve Cards/Tubes.
NOTE: Useful for quick assignment of the materials.
# gsSelectGroup
Selects the group component of the selected Curve Cards/Tubes.
NOTE: Useful when you are deleting curves from viewport selection.
# gsGroupCurves
Groups the selected curves and assigns the name from Group Name input field (or default name if empty).
# gsRegroupByLayer
Regroups all the curves based on their layer number, group names and collection names.
Group names can be changed in the Layer Names & Colors menu.
Groups can be colorized if the "Colorize Regrouped Layers" is enabled in the Options menu.
Collections with "template" in their name will be grouped under "CT_Templates". Can be changed in the options.
# gsGroupNameTextField
The name used by the Group Curves function.
If empty, uses the default name.
# gsCustomLayerNamesAndColors
Opens a menu where group names and colors can be changed and stored in a global preset.
# gsTransferAttributes
Transfers attributes from the FIRST selected curve to ALL the other curves in the selection.
NOTE: Shift + Click transfers the attributes from the LAST selected curve to ALL others.
NOTE2: Holding RMB on this button opens a marking menu with Copy-Paste and Filter functionality
# gsTransferUVs
Transfers UVs from the FIRST selected curve to ALL the other curves in the selection.
NOTE: Shift + Click transfers the UVs from the LAST selected curve to ALL others.
NOTE2: Holding RMB on this button opens a marking menu with Copy-Paste and Filter functionality
# gsResetPivot
Resets the pivot on all selected curves to the default position (root CV).
# gsRebuildWithCurrentValue
Rebuild selected curves using current rebuild slider value
# gsResetRebuildSliderRange
Reset rebuild slider range (1 to 50)
# gsDuplicateCurve
Duplicates all the selected curves and selects them.
NOTE: You can select either NURBS curve component, geometry component or group to duplicate.
# gsRandomizeCurve
Opens a window where different attributes of the selected curves can be randomized:
1. Enable the sections of interest and change the parameters.
2. Dragging the sliders in each section enables a PREVIEW of the randomization. Releasing the slider will reset the curves.
3. Click Randomize if you wish to apply the current randomization.
# gsExtendCurve
Lengthens a selected curves based on the Factor slider.
# gsReduceCurve
Shortens the selected curves based on the Factor slider.
# gsSmooth
Smoothes selected curves or curve CVs based on the Factor slider.
NOTE 1: At least 3 CVs should be selected for component smoothing.
NOTE 2: Holding RMB will open a marking menu where you can select a stronger smoothing algorithm.
# mirrorX
Mirrors or Flips all the selected curves on the World X axis.
# mirrorY
Mirrors or Flips all the selected curves on the World Y axis.
# mirrorZ
Mirrors or Flips all the selected curves on the World Z axis.
# gsControlCurve
Adds a Control Curve Deformer to the selected curves. Can be used to adjust groups of curves.
NOTE 1: Should NOT be used to permanently control clumps of cards. Use Bind instead.
# gsApplyControlCurve
Applies the Control Curve Deformer.
Either the Control Curve or any controlled Curves can be selected for this to work.
# gsCurveControlWindow
Opens a Curve Control Window. Contains all the available controls for curves.
# gsUVEditorMain
Opens a UV editor that can be used to setup and adjust UVs on multiple cards.
NOTE 1: Lambert material with PNG, JPG/JPEG or TIF/TIFF (LZW or No Compression) texture file is recommended. TGA (24bit and no RLE) is also supported.
NOTE 2: Make sure to select the curves or the group, not the geo, to adjust the UVs.
NOTE 3: Using default Maya UV editor will break GS CurveTools Cards, Tubes and Bound Groups.
NOTE 4: Default UV editor can be used when custom geometry is used in a Bound Groups.
<!-- ui.py --->
<!-- Curve Control Window Buttons and Frames --->
# gsLayerSelector
Shows the Layer of the selected curve.
Selecting different layer will change the layer of all the selected curves.
# gsColorPicker
Layer/Card Color Picker.
# gsCurveColorPicker
Curve Color Picker.
# selectedObjectName
Selected object name. Editing this field will rename all the selected objects.
# lineWidth
Controls the thickness of the selected curves.
# gsBindAxisAuto
Automatic selection of the bind Axis (recommended).
NOTE: Change to manual X, Y, Z axis if bind operation result is not acceptable.
# AxisFlip
Flips the direction of the bound geometry.
# editOrigObj
Temporarily disables curve bind and shows the original objects.
Used to adjust the objects after the bind.
To add or remove from the Bound group use Unbind.
# selectOriginalCurves
Selects the original curves that were attached to a bind curve.
Allows to edit their attributes without using Unbind or Edit Original Objects
# twistCurveFrame
Advanced twist control graph. Allows for precise twisting of the geometry along the curve. Click to expand.
# Magnitude
Twist multiplier. The larger the values, the more the twist. Default is 0.5.
# gsTwistGraphResetButton
Resets the graph to the default state.
# gsTwistGraphPopOut
Opens a larger graph in a separate window that is synced to the main graph.
# widthLockSwitch
Links/Unlinks the X and Z width sliders.
If linked, the sliders will move as one.
# LengthLock
Locks/Unlocks the length slider.
When Locked the geometry is stretched to the length of the curve and the slider is ignored.
# widthCurveFrame
Advanced width control graph. Allows for precise scaling of the geometry along the curve. Click to expand.
# gsWidthGraphResetButton
Resets the graph to the default state.
# gsWidthGraphPopOut
Opens a larger graph in a separate window that is synced to the main graph.
# profileCurveGraph
Advanced control over the profile of the card. Modifies the profile applied by the Profile slider. Click to expand.
Add or remove points and change them to increase or decrease the Profile value along the curve.
# autoEqualizeSwitchOn
Locks the points horizontally to equal intervals to avoid geometry deformation.
# autoEqualizeSwitchOff
Unlocks the points and allows for the full control.
# equalizeCurveButton
Snaps the points to the equal horizontal intervals.
# gsResetProfileGraphButton
Resets the curve to the default state.
# gsProfileGraphPopOut
Opens a larger graph in a separate window that is synced to the main graph.
# reverseNormals
Reverses the normals on the selected cards/tubes.
# orientToNormalsFrame
Orient selected cards/tubes to the normals of the selected geo.
# gsOrientToNormalsSelectTarget
Set selected mesh as a target for the algorithm.
# orientRefreshViewport
Toggles the viewport update during the alignment process.
Disabling can speed up the process.
# gsOrientToNormals
Starts the alignment process.
Will align the selected cards to the normals of the selected geometry.
# flipUV
Flips the UVs of the card/tube horizontally.
# resetControlSliders
Resets the range of the sliders to the default state.
# UVFrame
Legacy controls for the UVs. Just use the new UV Editor.
# solidifyFrame
Expands controls that control the thickness of the cards/tubes.
# solidify
Toggles the thickness of the geometry.
<!-- Curve Control Window Sliders --->
# lengthDivisions
Change the length divisions of the selected cards/tubes.
# dynamicDivisions
Toggles the dynamic divisions mode.
Dynamic divisions will change the divisions of the cards/tubes based on the length of the curves.
In dynamic divisions mode, L-Div slider will control the density of the divisions, not the fixed divisions count.
# widthDivisions
Change the width divisions of the selected cards/tubes.
# Orientation
Change the orientation of the card/tube around the curve.
# Twist
Smoothly twist the entire geometry card/tube. Twists the tip of the card.
# invTwist
Smoothly twist the entire geometry card. Twists the root of the card.
# Width
Change the width of the selected card.
# Taper
Linearly changes the width of the card/tube along the length of the curve.
# WidthX
Change the width of the tube along the X axis.
# WidthZ
Change the width of the tube along the Z axis.
# Length
Change the length of the attached geometry. Works only when Length Unlock button is checked.
# Offset
Offset the geometry along the curve.
# Profile
Change the profile of the card along the length of the curve uniformly.
# profileSmoothing
Smoothing will smooth the profile transition.
# otherFrame
Other less used options
# curveRefine
Controls the number of "virtual" vertices on the curve. These are the vertices that are used to calculate the geometry deformation.
Zero (0) value will disable the refinement and the geometry will be attached directly to the curve. The fastest option.
Larger refine values means smoother geometry that is a closer fit to the curve.
Only increase past 20 if you need additional precision or if there are any visual glitches with the geometry.
Large refine values can cause significant performance drop, lag and other issues on smaller curve sizes.
Recommended values are:
20 for curves with less than 20 CVs.
0 (disabled) or same as the number of CVs for curves with more than 20 CVs.
# autoRefine
Enables auto-refine for selected curves. Recommended to keep this on.
Manual refinement can be helpful if the geometry deformation is wrong or not precise enough.
# samplingAccuracy
Increases the sampling accuracy of the deformer that attaches the geometry to a curve.
Larger values = more precise geometry fit to a curve and more lag.
# surfaceNormals
Changes the smoothing angle of the normals of the geometry.
# gsIterationsSlider
Controls the number of iterations per card.
# gsMinimumAngle
Controls the target angle difference between the normal of the mesh and the card.
# solidifyThickness
Controls the amount of thickness on the geometry.
# solidifyDivisions
Controls the number of divisions on the solidify extrusion.
# solidifyScaleX
Changes the scale on the X axis.
# solidifyScaleY
Changes the scale on the Y axis.
# solidifyOffset
Controls the offset of the solidify extrusion.
# solidifyNormals
Controls the smoothing angle for normals of the solidify extrusion.
# geometryHighlight
If enabled, selecting the curve will also highlight the geometry component that is attached to that curve.
Works only on GS CurveTools curves and geo.
# curveHighlight
If enabled, selected curves and their components will be additionally highlighted for better visibility.
The curves and components will be in X-Ray mode by default.
Colors and transparency values can be changes in the menu below.
# gsSelectedCVColor
Selected CV highlight color
# gsSelectedCVAlpha
Selected CV highlight transparency (alpha)
# gsDeselectedCVColor
Deselected CV highlight color
# gsDeselectedCVAlpha
Deselected CV highlight transparency (alpha)
# curveVisibility
Toggle selected curves highlight
# gsCurveHighlightColor
Selected curve highlight color
# gsCurveHighlightAlpha
Selected curve highlight transparency (alpha)
# hullVisibility
Toggle hull visibility.
Hull is a line that connects all the CVs on the curve.
# gsHullHighlightColor
Hull highlight color
# gsHullHighlightAlpha
Hull highlight transparency (alpha)
# advancedVisibilityFrame
Better highlights for selected curves and components.
# lazyUpdate
Enables lazy update for selected curves.
Lazy update can slightly increase the performance of the highlight,
however it has some visual drawbacks (curve highlight can fail to update when switching curves in component selection mode)
# alwaysOnTop
Toggles X-Ray (always on top) drawing for highlighted components.
Disabling this defeats the purpose of the advanced visibility, but hey, it's your choice.
# curveDistanceColor
Toggles the distance color effect on the curve highlight.
Distance color darkens the curve color the further it is from the camera.
# cvDistanceColor
Toggles the distance color effect on the CV highlight.
Distance color darkens the CVs color the further it is from the camera.
# hullDistanceColor
Toggles the distance color effect on the hull highlight.
Distance color darkens the hull color the further it is from the camera.
# gsDistanceColorMinValue
Distance color minimum.
This value is the minimum allowed color multiplier for the Distance Color effect.
The lower this value, the darker further parts of the curve will be.
Black at 0.0
Original color at 1.0
# gsDistanceColorMaxValue
Distance color maximum.
This value is the maximum allowed color multiplier for the Distance Color effect.
The higher this value, the brighter closest parts of the curve will be.
Black at 0.0
Original color at 1.0
# CVocclusion
Toggles the experimental CV occlusion mode (hull is affected as well)
When the appropriate mesh name is added to Occluder Mesh input field,
this function will automatically hide CVs and hull lines that are behind this mesh (even in X-Ray mode).
Warning: enabling this mode can negatively impart viewport performance.
# gsSelectOccluderButton
This button adds the selected mesh name to the Occluder Mesh input field.
# gsOccluderMeshName
Type the full path for the occluder mesh here, or use the "Select Occluder" button on the left <-
<!-- Layer Customization --->
# gsGenerateLayerColorGradient
Generate a color gradient for the layer colors.
Rows control the number of Rows to generate.
Left color picker sets the initial color.
Right color picker sets the final color.
# gsRandomizeLayerColors
Generate random colors for the layers.
SatMin controls the minimum allowed saturation.
SatMax controls the maximum allowed saturation.
# gsResetAllLayerColors
Resets all the color swatches to the default color.
# gsGetCurrentSceneLayers
Populates the menu with the names and colors stored in the scene.
# gsSetAsCurrentSceneLayers
Applies the names and colors from the menu to the scene.
# gsLoadGlobalLayerPreset
Load the global names and colors preset to the menu.
NOTE: Don't forget to Set to Scene before closing the menu.
# gsSaveGlobalLayerPreset
Saves the current names and colors from the menu to the global preset.
<!-- UV Editor Window --->
# gsUVSelect
Enables the selection of the UVs.
Drag to draw a box selection.
# gsUVMove
Enables the Move tool.
Move the selected UVs or move individual UVs if nothing is selected.
# gsUVRotate
Enables the Rotation of the selected UVs.
Hold LMB and drag anywhere in the viewport to rotate the selected UVs.
Rotation pivot is the center of the individual unscaled UV.
# gsUVScale
Enables the Scaling of the selected UVs.
Hold LMB and drag in the viewport to scale the card Horizontally of Vertically.
Repeated hotkey click will toggle between Horizontal and Vertical scaling.
# gsUVHorizontalScale
Horizontal scaling mode selector.
# gsUVVerticalScale
Vertical scaling mode selector.
# gsDrawUVs
Enables the UVs Drawing Tool:
1. Select UVs using Selection Mode.
2. Enable the UV Drawing Tool.
3. Draw a UV Rectangle anywhere in the viewport to create/move the UVs there.
# gsHorizontalFlipUV
Flips the selected UVs horizontally.
Flipped UVs have the blue circle indicator inside the root rectangle.
# gsVerticalFlipUV
Flips the selected UVs vertically.
# gsResetUVs
Resets the selected UVs to the default 0,1 rectangle.
# gsSyncSelectionUVs
Syncs selection between UV editor and Maya Viewport.
# gsRandomizeUVs
Randomize selected UV positions between already existing UV positions.
***
Normal click will keep the overall density distribution of the UVs.
This means that if there is one card in one position and twenty cards in the other,
it will keep this distribution of 1 to 20.
***
Shift+Click will ignore the original density distribution
and simply randomize the UVs between the original positions.
# gsFocusUVs
Focuses on the selected UVs or on all the UVs if nothing is selected.
# gsUVIsolateSelect
Hides all the unselected UVs and shows only the selected ones.
# gsUVShowAll
Shows all the hidden UVs.
# UVEditorUseTransforms
Use "Coverage" and "Translate Frame" parameters from place2dTexture node for texture.
Offset is not supported.
Diffuse and Alpha channel MUST have the same coverage and translate frame values.
# UVEditorTransparencyToggle
Enable texture map transparency using Alpha map from Transparency plug in the material node
# UVEditorBGColorPicker
Background Color
# UVEditorGridColorPicker
Grid Color
# UVEditorFrameColorPicker
Frame Color
# UVEditorUVFrameSelectedColorPicker
Selected UV frame color
# UVEditorUVFrameDeselectedColorPicker
Deselected UV frame color
# UVEditorUVCardFillColorPicker
UV frame background color
<!-- Card to Curve Window --->
# gsGeoToCurve_outputTypeSwitch
Controls the output of Geo-to-Curve algorithm
# gsGeoToCurve_generateAuto
Automatically determine the final object type (card or tube) based on the selected geometry.
# gsGeoToCurve_generateCards
Generate cards from selected geometry (one-sided cards or tubes)
# gsGeoToCurve_generateTubes
Generate tubes from selected geometry (one-sided cards or tubes)
# gsGeoToCurve_generateCurves
Generate curves from selected geometry (one-sided cards or tubes)
# gsGeoToCurve_cardType
Controls the type of generated objects (Warp or Extrude)
# gsGeoToCurve_warp
Generate Warp cards or tubes
# gsGeoToCurve_extrude
Generate Extrude cards or tubes
# gsGeoToCurve_matchAttributes
Controls which attributes on the new cards/tubes should be approximated from the original geometry.
NOTE: This process is not perfect and final result can be inaccurate.
# gsGeoToCurve_orientation
Match orientation attribute during the generation process
# gsGeoToCurve_width
Match orientation attribute during the generation process
# gsGeoToCurve_taper
Match taper attribute during the generation process
# gsGeoToCurve_twist
Match twist attribute during the generation process
# gsGeoToCurve_profile
Match profile attribute during the generation process
# gsGeoToCurve_material
Copy material (shader) from the original geometry
# gsGeoToCurve_UVs
Tries to approximate the UVs from the original geometry
NOTE: Matches the bounding box of the UVs. Rotated and deformed UVs are not matched precisely.
# gsGeoToCurve_UVMatchOptions
Controls UV matching behaviour
# gsGeoToCurve_verticalFlip
Vertically flip matched UVs
# gsGeoToCurve_horizontalFlip
Horizontally flip matched UVs
# gsGeoToCurve_reverseCurve
Reverse generated curve direction
Root CV should be generated near the scalp of the model.
Enable or disable if resulting card direction and taper are reversed.
# gsGeoToCurve_convertSelected
Convert selected geometry to cards, tubes or curves based on the selected options.
Newly created procedural objects will be placed in the currently selected layer.
NOTE: if the currently selected layer is hidden (grayed out), the newly created objects will be hidden as well.
# gsGeoToCurve_deleteOriginalObject
Delete the original geometry after converting it to cards, tubes or curves.
# gsGeoToCurve_useAimMesh
Use the aim mesh (selected below) to place the root CV of the generated cards, tubes or curves close to the target mesh.
Placing CVs close to the target mesh will help with the alignment process and general adjustments later on.
The aim mesh should be a polygonal mesh.
# gsGeoToCurve_selectAimMesh
Select the aim mesh in the scene and click this button to add its name to the input field.

View File

@@ -51,12 +51,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'},
],
}
@@ -425,7 +427,6 @@ def load_tool_scripts():
_tool_log("[Tool] No scripts configured")
return
global _ADDED_SCRIPT_PATHS
loaded = 0
for cfg in script_configs:
name = cfg.get('name', '')
@@ -689,4 +690,4 @@ def cleanup_on_exit():
_tool_log(f"[Tool] Cleanup error: {e}")
# Register cleanup function
atexit.register(cleanup_on_exit)
atexit.register(cleanup_on_exit)

View File

@@ -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
;
}

View File

@@ -30,7 +30,7 @@ global proc shelf_Nexus_DevTools () {
-overlayLabelColor 0.8 0.8 0.8
-overlayLabelBackColor 0 0 0 0.5
-image "iconviewer.png"
-image1 "iconviewer.png"
-image1 "iconviewer.png"
-style "iconOnly"
-marginWidth 0
-marginHeight 1

View File

@@ -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