General / 13 March 2020

Houdini Blog #39: Viewer states 2

This post will be more about Viewer States with Houdini. Here I will show another example on how to use viewer states to control your HDA parameters.

In my spare time I have been working on a boolean tool or boxcutter tool. Where you can draw shapes to boolean with an input. This is very useful to make hardsurface objects like for my sci-fi building tool.

Here is a demo of the WIP

Since a few weeks the Labs tools has also a box cutter tool. Which is can do also cool things, so check that out as well.

You can also get the file used in the blog here:  . Note: This is a basic setup and you could further add better control and add features.

Simple setup

There are multiple ways you can build this tool, so here is just one simple way you could achieve something similar. You could also look at the Labs tool for another example.

To start make a HDA with a boolean node and an add node.

In the HDA expose the parameters from the add node, like in the image below here. (with the viewer state it will control the multi parameter of the add node)

The Viewer State

Last post I talk a bit on how to make a viewer state (check out the previous post here). So for this example let's start from a template. Here the Add point template will be used.

Using this template or sample will place down Python code for you and will work by default with this add node and parameters from it.

Add the top of the code are some instructions on how to use the templates/samples. Which is basically saying to make an add node and expose the values (like I mentioned above here).

# Usage: This sample adds points to the construction plane.
# If you embedded the state in a SOP HDA:
# 1) Dive in the HDA and add a SOP Add node
# 2) Open the Add node property page and promote the Number of Points parm (Alt+MMB)
# 3) LMB in the viewer to add points.
# If you created a file python state:
# 1) Create an empty geometry and dive in.
# 2) Create an Embedded HDA: Subnetwork, RMB, Create Digital Asset..., Operator Name: test, Save To Library: Embedded, Accept.
# 3) Dive in the Embedded HDA and add a SOP Add node
# 4) Open the Add node property page and promote the Number of Points parm (Alt+MMB)
# 5) Set Node Default State: test in Type Operator Properties, Accept.
# 6) LMB in the viewer to add points.

So following these instructions correctly will create new points for every mouse click (you need to go outside of the HDA).

Some important parts of the code is keeping track of the total amount point number. This value is then used to control a multiparm.

Every time using now a 'self.index' it will return the current number of points/clicks.

Setting a value in the multiparm will look like this then:

 self.node.parm("usept%d" % self.index).set(1) #set multiparm value

Next up is looking closely now at the way points are placed, they are all placed on the grid (or construction plane in this case) and not on the geometry.

To place points on the input geometry you will need to know 2 things. What is the input geometry and how to get the intersection of the mouse with the geometry.

Getting the input is first.

At the beginning in the init create this lines. 

self._geometry = None

Further go to 'def onEnter' in here place down the following.

#Find what the input geometry is
inputs = self.node.inputs()
if inputs and inputs[0]:
    self._geometry = inputs[0].geometry()

Now the information is stored under self._geometry and can use it to get the intersection with the mouse.

For intersection there are a couple of options you can do , check them here : 

Here I will be using the sopGeometryIntersection function for it. Under 'def onMouseEvent' this function will be used.

Use the following to make it work. (The other line, cplaneIntersection, is not used anymore)

self.node = kwargs["node"]

if self._geometry:
     MY_position = su.sopGeometryIntersection( self._geometry, origin, direction )

In MY_position is now a couple of values stored. SopGeometryIntersection will return intersected, position, normal and uvw. (this is important to know)

So when setting the point position, you have to keeping in mind that multiple values are now stored in the position variable.

Change the code from this

self.node.parmTuple("pt%d" % self.index).set(MY_position )

to this

#this will sure to get the position info
self.node.parmTuple("pt%d" % self.index).set(MY_position[1]) 

Finishing with SOPs

To make it complete you need to create a shape from the points create with the viewer state.

You can group the points by 3, each point can then control how the shape will look (sort of a point for each axis). In the add node you can do this by going in the polygon menu and use the By group options.

Like in the image here going from the points to lines by grouping them by 3. 

Last step will be looping over each line and use a bounds node to create the shape to subtract.

Now you have a base for a box cutter tool. This is not perfect and could still use some tweaks but the post is already pretty long so I will end here. The main thing that needs to be tweaked is that when placing a point it is limited to the input geometry.

Here is what the result looks like.

Hope you enjoyed this post and see you on the next one.

Feel free to share any feedback or thoughts.

Special thanks to my Patreons.