K.GEOFFREYSOGAPhD
HOUDINI PYTHON

| Home | html / css | Houdini Python
Houdini Python
The following snippets presume the following ...
node = hou.pwd()
geo = node.geometry()
          
... which is automatically generated in the python SOP.
The function hou.pwd() returns a node object, in this case equal to the global current node. The node.geometry() function returns a geometry object, referencing the SOP's geometry.
Loop over points
for point in geo.points():
	r = point.position()
	...
          
Creating geometry
N = 100
for i in range(N):
    point = geo.createPoint()
    x = i * 0.1
    y = i * 0.2
    z = i * 0.3
    point.setPosition((x,y,z))
          
Example: create a helix
import math

N = 100
rad = 1.0
w = 1.0
dy = 0.1

for i in range(N):
    point = geo.createPoint()
    t = i * 0.1
    x = rad * math.cos(w*t)
    y = t * dy
    z = rad * math.sin(w*t)
    point.setPosition((x,y,z))
          
Example: Helix Fire
An example of a houdini pyro effect, using a helix source, can be found here.
Add a detail attribute ...
I like the python SOP for calculations that can't be multi-threaded; e.g., a calculation involving a loop over all points. If the calculation is done once per geometry, the result can be stored in a detail attribute.
# create a detail attribute
# the type is determined by the type of the default value used
geo.addAttrib( hou.attribType.Global, "test", 10)

# set the value of a detail attribute
geo.setGlobalAttribValue( "test", 17)

# read the value of a detail attribute
print geo.attribValue("test") 
          
... and read into VEX
f@t = detail( 0, "test", 0);
          
Example: principle axes of the moment of inertia tensor

The moment of inertia tensor is used to describe how mass is distributed in a rigid body. The principle axes are the eigenvectors of this matrix, and the associated eigenvalues are known as the principle moments.

These quantities are normally used to describe the rotational dynamics of a rigid body. However, the eigenvectors are very useful in themselves, since they form a basis (coordinate system) fixed to the rigid body. Thus, you can consistently identify a set of points in that body, regardless of how it translates or rotates. Also, the principle axes can help pick out symmetries in the rigid body.

import numpy as np

rav = hou.Vector3(0,0,0)
n = 0
for point in geo.points():
    r = point.position()
    rav += r
    n += 1
    
rav /= float(n)

Ixx = 0.0
Iyy = 0.0
Izz = 0.0
Ixy = 0.0
Iyz = 0.0
Ixz = 0.0

for point in geo.points():
    r = point.position() - rav
    Ixx += r[1]*r[1] + r[2]*r[2]
    Iyy += r[0]*r[0] + r[2]*r[2]
    Izz += r[0]*r[0] + r[1]*r[1]
    Ixy += - r[0]*r[1]
    Iyz += - r[1]*r[2]
    Ixz += - r[0]*r[2]

IT = np.array([ [Ixx,Ixy,Ixz], [Ixy,Iyy,Iyz], [Ixz,Iyz,Izz] ])
w, v = np.linalg.eig(IT)

geo.addAttrib( hou.attribType.Global, "cog", rav )
geo.addAttrib( hou.attribType.Global, "evalues", w ) 
geo.addAttrib( hou.attribType.Global, "evec0", v[:,0] )
geo.addAttrib( hou.attribType.Global, "evec1", v[:,1] )
geo.addAttrib( hou.attribType.Global, "evec2", v[:,2] )
          
Interesting to note, the python distribution that ships with Houdini already includes the numpy module. Numpy is a popular module used for performing numerical calculations in Python.
In the following image sequence, the principle axes are shown for a system of points scattered over a box.
The principle axes clearly pick out the symmetries of the underlying geometry. Note that numpy does not guarantee the order of the eigenvectors that it outputs. Also, an eigenvector is only defined up to a constant; that is, an eigenvector multiplied by a constant is still an eigenvector. So numpy can produce an eigenvector that is "flipped", or multiplied by -1. To produce the above image, I used the dot product of two sequential eigenvectors and multiplied by -1 as needed to maintain the continuity of the sequence.
Access parameters
Basic functions to read from and write to parameters.
# read parameter values
hou.evalParm()
hou.evalParmTuples()

# set parameter values
hou.parm().set()
hou.parmTuple().set()
          
Example: principle axes, revisited
This is the same example as above, except the values of the vectors are stored as parameters on a null called "eigen". As mentioned above, it is helpful to compare sequential eigenvectors. The detail attribute doesn't seem to be kept between frames, so I had to store the values in parameters.
import numpy as np

node = hou.pwd()
geo = node.geometry()

# Add code to modify contents of geo.
# Use drop down menu to select examples.

rav = hou.Vector3(0,0,0)
n = 0
for point in geo.points():
    r = point.position()
    rav += r
    n += 1
    
rav /= float(n)

Ixx = 0.0
Iyy = 0.0
Izz = 0.0
Ixy = 0.0
Iyz = 0.0
Ixz = 0.0

for point in geo.points():
    r = point.position() - rav
    Ixx += r[1]*r[1] + r[2]*r[2]
    Iyy += r[0]*r[0] + r[2]*r[2]
    Izz += r[0]*r[0] + r[1]*r[1]
    Ixy += - r[0]*r[1]
    Iyz += - r[1]*r[2]
    Ixz += - r[0]*r[2]

IT = np.array([ [Ixx,Ixy,Ixz], [Ixy,Iyy,Iyz], [Ixz,Iyz,Izz] ])
w, v = np.linalg.eigh(IT)

hou.parmTuple("../eigen/cog").set(rav)
hou.parmTuple("../eigen/w").set(w)

ev = v[:,0]
eb = np.array(hou.evalParmTuple("../eigen/evec0"))
if np.dot(ev,eb) < 0:
    ev = ev * -1.
hou.parmTuple("../eigen/evec0").set(ev)

ev = v[:,1]
eb = np.array(hou.evalParmTuple("../eigen/evec1"))
if np.dot(ev,eb) < 0:
    ev = ev * -1.
hou.parmTuple("../eigen/evec1").set(ev)

ev = v[:,2]
eb = np.array(hou.evalParmTuple("../eigen/evec2"))
if np.dot(ev,eb) < 0:
    ev = ev * -1.
hou.parmTuple("../eigen/evec2").set(ev)