Importing Tilt Brush files into Houdini

Although Tilt Brush exports FBX files with the brush stroke geometry sometimes it can be useful to get the data in the .tilt file itself. The .tilt file contains the controller positions, orientations, trigger pressure and timestamps that Tilt Brush then creates stroke geometry from. In my case I particularly wanted the timestamps, as it makes Tilt Brush a simple 6-dof capture solution.

Houdini’s python integration and the Tilt Brush Toolkit make this fairly straightforward.

First you need to get the Tilt Brush Toolkit. You can either clone the git repo or download it from github, and you need to add the Python subdirectory to your PYTHONPATH environment variable.

Now you can use a Python SOP to process the .tilt file and generate geometry. Here is the code I use:

# This code is called when instances of this SOP cook.
node = hou.pwd()
geo = node.geometry()

# Add code to modify the contents of geo.
filename = node.evalParm('filename')

from tiltbrush.tilt import Tilt
tilt = Tilt(filename)

if tilt.sketch.filename:
  filenameAttrib = geo.addAttrib(hou.attribType.Global, "filename", "")
  geo.setGlobalAttribValue(filenameAttrib, tilt.sketch.filename)

brushAttrib = geo.addAttrib(hou.attribType.Prim, "brush", 0)
colorAttrib = geo.addAttrib(hou.attribType.Prim, "Cd", (0.0, 0.0, 0.0, 0.0))
sizeAttrib = geo.addAttrib(hou.attribType.Prim, "size", 0.0)
  
time = geo.addAttrib(hou.attribType.Point, "time", 0.0)
pressure = geo.addAttrib(hou.attribType.Point, "pressure", 0.0)
orientation = geo.addAttrib(hou.attribType.Point, "orient", (0.0, 0.0, 0.0, 1.0))
poly = geo.createPolygon()

for stroke in tilt.sketch.strokes:
  poly = geo.createPolygon()
  poly.setIsClosed(False)
  for cp in stroke.controlpoints:
    point = geo.createPoint()
    point.setPosition([i / 10.0 for i in cp.position])
    point.setAttribValue(orientation, cp.orientation)
    point.setAttribValue(time, cp.extension[1] / 1000.0)
    point.setAttribValue(pressure, cp.extension[0])
    poly.addVertex(point)
  poly.setAttribValue(brushAttrib, stroke.brush_idx)
  poly.setAttribValue(colorAttrib, stroke.brush_color)
  poly.setAttribValue(sizeAttrib, stroke.brush_size)

Now you have each stroke as a curve primitive, with point attributes for position, orientation, pressure and time. The orientation is a quaternion but you can convert that into a more useful normal and up vector with an Attribute Wrangle SOP:

v@N = qrotate(@orient, {0, 0, 1 });
v@up = qrotate(@orient, {0, 1, 0});

Sweeping a line SOP along the curves will give you an approximation of the actual stroke geometry.



Leave a Reply

Your email address will not be published. Required fields are marked *