pymxs
This topic is an introduction to creating and manipulating scene objects in 3ds Max using pymxs. This is an overview, and contains some examples. For more comprehensive information about all objects you can create and manipulate, see the Node: MAXWrapper group of topics (under 3ds Max Objects and Interfaces) in the MAXScript Help.
Objects in a 3ds Max scene include geometry, shapes, lights, cameras, helpers, spacewarps, and systems. They are organized in a tree structure called the scene graph, with the help of a node object, with a root node at the top. Each node can have one parent and multiple children. The Scene Explorer reflects this structure. Each scene graph node stores information about the object at that location in the scene graph, such as its name, position rotation and scale, shading, modifiers, visibility, layers, and so on. For more information, see Working with the Scene Graph.
Most examples in this topic are run from the MAXScript Listener window, which also has a Python commandline mode. This allows us to see results immediately in 3ds Max, and to query objects. Examples run in the listener are indicated with the Listener Python prompt >>>
.
Objects are created with constructors. All of the properties of an object can optionally be set when it is constructed, and if they are omitted the object's defaults are used. For example, to create a teapot:
>>> my_teapot = pymxs.runtime.teapot()
>>> my_teapot
<Teapot<$Teapot:Teapot001 @ [0.000000,0.000000,0.000000]>>
This creates a new teapot object at position [0,0,0], with a radius of 25 and 4 segments. To change these parameters at creation time, we can specify them in the constructor:
>>> teapot_position=pymxs.runtime.point3(100,20,10)
>>> my_teapot = pymxs.runtime.teapot(radius=50, pos=teapot_position, segments=2)
We can now manipulate any of the object's properties. For example, to turn our teapot into a simple pot by removing its handle and spout:
>>> my_teapot.spout = False
>>> my_teapot.handle = False
All of the available properties for a teapot are documented in the Teapot: GeometryClass topic in the MAXScript Help.
We can also use any of the common properties, operators or methods exposed by the Node
class. For example, we can query whether the object is a Shape (with isShapeObject()
) and get the number of face and vertices (with getPolygonCount()
):
>>> pymxs.runtime.isShapeObject(my_teapot)
False
>>> pymxs.runtime.getPolygonCount(my_teapot)
<Array<#(192, 98)>>
A modifier transforms an object. Most modifiers are "object modifiers" that are applied to objects and transform them in their own local space, and perform deformations such as bend, taper and twist.
A modifier takes an object and either modifies it directly, or creates a new object from the input object. Many modifiers only apply to certain characteristics (that is, channels) of an object such as the vertices or the faces.
Modifiers can be specific to certain types of object, for example, the Volume Select Modifier only works on triangle mesh objects (TriObject).
To modify our object from the previous example, we first create a new modifier, then set it's properties, and then add it to the object (though you can also add it to the object, and then set the properties).
>>> my_modifier = pymxs.runtime.taper()
>>> my_modifier.amount = 2.0
>>> my_modifier.curve = 1.5
>>> pymxs.runtime.addmodifier(my_teapot, my_modifier)
There are two ways to work with a geometry object's mesh in pymxs/MAXScript: convert the object to a mesh object (such as Editable_Poly) using pymxs.runtime.convertToMesh()
or convertToPoly()
, or apply one of the mesh modifiers to the object (such as Edit_Poly). Collapsing an object with modifers applied to it also converts it to a mesh object, an Editable_Mesh (a tri-mesh) by default, or an Editable_Poly if Edit_Poly is in the modifier stack.
It is also possible to construct a mesh from scratch in pymxs/MAXScript. See the mesh_and_cpv.py
sample for an example of how you could do this.
There are three transforms related to scene nodes: the Node Transform Matrix (held in the object's .transform
property), the Object-Offset Transform Matrix (held in the object's .objectoffsetpos
, .objectoffsetrot
, and .objectoffsetscale
properties), and the Object Transform Matrix, which is the combination of the Node Transform and Object-Offset matrices. This is held in the object's .objecttransform
property.
For a detailed discussion of how to access and manipulate these transforms, as well as the "shortcut" properties such as .pos and .rotation, see the "Using Node Transform Properties" topic in the MAXScript Help.
This example script is an adapation of the sample script in that topic adapted to use pymxs:
from pymxs import runtime as rt
# define function for coercing a rotation to a quat
rt.execute("fn r2q r = (return r as quat)")
def DumpXForms (obj):
# output node transform properties
print "{}:\t{}".format("transform", obj.transform)
print "{}:\t{}".format("position ", obj.pos)
print "{}:\t{}".format("rotation ", obj.rotation)
# output node's pivot point location
print "{}:\t{}".format("pivot ", obj.pivot)
# output object offsets
print "{}:\t{}".format("objectoffsetpos", obj.objectoffsetpos)
print "{}:\t{}".format("objectoffsetrot", obj.objectoffsetrot)
print "{}:\t{}".format("objectoffsetscale", obj.objectoffsetscale)
# output object transform
print "{}:\t{}".format("objecttransform ", obj.objecttransform)
# output vertex position in local and world coordinates
# we do this because 'in coordsys' is not available in pymxs
rt.setRefCoordSys(rt.Name('local'))
print "{}:\t{}".format("vert 1 (local) ", (rt.getvert( obj, 1)))
rt.setRefCoordSys(rt.Name('world'))
print "{}:\t{}".format("vert 1 (world1) ", (rt.getvert( obj, 1)))
# calculate and output vertex position in world coordinates
v_pos = rt.getvert( obj, 1) * obj.objecttransform
print"{}:\t{}".format("vert 1 (world2) ", v_pos)
# define function for rotating only the pivot point
def RotatePivotOnly( obj, rotation):
rotValInv=rt.inverse (rt.r2q(rotation))
with pymxs.animate(False):
obj.rotation*=rotValInv
obj.objectoffsetpos*=rotValInv
obj.objectoffsetrot*=rotValInv
b=rt.box(pos=rt.Point3(75,75,0)) # create a 25x25x25 box, vertex 1 at [62.5,62.5,0] (world)
rt.convertToMesh (b) # convert box to mesh so we can access the vertex location
DumpXForms(b) # print transforms
b.pivot= rt.Point3(50,50,0)# move pivot only to [50,50,0]
DumpXForms (b) # print transforms
rotation = rt.EulerAngles( 0, 0, 35)
RotatePivotOnly (b, rotation) # rotate pivot only 35 degrees about local Z
DumpXForms (b)# print transforms
You can access custom attributes on an object by using pymxs.runtime.custattributes
:
from pymxs import runtime as rt
print rt.custattributes.getpblockdefs(rt.custattributes.getdef(rt.getnodebyname("Sphere001"), 1))
Assuming $Sphere001 has a custom float attribute, the result would be:
#(#(#main, 0, 0, #(#rollout, #params), #(#myattr, #(#type, #float, #ui, #(#myattr), #default, 0.0))))