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)