Monday, December 3, 2012

Easily Create Graphical User Interfaces in Rhino Python

I have been doing some work for the Taubman College of Architecture Fabrication Lab. I've been working on some Graphical User Interface helper classes for use in Rhino Python. The idea behind these classes is they allow you to create custom, floating dialog user interfaces without having to use a UI creation tool like SharpDevelop.

I thought these would be generally useful so I made them free to download and use. Use the link below to download a ZIP file of everything you'll need.

Download the Code

Getting Started

Simply unzip the file into a directory of your choice. Run the Rhino Python Editor, load one of the example .py files (for instance Circle UI and choose "Run Script (no debugging)" from the python editor.

Here are a few examples of UIs you can easily make. These examples are all included in the ZIP file.

Circle UI

This is the most basic example. The UI is a single numeric control which allows the user to enter the radius for a circle. After the dialog is closed the circle is drawn in the Rhino viewport. This demonstrates the basic setup pattern and how a single variable can be changed in the UI and then used by your script.

Relative Prime UI

This demonstrates using two numeric input controls and a read-only label field which is updated based on the values in the two spinners. There are also OK, Cancel and Help buttons. This shows how the controls can interact with one another.

All Controls UI

This sample shows every control that's available and demonstrates some basic techniques to process the controls as the user interacts with them. It shows how to create and use labels, URL link labels, text boxes, read-only text fields, check boxes, buttons, combo boxes, separators, numeric up/dn controls, trackbars, and picture boxes.

Torus Knot UI

This example goes beyond being educational and is actually somewhat useful! This script creates torus knot curves and polysurfaces and provides an interactive preview in the viewport as the user changes the control values.
The basic idea for this example came from Steve Baer's excellent Starmaker sample. I used it as the pattern for the interactive redraw. However I'm not using the SharpDevelop IDE as Starmaker does.

Basic Steps - An Overview

To make a UI for your script there are just a few things you need to do:
  1. Import the necessary modules into your code. 
  2. Define the variables the UI controls will change.
  3. Write the code to create the UI form. 
  4. Write the code to process the UI controls.
  5. Display the form to the user. 
  6. Work with the updated variable values the UI has altered. 
The idea is that every one of those steps should be easy! Let's start with the simplest examply and see specifically how to accomplish each step. Load Circle UI into the Python Editor.

The Circle UI Example

This example is trivially simple. Here's the complete script:

Circle Example Details

1. Import the necessary modules into your code. 
First you need to import the Meier_UI_Utility module. This file contains the code to create the form, methods to add controls, and a method to arrange them on the form. You can see the import on line 3, in the code above.

2. Define the variables the UI controls will change.
The next thing to do is define a class which creates and manages the UI variables. That's class CircleUI above (line 16). Everything happens in the __init__ method. Line 19 defines a variable to hold the radius value the UI will change.

3. Write the code to create the UI form. 
Line 21 creates the UIForm object using the Meier_UI_Utility module. You can think of the form as the dialog window (the frame, the title bar, and the close button). This line also creates a panel and docks that into the form. You can think of the panel as an empty surface that will hold the controls. When the form is displayed the panel's controls are what appears inside the window.

From the ui variable you access the form using this code:
You access the panel on the form using this code:

The next step in building the UI is to add controls to the form's panel. There are methods provided such as addLabel, addTextBox, addPictureBox, etc. It is the parameters which you pass to these methods which describe the control. In the Circle example only two controls are needed - a label control and a numeric up/dn control. The label just shows the name "Radius:". The numeric up/dn allows the user to type in a value and use the up and down arrows to increment or decrement the value.

Here are the two calls to add them:

It is worth carefully looking at each parameter to understand them. First the label - it has the following parameters as defined in the addLabel method of the file.

Note: You should use as your reference for the parameters. Each is fully documented in the file itself as shown below.
Several of these parameters apply to every control. For example name and breakFlowAfter. Others are unique to each control type.

Every control has a name associated with it. You can use the name so your code can "find" the control later. We'll see examples of this in a more complex example. If you don't need to find the control you can simply pass "" for the name. The Circle example doesn't need to find it.

The text parameter is simply the label text - what shows up in the UI. In our case that's passed as "Radius:".
The label text can be any color you like. If you pass None for the color parameter you'll get black text. Otherwise pass an (R,G,B) list like (0, 255, 0) to get a custom color.

The breakFlowAfter parameter controls the layout. This one is important to understand.

Controls are laid out on the form from left to right, top to bottom.

You can think of them as "flowing" onto the form.

If a control does not "break the flow after itself" then the next control will appear directly to its right.

For example, the numeric up/dn control appears directly to the right of the label control. To get that we pass False as the label's breakFlowAfter parameter. Here is our call to addLine and the result:

If instead we passed True to breakFlowAfter the UI would appear like this:
The layout "flow" is broken and the next control begins again on a new line. This is the essence of how you manage the layout on the form. The entire UI for the Torus Knot example was done that way.

Next let's see the code to add the numeric up/dn control. Here's the definition from the file for the addNumericUpDown method - the comments describe the parameters:

The Circle example call to this method looks like this:

The name is passed as "". The next to arguments define the lower and upper limits for allowable values. So this control will allow values between 1 and 50. The next argument is the increment to use when the up and down arrows are clicked. Here the increment is set to 1. The next argument is the number of decimal places to display. This is set to 2. If you want integers to appear you'd pass 0. The next argument is the default value to use - that is what value appears in the control initially. Here we pass the self.radius variable. The next argument is the width - the horizontal size for the control in pixels. Next is breakFlowAfter and True is passed. If there were any additional control they would appear on a new line. In this example it really doesn't matter since there are no more controls.

4. Write the code to process the UI controls. 
The last parameter is an important one. It is called a delegate. A delegate is a method that you write with a specific signature (set of parameters). This method will be called as the user operates the control.

Some control don't have a delegate - because they need no processing. For example the label. Other controls, like the numeric up/dn do.

In this example the delegate is self.Radius_OnValueChange. As the name suggests this is what's called when the radius value changes. Here is the code we provide as that delegate:

The parameters to the method form the standard signature - (self, sender, e). The one we are most concerned with is sender. This is the "sending" control itself, e.g. the radius numeric up/dn control. We use the Value property of that control to get the current value and set our radius variable equal to it.
In this way, as the user operates the control our method gets called and we can store the variable or do any other work we like based on the change. This is a very simple example so the only thing we do is store the value. In more complex user interfaces you'll often do things like enable or disable other controls, or ask the viewports to redraw to reflect the changed value. The other examples - Relatively Prime, All Controls, and Torus Knot all demonstrates this.

The next step is to get the controls onto the form. What's actually happened during these addXYZ... methods is the controls are just accumulated. In order to place them on the form you need to call a method of the form named layoutControls().

That's all we need to create the UI and handle processing the controls.

5. Display the form to the user. 
What's next is displaying it to the user and then processing the results. In this Circle example that's very easy. Here's the code:

First we create the CircleUI object which we defined and store it in a variable named ui. Then we present the UI to the user by calling the Rhino method ShowSemiModal(). We pass in the form from our UI object (ui.form).
That method runs and puts up the dialog. The user can change the controls as well as do some simple work in Rhino itself. For example they can change layers, and create objects. However many operations are restricted while the dialog is up - for example they can't change the selection.

6. Work with the updated variable values the UI has altered.
When the user exits the dialog by pressing the [X] button in the title bar control returns to the script. In this example we simply add the circle to the drawing.

rs.AddCircle(rs.WorldXYPlane(), ui.radius)

Note that we pass the value from the ui object - ui.radius.

That's it. Those basic steps are all that's required to make a GUI and use the results. One you get used to this pattern it's easy to copy/paste code to create more complex UIs.

Other Examples

In this next section we'll look at a few more code snippets from some of the other examples.
The Relative Prime UI Example has two numeric input controls and a read-only label field which is updated based on the values in the two spinners. There are also OK, Cancel and Help buttons.

This example is a little larger and demonstrates compartmentalizing the code a bit more.

This example does some demo processing of the dialog return value. It prints "OK" or "Cancel" depending on how the user closes the dialog. Therefore an extra import module is needed. You can see that DialogResult is imported at the top of the code.
Instead of a single value to update as in the Circle example, this code has two: a and b. For the sake of demonstration this is broken out into its own small class, UIData. This objects holds the two value the UI will change.

It is constructed and then passed in the RelativelyPrimeUI object. As we'll see below that's the one that actually creates the form, adds the controls, and lays them out. This is very similar to the Circle example except the data to manipulate comes from another object. Notice how Main() create the data object then passes it to the RelativleyPrimeUI class which stores it.
The form is created as before. The controls are added as before, however they are broken out into their own method for clarity.

Let's see how this example updates the read-only label when the spinners change. As before there is a delegate passed to the AddNumericUpDown method to process the changes. Inside this delegate is where the processing happens.

Note that we have delegates for the A, B and the Help buttons. We also have a method which updates the UI to report is the current numbers are indeed relatively prime.

The A_OnValueChange() and B_OnValueChange() methods each store the value and then call an UpdateUI() method. This "finds" the read-only control and sets the string appropriately. You can see in the code above it searches using the name of the control. So the read-only name field is assigned the name "readOnlyResult". UpdateUI() uses this name to find it with the code shown above. Note the try/except mechanism - this is needed to prevent problems as the form is constructed. When the A and B spinners are added to the form their ValueChange method can be called as the initial value is set. That happens before the read-only field control is added to the form. When that's the case Find method will not succeed - the control isn't there yet and would crash. To prevent that we use try/except. If the try block fails we simply do nothing.

The delegate for the Help button is shown. This is called whenever the button is pressed. You can see it simply brings up a MessageBox.

One other thing to note here. Buttons which have text parameters "OK" or "Cancel" get special treatment. It is assumed these are meant to close the dialog and return the appropriate return value. So - that's how they are set up to work. A Cancel button also allows the Esc key to close the dialog.

Further Examples

Have a look at All Controls UI It shows the correct format for adding every UI control that's supported. The delegates are all used and simple processing code is provided.

The Torus Knot UI provides a larger sample which supports interactive redraw as the user changes the controls.

Final Thoughts

I sincerely hope you enjoy using these tools. However, this code is provided "as-is". If you leave comments I'll try to address them as I have time.

Known Issues

The following issues are known bugs or limitations:
  • These UI controls are based on Winforms from the Microsoft Windows operating system. Therefore these UIs will not work on Mac versions of Rhino. 
  • The PictureBox control has a vertical spacing issue. The layout code for the FlowLayoutPanel which makes them computes the required space incorrectly. The work around for this is to use a blank label before the PictureBox. Make sure to pass False to the breakFlowAfter parameter. Like this:
                       addLabel("", "", None, False)
                       addPictureBox("picbox1", "./SamplePicture.jpg", True)

Saturday, December 1, 2012

Torus Knot Maker


This post provides documentation for the Rhino 5 Python plug-in I wrote called the Torus Knot Maker.

A Torus Knot is a curve which lies on the surface of a torus (think donut shape). This plug-in generates them by sweeping a section curve along a path (a one rail sweep in Rhino). These are a few examples of the knots you can create:

The User Interface

The plug-in has a small dialog of controls and an interactive preview. The controls are documented below. 

Online Help: Clicking this control launches your default browser to this help page!

Path Curve Options
The two main parameters which define the path curve are conventionally called P and Q. See Torus Knot Wiki for more technical information.

  • P: The number of times the knot winds around the axis of rotational symmetry. 
  • Q: The number of times the knot winds around the interior of the torus. 
  • Number of Points: The number of control points in the path curve. 
  • Z Scale Factor: Use this number to increase the vertical height of the knot relative to its width. As fewer points are used in the path the knot can become "squashed down". This allows you to restore its height vertically. 

Note: P and Q need to be "relatively prime" otherwise the knot is simply a loop. Two numbers are relatively prime if they have no common factors other than 1 (in other words you cannot evenly divide both by some common value). Example: 2 and 3 are relatively prime (no common factor), but 2 and 4 are not relatively prime because you can divide both by 2 (2 is a common factor). As you change the values in the UI you'll quickly see when the values are not relatively prime because the shape is no longer twisting as if wrapping a torus.

Section Curve Options
These options affect the curve that is swept along the path.

  • Smooth: If checked the section curve is smoothed (a degree 3 curve). Otherwise it is a polyline (degree 1). With this unchecked the knot will have crisp edges. 
  • Radius: Controls the size of the section. Higher values make "thicker" knots. 
  • Rotation: This allows you to rotate the section about its center point. This is most noticeable when Smooth is off. 
  • Number of Points: The number of points in the section. 3 produces a triangular section, 4 a square, etc. The effect of this setting is less noticeable when Smooth is on. 

Output Options
These options control what is created after you press OK.

  • Path Curve: If checked the path curve is created (degree 3 NURBS); otherwise it is not. 
  • Section Curve: If checked the section curve is output (degree 1 or 3 NURBS); otherwise it is not. 
  • Surface: If checked a surface (polysurface) is created; otherwise it is knot (hardy, har, har...)


  • The interactive preview does not appear in a Rendered viewport. Therefore you must use one of the other display modes when using the plug-in. 
  • See the post Torus Knot Table - Design and Fabrication for details on actually making one of these knots from wood. 

Sunday, June 24, 2012

Torus Knot Table - Design and Fabrication

I designed a table based on the geometry of a torus knot. I wanted to make the table from hardwood. This design was meant as a study of CNC cut wood joinery as well as achieving a high degree of curvature in a table made by 5-axis CNC milling.

I did it as the final project for my Master of Science in Digital Technologies degree from Taubman College at the University of Michigan. Tabuman College has a fantastic digital fabrication lab where I had access to the 5-axis router, abrasive water jet cutter and, CNC bed mill.


A torus knot is a curve which lies on the surface of a torus (think donut shape). Here is an example:

I wrote a Python script inside Rhino to generate the geometry. It starts with a single curve like the one above. This post provides a Rhino Python script to generate torus knot curves.

Depending on the input curve a great many tables are possible. Here are some 3D printed models showing different forms. The table I choose to fabricate is the one in the middle.

You enter a variety of properties such as the number of pieces, the length of an edge cross section, and properties of the dovetail bit that'll be used to cut the joints.

Once you've answered the questions it generates all the required geometry for toolpath setup. Everything is layered separately for an easier time assigning toolpaths in the toolpath setup software - Mastercam. Each part is exported to a separate file if you like.

Here are all three versions of the table run through the script: 

Data about the table is stored in the Rhino file as notes. Here you can see the max sizes, board feet required, and the size of the required block of wood for each piece. 


The prototype table was made from a single piece of 16/4 Yellow Poplar. The board was 4" thick, 10" wide and 16' long.

Each piece is CNC machined in two phases. The first phase cuts the sides, top, back, front and make portion of the dovetail joint. Each surface is left with 1/8" of extra material. This allows the wood to move a bit after the internal stresses of cutting have occurred. The final 1/8" is removed in the second phase.

The first phase is cut on a pair of 6" high pods and held to the table using vacuum pressure. The wood is glued to an 3/4" thick MDF spoil board which can be cut into to accommodate the curvature of the part. Here are all the parts glued to the MDF. The MDF board is larger because the extra surface area was needed to get enough vacuum pressure to hold very securely.

Here's a part sitting on the pods. It's necessary to put them up on pods so the router head can reach the end to cut the dovetail and so it can swarf mill the top surface.

After the first phase is done the dovetail is fully milled. Each side except the bottom is milled to within 1/8" of the final surface.

Before the part can be milled in the next phase it has to be band sawn free from the MDF plate it was glued to. It's okay to leave up to 1/2" on the bottom of the piece - that'll be removed during the final milling.

The second phase of cutting involves holding the workpiece steady using an aluminum fixture. It holds the part using the dovetail joint. I designed and modeled the fixture in Rhino and made it from off-cut aluminum plates. I waterjet cut out the parts, then milled them on the CNC bed mill.


The assembled fixture allows the dovetail to be tightly clamped against the vertical plane of the fixture. A metal dowel pin is run through the fixture to ensure proper vertical alignment and prevent the part from sliding in the dovetail bracket during cutting. 

Here a part is clamped in the fixture and is ready to be cut. As you can see, every face is accessible except the dovetail which is fully milled during phase one.

The dovetail bracket which holds the dovetail end of the workpiece was cut with the same carbide tipped bit used to cut the dovetails. That, and correct setup, ensures a perfect fit.
The alignment of the fixture is critical. The milled front face of the base is carefully aligned with the grid in the table. Then the exact position of the fixture is measured using a dial gauge.


Those measured coordinates are entered as the "table origin" - so the CNC thinks that's the 0,0,0 point. This matches how they were exported in Rhino and imported into Mastercam.

Once held in the fixture the part can be milled on all five remaining faces. Here is a part prior to any cutting:

First the endgrain is cut back 1/8" and the dovetail socket is cut. Then the bottom is milled to the final profile. Then the sides and top are cut.

You can see that the final edges of the part are only 1/8" away from the aluminum fixture.

Careful simulation inside Mastercam is required to ensure that the bit will never strike the fixture. The 3/4" solid carbide bit used to do the swarf milling is over $500 and hitting the fixture would destroy it. Yikes. The image below, taken looking down the tool axis from above, shows how close the cutter comes to the fixture. The screw near the cutter is smaller than the others - that's because the simulation showed it would hit!

Any surface that turns yellow during the simulation is cut -including that screw.

Of all 30 parts only one could not be milled correctly. That's because the sweep from one part to the next is too great and would have resulted in a collision with the fixture as the top of the dovetail was cut. So the top of the dovetail (just a 3/4" wide strip) does not match the neighboring part in this single case.

That extra material had to be removed with a gouge and then sanded smooth.

Here are all 30 parts cut prior to assembly.



The parts are glued up in pair. Then those pairs are glued up and so on...


The table is sanded to remove any tool marks.

Walnut Version

Later I built another version of this table out of Walnut. It's discussed here