Extending MAXtoA Translation Through MaxScript Plugins - Arnold for 3ds Max
How This Works
This document describes a method that MAXtoA, i.e. the translator that translates the 3ds Max scene into an Arnold scene - uses when translating certain types of objects. The method allows 3 rd party developers or even users a trivial way to make nice 3ds Max "wrappers" with a nice custom UI around Arnold nodes that are not shaders. For customizing the UI for Arnold shaders, a different, much more sophisticated Qt-based functionality is available.
MaxScript "Scripted Plugins"
In 3ds Max, anyone can write a "scripted plugin." They can be full-blown plugins that do all the true work completely by scripting, but they can also be very lightweight "wrappers" around existing plugins. They just modify the user interface of some existing underlying functionality.
We utilize this in MAXtoA. By writing a plugin that exposes certain specially-named parameters, the MAXtoA translator will react with these parameters, and perform a translation task based solely on the parameters, their names, and values. MAXtoA completely ignores the "internals" of the plugin, if it is an empty dummy, or if you have "wrapped" it around some existing 3ds Max class.
3ds Max itself (or other renderers) will "see" the inner (wrapped) class, and this will be what shows up in the viewports or acts in the modifier stack, etc., whereas MAXtoA and by extension the Arnold renderer will react only on the special parameters, and instantiate some particular Arnold node, and set its parameters accordingly.
What Can be Extended?
Not everything in MAXtoA can be extended in this manner yet. Currently, three things work:
- Geometry: Any scripted geometry plugin can be turned into any Arnold shape node.
- Cameras: A scripted camera can be turned into any Arnold camera node.
- Modifiers on Lights: A scripted modifier applied to an Arnold light can be turned into any Arnold light filter node.
With these, it should be possible to do almost anything, and make any Arnold shape, camera or light filter node work in 3ds Max, regardless if the said node is built into Arnold, or comes from a third party plugin DLL.
Parameter Layout and Naming
The whole thing is extremely simple; the scripted plugin simply exposes parameters using a certain naming convention, and MAXtoA acts on this naming convention and performs the translation accordingly.
The "special" parameter names are:
- arnold_node: Has to be of MaxScript type #string and contains the name of the Arnold node to be instantiated for this particular script.
- arnold_node_ xxx : This is the way to set parameter xxx on the Arnold node. I.e., anything following the prefix "arnold_node_" is taken to be the parameter name. Take care to match the type in the scripted plugins parameter block with the type that the Arnold node expects!
- arnold_link_ xxx : This is the way to set parameter xxx as a link to a shader (texture map in 3ds Max) on the Arnold node. I.e., anything following the prefix "arnold_link_" is taken to be the parameter name on the Arnold node. Has to be of the MaxScript type #texturemap. MAXtoA takes care of the shader node creation and parameter assignment on the Arnold side and links the translated shader to the parameter on the Arnold node (defined as arnold_node here).
- arnold_decl_ xxx : For user parameter types, they need to be declared before they can be used. Anything following the "arnold_decl_" prefix is taken to be the parameter name. The parameter itself must be of MaxScript type #string and contain an Arnold parameter declaration string, for example, "constant FLOAT" for a float value. In Arnold 5.0, this is rarely necessary.
Note that parameters are "used" top to bottom, and declarations must obviously precede usage.
Code Examples
Simple Example: A Spherical Camera Plugin
This creates a new camera called "Spherical Cam" in the Arnold category, which simply turns on the Arnold "spherical_camera" node. There are no parameters to set.
plugin Camera arnold_spherical_camera
extends:FreeCamera
name:"Spherical Cam"
classID:#(135251,542126)
category:"Arnold"
replaceUI:true
(
parameters main rollout:params
(
arnold_node type:#string default:"spherical_camera"
)
rollout params "Nothing to see here"
(
)
)
Slightly Deeper Example: A Sphere Plugin
The following scripted plugin will create a new geometry type in the "Arnold" category called "Sphere". It wraps (extends) the built-in 3ds Max sphere so that it shows up in the viewport as such. But Arnold will be given the mathematically exact "sphere" shape node when rendering.
Note that the bulk of the complexity of this code is because we wrapped the actual sphere object, which we want to be able to create with the mouse and to keep that in sync with the parameter defining the Arnold object. A much simpler piece of code could have been written had we completely ignored the wrapped object; we could have set the UI in the rollout to apply directly to the Arnold style parameters, which means no updateArnold() function would have been needed, and it could have been only a few lines:
Here is the full code for the Sphere plugin:
plugin Geometry Arnold_Sphere
name:"Sphere"
classID:#(0xf00df103, 0xc55744a1)
category:"Arnold"
extends:Sphere
replaceUI:true
(
local lastSize, meshObj
parameters pblock rollout:params
(
arnold_node type:#string default:"sphere"
arnold_node_radius type:#float default:1.0
arnold_node_center type:#point3 default:[0.0,0.0,0.0]
radius type:#float animatable:true ui:radius default:1.0
)
fn fmax val1 val2 = if val1 > val2 then val1 else val2
fn updateArnold =
(
-- Modify the Arnold Sphere
arnold_node_radius = delegate.radius
)
tool create
(
on mousePoint click do
case click of
(
1: nodeTM.translation = gridPoint
2: #stop
)
on mouseMove click do
(
if click == 2 then (
radius = delegate.radius = fmax (abs gridDist.x) (abs gridDist.y)
updateArnold()
)
)
)
rollout params "Sphere Parameters"
(
Spinner radius "Radius:" range:[0, 1e9, 1]
on radius changed val do (
delegate.radius = val
updateArnold()
)
)
)
The Important Bit
The real key here is the parameter declaration, i.e., these few lines
arnold_node type:#string default:"sphere"
arnold_node_radius type:#float default:1.0
arnold_node_center type:#point3 default:[0.0,0.0,0.0]
These lines say "when you see me, build a node named 'sphere' and assign its 'radius' parameter of type float and the 'center' parameter of type point with the value from these variables.especially"
Declaration (rarely needed in Arnold 5.0)
Had we required custom declared user variables (which was e.g. needed in the Arnold 4.2 volume node) it could have looked like this:
arnold_node type:#string default:"volume"
arnold_node_min type:#point3 default:[-0.5,-0.5,0]
arnold_node_max type:#point3 default:[0.5,0.5,1]
arnold_node_dso type:#string default:"volume_openvdb"
arnold_decl_filename type:#string default:"constant STRING"
arnold_node_filename type:#filename default:"smoke.vdb"
arnold_decl_bounds_slack type:#string default:"constant FLOAT"
arnold_node_bounds_slack type:#float default:0.0
This would instantiate a node "volume" as before, and set its min, max, and dso parameters. But the rest of the parameters (due to the plugin nature of Arnold 4.2 volumes) were user-defined, so we needed to define a string parameter called "filename", a float parameter called "bounds_slack" and so on and so forth...
The arnold_decl_ ... functionality is retained in MAXtoA but is rarely needed with Arnold 5.0.
Link Example: An Arnold Gobo Filter Plugin
The next script is a stripped-down version of the Arnold Gobo filter modifier plugin ( arnoldlight_gobofilter.ms ). It showcases how one would add UI controls to support both a color picker and a Texmap lookup. (The Arnold target parameter here is gobo.slidemap )
plugin modifier Arnold_Light_Gobo
name:~ARNOLD_LIGHT_FILTER_GOBO_NAME~
category:"Arnold"
classID:#(0xf00df10d,0x4001a10d)
extends:ArnoldLightFilterModifier replaceUI:true version:1
(
parameters main rollout:params
(
arnold_node type:#string default:"gobo"
arnold_node_slidemap type:#color ui:slidemapcolor default:[255,255,255]
arnold_link_slidemap type:#texturemap ui:slidemap
)
rollout params ~ARNOLD_LIGHT_FILTER_GOBO_ROLLOUT_NAME~
(
colorpicker slidemapcolor "Color" color:[255,255,255] modal:false
mapButton slidemap "Map"
)
)
Additional Notes
A Special Note on ENUM Parameters
The type of the parameter in the parameter block of the scripted plugin must match the type on the Arnold node. One type is special to simplify MaxScript UI work: If the Arnold type is "enum", this would normally be represented by an integer value starting from zero. However, since 3ds Max "dropdown" controls are indexed one-based, and it is most natural to connect the UI of an "enum" type parameter directly to a control of type "dropdown", ENUM parameters will actually be returned as an integer, but in a one-based format. So the number 1 (not 0) represents the first enum value, 2 the second, etc.
Example snippet:
parameters main rollout:params
(
. . .
arnold_node_mode type:#integer default:2 ui:mode
. . .
)
rollout params " Parameters"
(
. . .
dropdownlist mode "Mode" items:#("Blend", "Mix", "Replace", "Add", "Sub")
. . .
)
This means the full parameter on the scripted node (the parameter named arnold_node_mode ) will have values 1, 2, 3, 4 or 5 based on what is selected by the dropdown, however, the value actually sent to Arnold will be 0, 1, 2, 3 or 4.
Technical Note (not actually restricted to scripting)
Technically, the behavior described in this document isn't actually restricted to scripted plugins. Rather, one can say this: Any 3ds Max plugin, whose first parameter block contains parameters following this naming convention, will trigger this type of translation. So even a C++ plugin could, hypothetically, use this mechanism to send values to an Arnold node.
But since the plugin in 3ds Max is now only used for...
- Showing up in the viewport.
- Showing UI.
- Exists for other renderers.
...it tends to make the most sense to just make a simple "empty" scripted plugin as indicated here. But there is actually no test because if the plugin is scripted or not, only the naming convention of the parameters is tested. The feature is simply triggered by the presence of a string-type parameter named "arnold_node".