Using an operator to override parameters in an ass file

Arnold 5.1 adds operators, which among other things, allow you to override parameters in ass files loaded by procedural nodes.

Here’s a quick example using the brand new MtoA 3.0

I exported some particles from Softimage, loaded them into Maya with an aiStandin (aka an Arnold procedural), and then used a set_parameter operator to scale the radius by 0.5

Note that I connect my operator by setting the Target Operator in the Render Settings.
I can chain two set_parameter operators together, to set the mode and then scale the radius:

[FAQ] Can I transfer Arnold shaders to another app?

For example, can I transfer Arnold shaders from 3ds Max to Maya? Or from Houdini to Maya?

The answer is no. The Arnold plugins don’t include code for reading shader nodes from an ASS file and creating shader trees inside the host application (eg Maya, Houdini, or Cinema 4D).

You can use ASS files to take shapes and shaders from one app and render them in another app, but those shapes and shaders are never created inside the other app.

An MtoA Standin (aka an Arnold Procedural in HtoA and C4DtoA) references an ASS file, and passes that on to Arnold at render time.


The ASS file contains Arnold nodes (shapes and shaders). Those Arnold nodes are not created inside the 3D application.

Instead, the standin node is translated to an Arnold procedural node, and at render time, that procedural node is expanded, and all the Arnold shapes and shaders are created inside the Arnold scene.

Overriding shader parameters in procedurals

Procedurals (aka standins) don’t expose the parameters of objects and shaders inside the procedural.

For example, you cannot override shader parameters like standard.Kd (diffuse color) or standard.emission (emission scale) by setting those parameters on a procedural node.

But what you can do is use “user data parameters”. Inside the procedural, use user data shaders to set shader parameters, but don’t define the user data parameters on the shape.

For example, if I wanted to be able to override the emission scale, then I would set up a shader like this:


A userDataFloat shader sets the Emission. The important thing is that the custom attribute emission_scale is not defined on the shape, so the default value is used instead.

By doing that, I can put an emission_scale attribute on the procedural node, and the aiUserDataFloat shader picks up that value.


In the above example, I’ve added a mtoa_constant_emission_scale attribute to the standinShape, and that allows me to set the Emission for that specific standin.

In Arnold scene source (ASS) format, I have this in my main scene file:

 name ArnoldStandIn1Shape
 dso "standin_torus_w_emission.ass"
 declare emission_scale constant FLOAT
 emission_scale 0.649999976

And in the ASS file loaded by the procedural, I have this:

 name pTorusShape1
 shader "someshader"
 # NO user data declared here 

 name someshader
 emission aiUserDataFloat1

 name aiUserDataFloat1
 floatAttrName "emission_scale"
 defaultValue 0.100000001


[Arnold] Overriding parameters of nodes created by a standin

A common question is “how can I override the subdivision/displacement of the nodes in a standin?”

The standard answer is: “you can’t”. A standin is an Arnold procedural node, and you can override only the parameters supported by the procedural node, which do not include subdivision or displacement.

But…here’s how to do with the [deprecated] override back door. The override node allows you set a parameter value on a specific node. So if a standin loads an ASS file with a polymesh named “tRexShape”, then you can override tRexShape.subdiv_iterations like this:

 tRexShape subdiv_iterations 2

So, in Maya, do this:

  • Create a standin that loads an ASS file with the polymesh nodes (in this example, a tRexShape with subdiv_iterations=1).
  • Create a second standin that loads an ASS file with the override node. Clear the Defer Standin Load check box for this second standin, and your standin will render with the override value for subdiv_iterations.


nodes aren’t supported by the Arnold API, and there’s no guarantee that support for override nodes won’t be removed at some point in the future.

Setting the procedural’s load_at_init parameter to true would also fix this.

There’s an Arnold error that recommends setting the load_at_init parameter, but people don’t always know where to find that in their favorite Arnold plugin.

ERROR| [proc] c4d|Arnold_Procedural: bounds supplied to procedural node are not large enough to contain the actual underlying geometry.
Replace given bounds: (-1, -1, -1) X (1, 1, 1), with: (-9.4028616, -9.43616867, -9.21633244) X (9.3304739, 9.43616867, 9.21633244).
Setting the procedural's load_at_init parameter to true would also fix this.

The load_at_init parameter controls whether the procedural (standin) is loaded during scene intialization (before rendering starts), or during rendering (when a ray hits the procedural bounding box).

By default, load_at_init is false, which means that procedural loading is deferred until render time.

And so, in MtoA, C4DtoA, and SItoA, the load_at_init parameter is exposed as Defer Standin Load, which is enabled by default. In HtoA, you have a Load At Init parameter on for the Arnold Procedural node.

[MtoA] Is there an ARNOLD_PROCEDURAL_PATH environment variable?

No, there isn’t. HtoA recently added support for an ARNOLD_PROCEDURAL_PATH environment variable, but neither MtoA or Arnold itself support ARNOLD_PROCEDURAL_PATH.

Here’s a few things you can do:

  • If you’re kicking ASS files, you can set the procedural search path on the kick command line. For example, assuming ARNOLD_PROCEDURAL_PATH is set in your environment, you could do this on Windows:
       kick -set options.procedural_search_path %ARNOLD_PROCEDURAL_PATH%

    Or this on Linux or OSX:

       kick -set options.procedural_search_path $ARNOLD_PROCEDURAL_PATH
  • You could add the procedural search path to the system path (PATH on Windows, LD_LIBRARY_PATH on Linux, DYLD_LIBRARY_PATH on OSX). Arnold will then find the procedural by searching through the system path.
  • Maya, you can put an environment variable in the Procedural Search Path text box. Note that you have to use square brackets, like this: [ARNOLD_PROCEDURAL_PATH].

Soccer balls and Arnold procedurals

I’m partial, but I like the Softimage soccer ball better than the Maya soccer ball.

The Softimage soccer ball is the one on the left. I took the ASS file for the soccer ball, did a few search and replace ops, and compiled my first procedural node. It’s pretty trivial, but it illustrates the basic framework. numNodes() returns the number of nodes you’re going to create; Arnold calls getNode() once for each node you create. I’m just creating one node, so I do it in getNode(), but you could also do it in init().

If you’re interested in learning more about the Arnold SDK and procedurals, you can download the SDK (docs included) at

#include <ai.h>
#include <string.h>

static int init(AtNode *mynode, void **user_ptr )
  AiMsgInfo( "[soccer] -- greetings!" );
  return 1;

static int cleanup( void *d )
  return 1;

static int numNodes( void *d )
  return 1;

static AtNode *getNode( void *d, int index )

	AtByte nsides[540] = {
		4, 4, 4, 4, 4,
		// ...
		4, 4, 4, 4, 4};
	unsigned int vidxs[2160] = {
		292, 301, 12, 293, 302, 303, 304, 305, 302, 305, 306,
		169, 538, 537, 116, 168, 540, 539, 79, 151, 25, 541};

	unsigned int nidxs[2160] = {
		0, 1, 2, 3, 4, 5, 6, 7, 4, 7, 8, 9, 4, 9, 10, 11, 4, 
		// ...
		1211, 1216, 1215, 1230, 1229, 311, 322, 1231, 312};

	float vlist[1626] = {
		2.33102179f, 4.68985701f, -7.74118519f, -4.0108633f, -2.08138514f, 8.18039227f,
		1.70574367f, 2.03463793f, 9.00535202f, 2.94963598f, 0.0429565944f, 8.85224628f};

	float nlist[3696] = {
		-0.00219265698f, -0.443717808f, -0.896163881f, -0.112498537f, -0.405027777f, -0.907356858f, 
		-0.316353977f, -0.846433043f, 0.428335458f, 0.514008343f, -0.00765316607f, 0.857751012f};

	AtMatrix m;
	AiM4Identity( m );
	AtNode *n = AiNode( "polymesh" );
	AiNodeSetByte(n, "visibility", 255);
	AiNodeSetArray(n, "vlist",  AiArrayConvert(1626,  1, AI_TYPE_FLOAT, vlist));
	AiNodeSetArray(n, "nsides", AiArrayConvert(540,  1, AI_TYPE_BYTE,  nsides));
	AiNodeSetArray(n, "vidxs",  AiArrayConvert(2160,  1, AI_TYPE_UINT,  vidxs));
	AiNodeSetArray(n, "nlist",  AiArrayConvert(3696,  1, AI_TYPE_FLOAT, nlist));
	AiNodeSetArray(n, "nidxs",  AiArrayConvert(2160,  1, AI_TYPE_UINT,  vidxs));
//	AiNodeSetArray(n, "uvidxs", AiArrayConvert(384,  1, AI_TYPE_UINT,  uvidxs));
//	AiNodeSetArray(n, "uvlist", AiArrayConvert(270,  1, AI_TYPE_FLOAT, uvlist));

	AiNodeSetMatrix(n, "matrix", m );

	AiNodeSetStr(n, "name", "SISoccerBall");
	return n;

// --------------------------------------------------------------------------------
// dso hook.
// --------------------------------------------------------------------------------
  vtable->Init     = init;
  vtable->Cleanup  = cleanup;
  vtable->NumNodes = numNodes;
  vtable->GetNode  = getNode;
  strcpy_s(vtable->version, AI_VERSION);
  return 1;