You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Tools/maya/script/LumberyardProxyTool.py

445 lines
20 KiB
Python

#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
from maya import cmds
import math
import maya.mel
#defines the shape labels
g_shapeTypes = ['box', 'capsule', 'sphere']
g_materialNames = ['mat_proxy_phys', 'no material']
g_boxIndex = 0
g_capsuleIndex = 1
g_sphereIndex = 2
g_orientTypes = ['x', 'y', 'z', 'xCenter', 'yCenter', 'zCenter']
g_defaultOrientTypeIndex = 0
g_xIndex = 0
g_yIndex = 1
g_zIndex = 2
g_xCenterIndex = 3
g_yCenterIndex = 4
g_zCenterIndex = 5
g_centerString = 'Center'
##########
##The colors that will be used in the editor
##########
g_UIColor = [0.165, 0.165, 0.165]
g_defaultMaterialColor = [0, 1, 0]
##########
##Width and height definitions for ui elements
##########
g_uiProxyWidthColumnWidth = 30
g_controlHeight = 22
g_listboxHeight = 35
g_buttonSpace = 4
g_rowItemHeight = 22
g_uiShapePulldownColumnWidth = 75
g_uiOrientPulldownColumnWidth = 50
g_uiMaterialNamePulldownWidth = 120
g_uiMaterialFieldColumnWidth = 125
g_scrollBarPadding = 36
g_buttonMarginPadding = 10
g_marginWidth = 5
g_boxWidth = 400
g_uiColumnWidth = [g_uiProxyWidthColumnWidth, g_uiShapePulldownColumnWidth, g_uiOrientPulldownColumnWidth, g_uiMaterialFieldColumnWidth]
#TODO: make width/height dynamic
g_windowHeight = 120
g_windowWidth = 318
#default name of display layer where proxies will reside
g_nameOfDisplayLayer = 'lyr_lumberyard_phys'
#postfix of the physics proxy
g_physPostfix = '_phys';
#the default width of proxies
g_proxyShapeDefaultWidth = 10
#estimating that the proxy width is 1/10th of the hip height
g_proxyWidthRatioToHeight = 0.1
class ProxyObject(object):
##This class represents both the variables and the ui of the proxy editor
def __init__(self) :
#defines the ui of the width text field
self.uiTextFieldWidth = ''
#defines the value of the width and depth
self.width = g_proxyShapeDefaultWidth
#defines the ui of the shape drop down ui
self.menuDropDownShape = ''
#defines this proxy's shape
self.shape = g_shapeTypes[g_boxIndex]
#defines this proxies material name
self.proxyMaterialName = ''
#defines the ui used to modify orientation
self.menuDropDownOrient = ''
#defines the orientation of the proxy
self.objectOrient = g_orientTypes[g_defaultOrientTypeIndex]
self.menuDropDownMaterialName = ''
proxyCreationSettings = ProxyObject()
def LumberyardProxyTool():
##Create the main window. If it exists, delete it, and then create it
if cmds.window('LUMBERYARDTOOL_PROXY_WINDOW', exists=True):
cmds.deleteUI('LUMBERYARDTOOL_PROXY_WINDOW')
LumberyardProxyToolUI()
def LumberyardProxyToolUI():
##This script defines the window or primary canvas of the ui.
lumberyardProxyWindow = cmds.window('LUMBERYARDTOOL_PROXY_WINDOW', title = "Proxy Tool (Experimental)", sizeable = False, iconName = 'Proxy Tool', widthHeight = (g_windowWidth, g_windowHeight))
LumberyardToolCreateProxyFrameMain()
cmds.showWindow(lumberyardProxyWindow)
cmds.window(lumberyardProxyWindow, edit = True, widthHeight = (g_windowWidth, g_windowHeight))
def LumberyardToolCreateProxyFrameMain():
##When the window is first open this is what the user will see
cmds.columnLayout('LUMBERYARDTOOL_PROXY_LAYOUT', adjustableColumn = True)
cmds.text(height = g_controlHeight, enable = True, font = 'tinyBoldLabelFont', align = 'center', width = g_uiProxyWidthColumnWidth+3, label = 'Select joints and click "Create Proxies"', backgroundColor = g_UIColor)
cmds.setParent('LUMBERYARDTOOL_PROXY_WINDOW')
cmds.rowLayout(numberOfColumns = 4, columnWidth4 = g_uiColumnWidth, backgroundColor = g_UIColor)
#title labels for all the variable categories that we'll have control over
cmds.text(height = g_controlHeight, enable = True, font = 'tinyBoldLabelFont', align = 'left', width = g_uiProxyWidthColumnWidth+3, label = 'width', backgroundColor = g_UIColor)
cmds.text(height = g_controlHeight, enable = True, font = 'tinyBoldLabelFont', align = 'left', width = g_uiShapePulldownColumnWidth, label = 'shape', backgroundColor = g_UIColor)
cmds.text(height = g_controlHeight, enable = True, font = 'tinyBoldLabelFont', align = 'left', width = g_uiOrientPulldownColumnWidth+25, label = 'orient', backgroundColor = g_UIColor)
cmds.text(height = g_controlHeight, enable = True, font = 'tinyBoldLabelFont', align = 'left', width = g_uiMaterialFieldColumnWidth + g_marginWidth, label = 'material name', backgroundColor = g_UIColor)
##This is the editable area of the ui that will be populated by our variables. In the beginning it is unpopulated but has instruction on what to do
cmds.setParent('LUMBERYARDTOOL_PROXY_LAYOUT')
cmds.scrollLayout ('LUMBERYARDTOOL_PROXY_LISTBOX', height = g_listboxHeight, width = g_boxWidth , backgroundColor = [0, 0, 0], visible = True)
cmds.setParent('LUMBERYARDTOOL_PROXY_LISTBOX')
#create a row
cmds.rowLayout(numberOfColumns = 4, columnWidth4 = g_uiColumnWidth)
#create the width ui
LumberyardCreateWidthTextField()
#create the shape ui
LumberyardCreateShapePullDown()
#create the orient ui
LumberyardCreateOrientPullDown()
LumberyardCreateMaterialPullDown()
#create the ui underneath the editable variables window. This is the canvas for our create button
cmds.setParent('LUMBERYARDTOOL_PROXY_LAYOUT')
cmds.setParent('..')
cmds.rowColumnLayout(numberOfRows = 1, width = g_boxWidth , columnSpacing = [1, g_buttonSpace])
cmds.button(label = 'Create Proxies', width = g_windowWidth-4, command = LumberyardCreateProxies, backgroundColor = g_UIColor)
def LumberyardCreateWidthTextField():
##This function creates the ui element that defines the width and depth of the proxy
#get the width of the proxy from the class
uiTextFieldProxyWidthValue = g_proxyShapeDefaultWidth
#create the ui text field
proxyCreationSettings.uiTextFieldWidth = cmds.textField(width = g_uiProxyWidthColumnWidth , backgroundColor = g_UIColor, text = uiTextFieldProxyWidthValue)
#add a command that will add functionality when the textfield is changed
cmds.textField(proxyCreationSettings.uiTextFieldWidth, edit = True, changeCommand = lambda *args: LumberyardChangeProxyWidth(cmds.textField(proxyCreationSettings.uiTextFieldWidth, query = True, text = True)))
LumberyardChangeProxyWidth(g_uiProxyWidthColumnWidth)
def LumberyardChangeProxyWidth(widthInput):
##This function is run when the width text field is updated
#get current width
currentWidth = proxyCreationSettings.width
#determine if this entry is a float
if LumberyardFloatConfirm(widthInput) == True:
if float(widthInput) > 0:
#if the entry is a float and greater than zero, update the class with the new width
proxyCreationSettings.width = widthInput
else:
#zero or negative floats are not a valid width value, so revert to previous value
cmds.textField(proxyCreationSettings.uiTextFieldWidth, edit = True, text = currentWidth)
else:
#if the entry is not a float, revert it to its previous value
cmds.textField(proxyCreationSettings.uiTextFieldWidth, edit = True, text = currentWidth)
def LumberyardCreateOrientPullDown():
##This function creates the ui pulldown for orientation
#create the ui pulldown
orientOptionPullDown = cmds.optionMenu(label = '', width = g_uiShapePulldownColumnWidth, backgroundColor = g_UIColor, changeCommand = lambda *args: LumberyardProxySetOrientType())
#add it to the class
proxyCreationSettings.menuDropDownOrient = orientOptionPullDown
#populate the pulldown with the predefined orient types
for orient in g_orientTypes:
cmds.menuItem('button_' + orient, label = orient)
LumberyardProxySetOrientType()
def LumberyardProxySetOrientType():
##This function is launched when the orient value is changed in the editor
#get the new value from the menu
orientString = (cmds.optionMenu(proxyCreationSettings.menuDropDownOrient, query = True, value = True))
#store the new value in the class
proxyCreationSettings.objectOrient = orientString
def LumberyardCreateShapePullDown():
##This function creates the ui pulldown for shape types in the editor
#create the ui pulldown
shapeOptionPullDown = cmds.optionMenu(label = '', width = g_uiShapePulldownColumnWidth, backgroundColor = g_UIColor,changeCommand = lambda *args: LumberyardProxySetShapeType())
#add it to the class
proxyCreationSettings.menuDropDownShape = shapeOptionPullDown
#populate the pulldown with the predefined shape types
for shapeType in g_shapeTypes:
cmds.menuItem('button_' + shapeType, label = shapeType)
LumberyardProxySetShapeType()
def LumberyardCreateMaterialPullDown():
##This function creates the ui pulldown for material name
#create the ui pulldown
materialOptionPullDown = cmds.optionMenu(label = '', width = g_uiMaterialFieldColumnWidth, backgroundColor = g_UIColor, changeCommand = lambda *args: LumberyardProxySetMaterialName())
#add it to the class
proxyCreationSettings.menuDropDownMaterialName = materialOptionPullDown
#populate the pulldown with the predefined materialName types
for materialName in g_materialNames :
cmds.menuItem('button_' + materialName, label = materialName)
LumberyardProxySetMaterialName()
def LumberyardProxySetMaterialName():
materialNameString = (cmds.optionMenu(proxyCreationSettings.menuDropDownMaterialName, query = True, value = True))
proxyCreationSettings.proxyMaterialName = materialNameString
def LumberyardProxySetShapeType():
##This function is launched when the shape value is changed in the editor
#get the new value from the menu
shapeString = (cmds.optionMenu(proxyCreationSettings.menuDropDownShape, query = True, value = True))
#store the new value in the class
proxyCreationSettings.shape = shapeString
def LumberyardAddMaterialToProxy(inputProxy):
##This function adds a material to a proxy shape object
#get the material name from the class
thisMaterialName = proxyCreationSettings.proxyMaterialName
#get the shading group
shadingGroup = thisMaterialName + 'SG'
#variable that defines the shader
shader = ''
#if the material does not exist, create a shader group that supports it
if cmds.objExists(thisMaterialName) == False:
shader = cmds.shadingNode('phong', asShader = True, name = thisMaterialName)
#add attribute that will define this proxy material
cmds.addAttr(shader, longName = 'lumberyardPhysMaterial', defaultValue = 1)
shadingGroup = cmds.sets(renderable = True, noSurfaceShader = True, empty = True, name = shadingGroup)
cmds.connectAttr(shader +'.outColor', shadingGroup + '.surfaceShader')
cmds.setAttr(shader + '.color', g_defaultMaterialColor[0], g_defaultMaterialColor[1], g_defaultMaterialColor[2], type = 'double3')
else:
shader = 'thisMaterialName'
#apply the shading group to the proxy object
cmds.sets(inputProxy[0], edit = True, forceElement = shadingGroup )
def CreateProxyPositionFromChildren(jointNode):
#get the children. the joint's children define the length of the proxy
jointChildren = cmds.listRelatives(jointNode, children = True, path = True, type = 'joint')
#get the joint node's position in world space
jointStartPosition = cmds.xform(jointNode, query = True, worldSpace = True, translation = True)
#get the average joint position for all of the child joints this will determine the proxy's length
sumOfJointPositionX = 0.0
sumOfJointPositionY = 0.0
sumOfJointPositionZ = 0.0
proxyPositionX = jointStartPosition[0]
proxyPositionY = jointStartPosition[1]
proxyPositionZ = jointStartPosition[2]
#if a joint has children, find out the length of the average child joints
if (jointChildren):
for jointChild in jointChildren:
jointPositionWorldSpace = cmds.xform( jointChild, query = True, worldSpace = True, translation = True)
sumOfJointPositionX = sumOfJointPositionX + jointPositionWorldSpace[0]
sumOfJointPositionY = sumOfJointPositionY + jointPositionWorldSpace[1]
sumOfJointPositionZ = sumOfJointPositionZ + jointPositionWorldSpace[2]
averageJointPositionX = sumOfJointPositionX / len(jointChildren)
averageJointPositionY = sumOfJointPositionY / len(jointChildren)
averageJointPositionZ = sumOfJointPositionZ / len(jointChildren)
#move to the midpoint of joint and averaged child joints
proxyPositionX = ((averageJointPositionX + jointStartPosition[0])/2)
proxyPositionY = ((averageJointPositionY + jointStartPosition[1])/2)
proxyPositionZ = ((averageJointPositionZ + jointStartPosition[2])/2)
proxyPosition = [proxyPositionX, proxyPositionY, proxyPositionZ]
return proxyPosition
def LumberyardCreateProxies(*args):
##This function creates the proxy objects and parents them to joints
#create display layers for proxy's visibility to easily be toggled
LumberyardCreateProxyLayer()
#variable for the display layer
layerPhys = g_nameOfDisplayLayer
#make a list of all of the selected objects
selectedObjects = cmds.ls(selection = True)
#if nothing is selected throw a warning
if (len(selectedObjects) == 0):
cmds.warning('No joints selected. Select Joints and press the Add Joints button.')
else:
for selectedObject in selectedObjects:
if cmds.objectType(selectedObject, isType = 'joint' ) == True:
shapeOfProxy = proxyCreationSettings.shape
#get the joint node's position in world space
jointStartPosition = cmds.xform(selectedObject, query = True, worldSpace = True, translation = True)
proxyPosition = jointStartPosition
proxyWidth = float(proxyCreationSettings.width)
proxyLength = proxyWidth
if (g_centerString in proxyCreationSettings.objectOrient) == False:
proxyPosition = CreateProxyPositionFromChildren(selectedObject)
#find out the length of the average child joints
distanceBetweenPoints = math.sqrt(math.pow((jointStartPosition[0] - proxyPosition[0]), 2) + math.pow((jointStartPosition[1] - proxyPosition[1]) ,2) + math.pow((jointStartPosition[2] - proxyPosition[2]), 2))
proxyLength = distanceBetweenPoints * 2
#create the proxy shape
if (shapeOfProxy == g_shapeTypes[g_boxIndex]):
proxyShape = cmds.polyCube(height = proxyLength, width = proxyWidth, depth = proxyWidth, createUVs = 1,constructionHistory = True)
else:
proxyRadius = proxyWidth/2
if (shapeOfProxy == g_shapeTypes[g_capsuleIndex]):
if (proxyLength > proxyWidth):
proxyShape = cmds.polyCylinder(radius = proxyRadius, height = proxyLength - (proxyRadius *2), roundCap = True, subdivisionsCaps = 3)
else:
proxyShape = cmds.polySphere(radius = proxyRadius * 2, subdivisionsAxis = 16, subdivisionsHeight = 16)
else:
proxyShape = cmds.polySphere(radius = proxyRadius * 2, subdivisionsAxis = 16, subdivisionsHeight = 16)
#add object to the phys display layer
cmds.editDisplayLayerMembers( layerPhys, proxyShape[0])
#add an attribute to easily find our created proxy
cmds.addAttr(proxyShape, longName = 'lumberyardPhysProxy', defaultValue = 1)
#add material to proxy
if (proxyCreationSettings.proxyMaterialName != 'no material'):
LumberyardAddMaterialToProxy(proxyShape)
#add orientation to proxy
LumberyardOrientProxy(proxyShape[0])
#move proxy into place
cmds.move(proxyPosition[0], proxyPosition[1], proxyPosition[2], proxyShape, worldSpace = True)
#orient the proxy so its aiming toward the parent joint. can we do this with vector math instead?
aimConsProxy = cmds.aimConstraint(selectedObject, proxyShape)
cmds.delete(aimConsProxy)
orientConsProxy = cmds.orientConstraint(selectedObject, proxyShape, skip = 'none')
cmds.delete(orientConsProxy)
#if the orient pulldown defines this as a center, center its orient on the joint.
if (g_centerString in proxyCreationSettings.objectOrient):
cmds.move(jointStartPosition[0], jointStartPosition[1],jointStartPosition[2], proxyShape[0], worldSpace = True)
else:
cmds.move(jointStartPosition[0], jointStartPosition[1], jointStartPosition[2], proxyShape[0] + ".scalePivot", proxyShape[0] + ".rotatePivot", worldSpace = True)
#parent it to the joint
cmds.parent(proxyShape[0], selectedObject)
#freeze the proxy. this will zero out its translate values relative to its parent joint
cmds.makeIdentity(proxyShape[0], apply = True, scale = 1, rotate = 1, translate = 1, normal = 0)
#delete construction history
cmds.delete(proxyShape[0],constructionHistory=True)
#rename the proxy with the proper name
cmds.rename(proxyShape[0], selectedObject + g_physPostfix)
else:
cmds.warning(selectedObject + " is not a joint. No proxy created.")
def LumberyardOrientProxy(proxyObjectInput):
##This function orients the proxy based on the settings in the editor ui
if proxyCreationSettings.objectOrient == g_orientTypes[g_xIndex] or proxyCreationSettings.objectOrient == g_orientTypes[g_xCenterIndex]:
cmds.xform(proxyObjectInput, rotateAxis = [0, 90, 90])
if proxyCreationSettings.objectOrient == g_orientTypes[g_yIndex] or proxyCreationSettings.objectOrient == g_orientTypes[g_yCenterIndex]:
cmds.xform(proxyObjectInput, rotateAxis = [0, 0, 0])
if proxyCreationSettings.objectOrient == g_orientTypes[g_zIndex] or proxyCreationSettings.objectOrient == g_orientTypes[g_zCenterIndex]:
cmds.xform(proxyObjectInput, rotateAxis = [90, 0, 0])
def LumberyardCreateProxyLayer():
##This function creates a layer for our proxy objects so visibility may be easily toggled
#if there is not already a display layer for the proxies, create one
if cmds.objExists(g_nameOfDisplayLayer) == False:
cmds.createDisplayLayer(name = g_nameOfDisplayLayer)
def LumberyardFloatConfirm(inputString):
##This function checks to see if the entered string is a float value and returns true or false
try:
float(inputString)
except ValueError:
return False
else:
return True