Best way to set up a network toolkit


#1

I’m looking into the best way to set up a network toolkit, for the moment I’m happy for it just to be a Custom Shelf with some buttons on to launch the tools, this is what we have in 3dsmax and it works well.

I’m struggling to get to grips with how the tools should be centralised. If each of my tools is a module located on our network how do I get each workstation to reference these modules?


#2

It is not a good idea to load plugins from a network device. It works but if you use Windows, this can lead to problems, which depend on the amount of workstations. Imagine you have a new version of a plugin with exciting new features. The basic idea is simply to replace the old plugin. Unfortunatly this will not work if any of the workstations have the plugin loaded. And if you have a bunch of rendermachines, it may never work. So you will be forced to finish all maya sessions, replace the plugin and start again. I’d recommend to create a simple batch file wich starts maya. Before it starts, all necessary modules are copied to the local disk and start from there.

Concerning modules: If you have a batch file to start maya you can set the MAYA_MODULE_PATH there for every plugin. Or you simply set it once to a directory where all module files are collected. Every module then points to the correct location of the module.


#3

Having a central network repository for tools is very handy and works for several dozens of machines (here its ~10 windows workstations and some more linux rendernodes).
It might get problematic in big installations but while it works it’s easier, you don’t have to worry about synchronizing every machine.

I’d opt against overwriting plugins. Keeping all versions as separate directories gives more flexibility and allows to easily move back to different environment (from an older project for example).

I’m not a big fan of modules and the method we use is an old trick, but works well for us.
Each package (not a maya module) has an INSTALL.mel script that is sourced by a maya startup script (usually a userSetup.mel) - so for example:

source "//server/Tools/autoRigger/1.1.0/__INSTALL__.mel

The INSTALL.mel file can be aware of it’s network location and load/source all needed plugins and py/mel scripts.
If you have a good environment control with this method it’s easy to select which version to load, and each package is “self contained”.

Here’s a (little messy) example of install script:


proc _INST_Voxelizer_sourceScripts(string $dirPath)
{
	if ( `filetest -d $dirPath` ){
		string $getMelFiles[] = `getFileList -fld ($dirPath) -fs "*.mel" `;
		if ( `size $getMelFiles` ){
			//print ("Sourcing: " + $dirPath + "
");
			for ( $melFile in $getMelFiles ){
				if($melFile == basename("_INST_Voxelizer.mel", "") )
					continue;
				string $f= `fromNativePath ($dirPath+$melFile)`;
				catchQuiet (`eval ("source \"" + $f + "\"")`) ;
			}
		}
	}
}

proc _INST_Voxelizer_sourcePyScripts(string $dirPath)
{
	if ( `filetest -d $dirPath` ){

		string $pyFiles[] = `getFileList -fld ($dirPath) -fs "*.py" `;
		string $pycFiles[] = `getFileList -fld ($dirPath) -fs "*.pyc" `;
		string $pythonFiles[] = `stringArrayCatenate $pyFiles $pycFiles`;
		for($i= 0; $i < size($pythonFiles); ++$i) {
			$pythonFiles[$i] = basename($pythonFiles[$i], fileExtension($pythonFiles[$i]));
			$pythonFiles[$i] = `substring $pythonFiles[$i] 1 (size($pythonFiles[$i])-1) `;
		}
		$pythonFiles = `stringArrayRemoveDuplicates $pythonFiles`;

		if ( `size $pythonFiles` ){
			//print ("Sourcing python files from : " + $dirPath + "
");
			string $pycmd= "import sys
";
			$pycmd += ("sys.path.append(\"" + $dirPath + "\")
");
			python $pycmd;

			for ( $File in $pythonFiles ){
				if($File != "") {
					string $pycmd= "from " + $File + " import * ";
					python $pycmd;
				}
			}
		}
	}
}

proc _INST_Voxelizer_loadPlugin(string $dirPath)
{
	float $mayaVersion = getApplicationVersionAsFloat();
	if(!endsWith($dirPath, "/"))	$dirPath += "/";
	string $plugin_dir = $dirPath + "plugins/";
	$plugin_dir += $mayaVersion;
	string $plugin_name = $plugin_dir + "/Voxelizer";

	//LD_LIBRARY_PATH
	/*
	if( `getenv "PLATFORM"` == "linux64" )
	{
		putenv "LD_LIBRARY_PATH" ($plugin_dir + ":" + `getenv "LD_LIBRARY_PATH"`);
	}
	*/

	print ("Loading " + $plugin_name + "
");
	catch(`loadPlugin $plugin_name`);
}

proc _INST_Voxelizer_icons(string $dirPath)
{
	if(!endsWith($dirPath, "/"))	$dirPath += "/";

	string $OS= `about -os`;
	if($OS == "win64")	$OS = "nt64";
	string $sep = ";";
	if($OS == "linux64")	$sep = ":";

	string $icons = $dirPath + "/icons";
	putenv( "XBMLANGPATH", ( $icons + $sep + getenv("XBMLANGPATH") ) );
}

global proc _INST_Voxelizer()
{
	print "Loading Voxelizer
";
	string $dirPath = substitute( "Mel procedure found in: ", (string)`whatIs "_INST_Voxelizer"`, "");
	string $filepart = (string)`match "[^/\\]*$" $dirPath`;
	$dirPath = `substitute $filepart $dirPath ""`;
	$dirPath = `fromNativePath $dirPath`; // <<== this is now a base dir for your package

	_INST_Voxelizer_sourceScripts($dirPath + "scripts/");
	_INST_Voxelizer_sourcePyScripts($dirPath + "scripts/");
	_INST_Voxelizer_icons($dirPath);
	_INST_Voxelizer_loadPlugin($dirPath);
}

_INST_Voxelizer();


#4

Interesting approach. I’m a huge fan of modules :).
With a module, everything what you do in your load script is done automatically, except the plugin loading. The great thing about modules is that you can use different paths/files for different maya versions and different OS.

But you are partially right about versions. In our environment the mayor versions have their own path but all minor releases and bugfixes will replace the old version.


#5

OK thanks for the info, I’m still trying to get something set up…

I’ve got a shelf set up that’s on a network location that all the workstations are loading, with all the buttons for our custom tools but I’ve yet to get them linked up to running the scripts.

The Module solution looks like it should work for us…
I’ve wrapped the code for my tool in, this file is called renderTest.py
def main():

with
if name == “main”:
main()

After it…

So now I have a module, where do I put it… we have a network scripts folder which is successfully loading MEL files,

It has a userSetup.mel file that sources a mel script for Deadline, and there’s Mel file for OpenPipeline which also successfully imitates in Maya so we can run it using ‘openPipeline’ in the Mel Listener.

I’m guessing I need to put…
import renderTest

…somewhere…


#6

You can put your file anywhere wherever the MAYA_SCRIPT_PATH points to.
Maya searches in MAYA_SCRIPT_PATH and in PYTHONPATH for python scripts.
If you are talking about a real maya module, then normally a module structure looks like this:

module

  • plug-ins
  • scripts
  • icons

If you have this structure, and the python file is located in the scripts directory of the module, it is found automatically.


#7

Thanks Haggi, for the moment I just want to import a module…

I’ve set the PYTHONPATH, put the renderTest.py file in that folder, I can now run
import renderTest

successfully…

but if I try running just
renderTest
which returns

Result: <module ‘RenderTest’ from ‘\path…path\RenderTest.py’>

but doesn’t launch my UI…

if I type
renderTest.main()
I get this returned…

Error: NameError: file \path…path\RenderTest.py line 132: global name ‘cmds’ is not defined

I’ve tried adding…
import maya.cmds as cmds
in my def Main… but I get the same problem…

Really frustrating teething problems :slight_smile:

I’d like to learn more about how to set a ‘proper maya module’ as well.


#8

Do you start the script from within Maya? If so, the usual “import maya.cmds as cmds” should work.


#9

I advise against sourcing and running scripts from the network. Changing out files underneath Maya while it is running can cause alot of headaches, and it flat out will not work with plugins, that need to be unloaded and reloaded in a scene containing no references to the plugin.

Things get worse if you introduce a bug in your code and copy it to the network. Now all users will be broken.

I would strongly advise some kind of update script that copies all the files down to the local machine.

We don’t use Modules, we have a custom startup script for Maya which configures the PLUG_IN_PATH and the PYTHON_PATH as well as some of the other environment variables for icons and such. Updates to our script are deployed usually twice a week, which gives the TA some time to test their changes after submitting their changes.

Each python script has to include the line “import maya.cmds” within it. If you launching GUIS within Maya, don’t use main. Main is for when a python program is run directly.

Instead you need a “showMyGui” function in your script.

For example:


import maya.cmds
 
class MyGui():
	pass
 
def showMyGui():
	gui = MyGui()
	gui.show()

And inside Maya:


import myGui
myGui.showMyGui()


#10

It doesn’t matter if a faulty code is loaded from network or copied to local machine, it will affect user same way.

Again:

  1. keep all version on network, do not overwrite anything (hard to control: what maya session is running what if you just overwrote files ? What is currently loaded on renderfarm nodes ?)
  2. develop an easy method to configure which user loads what version of plugins. For starters, all users can load the same stable release, and you (developer) and some other beta-test users can load development code branch.

Ad1: If you encounter problems with network sourcing (poor net or server), then you can start thinking on synchronizing workstations’ disks at maya boot. But this is another layer of complexity.


#11

An example that I’ve seen in production that’s quite similar to rgokovach’s setup: some sites use Perforce or some other source code repository and store their scripts/plug-ins/binaries there.

In this setup, launcher scripts or environment shells are used to setup Maya on launch and ensure it syncs to the correct version of the scripts/plug-ins/binaries for a given show/production. A config file in the repository is used to determine the versions to use even if the repository has a later version. This permits a TD or select group of artists to run the updated scripts/plug-ins in a test env before pushing it out to the rest of the production.

I’m not saying this is better than a network based setup and you could certainly add some formality to the network approach to stage your releases, but I will say that having a local copy of scripts/plug-ins is nice in that you don’t have to worry about network stability.


#12

Network vs local and overwriting vs keeping all versions available are two different things.
You can mix those decissions in any combination :slight_smile: ie. keep local and always overwrite or keep local all versions

What was important to me (and I think I failed to express it clearly) that

  1. network is easier, at least for beginners (actually we do have mix: maya locally, anything else network)
  2. overwriting is easier too, but dangerous

Anyway, I guess these things must be learned in practice, and tailored to custom needs :slight_smile:
OP seems to be succesful with loading scripts off network.

cheers ! :slight_smile:


#13

I’ve had a network repository for scripts with maxscript for years, at one point I had local copies of scripts on the machines but I actually found I had more issues with keeping those up to date. My tools are fairly simple UI tools for doing automated tasks so won’t be heavily linked in to the scene for now. I will look to move to a more advanced revision system at some point, for the moment I just want to get the same set of few tools which speed up the workflow out to every seat in our company.

So Now I have this…

import maya.cmds as cmds
class MyGui():
    # my code for the class, defs...
    cmds.window(widthHeight=(420, 100), title = "Render Test")
    cmds.button(label='Render Test with Low Quality Settings', height=40, command = lambda *args: renderTest())
    cmds.showWindow()

def showMyGui():
	gui = MyGui()
	gui.show()

I have this in my PYTHONPATH folder… when I open Maya, and run
import RenderTest

The tool launches the UI…

but if I try that again it doesn’t launch…
if I try
RenderTest()
I get…

Error: TypeError: file <maya console> line 1: ‘module’ object is not callable

And nothing launches…

RenderTest.showMyGUI
RenderTest.showMyGUI()
Don’t work…

Any more help is gratefully appreciated, it’s almost there!


#14

The symptoms you describe suggest that your module (named ‘RenderTest’) has some code that is not enclosed in function/class, and it gets executed on first invocation of ‘import RenderTest’.
That’s why it works first time. It does nothing on second invocation of ‘import RenderTest’, because module is already loaded and python skips this.
More, when you invole ‘RenderTest()’ you try to call module as it was a function - hence the error “‘module’ object is not callable”.
So I suspect that now your module actually looks like this:


import maya.cmds as cmds
class MyGui():
    # my code for the class, defs...
    cmds.window(widthHeight=(420, 100), title = "Render Test")
    cmds.button(label='Render Test with Low Quality Settings', height=40, command = lambda *args: renderTest())
    cmds.showWindow()

def showMyGui():
	gui = MyGui()
	gui.show()

#### !!! - this is wrong:
showMyGui()
#### !!! 


Your module should not have that last line. Everything in module must go into class or function.
Now, when you get your MOD fixed, all you need in maya is to have a shelf button with two commands:


import RenderTest
RenderTest.showMyGui()

I can’t be 100% sure without actuall code, but … :slight_smile:
IF this won’t work, you must have done smth. wrong with class definition or showMyGui() function.


#15

This is basically my code, I haven’t got that last line you mention.

class MyGui():
    # my code for the class, defs...
    class test(object):
        properties
    def renderTest(*args):
        #function called by the button

    cmds.window(widthHeight=(420, 100), title = "Render Test")
    scrollLayout = cmds.scrollLayout(
			horizontalScrollBarThickness=16,
			verticalScrollBarThickness=16)
	cmds.rowColumnLayout( numberOfColumns=1 )
    cmds.button(label='Render Test with Low Quality Settings', height=40, command = lambda *args: renderTest())
    cmds.showWindow()

def showMyGui():
	gui = MyGui()
	gui.show()

#16

But you have code in class that’s not a method - so effect is very similar.
Here’s a corrected version that works for me.
I put window building inside class constructor, and showing in a separate method.
Also, I simplified binding renderTest() method to button;


import maya.cmds as cmds

class MyGui():
    # my code for the class, defs...
    def renderTest(*args):
        print "Click!"

    # class constructor - builds window, but no show yet
    def __init__(self):
        self.WIN = cmds.window(widthHeight=(420, 100), title = "Render Test")
        scrollLayout = cmds.scrollLayout(
    			horizontalScrollBarThickness=16,
    			verticalScrollBarThickness=16)
    	cmds.rowColumnLayout( numberOfColumns=1 )
        cmds.button( label='Render Test with Low Quality Settings', height=40, command = self.renderTest )

    def show(self):
        cmds.showWindow(self.WIN)


def showMyGui():
	gui = MyGui()
	gui.show()


#17

Thanks! Eventually got it working!

Had a few issues with needing more self references but got it launching now on all the machines.

Thanks everyone, I’ve got lots to learn about Python, so expect more from me! Not sure I entirely understand why the self argument has to be passed around so much.

I had to put it in
def renderTest(self):
to be able to use the renderSettings class within the def. And now all works.

Thanks again!


#18

Methods that you add to a class are by default considered “instance methods” which require an instance of the class to call them. So for instance in your case, this is why you need to add self which serves as a reference to the instance of the class that you invoked the method from.

mg = MyGui()
# call as an instance method requires calling it from an instance of the class
mg.renderTest()

The value of an instance method is to access instance specific values. So for instance if you set a value on an instance of the class say in your init function, you would then be able to access it via the instance method

class MyGui():
	def __init__(self):
		self.foo = 5 #instance data member
	def renderTest(self):
		print self.foo
mg = MyGui()
mg.renderTest()

You could also specify a class as a class method. This is a method that doesn’t need an instance to run but does require a class. In this case, the first parameter to a class method is cls which is a reference to the class itself (not an instance of it).

class MyGui():
	foo = 5 #class data member
	@classmethod
	def renderTest(cls):
		print cls.foo
# dont need to instantiate the class to call this function
MyGui.renderTest()

Another type of function you can create is a staticmethod which can be called. This is the case where the method doesn’t depend on any part of the class. Something like utility functions for example.

class MyGui():
	@staticmethod
	def renderTest():
		print 5
MyGui.renderTest()

Which type of method you use depends what the intent of the function is. Do you need access to instance members, class members or nothing? I probably explained it worse than the dedicated webpages about these topics. If you search for python instance class static methods you should find a wealth of info on the topics.


#19

Thanks for that, going from Maxscript’s Struct semi-class/object system to a proper OOP language is trickier than I hoped, prepared in some ways and spoilt in others!

I’ve been reading about class inheritance and static methods but actually putting those in to practice is my next step.