Python is an object oriented programming language (OOP) that was created by Guido von Rossum in 1981. In 2021, Python has a considerable community behind it and is one of the most used programming languages around the world and in the CG/pipeline fields. In thiedgess blog, we will look at how we can use Python to interact with Maya. Since maya was created, different flavors of python interfaces have been released as Pymel, Mayapy and OpenMaya. While they are all supposed to be able to achieve globally the same result they differ in their philosophy.
Environnement Setup
There is several way to setup env variables in Maya.
Using sys python package directly from code editor or userSetup.py
import sys
sys.append("/path_to_scripts_folder")
Adding Maya env variables either in system global var or in Maya.env (Maya.env location depends on MAYA_APP_DIR env var) see the documentation for more information.
SHARED_MAYA_DIR = HostName:/usr/localhome/public/maya/<version>
MAYA_SCRIPT_PATH = $SHARED_MAYA_DIR/scripts:$MAYA_APP_DIR/scripts/custom
MAYA_PLUG_IN_PATH = $SHARED_MAYA_DIR/plug-ins
TMPDIR = /disk2/tempspace #override tmp folder
Adding a path files (.pth) to maya python site-packages installation folder. This will add to sys.path all folders mentionned in the .pth file when maya python is runned. Other alternative is to manually add the folder containing the .pth file using site python package in userSetup.py
import site
site.addsitedir("path_to_pth_file_folder") # Warning: should be the folder path
Using Maya modules
Batch mode init (Maya headless)
maya -batch -file someMayaFile.mb -command "file -save"
Using Maya.cmds in batch mode
from maya import cmds, standalone
standalone.initialize() # this is only needed in batch mode
Open web documentation
cmds.help('polyCube', doc=True, language='python')
Reload modules
import mySuperScript
reload(mySuperScript)
Run Mel Command
from maya import mel
def run_mel_script():
mel.eval("source myMelScript;")
mel.eval("myMelScript;")
Get vertex/edge/face numbers
# create a sphere
transform_node, shape_node = cmds.polySphere()
vertex_number = cmds.polyEvaluate(shape_node, vertex=True)
edge_number = cmds.polyEvaluate(shape_node, edge=True)
face_number = cmds.polyEvaluate(shape_node, face=True)
Get vertex/edge/face numbers
# create a sphere
transform_node, shape_node = cmds.polySphere()
vertex_number = cmds.polyEvaluate(shape_node, vertex=True)
edge_number = cmds.polyEvaluate(shape_node, edge=True)
face_number = cmds.polyEvaluate(shape_node, face=True)
Select vertex/edge/face
# create a sphere
transform_node, shape_node = cmds.polySphere()
cmds.select(shape_node + ".vtx[0:5]", replace=True) # vertex
cmds.select(shape_node + ".e[0:5]", replace=True) #edge
cmds.select(shape_node + ".f[0:5]", replace=True) #face
Move edges
# create a sphere
transform_node, shape_node = cmds.polySphere()
cmds.select(shape_node + ".e[0]", replace=True)
cmds.move(1.0, 2.0, 3.0, relative=True)
# alternative use polyMoveVertex
cmds.polyMoveVertex(shape_node + ".e[0]", t=[1.0, 2.0, 3.0]) # this is slower than cmds.move
Move pivot
cmds.ls(selection=True) # select a sphere
cmds.xform(pivot=(0,0,0)) # move pivot at origin
cmds.makeIdentity(apply=True) # freeze transforms so object starts at 0, 0, 0
List Relatives/Get child/parent nodes
cmds.listRelatives(children=True, allDescendents=True) # get child nodes
cmds.listRelatives(parent=True) # get direct parent
cmds.listRelatives(parent=True, fullPath=True) # get fullpath
Create curves
points = []
points.append( (-0.5, 0 , 0) )
points.append( ( 0, 0 , 0) )
points.append( ( 0.5, 0 , 0) )
cmds.curve(degree=1, p=points)
Create faces
points = []
points.append( (-0.5, 0 , 0) )
points.append( ( 0, 0 , 0) )
points.append( ( 0.5, 0 , 0) )
points.append( (-0.5, 0 , 0) )
cmds.polyCreateFacet(p=points)
Triangulate/Quadrangulate faces
points = []
points.append( (-0.5, 0 , 0) )
points.append( ( 0, 0 , 0) )
points.append( ( 0.5, 0 , 0) )
points.append( (-0.5, 0 , 0) )
cmds.polyCreateFacet(p=points)
# triangulate newly created face
cmds.polyTriangulate()
# quadrangulate from a triangulated face, Warning this doesn't always works well
cmds.polyQuad()
Create a polygon
f1 = [ (0, 0 , 0), ( 1, 0 , 0), ( 1, 1 , 0), (0, 1 , 0)]
f2 = [ (0, 0 , 0), ( 0, 0 ,1), ( 0, 1 , 1), (0, 1 , 0)]
f3 = [ (0, 0 , 1), (0, 1 , 1), ( 1, 1 ,0), (1, 0 , 0)]
faces = [
cmds.polyCreateFacet( p=f1, texture=1),
cmds.polyCreateFacet( p=f2, texture=1),
cmds.polyCreateFacet( p=f3, texture=1)
]
cmds.select( faces[1], faces[2], faces[3], replace=True )
# unite polygons
obj = cmds.polyUnite()
# merge redundant vertexes
cmds.select(obj[0] + ".vtx[:]")
cmds.polyMergeVertex(distance=0.0001)
getAttr & setAttr
# get all obj non default attributes
custom_user_attrs = cmds.listAttr(obj, userDefined=True)
# add a new custom attr
my_new_attr = "custom_attr"
if my_new_attr not in custom_user_attrs:
cmds.addAttr(
obj,
longName=my_new_attr,
keyable=True
)
# set attr
my_new_value = 3
cmds.setAttr( "{}.{}".format(obj, my_new_attr), my_new_value)
# get attr
my_new_Attr_value = cmds.getAttr("{}.{}".format(obj, my_new_attr))
Construction history
history = cmds.listHistory(obj) # Not super reliable as it can be deleted by user
cmds.delete(ch=True) # Delete construction history
Querying UV data
transform, shape = cmds.ls(selection=True)
uvs = cmds.polyEvaluate(shape, uvComponent=True)
uvPos = cmds.polyEditUV(shape + ".map[0]", query=True)
print "Num UVs => {} \nPos uv => {}".format(str(uvs), uvPos)
Get edges’ uv/vertexes
elt1 = cmds.polyListComponentConversion(shape + ".e[0]", fromEdge=True, toUV=True) # retrieve edge uv
elt2 = cmds.polyListComponentConversion(shape + ".e[0]", fromEdge=True, toVertex=True) # retrieve edge vertexes
Laying out UV
cmds.polyProjection(shape + ".f[*]", type="planar") # type value could be either planar / cylindrical / spherical
cmds.polyProjection(shape + ".f[0:50]", shape + ".f[75:80]", type="planar") # could receive several inputs
Create shading node network
# Creating a toon shader network
shader_node = cmds.shadingNode('blinn', asShader=True)
ramp_node = cmds.shadingNode('ramp', asTexture=True)
sample_node = cmds.shadingNode('samplerInfo', asUtility=True)
cmds.setAttr(ramp_node + ".interpolation", 0)
cmds.setAttr(ramp_node + ".colorEntryList[0].position", 0)
cmds.setAttr(ramp_node + ".colorEntryList[1].position", 0.3)
cmds.setAttr(ramp_node + ".colorEntryList[0].color", 0,0,0, type="float3") # float3 is because we provide 3 floats input
cmds.setAttr(ramp_node + ".colorEntryList[1].color", 1,0,0, type="float3") # float3 is because we provide 3 float input
cmds.connectAttr(sample_node + ".facingRatio", ramp_node + ".vCoord")
cmds.connectAttr(ramp_node + ".outColor", shader_node + ".color")
Connect all shading nodes attributes at once
# Creating a File network
file_tex = cmds.shadingNode('file', asTexture=True)
place_tex = cmds.shadingNode('place2dTexture', asUtility=True)
cmds.defaultNavigation(connectToExisting=True, source=place_tex, destination=file_tex)
Disconnect shading Node
# following previous File network code
cmds.disconnectAttr(place_tex + ".offset", file_tex + ".offset")
Check if node is a geo
def is_geo_generator(obj):
geo_type = ["mesh", "nurbsSurface", "subdiv"]
shape = cmds.listRelatives(obj, shapes=True)[0]
shape_type = cmds.nodeType(shape) # retrieve node type where geo nodeType being in geo_type
if shape_type in geo_type:
return True
return False
Set default shader to unshaded obj
def is_shaded(obj):
cmds.select(obj, replace=True)
cmds.hyperShade(obj, shaderNetworksSelectionMaterialNodes=True)
result = cmds.ls(selection=True)
if result:
return False
return True
# Get unshaded obj
transforms = cmds.ls(type="transform")
unshaded_nodes = [ node for node in transforms if is_shaded(node) is False]
# create shader
blinn_shader = cmds.shadingNode("blinn", asShader=True)
cmds.setAttr(blinn_shader + ".color", 0, 1, 1, type="double3")
# Set shader obj
for unshaded_node in unshaded_nodes:
cmds.select(unshaded, replace=True)
cmds.hyperShade(assign=blinn_shader)
Found nodes that are assigned a shader
# following previous code
def get_obj_from_shader(shader):
cmds.hyperShade(objects=shader)
objects = cmds.ls(selection=True)
return objects
blinn_shaded_nodes = get_obj_from_shader( blinn_shader )
Use shading nodes with 3D geo data
# use a plus minus average shading node to keep an object at the center
def center():
objs = cmds.ls(selection=True)
if len(objs) < 3:
cmds.error("Please select at least 3 nodes and retry!")
center_obj = objs.pop()
avg_node = cmds.shadingNode('plusMinusAverage', asUtility=True)
cmds.setAttr(avg_node + ".operation", 3) #set operation to average
px, py, pz = [0,0,0]
for i, obj in enumerate(objs):
cmds.connectAttr(obj + ".translateX", avg_node + ".input3D[{}].input3Dx".format(i))
cmds.connectAttr(obj + ".translateY", avg_node + ".input3D[{}].input3Dy".format(i))
cmds.connectAttr(obj + ".translateZ", avg_node + ".input3D[{}].input3Dz".format(i))
cmds.connectAttr(avg_node + ".output3D.output3Dx", center_obj + ".translateX")
cmds.connectAttr(avg_node + ".output3D.output3Dy", center_obj + ".translateY")
cmds.connectAttr(avg_node + ".output3D.output3Dz", center_obj + ".translateZ")
cmds.select("pSphere1", "pSphere2", "pCylinder1", replace=True)
center()
Create joints
root = cmds.joint(name="root", position =[2.5, -1, 0])
for i1 in range(5):
for i2 in range(3):
cmds.joint(name="root_finger{}_joint{}".format(i1, i2), position=(i1*2, i2*3, 0) )
cmds.select(root, replace=True)
# cmds.pickWalk(direction="Up") # could be use to travel trhough Maya hierarchy
Create set-driven key
def set_driven_key(objs, attr, min_driver_val, max_driver_val, min_driven_val, max_driven_val):
# set driver attr
driver_joint = objs[0]
driver_attr = driver_joint + attr
original_driver_val = cmds.getAttr(driver_attr)
childrens = cmds.listRelatives(children=True, allDescendents=True)
for child in childrens:
driven_attr = child + attr
# set driven key for max and min
cmds.setAttr(driver_attr, min_driver_val)
cmds.setDrivenKeyframe(driven_attr, cd=driver_attr, value=min_driven_val, driver_value=min_driver_val)
cmds.setAttr(driver_attr, max_driver_val)
cmds.setDrivenKeyframe(driven_attr, cd=driver_attr, value=max_driven_val, driver_value=max_driver_val)
cmds.setAttr(driver_attr, original_driver_val)
objs = cmds.ls(selection=True)
set_driven_key(
objs=objs,
attr=".rotateX",
min_driver_val=0,
max_driver_val=30,
min_driven_val=0,
max_driven_val=30
)
Creating custom attributes
# regular attr
cmds.addAttr(
obj,
shortName="my_custom_regular_attr",
longName="my_custom_regular_attr",
defaultValue=0,
minValue=-1,
maxValue=1,
keyable=True
)
# enum attr
cmds.addAttr(
obj,
shortName="my_custom_enum_attr",
longName="my_custom_enum_attr",
attributeType="enum",
enumName="Val1:Val2:Val3", # enum options separated by ':'
keyable=True
)
# bool attr
cmds.addAttr(
obj,
shortName="my_custom_bool_attr",
longName="my_custom_bool_attr",
attributeType="bool",
keyable=True
)
# color attr
cmds.attr(
obj,
shortName="my_custom_color_attr",
longName="my_custom_color_attr",
attributeType="float3",
usedAsColor=True
)
for color in ["colorR","colorG","colorB"]:
cmds.addAttr(
obj,
shortName="{}_attr".format(color),
longName="{}_attr".format(color),
attributeType="float",
parent="my_custom_color_attr"
)
Locking attributes
attr = obj + ".rotateX"
cmds.setAttr(attr, edit=True, lock=True, keyable=True, channelBox=False)
Create locators
cmds.spaceLocator(name="loc1")
Replace locator per a joint node
loc1 = cmds.spaceLocator(name="loc1")
# ... some code
# transform loc1 position
cmds.xform(loc1, absolute=True, translation=(1,0,0))
# get loc1 position
pos = cmds.xform(loc1, query=True, translation=True, worldSpace=True)
# create a joint at this place
cmds.joint(name="joint@loc1", position=pos)
Set inverse kinematic Handle (IK)
cmds.ikHandle(startJoint=hip_joint, endEffector=ankleJoint)
Limit joints rotations
cmds.setAttr(joint_node + ".jointTypeX", 0) # disable rotation around x axis
cmds.setAttr(joint_node + ".jointTypeZ", 0) # disable rotation around z axis
# enable rotation on Y axis between -45 to 45 degree only
cmds.transformLimits(joint_node, rotationY=(-45, 45), enableRotationY=(1,1))
# enable rotation on Y axis between -45 degree without upper limit
cmds.transformLimits(joint_node, rotationY=(-45, 45), enableRotationY=(1,0))
# enable rotation on Y axis between any value up to 45 degree max
cmds.transformLimits(joint_node, rotationY=(0, 45), enableRotationY=(0,1))
Get obj’s keyframes informations
obj = cmds.ls(selection=True)[0]
animated_attributes = cmds.listAnimatable(obj)
shot_frame_range=50
for animated_attribute in animated_attributes:
keyframe_count = cmds.keyframe(animated_attribute, query=True, keyframeCount=True)
if keyframe_count:
#index flag is only used if we don't want full shot frame range
times = cmds.keyframe(animated_attribute, query=True, index=(0,shot_frame_range), timeChange=True)
values = cmds.keyframe(animated_attribute, query=True, index=(0,shot_frame_range), valueChange=True)
print "{}.{}@{} = {}".format(obj, animated_attribute, times, values)
Animation layers
def create_anim_layer(layer_name):
animation_layers=cmds.animLayer(query=True, root=True)
# check if layer already exists
if animation_layers:
sub_anim_layers = cmds.animLayer(animation_layers, query=True, children=True)
if sub_anim_layers and layer_name in sub_anim_layers:
print "Error: a layer with this name already exists"
return
cmds.animLayer(layer_name)
# create layer
mylayer = create_anim_layer('mylayer')
# create a sphere and assigned it the layer
transform_node, shape_node = cmds.polySphere()
frame=1010
cmds.select(transform_node, replace=True)
# add all animatable attr, use attribute flag to specify the attr
cmds.animLayer(transform_node, edit=True, addSelectedObjects=True)
cmds.setKeyframe(transform_node + ".translateY", value=yVal, time=frame, animLayer="mylayer")
Setting keyframe
# set a keyframe at 1005 for tx=1
cmds.setKeyframe(
transform_node + ".translateX",
value=1,
time=1005,
inTangentType="linear", # optional
outTangentType="linear" # optional
)
Transfer animation between objects
# make a lambda function to split long name and retrieve attr name
get_attr_name = lambda longname: longname.split('.')[-1]
def transfer_anim():
objs = cmds.ls(selection=True)
if len(objs < 2):
print "Error: need at least 2 items to transfer animation from 1st selected item to others"
return
driver = objs[0]
animated_attributes = cmds.listAnimatable(driver)
for animated_attribute in animated_attributes:
keyframes = cmds.keyframe(animated_attribute, query=True, keyframeCount=True)
if keyframes:
cmds.copyKey(animated_attribute)
for driven_obj in objs[1:]:
# we can specify a animLayer flag to pasteKey to not overwrite keys data
cmds.pasteKey(
driven_obj,
attribute= get_attr_name(animated_attribute),
option="replace"
)
Create expression
def make_expression(node, attr, value):
# expression strings needs to be written with mel syntax
return "{}.{} = {}".format(node,attr,value)
expression_str = make_expression(transform_node, "translateX", "cos(time/2)*10")
cmds.expression(string=expression_str)
Create a light rig
# create a 3 points light rig
def create_light_rig(offset=10, rotation=30):
# need this since cmds.spotLight returns the light instead of the transform
keylight = cmds.listRelatives(
cmds.spotLight(rgb=(1,1,1), name="keylight"),
parent=True
)[0]
filllight = cmds.listRelatives(
cmds.spotLight(rgb=(0.8,0.8,0.8), name="filllight"),
parent=True
)[0]
backlight = cmds.listRelatives(
cmds.spotLight(rgb=(0.2,0.2,0.2), name="backlight"),
parent=True
)[0]
# keylight
cmds.move(0,0,offset, keylight)
cmds.move(0,0,0, keylight + ".rotatePivot")
cmds.rotate(-rotation, rotation, 0, keylight)
# filllight
cmds.move(0,0,offset, filllight)
cmds.move(0,0,0, filllight + ".rotatePivot")
cmds.rotate(-rotation, -rotation, 0, filllight)
# backlight
cmds.move(0,0,offset, backlight)
cmds.move(0,0,0, backlight + ".rotatePivot")
cmds.rotate(180 + rotation, 0, 0, backlight)
light_rig = cmds.group(empty=True, name="light_rig")
for light in [keylight, filllight, backlight]:
cmds.parent(light, light_rig)
cmds.select(light_rig, replace=True)
create_light_rig()
Create a light rig controllers UI
class LightControllersUI():
def __init__(self):
self._ui_name = "Lightcontrollers"
self.lights = []
self.light_controls = []
self.init_ui()
def delete_old_windows(self):
if cmds.window(self._ui_name, exists=True):
cmds.deleteUI(self._ui_name)
def init_ui(self):
# delete old windows if one exists
self.delete_old_windows()
self.win = cmds.window(self._ui_name, title=self._ui_name)
cmds.columnLayout()
scene_lights = cmds.ls(lights=True)
for light_index, scene_light in enumerate(scene_lights):
self.add_light_controls(light_index, scene_light)
cmds.showWindow(self.win)
def update_color_slider(self, light_index):
new_color_slider = cmds.colorSliderGrp(
self.light_controls[light_index],
query = True,
rgb = True
)
cmds.setAttr(
self.lights[light_index] + ".color",
new_color_slider[0],
new_color_slider[1],
new_color_slider[2],
type="float3"
)
def add_light_controls(self, light_index, light_shape):
light_transform = cmds.listRelatives(light_shape, parent=True)[0]
light_color = cmds.getAttr(light_shape + ".color")
color_slider = cmds.colorSliderGrp(
label = light_transform,
rgb = light_color[0],
changeCommand = lambda x: self.update_color_slider(light_index)
)
self.lights.append(light_shape)
self.light_controls.append(color_slider)
LightControllersUI()
Create a camera rig
def create_cam_rig(offset=10):
aim_target = cmds.spaceLocator()
for i in range(4):
cam = cmds.camera()
cmds.aimConstraint(aim_target[0], cam[0], aimVector=(0,0,-1))
pos_x = -offset if i%2 == 0 else offset
pos_y = 6
pos_z = -offset if i>=2 else offset
cmds.move(pos_x, pos_y, pos_z, cam[0])
create_cam_rig()
DG nodes manipulation
Maya’s command line render
Arnold render Setup Python API and commands
maya documentation here
Internal var
user_workspace_dir = cmds.internalVar(userWorkspaceDir=True)
user_script_dir = cmds.internalVar(userScriptDir=True)
FileDialog2
import maya.cmds as cmds
# fileMode flag decide what the dialog is to return.
# 0 Any file, whether it exists or not.
# 1 A single existing file.
# 2 The name of a directory. Both directories and files are displayed in the dialog.
# 3 The name of a directory. Only directories are displayed in the dialog.
# 4 Then names of one or more existing files.
## Read #######################################################################################################
basicFilter = "*.mb"
cmds.fileDialog2(fileMode=1, fileFilter=basicFilter, dialogStyle=2)
singleFilter = "All Files (*.*)"
cmds.fileDialog2(fileMode=1, fileFilter=singleFilter, dialogStyle=2)
multipleFilters = "Maya Files (*.ma *.mb);;Maya ASCII (*.ma);;Maya Binary (*.mb);;All Files (*.*)"
cmds.fileDialog2(fileMode=1, fileFilter=multipleFilters, dialogStyle=2)
multipleFilters = "Maya Files (*.ma *.mb);;Maya ASCII (*.ma);;Maya Binary (*.mb);;All Files (*.*)"
user_workspace_dir = cmds.internalVar(userWorkspaceDir=True)
cmds.fileDialog2(fileMode=1, fileFilter=multipleFilters, startingDirectory=user_workspace_dir, dialogStyle=2)
###############################################################################################################
## Write ######################################################################################################
basicFilter = "*.mb"
cmds.fileDialog2(fileMode=0, fileFilter=basicFilter, startingDirectory=user_workspace_dir, dialogStyle=2)
###############################################################################################################
Maya viewport contexts
# get current context
current_context = cmds.currentCtx()
# set current context
cmds.setToolTo('selectSuperContext')
cmds.setToolTo ('polySelectEditContext')
cmds.setToolTo ('polySlideEdgeContext')
cmds.setToolTo( 'moveSuperContext' )
# reset context
cmds.ctxAbort()
Maya custom contexts
# be stored in a customCtx.py file in scripts folder
# to be runned from a shelftool
def startCtx():
print ('Running init context')
def finalizeCtx():
objs = cmds.ls(selection=True)
x,y,z = [0,0,0]
for obj in objs:
pos = cmds.xform(obj, query=True, worldSpace=True, translation=True)
x += pos[0]
y += pos[1]
z += pos[2]
x /= len(objs)
y /= len(objs)
z /= len(objs)
loc = cmds.spaceLocator()
move(x,y,z,loc)
def createContext():
toolStartStr = 'python ("customCtx.startCtx()")'
toolFinishStr = 'python ("customCtx.finalizeCtx")'
# i1 flag could be provided if we want an icons
# just need 32px x 32px icon in the icon folder
newCtx = cmds.scriptCtx(
title="avg context",
setNoSelectionPrompt="Select at least 2 objs",
toolStart=toolStartStr,
finalCommandScript=toolFinishStr,
totalSelectionSets=1,
setSelectionCount=2,
setAllowExcessCount=True,
setAutoComplete=False,
toolCursorType="create",
)
cmds.setToolTo(newCtx)
createContext()
Create script job
Maya script jobs are like houdini parm callback and can be super usefull for a couple of use cases: switching asset variation, ik/fk rig switch, …
# small variation switcher proof of concept
def create_variation(variation_type):
x,y,z = [0,0,0]
try:
# node should always be there
x,y,z = cmds.xform('myAsset', query=True, absolute=True, translation=True)
cmds.delete('myAsset')
except:
pass
if variation_type == "cube":
obj = cmds.polyCube(name="myAsset")
variation_index = 0
else:
obj = cmds.polySphere(name="myAsset")
variation_index = 1
cmds.move(x,y,z, worldSpace=True)
shape_node = cmds.listRelatives(shapes=True)[0]
cmds.addAttr(
shape_node,
shortName="Variation",
longName="variation",
attributeType="enum",
enumName="cube:sphere",
defaultValue=variation_index,
keyable=False
)
cmds.scriptJob(attributeChange=["myAsset.variation", update_variation], killWithScene=True)
def update_variation():
variation_index = cmds.getAttr("myAssetShape.variation")
variation_type = [ 'cube', 'sphere' ][variation_index]
create_variation(variation_type)
# init scene
create_variation("sphere")
Maya use script jobs under the hood to make the interface interactive so messing with script jobs might produce unresponsiveness/crashes. It is super important to ensure we aren’t duplicating script jobs each time the code is being runned. Ensure your script job is not either protected or permanent
# retrieve all script jobs
scriptjobs = cmds.scriptJobs(listJobs=True)
# remove all script jobs => will mess with maya
cmds.scriptJob(killAll=True, force=True)
# remove all un-protected / un-permanent script jobs
cmds.scriptJob(killAll=True, force=False)
# remove specific job id
jid = cmds.scriptJob(attributeChange=["myAsset.variation", update_variation], killWithScene=True)
cmds.scriptJob(kill=jid) # would need to add force=True for protected scripts
# delete script jobs when UI is deleted with parent flag
def test_UI_scriptjobs():
win = cmds.window(title="UI test")
cmds.scriptJob(parent=win, event=["SelectionChanged", python_callback] )
cmds.showWindow(win)
Embed code in Maya scene file
In order to embed some code into a Maya scene file script node could be use. Embedding code into a scene could be usefull to have some critical code component without inducing pipeline dependancies.
def embed_script():
path = cmds.fileDialog2(
fileMode=1, # only allow a single file selection
fileFilter="Python files (*.py);;Mel Files (*.mel)",
startingDirectory=cmds.internalVar(userScriptDir=True)
)
if not path:
return
sourceType = "python" if path[0].endswith(".py") else "mel"
with open(path[0], 'r') as f:
cmds.scriptNode(
sourceType=sourceType,
scriptType=2,
beforeScript=f.read()
)
embed_script()
# scriptType flag possible value
# Specifies when the script is executed. The following values may be used:
# 0 Execute on demand.
# 1 Execute on file load or on node deletion.
# 2 Execute on file load or on node deletion when not in batch mode.
# 3 Internal
# 4 Execute on software render
# 5 Execute on software frame render
# 6 Execute on scene configuration
# 7 Execute on time changed
Sources
- Maya programming with python cookbook, A.Herbez (2016)
- Maya python for games and film, A.Mechtley & R.Trowbridge (2012)
- Practical Maya programming with python, R.Galanakis (2012)
- Maya documentation