May 23 2013

“Mango” Production Tracking System + Full Service Pipeline (PTS)

What is PTS?

A PTS (Production Tracking System) is a database that manages VFX/Animation projects and studio resources. This system helps producers, supervisors, and artists keep track of the multiple tasks that are required to successfully complete a show. It facilitates communication between departments and provides real-time reports on the current progress and cost of work. The PTS represents the integration of a “back-end” database (e.g., postgreSQL or MySQL) and “front-end” applications. The everyday user interacts with “front-end” applications which are designed to meet his unique needs. These applications can be stand-alone or integrated into third party commercial or proprietary software (e.g., task management application or resource publishing tools inside of Maya, Nuke, etc.).

Why is there a need for a PTS?

A well-designed PTS is integral to an effective studio. It allows artists, supervisors, and producers to find relevant information quickly. For example, an artist can see a list of incomplete tasks that are assigned to him. Then, a supervisor can use PTS to comment on the tasks which artists marked “ready for review” and keep a historical log of these comments. While a producer can use PTS to see which shots are over-budget and the human resources department can have a record of billable hours that are connected to specific tasks. Everyone involved in the production process is able to input and query this information through applications that are stand-alone and/or integrated with third-party commercial or proprietary software.

A few examples of common stand-alone applications in PTS are:

  • A Time-Card Application
    • creates an electronic record of billable hours in the database which replaces paper time-cards.
    • allows artists to connect these hours to a particular task which can be used to create more detailed reports.
  • A Task Management Application
    • allows supervisors to break large scale work into smaller easily-delegated tasks.
    • holds other important information to facilitate time-management (e.g., priorities and due-dates).
    • connects tasks to progress-tracking statuses (e.g., “in progress” or “awaiting supervisor review) which can be filtered by supervisors and artists.
  • A Dailies Application
    • lists all the image sequences sent by artists to be reviewed in dailies.
    • uses the versioning system in PTS to help supervisors quickly find older versions of the same sequence for comparison during review.
    • connects comments about the work being reviewed in dailies to the task that generated it to ensure the artist has a record of the notes.

A few examples of common applications which are integrated into third-party software in PTS are:

  • Publishing and Versioning Tools
    • help artists version their work while keeping a historical record of changes.
    • allow for easier hand-off of versioned work to the next step in the pipeline.
    • when necessary, allow artists to easily roll-back to previous versions of their work.
    • adds a layer of programmable quality control to ensure smooth ingestion into the pipeline.
  • An Asset Catalog
    • helps animators and layout artists quickly find and reference show-specific and updatable set-ups.
  • A Shot Resource Catalog
    • allows lighters and effects artists to quickly know the resources that make-up a 3d scene.
    • allows artists to compare version information about the resources currently in the scene.
    • Alerts artists when scene resources need to be updated.
  • Scene Building Tools
    • Eliminates the need for multiple artists to work on the same file by allowing artists to build unique files with shared resources.

A PTS adds accountability and power to a studio because it automates some of the more tedious aspects of the VFX/Animation pipeline. A pipeline that revolves around a well-designed PTS allows artists to focus the majority of their time on art, rather than addressing technical complications that arise from keeping scene files updated. In addition, it gives supervisors and producers the real-time information that allows them to track the progress of work and make better choices which maximize the project’s profit.

Why is a PTS not as Common in Smaller Studios?

If a PTS is so wonderful, why is everyone not using one? There are four simple answers to this question:

  1. Smaller studios don’t usually have the resources to employ full-time programers. So, developing and integrating a PTS into their pipeline is not cost-effective.
  2. The culture of many smaller studios means that there are no uniform standards and practices about how scene files are created and shared. Therefore, their database (the “back-end” of a PTS) mirrors this complexity and quickly outlives its usefulness.
  3. Popular expensive commercial solutions (e.g., Shotgun/Tank, FileMaker Pro, SharePoint) are a framework for a database but, they don’t offer uniform standards and practices. This leads to the previously described problem for studios who do not have strong standards and practices. Moreover, many of these commercial solutions don’t offer third-party integrated applications leaving studios to write their own.
  4. These expensive commercial options still require the additional cost of Technical Directors to update, maintain, populate, and further develop these systems; sometimes greatly increasing the cost of integrating such a system.

While smaller studios have a very large incentive to invest in a pipeline that revolves around a well-designed PTS, they often don’t. The expense in time and money for an inadequate product is too much of a risk.

What is Mango?

Mango is an economical plug-and-play solution that is designed around how people actually work in VFX/Animation studios. It recognizes that many studios do not have set standards and practices which are a predictable and repeatable way of creating quality work (Modular Asset Pipeline Flow). Mango not only provides a uniform way of accomplishing this work, but it is a ready-to-use PTS that is modeled around a uniform set of standards and practices. In addition, Mango includes tools that will run on multiple applications (e.g., Maya, 3ds Max, Nuke, Houdini) to help enforce the uniform standards and practices without limiting artists creative ability.

Mango eliminates the need to have multiple programmers to mold the system to work in your studio. It greatly simplifies the nature of the database that you will use to track this work. Mango minimizes the footprint that these kind of systems can have in a studio pipeline. It fundamentally changes the way the studio works. In other words, Mango helps to create and monitor a more efficient studio work system by providing a full end-to-end pipeline.

When will Mango be Complete?

Mango is an on-going personal project on which I am working full-time from home. It is the result of 10 years of experience working in a range of studios in the VFX/Animation industry. The base for the PTS and several applications will be complete by the end of July. The development cycle is going to be rapid this is why I am researching tying together multiple open-source libraries and technologies (e.g., Python, postgreSQL, SQLAlchemy). Not only will this make my development cycle more efficient, but it also makes it more accessible to other programmers.

Anyway stay tuned, I will add more posts related to my findings about database programing for the VFX industry.

Cheers!


Apr 26 2013

Modular Asset Pipeline Flow (Standards And Practices)

One of the main interests in my career has been developing a way in which I could optimize the process of creating and revising animated content in 3d through uniform standards and practices. My interest in this topic is rooted in my experience as a Character Technical Director (TD). A Character TD collaborates with many different departments. For example, he is the one that makes sure that models are technically sound for rigging. He also has to collaborate with animators to ensure that the animation set-ups are simple yet powerful enough for the animator to be able to achieve the desired performance. A Character TD interacts with lighting and effects to make sure that the animated performance transfers from animation department to the lighting department without breaking. He is usually in charge of adding more animated detailed to assets via simulations like hair and cloth. These are just a few of the responsibilities of a Character TD,basically, he has his hands in everything. Owing to their work responsibilities, most Character TDs frequently assume some of the responsibilities of pipeline development.

While I’ve been fortunate enough to work at studios that have had well-designed asset-based pipelines, I have worked at others that have failed to develop the standards and practices necessary to implement such a pipeline. I have drawn on these professional experiences in both kinds of studios to develop a specialization in writing 3D pipelines and developing this, often missing, set of standards and practices. The link below is an image of an up-to-date concept map of how assets should flow and can be developed through a pipeline. There are many benefits to implementing this kind of pipeline. A few of the most notable include:

  • Modeling, rigging, shading, lighting, effects, and animation can evolve parallel to each other rather than being linearly dependent. This monumentally increases the speed with which people can accomplish work by eliminating a known bottleneck.
  • It facilitates the tracking of different versions of a resource that form an asset and even allow roll-back. This ensures that artists always have the correct versions of resources on which their work depends.
  • It eliminates the need of transitioning from previs to postvis work. This avoids a common redundant step in many small studio pipelines.

This standardized asset flow allows for a very simple database model which is able to track and manage the work greatly simplifying the development of a PTS system (mango pts).

Hopefully, I will be able to blog in more detail about this soon.

Until next time!

 

Modular Asset Pipeline Flow

 


Apr 16 2013

XML Parssing in maxscript

A friend at work asked me for a quick example of how to parse an xml file in maxscript…
to my knowledge the only simple way to do this is by tapping into .net
.net has a nice, easy to use xml parser to explore the contents of an xml file..

his a snippet to get you started..
cheers!

/*
in this example we are looking at a maya point cache file which is loaded via an xml file..
we are looking to pull the cache type (single file, or file per frame )
we also want to get the number of frames..
*/
 xmlFile = @"C:\_cache\temp\XML_Test\Sphere001.xml" --your xml file

 dotnet.LoadAssembly "system.xml.dll" --source the xml parser into .net
 xmlDoc = dotNetObject "System.Xml.XmlDocument" --create your xml parser object

 xmlDoc.Load xmlFile --load the xml onto the parser object

 header =  xmlDoc.item["Autodesk_Cache_File"] --this is how you pull an element section by name

--elments can have children elements.. in this case we are quarying the value of the attribute "Type" inside the "cacheType" element which is of the "Autodesk_Cache_File"..
cacheType = (header.item["cacheType"].GetAttribute("Type"))
timePerFrame = (header.item["cacheTimePerFrame"].GetAttribute("TimePerFrame"))

print cacheType
print timePerFrame

look into using the show , and showmethods function in maxscript to exploer other functions and attributes available to the element objects.
this will allow you to do more complex parsing..
until next time!


Apr 16 2013

max script and regualr expressions…(the duber way)

cuse if you don’t know?? then now you know!!!
great post i found to day about using a .net class in maxscript to handle regular expression..
neat-O..
http://blog.duber.cz/3ds-max/the-power-of-regular-expressions


Apr 12 2013

Deploying Blur Python and QT for 3ds max in your studio..

So recently i had to figure out how to deploy Blur Python at pixomondo. most of you are probably thinking what’s the big deal?
blur python comes with an nice self contained installer? you just run the installer and “finito” Blur python for max is deployed…
well unfortunately, this type of installation is not ideal in a large studio where hundreds of machines might need python installed deployed and configured. needless to say that if you ever need to remove or upgrade the plugin this method can turn into a nightmare to manage.

Obviously this particular scenario is not unique to blur python, but it’s actually very common when configuring and managing all the 3rd party plugins that a studio might use at any given time along with max. in this situation most places that have the ability and resources to do so you usually setup up a bat file or script that artist can use to launch max.. this script usually copies all the script plugins and thing the artist needs from a network location and then initiates max start-up script copied over during the sync process can then configure max on start-up.. this way we can guarantee that every time the artist launches max his has the latest and greatest plugins every time he start a max session. so by now you are probably like “Blah Blah Blah, what does that have to do with running blur python from the network?”..

well the thing is that, if you ever tried to deploy blur python to a machine by poorly copying files or trying to load things from the network you might brake your max installation all together.. that’s why i figure i put this poorly written article together, and try and document what I’ve had to do to get blur python and blur qt running inside of max.

Ok so for my case test i was installing

BlurOffline_python26_2013-01-28_install_13200_64.exe on to 3dsmax 2012 sp10

it’s important to note this seance things might be different in previous or feature releases..

So the first thing we need to do to get the whole thing going, is to actually run the installer so that we can pull all the files from the installer and move them to our network location.

once you have ran the installer and made sure that blur python properly running in the test machine inside of max,  we need to find all the difference components that are needed  to run blur python, so that we can  move them to the network.

the main sections we want to concern our self with are…

  • the main python directory
    • c:\python26
  •  the folder containing all of blur Qt.dll’s
    • C:\Windows\System32\blur64
  • python26.dll
    • C:\Windows\SysWOW64\python26.dll
  • the blur python max plugin
    • c:\3dmax2012\plugins\blur\blurpython26.dlx
  • Blur Dev configuration filder
    • c:\blur
  • Blur Scripts
    • c:\3dsmax2012\scripts\starup
      • init_python.ms
      • init_pyhelper.ms
    • c:\3dsmax2012\scripts\python
  • 3dsmax factory QT dll’s
    • C:\Program Files\Autodesk\3DS Max 2012\bin\qt_bak

 

ideally we want to copy all this different components to a single network location from where to run copy or source them in from, an example of this would be

  • \\netowrklocation\pluginModules\blur_python26\python26
  • \\netowrklocation\pluginModules\blur_python26\blur64
  • \\netowrklocation\pluginModules\blur_python26\python26.dll
  • \\netowrklocation\pluginModules\blur_python26\plugins\blurpython26.dlx
  • \\netowrklocation\pluginModules\blur_python26\blur
  • \\netowrklocation\pluginModules\blur_python26\scripts
  • \\netowrklocation\pluginModules\blur_python26\bin\qt_bak

 

The biggest challenge with deploying blur python for max is that blur python needs to replace the factory qt dll’s with newer ones. if you don’t set this up properly it can completely hose your max installation. this will be very obvious if it happens seance on max start-up you will start seeing multiple “caddies.gup” errors. this basically means that max could not find QT on start-up, and max will quit out as soon as the initialization process is finished.

in order to setup the necessary qt dll’s there are two things that need to happen…

  1. you need to remove the following dll’s from the max root (i suggest you back this up somewhere so that you can restore them if needed)
    • QtCore4.dll
    • QtGui4.dll
    • QtXml4.dll
  2. add the path to the blur64 directory to the windows “Path” environment variable
    1. set Path=\\netowrklocation\pluginModules\blur_python26\blur64;%Path%

the thing to understand is that on start up, max will look for this dll’s on it’s root folder, but if it fails to find them it will then try and look for them in all the directories register in the windows “Path” environment variable. so by adding the path to the blur64 folder to the front of the “path” environment variable we make sure that max checks that folder first and loads everything it needs from that folder…

when you do this you should see max initialize with no more problems..

After qt is loaded the next thing we need to do to make sure the blurpython.dlx actually initializes. to ensure this  we copy the “C:\Windows\SysWOW64\python26.dll” to the 3ds max root directory…

this dll is installed when python is installed, and the blurPython.dlx will try to find it by looking in the max directory first, if it fails to find it, it then will then look inside the windows system paths, but by copying it to the max root directory we make sure that 3ds max finds the python version on the first try, and with out actually having to install python on the local machine..

Once you’ve done that the BlurPython.dlx will actually initialize, and if you have copied the “Blur Scripts” (look at the notes above) local max folder you should see the python menu get generated on max start-up..

at this point Blur Python still doesn’t know where to load the python.exe and all it’s libraries, this symptom is easily exhibited when you click on the python menu/new script and nothing happens (blur python idle should open up but it doesn’t)..

in order to fix this, we resolve once again to adding a few things to our windows environment..

  1. add the path to the python installation the “Path” windows environment variable
    • set Path=\\netowrklocation\pluginModules\blur_python26\python26;%Path%
  2. set up the defualt pythonpath environment variable
    • set pythonpath=\\netowrklocation\pluginModules\blur_python26\python26\Lib;\\netowrklocation\pluginModules\blur_python26\python26DLLs;\\netowrklocation\pluginModules\blur_python26\python26\lib-tk;\\netowrklocation\pluginModules\blur_python26\python26\lib\site-packages

at this point you should be able to fire up max, click on the python menu/new script and get the idle ui and start programing some max goodness with python..

if you don’t want to have to start max via a batch process then you could manually edit the system environment variables for the machine to make things work.. but i have found that wrapping everything in a nice batch file works well, and could be used but tools such a deadlie to render as well…

here an example of what the batch file would look like (i did not actually test this code so know you are using it at your own risk)…

@echo off
setlocal
set Path=\\netowrklocation\pluginModules\blur_python26\python26;%Path%
set pythonpath=\\netowrklocation\pluginModules\blur_python26\python26\Lib;\\netowrklocation\pluginModules\blur_python26\python26DLLs;\\netowrklocation\pluginModules\blur_python26\python26\lib-tk;\\netowrklocation\pluginModules\blur_python26\python26\lib\site-packages
set maxPath=C:\Program Files\Autodesk\3DS Max 2012
xcopy \\netowrklocation\pluginModules\blur_python26\blur c:\blur
xcopy \\netowrklocation\pluginModules\blur_python26\scripts %maxPath%scripts
xcopy  \\netowrklocation\pluginModules\blur_python26\plugins %maxPath%plugins
copy \\netowrklocation\pluginModules\blur_python26\python26.dll %maxPath%\python26.dll

start "" "%maxPath%3dsmax.exe" %*

 

if you wanted to optimize the batch file for speed you could switch the copy command to use robocopy.. and that way the files would only get copied if the one on the target machine are older than the ones on the network location..

Note

I have updated to the post to reflect something i left out… in some cases the python directory to the site packages will also have to be included in the pythonpath or blur python will not be able to find the IDLE libraries..


Apr 5 2013

Maya swaping out influences of a skincluster with mel

quick script i put together with the help of my buddy Paul Thuriot, for swapping out one influence transform for another in a skin cluster. this can be useful for transferring skinning information to different joints when editing or adding new functionality to riggs.

This code is kind of a cheat, so you don’t have to store the actual weights and entire data structure that goes with saving and loading weights.
the tricky thing here’s is running the appropriate steps in the proper order so that maya does all the heavy lifting for us.
take a looky 😀


{
	proc swapSkinClusterInflunces(string $oldInf,string $newInf,string $skinClus)
	{
		string $inf[] = `skinCluster -q -inf $skinClus`;
		for ($i in $inf) setAttr ($i+".liw") 1;
		setAttr ($skinClus+".normalizeWeights") 0;
		skinCluster -e -ai $newInf $skinClus;
		setAttr ($oldInf+".liw") 0;
		setAttr ($skinClus+".normalizeWeights") 1;
		skinCluster -e -ri $oldInf $skinClus;
		refresh();
		for ($i in $inf) setAttr ($i+".liw") 0;
	}
	proc moveNonUserBlockInfluencesToUserBlock()
	{
		source "blockPartySoftBootStrap.mel";
		string $newL[];
		string $sknClust[] = `ls -type "skinCluster"`;
		for ($e in $sknClust)
		{
			int $sknProc = 1;
			setAttr ($e+".normalizeWeights") on;
			string $inf[] = `skinCluster -q -inf $e`;
			for ($j in $inf)
			{
				string $geom[] = `skinCluster -q -g $e`;
				string $LName = longNameOf($j);
				string $buffer[];
				tokenize $LName "|" $buffer;
				if ((size($buffer[0]) < 4) || `substring $buffer[0] 1 4` != "user")
				{
					if ($sknProc)
					{
						$sknProc = 0;
						print ("....Processing "+$e+"' deforming '"+longNameOf($geom[0])+"'\n");
					}
					print ("\T\TReplacing Influence '"+longNameOf($j)+"'\n");
					//make and hang new locators if they do not exist...
					string $midManNm = shortNameOf($j)+"mvdENV";
					if (!objExists($midManNm))
					{
						string $midMan[] = `spaceLocator -n ($midManNm)`;
						parent $midMan[0] $j;
						setAttr ($midMan[0]+".translateX") 0;
						setAttr ($midMan[0]+".translateY") 0;
						setAttr ($midMan[0]+".translateZ") 0;
						setAttr ($midMan[0]+".rotateX") 0;
						setAttr ($midMan[0]+".rotateY") 0;
						setAttr ($midMan[0]+".rotateZ") 0;
						setAttr ($midMan[0]+".scaleX") 1;
						setAttr ($midMan[0]+".scaleY") 1;
						setAttr ($midMan[0]+".scaleZ") 1;
						$newL[size($newL)] = $midMan[0];
						parent -w $midMan[0];
						select $midMan[0] $j;
						do_bpHangOption("userRig",1);
					}
					//swap out influences
					swapSkinClusterInflunces($j,$midManNm,$e);

				}

			}
			if ($sknProc == 0)
			{
				print "-----------------\n";
			}

		}
		select $newL;
	}
	moveNonUserBlockInfluencesToUserBlock();
}

Apr 4 2013

3dsmax .Net and the windows environment

so i have never been a big fan of tying my tools to windows environment..
but the reality is that people do this all the time…
at some point when trouble shooting a script plugin or in my case BlurPython qt deployment you are going to have to see what the current windows environment looks like inside of the max session, which unfortunately can be very different from what the windows environment looks like to the current session of windows and other apps..
anyway.. i put small snippet togather that build on the previous hashTable (.net dictonary) example, that allows us to not only print the windows environment variables visible to max, but also returns a hash table that we could use refer to the values at a later time..
here’s the good stuff..

fn getHashKeys hash =
(
    --spit the posible keys out
    DNArray = dotNetObject "System.string[]" hash.keys.count
    hash.keys.copyTo DNArray 0
    keys = for i = 1 to hash.keys.count collect (DNArray.get (i-1))
    keys
)

fn printWindowsEnv =
(
    Environment = dotNetClass "System.Environment"
    env = Environment.GetEnvironmentVariables()
    keys = getHashKeys (env)
    sort keys
    for k in keys do format "%=%\n" k env.item[k]
    env
)
printWindowsEnv()

give it a go! see what it prints for you!


Apr 2 2013

Python lambda functions (late to the party?)

So, I’m sure I’m not the only guy that’s been working late at night trying to fix some python code on some pipeline tool written by someone else that stumble into a “something = lambda x: self.blahBlah(x)” line and was like, WTF is this lambda stuff?? well like usual I’m late to the party, and I’m sure this is something most python programers will laugh at me for, but here’s a good tutorial that help me understand what his method was all about… I’m not sure that the lambda is something i couldn’t live with out but it seems like a neat way of making wrappers for functions with a single line of code..
Enjoy the link..
http://www.blog.pythonlibrary.org/2010/07/19/the-python-lambda/


Jan 14 2013

Creating Menu’s in max

so very often TD’s look for ways to distribute their tools via menu’s. yet it has been my experience that using the code that’s documented can be really flaky seance any error half way through your code.. can make max go into to a weird state along with making your menu file corrupt all together (which means max my not start again for you until you fix this file). so while doing some work with manu’s i decided to put together a simple object oriented API that anyone could use to build this menu’s virtually with a limited number of commands..
the concept behind it is the new menu becomes a virtual object created by TD that he can append other items too.. once he is done appending this items he can commit his new menu to the ui, via the myMenu.commitMenu command.. pretty simple (i hope :D)

/*
example starts here
*/
filein @"C:\maxScript\libs\maxMenuBarApi.ms"
myMenu = ::maxMenuBarApi()



--make menu items
/*
makeMenuItem takes the following inputs
1)name to be used for the menu item..
2)category of the macro script it will be execute
3)varible name of the macros script to execute
*/

helloWorldMenuItem = myMenu.makeMenuItem "hello World Test" "0 Pixomondo Macros" "helloWorld"
helloPeterMenuItem =myMenu.makeMenuItem "hello Peter Test" "0 Pixomondo Macros" "helloPeter"
separatorMenuItem = myMenu.makeSeparator ()

menuI = #() --this will hold all the items we are adding to our menu..

append menuI helloWorldMenuItem
append menuI helloPeterMenuItem
append menuI separatorMenuItem

--adding sub menu to our menu
subMenu = myMenu.makeSubMenu "subMenu"
myMenu.addItem helloWorldMenuItem subMenu:subMenu
myMenu.addItem helloPeterMenuItem subMenu:subMenu
append menuI subMenu

--adding a nested sub menu
subMenu = myMenu.makeSubMenu "Nested sub menu"

menuA =  myMenu.makeSubMenu "menu A"
myMenu.addItem menuA subMenu:subMenu

menuB =  myMenu.makeSubMenu "menu B"
myMenu.addItem menuB subMenu:subMenu

myMenu.addItem helloWorldMenuItem subMenu:menuA
myMenu.addItem helloPeterMenuItem subMenu:menuA

myMenu.addItem helloWorldMenuItem subMenu:menuB
myMenu.addItem helloPeterMenuItem subMenu:menuB

append menuI subMenu

--let's add all the items to our ui now..
for m in menuI do myMenu.addItem m
--let's rename the menu
myMenu.setMenuName "My Menu Api Test"
--let's commit the menu
myMenu.commitMenu()

anyway the hope for this api is that TD’s can deploy menu’s faster, and with less code.. along with adding a separation of the building of the code, and the actual drawing of the menu’s.. in hope’s that bugs regarding the building of the data, will not corrupt any of the menu files of or put max in “strange limbo mode”

here’s what the API looks like..

/*
Max menu bar api
by Carlos Anguiano
*/
struct maxMenuBarApi 
(
	menuData, 
	fn dicGetKeys dic =
	(
		
        --spit the posible keys out  
        DNArray = dotNetObject "System.string[]" dic.keys.count  
        dic.keys.copyTo DNArray 0  
        out = for i = 1 to dic.keys.count collect (DNArray.get (i-1))  
		out
	),
	fn dicRemoveKey dic pkey =
	(
		if dic.item[pKey] != undefined then dic.remove pKey
	),
	fn dicSetProperty dic pKey pVal =
	(
		dicRemoveKey dic pKey
		dic.add pKey pVal
		pVal
	),
	fn getMenuName =
	(
		menuData.item["name"]
	),
	fn setMenuName newName =
	(
		dicSetProperty menuData "name" newName
	),
	fn getMenuItems subMenu:undefined =
	(
		if subMenu == undefined then subMenu = menuData
		subMenu.item["items"]
	),
	fn makeMenuItem itemName macroCat macroName  =
	(
		itemData = dotNetObject "System.Collections.Hashtable" 
		dicSetProperty itemData "type" "menuitem"
		dicSetProperty itemData "title" itemName
		dicSetProperty itemData "category" macroCat
		dicSetProperty itemData "macroName" macroName
		itemData
	),
	fn makeSubMenu itemName =
	(
		itemData = dotNetObject "System.Collections.Hashtable" 
		dicSetProperty itemData "type" "submenu"
		dicSetProperty itemData "title" itemName
		dicSetProperty itemData "items" #()

		itemData
	),
	fn makeSeparator =
	(
		itemData = dotNetObject "System.Collections.Hashtable" 
		dicSetProperty itemData "type" "separator"
		itemData
	),
	fn addItem item subMenu:undefined =
	(
		if subMenu == undefined then subMenu = menuData
		ar = getmenuItems subMenu:subMenu
		append ar item
		dicSetProperty subMenu "items" ar
		true
	),
	fn clearMenu =
	(
		mainMenu = menuMan.findMenu (getMenuName()) --see if the current menu already exists
		if mainMenu != undefined do -- if pixo menu exists then clean it out..
		(
			menuMan.unRegisterMenu mainMenu
			menuMan.updateMenuBar() 
			thePixoMenu = undefined
		)
		true
	),
	fn addItemToMenu def menu =
	(
		case def.item["type"] of
		(
			"menuitem":
			(
				theAction = menuMan.createActionItem def.item["macroName"] def.item["category"]
				theAction.setTitle def.item["title"]
				theAction.setUseCustomTitle true
				menu.addItem theAction -1
			)
			"separator": 
			(
				theAction = menuMan.createSeparatorItem()
				menu.addItem theAction -1
			)
			"submenu":
			(
				sb = menuMan.createMenu def.item["title"]
				theSubMenu = menuMan.createSubMenuItem def.item["title"] sb
				menu.addItem theSubMenu -1
				for i in def.item["items"] do addItemToMenu i sb
			)
			default:(format "% is not a valid menu item..\n" def.item["type"])
		)	
	),
	fn commitMenu =
	(
		mainMenuBar = menuMan.getMainMenuBar() 
		menuItems = getMenuItems()
		
		if menuItems == 0 then 	throw "There are no items in this menu to commit"
		
		clearMenu()
		
		newMenu = menuMan.createMenu  (getMenuName())
		for it in menuItems do addItemToMenu it newMenu		
		
		SubMenu = menuMan.createSubMenuItem  (getMenuName()) newMenu
		
		mainMenuBar.addItem SubMenu -1
		menuMan.updateMenuBar() 
	),
	fn _init_ = 
	(
		menuData = dotNetObject "System.Collections.Hashtable" 
		dicSetProperty menuData "name" "testMenu"
		dicSetProperty menuData "items" #()

		true
	),
	init = _init_()
)



Dec 20 2012

3ds Max Viewport Resizeing and Capturing explained

So i recently was asked by a fellow artist, how to properly re-size the view-ports for making previews.
it was something I had been meaning to look into but had avoided due to some bad experience i had while attempting to write a better preview tool 8 years ago (max 5) working at Digital Dimension on the Blade Trinity project.
Anyway it seems many years have passed now, and with the introduction to dotNet in maxscript some one has figured out i good way to control the size of the view port with out causing major distortion.

anyway I’ve put a simple structure that make the method of capturing a view port much simpler using some technique that i found on this cgtalk thread while i was doing my research.

the things to pay attention here is how to compile c# code at run time in order to expose the function SetWindowPos that can be found in the user32.dll

struct viewPortPreviewTools
(
	fn getViewPortAssembly =
	(
		source = "using System;\n"
		source += "using System.Runtime.InteropServices;\n"
		source += "using System.Text;\n"
		source += "class assembly\n"
		source += "{\n"
		source += " [DllImport(\"user32.dll\")]\n"
		source += " public static extern bool SetWindowPos(IntPtr hWnd, int hWndArg, int Left, int Top, int Width, int Height, int hWndFlags);\n"
		source += " [DllImport(\"user32.dll\")]\n"
		source += " static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);\n"
		source += " public struct RECT\n"
		source += " {\n"
		source += " public int Left;\n"
		source += " public int Top;\n"
		source += " public int Right;\n"
		source += " public int Bottom;\n"
		source += " }\n"
		source += " public int[] getWindowRect(IntPtr hWnd)\n"
		source += " {\n"
		source += " RECT rect;\n"
		source += " if ( GetWindowRect(hWnd, out rect) )\n"
		source += " {\n"
		source += " return new int[] { rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top };\n"
		source += " }\n"
		source += " return null;\n"
		source += " }\n"
		source += "}\n"

		csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
		compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"

		compilerParams.GenerateInMemory = true
		compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
		assembly = compilerResults.CompiledAssembly.createInstance "assembly"
		assembly
	),
	fn setwindowpos hWnd hWndArg Left Top Width Height hWndFlags =
	(
		ass = getViewPortAssembly()
		ViewHwnd = (dotnetobject "system.intptr" hWnd)
		ass.SetWindowPos ViewHwnd hWndArg Left Top Width Height hWndFlags 
		return true
	),
	fn getSuperViewportDib w:renderWidth h:renderHeight mult:1.0 displayImage:false  =
	(
		w = w * mult
		h = h * mult
		ViewHwnd = for w in (windows.getChildrenHWND #max) where w[4] == "ViewPanel" do exit with w[1]
		if act = (viewport.numViews != 1) do max tool maximize
		setwindowpos ViewHwnd 0 0 0 w h 0
		completeRedraw()
		img = (gw.GetViewportDIB())
		hwnd = windows.getmaxhwnd()
		WM_EXITSIZEMOVE = 0x232
		windows.sendmessage hwnd WM_EXITSIZEMOVE 0 0 -- force to update views client area
		if act do max tool maximize
		completeRedraw()
		if displayImage then display img
		img
	)

)

after sourcing in this code capturing the viewport in any size should be a simple as running the following code

vPPT = viewPortPreviewTools()
img = vPPT.dibFn mult:1 displayImage:True

i would like to thank all those involved in the cgtalk thread. this is one of those things that with out their contribution i could not have put together..
allot of the code here was taken from Lo and DenisT’s examples.