MAXScript Values in pymxs

This topic covers how Python and MAXScript values and types interoperate with pymxs.

MAXScript Type Marshalling

For simple types such as integers, floats and strings, pymxs has a direct reference to the data, and these values can be obtained and set directly.

For complex types such as Array, pymxs wraps these objects with a MXSWrapper object, pymxs.MXSWrapperBase. For example:

import pymxs
rt = pymxs.runtime

# Create and visit a MaxScript Array
rt.execute(" gma = #(1,2,3)")
print rt.gma    #output: #(1,2,3)
len(rt.gma)    #output: 3
rt.gma[1] = 999
rt.Print(rt.gma[1])    #output: 999
l = list(rt.gma) #[1,999,3]
l[2] = 888
print l    #output: [1, 888, 3]
print rt.gma   # output: #(1, 999, 3)

# Create a MaxScript Array via python sequence
ma = rt.Array(*(1, 2, 3))
print ma    #output: #(1,2,3)

For a demonstration of how to create, manipulate and cast complex MAXScript types in Python, see the demoPyMXSTypeInterop.py example.

MAXScript Names

Name literals are commonly used in MAXScript to signify options passed to functions, and have no corresponding type in Python. They can be created with pymxs.runtime.Name(). For example, to use the equivalent of toolmode.coordsys #world:

pymxs.runtime.toolMode.coordsys(pymxs.runtime.Name("world"))

Or to get all the properties for a box, using the equivalent of getpropnames box:

boxprops = pymxs.runtime.getpropnames(pymxs.runtime.box)

This yields a list containing runtime.Name objects, however. It might be more useful to generate a list of Python strings:

boxprops = [str(x) for x in list(pymxs.runtime.getpropnames(pymxs.runtime.box))]
# gives: ['typeinCreationMethod', 'typeInPos', 'typeInLength', 'typeInWidth', 'typeInHeight', 'length', 'width', 'height', 'widthsegs', 'lengthsegs', 'heightsegs', 'mapcoords']

By-Reference Parameter Handling

Values passed back to Python from pymxs MAXScript functions that take a by-reference parameter, can be translated to Python values using the pymxs.mxsreference() function. Note that this is supported for types other than Python lists.

For example:

import MaxPlus
rt = pymxs.runtime

MaxPlus.Core.EvalMAXScript("mySphere = sphere radius:10")
MaxPlus.Core.EvalMAXScript("myType = #reference")
MaxPlus.Core.EvalMAXScript("myResultArray = Array()")

offset1 = rt.Point3(50.0, 50.0, 10.0)
myReturnedList = []
rt.maxOps.cloneNodes(rt.mySphere,  offset=offset1, cloneType=rt.myType, newNodes=pymxs.mxsreference(myReturnedList))
print "python array: "
print myReturnedList

offset2 = rt.Point3(-50.0, -50.0, 10.0)
rt.maxOps.cloneNodes(rt.mySphere,  offset=offset2, cloneType=rt.myType, newNodes=pymxs.mxsreference(rt.myResultArray))
print "runtime array: "
print rt.myResultArray

Note: for variables other than Python lists, the variable must be visible in the pymxs.runtime context. For example, to pass a variable of type Color:

import pymxs
rt = pymxs.runtime
MaxPlus.Core.EvalMAXScript("myColor = color 100 10 10")
print rt.myColor
rt.maxOps.colorById(2,pymxs.mxsreference(rt.myColor))
print rt.myColor

Whereas, this approach will not work:

import pymxs
rt = pymxs.runtime
myColor=rt.color(10,10,10)
print myColor
rt.maxOps.colorById(2,pymxs.mxsreference(myColor))
print myColor

Dictionaries

MAXScript introduced the Dictionary type in 3ds Max 2017.1, and by default use name literals for key values.

Consider this MAXScript dictionary:

my_dict = Dictionary #(#one, 1) #(#two, 2)

In pymxs, this value is wrapped, and can be used like a MAXScript dictionary, for example getting the keys property:

>>> my_py_dict = pymxs.runtime.my_dict
<Dictionary<Dictionary #name one:1 two:2 >>
>>> my_py_dict.keys
<Array<#(#one, #two)>>

In some cases, it may be more convenient to convert the MAXScript Dictionary to a native Python Dict:

my_py_dict = {str(key):myd[key] for key in pymxs.runtime.my_dict.keys}
# yields: {'two': 2, 'one': 1}

Using pymxs objects as Python Dict keys

As of 3ds Max 2019 Update 1, you can use some pymxs objects (such as scene nodes) as key values in native Python dictionaries (though not in MAXScript Dictionaries). For example:

 import pymxs
rt = pymxs.runtime

t1 = rt.teapot()
t2 = rt.teapot(pos=rt.point3(20,20,0))
t2.parent = t1

node_dict = {t1:"Teapot 1", t2:"Teapot2"}

print (node_dict.keys())
print (node_dict.values())

To determine whether a MAXScript object can be used as a Python dictionary key, you can check whether it implements the python __hash__() function.