3ds Max Command Port

I don’t like 3ds Max, but some times in life you have to work with things you don’t like.

I have recently found a need to send commands to Max from a standalone python app I’m writing. After a few differently worded Google searches it became apparent that Max does not have this, and not only that, there isn’t any real solution. I did find one $5 solution that didn’t promise the source code, something I needed for my project. So I set out to write my own script to open up a command port in Max. This project met my needs at the time, it hasn’t been tested outside of those constraints so it may not work at all for you.

Here’s my little script with all the functions you need to get started.

https://gist.github.com/ImLucasBrown/54ef4b4b18d012a37e0db995ca165a5c

The listener() function uses the built-in socket library to open a port and listens for data to come through it. You can read more about sockets from this great little thread here (I don’t like puns so its not intended). If you run this function as is it will hang Max (or any application really) until it gets “exit“ as command.

def listener():
    max_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    max_socket.bind((HOST, COMMAND_PORT))
    command = None
    while command != "exit":
        max_socket.listen(5)
        connection_obj, sender = max_socket.accept()
        data = connection_obj.recv(1024)
        if data:
            command = str(data.decode())
            print("Command: ", data.decode())
            if command == "exit":
                break
            try:  # Try to evaluate the command.
                MaxPlus.Core.EvalMAXScript("%s" % command)
            except Exception as e:
                print(e)

    max_socket.close()

So preferably we want to be able to use Max while the port is open. Use this little function to thread the listener and avoid the application freezing while listening.

def open_max_command_port():
    listen_thread = threading.Thread(target=listener)
    listen_thread.start()

You should note, as far as I know, Python in Max is not a totally thread safe so this project can’t do everything, its more of a proof of concept that I whipped up in an afternoon.

To test this for yourself you just need to write a little Max startup script to load my whole script into memory and run the open_max_command_port() function.

Surface Constrained Control

I’ve been toying the the concept of constraining a joint control to the surface of a skinned mesh.
This has been my result and steps I’ve taken to get here are documented below.

You can download my scene file here, the node editor should have several tabs that show these graphs.

Result01.gif

The control hierarchy is setup like this:
The _orient can be used to setup the rest orientation of the setup.
The _auto can be driven but an RBF or anything really. More layers can be added, but they must be before _auto.

ControlHierarchy01.PNG


ReverseNodes01.PNG
ReverseNodes02.PNG

Connect the control’s translate through a multiplyDivide node and set input2 to -1. This allows us to change the transform of the control without any visual change.


Place the control setup at the location of the joint or wherever you want the rest location to be.

To constrain the control to the mesh I use a follicle in a setup like this.

SurfaceContraint01.PNG

The follicle shape node will need a UV coordinate to actually function so here is a little setup you can use to convert a worldspace position to a UV coordinate on a mesh.


Create a closestPointOnMesh node and a locator or any transform in worldspace. In this case I’ve made a locator and added to channels “U“ and “V“, just to make it easy to read the output.

UVprobe01.PNG

Snap the UV_PROBE to the location of the control setup and copy the UV cords into the follicle shape node you created.

UVprobe03.PNG
SurfaceContraint02.PNG
UVprobe02.PNG

Once you have the UV coordinate in the shape node you should see the follicle transform the the same position as the UV_PROBE and the control setup. Create a pointContraint between the follicle transform and the _surface node in the control setup.

With that done we need to do a little matrix math to finalize the setup and connect the output to the joint.

We need to create a composeMatrix node (I called mine _dummyMatrix) and connect the control to it. Then we need a multMatrix node. Connect the output from the composeMatrix you just made to the multMatrix.matrixIn[0]. Next connect the world matrix of the _auto node to multMatrix.matrixIn[1]. The final matrix we need to plug in is the parentInverseMatrix of the joint, this just puts our result into the local space of the joint. Make sure you plug it into the multMatrix.matrixIn[2].
The final step is to connect the result of the multMatrix to the joint via a decompMatrix node as shown.

MatrixMath01.PNG

Some things to note, if your skin weights change it will change how the controls act as well as the final shape of your mesh. This connection is not 1:1, it is dependent on the skin cluster of the mesh. In any case, this should result in something like this.

Result01.gif