Tuesday, May 6, 2008

Calling Python from MaxScript

Unlike Maya, 3ds Max does not have internal support for Python. But that shouldn't stop you from calling useful Python code in your MaxScripts! Here's the basics of how to do that using COM.

COM is a Windows system that supports, among other arcane things, interprocess communication. You can use a language like Python, Visual Basic, or C to define a COM "server". This is a class or function, defined by a unique identifier (GUID) and a name. Here's some gory details on COM if you're curious.

Here's a simple COM server using Python:
Requires the Python Win32 Extensions (which no TA should be without)

# A simple Python COM server.
class PythonComUtilities:
   # These tell win32 what/how to register with COM
   _public_methods_ = ['checksumMD5']
   _reg_progid_ = 'PythonCom.Utilities'
   # Class ID must be new/unique for every server you create
   _reg_clsid_ = '{48dd4b8f-f35e-11dc-a4fd-0013029ef248}'

   def checksumMD5(self, string):
      """Creates MD5 checksum from string"""
      import hashlib
      m = hashlib.md5()
      m.update(str(string))
      return m.hexdigest()

if (__name__ == '__main__'):
   print 'Registering COM server...'
   import win32com.server.register as comReg
   comReg.UseCommandLine(PythonComUtilities)
This defines a function, checksumMD5 that takes a string as input, and returns the MD5 checksum for that string.

To register the COM server on a PC, simply run the Python script. Windows records it in registry, noting which script/application it uses.

Now that's done, another application (3ds Max, in this case) can connect to that COM server's interface and call it like any other function. Here's an example of doing that from MaxScript:
-- Connect to the COM server by name
comObj = createOLEObject "PythonCom.Utilities"
-- Call the function it exposes, with a sample string
checksum = comObj.checksumMD5 "The quick brown fox."
It's that simple. The checkum value returned for our sample string is "2e87284d245c2aae1c74fa4c50a74c77".

You might be wondering what a checksum is, or what it's good for. Stay tuned and I'll show you some slick stuff you can do with them in 3ds Max. See Checksums in 3ds Max, Part 1 and Part 2.

Python COM server example adapted from code appearing in Python Programming in Win32 by Mark Hammond and Andy Robinson... a great book for getting more out of Windows with Python.

12 comments:

Jason Brackman said...

What kinds of things do you use the Hash5 for in max? I've used hash to find duplicate files (audio, textures, etc.), but what do you do with it in max? Do you have an intermediary file format that checks if its current based on a checksum?

Just wondering :).

Adam Pletcher said...

I mainly use it to find materials in the scenes that are identical property-wise, so they can be combined.

I go over that in these blogs:
http://techarttiki.blogspot.com/2008/03/checksums.html
http://techarttiki.blogspot.com/2008/07/checksums2.html

Jason Brackman said...

Thanks for the follow-up.

I have done something similar, but outside of max. Using exported Collada files, I get a list of all textures being used in all our assets and then have python use the hash library to find duplicates.

This requires a tech artist / or artist to do something with the information though.

Your solution appears automagic :). Do you run this near the end of a project? Or each time you export?

Adam Pletcher said...

You mean the RemoveDupeMaterials tool?

It was mostly run as-needed by myself or the enviro artists. A couple times near the end I made it run automatically on every file when loaded.

Adam Pletcher said...

Updated the checksumMD5 function above to use hashlib instead of the deprecated md5 module.

Jo said...

Don't know if you're still reading comments on old posts but it's worth a try.

I tried to do what you describe. With a simple pythoncom server such as yours ( just changed the class id and the function) and when I try to run the maxscript part, it gives the following error : -- Runtime error: createOLEObject: can't create instance PythonCom.Utilities

Did you ever have such a problem? ANy idea how to fix this ?

Nic blog btw :)

Adam Pletcher said...

@Jo:
Make sure you have User Account Control (UAC) fully disabled in Control Panel/User Accounts. UAC will silently stomp on lots of inter-process communication stuff.

Also make sure you're a full admin on that PC, since creating COM servers requires permissions to write to Windows registry.

Jo said...

@Adam: Thanks for your quick response!

Is it enough to run the com server as an admin? Because I did that, still don't work. But I don't have any error when I run it.

And about the UAC, I'm on windows XP and apparently it's not implemented before Vista.

Jo said...

No idea of how I could to get it to work ? Or at least how I could test my com server in some other way ? I never worked with them so I don't really know how to use it otherwise..

Really stuck here :(

Adam Pletcher said...

I don't have any ideas offhand, no. The only problems I've seen with this in the wild were attributed to UAC or admin permissions.

I'm not certain running it as an admin is enough, you might check your user is actually a full admin. Just guessing here, though.

Jo said...

Well thanks, I don't think it comes from there but I'll try running as a full admin.

Otherwise, do you know how I can test a comserver using something else than 3ds max ?

Adam Pletcher said...

You can test it using Python. If you created the COM server as described in the above post, you can test it from a Python prompt like this:


>>> import win32com.client
>>> com_obj = win32com.client.Dispatch('PythonCom.Utilities')
>>> com_obj.checksumMD5('The quick brown fox...')
u'9336f028b6d8712c67aa49a515d3e1fc'