Feb 20 2018

HEREISTITLE

HEREISCONTENT


Feb 18 2018

HEREISTITLE

HEREISCONTENT


Jan 9 2018

Maya Message Attribute Example

it’s been a long time since i last post in this blog….
Today i did a bit of brushing up on “message” attributes for maya, which are very similar to #maxobject attribute type in max…

Anyway basic searches didn’t really link to full working examples so here’s you go folks ūüôā
hopefully someone will find it useful…

import pymel.core as pm
def test():
    node = pm.PyNode('locator1')
    spheres = pm.ls('pSphere*',type='transform')

    
    #lets start easy with on object
    if not pm.attributeQuery('object1',node=node,exists=True):
        pm.addAttr(node,ln='object1',at='message')
        pm.connectAttr(spheres[0].message,node.object1,f=True)
    
    print 'Single Connection', pm.listConnections(node.object1) 
    #conncet a some more objects
    if not pm.attributeQuery('multiObject',node=node,exists=True):
        pm.addAttr(node,ln='multiObject',at='message',m=True,im=False)
        for s in spheres:pm.connectAttr(s.message,node.multiObject,na=True)
    
    print 'Multi Connection',pm.listConnections(node.multiObject) 
    
        
print '\n\n------>'
test()

Mar 8 2015

Vimeo Mango site!

If you haven’t check it out already….

Mango now has it’s on page, with tons of short demos show that mango pipeline tools set…

check it out!
https://vimeo.com/mangopipeline

also if you’re on facebook like the mango page!
https://www.facebook.com/mangoPipeline


Sep 30 2014

About “Mango Pipeline”

What is mango?
Mango is a development project to create a “pipeline in a box” like product, to most this will sound like a very bold claim, and let me assure you that it is. To attempt to live up to this claim i have structure the development road map of mango into different phases. Through out the different phases of mango development road map, we will be tackling the different aspects of the big picture Studio pipeline, mango is currently on phase1 and phase 2 should be released early¬† 2015, with more phases already planned.

What to expect from the Mango Phase 1 Deployment
Phase 1 is the entry point to the essentials building blocks that are need to be setup up and expandable pipeline, While at the same time using a common case scenario for small studios that it hopes to improve at the time of adoption.   The generalist pipeline is a common case scenario that can greatly be improved by automating and tracking their most basic needs.

this I identify as…

  1. Ingesting plates
  2. Making work files
  3. Making 3d elements for compositors
  4. Helping Compositors Build and keep comps up to date
  5. Publishing different outputs from comp (for editorial or DI)
  6. Dailies pipeline, reviewing work and having a running history (even when people don’t want , or can’t use shotgun)

To cover those basic goals, there was core infrastructure that had to be covered like, robust directory structure, and a fast locally hosted database driven versioning system, but i won’t bore you with those details. Instead let’s take a look at some of the more visible components of mango phase 1.

Application Agnostic Pipeline
One of my main initiatives is to write mango apps so that they can run in multiple applications with out much effort. This is done by using an API that translate generic command like file open, close, etc to native command the apps hosting the application can understand, By doing this the Pipeline logic, and UI code of the apps can be share across multiple applications.
This allows me the following…

  1. I can easily integrate new applications with mango as they start trending as long as they have python pyside support
  2. I can handle all future software integrations my self
  3. I can continue to update and enhance the applications with out the need to maintain duplicate code.
  4. Reduces testing and bug fixing freeing me up to work on other aspects of the pipeline

This is all done in the hopes that you the client, doesn’t have to worry about Mango or the big picture of Pipeline.
The current list of application that have been integrated are

  • 3dsmax
  • Maya
  • Nuke
  • Hiero.

With all that said let’s take a look at some of the applications you will be inheriting and the role the play in the Mango Phase 1 the “generalist” pipeline.
Software Launcher.

  • Configures all artist software before launching the app for the artist and allowing to begin work
  • Serves as the primary system to distribute tools, and plugins based on simple text based profiles
  • Sets a repository (for tools and plugins) standard for all applications (3ds Max, Nuke, Maya, Hiero) so that a single person can maintain all packages
  • It’s fully integrated with deadline and the render pipeline so that slaves will all ways have he same set of tools that were used when a job was submitted
  • Minimizes the need for IT to manage and push plugin installations by automating the process via deadline and user interaction with the software launcher
  • New plugins and tools can be deployed sooner and with out affecting other shows by separating this new additions into new profiles

Plate ingestion and proxy creation

  • Quickly ingest image sequences to be tracked in the pipeline via stand alone tool
  • Batch create project, sequence, shot and plate ingestion via csv ingestion tool
  • I’m currently working on writing a Hiero exporter so that plates can be ingested directly from Hiero(More on this at the end)
  • Automatically create jpg proxy versions of the ingested files via (ffmpg)
  • Automate Shotgun project creation and and tasks generation for shots

Version Viewer(Stand alone and software integrated)

  • Quickly find any and all resources made in the mango pipeline
  • Quickly jump to the directory where a selected resource version is stored
  • view resource dependencies (what went into making the currently selected version)
  • Flip current version (Currently using Open source flipbook application Jefecheck, but can be expanded to use other flipbook apps)
  • Submit version for review in shotgun (think of this as submit to dailies)
  • Setup or edit any new or exiting projects sequences or shots
  • Access the plate ingestion tools


Work Flow  Tools

Workflow tools are the apps integrated in the artist application that dictate how we do things (Mostly create resources, do hand offs, and receive new and updated work).
here’s a list of the workflow tools we will be deploying with phase 1

Workspace manger (3ds Max,Nuke,Maya,Hiero)

  • Quick tool for setting your work shot context
  • Create New work files
  • Open any previously create work files,
  • Checkout work files created by other artist
  • Find out if someone made a new version of your file in your absence

Publish Work File (3ds Max,Nuke,Maya,Hiero)

  • Just like it sounds published the currently opened work file to be shared with the rest of the studio

Snapshot Work File (3ds Max,Nuke,Maya,Hiero)

  • This, like the Publish Work File, allows the artist to iterate his current file but without publishing the work file. Think of it a private iteration (stays in the user work area)

Render Pass Manger(3ds Max)

  • Proprietary Render Pass manger for 3ds max
  • Fully tested and developed in production
  • Focus on smaller learning curve (vs RPManager), and optimized for larger scenes
  • Fully integrated into the Mango Versioning System
  • Batch Render and Publish your passes as 3delements
  • Automatically sets Exr optimized settings
  • Fully integrated with deadline (render passes can be batched rendered and published on the farm)
  • Automatically handles dependency tracking

Comp Central(Nuke)

  • This is a shot resource catalog application built for compositors
  • Find, review and import any resources available for comp
  • Find out what “Read” nodes are out of date and update them
  • Rollback “Read” nodes to any previous version at any time

Element Publish (Nuke, and Maya)

  • Render and publish your nuke outputs as any of the following resource types (comp, precomp, paint, roto, plate)
  • Render and publish¬† your Maya Render Passes as 3delements
  • Fully integrated with mango versioning system,
  • Fully integrated with deadline(render passes can be batched rendered and published on the farm)
  • Elements can also be published locally
  • Automatically handles dependency tracking

All of this tools will be available too you from day one and will be deeply covered in the training time we will have together. Please also keep in mind that as subscription customer, i will be extending Mango, and maintain all the tools and workflows above. Which means that you won’t really have to maintain any of this tools and workflows in house, and the workflows will continue to expand¬† as other areas of the pipeline are tackled.

Support and Mango Tickets
All support for mango goes through the http://mango1.zendesk.com, here you can make an account and submit problems, bugs, request
as well as track the status of any outstanding tickets (you will receive emails as well).

Shotgun Toolkit vs Mango
Mango is meant to replace the need for Shotgun Tool Kit altogether, and while Mango and STK  seem similar in fashion, it is important to know that they operate on different concepts.
When i worked with the Shotgun Tool Kit (at Pixo) I was not pleased with the over all pipeline vision that shotgun had for the tool kit, which was basically none. I can only guess this is why it is a marketed as¬† “tool kit” and not a “pipeline”.
Being that the bundled apps, “empty shells” with “hooks”, are written in a generic way so that the user can implement their own vision. I saw no real value in them, at least not in the context of my vision which revolves mostly about user experience. The overcomplicated system that comes along with using the toolkit purely as a deployment tool and directory structure generation tool, was so complicated and cover-some, That’s why I decided to create my on kit. A simpler system that allowed me to develop quickly from scratch and that did not impose road blocks by making me funnel my vision through tools that were created with no¬† practical cases in mind. What you get from Mango is not a tool kit, it is an end to end pipeline and set of standards and practices that will be developed in several separate pre-planed phases. To me the vision of the product is more important the the code it’s self. This is the main reason why i felt Mango could fill a real need in the industry, “Pipeline in a box”.¬† Mango those optionally interface with shotgun, but limits it to project management (Planning, Bidding, task tracking, work review, notes, etc), and keeps all the resource tracking to it’s self. Ideally Shotgun in mango is just a module, meaning that if at some point something better, and more affordable comes along, we can switch without changing any other aspect of the pipeline, or you can chose to not use it all.

Hiero Integration
Based on the customer feed back that I’ve received, Hiero still has some serious issues that will keep it from fully replacing smoke and flames in the near future as a finishing tool.¬† On the other hand it seems to be a great tool for creating project structure and kicking out plates and other elements received from clients via EDL, early in the post production stage.
Because of this, the first Hiero Mango integration project I want to tackle is a Mango Exporter for Hiero.
the idea being that if we get assets (movies or imagess sequences) and an EDL from the client, we can quickly create the entire mango project structure and ingest plates (and create secondary resolutions) for the entire project via Hiero.
I would like to spend some time with you guys and see in what other ways you are using Heiro in the production pipeline, to  see how else we can integrate or workflow and maximize the Hiero Mango integration.
but the Mango Exporter for Hiero is something that i hope to start developing this week.

If you guys are interested you can check an outline of the Mango development road map, and the currently scheduled phases.

Mango Development Road Map

  • Phase 1 (Finished)
    • work files
    • ¬†lighting
    • comp
    • dailies
  • Phase 2 (Hope to finish early next year)
    • Modeling
    • Rigging
    • Animation
    • Look Dev
    • Finalizing Stereo (Nuke, Maya)
  • Phase 3
    • Asset Library
    • Show Archival
  • Phase 4
    • Inter studio remote location work sharing
  • Phase 5
    • Editorial
    • Flame, Smoke
    • Hiero
  • Phase 7
    • Color pipeline
    • show,seq, shot LUTs
    • openColorIO (Hardware profiles)
    • lens Distortion Pipeline

Jul 22 2014

Using Vector Math to Rig an Arm…

Quick example on how to use vector math (mostly) to get the most out of locator based rigging. The following example makes a joint chain and a pole vector locator using 3 positions (Shoulder, elbow, and wrist). All the joint orientations and pole vector position are worked out automatically by the use of some simple subtraction addition and cross multiplication of vectors. In the future, I’m hoping to write a more in-depth explanation of my process. But, in the mean while I hope it helps those diving into auto rigging who might not be so fresh on vector math ūüôā
The example is done in Maya, since Maya joints can have much different orientations than those visually displayed in the view-port.
Being that this snippet is in python, we must make use of the mayaAPIs vector and matrix classes which is a departure to the way in which we used to do it, in mel.

Here’s the snippet!
hope you find it useful ūüôā

from maya import cmds
import maya.api.OpenMaya as om

def makeIkPlaneSetup(helpers, poleVectorDistance=5.0):
    if len(helpers) != 3:
        raise Exception('makeIkPlaneSetup input error, you need objects to pull positions from there were %s inputs\n' % len(helpers))
   
    
    shld = om.MVector(cmds.xform(helpers[0],q=True,ws=True,t=True))
    elbow = om.MVector(cmds.xform(helpers[1],q=True,ws=True,t=True))
    wrist = om.MVector(cmds.xform(helpers[2],q=True,ws=True,t=True))
    
    #figure out the upNode (plane direction)
    planeX = wrist - shld
    planeXL = planeX.length()

    armDis = (elbow - shld).length()
    foreArmDis = (wrist-elbow).length()
    fraction = armDis/(foreArmDis+armDis)
    planeP = shld + (planeX.normalize() * (planeXL*fraction))
    upNode = (elbow-planeP).normalize()
    
    pvPos = shld + (upNode * poleVectorDistance)
    
    #shoulder orintation matrix
    shdXAxis = (elbow-shld).normalize()
    shdYAxis = (upNode ^ shdXAxis).normalize() #cross product a noramalize....
    shdZAxis = (shdXAxis ^ shdYAxis).normalize() #cross product a noramalize....
  
  
    shldM = om.MMatrix([[shdXAxis.x,shdXAxis.y,shdXAxis.z,0],
                        [shdYAxis.x,shdYAxis.y,shdYAxis.z,0],
                        [shdZAxis.x,shdZAxis.y,shdZAxis.z,0],
                        [shld.x,shld.y,shld.z,1]])
    
    elbowXAxis = (wrist-elbow).normalize()
    elbowYAxis = shdYAxis
    elbowZAxis = (elbowXAxis ^ elbowYAxis).normalize()
    
    elbowM = om.MMatrix([[elbowXAxis.x,elbowXAxis.y,elbowXAxis.z,0],
                         [elbowYAxis.x,elbowYAxis.y,elbowYAxis.z,0],
                         [elbowZAxis.x,elbowZAxis.y,elbowZAxis.z,0],
                         [elbow.x,elbow.y,elbow.z,1]])
    
                     
    wristM = om.MMatrix([[elbowXAxis.x,elbowXAxis.y,elbowXAxis.z,0],
                         [elbowYAxis.x,elbowYAxis.y,elbowYAxis.z,0],
                         [elbowZAxis.x,elbowZAxis.y,elbowZAxis.z,0],
                         [wrist.x,wrist.y,wrist.z,1]])                
    
    pvM = om.MMatrix([[shdXAxis.x,shdXAxis.y,shdXAxis.z,0],
                      [shdYAxis.x,shdYAxis.y,shdYAxis.z,0],
                      [shdZAxis.x,shdZAxis.y,shdZAxis.z,0],
                      [pvPos.x,pvPos.y,pvPos.z,1]]) 
                          
    #convert matrix values to list for xform input 
    shldML = [v for v in shldM]
    elbowML = [v for v in elbowM]
    wristML = [v for v in wristM]
    pvML = [v for v in pvM]
    
    #make pole vector point
    pv = cmds.spaceLocator()
    cmds.select(clear=True) #we'll keep joints parented to avoid rotation offset which maya creates when parenting joints post creation
    
    shldJ = cmds.joint()
    elbowJ = cmds.joint()
    wristJ = cmds.joint()
    
    
    cmds.xform(shldJ,ws=True,m=shldML)
    cmds.xform(elbowJ,ws=True,m=elbowML)
    cmds.xform(wristJ,ws=True,m=wristML)
    cmds.xform(pv,ws=True,m=pvML)
    
 
    
           
    
makeIkPlaneSetup(cmds.ls(sl=True))

 


May 16 2014

Resetting Skinwrap Modifiers via maxscript

If you’re reading this right now, you’re probably trying to figure out why the F*&%$@ the built in method for resetting a skin-wrap in max, meshDeformOps.reset(), is doing absolutely nothing for you…
Turns out this method is broken in max.
Here’s a little hack that will allow press the reset button it’s self on any skinwrap via maxscript allowing you to by pass this roadblock.

cheers,

Los.

fn resetSkinWrap md = 
(
	deselect $*
	select (refs.dependentNodes md)[1]
	max modify mode
	modpanel.setcurrentobject md

	maxHWND = windows.getMaxHWND() 
	for c in  (windows.getChildrenHWND maxHWND) do
	(
		if c[4] == "CustButton" and c[5] == "Reset" then
		(
			UIAccessor.PressButton c[1]
			return  True
		)
	)
	false
)
--usage
for m in (getClassInstances skin_wrap) do resetSkinWrap m


May 2 2014

Parenting Qt gui’s to the main 3dsMax window (BlurPython)

I had a chance to break down the blurdev “Dialog” class today. This class is a modified QDialog class that automatically handles parenting the dialog to the max window. The way in which the Blur Developers achieve this is by compiling a Qt4 module (QtWinMigrate) that does not ship with either PySide or PyQt4 that can convert the windows handle id of the main max window into a QObject that we can use as a parent. the process is very simple, here’s a snippet…

from blurdev.gui.winwidget import WinWidget
parent = WinWidget.newInstance(blurdev.core.hwnd()) 

Here’s an example built directly into a class that inherits a QMainWindow…

import blurdev
from blurdev.gui.winwidget import WinWidget
from PyQt4 import QtGui,QtCore, uic

class test(QtGui.QMainWindow):
	def __init__(self,parent=None):
		if not parent:
			parent = WinWidget.newInstance(blurdev.core.hwnd()) 
		super(test,self).__init__(parent)
		self.setAttribute(QtCore.Qt.WA_DeleteOnClose )
		self.setMouseTracking( True )
		self.checkScreenGeo = True
		self.aboutToClearPathsEnabled = True

gui = test()
gui.show()

last but not least here’s the example from my previous uic post using this code to parent the window to Max.

from PyQt4 import QtGui, QtCore, uic  
base,form = uic.loadUiType(r'c:\uifiles\mainUI.ui')
class mainApp(base,form):
    def __init__(self,parent=None):
        super(mainApp,self).__init__(parent)
        self.setupUi(self)
        #----
        self.populateList()
        self.connectWidgets()
    #populate a list box...
    def populateList(self):
        self.scnObjects = mxs.objects
        for o in self.scnObjects:self.listWidget.addItem(o.name)
    #-- connect event handlers
    def connectWidgets(self):
        self.listWidget.itemSelectionChanged.connect(self.selectObjects)
        self.pushButton.clicked.connect(self.pressButton)
    #--list box events
    def selectObjects(self):
        sel = []
        mxs.deselect(mxs.objects)
        for i in self.listWidget.selectedItems():
            obj = self.scnObjects[(self.listWidget.indexFromItem(i).row())]
            sel.append(obj)
        mxs.select(sel)
        mxs.ForceCompleteRedraw()
    #-- button event
    def pressButton(self):
        sel = self.listWidget.selectedItems()
        QtGui.QMessageBox.about(self,'QtMessage',"%i objects selected" % len(mxs.selection))

def open():
	from blurdev.gui.winwidget import WinWidget
	app = mainApp(parent=WinWidget.newInstance(blurdev.core.hwnd()))
	app.show()
	return app

test = open()

Give it a go, i hope it works for you. Until next time, happy coding!


Apr 22 2014

Maxscript Simple Dictionary value type via custom struct

Sometimes being able to use a simple dictionary (vs crazy nested arrays) in max script can greatly simplify your code and can allow you to layout your logic in more manageable way.

Unfortunately maxscript doesn’t offer a native dictionary value type, and the .net equivalent “hashtable” is a pain to use in max. If you are not to keen on the use of .net hashtables you might want to try a custom struct like this one…

This dictionary struct uses “sorted keys” and “bsearch” to speed up the look up of keys and their values vs the “findItem” method that can be exponentially slower as as the number of keys grows in size.
take a look…

struct losDic
(
	private
	table = #(),

	fn binSort a b =
	(
		if a[1] > b[1] then 1
		else if a[1] < b[1] then -1
		else 0
	),
	fn formatDic dic level:0 spaces:4 =
	(
		strm = "\n" as stringStream
		padding = ""
		if level != 0 then
		(
			for a=1 to level do for b=1 to spaces do padding += " "
		)
		for k in (dic.keys()) do
		(
			val = dic.getK k
			case (classof val) of
			(
				(losDic):format "%%:\n%" padding k (formatDic val level:(level+1) spaces:spaces) to:strm
				default:format "%%:%\n" padding k val to:strm
			)
		)
		(strm as string)
	),	
	public
	fn count = keys.count,
	fn getK k = 
	(
		val = bsearch #(k) table binSort
		if val == undefined then return val
		val[2]

	),
	fn setK k v =
	(
		val = bsearch #(k) table binSort
		if val == undefined then
		(
			append table #(k,v)
			qsort table binSort
			return this
		)
		val[2] = v
		this
	),
	fn hasK k =
	(
		t = case (bsearch #(k) table binSort) of
		(
			(undefined):false
			default:true
		)
		t
	),
	fn delK k =
	(
		indx --findItem keysL (k as name)
		for i=1 to table.count where table[i][1] == k do
		(
			indx = i
			exit
		)
		if indx == 0 then throw ("there is no key "+k+" in dictionary")
		deleteItem table indx
		this
	),
	fn keys =
	(
		out = for k in table collect k[1]
		out
	),
	fn pprint =
	(
		print (formatDic this)
		ok
	)
)

Here’s an example of how it’s basic usage…

--make the dictionary
dic = losDic()
--set keys
dic.setK "pappa" 9999
--embeded dictonaries
dic.setK "powers" (losDic())
dic.setK "object" (sphere())
--set key in  the embeded dictionary
(dic.getK "powers").setK "lazerEyes" true
(dic.getK "powers").setK "bulletProof" true
--query the available keys
print "------keys------"
print (dic.keys())
print "----------------"

--loop throug a dictionary
print "print keys and values"
for k in dic.keys() do format "%:%\n" k (dic.getK k)

print "preaty print"
--prety print the dictionary
dic.pprint()

till next time!!


Apr 17 2014

Easy PyQt gui in 3ds max via blur python…

Before I start this post, I would like to mention that most of the stuff covered in this post also applies to developing in PySide and MaxPlus. I’m currently using 3ds Max 2012 at work, which is why I chose to use PyQt and Blur Python, instead of MaxPlus and PySide. The 3 development concepts (Qt Designer, uic module, and packages) I’m going to cover in this post are general standards to developing in Python and Qt altogether, and should be applied when developing for other software such as Maya and Nuke as well.

One of the most exciting aspects of having Python and Qt running in 3ds Max is the ability to write guis using the Qt4 framework. While maxscript and its strange but convenient implementation of .net have proven to be an unstoppable combination. Python and the Qt4 framework bring an ease to the development process that will leave the most hardcore maxscript gurus wondering how they ever lived without it.

With that said, I have noticed that many of my fellow max scripters using Python and Qt for the first time don’t have a good enough entry point to some of the more common practices that make development with these tools super fast and incredibly fun. I hope the following code along with this write-up will help the readers get the most out of Qt and Python when developing for 3ds Max and possibly other software like Maya, Nuke, etc. In this write-up I will be covering 3 simple concepts that I use in my daily tool development. This write-up assumes that the reader has a strong knowledge of maxscript and a basic knowledge of the python syntax. No knowledge of Qt is required. The code samples do make small use of inheritance, an object oriented programing concept that might be foreign to TDs who’ve only work in maxscript where this method does not exist. The code is simple enough to implement without the need for a deep understanding of how inheritance works.

Let’s get started!

Qt Designer (Stop writing gui code)

One of the coolest aspects of Qt is being able to use the “what you see is what you get” Qt Designer application (ships with Qt). This application allows the user to quickly create complex guis that can range from full application windows, dialogs or even other widgets, to extend Qt Designer and Qt itself. Qt Designer achieves this without the need to write a single line of code. Some max TDs might find it reminiscent to the “maxscript visual editor” tool inside of 3ds Max, except for one important factor…

It doesn’t suck.

In all seriousness Qt Designer is a fantastic little app that allows you to visualize and create your own gui by dragging widgets from a repository right into your dialog. It also allows you to use Qt’s simple and intuitive layout widgets, which developers can use to make gui that resize beautifully without all the extra code usually involved in writing resizable max roll-outs and dialogs. The layout system in Qt is enough to make most veteran maxscript developers never want to use native UI functions in maxscript ever again.

So while yes, you could just write Qt gui’s¬†in Python programmatically, which can be a pain to maintain as tools grow and change, the need to do so becomes obsolete for static UI’s. Freeing the developer to work on more important things, like logic.

UIC module (Using .ui files in python)

So now that you are all excited about using Qt Designer to start making some next level guis inside 3ds Max, you might be stuck figuring out how to get your Designer gui (a “.ui” file) into Python. A common practice is to use a uic.exe that ships with PyQt to translate¬† a “*.ui” file¬† (xml format) to a¬† “*.py” file (python code) that can be imported or pasted into your python scripts.

I personally do not like this method for the following reasons:

  1. Anytime a gui change is needed the developer has to do an extra step of converting the “*.ui” file to a “*.py”
  2. This practice encourages developers to do patches or edits in the newly created “*.py” file. A practice that can be destructive when changes require going back and forth between Qt Designer and Python.
  3. Due to reasons 1 and 2 developers might limit the use Qt Designer to creating the first version of the “*.ui” file and make all future changes outside of Qt Designer directly in converted¬† “*.py” files. This destroys the “Feng shui” of having a separation between the gui code and the code that makes up the logic of the gui.

So, what is the easier more elegant way of integrating the use of your “*.ui” files straight into Python, without the need for messy conversions, you ask?? The answer is simple…

The uic module.

from PyQt4 import uic

When used properly uic can provide a fast and easy way to convert “*.ui” files to Python classes you can start using at run-time (and in a very sexy object oriented way).

Here’s a snippet showing my preferred usage of the uic module in Python for creating fancy guis:

from PyQt4 import QtGui, QtCore, uic

base,form = uic.loadUiType(r'c:\uifiles\mainUI.ui'))
class mainApp(base,form):
    def __init__(self,parent=None):
       super(mainApp,self).__init__(parent)
       self.setupUi(self)

In this snippet our dialog will be its own class of name “mainApp”. To display the interface all we have to do is the usual:

gui = mainApp()
gui.show()

At this point our gui is done, leaving us free to just focus on the logic of our tool and connecting the widgets to the logic. Making a very clean separation between the gui and the main logic of our code. Isn’t uic fun??!!

Note:PySide uic module does not have a “loadUiType” method, but it’s easy enough to make your own here’s an example.

Python Packages (Keep your code organized and portable)

For many maxscripters that don’t have alot of experience using Python, Python packages will be somewhat of a new concept. But it’s a pretty simple one that will allow you to keep your tools very clean and organized. You won’t have to worry about them clashing with other people’s tools and libraries.

Note:For a very nerdy (by which I mean technical and in depth) description to Python packages you can checkout the following link.

To understand packages we need to understand how the Python file sourcing mechanism (“import”) works and how it relates to the maxscript’s equivalent (“filein”).

In maxscript to source code from a secondary location one can simply do a …

fileIn @"c:\myMascriptFucntions.ms"

In Python it’s not that simple. Python files have to live in very specific locations for them to be “fileined” via the “import” command. These locations are loaded via the pythonpath environment variable. If the folders that your auxiliary files are stored in are NOT in¬† the list of paths declared by the¬†pythonpath variable, Python will fail to find them when you attempt to import them. By making packages we avoid the potential nightmare of having to add the path of every folder containing auxiliary files to the pythonpath before importing them and instead we can register our root folder and any sub folders as packages. This lets Python know to automatically search this folder for any auxiliary modules our tools are attempting to load.

To start using packages the easy way, we can simply start developing from a folder that is already in the default pythonpath such as “C:\Python27\Lib\site-packages”. But, we can also make folders outside of the Python root directory and develop from there as long as we let Python know to look for packages or modules in these external folders.

In my example I’ve chosen the second options and will be using the following folder as my main development area…

“C:\dev\workspace\losDev”

To start the example tool we will¬†create a folder called sampleQtMaxGui (then name of our tool). We will then place a “__init__.py” file inside this folder effectively making our tool package.

“C:\dev\workspace\losDev\sampleQtMaxGui\__init__.py”

This registers the folder “C:\dev\workspace\losDev\sampleQtMaxGui” as a package (assuming we have added “C:\dev\workspace\losDev” to the pythonpath environment variable). At this point, Python will be able to source any files inside of this newly created package. Furthermore, we can now import our new package into any Python session via the following line…

import sampleQtMaxGui

If we wanted to get very fancy and organize our tools in more detail, we can always make more packages inside of our initial package. For example, the following structure would make a package called “3dsMax” that would host all our Python tools that only run in 3ds Max,each tool would be hosted in it’s own package.

C:\dev\workspace\losDev\3dsMax\__init__.py
C:\dev\workspace\losDev\3dsMax\sampleQtMaxGui\__init__.py
C:\dev\workspace\losDev\3dsMax\mindBlowingTool\__init__.py

Importing the “sampleQtMaxGui” tool in to a Python session would then look like this…

import 3dsmax.sampleQtMaxGui

I could then shorten the name space into a smaller variable by using the “as” expression to import the module into a shorter name space.

import 3dsmax.sampleQtMaxGui as sampleQtMaxGui

The big thing to take away from this, is that in Python it is not possible to import from sub-folders unless they contain a “__init__.py” file.¬† These “__init__.py” files don’t have to contain any code, they only need to exist. But once they do, you’ll be able to source them easily via the import method.

Putting it all together

Ok, so now that we’ve talked about Qt Designer, the uic module, and python packages, it is time that we put it all together and see how this all works in practice. What better way to do this than to make a simple example tool. (download the finished sample code here)

Before we start our tool, here are a few things that we need to know for this example:

  1. The folder containing all my tools will be “C:\dev\workspace\losDev”.
  2. We will be creating a single tool called sampleQtMaxGui.
  3.  This tool will be deployed as a package.

So, let’s make folder called “sampleQtMaxGui” and place a “__init__.py” file inside of it (ex. C:\dev\workspace\losDev\sampleQtMaxGui\__init__.py). Next, let’s make a simple “*.ui” file in Qt Designer that will be made up of a listWidget (list box) and a pushButton (button).
Here are the steps…

  1. Find designer.exe in your computer and launch it.
  2. Select the “main window” as your initial template.
  3. Drag and drop a list-widget and a push-button for the widget repository into your dialog (don’t worry about placement).
  4. Right click on the dialog and set the layout to vertical (this should automatically position your tools)
  5. After adding the widgets¬†save the gui as “mainUI.ui” inside our tool’s package under a folder called “views” (ex. C:\dev\workspace\losDev\sampleQtMaxGui\views\mainUI.ui)

Once we’ve saved the “*.ui” file inside of the package it is time to start doing the Python work. In order to keep this example simple, all of our Python scripting will happen inside the “__init__.py”. The first lines of code will be used to import all the necessary Qt modules, the Blur python maxscript commands, and the “os” module.

from PyQt4 import QtCore, QtGui, uic
from Py3dsMax import mxs
import os

Next, we will be loading our “.ui” file via the “loadUiType” method in the uic module, effectively converting the content of the file into a class we can use to inherit from.

base,form = uic.loadUiType(os.path.join(os.path.dirname(__file__),'views','mainUI.ui'))

Note the use of “os.path.dirname(__file__)“. “__file__” is a global variable that python registers, which contains the absolute path to the file we’re currently working on. By using the “dirname” method of the “os” module, we can parse this path for the name of our tool’s root directory. We then use the “join” method to concatenate this folder, our subfolder containing our “*.ui” files (“views”), and the name of our “*.ui” file. This will effectively reproduce the absolute path of our “mainUI.ui” file regardless of where the package lives.

Now, it’s time to setup our gui class and have it inherit the values generated by the “loadUiType” method…

class mainApp(base,form):
    def __init__(self,parent=None):
        super(mainApp,self).__init__(parent)
        self.setupUi(self)

The “__init__” function is a Python method built into the class mechanism that gets executed whenever our class is instantiated. This means that as soon as we run the following line this method will be ran as well…

x = mainApp()

The line “super(mainApp,self).__init__(parent)” makes sure that our parent class is also initiated. The “super(mainApp,self)” method is similar to the “delegate” pointer used when writing scripted plugins in maxscript and can be used to access method and properties stored in the parent class. After initializing our delegate or parent class, the “setupUi” method becomes automatically available to our custom class. By executing this method next, all the widgets we created in Qt Designer are then added to our class as well, leaving our class now capable to render an exact clone of the gui saved in the “.ui” file.

The “__init__” method of our custom class is also a perfect place to add other functions that would populate and connect our widgets. We exemplify this by adding the following methods to our class “populateList” and “connectWidgets”.

class mainApp(base,form):
    def __init__(self,parent=None):
        super(mainApp,self).__init__(parent)
        self.setupUi(self)
    #populate a list box...
    def populateList(self):
        self.scnObjects = mxs.objects
        for o in self.scnObjects:self.listWidget.addItem(o.name)
    #-- connect event handlers
    def connectWidgets(self):
        pass

After adding the new methods to our class, we can execute them inside the “__init__” method.

class mainApp(base,form):
    def __init__(self,parent=None):
        super(mainApp,self).__init__(parent)
        self.setupUi(self)
        #----
        self.populateList()
        self.connectWidgets()
    #populate a list box...
    def populateList(self):
       self.scnObjects = mxs.objects
       for o in self.scnObjects:self.listWidget.addItem(o.name)
    #-- connect event handlers
    def connectWidgets(self):
        pass

Next, we add the event handler methods that are to be connected in our currently empty “connectWidgets” method. For this example we will create two new methods¬† “selectObjects” that will be fired when the user changes the selection on the list box, and a “pressButton” method that we’ll be fired when the user press our push button…

class mainApp(base,form):
    def __init__(self,parent=None):
        super(mainApp,self).__init__(parent)
        self.setupUi(self)
        #----
        self.populateList()
        self.connectWidgets()
    #populate a list box...
    def populateList(self):
        self.scnObjects = mxs.objects
        for o in self.scnObjects:self.listWidget.addItem(o.name)
    #-- connect event handlers
    def connectWidgets(self):
        pass
    #--list box events
    def selectObjects(self):
        sel = []
        mxs.deselect(mxs.objects)
        for i in self.listWidget.selectedItems():
            obj = self.scnObjects[(self.listWidget.indexFromItem(i).row())]
            sel.append(obj)
        mxs.select(sel)
        mxs.ForceCompleteRedraw()
    #-- button event
    def pressButton(self):
        sel = self.listWidget.selectedItems()
        QtGui.QMessageBox.about(self,'QtMessage',"%i objects selected" % len(mxs.selection))

The last thing we do to finish our class is connect the callbacks from our widgets to the methods we want them to execute. In order to do this we add the following lines to the “connectWidgets” method. Note: callbacks in Qt are referred to as “Signals” and are built directly onto the widgets themselves, keep this in mind when looking at documentation for widgets.

        self.listWidget.itemSelectionChanged.connect(self.selectObjects)
        self.pushButton.clicked.connect(self.pressButton)

In the snippet above “listWidget” is the object name of the list box we created in Qt Designer and “pushButton” refers to the object name of the button that will also created in Qt Designer.

Our final code should look something like this…

class mainApp(base,form):
    def __init__(self,parent=None):
        super(mainApp,self).__init__(parent)
        self.setupUi(self)
        #----
        self.populateList()
        self.connectWidgets()
    #populate a list box...
    def populateList(self):
        self.scnObjects = mxs.objects
        for o in self.scnObjects:self.listWidget.addItem(o.name)
    #-- connect event handlers
    def connectWidgets(self):
        self.listWidget.itemSelectionChanged.connect(self.selectObjects)
        self.pushButton.clicked.connect(self.pressButton)
    #--list box events
    def selectObjects(self):
        sel = []
        mxs.deselect(mxs.objects)
        for i in self.listWidget.selectedItems():
            obj = self.scnObjects[(self.listWidget.indexFromItem(i).row())]
            sel.append(obj)
        mxs.select(sel)
        mxs.ForceCompleteRedraw()
    #-- button event
    def pressButton(self):
        sel = self.listWidget.selectedItems()
        QtGui.QMessageBox.about(self,'QtMessage',"%i objects selected" % len(mxs.selection))

We’re now completely done with our gui class, but there’s still one last thing we want to do. To make it simple to open our gui from any where, we will add one more method outside of our class called “open”. Here’s what that looks like…

def open():
    app = mainApp()
    app.show()
    return app

This function will allow us to open the gui right after importing our tool with the following lines…

import sampleQtMaxGui
ui = sampleQtMaxGui.open()

Running your tool from maxscript

To have a maxscript execute your tool, we make the following snippet in maxscript…

st = "import sys
losDevPath = r'C:\dev\workspace\losDev'
sys.path.insert(0,losDevPath)
import sampleQtMaxGui
gui = sampleQtMaxGui.open()
"
python.exec st

It’s important to know that the following lines are only necessary if the path to development folder has not already been added to the pythonpath. These lines allow you to add the path to your development folder at run-time vs having to mess with your machines environment variables. But, keep in mind this is a ghetto way of doing things. If you have no idea how to add your development folder to the pythonpath and you’re not working in a folder that is visible by Python, these lines might get you by for now. I strongly suggest you use this method only for testing or experimenting with the sample code in this post.

import sys
losDevPath = r'C:\dev\workspace\losDev'
sys.path.insert(0,losDevPath)

If the path to your development folder is already in the pythonpath you can use this instead.

st = "import sampleQtMaxGui
gui = sampleQtMaxGui.open()
"
python.exec st

One thing to keep in mind is that once you import a Python script into 3ds Max (or any other Python session), the code gets compiled and later changes to the code will not be refreshed unless your restart max, or force reload the library. If you want to load the latest code every time you import your tool you can add the “reload” method.

st = "import sampleQtMaxGui
reload(sampleQtMaxGui)"
gui = sampleQtMaxGui.open()

python.exec st

Anyway that’s it for now, good luck coding!!