Computational design in Grasshopper
Grasshopper is a visual programming environment which runs as a plugin on top of McNeel’s Rhino 3d modelling software. Grasshopper is now one of the most popular tools for computational design among architects and other designers. The platform’s popularity has been driven by three main factors:
- The flexibility and simplicity of Rhino for a variety of design tasks
- The simplicity of Grasshopper’s visual node-based interface which doesn’t require the writing of any code and has a much shallower learning curve than other code-based tools
- The openness and flexibility of the Grasshopper platform which has led to a large ecosystem of third-party tools and plugins created to work in Grasshopper
Due to the platform’s simplicity and popularity, we will use Rhino and Grasshopper as the generative design platform for the class. We will use Grasshopper to specify our design spaces, with the addition of some actual computer code for more complex operations. We will also use Grasshopper and a series of plugins to calculate measures to evaluate the performance of individual designs. For searching through the design space and optimizing our designs we will use Discover — an open-source Genetic Algorithm library specifically designed to work with models created in Grasshopper.
In this post we will take a look at how computational models are designed in Grasshopper, and consider the extent to which it implements the 5 elements of computer programming we discussed in the previous post.
Disclaimer: This discussion assumes a basic familiarity with Grasshopper, and is not meant to be a comprehensive tutorial or guide about the plugin. The purpose of this post is to go a level deeper and describe how Grasshopper implements the core ideas of computational design as described previously. For a more thorough tutorial on Grasshopper including its interface and how it works with the main Rhino workspace you can consult the great introductory tutorials at http://grasshopperprimer.com/ or follow these video tutorials:
The grasshopper environment
As mentioned previously, Grasshopper is a visual programming environment. This means that, unlike the text-based syntax of traditional programming languages, all operations in Grasshopper are represented by visual elements within a graphical user interface which designers use to develop their models.
All the functionality of Grasshopper is structured around a set of nodes which operate like functions in computer programming. Each of these nodes performs a specific operation given one or more inputs, and creates one or more outputs. The inputs are represented as semi-circular ‘ports’ on the left of the node, while the outputs are represented as ports on the right side.
Grasshopper supports a variety of different data types, both geometric data it inherits from Rhino, as well as other data types that are native to Grasshopper. Some of the non-geometric data types supported by Grasshopper include:
• Float — decimal numbers
• Integer — whole numbers
• Boolean — true/false values
• String — text data
Some of the geometric data types include:
• Point — a location defined by three parameters (x, y, z)
• Vector — a direction and magnitude from the origin defined by three parameters (x, y, z)
• Line — a 1-degree curve segment defined by two points
• Curve — a general NURBS curve
• Surface — a single NURBS surface
• BREP (boundary representation)— a collection of joined surfaces
• Mesh — a surface representation defined by a collection of connected 3 and 4-sided faces
Let’s see how these nodes and data types work in practice by developing a simple definition of a box in Grasshopper.
The image above shows the ‘Move’ node, which creates a moved version of a piece of geometry based on a translation vector. The inputs into the node are the geometry and the vector. The outputs are the new, moved geometry, as well as data about the transformation.
In addition to the ‘function’ nodes which perform operations, Grasshopper has a series of input nodes which bring data into the model. These input nodes are similar to the ‘variables’ in computer programming. There are two types of input nodes in Grasshopper:
- Container nodes are used to store data or reference geometry from the Rhino model. Grasshopper has a different container node for each type of data it supports. These nodes do not execute any function and have only one input that takes in a certain kind of data, and one output that gives the data directly back. By right-clicking on the node you can import one or more pieces of geometry data from the main Rhino model.
- Parameter nodes allow users to specify non-geometric variables directly in the Grasshopper interface. The most common of these parameter nodes is the value slider and text panel.
In order to execute the functions contained in the nodes, the nodes are connected to each other with ‘wires’ such that the output port of one node is connected to the input port of another node. These connections pass data from one node to the other, creating a one-way ‘stream’ of data between the nodes. When a node receives data in its inputs it executes its internal function, creating the outputs that then go to other nodes based on the connections. The whole network of nodes and wires create a ‘data flow’ which defines a geometric model through a sequential series of operations performed on geometric and non-geometric data.
This ‘data flow’ model of computation is similar to procedural programming, in that the operations are executed in order, starting from the container and parameter nodes and flowing through the network until all nodes with input connections have been executed, after which the program ends. Unlike procedural programming, however, the operations are not strictly linear, but execute as soon as they receive their required inputs.
In the image above we extend our definition by creating a ‘Pt’ container node which imports a point from Rhino and feeds it as the geometry input of the ‘Move’ node. We also have a number slider which creates a numerical value of ‘10.0’. This value is then input into the ‘Vec’ component which creates a vector which is then fed as the translation vector into the ‘Move’ node. The output of the ‘Move’ node is now a new point which is moved 10.0 units along the x, y, and z axes.
Nodes in grasshopper support a variety of functions which fall into three basic categories:
- Nodes that create new geometry.
- Nodes that transform existing pieces of geometry. These nodes output a new version of the transformed data, keeping the original input intact.
- Nodes that perform numerical calculations based on numerical or geometric inputs.
Let’s explore some examples of these node types by adding some to our example.
First we add another node called ‘Box’ which takes in two points and creates a new box oriented along the Cartesian axes with those points as the opposite corners. The output of this node is the geometry of the new box.
Next we add another node called ‘Volume’ which takes in a closed surface geometry and calculates its volume. This node has two outputs — a number representing the surface’s volume, and a point representing its centroid. We can see the volume output by connecting it to a text panel which displays any data connected to its input port.
Working with multi-data streams
So far we have only looked at wires that carry single pieces of information between nodes — a single number, a single point, a single box. Often, however, we want to work on many pieces of data at once. For example, we might have a collection of points which define window locations on a façade. We want to be able to work with all these points at once to avoid having to recreate a whole network of nodes for each individual window. Let’s see how this works in Grasshopper by extending our simple box definition.
Let’s say that instead of creating a single box, we would now like to define a set of boxes of different sizes. To do this we can use a ‘Series’ node to create a list of numbers based on a starting value, the step size, and the number of values we want. In this case we set the starting value and step size to 1 and the number of values to 10 to get a set of numbers 1–10. You can see these values in the yellow box in the image, which you can get to by hovering over any input or output label in a node. As you can see, the wires flowing out of the output of the ‘Series’ node look different. The double line is an indicator that the data flowing through the wire is not a single piece of data, but a sequence or list of data.
You can also see how this stream of multiple data permeates throughout the rest of the model. Now instead of creating one vector we create 10 separate vectors, each using one of the 10 different values created by the ‘Series’ node for its dimensions. These 10 vectors are fed into the ‘Move’ node which now creates 10 new points, each defined by the 10 vectors, which then create 10 new boxes, etc.
In reality, the functions within each of these nodes is executing multiple times — one time for each piece of data in the multi-data stream. This behavior can be though of as an ‘inherent loop’, since it is looping over each piece of data it receives and executing some operation each time.
This type of computation can also be thought of as ‘data mapping’ since every node is executing operations based only on the data that comes into its inputs. This means that all operations are tied to the input data and no arbitrary processes can be executed within the loop. This is a major limitation of Grasshopper which we will return to later.
So grasshopper supports looping over data to an extent — what about conditionals?
Grasshopper allows us to use conditions in sets of data to split the data into different streams, which allows us to then handle the two streams differently. This is done through two different types of nodes. The first is a condition node which takes in a data stream and tests each piece for some condition. In the example above we use the ‘Smaller’ node to test whether the volume of each box is smaller than 150. This node creates a new list of Boolean data, the same length as the input, with either a True or False based on whether the condition was met. Grasshopper calls this stream of Boolean values a pattern.
Once we have this pattern we can use a ‘Dispatch’ node to split the data into two streams based on the condition. In this case we split the volumes into two groups — those less than 150 and those greater than 150. If we wanted to work with the box geometry in different ways based on their volumes we could instead plug the output of the ‘Box’ node directly into the ‘L’ input of the ‘Dispatch’ node instead of the volume values. To do the same thing in procedural programming we would write a loop to iterate over each box geometry. Then within the loop we would check each box’s volume and use a conditional to execute different functions on each one.
Data trees
The multi-data streams we saw in the previous example were simple ‘flat’ lists of data in one dimension — but what if we wanted to use a more complex data structure? For example, what if we wanted to organize our list of points specifying the location of windows on a façade into multiple groups, with each group containing windows located on each floor? This type of organization could help us handle windows on each floor differently, which would be difficult to do if all the points were contained in a single flat list.
In Grasshopper, multi-data streams beyond a single dimension are called ‘trees’, which is a kind of metaphor for how you can imagine each dimension forming a series of ‘branches’ leading to the data. In our example, each floor of the building could have its own branch, with each branch containing a list of points for the windows on that floor.
You will find that some nodes in Grasshopper output data in the form of trees, and you often need to work with data in tree format to accomplish certain tasks. Thus it is important that you understand how these data trees are organized and how you can work with data in this format. Understanding data trees is also important because it impacts the way that the ‘inherent loops’ of the nodes get executed. To make this concrete, let’s look at an example of how various types of data streams impact the execution of a single node — the ‘Line’ node which takes in two sets of points and creates lines to connect them.
As expected, if we connect two individual points to the inputs of the ‘Line’ node, it outputs a single line connecting the points.
Now, let’s turn the second input into a stream of points. The ‘Line’ node now creates 10 lines, with one line connecting each point in the second input to the single point in the first input. In general, when given sets of data as 1-d lists, the number of times a Grasshopper node will execute is determined by the length of the longest list. If one of the other input lists is shorter, its last element will be repeated so that it matches the length of the longest list. This is done to ensure that all data is used. We can see this by creating a series of points for the first input, but specifying a smaller number of points. As you can see we still get 10 lines, but the last five lines all share the last point in the shorter list.
If we make the length of the two lists equal, we still get 10 lines, with each line connecting each corresponding point in the list. The first point in the first list is connected to the first point in the second list, and so on.
But what if we want to connect each point in the first list to each point in the second? This is where trees come in — and depending on your familiarity with data trees this can be one of Grasshopper’s most powerful features or its most frustratingly confusing one.
Basically, in order for the function to map from each element of the first list to each element of the second we need to change the structure of one of the lists such that each piece of data is on a separate branch. To do this in our example we use the ‘Graft’ node, which puts each piece of data in the list on it’s own branch.
With these inputs, the ‘Line’ node will execute in the expected way, except now it will match the data at the level of branches. It will match the first branch of the first stream to the first branch of the second, and so on. As before, if one tree has less branches than the other the last branch will be repeated to match the number of branches in the longer list. In our case, the first data set has only one branch with 10 points, so it gets repeated and applied to the 10 branches of the other set. This has the effect of applying the line function from each point in one set to all points in the other, and causes the ‘Line’ node to create 100 separate lines.
You can see how a data tree is structured by hovering over the output of the ‘Line’ node. The tooltip that pops will tell you how many pieces of data are in the tree, as well as the indexes of each branch and the number of data contained in each one. You can also look at the structure by feeding the data tree into a text panel. The text panel will list each piece of data individually, but separate each branch by a darker band which contains a set of numbers giving the index or position of that branch. The first set of data only has a single branch (which is equivalent to a ‘flat’ list), whose index is 0. All ten points are stored in this one branch. Meanwhile, the second set has ten individual branches, each storing only one point.
Data trees are a powerful way to organize and work with different data structures in Grasshopper. However, they can also be very confusing and unintuitive to work with, especially when you are first starting out, or when dealing with more complex data structures. In fact, the difficulty of working with complex data structures is only one of the limitations of Grasshopper when it comes to computational design. We can actually think of five separate limitations, based on the five elements of computer programming we described previously:
- Hard to deal with variables having data structures beyond a 1-d flat list
- Can’t make arbitrary loops (only data mapping)
- Complex conditionals get messy because each one requires separate conditional and dispatch nodes
- Can’t create custom functions
- No support for classes or object-oriented programming
Luckily, we can easily extend the functionalities of Grasshopper to allow us to utilize all five elements of programming by embedding our own computer code directly within nodes in the Grasshopper document. This will be the subject of the next section.