Procedural Klein Bottle in Python SOPS – Houdini

Procedural Klein Bottle in Python SOPS – Houdini

by tomminor 0 Comments

What’s a Klein Bottle?

Klein Bottle (3D)

A Klein Bottle can be thought of as a 3D Möbius strip, a surface with no interior that produces a shape that looks like an odd bottle. I don’t claim to be an expert at generating such things, but Paul Bourke is and you can read about it (and loads of other cool shapes) in more detail on his site.

Generating geometry in Houdini Python

While it seems possible to generate geometry in Vex, I decided to see how to do it with Python as it gives me an excuse to dabble in the Houdini Python API and I think it fits this type of project better. The bible for anything Houdini Python is the documentation itself, so I started looking at the examples there.

The main thing I discovered was that you can only generate geometry is limited to the Python SOP context, so a regular Python node will not work. You will need to make a new node using File >> New Operator Type >> Python Type, then set the Network Type to Geometry Operator.

Once all that’s set up it’s very easy to create geometry, simply use something like:

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

poly = geo.createPolygon()

p0 = geo.createPoint()
p0.setPosition( [0,1,0] )
p1 = geo.createPoint()
p1.setPosition( [1,1,0] )
p2 = geo.createPoint()
p2.setPosition( [1,0,0] )
p3 = geo.createPoint()
p3.setPosition( [0,0,0] )

poly.addVertex(p0)
poly.addVertex(p1)
poly.addVertex(p2)
poly.addVertex(p3)

Although I had to write some extra code to generate polygons properly, generating the points of the Klein was easy to do in a loop and pretty fast until I set it to high point counts.

Adding attributes to new points

The only other issue I had was adding attributes to points etc, I got errors related to the attribute not existing on the point. I eventually found an (obvious in hindsight) solution to this, you simply have to add the attribute to the geometry first and then you can set the value on the point:

nrmAttrib = geo.addAttrib(hou.attribType.Point, "N", (0.0, 0.0, 0.0))

for i in range(0, N):
    for j in range(0, N):
        u = umin + i * (umax - umin) / float(N)
        v = vmin + j * (vmax - vmin) / float(N)
        p = eval(u,v)
        
        p0 = geo.createPoint()
        p0.setPosition( p )
        p0.setAttribValue(nrmAttrib, calcNormal( eval(u, v), eval(u + delta, v), eval(u, v + delta)) )

The result

Here’s the final Klein geometry rendered out in Mantra with a glass shader, it’s missing a section because of the way I’m generating faces for it and I lack the time to debug it, but I think it makes it more unique than the generic klein bottle anyway.
klein

Bonus: Using it to test out my Reaction Diffusion

(with a laplacian based on connected topology)

Copy blendshape to similiar geometry script (Maya)

A quick script I wrote that helps you copy blendshapes from a target mesh to only the selected vertices of another mesh, which is useful if you have models with identical faces but different bodies (that are a single object).

For example, if you want to copy a face blendshape from a character onto a copy of the character with identical face geometry but different body topology, you would :

  1. Duplicate the character with different body topology.
  2. Select the original blendshape mesh that you want to transfer to the duplicate.
  3. Shift select the vertices on the duplicate character that you want to be affected (such as all the head vertices).
  4. Run my script.
  5. Blendshape >> Add the result to your skinned character.

I’m unsure if there is a better way of doing this, but this script worked for a simple cartoon character.

import maya.cmds as cmds
 
selection = cmds.ls(sl=True)
 
if len(selection) > 0:
    targetMesh = selection[-1]
    affectedVertices = selection[0:-1] # Assume the rest of the selection is made of vertices
    affectedMesh = cmds.listRelatives(cmds.listRelatives(affectedVertices[0], p=True), p=True)
    
    vtxSet = cmds.sets(affectedVertices, v=True, n="SelectionSet")
    print cmds.sets(vtxSet, q=True)
    
    # Move to origin
    cmds.move(32, 0, 0, affectedMesh, ws=True)
    cmds.move(32, 0, 0, targetMesh, ws=True)
    
    # Select target mesh and affected vertices
    cmds.select(cl=True)
    cmds.select(targetMesh)
    cmds.select(vtxSet, add=True)
    
    cmds.transferAttributes(transferPositions=1, transferNormals=1, transferUVs=2, transferColors=0, sourceUvSpace="map1", sampleSpace=3, searchMethod=0, flipUVs=0, colorBorders=1)
    
    cmds.hide(targetMesh)
    
    cmds.select(cl=True)
else:
    print "Select vertices to move and target mesh"