RhinoScripting for a Wizard LARP

A friend of mine approached me about helping him with a costume for a wizard character in a live-action roleplaying game. We decided that a cloak was a good place to start, and I immediately wanted to incorporate some technomancer aspects—for example, a 3D-printed cloak clasp. I’ve been getting back into the swing of working with Rhino after upgrading my OS to be able to use the free beta version of Rhino for Mac (which has stabilized a lot since the last time I looked into it), so that seemed like a good place to start. Rhino is very popular among architectural designers and Grasshopper, the visual UI for scripting Rhino processes, has grown an extensive community since last I checked, but what really appealed to me was using a plugin to do some scripting in Python. I’d have access to all of the Rhino commands in a language I already knew how to work with. Using it is relatively straightforward if you’re already familiar with Rhino’s commands+arguments workflow, which is translated very literally into the Python module.

I love the work that Nervous System does with subdivided surfaces, and I’d just overheard half of a lecture on Voronoi diagrams, so I was thinking about Voronoi filigrees. I pictured something kind of spiky and airy, with a bit of blobbiness around the intersections of lines, appropriately organic yet shiny for a fantasy-medieval technomancer.

I didn’t have enough time alotted to make it totally generative, but I wanted two scripts to make the task easier: one for generating Voronoi diagrams out of points, and one to turn lines into 3D shapes. The former turned out to be a matter of grabbing an existing library and interpreting its data. The latter uses Rhino’s “pipe” command, which takes a curve (in the 3D modeling sense: any series of connected points, including line segments) and a series of radii at points along the curve and generates a pinched cylinder. I also merged spheres at the ends of the pipes so that each strut is a self-contained blobby unit.

# In honey.py, the underlying shape-making code:
import rhinoscriptsyntax as rs

def makeCurvesBlobby(curves, vertex_radius):
  for curve in curves:
      # AddPipe args: curve_id, parameters, radii, blend_type=0, cap=0, fit=False
      # parameters are the locations along the curve for the radii to apply, where 0 is the start and 1 is the end of the curve
      pipe = rs.AddPipe(curve, (0,0.5,1), (vertex_radius, vertex_radius/2, vertex_radius))
      startsphere = rs.AddSphere(rs.CurveStartPoint(curve), vertex_radius)
      endsphere = rs.AddSphere(rs.CurveEndPoint(curve), vertex_radius)
      if pipe and startsphere and endsphere:
          pipe = rs.BooleanUnion([startsphere, pipe, endsphere], True)
          # BooleanUnion args: list of objects to union, whether or not to delete the input

# And in blobbify.py, the user interface:
import rhinoscriptsyntax as rs
import honey

curves = rs.GetObjects(message="Select curves to be blobbified...", filter=4)
# "filter" means the object selection only allows for a certain kind of object; in this case, curves
radius = rs.GetReal(message="Enter endpoint radius:", number=1.0 )
honey.makeCurvesBlobby(curves, radius)

which turns this: Curves into this: Blobby surface The full code (including things like interpreting between the rhinoscriptsyntax and voronoi modules’ similar but non-identical ideas about how points should be represented) is on Github.

Unfortunately, the timeline for this project is somewhat compressed and the deadline to get the print done in time for the LARP snuck by us, so I’m putting the design of the clasp on hold to get the rest of the cloak out the door in time with a placeholder clasp.

Python/Rhino things to watch out for

Boolean operations (in the 3D modeling sense: adding and subtracting shapes from each other) are just as prone to mysterious failure as they are in plain Rhino. I suspect I’m running into problems relating to coplanar surfaces, which Rhino’s boolean solver hates. I’m “fixing” this by only automating the booleans in each strut (pipe plus spheres) and merging the whole structure by hand. This was never going to be a fully autonomous process, so no big problem there, but it’s something I’ll want to fiddle with in the future.

Another thing is that it’s possible that one source of mysterious failures I saw was an incompatibility between Python’s floats and Rhino’s ideas about acceptable tolerances. I incorporated this:

from decimal import *
getcontext().prec = 7
# need python code to have same precision as Rhino

def decimate(input):
  # output a Decimal at rhino-compatible amounts of precision
  return Decimal(input).quantize(Decimal('1.000'))

and run decimate() on the data I get back from voronoi.py instead of telling Rhino to plot arbitrarily precise lines. It seems to help, but it’s possible that this is pure superstition.

More vexingly, there are some problems with the Rhino for Mac Python plugin itself. A lesser one is that the documentation is not always very detailed, and Rhino’s own functions fail silently; for example, it took me a pretty long time to realize that all of the strings offered up to GetBoolean() needed to be purely alphanumeric, with no spaces, because it would display the prompt properly but just not show the expected tickyboxes. A bigger problem is that the output of print statements only shows up once/if a script has successfully run, which naturally makes it hard to use them for debugging. Another big one is that something in the system somewhere occasionally decides to ignore new code in favor of a cached version hidden somewhere, which leads to a lot of debug frustration as nothing you do can possibly affect your bugs until you remember what is going on and reboot Rhino. I’ve mentioned both of these issues on the forum, so perhaps I will have some solutions soon.

Update: My forum post has yielded a link to a post which answers my caching question: Python isn’t reloading imported modules unless the code explicitly calls reload(module). Good to know!

| Code