Attachments

Hipfile

jamesr_attributebindings.hiplc

Basics

Let’s build a super simple setup that applies randomness to an attribute on some points. The user will specify which attribute they want to write to. We also want the user to be able to scale the points by any attribute they specify without changing any of the code.

So the first thing we will need is some logic to randomize a value, and write it out. In this case, let’s start with pscale so we can see what’s happening.

Scale some points

Scale some points

1
2
3
float r = rand(i@ptnum);

f@pscale *= r * chf("global_scale");

Easy enough!

point(), setpointattrib() and Friends

Reading

Next, we’ll need to fetch the user-specified attribute that we want to scale the randomness by. The most familiar way is by using the point() function.

User scale attribute

User scale attribute

1
f@coolscale = pow(relbbox(0, v@P).z, 4.0);

In this case we have a user-specified attribute called coolscale. We’ll use the point() function and a string parameter chs() to get that value.

Using the custom attribute

Using the custom attribute

So far so good. We’ve queried the custom user attribute and scaled our randomness by it.

Writing

We’ve successfully written to pscale so far. But remember that our setup’s requirements call for the user to be able to specify the output attribute. Currently, we have it hardcoded to f@pscale.

In order to write to a custom attribute, let’s add another parameter to specify what the attribute should be called and use the setpointattrib() function to write the value to the points.

Output Attrib parameter

Output Attrib parameter

1
2
3
4
5
6
7
float r = rand(i@ptnum);

float user_scale = point(0, chs("user_scale_attrib"), i@ptnum);

float scale =  r * user_scale * chf("global_scale");

setpointattrib(0, chs("output_attrib"), i@ptnum, scale);

While this function certainly does what we are asking, it is painfully slow when iterating over many many points, which isn’t an uncommon task! So how can we do it all a bit better? Let’s take a look at the Bindings tab.

Attribute Bindings Tab

Attribute Bindings tab

Attribute Bindings tab

The idea is pretty straightforward. The Attribute Name is the name of the attribute you really want to write to. Vex Parameter is simply what you’ll call that attribute inside your code. Think of this as an alias for the attribute name that you actually care about.

We can modify our setup to use this method instead:

Rewrite code

Rewrite code

1
2
3
float r = rand(i@ptnum);

f@scaled =  r * f@user_scale * chf("global_scale");

Our code has just gotten much simpler. We only need to refer the attributes that we put in the Vex Parameter parameters using the familiar @ syntax.

New bindings

New bindings

We can take advantage of the chs() channels we already made, and just channel reference them in the bindings section. That way the interface can stay user-friendly (especially for whenever you want to promote these up to the interface of a digital asset or something).

Speed Comparison

Let’s do a comparison with the Performance Monitor.

Performance Test

Performance Test

With ~112k points we can see that the setpointattrib() method takes about 0.081 seconds to cook, whereas the Attribute Bindings method takes 0.002 seconds! That’s a pretty big difference, though 0.08 seconds is pretty negligible too.

What happens if we try with a a point cloud consisting of 30,000,000 points?

Performance Test - 30mio points

Performance Test - 30mio points

Attribute Bindings wins by a factor of ~30x on my machine. Now the difference between 0.13s and 3.9s per cook might not seem like a huge amount if you’re already waiting a minute or so per-frame to process a heavy point cloud (like a big FLIP sim). But consider that in this example we are writing just a single attribute, in one wrangle. In a real-world setup, you might have several attributes and be doing a few different things in different wrangles and steps which can really add up!

Volumes

This technique is especially useful when dealing with fields in Volume VOPs. Have you ever dived inside a Gas Turbulence DOP or any similar nodes? If you look in the Attribute Bindings section, you’ll see that SideFX uses these all the time! It’s how you’re able to specify the name of any Control Field, but internally they only need to use one name!

Let’s try it out on our own. We’ll create a Volume VOP that adds noise to both density and temperature. There’s actually a shortcut toggle we can use without needing to set all the names ourselves.

Bind Each to Density

Bind Each to Density

Volume Bindings

Volume Bindings

  1. Create a Volume VOP.
  2. Add some nodes inside. Don’t add any extra Bind Export nodes, just pipe them out to density.
  3. On the Volume Bindings tab uncheck Autobind by Name.
  4. Enable Bind Each to Density.

Now we’ve applied the same operation to all of the fields! This is really useful if you’re creating any tools that modify volumes, and you want the user to be able to easily run over fields called anything.

Attributes to Create

Attributes to Create - Default

Attributes to Create - Default

This parameter is often overlooked, and its default is just * - which means any attributes referred to in the wrangle using the @ syntax will be created if it they don’t exist already.

The default is usually fine. But sometimes, you might be using an attribute temporarily just to do some sort of calculation, and you don’t actually want it to be created and passed along through the output. In that case, you can just use the ^ character plus the attribute name to skip it! This is also useful if you have a tool that allows a user attribute to passed in but does not require it.

Take this for example:

We have a setup that modifies the thickness of some curves. By default, the code will apply some randomness. The user is also given the option to provide an attribute by which to multiply the randomized thickness. For clarity, let’s provide them a sensible default like thicknessscale (sort of how vellum and other tools across houdini fill it in too).

Parameter defaults and code

Parameter defaults and code

If we structure our code like so:

1
2
3
float r = rand(@seed + 65536);

f@pscale = r * f@scale * chf("global_scale");

with the following attribute bindings:

Attribute Bindings

Attribute Bindings

we would expect that the f@pscale attribute is scaled by some random number, and the curves will change shape.

But what happens if the user doesn’t want to do any extra scaling, and they didn’t specify any attribute? If no attribute is provided, and the binding is left blank or the attribute doesn’t exist we wind up with a bit of an issue…

Scales are zero

Scales are zero

All the scales are now zero! Well that’s not really what we want… if the user doesn’t specify an attribute (or if it doesn’t exist), we should carry on and happily apply just the randomized value to the thickness. Let’s modify the code a bit:

1
2
3
4
5
float @scale = 1.0; // Initialize it in case the user doesn't

float r = rand(@seed + 65536);

f@pscale = r * @scale * chf("global_scale");
Scales are working now

Scales are working now

This works excellently! Now, even though the attribute is missing, everything is just multiplied by 1.0, so we’re in the clear. But let’s look at the attributes now…

Extra Attribute

Extra Attribute

Oh no! Since that default value we have sitting in there wasn’t cleared out, and since it doesn’t already exist on the points, we wound up creating some attribute called thicknessscale with a value of 1.0! That’s sort of annoying. If the user didn’t ask for an attribute to be created, we should really just leave it alone.

Leaving it alone is simple. Just exclude it from that Attributes to Create parameter.

Exclude `f@scale` attribute

Exclude f@scale attribute

1
* ^scale ^seed

The attributes specified in this list are the same as the Vex Parameters you’re using in your code, even if they are bound to something different.

No extra attributes left

No extra attributes left

If the attribute does exist on the points beforehand, don’t worry - this option won’t cause it to be deleted. It will still pass through just as expected, with the added bonus that since it’s being ignored in the Attributes to Create parameter, we aren’t able to actually write to it, which means we can’t muck it up with our code!

Result

Let’s see it in action with the user specifying their own scaling attribute on top of our randomization:

Final Result

Final Result

Final Notes

Groups

We can do most of the same stuff with groups. Just remember than Vex expects the prefix i@group_ before group names, which also applies to the Vex Parameter parameter in the bindings section.

Group Bindings

Group Bindings

Group Bindings Example

Group Bindings Example

An important note - if you’re using the Output Selection Group parameter to visualize the group in the viewport (and pass the selection to downstream nodes), note that this parameter is expecting Group Name not the Vex Parameter!

Output Selection Group

Output Selection Group