In this tutorial we will discuss how to create a Python "SOP" - a Surface OPerator used to create and manipulate geometries in Houdini. We will also link more complex Python SOPs at the end of this tutorial and on the scripts page once they are created. In addition, if you want to learn more about Houdini SOPS in general, a good place to start is the Houdini Docs page on SOPS.
Before You Begin
- Set up Houdini and yt using the instructions on Getting Started page.
- Download the snapshot from the Isolated Galaxy simulation by clicking on the yt-sample dataset download page RIGHT HERE (292 Mb), or use your own local AMR simulation outputs.
Please note that if you have a larger dataset, you may want to preprocess it into a VDB file format which is a more efficient means of importing data into Houdini. More information about this can be found on the Use Python VDB Converter Tutorial.
Just want the HIP file?
If you just want to skip this tutorial and download a HIP file that includes this simple Python SOP you can find it under the "withPythonSOP.hipnc" heading of the List of Houdini Files page.
The various steps of this tutorial will take you through the process of adding a Python SOP to Houdini that can load grid data.
NOTE: a step by step walk through of the Python SOP code is available in a drop-down section in "Add Python Code".
Start Adding New Operator
- To start the creation of your new Digital Asset, within Houdini access the menus:
File → New Operator Typefrom the Menu section bar (see the Getting Started section of this website for further description of available toolbars and menus).
- Select a name and label for your new Python SOP (default is “newop" and “New Operator", respectively).
- Set the Operator Style to “Python Type".
- Set the Network Type to “Geometry Operator".
- Hitting "Accept" will open up a pop up window to format your new Digital Asset.
Modify New Operator Main Panel
- Change the minimum number of inputs to zero so our SOP doesn't look for any external inputs (purple circle).
- Select code (red circle) to move onto the next step.
Add Python Code
- Grab the Python SOP code from the Bitbucket repo and paste into the "Code" subpanel.
# This code is called when instances of this SOP cook. node = hou.pwd() geo = node.geometry()
# must add all these to the "Parameter" section of the SOP dfile = node.parm("datafile").eval() # data file to read in - Parameter type: File rlevel = node.parm("refinement_level").eval() # refinement level - Parameter type: int logVal = node.parm("LogValue").eval() # take log of field or not - Parameter type: Toggle fieldName = node.parm("field_name").eval() # name of field - Parameter type: string fieldType = node.parm("field_type").eval() # type of field - Parameter type: string
These are the names of all the parameters that will show up in the Parameters panel once you add this Python SOP.
See the Add Parameters section next to see how to apply them.
from yt import load import numpy as np
Loading up some libraries we need to use.
Don't forget that you need to have yt installed and your PYTHONPATH aware of where it is located. See the Getting Started page for more info.
# load your selected data file and grab the data ds = load(dfile) dd = ds.all_data()
Load the data in the data file "dfile" (again, see Add Parameters section next for more details).
# create covering grid all_data = ds.covering_grid(level=rlevel, left_edge=[0,0.0,0.0], dims=ds.domain_dimensions*ds.refine_by**rlevel)
Use yt to generate a covering grid from your data file at the specified refinement level, from the parameter "rlevel".
The "ds.refine_by" call uses yt to figure out how many divisions occur for each AMR level (usually 2).
# selects fields like "density" or ("Gas", "Density") if len(fieldType) == 0: field = fieldName else: field = (fieldType, fieldName)
Tells you the "type" of the variable you are selecting - things like "stars", "gas", "dark matter" are usual.
If you aren't sure, use yt to load your data externally to Houdini (or internally in a Python Panel) and after you load the data type "ds.field_list" and "ds.derived_field_list" to see what your options are.
If "fieldType" is empty this will try to guess what type your variable is.
# to take the log or to not take the log, that is the question if logVal == 1: pointdata = np.log10(all_data[field].v).flatten() else: pointdata = (all_data[field].v).flatten()
Do you want to take the log of your selected variable? Again, no pants that are fancy here.
# rescale from 0->1 for plotting minp = pointdata.min() maxp = pointdata.max() pointdata = (pointdata - minp)/(maxp-minp)
Typically, shaders expect values that go from 0 to 1 in your volume values, so we shift things over for them.
This step is especially important if you have taken the log of the data since Houdini won't know what to do with negative volume values.
# make a new volume of correct size volume_geo = geo.createVolume(ds.domain_dimensions*ds.refine_by**rlevel, ds.domain_dimensions*ds.refine_by**rlevel, ds.domain_dimensions*ds.refine_by**rlevel)
Now Houdini will generate a new (empty) volume with the dimensions of your yt-generated covering grid.
# set the values of the volume back in Houdini volume_geo.setAllVoxels( pointdata.flatten() )
Finally, we set all the voxels in our volume to the values from our volumetric data set.
- Drag and drop parameter types from the "Create Parameters" sub-panel to the "Existing Parameters" sub-panel. Make sure you are using the right type! When in doubt check out the comments in the Python SOP code in the previous figure.
- Name these parameters appropriately in the "Parameter Desription" sub-panel. Make sure you are using the right names! Again, if in doubt, check out calls in "node.param" in Python SOP code in above figure.
Use your Python SOP!
Now that you've got a working SOP, you can load variables from data files at different resolutions interactively in the GUI as shown by the modifiable parameters in the upper right hand panel of Houdini.