Wednesday, March 16, 2011

Maya wxWindow

This a a template to create interactive wxWindows in Maya. The same UI can be ported to most applications using a python interpreter by only changing the makeCube method. However, not all software and/or versions fully support wxWidgets. The key difference you'll want to remember between a standard window and one from Maya is to omit initializing wx.app.MainLoop().
import wx
import maya.cmds as cmds

def getApp(kill = None):
 if kill:
  if MyFrame.instance:
   MyFrame.instance.Hide()
   MyFrame.instance.Destroy()
   MyFrame.instance = None
 else:
  if MyFrame.instance:
   MyFrame.instance.Show()
  else:
   MyFrame.create()
 
class MyFrame(wx.Frame):
 instance = None
 
 @classmethod
 def create( cls, *args, **kw ):
  app = wx.GetApp()

  if app is None:
   app = wx.App( redirect = False )
  
  frame = cls( None, app, *args, **kw )
  frame.Show(True)
 
 def __init__(self, parent, app):
  MyFrame.instance = self
  wx.Frame.__init__(self, parent, wx.ID_ANY, "MyFrame", wx.DefaultPosition, wx.Size(500,500), wx.DEFAULT_FRAME_STYLE )
  
  self.panel = wx.Panel(self, -1)
  self.sphereButton = wx.Button(self.panel, -1, "Sphere")
  self.cubeButton = wx.Button(self.panel, -1, "Cube")
   
  self.Bind( wx.EVT_ICONIZE, self.OnMinimize )
  self.Bind( wx.EVT_CLOSE, self.OnClose )
  self.sphereButton.Bind(wx.EVT_BUTTON, self.makeSphere)
  self.cubeButton.Bind(wx.EVT_BUTTON, self.makeCube)
  
  mainSizer = wx.BoxSizer(wx.VERTICAL)
  panelSizer = wx.BoxSizer(wx.HORIZONTAL)
  
  panelSizer.Add(self.sphereButton, 0, 0, 0)
  panelSizer.Add(self.cubeButton, 0, 0, 0)
  self.panel.SetSizer(panelSizer)
  mainSizer.Add(self.panel, 1, wx.EXPAND, 0)
  
  self.SetSizer(mainSizer)
  mainSizer.Fit(self)
  self.Layout()
 
 def makeSphere(self, evt):
  cmds.polySphere()
  
 def makeCube(self, evt):
  cmds.polyCube()
 
 def OnClose(self,event):
  self.Show(False)

 def OnMinimize(self, event):
  self.Show(0);

XSI Plugin

Creating XSI plugins in python is extremely easy. This file will add a new menu and menuItem to the main toolbar. You can actually attach a menuItem to many other XSI widgets besides the main toolbar using the RegisterMenu command. Like most UI elements, they are not initialized until they are called the first time.
import win32com.client
from win32com.client import constants

def XSILoadPlugin( in_reg ):
 in_reg.Author = ""
 in_reg.Name = "myPlugin"
 in_reg.Email = ""
 in_reg.URL = ""
 in_reg.Major = 1
 in_reg.Minor = 0
 print in_reg.Name, "has been loaded."
 
 in_reg.RegisterMenu(constants.siMenuMainTopLevelID,"MenuTitle",0,0)
 in_reg.RegisterCommand("MenuItem","MenuItem") #command, scriptname
 return 1

def XSIUnloadPlugin( in_reg ):
 print in_reg.Name, "has been unloaded."
 return 1

def MenuTitle_Init( in_ctxt ):
 oMenu = in_ctxt.Source
 oMenu.AddCommandItem("MenuItem","MenuItem") #menuname, command
 return 1

def MenuItem_Init( in_ctxt ):
 oCmd = in_ctxt.Source
 oCmd.Description = ""
 oCmd.ReturnValue = 1
 return 1

def MenuItem_Execute(  ):
 print "Command to Run"
 return 1

Sunday, January 23, 2011

Maya Globals

Here is a quick PyMEL script to get a list of Maya's global variables which you can use to easily grab some data without having to run your own commands or needing to query around the interface.  There are 750+ by default, and Mental Ray will add a handful as well.  Many values are actually not set, or are empty by default. 
from pymel.core import *
for index, eachEnv in enumerate(MelGlobals().keys()):
    print "%s- %-50s %s" %(index, eachEnv, melGlobals[eachEnv])

Some nice ones to keep in mind are:
  • $gMainPane
  • $gMainWindow
  • $panelName

There are also some humorous ones:
  • $foobar
  • $WantProtein

Friday, January 7, 2011

Passing Values Between Python and MEL

MEL to Python
MEL:
$foo="Hi"
Python:
import maya.mel as mel
foo=mel.eval("$temp=$foo")

Python to MEL
Python:
bar="Hi"
MEL:
$bar=python("bar") 

Python to/from MEL
Plugin:
import maya.OpenMaya as om
import maya.OpenMayaMPx as omMPx

nodeId = om.MTypeId(0x101118)

#node data
class user(omMPx.MPxNode):
 INPUT = om.MObject()

 def __init__(self):
  omMPx.MPxNode.__init__(self)
def userCreator():
 return omMPx.asMPxPtr( user() )
def userIniter():
 numAttr = om.MFnNumericAttribute()
 user.x = numAttr.create("x","x",om.MFnNumericData.kFloat,0.0)
 numAttr.setStorable(1)
 numAttr.setWritable(1)
 user.addAttribute(user.x)

#plugin requires
def initializePlugin(mobject):
 mplugin = omMPx.MFnPlugin(mobject)
 try:
  mplugin.registerNode("user", nodeId, userCreator, userIniter)
 except:
  print "Error loading"
  raise
def uninitializePlugin(mobject):
 mplugin = omMPx.MFnPlugin(mobject)
 try:
  mplugin.deregisterNode( nodeId )
 except:
  print "Error removing"
  raise
MEL:
if(!size(`ls "userData"`)){
    createNode "user" -name "userData";
}
setAttr "userData.x" 5;
getAttr "userData.x";
Python:
import maya.cmds as cmds
if not cmds.ls("userData"):
    myUser=cmds.createNode("user", name="userData")
cmds.setAttr("userData.x", 5)
print cmds.getAttr("userData.x")

Sunday, January 2, 2011

Useful Commands

whatIs
whatIs script;   //mel only
whatIs statusLineUpdateInputField;   
// Result: Mel procedure found in: C:/Program Files/Autodesk/Maya2009/scripts/startup/statusLine.mel // 

When you need to emulate a command from Maya and the script editor only gives feedback when Echo All Commands is checked.  Maya most likely echoed a built in mel procedure that runs multiple mel commands in the background. Just open the file, find the procedure, and begin sifting through Maya's internals. 

evalDeffered
cmds.evalDeferred("cmds.refresh(force=1)", lowestPriority=1) 
cmds.evalDeferred(lambda : localCommand, lowestPriority=1) 

Often a command will return a result before the intended process is complete.  This is often a result of commands firing idle scriptJobs.  The problem is your code continues while Maya is trying to play catch up.  evalDeferred will wait until Maya is completely idle (no more queued scriptJobs) before continuing.

Attributes and Values

Here is a simple script to list all the attributes, attributeType, and values.
selection=cmds.ls(sl=1)[0]
selection=cmds.ls(sl=1)[0]
attributes=cmds.listAttr(selection)
attributeList=[]
print "\n"
for eachAttr in attributes:
   name=eachAttr,
   try: type=cmds.nodeType(selection+"."+eachAttr),
   except: type="Error"
   try: value=cmds.getAttr(selection+"."+eachAttr)
   except: value="Cannot Read"
   attributeList.append([name[0], type[0], value])
col1length=max([len(str(item[0])) for item in attributeList])
col2length=max([len(str(item[1])) for item in attributeList])
col3length=max([len(str(item[2])) for item in attributeList])
print ("Attribute".ljust(col1length), 
       "Type".ljust(col2length), 
       "Value".ljust(col3length))
for eachAttr in attributeList:
   print str(eachAttr[0]).ljust(col1length),
   print str(eachAttr[1]).ljust(col2length),
   print str(eachAttr[2]).ljust(col3length)

materialInfo

Quite often, unintentionally, mesh will only display in wireframe. More than often it is from accidentally deleting the connection from the mesh to the shading group. However, if you are scripting a file translator or shader exporter you might have ignored the materialInfo node. Almost invisible to the user it is necessary for hardware rendering of any shader.

A: Correct Connections, B: MaterialInfo Error, C: Deleted ShadingGroup
As you can see the problem is indistinguishable in the hypershade.  The only way to tell if you've got this problem is to view B's material in the Attribute Editor.  If most of it's Hardware Texturing features are disabled try the following.
miNode=cmds.createNode("materialInfo")
cmds.connectAttr("phong1SG.message", "%s.shadingGroup"%miNode)
cmds.connectAttr("phong1.message", "%s.material"%miNode) 

Saturday, January 1, 2011

Other Extensions

If you've looked into the Python install dir you've most likely seen .pyc and .pyo files. Unlike .pyw files which are simply renamed extension, .pyc and .pyo are encoded by the interpreter and are no longer human readable.  Typically compiled python (.pyc) are created by the interpreter once a .py file is run.  However, if the option was turned off during installation the following can create them for you. -O and -OO flags will optimize the code further.
python -m py_compile myFile.py
python -m compileall -l .
Although it would be nice to get a speed boost from optimizing and compiling our code that is not the case.  You may find a minor improvement in the launch time, since the interpreter no longer needs to convert it to bytecode, but the main advantage is to have some code protection if you are distributing your program.

Hidding the Console

As you have probably noticed the typical Python extension is .py.  If you change this in explorer to .pyw pythonw.exe will execute the script instead of python.exe.  As a result the console window will be suppressed and any GUI windows will still come up.  Preventing the console looks more professional for a finished build, but during development it is still very useful for debugging.

.py appication launches a shell.