In 3ds Max 2019 custom file streams were exposed in the C++ API and MAXScript. This feature was a direct response to customer feedback (see https://forums.autodesk.com/t5/3ds-max-ideas/maxscript-python-access-to-a-max-file-be-able-to-read-object/idi-p/6787124). This functionality allows you to store arbitrary string data in the 3ds Max scene file itself, which is very handy for adding metadata you might want for handling scene files in your pipeline. The great thing about this feature is that it uses the standard Microsoft Structured Storage format, so you can read and write to these streams outside of 3ds Max. (See https://docs.microsoft.com/en-us/windows/desktop/stg/structured-storage-start-page)
The 3ds Max SDK has some great samples illustrating how to use this feature in C++ (see maxsdk/samples/utilities/CustomFileStream – there are two projects there, one that reads and writes to storage as a Max plug-in, the other that accesses these streams as a stand-alone application, which also provides a C++ API for doing so efficiently). This post will explore how we might use this feature purely from Python. Luckily, Custom File Streams are exposed in MAXScript, which means we can also use them in Python, in the pymxs module.
There are two scripts in this example. The first is run in 3ds Max, and saves layer and geometry data to the current 3ds Max scene file. This is fairly simple, it leverages pymxs and the new customFileStream interface to gather some data about the scene and write it to the scene stream. Using pymxs, we can use the MAXScript interface customFileStream, which implements a method to write string data (writeStream), and another to write an array of string data (writeStreamArray). The interface implements several other methods for manipulating streams, you can see the documentation here. We also register ourselves as a callback, so the file stream data gets updated every time the scene is saved.
The second script can be run outside of Max, and has no dependencies on the MaxPlus or pymxs Python modules. It does, however, depend on an OLE structured storage utility library for Python called olefile (see https://olefile.readthedocs.io/en/latest/Install.html).
Script #1:
# custom_stream_scene_data.py |
This is pretty straight forward. We create a function to write all the geometry and layers to arrays in the current file’s custom file stream. It uses a trick to get around some MAXScript syntax that can’t be replicated in Python by wrapping the “as String” coercion in a function on the MAXscript layer, and then calling that from the pymxs.runtime. To register as a callback, we first create an undefined variable in the MAXScript layer, and then connect it to our Python function.
Script #2:
# Read custom file stream data from a Max file in Python |
Here we open the Max scene file and read our three streams (always located under ‘CustomFileStreamDataStorage’). The first 6 bytes of the stream are used by Max to set flags, such as persistence (see the SDK documentation for Custom File Streams for more information). We’re throwing those away, but you will want to understand them if you want to write data to these streams outside of Max. We detect the null Unicode character \x00 as a delimiter for our arrays.
For my test scene I get output that looks like this:
|
I hope this example gives you some ideas on how you can leverage custom file streams in 3ds Max for your own workflows.