Creating a polymesh with the Arnold Python api


polymesh
Here’s a snippet that shows how to create a simple four-polygon polymesh node with the Arnold Python API.

n = AiNode( "polymesh" )
AiNodeSetStr( n, "name", "grid" )

nsides = [4, 4, 5, 6]
AiNodeSetArray( n, "nsides", AiArrayConvert(len(nsides), 1, AI_TYPE_UINT, (c_uint*len(nsides))(*nsides) ) )

vidxs = [0, 1, 4, 3, 1, 2, 5, 4, 3, 4, 7, 6, 9, 4, 5, 11, 8, 10, 7]
AiNodeSetArray( n, "vidxs", AiArrayConvert(len(vidxs), 1, AI_TYPE_UINT, (c_uint*len(vidxs))(*vidxs) ) )

nidxs = [0, 1, 2, 3, 1, 4, 5, 2, 3, 2, 6, 7, 8, 2, 5, 9, 10, 11, 6]
AiNodeSetArray( n, "nidxs", AiArrayConvert(len(nidxs), 1, AI_TYPE_UINT, (c_uint*len(nidxs))(*nidxs) ) )

vlist = [-1, 0, -1, -1, 0, 0, -1, 0, 1, -0.197835326, 0, -0.742445886, 0, 0, 0, 0, 0, 1, 0.802164674, 0, -0.742445886, 1, 0, 0, 1, 0, 1, 0.270379633, 0, -1.21302056, 1, 0, 0.508926511, 0.496316135, 0, 1]
AiNodeSetArray( n, "vlist", AiArrayConvert(len(vlist), 1, AI_TYPE_FLOAT, (c_float*len(vlist))(*vlist) ) )

nlist = [0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]
AiNodeSetArray( n, "nlist", AiArrayConvert(len(nlist), 1, AI_TYPE_FLOAT, (c_float*len(nlist))(*nlist) ) )

m = AtMatrix( 	1, 0, 0, 0,
		0, 1, 0, 0,
		0, 0, 1, 0,
		0, 0, 0, 1 )

am = AiArrayAllocate(1, 1, AI_TYPE_MATRIX)
AiArraySetMtx( am, 0, m )

AiNodeSetArray( n, "matrix", am  )

AiNodeSetBool( n, "smoothing", True )

AiNodeSetByte(n, "visibility", 255 )

# Assign a shader to the polymesh node
u = AiNode( "utility" )
AiNodeSetStr( u, "name", "aiUtility1" )

AiNodeSetPtr( n, "shader", u )

And here’s the resulting node in the ASS file:

polymesh
{
 name grid
 nsides 4 1 UINT
4 4 5 6
 vidxs 19 1 UINT
  0 1 4 3 1 2 5 4 3 4 7 6 9 4 5 11 8 10 7
 nidxs 19 1 UINT
  0 1 2 3 1 4 5 2 3 2 6 7 8 2 5 9 10 11 6
 vlist 12 1 POINT
  -1 0 -1 -1 0 0 -1 0 1 -0.197835326 0 -0.742445886 0 0 0 0 0 1 0.802164674 0 -0.742445886 1 0 0
  1 0 1 0.270379633 0 -1.21302056 1 0 0.508926511 0.496316135 0 1
 nlist 12 1 VECTOR
  0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0
 smoothing on
 visibility 255
 matrix
 1 0 0 0
 0 1 0 0
 0 0 1 0
 0 0 0 1
 shader "aiUtility1"
}

[Arnold] Standard shader AOVs and shading trees


Here’s a question asked recently. Given a shading tree like the one below, why don’t AOVs like diffuse_direct include the blended color from the Blend Color node?

aovs_blend_color

The answer: because it is the Standard shader that writes the AOV, not the Blend Color shader. The diffuse_direct AOV gets the diffuse layer calculated by the Standard shader, and that’s it.

The MtoA AOV browser shows you what AOVs are implemented by which shaders:
aovs_mtoa_browser

[Arnold] System requirements: minimum Windows version


As of Arnold 4.2.3.1, the minimum Windows version is Windows 7. We no longer support Windows versions before Windows 7 and Windows Server 2008 R2.

This applies to all plug-ins (such a MtoA 1.2.02 and later, or SItoA 3.4 and later) that use Arnold 4.2.3.1 or later.

If you try to load an Arnold plugin on an unsupported Windows, you’ll get an error (something like “The specified procedure could not be found”).

On Vista with Arnold 4.2.3.1 or later, kick -nodes gives you a more specific error:

"kick.exe - Entry Point Not Found" 
"The procedure entry point SetThreadGroupAffinity could not be located in the dynamic link library KERNEL32.dll."

[Python] Linking nodes


There’s been a few questions recently about nodes that look like this in an ASS file:

MayaPlusMinusAverage3D
{
 name plusMinusAverage2
 operation "sum"
 input3D 2 1 POINT
0 0 0 0 0 0
 input3D[0] file1
 input3D[1] bulge1
}

That’s the ASS representation of this (in Maya):
mayaplusminusaverage3D
On line 6, you have the default values (two POINTs) for input3D. If there was nothing plugged into the input3D, you’d get the points (0,0,0) and (0,0,0).

Here’s an example that shows how to set up the node links for a MayaPlusMinusAverage3D node.

from arnold import *

ass_file = "mayaplusminusaverage.ass"

AiBegin()
AiMsgSetConsoleFlags(AI_LOG_ALL)

# Load the mtoa shaders, which include MayaPlusMinusAverage3D
AiLoadPlugins('C:/solidangle/mtoadeploy/2015/shaders')

AiASSLoad(ass_file, AI_NODE_ALL)

# Create a MayaPlusMinusAverage3D node
n = AiNode( "MayaPlusMinusAverage3D" )

# Set the name parameter
AiNodeSetStr( n, "name", "plusMinusAverage2" )

# Set the operation parameter
AiNodeSetStr( n, "operation", "sum" )

# input3D is of type POINT[] (an array of POINTs)

# Allocate an array; by default the array is initialized to 0s
a = AiArrayAllocate( 2, 1, AI_TYPE_POINT )
AiNodeSetArray(n, "input3D", a )

# Link input3D to the file and bulge nodes, which are assumed to already exist
f = AiNodeLookUpByName( "file1" )
b = AiNodeLookUpByName( "bulge1" )
AiNodeLink(f, "input3D[0]", n);
AiNodeLink(b, "input3D[1]", n);

AiASSWrite(ass_file, AI_NODE_ALL, False)
AiEnd()

Here’s another example. This time, one input3D is set to (0.3, 0.2, 0.4), and the other input3D is linked to a Bulge node.

MayaPlusMinusAverage3D
{
 name plusMinusAverage3
 operation "sum"
 input3D 2 1 POINT
0.3 0.2 0.4 0 0 0
 input3D[0] file1
}

mayaplusminusaverage3D_1
And here’s the Python snippet that creates the node and sets up the link:

n = AiNode( "MayaPlusMinusAverage3D" )

b = AiNodeLookUpByName( "bulge1" )

AiNodeSetStr( n, "name", "plusMinusAverage3" )
AiNodeSetStr( n, "operation", "sum" )

a = AiArrayAllocate( 2, 1, AI_TYPE_POINT )
AiArraySetPnt(a, 0, AtPoint( 0.3, 0.2, 0.4 ) )

AiNodeSetArray(n, "input3D", a )

AiNodeLink(b, "input3D[1]", n);

A couple of notes:

  • You can get the type of parameters like input3D with kick:
    kick -l ..\shaders -info MayaPlusMinusAverage3
    D
    node:         MayaPlusMinusAverage3D
    type:         shader
    output:       RGB
    parameters:   3
    filename:     ..\shaders/mtoa_shaders.dll
    version:      4.2.3.1
    
    Type          Name                              Default
    ------------  --------------------------------  --------------------------------
    
    ENUM          operation                         sum
    POINT[]       input3D                           (empty)
    STRING        name
    
  • For the full list of parameter types such as AI_TYPE_POINT, see the “AtParamEntry API” in the Arnold API Reference (or python/arnold/ai_params.py in the Arnold install).

[Arnold] Getting started with the Arnold Python API


You can download Arnold here. The Arnold download (aka the Arnold SDK) includes the Arnold library, the Arnold C++ API, the API docs, and the Python bindings for the Arnold API.

To use the Arnold Python API, you need to add the Arnold python folder to the PYTHONPATH:

set PYTHONPATH=C:\solidangle\arnold\Arnold-4.2.4.0-windows\python

Here’s a “hello world” written with the Arnold Python API:

#
# hello_world.py
#
from arnold import *

AiBegin()

AiMsgSetConsoleFlags( AI_LOG_INFO )

AiMsgInfo( 'Hello World' )

AiEnd()

Here’s a quick breakdown of the script.

  • You need to import the arnold module.
  • An Arnold session always starts with AiBegin() and ends with AiEnd(). You have to call AiBegin() to initialize Arnold and enable the Arnold API. Try commenting it out and see what happens…
  • We need to call AiMsgSetConsoleFlags() to set the log verbosity level (otherwise we won’t see our “Hello World”, because the default level is AI_LOG_NONE).
  • AiMsgInfo() sends our “Hello World” to the log.

Assuming that Python in is your PATH, you can run the hello world script like this:

python hello_world.py

and that will give you this output:


C:\solidangle\arnold\scripts>python helloworld.py
        | log started Wed Mar 04 14:31:40 2015
        | Arnold 4.2.4.0 windows icc-14.0.2 oiio-1.4.14 rlm-11.2.2 2015/02/26 15:08:42
        | running on StephenBlair-PC with pid 33812
        |  1 x Intel(R) Xeon(R) CPU E3-1240 V2 @ 3.40GHz (4 cores, 8 logical) with 16338MB
        |  Windows 7 Professional Service Pack 1 (version 6.1, build 7601)
        |
        | Hello World
        |
        | releasing resources
        | Arnold shutdown

For documentation, you use the Arnold SDK documentation. It’s for the C++ API, but the Python API is basically a one-to-one wrapper around the C++ API.

You can find the docs in the doc/api/index.html folder of the Arnold installation.

AiBegin() and AiEnd() are part of the Rendering API.
AiMsgSetConsoleFlags() and AiMsgInfo() are part of the Message Logging API, so go there to check out the possible flags, and what other logging functions are available.
arnold_api_reference

[Arnold] [RLM] ARNOLD_LICENSE_HOST and ARNOLD_LICENSE_PORT no longer supported


If you’re still using ARNOLD_LICENSE_HOST and ARNOLD_LICENSE_PORT, now’s the time to stop. As of Arnold 4.2.4.0, these deprecated environment variables are no longer supported.

Instead, use solidangle_LICENSE.

For example, if you currently have these environment variable settings:

ARNOLD_LICENSE_HOST=lic_server
ARNOLD_LICENSE_PORT=5055

you can replace them with this:

solidangle_LICENSE 5055@lic_server

Note: solidangle_LICENSE was added in Arnold 4.0.4 (released back in 23-May-2012).

Also new with Arnold 4.2.4.0 is the ability to use a .lic file instead of an environment variable. Just create a .lic file that looks like this:

HOST lic_server 5055

and put the .lic file in the same folder as Arnold (ai.dll, libai.so, or libai.dylib).

If you want to put the .lic file somewhere else, then you need to set solidangle_LICENSE to point to the lic file.

The case of the disappearing particles


In this case, nParticles (render type = point) weren’t visible behind a refractive plane:
particles_not_visible

For the points render type, you get an Arnold points shape (with mode “disk”), so I exported an ASS file to see the parameter settings on the points node. I noticed the visibility 243 right away (the default visibility is 255, which is visible to all rays). And sure enough, in the Render Stats for the particle shape, some rays were turned off (and the check boxes were disabled too).
particles_render_stats

I used the User Options to force the visibility to 255
particles_useroptions
and the particles behind the refractive surface appeared:
particles_visible

This is a Maya thing. For most particle render types, the particles are not visible in reflections or refractions. It says so in the docs:

You can turn on reflections, refractions, and shadows when you software render Clouds, Tubes, and Blobby Surfaces

I’m not sure where the default values are set, but I did find the AEtemplate code that disables the render stats for all particle render types except the “s/w” type:

// C:\Program Files\Autodesk\Maya2015\scripts\AETemplates\AEparticleLayout.mel
		if( $value == 7 || $value == 8 || $value == 9 ) {
			// software particle type
			editorTemplate -dimControl $nodeName "visibleInReflections" false;
			editorTemplate -dimControl $nodeName "visibleInRefractions" false;
			editorTemplate -dimControl $nodeName "castsShadows"    		false;
			editorTemplate -dimControl $nodeName "receiveShadows" 		false;
			editorTemplate -dimControl $nodeName "primaryVisibility" 	false;
		} else {
			// hardware particle type
			editorTemplate -dimControl $nodeName "visibleInReflections" true;
			editorTemplate -dimControl $nodeName "visibleInRefractions" true;
			editorTemplate -dimControl $nodeName "castsShadows"			true;
			editorTemplate -dimControl $nodeName "receiveShadows" 		true;
			editorTemplate -dimControl $nodeName "primaryVisibility" 	true;
		}

So, since that AE template code just enables and disables UI controls, but doesn’t change the actual values, another way to enable Visible in Refractions (or Visible in Reflections) is to do this:

  1. Change the Particle Render Type to one of the “s/w” types, like Blobby Surface.
  2. Now the Visible in Refractions check box is enabled.
  3. Select the check box.
  4. Go back and change the Particle Render Type to points.

Now when you render, the points are visible to refraction rays, so they show up behind the glass.

[Arnold] [Python] Iterating over the shape nodes in a scene


Here’s a snippet that loops over the shape nodes in an ASS file.

from arnold import *
AiBegin()

AiMsgSetConsoleFlags(AI_LOG_ALL)

# Required if the ASS file uses any SItoA shaders
AiLoadPlugins('C:/softimage/workgroups/sitoa-3.4.0-2015/Addons/SItoA/Application/Plugins/bin/nt-x86-64')

AiASSLoad('C:/softimage/projects/Support/Arnold_Scenes/Default_Pass_Main.1.ass')

# Iterate over all shape nodes
iter = AiUniverseGetNodeIterator(AI_NODE_SHAPE);
while not AiNodeIteratorFinished(iter):
	node = AiNodeIteratorGetNext(iter)
	print "[script] AiNodeGetName: {0}".format( AiNodeGetName( node ) )
	
	ne = AiNodeGetNodeEntry( node )
	print "[script]  AiNodeEntryGetName: {0}".format( AiNodeEntryGetName( ne ) )
	print "[script]  AiNodeEntryGetType: {0}".format( AiNodeEntryGetType( ne ) )
	print "[script]  AiNodeEntryGetTypeName: {0}".format( AiNodeEntryGetTypeName( ne ) )
	
AiNodeIteratorDestroy(iter)
AiEnd()

This would print something like this:

C:\solidangle\scripts>python shapes.py
[script] AiNodeGetName: root
[script]  AiNodeEntryGetName: list_aggregate
[script]  AiNodeEntryGetType: 8
[script]  AiNodeEntryGetTypeName: shape
[script] AiNodeGetName: cube.SItoA.1000
[script]  AiNodeEntryGetName: polymesh
[script]  AiNodeEntryGetType: 8
[script]  AiNodeEntryGetTypeName: shape

Note the root node of type list_aggregate. It’s shape used internally by Arnold. Just be aware that that root node is there in the universe, because even if you load an ASS file that contains just polymesh nodes, there will be a root node that you’ll have to skip over.

[Arnold] [kick] Changing output paths for AOVs


The -o flag changes the output path for the beauty AOV only. For other AOVs, kick still uses the output paths specified in the ASS file (the AOV output paths are specified by the filename parameter of the driver nodes in an ASS file).

driver_exr
{
 name defaultArnoldDriver@driver_exr.RGBA.direct_diffuse
 filename "C:/Users/StephenBlair/Documents/maya/projects/Support/images/direct_diffuse/Natural_Museum.exr"
 compression "zip"
 half_precision off
 tiled off
 preserve_layer_name off
 autocrop off
 append off
}

It is possible, however, to use kick -set to change the output path and file name of specific AOVs, but you’d have to know the AOV names in advance. For example:

kick -set defaultArnoldDriver@driver_exr.RGBA.direct_diffuse.filename "C:/temp/direct_diffuse.exr" -i example.ass

[MtoA] Mapping textures to Yeti hair


Like MtoA, Yeti exports UV coordinates in the uparamcoord and vparamcoord parameters, and you use these to map textures in the aiHair shader with the Uparam and Vparam extra attributes.
yet_uv_3

For example, if you plug a texture into the Rootcolor and tipcolor
yet_uv_2
then you can use uparamcoord and vparamcoord to map that texture onto the hair:
yet_uv_1

Tip If you enable Expand Procedurals and export an ASS file, you can see what parametes Yeti exports:

### created by pgYetiMayaShape
curves
{
 name pgYetiMayaShape_|pgYetiMaya|pgYetiMayaShape|scatter01_pSphereShape1_grow01
 num_points 621 1 b85UINT
# ...
 points 4347 1 b85POINT
# ...
 basis "catmull-rom"
 mode "ribbon"
 min_pixel_width 0
 visibility 255
 self_shadows on
 matrix
 1 0 0 0
 0 1 0 0
 0 0 1 0
 0 0 0 1
 shader "initialShadingGroup"
 opaque on
 id -1230660816
 declare curve_id uniform UINT
 curve_id 621 1 b85UINT
# ...
 declare uparamcoord uniform FLOAT
 uparamcoord 621 1 b85FLOAT
# ...
 declare vparamcoord uniform FLOAT
 vparamcoord 621 1 b85FLOAT
# ...
 declare surf_s uniform FLOAT
 surf_s 621 1 b85FLOAT
# ...
 declare surf_t uniform FLOAT
 surf_t 621 1 b85FLOAT
# ...
 declare length uniform FLOAT
 length 621 1 b85FLOAT
# ...
 declare fur_id uniform FLOAT
 fur_id 621 1 b85FLOAT
# ...
 declare surf_n uniform VECTOR
 surf_n 621 1 b85VECTOR
# ...
 declare filename constant STRING
 filename "C:/Users/StephenBlair/Documents/maya/projects/Support//yeti/tmp/yeti_combExample_pgYetiMayaShape_1886244453_1.fur"
 declare mtoa_shading_groups constant ARRAY NODE
 mtoa_shading_groups "initialShadingGroup"
 declare frame constant INT
 frame 1
 declare verbosity constant INT
 verbosity 1
 declare density constant FLOAT
 density 10
 declare width constant FLOAT
 width 1
 declare threads constant INT
 threads 0
 declare min_pixel_width constant FLOAT
 min_pixel_width 0
 declare mode constant INT
 mode 0
 declare imageSearchPath constant STRING
 imageSearchPath ""
 declare samples constant ARRAY FLOAT
 samples 1
}