Quantcast
Channel: mel wiki
Viewing all 610 articles
Browse latest View live

Troubleshooting Shelves

$
0
0
There can come a time where something goes wrong with your shelves: Maybe one is empty, when it should be full. Here's a way to sort of 'reset' your shelves to get them working again.

Let's say your shelf is called "myAwesomeTools":
First off, close Maya. Maya saves its prefs when it closes, and we need to modify them.
Second, (after making a backup of it...) open in your favorite text editor:
C:\Users\<userName>\Documents\maya\<mayaVersion>\prefs\userPrefs.mel
Do a text search for the name of your shelf. You should find it at minimium in two different locations, looking like this:
 -sv "shelfFile34""shelf_myAwesomeTools"
and this:
 -sv "shelfName34""myAwesomeTools"
These are both calls to optinVars: The first one stores the name of the shelf_myAwesomeTools.mel script that holds all your script contents.
The second one stores the actual name of the shelf you see on-screen, "myAwesomeTools".
It's possible if there are problems, you'll find these on other lines as well: REMOVE THEM ALL FROM THAT FILE, save, and restart Maya.

From there, your shelf will be missing: You can now 'Load Shelf...' from the menu to bring it back. It should be a happy shelf.

These problems can also happen when calling to the mel loadNewShelf, that is trying to load a shelf from some network location at startup: Using this system to clean out the old optionVars, you can get your networked shelf back.

Query influences in Maya 'Deformer Weights' file

$
0
0
Maya has the option (since 201X?) to "Export Deformer Weights" (and import). It saves these out as xml file, looking something like this:
<?xml version="1.0"?>
<deformerWeight>
  <headerInfo fileName="C:/temp/maya/myAwesomeWeightFile.xml" worldMatrix="1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 "/>
  <shape name="awesomeMesh0Shape" group="907" stride="3" size="959" max="959">
    <point index="0" value=" -46.970287 -22.764431 118.459167"/>
    <point index="1" value=" -48.517704 -21.456564 117.263725"/>
    <point index="2" value=" -49.214066 -20.140640 116.087372"/>
    <point index="3" value=" -45.769356 -16.708261 111.223663"/>
    etc....
  </shape>
  <weights deformer="skinCluster3" source="awesomeJoint01" shape="awesomeMesh0ShapeShape" layer="0" defaultValue="0.000" size="185" max="957">
    <point index="0" value="0.943"/>
    <point index="1" value="0.936"/>
    <point index="2" value="0.915"/>
    <point index="3" value="0.825"/>
    etc...
What if you want to get a list of all the influences in one of those files? Python to the rescue:
import xml.dom.minidom
def getInfFromXml(xmlFile):
    infs = []
    mDom = xml.dom.minidom.parse(xmlFile)
    for i in mDom.getElementsByTagName("weights"):
        infs.append(i.getAttribute("source"))
    return infs
xmlFile = r"c:\temp\maya\myAwesomeWeightFile.xml"
print getInfFromXml(xmlFile)
# ["awesomeJoint01", "awesomeJoint02", etc..]

The initial mel code (called to from the UI) that writes out the weight data lives here:
C:\Program Files\Autodesk\Maya20XX\scripts\others\performExportDeformerWeights.mel
Which is a wrapper for this command:
deformerWeights -export -deformer "skinCluster3" -path "C:/temp/maya/""AwesomeWeightFile.xml";
Docs for deformerWeights

PyMel : UV access

$
0
0
The PyMel Mesh class has a number of methods for interacting with UVs:
  • .assignUV : Maps a texture coordinate to a specified vertex of a polygon.
  • .assignUVs : This method maps all texture coordinates for the mesh.
  • .clearUVs : This method clears out all texture coordinates for the mesh, and leaves behind an empty UVset.
  • .createUVSet : Create a new empty uv set for this mesh.
  • .deleteUVSet : Deletes a named uv set from the object.
  • .getAssignedUVs : This method finds all texture coordinates for the mesh that have been mapped, and returns them in the same format as the assignUVs.
  • .getAssociatedUVSetTextures : Get a list of texture nodes which are using a given uv set.
  • .getCurrentUVSetName : Get the name of the “current” uv set. The “current” uv set is the uv set which is used for uv operations when no uv set is explicitly specified.
  • .getFaceUVSetNames : returns the list of UV sets mapped to a face
  • .getPolygonUV : Get the value of the specified texture coordinate for a vertex in a polygon.
  • .getPolygonUVid : Get the id of the specified texture coordinate for a vertex in a polygon.
  • .getUV : Get the value of the specified texture coordinate from this mesh’s uv list.
  • .getUVAtPoint : Find the point closet to the given point, and return the UV value at that point.
  • .getUVSetFamilyNames : Get the names of all of the uv set families on this object.
  • .getUVSetNames : Get the names of all of the uv sets on this object.
  • .getUVSetsInFamily : Get the names of the uv sets that belong to this set family.
  • .getUVs : This method copies the texture coordinate list for this mesh into the given uv arrays.
  • .getUvShellsIds : Constructs an array of unique integer for each UV shell.
  • .isUVSetPerInstance : Return true if this set is per-instance, and false if it is shared across all instances.
  • .numUVSets : Returns the number of uv sets for an object.
  • .numUVs : Returns the number of texture (uv) coordinates for this mesh.
  • .renameUVSet : Renames a uv set from one name to another for this mesh.
  • .setCurrentUVSetName : Set the “current” uv set for this object.
  • .setSomeUVs : Sets the specified texture coordinates (UV’s) for this mesh.
  • .setUV : Sets the specified texture coordinate.
  • .setUVs : Sets all of the texture coordinates (uv’s) for this mesh.
If you select a bunch of UV's, PyMel will tell you have MeshUVs instances picked, but the docs on that class are empty.

If you want to convert from a MeshUV to a MeshVertex, the only current way I've found is via polyListComponentConversion: However the PyMel wrapper returns strings, rather than PyNodes, so you need to wrapper it:
# select a UV:
myUv = pm.ls(selection=True)  # MeshUV
theVert = pm.PyNode(pm.polyListComponentConversion(myUv[0], toVertex=True)[0]) # MeshVertex
Modified example code from Mason Sheffield:
import pymel.core as pm

mesh = pm.ls(selection=True)[0]
print "Mesh:", mesh
print "\tNum uv sets:", mesh.numUVSets()
uvSets = mesh.getUVSetNames()
for uvSet in uvSets:
    print "\t", uvSet,
    mesh.setCurrentUVSetName(uvSet)
    print ": UVs :", mesh.numUVs()
Mesh: myAwesomeMesh
	Num uv sets: 2
	map1 : UVs : 204
	uvSet1 : UVs : 204

Also see:

For each selected UV, snap the mesh pivot to it

$
0
0
Pretend you have 1000 separate leaf mesh. They're all UV'd the same, but have been polycombined. You need to split them into separate mesh, and have their pivots placed at the root of the leaf (which is a consistent UV point). How to do?

After separation, select all the mesh and open the UV Texture Editor. Drag-select over the UV you want to snap the pivot too: Since all the mesh have the same uv's, you should now have 1000 uv's picked. Run the below code: It will find the vert for each UV, the mesh for the vert, then snap its transform-level pivot to that UV's position.
import pymel.core as pm

def snapPivToSelUv():
    sel = pm.ls(selection=True)
    if any([item for item in sel if not isinstance(item, pm.MeshUV)]):
        pm.displayError("Only UVs can be selected")
        return
    pm.undoInfo(openChunk=True)
    pm.waitCursor(state=True)
    try:
        for uv in sel:
            # Convert from UV to vert.
            # polyListComponentConversion returns a strings, rather than PyNodes
            vert = pm.PyNode(pm.polyListComponentConversion(uv, toVertex=True)[0])
            pos = pm.pointPosition(vert, world=True)
            vert.node().getParent().setPivots(pos, worldSpace=True)
        pm.displayInfo("Done: Snapped pivots to UVs on %s nodes!"%len(sel))
    finally:
        pm.undoInfo(closeChunk=True)
        pm.waitCursor(state=False)

snapPivToSelUv()

How can I add things to sets, or make subsets?

$
0
0
I find it really handy to organize sets inside of sets. However, my brain can never seem to wrap itself around how to add a 'child set' to a 'parent set' via code: The syntax to me is... a bit backwards:

Given a parental set 'parentSet', and a set (or any other node) you want to add as a subset called 'subset':
Mel:
sets -addElement parentSet subSet;
Python:
import maya.cmds as mc
mc.sets("subSet", addElement="parentSet")
In both instances of the command, to me, it looks like you're trying to add parentSet to subSet, but that's just not the case.

PyMel:
PyMel flipped it around, so it makes a lot more sense:
import pymel.core as pm
mc.sets("parentSet", addElement="subSet")
Regardless of the method use, in the Outliner this would give you the effect:
- parentSet
|
---- subSet

Why is fileDialog2 crashing Maya?

$
0
0
Note the below code is in PyMel, but the same problem happens when using maya.cmds Python.

Have had weird instances where code like this:
import pymel.core as pm

fFilter = "*.txt"
theFile = pm.fileDialog2(caption="Save skin data", fileMode=0, fileFilter=fFilter, startingDirectory=startDir)
Would crash Maya after a file was selected, and the save button was pressed (Windows 7).

I found that changing the file filter to the below code fixed it:
import pymel.core as pm

fFilter = "Text (*.txt)"
theFile = pm.fileDialog2(caption="Save skin data", fileMode=0, fileFilter=fFilter, startingDirectory=startDir)
What's weird is both work on my machine, but on other's machines it would crash.
The big difference was (using the broken top code), in the save dialog, the "Save as type" for them was completely empty, while I would see "*.txt". When I changed it to the bottom code, I would see "Text (*.txt)", but they'd only see "Text". But at least it stopped crashing.

Convert from mel commands to Python

$
0
0
PyMel has a nice utility to convert from mel straight to Python:
import pymel.tools.mel2py as mel2py
print mel2py.mel2pyStr('aimConstraint -mo -weight 1 -aimVector 0 -1 0 -upVector 0 0 1 -worldUpType "vector" -worldUpVector 0 0 1 -skip y;')
aimConstraint(weight=1,upVector=(0, 0, 1),skip='y',mo=1,worldUpType="vector",aimVector=(0, -1, 0),worldUpVector=(0, 0, 1))
Tip from Mason Sheffield.

Qt : How can I get a pointer to the main Maya window?

$
0
0
Maya 2014 & newer using PySide:
import maya.OpenMayaUI as omui

from PySide import QtGui
from shiboken import wrapInstance

def getMayaMainWin():
    ptr = omui.MQtUtil.mainWindow()
    return wrapInstance(long(ptr), QtGui.QWidget)

Older versions of Maya using Qt directly:
import maya.OpenMayaUI as omui
import sip
import PyQt4.QtCore as QtCore
def getMayaMainWin():
    """
    Return the Maya main window as a QtGui.QMainWindow instance
    """
    ptr = omui.MQtUtil.mainWindow() # QWidget pointer
    return sip.wrapinstance(long(ptr), QtCore.QObject)
This can be used to then parent your PyQt UI's to, so they won't 'pop' behind it when they loose focus.

A QMainWindow is the main application window, and Maya's main window is an instance of this class (since Maya 2011?). When making your own PyQt windows, you usually instance QDialog's, and then parent them to this main window.

Docs:

Python & Qt versions in Maya

$
0
0
MayaPythonQtCompatible PyQtCompatible PySide
8.52.4.3
20082.5.1
20092.5.1
20102.6.1
20112.6.44.5.3 (it begins...)??
20122.6.44.7.1??
20132.6.44.7.14.9.1?
20142.7.34.8.24.101.1.2 - Ships with Maya!
20152.7.34.8.5?1.2
Starting in 2011, Maya's entire ui back-end was converted to Qt. You still use the same Maya commands to author UI's, but behind the scenes Qt is appearing on your screen.

This is not to be confused with PyQt or PySide, which are Python (not Maya) implementations of Qt. You can also get PyQt& PySide working in Maya, if you jump through some hoops, if using versions before 2014: Starting in 2014, PySide started shipping with Maya: No more needing to compile PyQt.

Also see:

Installing PyQt

$
0
0
A new blog post came out that covers part of the install process after I got it working. Here is part1:
http://around-the-corner.typepad.com/adn/2012/10/building-qt-pyqt-pyside-for-maya-2013.html

There are a variety of sources with instructions for installing PyQt. These are simply my notes based on those combined sources. Resources I pulled from include:
This installation is for Maya 2013 on Windows 7, and I mainly followed the above pdf.

  1. Downloaded these zips, and extracted to a temp folder:
    1. PyQt-win-gpl-4.9.4.zip -> \PyQt-win-gpl-4.9.4
    2. qt-4.7.1-modified_for_maya_2013.zip - "Maya's modified version of the Qt source code" (from the above pdf) -> \Qt-4.7.1-Maya
    3. sip-4.13.3.zip -> \sip-4.13.3
  2. \Qt-4.7.1-Maya itself contains a compressed tar, and install notes (howToBuildQtOnWindows_m2013.txt): I extracted the tar here: c:\qt-adsk-4.7.1 (per pdf install notes) and then removed \Qt-4.7.1-Maya.
  3. Build Qt. The above txt file says to download the sdk from here http://get.qt.nokia.com/qtsdk/qt-sdk-win-opensource-2010.05.exe, but that link seems dead. So I instead downloaded the sdk from here: http://qt.nokia.com/downloads and installed it. It is big and huge :-S
    1. To build qt, the instructions say that I need to unzip the provided openssl-1.0.0e.zip,... but that file doesn't exist on my HD and wasn't included with any of the previous downloads. And now I'm stuck :(


Giving up on that, I found a link to a 'Maya 2013 - Windows 7 - 64-bit' precompiled version... here: http://codecg.com/2012/04/15/pyqt4-for-maya2013-on-windows-7-x64/
It was a simple matter to get it working:
  1. Unzip to temp folder
  2. Under the \site-packages dir, copy the \PyQt folder along with the three other files (sip.pyd, sipconfig.pyd, sipdistutils.py) to Maya-Python's \site_packages dir, which for me lives here:
C:\Program Files\Autodesk\Maya2013\Python\Lib\site-packages
Restart Maya, try some example code (copied from here):
import maya.OpenMayaUI as mui
import PyQt4.QtCore as QtCore
import PyQt4.QtGui as QtGui
import sip

def getMayaWindow():
    #Get the maya main window as a QMainWindow instance
    ptr = mui.MQtUtil.mainWindow()
    return sip.wrapinstance(long(ptr), QtCore.QObject)

class MayaSubWindow(QtGui.QMainWindow):
    'My custom window, which i want to parent to the maya main window'
    def __init__(self, parent=getMayaWindow()):
        #Init my main window, and pass in the maya main window as it's parent
        QtGui.QMainWindow.__init__(self, parent)

#Show my window
myWindow = MayaSubWindow()
myWindow.show()
Success!!!

PyQt : Embedding a Maya widget into a PyQt UI

$
0
0
Really good blog post here, by Nathan Horne
Copied below (and slightly changed) for redundancy:
# http://nathanhorne.com/?p=381
import maya.cmds as mc
import maya.OpenMayaUI as omui
from PyQt4 import QtGui, QtCore
import sip

def getMayaWindow():
    ptr = omui.MQtUtil.mainWindow()
    return sip.wrapinstance(long(ptr), QtCore.QObject)

def toQtObject(mayaName):
    '''
    Given the name of a Maya UI element of any type,
    return the corresponding QWidget or QAction.
    If the object does not exist, returns None
    '''
    ptr = omui.MQtUtil.findControl(mayaName)
    if ptr is None:
        ptr = omui.MQtUtil.findLayout(mayaName)
    if ptr is None:
        ptr = omui.MQtUtil.findMenuItem(mayaName)
    if ptr is not None:
        return sip.wrapinstance(long(ptr), QtCore.QObject)

class MayaSubWindow(QtGui.QMainWindow):
    def __init__(self, parent=getMayaWindow()):
        super(MayaSubWindow, self).__init__(parent)
        self.executer = mc.cmdScrollFieldExecuter(sourceType="python")
        qtObj = toQtObject(self.executer)
        #Fill the window, could use qtObj.setParent
        #and then add it to a layout.
        self.setCentralWidget(qtObj)

myWindow = MayaSubWindow()
myWindow.show()
Here is another example:

PyQt : How can I get the PyQt widget for a named Maya control?

$
0
0
# Code snippet pulled from:
# http://nathanhorne.com/?p=381

import maya.OpenMayaUI as omui
import sip
from PyQt4 import QtCore

def toQtObject(mayaName):
    '''
    Given the name of a Maya UI element of any type,
    return the corresponding QWidget or QAction.
    If the object does not exist, returns None
    '''
    ptr = omui.MQtUtil.findControl(mayaName)
    if ptr is None:
        ptr = omui.MQtUtil.findLayout(mayaName)
    if ptr is None:
        ptr = omui.MQtUtil.findMenuItem(mayaName)
    if ptr is not None:
        return sip.wrapinstance(long(ptr), QtCore.QObject)
Here's another example, showing how you can get a QWidget for Maya's 'timeline':
# Snippet pulled from here:
# http://dannywynne.com/scripts/timeline_markers.zip

import maya.OpenMayaUI as omui
import sip

ptr = omui.MQtUtil.mainWindow() # QtGui.QWidget pointer to the QtGui.QMainWindow
maya_window = sip.wrapinstance(long(ptr), QtCore.QObject) # Returns QtCore.QObect
maya_timeline = maya_window.findChild(QtGui.QWidget, "timeControl1")
Docs:

MainMenu

Qt Designer

$
0
0
Starting in 2011, Maya's entire ui back-end was converted to Qt. You still use the same Maya commands to author UI's, but behind the scenes Qt is appearing on your screen.

This is not to be confused with PyQt, which is a Python (not Maya) implementation of Qt. You can also get PyQt working in Maya, if you jump through some hoops, see Installing PyQt.

C:\Program Files\Autodesk\Maya20XX\bin\designer.exe
Posts:

API :Access Qt widget data

$
0
0
Starting with Maya 2011 they introduced a new API class MQtUtil.
Notes from the docs:
The safest way to use the Qt API from within Maya is to create your own Qt window and populate it with your own controls.

While it is possible to use the Qt API to modify existing Maya UI elements, such as those created using Maya commands from MEL or Python, such modifications are not supported by Autodesk and could lead to Maya becoming unstable or inoperable.

In practice, you will likely find a number of modifications which appear safe, such as changing a control's text or reorganizing the items on a menu. However, Autodesk provides no guarantees that the underlying implementations of those UI elements won't change from one release of Maya to another, potentially in ways that may make formerly safe usage become unsafe. So if you choose to incorporate such actions into your plug-in be aware that you may have to modify your code in future versions of Maya.
Here is a very simple example using it:
import maya.cmds as mc
import maya.OpenMayaUI as omui

# Create the window:
class App(object):
    def __init__(self):
        self.name = "tempWin"
        if mc.window(self.name, exists=True):
            mc.deleteUI(self.name)
        self.window = mc.window(self.name, resizeToFitChildren=True)
        self.rootLayout = mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
        self.button = mc.button("awesomeButton", label="this is an awesome button")
        mc.showWindow()
# Display window, capture an instance:
app = App()

butQtClasses = mc.objectTypeUI(app.button, superClasses=True)
qwidget = omui.MQtUtil.findControl(app.button)

print app.button, butQtClasses
print qwidget, type(qwidget)
prints:
tempWin|columnLayout6|awesomeButton [u'QPushButton', u'QAbstractButton', u'QWidget', u'QObject']
_b056e53200000000_p_QWidget <type 'PySwigObject'>
From here, you can see that the Qt widget has been wrapped in a Python 'swig' object.

How can I make a window dockable?

$
0
0
Starting with the introduction of Qt in Maya 2012, you can make your own custom windows dockable in the Maya UI.
The below examples will take a window (who's name is defined by the myAwesomeWindow variable) and turn it into a floating dock, that can then be dragged via the mouse and inserted into various parts of the Maya UI.
Docs: dockControl
# Python code
import maya.cmds as mc

myAwesomeWindow = "the name of your window..."

# Define the name for the docControl:
dcName = 'myDC'

# Just like you need to delete pre-existing windows before you make new ones,
# you need to delete pre-existing dockControls:
if mc.dockControl(dcName, query=True, exists=True):
    mc.deleteUI(dcName)

# Create the dockControl that will house the window:
mc.dockControl(dcName, allowedArea='all', area='right', floating=True, 
               content=myAwesomeWindow , label='Awesome Dock')

Maya crashes during custom window refresh

$
0
0
Ever since Maya switched its back-end to QT, I've noticed some instability, that has continued up to Maya 2014. During certain window commands, usually based around their deletion, but sometimes their creation, calling to these commands can cause Maya to crash.

For example, in a script you call to deleteUI to delete a window. And when you do this, Maya crashes. Or at some point in a script you create a window, and it causes Maya to crash. How can you make this work?

As it turns out, you need to 'evalDeferred' the creation\deletion. Without this step, Maya will continue to crash:
import maya.cmds as mc

# Create a window safely (by calling to some window creation class):
mc.evalDeferred(lambda *args:MyWindow(), lowestPriority=True)

# Delete a window safely by string name:
if mc.window("myWindow", exists=True):
    mc.evalDeferred(lambda *args:mc.deleteUI("myWindow"), lowestPriority=True)

Using Qt Designer

$
0
0
Starting in Maya 2011, Autodesk changed the UI system to Qt (made by Nokia). I found this snazzy video blog discussing some of the high level features:
Qt Designer can be found here:
C:\Program Files\Autodesk\Maya20XX\bin\designer.exe

Rough overview:

  • Make ui in QT Designer. Save out .ui file.
    • To add commands to the widgets, you need to add 'dynamic attributes' to them that emulate the Maya flags.
    • For example, if you're making a button, you need to add a dynamic 'string' attribute with the value '-command', via the big green '+' symbol in the UI.
    • The value of this attr must be a mel procedure (not Python) that is executed, surrounded in quotes.
    • F3 is used to enter 'Edit Widgets' mode (the default), F4 is used to enter 'Edit Signals/Slots', which allows you to have one widget control another.
  • Author Maya code (Python, in my case) to load said ui. If you save the the Python module in the same dir as the .ui file, it makes it easy to find.

Supported Widgets:

From the above link, transcribed and updated list of Qt widgets supported in Maya. Question-marks just mean I haven't tested those yet.
Qt NameItem LocationMaya EquivalentCommand flagSupports '#1' argument passing?
push ButtonButtonsbutton-commandNo
radio buttonButtonsradioButton?
check boxButtonscheckBox?
list viewItem Views (Model Based)textScrollList?
combo boxContainersoptionMenu?
group boxContainersNONE
tab widgetContainerstabLayout?
line editInput WidgetstextField?
text editInput WidgetsscrollField-enterCommand, -changeCommandNo
spin boxInput WidgetsNONE
double spin boxInput WidgetsNONE
dialInput WidgetsNONE
horizontal scroll barInput WidgetsNONE
vertical scroll barInput WidgetsNONE
vertical sliderInput WidgetsintSlider-dragCommand, -changeCommandYes
horizontal sliderInput WidgetsintSlider-dragCommand, -changeCommandYes
vertical lineInput WidgetsNONE
horizontal lineInput WidgetsNONE
labelDisplay WidgetsNONE
progress barDisplay WidgetsprogressBar?
main windowwindow

Qt Widget Types in Maya

$
0
0
Check out the docs for objectTypeUI for examples of how to print out the types. From that example, a modified version:
import maya.cmds as mc
allTypes = mc.objectTypeUI(listAll=True)
for i in range(0, len(allTypes), 3):
    print allTypes[i], allTypes[i+1], allTypes[i+2]
Prints: "command name", "ui type", "class name" (Note that the class name is internal and is subject to change).
commandLine   commandLine   TcommandLine  
cmdShell   cmdShell   TcommandShell  
button   button   QPushButton  
canvas   canvas   QPushButton  
checkBox   checkBox   QmayaCheckBox  
etc...

Emulating connectControl in Qt

$
0
0
The Maya connectControl command can setup a bi-directional link between an object.attribute, and a UI control. Like linking the rotateZ of something to a slider: Move the slider, the object rotates. Rotate the object, and the slider updates.

How is this achievable in Qt? I'm just learning Qt myself, and there could quite possible be a less convoluted solution, but what I've come up with behaves just fine.

Here's an overview of how it works:
  • Window is created. A signal/slot combo is setup so that whenever the dial is changed, it will modify the rotateZ on the defined object.
  • User picks some object, and presses the "Define Object" button.
  • This executes the butCmd method, which:
    • Sets the self.transform attr with the current selected node.
    • Deletes any pre-existing attr-changed callbacks.
    • Fires the createAttrChangedCallback method, which creates a callback that will execute the attrChangedCallbackCmd method whenever the nodes rotateZ is changed.
  • When the node is rotated by the user and the attrChangedCallbackCmd method executes:
    • The first thing it does is to blockSignals on the QDial: It then sets the new dial value, then stops blocking the signals.
    • If that signal blocking didn't happen, Maya will effectively go into a cyclic loop: The user rotates the node, which updates the dial, which updates the node, which updates the dial, etc.
Code:
# dialer.py

from math import degrees

import pymel.core as pm
import maya.OpenMayaUI as omui
import maya.OpenMaya as om

from PySide import QtCore
from PySide import QtGui

from shiboken import wrapInstance


def maya_main_window():
    # Get a pointer to Maya's main menu qt widget:
    main_window_ptr = omui.MQtUtil.mainWindow()
    return wrapInstance(long(main_window_ptr), QtGui.QWidget)

def getMObject(node):
    """
    # Return a MObject for the passed in node name
    """
    selList = om.MSelectionList()
    selList.add(str(node))
    mObject = om.MObject()
    selList.getDependNode(0, mObject)
    return mObject

#-------------------------------------------------------------------------------

class App(QtGui.QDialog):

    def __init__(self):
        """
        Create the window.
        """
        super(App, self).__init__(maya_main_window())

        # Delete the window if it already exists:
        self.title = "Dialatron"
        for widget in QtGui.QApplication.topLevelWidgets():
            if self.title == widget.windowTitle():
                widget.close()

        # This tracks the ID for our attr changed callback, so it can be later
        # removed when the window is closed, or a new node is picked:
        self.cid = None
        # This tracks the selected object:
        self.transform = None

        self.setWindowTitle(self.title)
        self.setWindowFlags(QtCore.Qt.Tool)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        self.createLayout()
        self.createSignals()
        self.show()

    def closeEvent(self, event):
        """
        Called when a Qt window is closed.  We use it to remove the attr changed
        callback.
        """
        if self.cid:
            om.MMessage.removeCallback(self.cid)

    def createLayout(self):
        """
        Create the main ui layout and widgets
        """
        self.button = QtGui.QPushButton("Define Object")
        self.dial = QtGui.QDial(minimum=0, maximum=360)
        self.dial.setNotchesVisible(1)
        self.dial.setWrapping(1)

        # Acts like a columnLayout in Maya:
        mainLayout = QtGui.QVBoxLayout()
        mainLayout.setContentsMargins(10,10,10,10)
        mainLayout.setSpacing(10)
        mainLayout.addWidget(self.button)
        mainLayout.addWidget(self.dial)
        self.setLayout(mainLayout)

    #---------
    # Signals:

    def createSignals(self):
        """
        These create the *signals* based on ui interaction, and connects them
        to *slots*.
        """
        self.button.clicked.connect(self.butCmd)
        self.dial.valueChanged.connect(self.dialCmd)

    #----------
    # Slots:

    def butCmd(self):
        """
        Slot for the button signal.  It both defines the object to 'dial', and
        creates the attr changed callback.
        """
        sel = pm.ls(selection=True)
        if not len(sel):
            pm.displayWarning("Nothing selected")
            return
        self.transform = sel[0]
        self.createAttrChangedCallback()
        print "Defined '%s' for dialation"%self.transform

    def dialCmd(self):
        """
        Slot for the dial signal:  When happens when the user turns the dial.
        """
        sender = self.sender()
        val = sender.value()
        if self.transform and pm.objExists(self.transform):
            pm.PyNode(self.transform).rotateZ.set(val)

    #---------
    # Callbacks:

    def createAttrChangedCallback(self):
        """
        Create the callback that will be executed when an attribute changes.  It
        calls to attrChangedCallbackCmd.
        """
        if self.cid:
            om.MMessage.removeCallback(self.cid)
        if pm.objExists(self.transform):
            self.cid = om.MNodeMessage.addAttributeChangedCallback(getMObject(self.transform),
                                                                   self.attrChangedCallbackCmd, self.dial)

    def attrChangedCallbackCmd(self, msg, plug, otherPlug, *args):
        """
        Function executed when an attr is changed.
        """
        if 'rotateZ' in plug.name():
            qdial = args[0]
            val = degrees(plug.asDouble()) # Rotations are returned as radians.
            # If we don't block the signal emitted from the dial, we'll get into
            # a cyclic loop.
            qdial.blockSignals(1)
            qdial.setValue(val)
            qdial.blockSignals(0)
To execute:
import dialer
dialer.App()
Viewing all 610 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>