Update
BIN
2025/icons/QuadRemesher.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
2025/icons/creaseplus.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
2025/icons/springmagic.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
2
2025/scripts/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -0,0 +1,8 @@
|
||||
1. Unzip all files
|
||||
|
||||
2. copy "springmagic" folder into Maya scripts path, i.e.
|
||||
"C:\Users\YOUR_USER_NAME\Documents\maya\scripts"
|
||||
|
||||
3. Start Maya and run command below in command bar with Python way
|
||||
import springmagic
|
||||
springmagic.main()
|
||||
@@ -0,0 +1,57 @@
|
||||
############################################
|
||||
# Utility Functions
|
||||
############################################
|
||||
|
||||
# to get object parrent
|
||||
# parent = obj.getParent()
|
||||
|
||||
# to get all parents of a joint
|
||||
# parentList = joint.getAllParents()
|
||||
|
||||
# to get root bone
|
||||
# rootBone = joint.root()
|
||||
|
||||
# to get object all children
|
||||
# children = pm.listRelatives(obj, allDescendents = 1)
|
||||
|
||||
# to make sure the selection is a mesh
|
||||
# pm.nodeType(pm.ls(sl=True, type='transform')[0].getShape()) == 'mesh'
|
||||
|
||||
# to get vertex in selection as flatten
|
||||
# pm.ls(sl=True, type='float3', flatten=True)[0]
|
||||
|
||||
# to get skin cluster
|
||||
# pm.listHistory(pm.ls(sl=True), type='skinCluster')[0]
|
||||
|
||||
# to get all influcent bone of a skin cluster
|
||||
# obj.getInfluence()
|
||||
|
||||
# About path module
|
||||
|
||||
# from pymel.util.path import path
|
||||
# filePath = 'c:/temp/test/myTestFile.txt'
|
||||
# fpPathObj = path(filePath)
|
||||
# fpPathObj
|
||||
# # Result: path('c:/temp/test/myTestFile.txt') #
|
||||
# fpPathObj.basename()
|
||||
# # Result: 'myTestFile.txt' #
|
||||
# # .name is a property which returns the same
|
||||
# fpPathObj.name
|
||||
# # Result: 'myTestFile.txt' #
|
||||
# # namebase returns fileName only w/o extension
|
||||
# fpPathObj.namebase
|
||||
# # Result: 'myTestFile' #
|
||||
# # return directory above file
|
||||
# fpPathObj.parent
|
||||
# # Result: path('c:/temp/test') #
|
||||
# # check extension
|
||||
# fpPathObj.endswith('txt')
|
||||
# # Result: True #
|
||||
# # check existance
|
||||
# fpPathObj.exists()
|
||||
# # Result: True #
|
||||
# # check to see if folder type
|
||||
# fpPathObj.parent.isdir()
|
||||
# # Result: True #
|
||||
# fpPathObj.parent.parent.name
|
||||
# # Result: 'temp' #
|
||||
12
2025/scripts/animation_tools/springmagic/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
__version__ = "3.5a"
|
||||
|
||||
from springmagic.main import main
|
||||
|
||||
|
||||
def version():
|
||||
"""
|
||||
Return the current version of the Spring Magic
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return __version__
|
||||
976
2025/scripts/animation_tools/springmagic/core.py
Normal file
@@ -0,0 +1,976 @@
|
||||
# - * - coding: utf - 8 - * -
|
||||
# PEP8 formatting
|
||||
|
||||
#####################################################################################
|
||||
#
|
||||
# Spring Magic for Maya
|
||||
#
|
||||
# Calculate bone chain animation by settings, support collisions and wind force
|
||||
# Can work with rigging controller as well
|
||||
#
|
||||
# Need pringMagic.ui file to work with
|
||||
# This script need also icon file support, which should be put in same folder
|
||||
#
|
||||
# feel free to mail me redtank@outlook.com for any bug or issue
|
||||
#
|
||||
# Yanbin Bai
|
||||
# 2021.02
|
||||
#
|
||||
#####################################################################################
|
||||
|
||||
import math
|
||||
import logging
|
||||
# import copy
|
||||
|
||||
import pymel.core as pm
|
||||
import pymel.core.datatypes as dt
|
||||
import maya.cmds as cmds
|
||||
|
||||
from . import decorators
|
||||
from . import springMath
|
||||
|
||||
from .utility import *
|
||||
|
||||
from itertools import cycle
|
||||
from itertools import chain
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
###################################
|
||||
# spring magic
|
||||
####################################
|
||||
|
||||
kWindObjectName = 'spring_wind'
|
||||
kSpringProxySuffix = '_SpringProxy'
|
||||
kCollisionPlaneSuffix = '_SpringColPlane'
|
||||
kCapsuleNameSuffix = '_collision_capsule'
|
||||
kNullSuffix = '_SpringNull'
|
||||
|
||||
|
||||
# kTwistNullSuffix = '_SpringTwistNull'
|
||||
|
||||
|
||||
class Spring:
|
||||
|
||||
def __init__(self, ratio=0.5, twistRatio=0.0, tension=0.0, extend=0.0, inertia=0.0):
|
||||
self.ratio = ratio
|
||||
self.twist_ratio = twistRatio
|
||||
self.tension = tension
|
||||
self.extend = extend
|
||||
self.inertia = inertia
|
||||
|
||||
|
||||
class SpringMagic:
|
||||
|
||||
def __init__(self, startFrame, endFrame, subDiv=1.0, isLoop=False, isPoseMatch=False, isCollision=False,
|
||||
isFastMove=False, wipeSubframe=True):
|
||||
self.start_frame = startFrame
|
||||
self.end_frame = endFrame
|
||||
|
||||
self.sub_div = subDiv
|
||||
self.is_loop = isLoop
|
||||
self.is_pose_match = isPoseMatch
|
||||
self.is_fast_move = isFastMove
|
||||
self.wipe_subframe = wipeSubframe
|
||||
|
||||
self.is_collision = isCollision
|
||||
self.collision_planes_list = None
|
||||
|
||||
self.wind = None
|
||||
|
||||
|
||||
class SpringData:
|
||||
cur_position_locator = None
|
||||
prev_position_locator = None
|
||||
prev_grand_child_position_locator = None
|
||||
|
||||
_instances = WeakValueDictionary()
|
||||
|
||||
@property
|
||||
def Count(self):
|
||||
return len(self._instances)
|
||||
|
||||
def __init__(self, springMagic, spring, transform, child, grand_child, grand_parent):
|
||||
# self.current_child_position
|
||||
|
||||
self._instances[id(self)] = self
|
||||
|
||||
self.springMagic = springMagic
|
||||
self.spring = spring
|
||||
|
||||
self.parent = transform
|
||||
self.child = child
|
||||
self.grand_child = grand_child
|
||||
self.grand_parent = grand_parent
|
||||
|
||||
self.child_position = get_translation(child)
|
||||
self.grand_child_position = get_translation(grand_child) if grand_child else None
|
||||
|
||||
self.previous_child_position = self.child_position
|
||||
|
||||
self.rotation = get_rotation(transform)
|
||||
self.up_vector = get_matrix(transform)[4:7]
|
||||
|
||||
transform_pos = get_translation(transform)
|
||||
self.bone_length = springMath.distance(transform_pos, self.child_position)
|
||||
|
||||
self.has_child_collide = False
|
||||
self.has_plane_collide = False
|
||||
|
||||
# create temporary locators use for aim constraint
|
||||
if not SpringData.cur_position_locator:
|
||||
SpringData.cur_position_locator = pm.spaceLocator(name='cur_position_locator')
|
||||
|
||||
if not SpringData.prev_position_locator:
|
||||
SpringData.prev_position_locator = pm.spaceLocator(name='prev_position_locator')
|
||||
|
||||
if not SpringData.prev_grand_child_position_locator:
|
||||
SpringData.prev_grand_child_position_locator = pm.spaceLocator(name='prev_grand_child_position_locator')
|
||||
|
||||
# Weight attribute to de/activate the aim constraint in pose match mode
|
||||
self.pairblend_weight_attribute = None
|
||||
self.aim_constraint = None
|
||||
|
||||
self.__create_child_proxy()
|
||||
self.__prepare_animation_key()
|
||||
self.__create_aim_constraint()
|
||||
self.__init_pairblend_weight()
|
||||
|
||||
def __del__(self):
|
||||
|
||||
if self.Count == 0:
|
||||
# print('Last Counter object deleted')
|
||||
|
||||
# delete temporary locators (useful, it's delete constraints at the same time)
|
||||
pm.delete(SpringData.cur_position_locator, SpringData.prev_position_locator,
|
||||
SpringData.prev_grand_child_position_locator)
|
||||
|
||||
SpringData.cur_position_locator = None
|
||||
SpringData.prev_position_locator = None
|
||||
SpringData.prev_grand_child_position_locator = None
|
||||
|
||||
# remove all spring nulls, add recursive incase name spaces
|
||||
# pm.delete(pm.ls('*' + kNullSuffix + '*', recursive=1))
|
||||
else:
|
||||
# print(self.Count, 'Counter objects remaining')
|
||||
pass
|
||||
|
||||
if self.child_proxy:
|
||||
# remove spring nulls, add recursive incase name spaces
|
||||
pm.delete(pm.ls('*' + self.child_proxy + '*', recursive=1))
|
||||
|
||||
def update(self, has_collision, has_hit_plane, child_pos_corrected):
|
||||
# Update current transform with the new values
|
||||
self.child_position = get_translation(self.child)
|
||||
self.grand_child_position = get_translation(self.grand_child) if self.grand_child else None
|
||||
self.previous_child_position = child_pos_corrected
|
||||
|
||||
self.rotation = get_rotation(self.parent)
|
||||
self.up_vector = get_matrix(self.parent)[4:7]
|
||||
|
||||
self.has_child_collide = has_collision
|
||||
self.has_plane_collide = has_hit_plane
|
||||
|
||||
def __create_child_proxy(self):
|
||||
# create a null at child pos, then parent to obj parent for calculation
|
||||
child_proxy_locator_name = self.parent.name() + kNullSuffix
|
||||
child_proxy_list = pm.ls(child_proxy_locator_name)
|
||||
|
||||
if not child_proxy_list:
|
||||
self.child_proxy = pm.spaceLocator(name=child_proxy_locator_name)
|
||||
else:
|
||||
self.child_proxy = child_proxy_list[0]
|
||||
|
||||
self.child_proxy.getShape().setAttr('visibility', False)
|
||||
|
||||
pm.parent(self.child_proxy, self.parent.getParent())
|
||||
# pm.parent(child_proxy, self.grand_parent)
|
||||
|
||||
if not self.springMagic.is_pose_match:
|
||||
self.child_proxy.setTranslation(self.child.getTranslation(space='world'), space='world')
|
||||
self.child_proxy.setRotation(self.child.getRotation(space='world'), space='world')
|
||||
|
||||
def __prepare_animation_key(self):
|
||||
if not self.springMagic.is_pose_match:
|
||||
# remove exists keys
|
||||
pm.cutKey(self.parent, time=(self.springMagic.start_frame, self.springMagic.end_frame + 0.99999))
|
||||
pm.cutKey(self.child, time=(self.springMagic.start_frame, self.springMagic.end_frame + 0.99999))
|
||||
|
||||
# set key
|
||||
pm.setKeyframe(self.parent, attribute='rotate')
|
||||
|
||||
if self.spring.extend != 0.0:
|
||||
pm.setKeyframe(self.child, attribute='tx')
|
||||
|
||||
def __create_aim_constraint(self):
|
||||
# Create a constraint per transform to speed up computation, not active yet (weight=0)
|
||||
self.aim_constraint = pm.aimConstraint(SpringData.cur_position_locator, SpringData.prev_position_locator,
|
||||
SpringData.prev_grand_child_position_locator, self.parent,
|
||||
aimVector=[1, 0, 0], upVector=[0, 1, 0], maintainOffset=False, weight=0)
|
||||
|
||||
def __init_pairblend_weight(self):
|
||||
# if transform rotation has no animation, set a key at start frame to force the creation of a pairblend when the aim constraint is created
|
||||
for rotation_input in ['rx', 'ry', 'rz']:
|
||||
rotation_connection = pm.listConnections(self.parent + '.' + rotation_input, d=False, s=True)
|
||||
|
||||
if not rotation_connection:
|
||||
pm.setKeyframe(self.parent, attribute=rotation_input)
|
||||
|
||||
pairblends = pm.listConnections(self.parent, type="pairBlend", destination=True, skipConversionNodes=True)
|
||||
|
||||
# Find the pairblend connected to the aim constraint
|
||||
for pairblend in pairblends:
|
||||
|
||||
connected_constraint_list = cmds.listConnections(pairblend.name(), type='constraint', destination=False)
|
||||
|
||||
if self.aim_constraint.name() in connected_constraint_list:
|
||||
|
||||
# Get pairblend weight connected attribute
|
||||
# return [u'joint2.blendAim1')]
|
||||
weight_attribute_list = cmds.listConnections(pairblend + '.weight', d=False, s=True, p=True)
|
||||
|
||||
if weight_attribute_list:
|
||||
self.pairblend_weight_attribute = weight_attribute_list[0]
|
||||
|
||||
def set_pairblend_weight(self, blend_value):
|
||||
if self.pairblend_weight_attribute:
|
||||
pm.setAttr(self.pairblend_weight_attribute, blend_value)
|
||||
|
||||
def keyframe_child_proxy(self):
|
||||
|
||||
if self.child_proxy:
|
||||
# Deactivate pairblend weight
|
||||
# Aim constraint weight set to 0 is not enough, it paratizes the process
|
||||
self.set_pairblend_weight(0.0)
|
||||
|
||||
self.child_proxy.setTranslation(self.child.getTranslation(space='world'), space='world')
|
||||
pm.setKeyframe(self.child_proxy, attribute='translate')
|
||||
self.child_proxy.setRotation(self.child.getRotation(space='world'), space='world')
|
||||
pm.setKeyframe(self.child_proxy, attribute='rotate')
|
||||
|
||||
self.set_pairblend_weight(1.0)
|
||||
|
||||
def apply_inertia(self, currentChildPosition):
|
||||
ratio = self.spring.ratio / self.springMagic.sub_div
|
||||
inertia_offset = [0.0, 0.0, 0.0]
|
||||
|
||||
if self.spring.inertia > 0.0:
|
||||
bone_ref_loc_offset_dir = currentChildPosition - self.child_position
|
||||
bone_ref_loc_offset_distance = (
|
||||
(bone_ref_loc_offset_dir) * (1 - ratio) * (1 - self.spring.inertia)).length()
|
||||
|
||||
inertia_offset = bone_ref_loc_offset_dir.normal() * (
|
||||
bone_ref_loc_offset_distance / self.springMagic.sub_div)
|
||||
|
||||
# apply mass
|
||||
force_direction = self.child_position - self.previous_child_position
|
||||
force_distance = force_direction.length() * self.spring.inertia
|
||||
|
||||
# offset position
|
||||
inertia_offset += force_direction.normal() * (force_distance / self.springMagic.sub_div)
|
||||
|
||||
return inertia_offset
|
||||
|
||||
def apply_wind(self, frame):
|
||||
wind_offset = [0.0, 0.0, 0.0]
|
||||
|
||||
if self.springMagic.wind:
|
||||
wind_max_force = self.springMagic.wind.getAttr('MaxForce')
|
||||
wind_min_force = self.springMagic.wind.getAttr('MinForce')
|
||||
wind_frequency = self.springMagic.wind.getAttr('Frequency')
|
||||
|
||||
mid_force = (wind_max_force + wind_min_force) / 2
|
||||
|
||||
# get source x - axis direction in world space
|
||||
wind_direction = get_matrix(self.springMagic.wind)[:3]
|
||||
# sDirection = sObj.getMatrix()[0][:3]
|
||||
wind_direction = dt.Vector(wind_direction[0], wind_direction[1], wind_direction[2]).normal()
|
||||
wind_distance = math.sin(frame * wind_frequency) * (wind_max_force - wind_min_force) + mid_force
|
||||
|
||||
# offset position
|
||||
wind_offset = wind_direction.normal() * wind_distance
|
||||
|
||||
return wind_offset
|
||||
|
||||
def detect_collision(self, new_obj_pos, new_child_pos, capsule_list):
|
||||
col_pre = col_cur = None
|
||||
|
||||
child_pos_corrected = self.child_position
|
||||
|
||||
if self.springMagic.is_collision and capsule_list:
|
||||
|
||||
if preCheckCollision(new_obj_pos, self.bone_length, capsule_list):
|
||||
|
||||
# check collision from previous pos to cur pos
|
||||
col_pre, col_body_pre, hitCylinder_pre = springMath.checkCollision(new_child_pos, self.child_position,
|
||||
capsule_list, True)
|
||||
|
||||
# check collision from cur pos to previous pos
|
||||
col_cur, col_body_cur, hitCylinder_cur = springMath.checkCollision(new_child_pos, self.child_position,
|
||||
capsule_list, False)
|
||||
|
||||
if col_pre and (col_cur is None):
|
||||
new_child_pos = col_pre
|
||||
elif col_cur and (col_pre is None):
|
||||
child_pos_corrected = col_cur
|
||||
elif col_pre and col_cur:
|
||||
|
||||
# move cur child pose to closest out point if both pre and cur pos are already inside of col body
|
||||
# if distance(col_pre, new_child_pos) < distance(col_cur, new_child_pos):
|
||||
mid_point = (self.child_position + new_child_pos) / 2
|
||||
|
||||
if springMath.distance(col_pre, mid_point) < springMath.distance(col_cur, mid_point):
|
||||
new_child_pos = col_pre
|
||||
else:
|
||||
new_child_pos = col_cur
|
||||
|
||||
if self.springMagic.is_fast_move:
|
||||
child_pos_corrected = new_child_pos
|
||||
|
||||
# # draw debug locator
|
||||
# if col_pre and col_cur:
|
||||
# locator1 = pm.spaceLocator(name=obj.name() + '_col_pre_locator_' + str(i))
|
||||
# locator1.setTranslation(col_pre)
|
||||
# locator1 = pm.spaceLocator(name=obj.name() + '_col_cur_locator_' + str(i))
|
||||
# locator1.setTranslation(col_cur)
|
||||
|
||||
return True if col_pre or col_cur else False, new_child_pos, child_pos_corrected
|
||||
|
||||
def detect_plane_hit(self, new_obj_pos, new_child_pos, grand_parent_has_plane_collision):
|
||||
has_hit_plane = False
|
||||
|
||||
if self.springMagic.is_collision and self.springMagic.collision_planes_list[0]:
|
||||
collision_plane = self.springMagic.collision_planes_list[0]
|
||||
has_plane_collision = springMath.checkPlaneCollision(new_obj_pos, new_child_pos, collision_plane)
|
||||
|
||||
if has_plane_collision or grand_parent_has_plane_collision:
|
||||
new_child_pos = repeatMoveToPlane(self.parent, new_child_pos, self.child, collision_plane, 3)
|
||||
has_hit_plane = True
|
||||
|
||||
return has_hit_plane, new_child_pos
|
||||
|
||||
# calculate upvector by interpolation y axis for twist
|
||||
def compute_up_vector(self):
|
||||
twist_ratio = self.spring.twist_ratio / self.springMagic.sub_div
|
||||
|
||||
cur_obj_yAxis = get_matrix(self.child_proxy)[4:7]
|
||||
prev_up_vector = dt.Vector(self.up_vector[0], self.up_vector[1], self.up_vector[2]).normal()
|
||||
cur_up_vector = dt.Vector(cur_obj_yAxis[0], cur_obj_yAxis[1], cur_obj_yAxis[2]).normal()
|
||||
|
||||
up_vector = (prev_up_vector * (1 - twist_ratio)) + (cur_up_vector * twist_ratio)
|
||||
|
||||
return up_vector
|
||||
|
||||
def aim_by_ratio(self, upVector, newChildPos, childPosCorrected):
|
||||
ratio = self.spring.ratio / self.springMagic.sub_div
|
||||
tension = self.spring.tension / (1.0 / (springMath.sigmoid(1 - self.springMagic.sub_div) + 0.5))
|
||||
|
||||
# print("obj: " + str(self.parent.name()))
|
||||
# print("newChildPos: " + str(newChildPos))
|
||||
# print("childPosCorrected: " + str(childPosCorrected))
|
||||
# print("grand_child_position: " + str(self.grand_child_position))
|
||||
# print("upVector: " + str(upVector))
|
||||
# print("ratio: " + str(ratio))
|
||||
# print("tension: " + str(tension))
|
||||
|
||||
SpringData.cur_position_locator.setTranslation(newChildPos)
|
||||
SpringData.prev_position_locator.setTranslation(childPosCorrected)
|
||||
|
||||
pm.aimConstraint(self.parent, e=True, worldUpVector=upVector)
|
||||
|
||||
pm.aimConstraint(SpringData.cur_position_locator, self.parent, e=True, w=ratio)
|
||||
pm.aimConstraint(SpringData.prev_position_locator, self.parent, e=True, w=1 - ratio)
|
||||
|
||||
if self.has_child_collide and self.grand_child_position and tension != 0:
|
||||
SpringData.prev_grand_child_position_locator.setTranslation(self.grand_child_position)
|
||||
pm.aimConstraint(SpringData.prev_grand_child_position_locator, self.parent, e=True, w=(1 - ratio) * tension)
|
||||
|
||||
pm.setKeyframe(self.parent, attribute='rotate')
|
||||
|
||||
pm.aimConstraint(SpringData.cur_position_locator, SpringData.prev_position_locator,
|
||||
SpringData.prev_grand_child_position_locator, self.parent, e=True, w=0.0)
|
||||
|
||||
def extend_bone(self, childPosCorrected):
|
||||
if self.spring.extend != 0.0:
|
||||
child_translation = self.child.getTranslation()
|
||||
# get length between bone pos and child pos
|
||||
x2 = (childPosCorrected - get_translation(self.parent)).length()
|
||||
x3 = (self.bone_length * (1 - self.spring.extend)) + (x2 * self.spring.extend)
|
||||
self.child.setTranslation([x3, child_translation[1], child_translation[2]])
|
||||
pm.setKeyframe(self.child, attribute='tx')
|
||||
# else:
|
||||
# self.child.setTranslation([self.bone_length, child_translation[1], child_translation[2]])
|
||||
|
||||
|
||||
def createCollisionPlane():
|
||||
# remove exist plane
|
||||
collision_plane = get_node('*' + kCollisionPlaneSuffix + '*')
|
||||
|
||||
if collision_plane:
|
||||
pm.delete(collision_plane)
|
||||
|
||||
collision_plane = pm.polyPlane(name="the" + kCollisionPlaneSuffix, sx=1, sy=1, w=10, h=10, ch=1)[0]
|
||||
|
||||
# one side display
|
||||
pm.setAttr(collision_plane.doubleSided, False)
|
||||
|
||||
# lock scale
|
||||
pm.setAttr(collision_plane.sx, lock=True)
|
||||
pm.setAttr(collision_plane.sy, lock=True)
|
||||
pm.setAttr(collision_plane.sz, lock=True)
|
||||
|
||||
pm.select(collision_plane)
|
||||
|
||||
|
||||
def removeBody(clear=False):
|
||||
cylinder_list = getCapsule(clear)
|
||||
|
||||
pm.delete(cylinder_list)
|
||||
|
||||
collision_plane = get_node('*' + kCollisionPlaneSuffix + '*')
|
||||
|
||||
if collision_plane:
|
||||
pm.delete(collision_plane)
|
||||
|
||||
|
||||
def addWindObj():
|
||||
windCone = pm.cone(name=kWindObjectName)[0]
|
||||
|
||||
windCone.setScale([5, 5, 5])
|
||||
|
||||
pm.delete(windCone, constructionHistory=1)
|
||||
|
||||
# add wind attr
|
||||
pm.addAttr(windCone, longName='MaxForce', attributeType='float')
|
||||
pm.setAttr(windCone.name() + '.MaxForce', 1, e=1, keyable=1)
|
||||
pm.addAttr(windCone, longName='MinForce', attributeType='float')
|
||||
pm.setAttr(windCone.name() + '.MinForce', 0.5, e=1, keyable=1)
|
||||
pm.addAttr(windCone, longName='Frequency', attributeType='float')
|
||||
pm.setAttr(windCone.name() + '.Frequency', 1, e=1, keyable=1)
|
||||
# pm.addAttr(windCone, longName='Wave', attributeType='float')
|
||||
# pm.setAttr(windCone.name() + '.Wave', 0.5, e=1, keyable=1)
|
||||
|
||||
setWireShading(windCone, False)
|
||||
|
||||
pm.makeIdentity(apply=True)
|
||||
windCone.setRotation([0, 0, 90])
|
||||
|
||||
|
||||
def bindControls(linked_chains=False):
|
||||
selected_ctrls = pm.ls(sl=True)
|
||||
pm.select(clear=True)
|
||||
|
||||
# The chains are linked, we can sort them
|
||||
if linked_chains:
|
||||
# Create list for every ctrls chains
|
||||
# ie [[ctrl1, ctrl1.1, ctrl1.2], [ctrl2, ctrl2.1, ctrl2.2, ctrl2.3]]
|
||||
all_ctrls_descendants_list = pm.listRelatives(selected_ctrls, allDescendents=True)
|
||||
top_hierarchy_ctrls_list = [x for x in selected_ctrls if x not in all_ctrls_descendants_list]
|
||||
|
||||
ctrls_chains_list = [[x] + [y for y in pm.listRelatives(x, allDescendents=True) if y in selected_ctrls][::-1]
|
||||
for x in top_hierarchy_ctrls_list]
|
||||
# No sorting possible because the controlers have no lineage
|
||||
else:
|
||||
ctrls_chains_list = [selected_ctrls]
|
||||
|
||||
proxy_joint_chain_list = []
|
||||
|
||||
for ctrls_list in ctrls_chains_list:
|
||||
|
||||
proxy_joint_list = []
|
||||
|
||||
for ctrl in ctrls_list:
|
||||
# create proxy joint in ctrl world position
|
||||
ctrl_position = pm.xform(ctrl, worldSpace=1, rp=1, q=1)
|
||||
|
||||
proxyJoint = pm.joint(name=ctrl.name() + kSpringProxySuffix, position=ctrl_position, radius=0.2, roo='xyz')
|
||||
proxy_joint_list.append(proxyJoint)
|
||||
|
||||
for joint in proxy_joint_list:
|
||||
# set joint orientation
|
||||
pm.joint(joint, edit=1, orientJoint='xyz', zeroScaleOrient=True)
|
||||
|
||||
# Straight bones alignment
|
||||
joint.setRotation([0, 0, 0])
|
||||
joint.setAttr('rotateAxis', [0, 0, 0])
|
||||
joint.setAttr('jointOrient', [0, 0, 0])
|
||||
|
||||
# Free rotation (move rotation values to joint orient values)
|
||||
# pm.makeIdentity(proxy_joint_list[idx], apply=True, t=False, r=True, s=False, pn=True)
|
||||
|
||||
if proxy_joint_list:
|
||||
# parent root proxy joint to control parent
|
||||
pm.parent(proxy_joint_list[0], ctrls_list[0].getParent())
|
||||
|
||||
# Necessary to start a new joint chain
|
||||
pm.select(clear=True)
|
||||
|
||||
proxy_joint_chain_list += [proxy_joint_list]
|
||||
|
||||
for idx, joint in enumerate(proxy_joint_list[:-1]):
|
||||
# orient joint chain
|
||||
cns = pm.aimConstraint(ctrls_list[idx + 1], proxy_joint_list[idx], aimVector=[1, 0, 0], upVector=[0, 0, 0],
|
||||
worldUpVector=[0, 1, 0], skip='x')
|
||||
pm.delete(cns)
|
||||
|
||||
for idx, joint in enumerate(proxy_joint_list):
|
||||
pm.parentConstraint(proxy_joint_list[idx], ctrls_list[idx], maintainOffset=True)
|
||||
|
||||
pm.select(proxy_joint_chain_list)
|
||||
|
||||
|
||||
def clearBind(startFrame, endFrame):
|
||||
proxyJointLst = pm.ls(sl=True)
|
||||
pm.select(d=True)
|
||||
|
||||
ctrlList = []
|
||||
|
||||
for bone in proxyJointLst:
|
||||
ctrl = pm.ls(bone.name().split(kSpringProxySuffix)[0])[0]
|
||||
ctrlList.append(ctrl)
|
||||
|
||||
if ctrlList:
|
||||
pm.bakeResults(*ctrlList, t=(startFrame, endFrame))
|
||||
|
||||
pm.delete(proxyJointLst)
|
||||
|
||||
|
||||
def bindPose():
|
||||
pm.runtime.GoToBindPose()
|
||||
|
||||
|
||||
# Prepare all information to call SpringMagicMaya function
|
||||
def startCompute(spring, springMagic, progression_callback=None):
|
||||
autokeyframe_state = cmds.autoKeyframe(query=True, state=True)
|
||||
cmds.autoKeyframe(state=False)
|
||||
|
||||
# get selection obj
|
||||
objs = pm.ls(sl=True)
|
||||
|
||||
# check objects validity
|
||||
for obj in objs:
|
||||
# has duplicate name obj
|
||||
nameCntErr = (len(pm.ls(obj.name())) > 1)
|
||||
|
||||
# is a duplicate obj
|
||||
nameValidErr = (obj.name().find('|') > 0)
|
||||
|
||||
if nameCntErr or nameValidErr:
|
||||
raise ValueError(obj.name() + ' has duplicate name object! Stopped!')
|
||||
|
||||
obj_translation = obj.getTranslation()
|
||||
|
||||
if (obj_translation[0] < 0 or abs(obj_translation[1]) > 0.001 or abs(
|
||||
obj_translation[2]) > 0.001) and obj.getParent() and (obj.getParent() in objs):
|
||||
pm.warning(obj.getParent().name() + "'s X axis not point to child! May get broken result!")
|
||||
|
||||
# Search for collision objects
|
||||
if springMagic.is_collision:
|
||||
springMagic.collision_planes_list = [get_node('*' + kCollisionPlaneSuffix + '*')]
|
||||
|
||||
# Search for a wind object
|
||||
if pm.ls(kWindObjectName):
|
||||
springMagic.wind = pm.ls(kWindObjectName)[0]
|
||||
|
||||
SpringMagicMaya(objs, spring, springMagic, progression_callback)
|
||||
|
||||
cmds.autoKeyframe(state=autokeyframe_state)
|
||||
|
||||
|
||||
# @decorators.viewportOff
|
||||
@decorators.gShowProgress(status="SpringMagic does his magic")
|
||||
def SpringMagicMaya(objs, spring, springMagic, progression_callback=None):
|
||||
# on each frame go through all objs and do:
|
||||
# 1. make a vectorA from current obj position to previous child position
|
||||
# 2. make a vectorB from current obj position to current child position
|
||||
# 3. calculate the angle between two vectors
|
||||
# 4. rotate the obj towards vectorA base on spring value
|
||||
|
||||
start_frame = springMagic.start_frame
|
||||
end_frame = springMagic.end_frame
|
||||
sub_div = springMagic.sub_div
|
||||
|
||||
# remove all spring nulls, add recursive incase name spaces
|
||||
pm.delete(pm.ls('*' + kNullSuffix + '*', recursive=True))
|
||||
|
||||
# get all capsules in scene
|
||||
capsule_list = getCapsule(True) if springMagic.is_collision else None
|
||||
|
||||
if progression_callback:
|
||||
progression_callback(0)
|
||||
|
||||
# Save object previous frame information in a ordered dict
|
||||
spring_data_dict = OrderedDict()
|
||||
|
||||
# Initialize data on the first frame
|
||||
pm.currentTime(start_frame, edit=True)
|
||||
|
||||
# Create a list of objects chains
|
||||
# ie [[nt.Joint(u'joint1'), nt.Joint(u'joint2'), nt.Joint(u'joint4')], [nt.Joint(u'joint7'), nt.Joint(u'joint8'), nt.Joint(u'joint10')]]
|
||||
all_joints_descendants_list = pm.listRelatives(objs, allDescendents=True, type='transform')
|
||||
top_hierarchy_joints_list = [x for x in objs if x not in all_joints_descendants_list]
|
||||
|
||||
# transforms_chains_list = map(lambda x: [x] + [y for y in pm.listRelatives(x, allDescendents=True) if y in objs][::-1], top_hierarchy_joints_list)
|
||||
|
||||
# Deal with the specific case of root bone with no parent.
|
||||
# The root bone is considered the driver, so we remove it from the calculation.
|
||||
transforms_chains_list = [
|
||||
([x] if x.getParent() else []) + [y for y in pm.listRelatives(x, allDescendents=True) if y in objs][::-1] for x
|
||||
in top_hierarchy_joints_list]
|
||||
|
||||
# Remove empty lists
|
||||
transforms_chains_list = [x for x in transforms_chains_list if x != []]
|
||||
|
||||
# Create progression bar generator values
|
||||
number_of_progession_step = 0
|
||||
|
||||
if springMagic.is_pose_match:
|
||||
number_of_progession_step += end_frame - start_frame + 1
|
||||
|
||||
if springMagic.is_loop:
|
||||
# Doesn't process the first frame on the first loop
|
||||
number_of_progession_step += ((end_frame - start_frame) * 2 + 1) * sub_div
|
||||
else:
|
||||
# Doesn't process the first frame
|
||||
number_of_progession_step += (end_frame - start_frame) * sub_div
|
||||
|
||||
progression_increment = 100.0 / number_of_progession_step
|
||||
progression_generator = frange(progression_increment, 100.0 + progression_increment, progression_increment)
|
||||
|
||||
# Create spring data for each transforms at start frame
|
||||
for transforms_chain in transforms_chains_list:
|
||||
|
||||
if SpringMagicMaya.isInterrupted():
|
||||
break
|
||||
|
||||
transforms_cycle = cycle(transforms_chain)
|
||||
|
||||
# Prime the pump
|
||||
parent = first_transform = next(transforms_cycle)
|
||||
grand_parent = parent.getParent()
|
||||
child = next(transforms_cycle)
|
||||
grand_child = next(transforms_cycle)
|
||||
|
||||
# skip end bone
|
||||
for transform in transforms_chain[:-1]:
|
||||
|
||||
if SpringMagicMaya.isInterrupted():
|
||||
break
|
||||
|
||||
# End of cycle iteration
|
||||
if grand_child == first_transform:
|
||||
grand_child = None
|
||||
|
||||
spring_data_dict[parent.name()] = SpringData(springMagic, spring, parent, child, grand_child, grand_parent)
|
||||
|
||||
grand_parent, parent, child, grand_child = parent, child, grand_child, next(transforms_cycle)
|
||||
|
||||
# Save joints position over timeline
|
||||
# Parse timeline just one time
|
||||
if springMagic.is_pose_match:
|
||||
for frame in range(0, end_frame - start_frame + 1):
|
||||
|
||||
if SpringMagicMaya.isInterrupted():
|
||||
break
|
||||
|
||||
pm.currentTime(start_frame + frame, edit=True)
|
||||
|
||||
for spring_data in list(spring_data_dict.values()):
|
||||
|
||||
if not SpringMagicMaya.isInterrupted():
|
||||
spring_data.keyframe_child_proxy()
|
||||
|
||||
progression = next(progression_generator)
|
||||
progression = clamp(progression, 0, 100)
|
||||
|
||||
if progression_callback:
|
||||
progression_callback(progression)
|
||||
|
||||
SpringMagicMaya.progress(progression)
|
||||
|
||||
# Generate frame index
|
||||
# Skip first frame on first calculation pass
|
||||
frame_increment = 1.0 / sub_div
|
||||
frame_generator = frange(frame_increment, end_frame - start_frame + frame_increment, frame_increment)
|
||||
|
||||
# On second calculation pass compute first frame
|
||||
if springMagic.is_loop:
|
||||
frame_generator = chain(frame_generator, frange(0, end_frame - start_frame + frame_increment, frame_increment))
|
||||
|
||||
for frame in frame_generator:
|
||||
|
||||
# print('Frame: ' + str(frame))
|
||||
|
||||
if SpringMagicMaya.isInterrupted():
|
||||
break
|
||||
|
||||
pm.currentTime(start_frame + frame, edit=True)
|
||||
|
||||
for previous_frame_spring_data in list(spring_data_dict.values()):
|
||||
|
||||
if SpringMagicMaya.isInterrupted():
|
||||
break
|
||||
|
||||
grand_parent_spring_data = None
|
||||
if previous_frame_spring_data.grand_parent and previous_frame_spring_data.grand_parent.name() in list(
|
||||
spring_data_dict.keys()):
|
||||
grand_parent_spring_data = spring_data_dict[previous_frame_spring_data.grand_parent.name()]
|
||||
|
||||
# get current position of parent and child
|
||||
parent_pos = get_translation(previous_frame_spring_data.parent)
|
||||
|
||||
# print("obj: " + str(previous_frame_spring_data.parent.name()))
|
||||
|
||||
new_child_pos = get_translation(previous_frame_spring_data.child_proxy)
|
||||
|
||||
# Apply inertia
|
||||
new_child_pos += previous_frame_spring_data.apply_inertia(new_child_pos)
|
||||
|
||||
# apply wind
|
||||
new_child_pos += previous_frame_spring_data.apply_wind(start_frame + frame)
|
||||
|
||||
# detect collision
|
||||
has_collision, new_child_pos, child_pos_corrected = previous_frame_spring_data.detect_collision(parent_pos,
|
||||
new_child_pos,
|
||||
capsule_list)
|
||||
|
||||
# detect plane collision
|
||||
grand_parent_has_plane_collision = False
|
||||
if grand_parent_spring_data:
|
||||
grand_parent_has_plane_collision = grand_parent_spring_data.has_plane_collide
|
||||
|
||||
has_hit_plane, new_child_pos = previous_frame_spring_data.detect_plane_hit(parent_pos, new_child_pos,
|
||||
grand_parent_has_plane_collision)
|
||||
|
||||
# calculate upvector by interpolation y axis for twist
|
||||
up_vector = previous_frame_spring_data.compute_up_vector()
|
||||
|
||||
# apply aim constraint to do actual rotation
|
||||
previous_frame_spring_data.aim_by_ratio(up_vector, new_child_pos, child_pos_corrected)
|
||||
|
||||
# Extend bone if needed (update child translation)
|
||||
previous_frame_spring_data.extend_bone(child_pos_corrected)
|
||||
|
||||
# Update current transform with the new values
|
||||
previous_frame_spring_data.update(has_collision, has_hit_plane, child_pos_corrected)
|
||||
|
||||
# Update the grand parent has_child_collide value
|
||||
if grand_parent_spring_data:
|
||||
grand_parent_spring_data.has_child_collide = has_collision
|
||||
|
||||
progression = next(progression_generator)
|
||||
progression = clamp(progression, 0, 100)
|
||||
|
||||
if progression_callback:
|
||||
progression_callback(progression)
|
||||
|
||||
SpringMagicMaya.progress(progression)
|
||||
|
||||
# bake result on frame
|
||||
if springMagic.wipe_subframe and not SpringMagicMaya.isInterrupted():
|
||||
transform_to_bake_list = [spring_data.parent for spring_data in list(spring_data_dict.values())]
|
||||
|
||||
# Deactivate all pairblend otherwise bake doesn't work with animation layers
|
||||
for spring_data in list(spring_data_dict.values()):
|
||||
spring_data.set_pairblend_weight(0.0)
|
||||
|
||||
bakeAnim(transform_to_bake_list, start_frame, end_frame)
|
||||
|
||||
|
||||
def bakeAnim(objList, startFrame, endFrame):
|
||||
pm.bakeResults(
|
||||
objList,
|
||||
t=(startFrame, endFrame),
|
||||
sampleBy=1,
|
||||
disableImplicitControl=False,
|
||||
preserveOutsideKeys=True,
|
||||
sparseAnimCurveBake=False,
|
||||
removeBakedAttributeFromLayer=False,
|
||||
bakeOnOverrideLayer=False,
|
||||
minimizeRotation=True,
|
||||
shape=False,
|
||||
simulation=False)
|
||||
|
||||
|
||||
SM_boneTransformDict = {}
|
||||
|
||||
|
||||
def copyBonePose():
|
||||
global SM_boneTransformDict
|
||||
|
||||
for obj in pm.ls(sl=True):
|
||||
SM_boneTransformDict[obj] = [obj.getTranslation(), obj.getRotation()]
|
||||
|
||||
|
||||
def pasteBonePose():
|
||||
global SM_boneTransformDict
|
||||
|
||||
for obj in pm.ls(sl=True):
|
||||
if obj in list(SM_boneTransformDict.keys()):
|
||||
logging.debug(SM_boneTransformDict[obj][0])
|
||||
|
||||
obj.setTranslation(SM_boneTransformDict[obj][0])
|
||||
obj.setRotation(SM_boneTransformDict[obj][1])
|
||||
|
||||
|
||||
def preCheckCollision(objPos, objLength, capsuleList):
|
||||
# print('objPos:' + str(objPos))
|
||||
# print('objLength:' + str(objLength))
|
||||
|
||||
# pre check bone length compare with collision body radius
|
||||
# will improve performance if bone is far from capsule
|
||||
for capsule in capsuleList:
|
||||
capsule_children_list = pm.listRelatives(capsule, children=1, type='transform')
|
||||
|
||||
p = capsule_children_list[0].getTranslation(space='world')
|
||||
q = capsule_children_list[1].getTranslation(space='world')
|
||||
r = capsule.getAttr('scaleZ')
|
||||
|
||||
bone_to_capsule_distance = springMath.dist_to_line(p, q, objPos)
|
||||
|
||||
# print('p:' + str(p))
|
||||
# print('q:' + str(q))
|
||||
# print('r:' + str(r))
|
||||
# print('boneToCapsuleDistance:' + str(bone_to_capsule_distance))
|
||||
|
||||
# means close enough to have a hit change
|
||||
if bone_to_capsule_distance < objLength + r:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def repeatMoveToPlane(obj, objPos, objTarget, colPlane, times):
|
||||
# Y axis direction of plane
|
||||
n = dt.Vector(get_matrix(colPlane)[4:7])
|
||||
q = get_translation(colPlane)
|
||||
d = n.dot(q)
|
||||
|
||||
# for i in range(times):
|
||||
# pt = objPos
|
||||
# obj.setTranslation(proj_pt_to_plane(pt, n, d), space='world')
|
||||
# if (i + 1) != times:
|
||||
# obj.setTranslation(get_translation(objTarget), space='world')
|
||||
pt = objPos
|
||||
outPos = springMath.proj_pt_to_plane(pt, n, d)
|
||||
|
||||
return outPos
|
||||
|
||||
|
||||
def setWireShading(obj, tmp):
|
||||
obj.getShape().overrideEnabled.set(True)
|
||||
obj.getShape().overrideShading.set(False)
|
||||
|
||||
if tmp:
|
||||
obj.getShape().overrideDisplayType.set(1)
|
||||
|
||||
|
||||
def addCapsuleSphereConstraint(sphereObj):
|
||||
# create a locator and make sphere follow it
|
||||
locator = pm.spaceLocator(name=sphereObj.name() + '_locator' + kCapsuleNameSuffix)
|
||||
|
||||
locator.setTranslation(sphereObj.getTranslation())
|
||||
locator.setRotation(sphereObj.getRotation())
|
||||
locator.getShape().setAttr('visibility', False)
|
||||
|
||||
pm.parentConstraint(locator, sphereObj)
|
||||
|
||||
return locator
|
||||
|
||||
|
||||
def createCapsuleGeometry(size):
|
||||
# create geometry
|
||||
cylinder, cylinder_history = pm.cylinder(radius=size, sections=8, heightRatio=3)
|
||||
pm.rename(cylinder.name(), cylinder.name() + kCapsuleNameSuffix)
|
||||
|
||||
sphereA, sphereA_history = pm.sphere(radius=size, endSweep=180, sections=4)
|
||||
pm.rename(sphereA.name(), sphereA.name() + kCapsuleNameSuffix)
|
||||
|
||||
sphereB, sphereB_history = pm.sphere(radius=size, endSweep=180, sections=4)
|
||||
pm.rename(sphereB.name(), sphereB.name() + kCapsuleNameSuffix)
|
||||
|
||||
# set to wireframe shader
|
||||
setWireShading(cylinder, False)
|
||||
setWireShading(sphereA, True)
|
||||
setWireShading(sphereB, True)
|
||||
|
||||
# build a capsule with geometry
|
||||
cylinder.setAttr('rotateZ', 90)
|
||||
sphereA.setAttr('translateY', -1.5 * size)
|
||||
sphereB.setAttr('rotateZ', 180)
|
||||
sphereB.setAttr('translateY', 1.5 * size)
|
||||
|
||||
# add constrain
|
||||
locatorA = addCapsuleSphereConstraint(sphereA)
|
||||
locatorB = addCapsuleSphereConstraint(sphereB)
|
||||
|
||||
pm.parent(locatorA, cylinder)
|
||||
pm.parent(locatorB, cylinder)
|
||||
|
||||
pm.parent(sphereA, cylinder)
|
||||
pm.parent(sphereB, cylinder)
|
||||
|
||||
sphereA.setAttr('inheritsTransform', False)
|
||||
sphereB.setAttr('inheritsTransform', False)
|
||||
|
||||
pm.connectAttr(cylinder.scaleY, (sphereA_history.name() + '.radius'))
|
||||
pm.connectAttr(cylinder.scaleY, (sphereB_history.name() + '.radius'))
|
||||
pm.connectAttr(cylinder.scaleY, cylinder.scaleZ)
|
||||
|
||||
return cylinder
|
||||
|
||||
|
||||
def getCapsule(getAll):
|
||||
if getAll:
|
||||
nurbsTransLst = pm.ls(type='transform')
|
||||
else:
|
||||
nurbsTransLst = pm.ls(sl=True)
|
||||
|
||||
nurbsSurfaceLst = []
|
||||
for obj in nurbsTransLst:
|
||||
if obj.getShape() and (pm.nodeType(obj.getShape()) == 'nurbsSurface'):
|
||||
nurbsSurfaceLst.append(obj)
|
||||
|
||||
cylinderLst = []
|
||||
for obj in nurbsTransLst:
|
||||
if 'ylinder' in obj.name() and kCapsuleNameSuffix in obj.name():
|
||||
cylinderLst.append(obj)
|
||||
|
||||
return cylinderLst
|
||||
|
||||
|
||||
def addCapsuleBody():
|
||||
# create capsule body for collision
|
||||
# place capsule at ori point of nothing selected in scene
|
||||
# place capsule match with object position and rotation if select scene object
|
||||
collisionBoneList = []
|
||||
objs = pm.ls(sl=True)
|
||||
|
||||
for obj in objs:
|
||||
children = pm.listRelatives(obj, children=1)
|
||||
|
||||
# only add capsule to the obj which has child
|
||||
if children:
|
||||
collisionBoneList.append([obj, children[0]])
|
||||
|
||||
if collisionBoneList:
|
||||
for couple in collisionBoneList:
|
||||
baseBone = couple[0]
|
||||
endBone = couple[1]
|
||||
capsule = createCapsuleGeometry(1)
|
||||
|
||||
pm.parent(capsule, baseBone)
|
||||
# match capsule to bone
|
||||
endBoneTrans = endBone.getTranslation()
|
||||
capsule.setTranslation(endBoneTrans * 0.5)
|
||||
capsule.setAttr('scaleX', endBoneTrans[0] / 3)
|
||||
capsule.setAttr('scaleY', endBoneTrans[0] / 3)
|
||||
cns = pm.aimConstraint(endBone, capsule, aimVector=[1, 0, 0])
|
||||
pm.delete(cns)
|
||||
|
||||
else:
|
||||
capsule = createCapsuleGeometry(1)
|
||||
capsule.setAttr('scaleX', 10)
|
||||
capsule.setAttr('scaleY', 10)
|
||||
pm.select(clear=1)
|
||||
121
2025/scripts/animation_tools/springmagic/decorators.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import maya.mel as mel
|
||||
import maya.cmds as cmds
|
||||
|
||||
from functools import wraps
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Decorators
|
||||
# -----------------------------------------------------------------------------
|
||||
def viewportOff(func):
|
||||
"""
|
||||
Decorator - turn off Maya display while func is running.
|
||||
if func will fail, the error will be raised after.
|
||||
"""
|
||||
@wraps(func)
|
||||
def wrap(*args, **kwargs):
|
||||
|
||||
# Turn $gMainPane Off:
|
||||
mel.eval("paneLayout -e -manage false $gMainPane")
|
||||
|
||||
# Decorator will try/except running the function.
|
||||
# But it will always turn on the viewport at the end.
|
||||
# In case the function failed, it will prevent leaving maya viewport off.
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception:
|
||||
raise # will raise original error
|
||||
finally:
|
||||
mel.eval("paneLayout -e -manage true $gMainPane")
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
class gShowProgress(object):
|
||||
"""
|
||||
Function decorator to show the user (progress) feedback.
|
||||
@usage
|
||||
|
||||
import time
|
||||
@gShowProgress(end=10)
|
||||
def createCubes():
|
||||
for i in range(10):
|
||||
time.sleep(1)
|
||||
if createCubes.isInterrupted(): break
|
||||
iCube = cmds.polyCube(w=1,h=1,d=1)
|
||||
cmds.move(i,i*.2,0,iCube)
|
||||
createCubes.step()
|
||||
"""
|
||||
|
||||
def __init__(self, status='Busy...', start=0, end=100, interruptable=True):
|
||||
import maya.mel
|
||||
|
||||
self.mStartValue = start
|
||||
self.mEndValue = end
|
||||
self.mStatus = status
|
||||
self.mInterruptable = interruptable
|
||||
self.mMainProgressBar = maya.mel.eval('$tmp = $gMainProgressBar')
|
||||
|
||||
def step(self, inValue=1):
|
||||
"""Increase step
|
||||
@param inValue (int) Step value"""
|
||||
cmds.progressBar(self.mMainProgressBar, edit=True, step=inValue)
|
||||
|
||||
def progress(self, inValue):
|
||||
"""Set progression value
|
||||
@param inValue (int) Progress value"""
|
||||
cmds.progressBar(self.mMainProgressBar, edit=True, progress=inValue)
|
||||
|
||||
def isInterrupted(self):
|
||||
"""Check if the user has interrupted the progress
|
||||
@return (boolean)"""
|
||||
return cmds.progressBar(self.mMainProgressBar, query=True, isCancelled=True)
|
||||
|
||||
def start(self):
|
||||
"""Start progress"""
|
||||
cmds.waitCursor(state=True)
|
||||
cmds.progressBar(
|
||||
self.mMainProgressBar,
|
||||
edit=True,
|
||||
beginProgress=True,
|
||||
isInterruptable=self.mInterruptable,
|
||||
status=self.mStatus,
|
||||
minValue=self.mStartValue,
|
||||
maxValue=self.mEndValue)
|
||||
cmds.refresh()
|
||||
|
||||
def end(self):
|
||||
"""Mark the progress as ended"""
|
||||
cmds.progressBar(self.mMainProgressBar, edit=True, endProgress=True)
|
||||
cmds.waitCursor(state=False)
|
||||
|
||||
def __call__(self, inFunction):
|
||||
"""
|
||||
Override call method
|
||||
@param inFunction (function) Original function
|
||||
@return (function) Wrapped function
|
||||
@description
|
||||
If there are decorator arguments, __call__() is only called once,
|
||||
as part of the decoration process! You can only give it a single argument,
|
||||
which is the function object.
|
||||
"""
|
||||
def wrapped_f(*args, **kwargs):
|
||||
# Start progress
|
||||
self.start()
|
||||
# Call original function
|
||||
inFunction(*args, **kwargs)
|
||||
# End progress
|
||||
self.end()
|
||||
|
||||
# Add special methods to the wrapped function
|
||||
wrapped_f.step = self.step
|
||||
wrapped_f.progress = self.progress
|
||||
wrapped_f.isInterrupted = self.isInterrupted
|
||||
|
||||
# Copy over attributes
|
||||
wrapped_f.__doc__ = inFunction.__doc__
|
||||
wrapped_f.__name__ = inFunction.__name__
|
||||
wrapped_f.__module__ = inFunction.__module__
|
||||
|
||||
# Return wrapped function
|
||||
return wrapped_f
|
||||
75
2025/scripts/animation_tools/springmagic/history.txt
Normal file
@@ -0,0 +1,75 @@
|
||||
#####################################################################################
|
||||
#
|
||||
# Spring Magic for Maya
|
||||
#
|
||||
# Calculate bone chain animation by settings, support collisions and wind force
|
||||
# Can work with rigging controller as well
|
||||
#
|
||||
# Need pringMagic.ui file to work with
|
||||
# This script need also icon file support, which should be put in same folder
|
||||
#
|
||||
# feel free to mail me redtank@outlook.com for any bug or issue
|
||||
#
|
||||
# Yanbin Bai
|
||||
# 2021.02
|
||||
#
|
||||
#####################################################################################
|
||||
|
||||
3.5a
|
||||
- Fix bug tension calculation introduced in the 3.5 (Benoit Degand)
|
||||
- Fix bug inertia calculation introduced in the 3.5 (Benoit Degand)
|
||||
- Clarify code splitting source code in class and methods (Benoit Degand)
|
||||
|
||||
3.5
|
||||
- Apply PEP8 coding format (Benoit Degand)
|
||||
- Add possiblity to cancel the operation (Esc) (Benoit Degand)
|
||||
- Increase speed (x2), avoiding locators and aim constraints intensive creation/deletion (Benoit Degand)
|
||||
- Fragment source code in several files (Benoit Degand)
|
||||
- Pose Match default off
|
||||
|
||||
3.4b
|
||||
- fix collision bug
|
||||
|
||||
3.4a
|
||||
- fix wind bug
|
||||
|
||||
3.4
|
||||
- add plane collision
|
||||
- add pose match
|
||||
|
||||
3.3
|
||||
- add inertia effect
|
||||
|
||||
3.2
|
||||
- fix wind effect cannot set key issue
|
||||
|
||||
3.1
|
||||
- add bind controller
|
||||
- add wind
|
||||
- add flex setting
|
||||
- improve performance
|
||||
- fix twist bug
|
||||
- add capsule icon
|
||||
- seperate skinTools to spring magic and skin magic
|
||||
|
||||
3.0
|
||||
- re-write spring magic to improve performance
|
||||
- add capsule collision for spring magic
|
||||
- add donate page
|
||||
|
||||
2.7.8
|
||||
- fix script stop working issue cause by highend3d.com changed their web page
|
||||
|
||||
2.7.7
|
||||
- add time out for update checking in case of network issue
|
||||
|
||||
2.7.6
|
||||
- fix spring magic calculation issue on MAYA 2016
|
||||
- update UI for MAYA 2016
|
||||
Thanks for all the help from Nobuyuki Kobayashi nobuyuki@unity3d.com
|
||||
|
||||
2.7.5
|
||||
- add floor collision to spring magic
|
||||
|
||||
2.7
|
||||
- Add spring magic
|
||||
BIN
2025/scripts/animation_tools/springmagic/icons/China Flag.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/Shelf.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/Title.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/addCapsule.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/addPlane.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/ali_pay.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/bilibili.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/bitcoin.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/clearCapsule.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/ctrl_bake.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/ctrl_bind.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/donut.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/english.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/info.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/japanese.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/language.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/linkedin.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/paypal.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/redo.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/removeCapsule.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/spring.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/update.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/vimeo.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/wechat_pay.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/weightBone.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/wind.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
2025/scripts/animation_tools/springmagic/icons/youtube.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
13
2025/scripts/animation_tools/springmagic/main.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from .import ui as ui
|
||||
|
||||
|
||||
def main(*args, **kwargs):
|
||||
widget = ui.SpringMagicWidget()
|
||||
widget.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import springmagic
|
||||
|
||||
with springmagic.app():
|
||||
springmagic.main()
|
||||
36
2025/scripts/animation_tools/springmagic/mkDevTools.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from os import path
|
||||
import compileall
|
||||
import sys, types
|
||||
|
||||
|
||||
def recompile(modulename):
|
||||
"""Recompile the given module, its directory's contents!"""
|
||||
myScriptPath = sys.modules[modulename.__name__].__path__[0]
|
||||
if path.isdir(myScriptPath):
|
||||
compileall.compile_dir(myScriptPath, force=True)
|
||||
|
||||
|
||||
def reload_module(modulename):
|
||||
"""Reload the given module and all children"""
|
||||
|
||||
# Get a reference to each loaded module
|
||||
loaded_modules = dict([(key, value) for key, value in list(sys.modules.items())
|
||||
if key.startswith(modulename.__name__) and
|
||||
isinstance(value, types.ModuleType)])
|
||||
|
||||
# Delete references to these loaded modules from sys.modules
|
||||
for key in loaded_modules:
|
||||
del sys.modules[key]
|
||||
|
||||
# Load each of the modules again
|
||||
# Make old modules share state with new modules
|
||||
for key in loaded_modules:
|
||||
print('re-loading %s' % key)
|
||||
newmodule = __import__(key)
|
||||
oldmodule = loaded_modules[key]
|
||||
oldmodule.__dict__.clear()
|
||||
oldmodule.__dict__.update(newmodule.__dict__)
|
||||
|
||||
def refresh(modulename):
|
||||
recompile(modulename)
|
||||
reload_module(modulename)
|
||||
28
2025/scripts/animation_tools/springmagic/springMagic.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import sys
|
||||
import os
|
||||
import inspect
|
||||
|
||||
|
||||
def main():
|
||||
# Add SprinMagic path to PYTHON_PATH
|
||||
script_name = inspect.getframeinfo(inspect.currentframe()).filename
|
||||
script_path = os.path.dirname(os.path.abspath(script_name))
|
||||
path_name = os.path.dirname(script_path)
|
||||
|
||||
if os.path.exists(path_name) and path_name not in sys.path:
|
||||
sys.path.append(path_name)
|
||||
|
||||
# Import SpringMagic module
|
||||
|
||||
# Recompile SpringMagic module if modification has been made
|
||||
|
||||
from . import main as app
|
||||
from . import mkDevTools as dev
|
||||
|
||||
import springmagic
|
||||
dev.refresh(springmagic)
|
||||
# Launch SpringMagic
|
||||
app.main()
|
||||
|
||||
# Remove SprinMagic path from PYTHON_PATH
|
||||
sys.path.remove(path_name)
|
||||
1664
2025/scripts/animation_tools/springmagic/springMagic.ui
Normal file
1821
2025/scripts/animation_tools/springmagic/springMagic_chn.ui
Normal file
1664
2025/scripts/animation_tools/springmagic/springMagic_eng.ui
Normal file
1760
2025/scripts/animation_tools/springmagic/springMagic_jpn.ui
Normal file
291
2025/scripts/animation_tools/springmagic/springMath.py
Normal file
@@ -0,0 +1,291 @@
|
||||
import math
|
||||
import pymel.core as pm
|
||||
import pymel.core.datatypes as dt
|
||||
|
||||
|
||||
def sigmoid(x):
|
||||
return 1 / (1 + math.exp(-x))
|
||||
|
||||
|
||||
def distance(a, b):
|
||||
return (b - a).length()
|
||||
|
||||
|
||||
def lerp_vec(a, b, t):
|
||||
return (a * (1 - t)) + (b * t)
|
||||
|
||||
|
||||
def dist_to_plane(pt, n, d):
|
||||
return n.dot(pt) - (d / n.dot(n))
|
||||
|
||||
|
||||
def dist_to_line(a, b, p):
|
||||
ap = p - a
|
||||
ab = b - a
|
||||
result = a + ((ap.dot(ab) / ab.dot(ab)) * ab)
|
||||
return distance(result, p)
|
||||
|
||||
|
||||
def is_same_side_of_plane(pt, test_pt, n, d):
|
||||
d1 = math.copysign(1, dist_to_plane(pt, n, d))
|
||||
d2 = math.copysign(1, dist_to_plane(test_pt, n, d))
|
||||
|
||||
# print(pt, test_pt, d1, d2)
|
||||
return d1 * d2 == 1.0
|
||||
|
||||
|
||||
def proj_pt_to_plane(pt, n, d):
|
||||
t = n.dot(pt) - d
|
||||
return (pt - (n * t))
|
||||
|
||||
|
||||
def pt_in_sphere(pt, c, r):
|
||||
return (pt - c).length() <= r
|
||||
|
||||
|
||||
def pt_in_cylinder(pt, p, q, r):
|
||||
n = (q - p).normal()
|
||||
d = n.dot(p)
|
||||
|
||||
if not is_same_side_of_plane(pt, (p + q) / 2.0, n, d):
|
||||
return False
|
||||
|
||||
n = (q - p).normal()
|
||||
d = n.dot(q)
|
||||
|
||||
if not is_same_side_of_plane(pt, (p + q) / 2.0, n, d):
|
||||
return False
|
||||
|
||||
proj_pt = proj_pt_to_plane(pt, n, d)
|
||||
# logging("proj_pt", proj_pt)
|
||||
# logging("q", q)
|
||||
# logging("distance(proj_pt, q)", distance(proj_pt, q))
|
||||
|
||||
return distance(proj_pt, q) <= r
|
||||
|
||||
|
||||
def segment_sphere_isect(sa, sb, c, r):
|
||||
NotFound = (False, None)
|
||||
|
||||
p = sa
|
||||
d = (sb - sa).normal()
|
||||
|
||||
m = p - c
|
||||
b = m.dot(d)
|
||||
c = m.dot(m) - r * r
|
||||
|
||||
if c > 0.0 and b > 0.0:
|
||||
return NotFound
|
||||
|
||||
discr = b * b - c
|
||||
if discr < 0.0:
|
||||
return NotFound
|
||||
|
||||
t = -b - math.sqrt(discr)
|
||||
if t < 0.0:
|
||||
return NotFound
|
||||
|
||||
dist = distance(sa, sb)
|
||||
q = p + d * t
|
||||
return ((t >= 0 and t <= dist), q)
|
||||
|
||||
|
||||
def segment_cylinder_isect(sa, sb, p, q, r):
|
||||
SM_EPSILON = 1e-6
|
||||
d = q - p
|
||||
m = sa - p
|
||||
n = sb - sa
|
||||
md = m.dot(d)
|
||||
nd = n.dot(d)
|
||||
dd = d.dot(d)
|
||||
|
||||
NotFound = (False, None)
|
||||
if md < 0 and md + nd < 0:
|
||||
return NotFound
|
||||
|
||||
if md > dd and md + nd > dd:
|
||||
return NotFound
|
||||
|
||||
nn = n.dot(n)
|
||||
mn = m.dot(n)
|
||||
|
||||
a = dd * nn - nd * nd
|
||||
k = m.dot(m) - r * r
|
||||
c = dd * k - md * md
|
||||
|
||||
if abs(a) < SM_EPSILON:
|
||||
if c > 0:
|
||||
return NotFound
|
||||
if md < 0:
|
||||
t = -mn / nn
|
||||
elif md > dd:
|
||||
t = (nd - mn) / nn
|
||||
else:
|
||||
t = 0
|
||||
return (True, lerp_vec(sa, sb, t))
|
||||
|
||||
b = dd * mn - nd * md
|
||||
discr = b * b - a * c
|
||||
if discr < 0:
|
||||
return NotFound
|
||||
|
||||
t = (-b - math.sqrt(discr)) / a
|
||||
if t < 0.0 or t > 1.0:
|
||||
return NotFound
|
||||
if (md + t * nd < 0.0):
|
||||
if nd <= 0.0:
|
||||
return NotFound
|
||||
t = -md / nd
|
||||
return (k + 2 * t * (mn + t * nn) <= 0.0, lerp_vec(sa, sb, t))
|
||||
elif md + t * nd > dd:
|
||||
if nd >= 0.0:
|
||||
return NotFound
|
||||
t = (dd - md) / nd
|
||||
return (k + dd - 2 * md + t * (2 * (mn - nd) + t * nn) <= 0.0, lerp_vec(sa, sb, t))
|
||||
|
||||
return (True, lerp_vec(sa, sb, t))
|
||||
|
||||
|
||||
def pt_in_capsule(pt, p, q, r):
|
||||
return pt_in_cylinder(pt, p, q, r) or pt_in_sphere(pt, p, r) or pt_in_sphere(pt, q, r)
|
||||
|
||||
|
||||
def segment_capsule_isect(sa, sb, p, q, r):
|
||||
# sa = dt.Vector()
|
||||
# ray start point pos vector
|
||||
# sb = dt.Vector()
|
||||
# ray end point pos vector
|
||||
# p = dt.Vector()
|
||||
# capsle one sphere tip pos
|
||||
# q = dt.Vector()
|
||||
# capsle another sphere tip pos
|
||||
# r = float
|
||||
# radio of capsle sphere
|
||||
|
||||
if pt_in_capsule(sa, p, q, r):
|
||||
if pt_in_capsule(sb, p, q, r):
|
||||
# both inside. extend sb to get intersection
|
||||
newb = sa + (sb - sa).normal() * 200.0
|
||||
sa, sb = newb, sa
|
||||
else:
|
||||
sb, sa = sa, sb
|
||||
|
||||
# d = (sb - sa).normal()
|
||||
|
||||
i1 = segment_sphere_isect(sa, sb, p, r)
|
||||
i2 = segment_sphere_isect(sa, sb, q, r)
|
||||
i3 = segment_cylinder_isect(sa, sb, p, q, r)
|
||||
|
||||
dist = float('inf')
|
||||
closest_pt = None
|
||||
hit = False
|
||||
hitCylinder = False
|
||||
|
||||
for i in [i1, i2, i3]:
|
||||
|
||||
if i[0]:
|
||||
hit = True
|
||||
pt = i[1]
|
||||
|
||||
if distance(sa, pt) < dist:
|
||||
closest_pt = pt
|
||||
|
||||
dist = min(dist, distance(sa, pt))
|
||||
# draw_locator(i1[2], 'i1')
|
||||
|
||||
return (hit, closest_pt, hitCylinder)
|
||||
|
||||
|
||||
def checkCollision(cur_pos, pre_pos, capsuleLst, isRevert):
|
||||
# calculate collision with all the capsule in scene
|
||||
if isRevert:
|
||||
sa = cur_pos
|
||||
sb = pre_pos
|
||||
else:
|
||||
sb = cur_pos
|
||||
sa = pre_pos
|
||||
|
||||
isHited = False
|
||||
closest_pt_dict = {}
|
||||
|
||||
for obj in capsuleLst:
|
||||
objChildren = pm.listRelatives(obj, children=1, type='transform')
|
||||
p = objChildren[0].getTranslation(space='world')
|
||||
q = objChildren[1].getTranslation(space='world')
|
||||
r = obj.getAttr('scaleZ') * 1
|
||||
|
||||
hit, closest_pt, hitCylinder = segment_capsule_isect(sa, sb, p, q, r)
|
||||
|
||||
if hit:
|
||||
isHited = True
|
||||
closest_pt_dict[obj.name()] = [obj, closest_pt]
|
||||
# drawDebug_box(closest_pt)
|
||||
|
||||
if isHited:
|
||||
pt_length = 9999
|
||||
closest_pt = None
|
||||
col_obj = None
|
||||
|
||||
for pt in list(closest_pt_dict.keys()):
|
||||
lLength = (closest_pt_dict[pt][1] - pre_pos).length()
|
||||
|
||||
if lLength < pt_length:
|
||||
pt_length = lLength
|
||||
closest_pt = closest_pt_dict[pt][1]
|
||||
col_obj = closest_pt_dict[pt][0]
|
||||
|
||||
# return col pt and col_body speed
|
||||
return closest_pt, col_obj, hitCylinder
|
||||
else:
|
||||
return None, None, None
|
||||
|
||||
|
||||
def ckeckPointInTri(pos, pa, pb, pc):
|
||||
ra = math.acos(((pa - pos).normal()).dot((pb - pos).normal()))
|
||||
ra = dt.degrees(ra)
|
||||
rb = math.acos(((pb - pos).normal()).dot((pc - pos).normal()))
|
||||
rb = dt.degrees(rb)
|
||||
rc = math.acos(((pc - pos).normal()).dot((pa - pos).normal()))
|
||||
rc = dt.degrees(rc)
|
||||
|
||||
return (abs(ra + rb + rc) > 359)
|
||||
|
||||
|
||||
def getVertexPositions(obj):
|
||||
vertex_positions_list = []
|
||||
|
||||
for vertex in obj.vtx:
|
||||
vertex_positions_list.append(vertex.getPosition(space='world'))
|
||||
|
||||
return vertex_positions_list
|
||||
|
||||
|
||||
def checkPlaneCollision(objPos, childPos, colPlane):
|
||||
|
||||
v_coords = getVertexPositions(colPlane)
|
||||
|
||||
collision_plane_matrix = pm.xform(colPlane, worldSpace=1, matrix=1, q=1)
|
||||
n = dt.Vector(collision_plane_matrix[4:7]) # Y axis direction of plane
|
||||
q = v_coords[1]
|
||||
d = n.dot(q)
|
||||
|
||||
# get obj distance to plane
|
||||
toPlaneDistance = dist_to_plane(objPos, n, d)
|
||||
toPlaneDistance_child = dist_to_plane(childPos, n, d)
|
||||
|
||||
# child projection position on plane
|
||||
projectPos_child = proj_pt_to_plane(childPos, n, d)
|
||||
|
||||
inPlane = False
|
||||
|
||||
if ckeckPointInTri(projectPos_child, v_coords[0], v_coords[1], v_coords[2]):
|
||||
inPlane = True
|
||||
elif ckeckPointInTri(projectPos_child, v_coords[3], v_coords[1], v_coords[2]):
|
||||
inPlane = True
|
||||
|
||||
# bone above plane and bone child under plane and child project point on plane
|
||||
# means has collision with plane
|
||||
if (toPlaneDistance > 0) and (toPlaneDistance_child < 0) and inPlane:
|
||||
return projectPos_child
|
||||
else:
|
||||
return None
|
||||
460
2025/scripts/animation_tools/springmagic/ui.py
Normal file
@@ -0,0 +1,460 @@
|
||||
import os
|
||||
import time
|
||||
import inspect
|
||||
import webbrowser
|
||||
import urllib.request, urllib.error, urllib.parse
|
||||
import random
|
||||
import datetime
|
||||
import maya.mel as mel
|
||||
import pymel.core as pm
|
||||
|
||||
from . import core as core
|
||||
|
||||
from shutil import copyfile
|
||||
|
||||
kSpringMagicVersion = 30500
|
||||
|
||||
scriptName = inspect.getframeinfo(inspect.currentframe()).filename
|
||||
scriptPath = os.path.dirname(os.path.abspath(scriptName))
|
||||
|
||||
# Parameter Initialization
|
||||
ui_file = scriptPath + os.sep + 'springMagic.ui'
|
||||
|
||||
# Constants
|
||||
kVimeoLink = r''
|
||||
kYoutubeLink = r'https://animbai.com/2017/10/14/skintools-tutorials/'
|
||||
|
||||
kUpdateLink = r'https://animbai.com/category/download/'
|
||||
kVersionCheckLink = r'http://animbai.com/skintoolsver/'
|
||||
kOldPersonalLink = r'http://www.scriptspot.com/3ds-max/scripts/spring-magic'
|
||||
|
||||
|
||||
def widgetPath(windowName, widgetNames):
|
||||
"""
|
||||
@param windowName: Window instance name to search
|
||||
@param widgetNames: list of names to search for
|
||||
"""
|
||||
returnDict = {}
|
||||
mayaWidgetList = pm.lsUI(dumpWidgets=True)
|
||||
|
||||
for widget in widgetNames:
|
||||
for mayaWidget in mayaWidgetList:
|
||||
if windowName in mayaWidget:
|
||||
if mayaWidget.endswith(widget):
|
||||
returnDict[widget] = mayaWidget
|
||||
|
||||
return returnDict
|
||||
|
||||
|
||||
class SpringMagicWidget():
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.init()
|
||||
|
||||
def init(self):
|
||||
try:
|
||||
pm.deleteUI(self.ui)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# title = pm.window(pm.loadUI(ui_file = ui_file))
|
||||
|
||||
self.ui = pm.loadUI(f=ui_file)
|
||||
|
||||
ui_widget_list = [
|
||||
'main_progressBar',
|
||||
'main_processLabel',
|
||||
'main_textEdit',
|
||||
'main_lang_id',
|
||||
'spring_language_list',
|
||||
'springSpring_lineEdit',
|
||||
'springSubs_lineEdit',
|
||||
'springXspring_lineEdit',
|
||||
'springTension_lineEdit',
|
||||
'springExtend_lineEdit',
|
||||
'springInertia_lineEdit',
|
||||
'springSubDiv_lineEdit',
|
||||
'springLoop_checkBox',
|
||||
'springPoseMatch_checkBox',
|
||||
'springClearSubFrame_checkBox',
|
||||
'springFrom_lineEdit',
|
||||
'springEnd_lineEdit',
|
||||
'springActive_radioButton',
|
||||
'springFrom_radioButton',
|
||||
# 'springUpAxis_comboBox',
|
||||
'springApply_Button',
|
||||
'springCapsule_checkBox',
|
||||
'springFastMove_checkBox',
|
||||
'springFloor_checkBox',
|
||||
'springFloor_lineEdit',
|
||||
'springBindPose_button',
|
||||
'springStraight_button',
|
||||
'springCopy_button',
|
||||
'springPaste_button',
|
||||
# 'donateBitcoin_lineEdit',
|
||||
'miscUpdate_pushButton',
|
||||
'springAddBody_Button',
|
||||
'springClearBody_Button',
|
||||
'springAddPlane_Button',
|
||||
'springAddWindCmd',
|
||||
'springBind_Button',
|
||||
'springBake_Button',
|
||||
'shelf_button',
|
||||
'vimeo_pushButton',
|
||||
'language_button',
|
||||
'statusbar',
|
||||
'springWind_Button']
|
||||
|
||||
self.uiObjects = widgetPath(self.ui, ui_widget_list)
|
||||
|
||||
# Main UI
|
||||
self.main_progressBar = pm.progressBar(self.uiObjects['main_progressBar'], edit=True)
|
||||
self.main_processLabel = pm.text(self.uiObjects['main_processLabel'], edit=True)
|
||||
self.main_lineEdit = pm.ui.PyUI(self.uiObjects['main_textEdit'], edit=True)
|
||||
self.lang_id = pm.text(self.uiObjects['main_lang_id'], edit=True)
|
||||
|
||||
self.language_list = pm.textScrollList(self.uiObjects['spring_language_list'], edit=True,
|
||||
selectCommand=self.languageSelectedCmd, visible=False)
|
||||
|
||||
self.spring_lineEdit = pm.textField(self.uiObjects['springSpring_lineEdit'], edit=True,
|
||||
changeCommand=self.springRatioChangeCmd)
|
||||
self.subs_lineEdit = pm.textField(self.uiObjects['springSubs_lineEdit'], edit=True)
|
||||
self.Xspring_lineEdit = pm.textField(self.uiObjects['springXspring_lineEdit'], edit=True,
|
||||
changeCommand=self.twistChangeCmd)
|
||||
self.tension_lineEdit = pm.textField(self.uiObjects['springTension_lineEdit'], edit=True,
|
||||
changeCommand=self.tensionChangeCmd)
|
||||
self.extend_lineEdit = pm.textField(self.uiObjects['springExtend_lineEdit'], edit=True,
|
||||
changeCommand=self.extendChangeCmd)
|
||||
self.inertia_lineEdit = pm.textField(self.uiObjects['springInertia_lineEdit'], edit=True,
|
||||
changeCommand=self.inertiaChangeCmd)
|
||||
self.sub_division_lineEdit = pm.textField(self.uiObjects['springSubDiv_lineEdit'], edit=True,
|
||||
changeCommand=self.subDivChangeCmd)
|
||||
self.loop_checkBox = pm.checkBox(self.uiObjects['springLoop_checkBox'], edit=True)
|
||||
self.pose_match_checkBox = pm.checkBox(self.uiObjects['springPoseMatch_checkBox'], edit=True)
|
||||
self.clear_subframe_checkBox = pm.checkBox(self.uiObjects['springClearSubFrame_checkBox'], edit=True)
|
||||
self.from_lineEdit = pm.textField(self.uiObjects['springFrom_lineEdit'], edit=True)
|
||||
self.end_lineEdit = pm.textField(self.uiObjects['springEnd_lineEdit'], edit=True)
|
||||
self.active_radioButton = pm.radioButton(self.uiObjects['springActive_radioButton'], edit=True)
|
||||
self.from_radioButton = pm.radioButton(self.uiObjects['springFrom_radioButton'], edit=True)
|
||||
# self.upAxis_comboBox = pm.optionMenu(self.uiObjects['springUpAxis_comboBox'], edit=True)
|
||||
self.apply_button = pm.button(self.uiObjects['springApply_Button'], edit=True, command=self.applyCmd)
|
||||
self.add_body_button = pm.button(self.uiObjects['springAddBody_Button'], edit=True, command=self.addBodyCmd)
|
||||
self.clear_body_button = pm.button(self.uiObjects['springClearBody_Button'], edit=True,
|
||||
command=self.clearBodyCmd)
|
||||
self.add_plane_button = pm.button(self.uiObjects['springAddPlane_Button'], edit=True,
|
||||
command=self.createColPlaneCmd)
|
||||
self.wind_button = pm.button(self.uiObjects['springWind_Button'], edit=True, command=self.addWindCmd)
|
||||
self.bind_button = pm.button(self.uiObjects['springBind_Button'], edit=True, command=self.bindControlsCmd)
|
||||
self.bake_button = pm.button(self.uiObjects['springBake_Button'], edit=True, command=self.clearBindCmd)
|
||||
self.shelf_button = pm.button(self.uiObjects['shelf_button'], edit=True, command=self.goShelfCmd)
|
||||
self.vimeo_button = pm.button(self.uiObjects['vimeo_pushButton'], edit=True, command=self.youtubeCmd)
|
||||
self.language_button = pm.button(self.uiObjects['language_button'], edit=True, command=self.languageCmd)
|
||||
|
||||
self.collision_checkBox = pm.checkBox(self.uiObjects['springCapsule_checkBox'], edit=True)
|
||||
self.fast_move_checkBox = pm.checkBox(self.uiObjects['springFastMove_checkBox'], edit=True)
|
||||
self.floor_checkBox = pm.checkBox(self.uiObjects['springFloor_checkBox'], edit=True)
|
||||
self.floor_lineEdit = pm.textField(self.uiObjects['springFloor_lineEdit'], edit=True,
|
||||
changeCommand=self.twistChangeCmd)
|
||||
|
||||
self.bind_pose_button = pm.button(self.uiObjects['springBindPose_button'], edit=True, command=self.setCmd)
|
||||
self.straight_button = pm.button(self.uiObjects['springStraight_button'], edit=True, command=self.straightCmd)
|
||||
self.copy_button = pm.button(self.uiObjects['springCopy_button'], edit=True, command=self.copyCmd)
|
||||
self.paste_button = pm.button(self.uiObjects['springPaste_button'], edit=True, command=self.pasteCmd)
|
||||
|
||||
# self.statusbar = pm.button(self.uiObjects['statusbar'], edit=True, menuItemCommand=self.testCmd)
|
||||
|
||||
self.misc_update_button = pm.button(self.uiObjects['miscUpdate_pushButton'], edit=True,
|
||||
command=self.updatePageCmd)
|
||||
|
||||
self.spam_word = ['', '', '', '', '']
|
||||
|
||||
def show(self):
|
||||
|
||||
pm.showWindow(self.ui)
|
||||
|
||||
self.checkUpdate()
|
||||
|
||||
def progression_callback(self, progression):
|
||||
pm.progressBar(self.main_progressBar, edit=True, progress=progression)
|
||||
|
||||
#############################################
|
||||
# Buttons callbacks
|
||||
############################################
|
||||
|
||||
def showSpam(self, *args):
|
||||
sWord = self.spam_word[random.randint(0, 4)]
|
||||
# print as unicode\
|
||||
kwargs = {"edit": True}
|
||||
sWord = sWord if isinstance(sWord, str) else str(sWord, "utf8", errors="ignore")
|
||||
kwargs.setdefault("label", sWord)
|
||||
|
||||
pm.text(self.main_processLabel, **kwargs)
|
||||
|
||||
def pasteCmd(self, *args):
|
||||
core.pasteBonePose()
|
||||
|
||||
def setCmd(self, *args):
|
||||
|
||||
picked_bones = pm.ls(sl=1, type='joint')
|
||||
|
||||
if picked_bones:
|
||||
self.apply_button.setEnable(False)
|
||||
|
||||
core.bindPose()
|
||||
|
||||
# Select only the joints
|
||||
pm.select(picked_bones)
|
||||
|
||||
self.apply_button.setEnable(True)
|
||||
|
||||
def straightCmd(self, *args):
|
||||
|
||||
picked_bones = pm.ls(sl=1, type='joint')
|
||||
|
||||
if picked_bones:
|
||||
self.apply_button.setEnable(False)
|
||||
|
||||
for bone in picked_bones:
|
||||
core.straightBonePose(bone)
|
||||
|
||||
# Select only the joints
|
||||
pm.select(picked_bones)
|
||||
|
||||
self.apply_button.setEnable(True)
|
||||
|
||||
def applyCmd(self, *args):
|
||||
picked_transforms = pm.ls(sl=1, type='transform')
|
||||
|
||||
if picked_transforms:
|
||||
self.apply_button.setEnable(False)
|
||||
|
||||
pm.text(self.main_processLabel, edit=True, label='Calculating Bone Spring... (Esc to cancel)')
|
||||
|
||||
springRatio = 1 - float(self.spring_lineEdit.getText())
|
||||
twistRatio = 1 - float(self.Xspring_lineEdit.getText())
|
||||
isLoop = bool(self.loop_checkBox.getValue())
|
||||
isPoseMatch = bool(self.pose_match_checkBox.getValue())
|
||||
isFastMove = self.fast_move_checkBox.getValue()
|
||||
isCollision = self.collision_checkBox.getValue()
|
||||
|
||||
subDiv = 1.0
|
||||
if isCollision:
|
||||
subDiv = float(self.sub_division_lineEdit.getText())
|
||||
|
||||
# get frame range
|
||||
if self.active_radioButton.getSelect():
|
||||
startFrame = int(pm.playbackOptions(q=1, minTime=1))
|
||||
endFrame = int(pm.playbackOptions(q=1, maxTime=1))
|
||||
else:
|
||||
startFrame = int(self.from_lineEdit.getText())
|
||||
endFrame = int(self.end_lineEdit.getText())
|
||||
|
||||
tension = float(self.tension_lineEdit.getText())
|
||||
inertia = float(self.inertia_lineEdit.getText())
|
||||
extend = float(self.extend_lineEdit.getText())
|
||||
|
||||
wipeSubFrame = self.clear_subframe_checkBox.getValue()
|
||||
|
||||
spring = core.Spring(springRatio, twistRatio, tension, extend, inertia)
|
||||
springMagic = core.SpringMagic(startFrame, endFrame, subDiv, isLoop, isPoseMatch, isCollision, isFastMove,
|
||||
wipeSubFrame)
|
||||
|
||||
startTime = datetime.datetime.now()
|
||||
|
||||
try:
|
||||
core.startCompute(spring, springMagic, self.progression_callback)
|
||||
|
||||
deltaTime = (datetime.datetime.now() - startTime)
|
||||
|
||||
pm.text(self.main_processLabel, edit=True,
|
||||
label="Spring Calculation Time: {0}s".format(deltaTime.seconds))
|
||||
|
||||
except ValueError as exception:
|
||||
pm.text(self.main_processLabel, edit=True, label='Process aborted')
|
||||
pm.warning(exception)
|
||||
|
||||
# Select only the joints
|
||||
pm.select(picked_transforms)
|
||||
|
||||
pm.progressBar(self.main_progressBar, edit=True, progress=0)
|
||||
|
||||
self.apply_button.setEnable(True)
|
||||
|
||||
def copyCmd(self, *args):
|
||||
core.copyBonePose()
|
||||
|
||||
def webCmd(self, *args):
|
||||
# open my linked in page :)
|
||||
webbrowser.open(kOldPersonalLink, new=2)
|
||||
|
||||
def twistChangeCmd(self, *args):
|
||||
self.limitTextEditValue(self.Xspring_lineEdit, defaultValue=0.7)
|
||||
|
||||
def extendChangeCmd(self, *args):
|
||||
self.limitTextEditValue(self.extend_lineEdit, defaultValue=0.0)
|
||||
|
||||
def inertiaChangeCmd(self, *args):
|
||||
self.limitTextEditValue(self.inertia_lineEdit, defaultValue=0.0)
|
||||
|
||||
def springRatioChangeCmd(self, *args):
|
||||
self.limitTextEditValue(self.spring_lineEdit, defaultValue=0.7)
|
||||
|
||||
def tensionChangeCmd(self, *args):
|
||||
self.limitTextEditValue(self.tension_lineEdit, defaultValue=0.5)
|
||||
|
||||
def subDivChangeCmd(self, *args):
|
||||
# self.limitTextEditValue(self.sub_division_lineEdit, defaultValue=1)
|
||||
pass
|
||||
|
||||
def addWindCmd(self, *args):
|
||||
core.addWindObj()
|
||||
|
||||
def addBodyCmd(self, *args):
|
||||
core.addCapsuleBody()
|
||||
|
||||
def createColPlaneCmd(self, *args):
|
||||
core.createCollisionPlane()
|
||||
|
||||
def removeBodyCmd(self, *args):
|
||||
core.removeBody(clear=False)
|
||||
|
||||
def clearBodyCmd(self, *args):
|
||||
core.removeBody(clear=True)
|
||||
|
||||
def bindControlsCmd(self, *args):
|
||||
core.bindControls()
|
||||
|
||||
def clearBindCmd(self, *args):
|
||||
|
||||
# get frame range
|
||||
if self.active_radioButton.getSelect():
|
||||
startFrame = int(pm.playbackOptions(q=1, minTime=1))
|
||||
endFrame = int(pm.playbackOptions(q=1, maxTime=1))
|
||||
else:
|
||||
startFrame = int(self.from_lineEdit.getText())
|
||||
endFrame = int(self.end_lineEdit.getText())
|
||||
|
||||
core.clearBind(startFrame, endFrame)
|
||||
|
||||
def goShelfCmd(self, *args):
|
||||
parentTab = mel.eval(
|
||||
'''global string $gShelfTopLevel;string $shelves = `tabLayout -q -selectTab $gShelfTopLevel`;''')
|
||||
imageTitlePath = scriptPath + os.sep + "icons" + os.sep + "Title.png"
|
||||
commandLine = "import springmagic\nspringmagic.main()"
|
||||
|
||||
pm.shelfButton(commandRepeatable=True, image1=imageTitlePath, label="Spring Magic", parent=parentTab,
|
||||
command=commandLine)
|
||||
|
||||
def languageCmd(self, *args):
|
||||
self.language_list.setVisible(not self.language_list.getVisible())
|
||||
|
||||
def languageSelectedCmd(self, *args):
|
||||
self.language_list.setVisible(False)
|
||||
self.applyLanguage(int(self.language_list.getSelectIndexedItem()[0]))
|
||||
|
||||
def youtubeCmd(self, *args):
|
||||
try:
|
||||
webbrowser.open(kYoutubeLink, new=2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def vimeoCmd(self, *args):
|
||||
# try:
|
||||
# webbrowser.open(kVimeoLink, new=2)
|
||||
# except Exception:
|
||||
# pass
|
||||
pass
|
||||
|
||||
def updatePageCmd(self, *args):
|
||||
try:
|
||||
webbrowser.open(kUpdateLink, new=2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def applyLanguage(self, lanId):
|
||||
lanDict = {1: '_chn', 2: '_eng', 3: '_jpn'}
|
||||
|
||||
if lanId in list(lanDict.keys()):
|
||||
# get new language ui file path
|
||||
new_ui_file = scriptPath + os.sep + os.path.basename(ui_file).split('.')[0] + lanDict[lanId] + '.' + \
|
||||
os.path.basename(ui_file).split('.')[1]
|
||||
copyfile(new_ui_file, ui_file)
|
||||
|
||||
# Reload interface
|
||||
self.init()
|
||||
self.show()
|
||||
|
||||
def detectMayaLanguage(self):
|
||||
mayaLan = None
|
||||
try:
|
||||
mayaLan = os.environ['MAYA_UI_LANGUAGE']
|
||||
except Exception:
|
||||
import locale
|
||||
mayaLan = locale.getdefaultlocale()[0]
|
||||
|
||||
lanDict = {'zh_CN': 1, 'en_US': 2, 'ja_JP': 3}
|
||||
self.applyLanguage(lanDict[mayaLan])
|
||||
|
||||
def printTextEdit(self, textEdit, inputString):
|
||||
ctime = time.ctime()
|
||||
ptime = ctime.split(' ')
|
||||
inputString = ptime[3] + ' - ' + inputString
|
||||
pm.scrollField(textEdit, edit=True, insertionPosition=0, insertText=inputString + '\n')
|
||||
|
||||
def checkUpdate(self):
|
||||
|
||||
self.misc_update_button.setVisible(0)
|
||||
|
||||
page_content = None
|
||||
|
||||
site = kVersionCheckLink
|
||||
hdr = {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
|
||||
'Accept-Encoding': 'none',
|
||||
'Accept-Language': 'en-US,en;q=0.8',
|
||||
'Connection': 'keep-alive'}
|
||||
|
||||
req = urllib.request.Request(site, headers=hdr)
|
||||
|
||||
try:
|
||||
page = urllib.request.urlopen(req, timeout=5)
|
||||
page_content = page.read()
|
||||
except Exception:
|
||||
print('checkUpdate failed')
|
||||
|
||||
if page_content:
|
||||
if isinstance(page_content, bytes):
|
||||
page_content = page_content.decode("utf-8")
|
||||
if len(page_content.split('|springMagic|')) > 1:
|
||||
new_kSpringMagicVersion = int(page_content.split('|springMagic|')[1])
|
||||
|
||||
if new_kSpringMagicVersion > kSpringMagicVersion:
|
||||
self.misc_update_button.setVisible(1)
|
||||
|
||||
self.spam_word = []
|
||||
|
||||
prefix = '|spam'
|
||||
suffix = '|'
|
||||
|
||||
if self.lang_id.getLabel() == 'chn':
|
||||
suffix = 'chn|'
|
||||
|
||||
self.spam_word = [page_content.split(prefix + str(i) + suffix)[1] for i in range(1, 6)]
|
||||
else:
|
||||
pm.text(self.main_processLabel, edit=True, label='Check update failed, try later.')
|
||||
|
||||
self.showSpam()
|
||||
|
||||
def limitTextEditValue(self, ui_object, minValue=0, maxValue=1, roundF=2, defaultValue=0):
|
||||
value = 0
|
||||
|
||||
try:
|
||||
value = float(ui_object.getText())
|
||||
value = round(value, roundF)
|
||||
value = max(min(maxValue, value), minValue)
|
||||
except Exception:
|
||||
value = defaultValue
|
||||
|
||||
ui_object.setText(str(value))
|
||||
56
2025/scripts/animation_tools/springmagic/utility.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import pymel.core as pm
|
||||
import pymel.core.datatypes as dt
|
||||
|
||||
|
||||
##########################
|
||||
# Usefull function
|
||||
##########################
|
||||
|
||||
def clamp(n, minn, maxn):
|
||||
return max(min(maxn, n), minn)
|
||||
|
||||
|
||||
def get_node(name):
|
||||
node_list = pm.ls(name)
|
||||
node = None
|
||||
|
||||
if node_list:
|
||||
node = node_list[0]
|
||||
|
||||
return node
|
||||
|
||||
|
||||
def get_matrix(obj):
|
||||
return pm.xform(obj, worldSpace=1, matrix=1, q=1)
|
||||
|
||||
|
||||
def frange(start, stop=None, step=None):
|
||||
# if set start=0.0 and step = 1.0 if not specified
|
||||
start = float(start)
|
||||
|
||||
if stop is None:
|
||||
stop = start + 0.0
|
||||
start = 0.0
|
||||
|
||||
if step is None:
|
||||
step = 1.0
|
||||
|
||||
# print("start = ", start, "stop = ", stop, "step = ", step)
|
||||
|
||||
count = 0
|
||||
while True:
|
||||
temp = float(start + count * step)
|
||||
if step > 0 and temp >= stop:
|
||||
break
|
||||
elif step < 0 and temp <= stop:
|
||||
break
|
||||
yield temp
|
||||
count += 1
|
||||
|
||||
|
||||
def get_translation(n):
|
||||
return dt.Vector(pm.xform(n, worldSpace=1, translation=1, query=1))
|
||||
|
||||
|
||||
def get_rotation(n):
|
||||
return pm.xform(n, worldSpace=1, rotation=1, query=1)
|
||||
13
2025/scripts/animation_tools/springmagic/安装说明.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
1. 解压缩 springmagic.zip 并复制 "springmagic" 目录至位于 Windows 用户路径下的 Maya 脚本目录
|
||||
例如
|
||||
"C:\Users\你的用户名\Documents\maya\scripts"
|
||||
2. 启动 Maya,在 Maya 里运行如下 Python 命令, 会出现工具界面
|
||||
import springmagic
|
||||
springmagic.main()
|
||||
3. 用工具界面右上方的创建快捷按钮功能,在书签栏创建一个快捷按钮,方便下次使用
|
||||
|
||||
|
||||
复制这个
|
||||
|
||||
import springmagic
|
||||
springmagic.main()
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"TAB_OPEN": 0}
|
||||
{"TAB_OPEN": 2}
|
||||
@@ -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)
|
||||
|
||||
@@ -9,5 +9,6 @@ General modeling utilities
|
||||
from .batchextrusion import show_batch_extrusion_ui
|
||||
|
||||
__all__ = [
|
||||
'show_batch_extrusion_ui'
|
||||
'show_batch_extrusion_ui',
|
||||
'creaseplus'
|
||||
]
|
||||
|
||||
1153
2025/scripts/modeling_tools/creaseplus/CreasePlus.mel
Normal file
BIN
2025/scripts/modeling_tools/creaseplus/CreasePlus_Doc.png
Normal file
|
After Width: | Height: | Size: 476 KiB |
452
2025/scripts/modeling_tools/creaseplus/CreasePlus_Extension1.mel
Normal file
@@ -0,0 +1,452 @@
|
||||
//AUTHOR : BAIDHIR HIDAIR © 2017.
|
||||
//don't modify , don't distribute.
|
||||
|
||||
//PP
|
||||
|
||||
if(!`polyCreaseCtx -ex "spWeightCtx"`){
|
||||
polyCreaseCtx -es 1 -r 1 "spWeightCtx";
|
||||
}
|
||||
///////////////////////////////////////////////////// LOCAL REALM
|
||||
|
||||
proc float spgetmaxf(float $valz[]){
|
||||
|
||||
int $tmp2 = $valz[0];
|
||||
|
||||
for($j = 0; $j < size($valz); $j++){
|
||||
|
||||
if($tmp2 < $valz[$j]){
|
||||
$tmp2 = $valz[$j];
|
||||
}
|
||||
}
|
||||
|
||||
return $tmp2;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////// GLOBAL REALM /////////////////////////////////////
|
||||
|
||||
global proc spSmartLvl(){
|
||||
|
||||
string $edges[] = `filterExpand -ex 1 -sm 32`;
|
||||
|
||||
|
||||
if(!`size $edges`){error;}
|
||||
|
||||
polyOptions -dce 0;
|
||||
|
||||
string $op[] = `listRelatives -p -f $edges`;
|
||||
|
||||
setAttr ($op[0] + ".osdSmoothTriangles") 1;
|
||||
|
||||
float $Wval[] = `polyCrease -q -v ($op[0] + ".e[*]")`;
|
||||
|
||||
float $higherW = `spgetmaxf $Wval`;
|
||||
|
||||
int $smtLvl = (int) `ceil $higherW`;
|
||||
|
||||
if($smtLvl < 1){$smtLvl = 1;}
|
||||
|
||||
setAttr ($op[0] + ".smoothLevel") ($smtLvl + 1);
|
||||
|
||||
}
|
||||
|
||||
global proc spFastCrease(){
|
||||
|
||||
if(size(`filterExpand -ex 1 -sm 12`)){
|
||||
polySelectConstraint -m 0 -dis; polySelectConstraint -m 3 -t 0x8000 -sm 1; polySelectConstraint -m 0 -dis;
|
||||
}
|
||||
|
||||
string $edges[] = `filterExpand -ex 1 -sm 32`;
|
||||
|
||||
if(!`size $edges`){ error "You must select edge components.\n";}
|
||||
|
||||
polyOptions -dce 0;
|
||||
|
||||
string $op[] = `listRelatives -p -f $edges`;
|
||||
|
||||
|
||||
setAttr ($op[0] + ".osdSmoothTriangles") 1;
|
||||
|
||||
setAttr ($op[0] + ".displaySmoothMesh") 2;
|
||||
|
||||
setToolTo "spWeightCtx";
|
||||
|
||||
scriptJob -cu 1 -ro 1 -e "PostToolChanged" "spSmartLvl";
|
||||
}
|
||||
|
||||
|
||||
global proc spNoCrease(){// Remove Crease / Weights
|
||||
|
||||
string $op[] = eval("listRelatives -p -f `eval(\"listRelatives -p -f `polyListComponentConversion -tv`\")`");
|
||||
|
||||
if(!`size $op`){ error "Select objects or components.\n"; }
|
||||
|
||||
string $opShape[] = `listRelatives -c -f $op`;
|
||||
|
||||
if(size(`filterExpand -ex 1 -sm 12`)){
|
||||
|
||||
polyOptions -dce 1;
|
||||
|
||||
for($i in $op){
|
||||
polyCrease -op 2 $i;
|
||||
setAttr ($opShape[0] + ".displaySmoothMesh") 2;
|
||||
}
|
||||
|
||||
select -r $op;
|
||||
}else{
|
||||
|
||||
polyOptions -dce 0;
|
||||
|
||||
polyCrease -op 1 `ls -sl -fl`;
|
||||
|
||||
setAttr ($opShape[0] + ".displaySmoothMesh") 2;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
global proc spSmoothOs(){ // Final Smooth
|
||||
|
||||
string $op[] = eval("listRelatives -p -f `eval(\"listRelatives -p -f `polyListComponentConversion -tv`\")`");
|
||||
|
||||
if(!`size $op`){error "You must select Object(s).\n";}
|
||||
|
||||
string $opShape[] = `listRelatives -c -f $op`;
|
||||
|
||||
polyOptions -dce 0; LowQualityDisplay;
|
||||
|
||||
for($i in $op){
|
||||
|
||||
|
||||
float $Wval[] = `polyCrease -q -v ($i + ".e[*]")`;
|
||||
|
||||
float $higherW = `spgetmaxf $Wval`;
|
||||
|
||||
int $smtLvl = (int) `ceil $higherW`;
|
||||
if($smtLvl < 1 || $smtLvl > 5){$smtLvl = 1;}
|
||||
|
||||
|
||||
string $Node[0] =`polySmooth $i`;
|
||||
|
||||
|
||||
string $ud[] = `listAttr -ud $i`;
|
||||
|
||||
for($j in $ud){
|
||||
deleteAttr -at $j $i;
|
||||
}
|
||||
|
||||
|
||||
addAttr -k 1 -ln "smoothLevel" -at "short" -dv ($smtLvl+1) -hnv 1 -min 0 -max 5 $i;
|
||||
|
||||
connectAttr ($i + ".smoothLevel") ($Node[0] + ".divisions");
|
||||
|
||||
}
|
||||
|
||||
select -r $op;
|
||||
|
||||
}
|
||||
|
||||
global proc smoothSg(){ // Smooth Based on Smoothing Groups
|
||||
|
||||
string $op[] = eval("listRelatives -p -f `eval(\"listRelatives -p -f `polyListComponentConversion -tv`\")`");
|
||||
|
||||
if(!`size $op`){error "You must select Objects.\n";}
|
||||
|
||||
|
||||
LowQualityDisplay;
|
||||
|
||||
|
||||
for($i in $op){
|
||||
|
||||
|
||||
polySelectConstraint -m 0 -dis; polySelectConstraint -m 3 -t 0x8000 -sm 1; polySelectConstraint -m 0 -dis;
|
||||
|
||||
if(size(`ls -sl -fl`)){polyCrease -op 2 $i; polyCrease -v 5.0 `ls -sl -fl`; }
|
||||
|
||||
|
||||
string $Node1[] = `polySmooth $i`;
|
||||
//basediv
|
||||
|
||||
polyCrease -op 2 $i;
|
||||
|
||||
string $ud[] = `listAttr -ud $i`;
|
||||
|
||||
for($j in $ud){
|
||||
deleteAttr -at $j $i;
|
||||
}
|
||||
|
||||
|
||||
addAttr -k 1 -ln "baseDiv" -at "short" -dv 2 -hnv 1 -min 0 -max 5 $i;
|
||||
|
||||
connectAttr ($i + ".baseDiv") ($Node1[0] + ".divisions");
|
||||
|
||||
string $Node2[] =`polySmooth $i`;
|
||||
//smoothdiv
|
||||
|
||||
|
||||
addAttr -k 1 -ln "smoothDiv" -at "short" -dv 1 -hnv 1 -min 0 -max 5 $i;
|
||||
|
||||
connectAttr ($i + ".smoothDiv") ($Node2[0] + ".divisions");
|
||||
}
|
||||
|
||||
select -r $op;
|
||||
}
|
||||
|
||||
global proc spCreasePreset(int $power){// Prefab of the creasing command
|
||||
|
||||
string $op[] = eval("listRelatives -p -f `eval(\"listRelatives -p -f `polyListComponentConversion -tv`\")`");
|
||||
|
||||
if(!`size $op`){error "Select Objects or components.\n"; }
|
||||
|
||||
polyOptions -dce 0;
|
||||
|
||||
|
||||
if(size(`filterExpand -ex 1 -sm 12`)){ // if objects are selected instead
|
||||
|
||||
for($i in $op){
|
||||
|
||||
polySelectConstraint -m 0 -dis; polySelectConstraint -m 3 -t 0x8000 -sm 1; polySelectConstraint -m 0 -dis;
|
||||
|
||||
if(!size(`ls -sl -fl`)){continue;}
|
||||
|
||||
|
||||
if($power == 1){
|
||||
polyCrease -op 2 $i;
|
||||
polyCrease -v 2.0;
|
||||
}else if($power == 2){
|
||||
polyCrease -op 2 $i;
|
||||
polyCrease -v 3.3;
|
||||
}else if($power == 3){
|
||||
polyCrease -op 2 $i;
|
||||
polyCrease -v 4.0;
|
||||
}else{
|
||||
error ("argument " + $power + " has no behavior, use 1, 2, 3 instead.\n");
|
||||
}
|
||||
|
||||
string $shape[0] = `listRelatives -c -f $i`;
|
||||
|
||||
setAttr ($shape[0] + ".osdSmoothTriangles") 1;
|
||||
|
||||
float $Wval[] = `polyCrease -q -v ($i + ".e[*]")`;
|
||||
|
||||
float $higherW = `spgetmaxf $Wval`;
|
||||
|
||||
int $smtLvl = (int) `ceil $higherW`;
|
||||
|
||||
if($smtLvl < 1){$smtLvl = 1;}
|
||||
|
||||
setAttr ($shape[0] + ".smoothLevel") ($smtLvl + 1);
|
||||
}
|
||||
select -r $op; HighQualityDisplay;
|
||||
}else{
|
||||
//components
|
||||
|
||||
if($power == 1){
|
||||
polyCrease -v 2.0;
|
||||
}else if($power == 2){
|
||||
polyCrease -v 3.0;
|
||||
}else if($power == 3){
|
||||
polyCrease -v 4.0;
|
||||
}else{
|
||||
error ("argument " + $power + " has no behavior, use 1, 2, 3 instead.\n");
|
||||
}
|
||||
|
||||
string $shape[0] = `listRelatives -c -f $op[0]`;
|
||||
|
||||
setAttr ($shape[0] + ".osdSmoothTriangles") 1;
|
||||
|
||||
float $Wval[] = `polyCrease -q -v ($op[0] + ".e[*]")`;
|
||||
|
||||
float $higherW = `spgetmaxf $Wval`;
|
||||
|
||||
int $smtLvl = (int) `ceil $higherW`;
|
||||
|
||||
if($smtLvl < 1){$smtLvl = 1;}
|
||||
|
||||
setAttr ($shape[0] + ".smoothLevel") ($smtLvl + 1);
|
||||
|
||||
|
||||
hilite; HighQualityDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
global proc spLevel(int $op){ //Utility for Levels of Weight and SubD
|
||||
|
||||
string $ops[] = eval("listRelatives -p -f `polyListComponentConversion -tv`");
|
||||
|
||||
if(!`size $ops`){error;}
|
||||
|
||||
string $edges[] = `filterExpand -ex 1 -sm 32`;
|
||||
|
||||
int $itr;
|
||||
|
||||
polyOptions -dce 0;
|
||||
|
||||
if($op == 1){//Lower Rez
|
||||
|
||||
for($i in $ops){
|
||||
|
||||
int $lvl = `getAttr ($i + ".smoothLevel")`;
|
||||
|
||||
setAttr ($i + ".smoothLevel ") ($lvl-1);
|
||||
|
||||
}
|
||||
|
||||
select -r $ops; HighQualityDisplay;
|
||||
|
||||
}else if($op == 2){//Higher Rez
|
||||
|
||||
for($i in $ops){
|
||||
|
||||
int $lvl = `getAttr ($i + ".smoothLevel")`;
|
||||
|
||||
setAttr ($i + ".smoothLevel ") ($lvl+1);
|
||||
}
|
||||
|
||||
select -r $ops; HighQualityDisplay;
|
||||
|
||||
}else if($op == 3){ //Lower Weight
|
||||
|
||||
for($i in $ops){
|
||||
|
||||
setAttr ($i + ".osdSmoothTriangles") 1;
|
||||
|
||||
float $Wval[] = `polyCrease -q -v ($i + ".e[*]")`;
|
||||
|
||||
float $higherW = `spgetmaxf $Wval`;
|
||||
|
||||
int $smtLvl = (int) `ceil $higherW`;
|
||||
|
||||
if($smtLvl < 1){$smtLvl = 1;}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
for($j in $edges){
|
||||
polyCrease -v ($Wval[$itr] - 1) $j;
|
||||
$itr++;
|
||||
}
|
||||
|
||||
setAttr ($i + ".smoothLevel") ($smtLvl);
|
||||
|
||||
}
|
||||
|
||||
select -r $ops; HighQualityDisplay;
|
||||
|
||||
}else if($op == 4){ // Higher Weight
|
||||
|
||||
for($i in $ops){
|
||||
|
||||
setAttr ($i + ".osdSmoothTriangles") 1;
|
||||
|
||||
float $Wval[] = `polyCrease -q -v ($i + ".e[*]")`;
|
||||
|
||||
float $higherW = `spgetmaxf $Wval`;
|
||||
|
||||
int $smtLvl = (int) `ceil $higherW`;
|
||||
|
||||
if($smtLvl < 1){$smtLvl = 1;}
|
||||
|
||||
|
||||
|
||||
for($j in $edges){
|
||||
polyCrease -v ($Wval[$itr] + 1) $j;
|
||||
$itr++;
|
||||
}
|
||||
|
||||
setAttr ($i + ".smoothLevel") ($smtLvl + 2);
|
||||
}
|
||||
|
||||
select -r $ops; HighQualityDisplay;
|
||||
|
||||
}else{
|
||||
error ($op + " is not an option, Try with 1, 2, 3 , 4 instead.\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
global proc spPhysicalCrease(){// physical Crease
|
||||
|
||||
global int $cp_maya_v1;
|
||||
|
||||
string $op[] = eval("listRelatives -p -f `eval(\"listRelatives -p -f `polyListComponentConversion -tv`\")`");
|
||||
|
||||
string $ControlNode[];
|
||||
|
||||
if(`currentCtx` == "cpCtx"){
|
||||
if(`cpIsBvlOp $op[0]`){
|
||||
select -r $op[0]; cpAttrSwitch; return;
|
||||
}
|
||||
}
|
||||
|
||||
if(size(`filterExpand -ex 1 -sm 12`)){
|
||||
|
||||
for($i in $op){
|
||||
|
||||
|
||||
select -r $i;
|
||||
|
||||
polySelectConstraint -m 0 -dis; polySelectConstraint -m 3 -t 0x8000 -sm 1; polySelectConstraint -m 0 -dis;
|
||||
|
||||
if($cp_maya_v1 > 2016){
|
||||
$ControlNode = `polyBevel3 -af 1 -oaf 0 -c 0 -sg 1 -sn 1 -sa 180 -o 0`;
|
||||
|
||||
}else{
|
||||
$ControlNode = `polyBevel3 -af 1 -oaf 0 -sg 1 -fn 1 -sa 180 -o 0`;
|
||||
|
||||
}
|
||||
|
||||
|
||||
string $ud[] = `listAttr -ud $i`;
|
||||
|
||||
for($j in $ud){
|
||||
deleteAttr -at $j $i;
|
||||
}
|
||||
|
||||
addAttr -ln "hOffset" -k 1 -at "doubleLinear" -hnv 1 -min 0 -dv 0.1 $i;
|
||||
|
||||
connectAttr ($i + ".hOffset") ($ControlNode[0] + ".offset");
|
||||
|
||||
addAttr -ln "hDivisions" -k 1 -at "long" -hnv 1 -min 0 -dv 1 $i;
|
||||
|
||||
connectAttr ($i + ".hDivisions") ($ControlNode[0] + ".segments");
|
||||
|
||||
}
|
||||
|
||||
select -r $op; cpAttrSwitch;
|
||||
|
||||
}else{
|
||||
if($cp_maya_v1 > 2016){
|
||||
$ControlNode = `polyBevel3 -af 1 -oaf 0 -c 0 -sg 1 -sn 1 -sa 180 -o 0`;
|
||||
|
||||
}else{
|
||||
$ControlNode = `polyBevel3 -af 1 -oaf 0 -sg 1 -fn 1 -sa 180 -o 0`;
|
||||
|
||||
}
|
||||
|
||||
string $ud[] = `listAttr -ud $op[0]`;
|
||||
|
||||
for($j in $ud){
|
||||
deleteAttr -at $j $op[0];
|
||||
}
|
||||
|
||||
addAttr -ln "hOffset" -k 1 -at "doubleLinear" -hnv 1 -min 0 -dv 0.1 $op[0];
|
||||
|
||||
connectAttr ($op[0] + ".hOffset") ($ControlNode[0] + ".offset");
|
||||
|
||||
addAttr -ln "hDivisions" -k 1 -at "long" -hnv 1 -min 0 -dv 1 $op[0];
|
||||
|
||||
connectAttr ($op[0] + ".hDivisions") ($ControlNode[0] + ".segments");
|
||||
|
||||
select -r $op; cpAttrSwitch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
global proc spShowCreaseEd(){
|
||||
python "from maya.app.general import creaseSetEditor; creaseSetEditor.showCreaseSetEditor()";
|
||||
}
|
||||
31
2025/scripts/modeling_tools/creaseplus/__init__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
CreasePlus MEL Tool Wrapper
|
||||
Provides Python interface to launch the CreasePlus MEL tool
|
||||
"""
|
||||
|
||||
import maya.cmds as cmds
|
||||
import maya.mel as mel
|
||||
import os
|
||||
|
||||
|
||||
def start():
|
||||
"""Launch CreasePlus MEL tool"""
|
||||
# Get the directory containing this script
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
icon_dir = os.path.join(script_dir, 'icons').replace('\\', '/')
|
||||
|
||||
# Set global MEL variable for icon path
|
||||
mel.eval(f'global string $cp_icon_path = "{icon_dir}/"')
|
||||
|
||||
# Source the MEL script
|
||||
mel_script = os.path.join(script_dir, 'CreasePlus.mel').replace('\\', '/')
|
||||
|
||||
try:
|
||||
mel.eval(f'source "{mel_script}"')
|
||||
mel.eval('cpUi')
|
||||
print(f"CreasePlus: UI launched successfully")
|
||||
except Exception as e:
|
||||
print(f"CreasePlus: Error launching UI: {e}")
|
||||
46
2025/scripts/modeling_tools/creaseplus/cp_(read)commands.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
cpAttrSwitch;
|
||||
//Use to toggle between attributs, select object first.
|
||||
cpDisplayBool;
|
||||
//Perform boolean with operands display
|
||||
cpKeepBool;
|
||||
//perform boolean preserving the operands
|
||||
cpHbevel;
|
||||
//perform hBevel
|
||||
cpMirror;
|
||||
//Mirrors the mesh
|
||||
cpPanelBool;
|
||||
//Performs panel boolean : creates panel cutting/carving with operands
|
||||
cpMeshSlicer;
|
||||
//slice mesh with curve, select curve and mesh first.
|
||||
cpTglBoolv;
|
||||
//Toggle visibility of the boolean objects
|
||||
cpHedgeSel;
|
||||
//Select Hard edges of the selected objects
|
||||
cpShapeShifter;
|
||||
//triggers ShapeShifter if found on the disk
|
||||
cpGoz;
|
||||
//Send meshes to Zbrush, with nGon cleanup
|
||||
cpQsmooth;
|
||||
//Apply a 30 degree smoothing to selected objects are components
|
||||
cpHardDisplay;
|
||||
//Toggles display of hard edges in realtime
|
||||
cpmakeUV;
|
||||
//make UV based on hard edges
|
||||
cpCurveCham;
|
||||
//Perform curve bevel (must draw two curve point around a corner before use)
|
||||
cpTransferBevel;
|
||||
//transfer bevel settings from one object to the others
|
||||
cpCleanAttrs;
|
||||
//kills custom attributes of the objects
|
||||
cpBakThatNod;
|
||||
//Bake selected Node from the channel Box(select object then node)
|
||||
cpInstanceBool;
|
||||
// perform Instance bool
|
||||
|
||||
cpAttachCurve;
|
||||
cpCurveBool;
|
||||
cpCloseCurve;
|
||||
cpCurveMultiply;
|
||||
|
||||
cpUi;
|
||||
//Calls Crease+ 's UI
|
||||
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_bevel.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_bool.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_curve_attach.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_curve_bevel.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_curve_bool.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_curve_close.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_curve_draw.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_eye.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_goz.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_hard_display.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_keep_bool.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_mesh_slicer.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_mirror.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_panelbool.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_quicksmooth.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_sel_hard.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/cp_ss.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/sp_crease.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/sp_nocrease.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/sp_smooth.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
2025/scripts/modeling_tools/creaseplus/icons/sp_weight_tool.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
967
2025/scripts/modeling_tools/gs_curvetools/core/tooltips.md
Normal 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 can’t 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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||