Generative Design

Catalog of lecture notes, technical tutorials, and other content from an advanced computational design course at Columbia University’s Graduate School of Architecture, Planning, and Preservation (GSAPP)

Working with geometry in Python

Danil Nagy
Generative Design
Published in
14 min readFeb 12, 2017

--

The core Python language is very limited in functionality, being restricted to the basic algebra, flow control structures, and data manipulation functions we worked with in the previous three sections. This is done intentionally to keep the core Python language as light-weight and fast as possible. To extend Python’s functionality to more advanced uses, we rely on a series of external libraries which define objects and methods useful for specific tasks. To use the specialized features of these libraries we must first make sure they are installed onto our computer, and then we need to import into our script. Some very common libraries such as random come pre-installed with Python but still must be imported into every script we want to use it in. Other libraries such as numpy have to be installed separately before they can be imported into our scripts. To enable us to work with geometry in Python, Rhino and Grasshopper provide several pre-installed libraries which we will cover in this section.

Working with libraries

The three main libraries provided by Rhino/Grasshopper which allow Python to access geometric data types and operations are:

  1. ghpythonlib.components — allows you to directly reference Grasshopper components in Python code
  2. Rhino.Geometry — allows you to access all commands and data types in the main RhinoCommon library
  3. rhinoscriptsyntax — a wrapper over the Rhino.Geometry library which provides similar commands and functionality to Rhinoscript

Note: the ghpythonlib library is only available through the GHPython node starting with version 0.6.0.3 so if you are using an older version of the plugin this library may not be available. You can download the latest version from http://www.food4rhino.com/app/ghpython.

Let’s see how each one of these work through a simple example. We will create a simple Python script that takes a point as an input and creates a circle with that point as the center and radius ‘2’. The Grasshopper definition consists of a Python node with one input and one output, a Pt node connected to the input, and a Panel node connected to the output so we can see the results.

Double-click on the Python node to open up the Python script editor. First let’s see how the input point is represented in Python. We can see this by printing the input’s value and use Python’s type() function to print its type. Type these lines in the script and click the ‘Test’ button to see the results:

print x
print type(x)

You should see something like this:

ac7825d2-24b0-4225-b52a-f04a3daa0849
<type 'Guid'>

Wait a second, didn’t we input a point — why are we getting this ‘Guid’?

By default any geometry input into a Python node is not brought in as the geometry itself, but as a reference to the geometry in the Grasshopper environment. This reference is stored in the geometry’s unique ID number, a text string called a ‘Guid’. This can create problems for most geometric functions, which expect actual geometry data instead of a ‘Guid’ reference.

We can fix this by telling Python exactly what kind of data we are inputting using ‘Type hints’. To set the input’s type, right click on the ‘x’ input of the Python node, go to ‘Type hint’, and select Point3d. The ‘Type hint’ menu shows all the geometry types supported by Grasshopper. When we specify that the input is of the type ‘Point3d’, Python will automatically convert the ‘Guid’ reference to the actual point geometry so that we can use it with geometric functions in our script.

Now that we have correctly specified the type of the geometry we are inputting, we can start to build our simple script. We will start by importing the ghpythonlib.components library. Delete the previous two lines and type on the first line:

import ghpythonlib.components as ghcomp

This will import the components portion of the main ghpythonlib library into our script so we can use it’s methods to work with geometry. We use the import ... as ... syntax to give the library a shorter keyword that will save us typing and make the script cleaner. Now when we use the library we can reference it with ‘ghcomp’ rather than typing the full ‘ghpythonlib.components’ each time.

The ghpythonlib.components library contains methods that replicate the behavior of each node in Grasshopper. Each method expects the same number and type of inputs as its Grasshopper node equivalent, and returns the same outputs. If the node has more than one output the return will be a list whose length is the number of outputs.

Let’s use the library’s Circle method to create a circle based on a center point and radius, just like the ‘Circle’ node in Grasshopper. On the next line type:

a = ghcomp.Circle(x, 2)

As expected this creates a circle with a radius of 2, centered on our input point.

Now let’s do the same thing with the Rhino.Geometry library. Below the first import line, type in

import Rhino.Geometry as rh

This line imports the Geometry portion of the main Rhino library and assigns it the keyword ‘rh’. Now we can change the line of code that creates the circle to:

a = rh.Circle(x, 2)

If you run the script you will see that the result is exactly the same — a circle centered on our input point with a radius of 2.

Finally, let’s look at the same example using the rhinoscriptsyntax library. We can import the library by typing:

import rhinoscriptsyntax as rs

and use the library’s .AddCircle() method to create a new circle based on our input point and radius.

a = rs.AddCircle(x, 2)

We have now seen three different libraries that allow you to work with geometry in Python, so which one should you use? Ultimately all three do basically the same thing and create the same exact geometry, but they each come with certain benefits and limitations:

  • The ghpythonlib.components library makes it easier to get started because you can directly use the same nodes you are used to using in Grasshopper. However you are restricted to what is available in Grasshopper, and some of the methods can be clunky compared to those in RhinoCommon.
  • The rhinoscriptsyntax library was created to make it easier for those already used to using RhinoScript to transition to using Python. The methods in the rhinoscriptsyntax library replicate those in RhinoScript, but do so by ‘wrapping up’ methods from RhinoCommon. Thus they may make some geometric operations easier and cleaner, but also limit the scope of possibilities compared to using the full RhinoCommon.
  • The Rhino.Geometry library is the most comprehensive and robust way to work with geometry in Python because it exposes all of the methods in the full RhinoCommon library. RhinoCommon was a universal cross-platform library developed by McNeel for the release of Rhino 5 which allows all versions of Rhino and Grasshopper to access the same geometric data types and methods. By tapping into this library we gain access to everything capability of Rhino, which allows us to do things we could not do with either Grasshopper or RhinoScript.

One major difficulty when starting to work with libraries such as rhinoscriptsyntax or Rhino.Geometry is knowing the exact syntax of each geometric method available, what to pass in for inputs, and what returns to expect from each one. In Grasshopper you can easily see what nodes are available to you by looking through the options in the toolbar. You can also see each node’s expected inputs and outputs by looking at the ports of the node. With code it is a bit more difficult because there is no graphic interface for any of the methods. So how do we know what methods available and how to use them?

The best way is to search through the documentation of each library, which contains a full description of each object implemented by the library and its methods. You can find the documentation of the two libraries here:

In practice, searching through full documentation sets can be difficult and confusing for someone just getting started, so the Python script editor provides two tools that make it easier to find out what methods are available in a library and how to use them.

The first is the autocomplete feature, which gives you hints on what methods are available in the library as you type the code. You may have already noticed it as you were writing the lines above. Lets type the line

a = rh.Circle(x, 2)

again character by character to see how this works. Remember that rh is a keyword representing the Rhino.Geometry library, and the . symbol is Python’s way of accessing an object’s or library’s methods. Once you type in the . symbol a window will pop up with a list of all the methods in that library. This would also work if you had an instance of an object and where trying to access it’s methods. As you continue typing, the pop-up list will automatically scroll down to the portion you are typing and highlight the best matching method. Once you see the method you want highlighted you can press ‘Enter’ or double-click on the name to enter the method name into the script.

Following the method name you create a set of parenthesis where you pass in the method’s inputs. Once you type the first ( the Python window will automatically load the documentation of that method into the results windows which tells you what inputs the method expects and what outputs it generates.

In the case of the .Circle() method you can see that it actually supports many different combinations of inputs. In Python this is called ‘overloading’ a method, and allows a single method to do different things based on different combinations of inputs. In this case it allows us to create Circles in several different ways such as based on a center and radius or based on 3 points. Overloading is another advantage of using the Rhino.Geometry library over the ghpythonlib.components library. Instead of remembering the 7 different nodes for creating circles in Grasshopper, we have a single .Circle() method which can make circles in different ways based on the inputs we give it.

Working with sets of data

So far we have seen how we can define geometry in Python based on a single input. But what if we want to work with inputs that contain multiple values? Let’s extend our definition to see how this works.

Instead of a single point we will now create a grid of points by using two Series components to create two lists of 5 values each, and plug in those lists as the x and y components of the Pt node. To get a grid we need to graft one of the number lists, which we can do with a shortcut by right-clicking on the output of the second ‘Series’ node and selecting ‘Graft’. We will also flatten the resulting set of points by right-clicking on the output of the Pt node and selecting ‘Flatten’. Let’s also reduce the circle radius to 0.4 so we can see the circles easier.

There are two main ways to handle multi-value inputs with the Python node. The default way is to have the Python node act just like any other node in Grasshopper. This means that when you plug in a multi-value data stream into any of the node’s inputs, the node will execute once for each value in the stream. This is how our node is working now. You can see that in the script we are only referencing a single point, and the node is actually running 25 times to create a circle for each point in the stream.

The second way to handle multi-value data streams is to bring them directly into the Python node as a list. In this case the node will execute only once, and you can work with the different values directly in the script. To enable this kind of input you need to right-click on the input’s name in the Python node and select ‘List Access’. Now all 25 points are brought into our script as a Python list, so we need to change our code to loop over all the points and create a circle for each one. We also have to define the ‘a’ output as a list, and use the .append() method to add the circles to that list as we generate them.

a = []
for pt in x:
a.append(rh.Circle(pt, 0.4))

So which of these strategies should you use when working with multi-value data streams?

For simple tasks where you only want to deal with one value at a time it is often easier to use ‘Item Access’ mode and let Grasshopper handle the looping. However, for more complex tasks it is often easier to bring the data in all at once using ‘List Access’ mode and use Python’s loops and conditionals to deal with it directly. Often this allows you to work around Grasshopper’s complicated data tree structure which can be confusing and non-intuitive for advanced tasks involving complex data structures.

However, what if our data is already in a tree structure in Grasshopper and we need to maintain that structure in our script? Can we import the data tree directly into our Python script and work with it there?

Working with data trees in Python

Working with Grasshopper’s data tree structure in Python adds extra complexity and should be avoided if at all possible. If we only need to work with one value in the tree at a time we can use ‘Item Access’ mode and Grasshopper will maintain the same data tree structure in the output (you can try this by reverting to our ‘Item Access’ implementation above and getting rid of the ‘Flatten’ shortcut in the ‘Pt’ output). You can also flatten the trees before inputting them into Python and use the ‘List Access’ mode to work with them directly as Python lists as we did above.

However, if you absolutely must deal with the data tree structure directly in Python, you can do so by changing the input to ‘Tree Access’ and bringing the data tree structure directly into Python. Let’s see how we can work with this data by making some modifications to our circle script. Let’s take off the ‘Flatten’ shortcut in the Pt node’s output and change the ‘x’ input of the Python node to ‘Tree Access’ mode.

This will bring the center points into our script as a data tree with 5 branches of 5 points each. The data is now represented in Python as a special type called ‘DataTree’ (we can see this by using the type() function in Python and printing the results).

print type(x)

This data type has several properties and methods that allow you to work with the tree structure and access the data in the different branches.

  • The .BranchCount property stores the number of branches in the data tree
  • The .Path() method returns the path of a branch given it’s index
  • The .Branch() method returns a list of data in a branch given it’s index.

Using these methods we can modify our script to work directly with the tree data. First we create a loop to iterate over all the branches in the tree (we use range() to create a list of indexes from 0 to the number of branches) and loop over them using the variable i.

for i in range(x.BranchCount):

Then we create a second loop to iterate over each point stored in each branch. Remember that the i variable is iterating over the index of each branch, so we can use x.Branch(i) to access the data in each branch one at a time.

	for pt in x.Branch(i):

What if we also want to output our results in data tree format? Again, this adds extra complexity to our script and should be avoided if possible. If we really need to do it though, we can. In this case we need to actually create a new ‘DataTree’ object, which requires us to import two additional objects from the main Grasshopper library into our script. We can import them by writing these two lines at the top of our script:

from Grasshopper import DataTree
from Grasshopper.Kernel.Data import GH_Path

The DataTree object allows us to create new data tree variables while the GH_Path object allows us to create path variables which tell the data trees where to store data. Both of these objects are found within the main Grasshopper Python library and can be imported using the from ... import ... syntax to import only the specific objects we need.

Now we need to change the ‘a’ output to work as a data tree instead of as a basic Python list. First we declare ‘a’ as an instance of the DataTree object:

a = DataTree[object]()

Then inside of the first loop we create a new variable to represent the path to the branch where we will place the data:

	newPath = GH_Path(i)

The GH_Path object can define any data tree path by taking in a sequence of integer values. In this case we pass in the ‘i’ variable which is storing the index of each incoming branch. This will in effect replicate the structure of the incoming data tree.

Finally we use the DataTree’s .Add() method to add each circle to the tree based on the specified path.

		a.Add(rh.Circle(pt, .4), newPath)

Computing geometry in Python

Let’s finish this section by looking at how we can use the Rhino.Geometry library to do geometry-based computation directly in Python. First let’s extend our definition by creating a new input to the Python node which takes in a single point referenced from the Rhino document. Remember to also change its ‘Type hint’ to ‘Point3d’ so that Python converts it to point geometry.

Now we will change our circle definition to compute the radius of each circle dynamically based on the distance from its center to our referenced point.

First we write a new line of code to compute the radius.

		radius = pt.DistanceTo(y) / 5.0

We use the .DistanceTo() method of the circle’s center point stored in the pt variable to compute the distance to the new point stored in the y variable and divide the distance by 5.0 to make all the circles smaller. We then change our circle definition to use this radius instead of the previous hard-coded one:

		a.Add(rh.Circle(pt, radius), newPath)

This gives our circles a dynamic relationship to the referenced point, allowing us to create different patterns by moving the point.

This example shows how we can use the Rhino.Geometry library to replicate all the capabilities of Rhino and Grasshopper directly with Python code. Although working this way takes practice, it gives us a huge degree of control over our geometry and allows us to describe complex design spaces beyond the limits of what can be directly done in Grasshopper. In the next few sections we will study some unique strategies for controlling design spaces that are only possible through direct implementation in code.

--

--

Generative Design
Generative Design

Published in Generative Design

Catalog of lecture notes, technical tutorials, and other content from an advanced computational design course at Columbia University’s Graduate School of Architecture, Planning, and Preservation (GSAPP)

Responses (3)