ARCHIOLOGICS GRASSHOPPER PYTHON
PROFESOR ADOLFO NADAL SERRANO
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
INTRO TO GENERATIVE DESIGN PYTHON WITH GRASSHOPPER
[ 56 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
I. PYTHON RESOURCES 1. Rhinopython There are many resources you should consider when first looking at python code for generating geometry or handling information in rhinoceros. - Python language reference: https://docs.python.org/2/reference/ - Python documentation from the python organization https://www.python.org/doc/ - Common methods: http://www.astro.up.pt/~sousasag/Python_For_Astronomers/Python_qr.pdf - Our archiologics manual also intends to not only offer an introductory text to coding and generating geometry, but also a short reference to a series of methodologies that you might find useful in your future life as programmer: check the Rhinopython manual, by archiologics’ Adolfo Nadal: http://archiologics.com/images/stories/teaching/manuals/131019_RhinoPython1_WEB.pdf or http://issuu.com/adolfo.nadal/docs/131019_rhinopython1 - Rhinopython or GhPython script library (work in progress): http://archiologics.com/2011-10-20-15-32-52/python/rhinopython or http://archiologics.com/2011-10-20-15-32-52/python/ghpython. - You can find more manuals in http://blog.rhino3d.com/2014/12/grasshopper-and-python-manuals-in.html 2. Grasshopper python There are series of nice onine teaching resources that you might find useful. I personally recommend you to look at the following: - Archiologics GhPython script resources: on http://archiologics.com/2011-10-20-15-32-52/python/ghpython you will find several articles regarding recurssion, geometry, and others (under construction) - ATLV: please follow either their grasshopper contents in http://atlv.org/education/grasshopper/#9 or their GhPython http://atlv.org/education/ghpython/ page. It is a great online resource for both beginners and intermediate users. II. PREREQUISITES 1. Python Install python from https://www.python.org/downloads/ 2. Grasshopper python/GhPython You need to login to Food4Rhino, download GhPython, and install it following the regular steps. You will then need to copy the *.gha file into the “special folders>components folder” folder that you migh have setup in grasshopper. GhPython will be already fully integrated in Grasshopper 1, when Rhino 6 will be released (McNeel states that it will be released “soon”, but does not confirm any date). As you can read on the GhPython Food4Rhino page -http://www.food4rhino.com/project/ghpython?etx -, GhPython is a tool “for designers who want to use the same flexible language everywhere, GhPython is the Python interpreter component for Grasshopper that allows to execute dynamic scripts of any type. Unlike other scripting components, GhPython allows to use the rhinoscriptsyntax to start scripting without needing to be a programmer. Once on-board and with some practice, you can also get the most of external Python and .Net modules and libraries. This component is open-source, and works in Rhino 5. Join this group to receive updates of new versions, and visit the Grasshopper forum for support.
To install:
In Grasshopper, choose File > Special Folders > Components folder. Save the gha file there. Right-click the file > Properties > make sure there is no “blocked” text Restart Rhino and Grasshopper”
As of today, less than 19000 downloads have been reported. You will then belong to a very exclusive group of developers, designers, and programmers.
[ 58 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
III. STARTING TO USE GHPYTHON 1. Interface GhPython’s interface is quite similar to the rhinopython text editor, having, to say the truth, less functionality than the native python editor -which includes, if you remember, a nice link to the available library methods. In GhPython, you will need to know the functions by heart, or, in most cases, look at the reference from within the rhinoscript editor.
Editor Component User input
Text output Output window
Options panel User output
Script test button (reload)
OK button
Close button
[Fig 47. GhPython Interface]
You will write your code in the editor window. In order to display the editor, either double click on the component, or choose the Open Editor option from the component dialog box. As we said before, the editor window is quite rudimentary, so you will need to have some prior experience before starting a script from scratch. b. Component items Inputs: just as any other grasshopper component, GhPython components have inputs and outputs, the only difference is that these are not defined by default. You need to set the input type, name, and what to do with the data inside the editor. Also, make sure that the input name and type match those used in your code, otherwise, it will simply not work. In other words, before starting your code: - Make sure you name your inputs correctly according to stardard naming conventions (ie, use no blank spaces) - Make sure you choose the correct data type (a number must be a number, not a string) under the available ones in the data hint option of the input item menu (see below) - Make sure you choose the right access type from the following options: · Item Access: single item, recommended for single objects or inputs, easily controllable from within the code as wll -it behaves as a variable. · List Access: list, one dimensional. Recomended for collections of objects, easily controllable from within the code. List items are refernced using [index] in python. You can find more information on python lists on this webside: http:// effbot.org/zone/python-list.htm · Tree Access: grasshopper-like data tree structure consisting of a recursive branching organization. Not recommended if you can avoid it. Also, you will need to be familiar with the Rhino Common library (see http://4.rhino3d.com/5/rhinocommon/ for further reference), and should probably have some experience with Open Nurbs in order to fully understand how to deal with
[ 59 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
geometry. Any experience in programming will be very much appreciated -you will find lots of help on https://github.com/ mcneel/rhinocommon. Outputs: as opposed to inputs, outputs will be primarily defined from within the script, and will store any information you want grasshopper to take into account as “bakable”. Of course, you can use as many variables as you want, but only those that appear as outputs in the component will be traceable in future components. Probably you have noticed the “out” component that appears as a default in the grasshopper GhPython component. This output is there to show you any possible command-line like outputs, messages, and other debugging information you might find useful for your own purposes. It should always be there. Conect it to a panel component to maximize its potential. Outputs must be declared inside the script, and might be of any data type (which you will decide yourself ) and have any valid name. Also, they can be lists and hold any amount of objects or sub-lists. In order for grasshopper to read these variables, you will need to define them globally (outside of any def or such blocks).
Context menu
basic data types Access types vectors/ranges lines native grasshopper geometry types
Type hint selector [Fig 48. GhPython Interface: input options]
[ 60 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
Use this snippet for single-value variables: #import.... a=0 b = ““ ... ... ...
And define a, and b as outputs in your component. The name must match in order for the output to work properly (or, better yet, at all), otherwise GhPython will be “unable to comply” and will not recognize the variable as it will not find any name refering to the variable you were expecting to have defined.
You can use this snippet for lists. #import.... a = [] #declare the list at the top of the code ... ... ... a.append(object)
And then define a as an output. The output will be a list in this case (the actual data contained inside the list will depend on what you do with it and on the elements that you append to the list)., and you need to take that into account when dealing with your grasshopper definition. c. Executing code Executing python code from the component is very simple. If you are not editing the component, the code will be updated automatically as the inputs change, as in any other grasshopper component. While editing code, you can always run the script by pressing the test button on the lower left corner of the editor interface. This will simple re-run the code, showing any possible issues or errors in the output window located at the bottom of the editor window (see fig. 47). Python is not significantly slower than any other language, nor it is to run code in GhPython components. If you feel your component is too slow, you might want to check the logic behind your code: your algorithm might be performing too many operations. Also, take into account that grasshopper displays grometry dinamically, which is a very heavy processing operation. Thus, be careful when using recursive algorithms that multiply your geometry exponencially, and set min and max values for your sliders that ensure the most water-tightness for your script. d. A simple example: hints The following example contains all necessary information that must be included in any python script that you want to execute in grasshopper. The script consists of the following blocks: - Imports: needed in order for commands to work. - Global declarations: where the output variables need to be declared and initialized - Definitions (functions) that you use in your code - Calls to definitions and sequential code #script written by Adolfo Nadal 1 #import rhinoscriptsyntax library 2 import rhinoscriptsyntax as rss 3 #IF NEEDED import also rhino.geometry>Nurbs, 3dPoints.... native grasshopper geometry 4 import Rhino.Geometry as rg 5
[ 61 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
print(nrPuntos) print(nrPuntos1) print(nrPuntos2) #create list for the output with THE SAME NAME AS THE OUTPUT IN THE COMPONENT #this is an empty list > you need to add (append) objects to it puntos = [] #execute for i in range(nrPuntos): for j in range(nrPuntos1): for k in range(nrPuntos2): x=i y=j z=k #calculate pt = rg.Point3d(x,y,z) puntos.append(pt)
Let us analyze how to proceed: - Component preparation: first you need to make sure that you have the proper inputs. These wil, in the present code, control how many points you will create. Zoom in into the component and modify/add inputs untile they are as follows: · nrPuntos: integer type, item access. This variable will control the number of points in x. You need to connect it to a integer-like panel, number slider, or similar. · nrPuntos1: integer type, item access. This variable will control the number of points in y. You need to connect it to a integer-like panel, number slider, or similar. · nrPuntos2: integer type, item access. This variable will control the number of points in z. You need to connect it to a integer-like panel, number slider, or similar. Once you have defined the inputs as stated above, please proceed to define an output named “puntos”. Please make sure that you spell the names correctly, python is a case-sensitive language. - Once you have organized your component, we can see what the code does. Please note that there are no definitions or functions (in python, “defs”) in this particular piece of code -it is way too simple and it is not necessary. · Lines 1-5: imports and comments. In this case, we will simple import the rhino geometry library, which allows us to access geometry-creation methods. We will only use the method Point3d(x,y,z) from the rg (rhinogeometry library) to create grasshopper points at the specified (x,y,z) location, being x,y,z, floating-point numbers. But for now, just note that you import libraries using the “import” keyword, and that you assign a reference name by using the “as” keyword. Further reference to the library in the code will make use of that name, in this case, “rg”. · Lines 6-8 are completely unnecessary as far as the actual algorithm is concerned, these lines print the values of the different inputs just to make sure they are correct. · Line 12 declares and initializes the list that will contain our set of points. In python, you can declare and initialize an empty list by assigning the “[]” to a variable name. In order for the points to appear on the screen, we need to fill this list with point objects, appending them to the list. Also, the output name of the component must match the one in the code (in this case, “puntos”). · Lines 16-25 execute the code. 3 nested loops create a “box” of 3d points (and associates the variables i, j, and k -counters- to the actual value of the point’s x, y, and z coordinates). As you can see, the value i is used for x (line 19), the value j (line 20) is used for y, and the value k (line 21) for z. The maximum value for those variables will be, respectively, nrPuntos, nrPuntos1, and nrPuntos2. Line 24 adds the point object to the grasshopper geometry and stores it temporarily in side the pt variable. Line 25 appends the point stored in pt to the puntos list, making it accessible to the grasshopper definition.
[ 62 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
[Fig 49. GhPython Point Cube definition]
[Fig 50. Point cube -baked with color to the right]
If you feel confortable with the code, try the one below. Besides creating the cube of points we have just seen, it expands grasshopper python’s ability to bake geometry with properties, modifying the object’s color according to the actual x,y,z values of the points. Furthermore, it allows you to activate or deactivate the calculation with a boolean toggle component. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
[ 63 ]
#script written by Adolfo Nadal #import rhinoscriptsyntax library import rhinoscriptsyntax as rss #IF NEEDED import also rhino.geometry>Nurbs, 3dPoints.... native grasshopper geometry import Rhino.Geometry as rg #import these to MODIFY an object attibute when baking #sc allows us to ADD objects to the model ->simulate a bake #rd allows us to OBTAIN properties #sd to “print” color #Rhino to get the current document import scriptcontext as sc import Rhino.DocObjects as rd import System.Drawing as sd import Rhino print(nrPuntos) print(nrPuntos1) print(nrPuntos2)
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
#create list for the output with THE SAME NAME AS THE OUTPUT IN THE COMPONENT #this is an empty list > you need to add (append) objects to it puntos = [] sc.doc = Rhino.RhinoDoc.ActiveDoc #execute only if calculate is on (True) if (calculate==True): for i in range(nrPuntos): for j in range(nrPuntos1): for k in range(nrPuntos2): x=i y=j z=k #calculate pt = rg.Point3d(x,y,z) puntos.append(pt) #use rg when changing object attributes, otherwise fine if(bake==True): #add with attributes ->color r = 255*(i/nrPuntos) g=0 b = 255-255*(k/nrPuntos2) if(r>255) : r = 255 if(b<0): b=0 #obtain attributes attr = rd.ObjectAttributes() attr.ColorSource = rd.ObjectColorSource.ColorFromObject attr.ObjectColor = sd.Color.FromArgb(r,g,b) sc.doc.Objects.AddPoint(pt,attr)
- There are some notable differences between both versions of the code. Here you have them line by line. · Lines 1-14: imports and comments. This extended version requires more libraries, which are commented in the code. Besides the rg (rhinogeometry library), we need to import the current doc, the rhinoDoc library, and the system drawing library, which we will use in order to modify the object’s attributes. You can see that all these libraries are references throughout the code by their names rd (RhinoDoc), sc (Script Context),and sd (System.Drawing). · Lines 16-18 are completely unnecessary as far as the actual algorithm is concerned, these lines print the values of the different inputs just to make sure they are correct. · Line 21 declares and initializes the list that will contain our set of points. Please read the previous version for further information. · Line 22 stores the current active doc inside a variable, “sc.doc” · Line 25 makes sure that the script is only executed if the “calculate” value is set to True. · Lines 26-52 execute the main part of the code where the magic happens. Line 26 to 36 are the same as in the previous example, so you should know them already. Line 38 controls the execution of lines 39 to 52 depending on the value of the variable bake, which must be set to true if you want to bake your geometry. Lines 39 to 46 calculate the RGB values according to the position of the points, and lines 49 to 51 assign those values as attributes of the point objects that are baked in line 52. More precisely, line 49 stores the ObjectAttributes object in the rhino document in the variable attr. Line 50 sets the color source to the color from object, and line 51 ultimately assigns the calculated r,g,b values to the object color. Line 52
[ 64 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
bakes the object with the given attributes.
[Fig 51. Point cube definition -note the calculation and bake toggles]
IV. OTHER GHPYTHON EXAMPLES Once you have been exposed to the intricacies of GhPython, it is a matter of time that you become a proficient user. Let us just illustrate you with a couple of examples that might get you there while providing some fun. 1. Recursive tree One of the most prominent limitations of grasshopper lies in its parametric nature. While being a very good tool for associating parameters and data, grasshopper is still quite a linear tool, executing the logic in a one-way-only manner. In other words, it is difficult to manage loops, conditionals, and other flow-control structures without scripting. Furthermore, it is simply not possible to use recursive operations using the default provided tools. It is possible, though, to create an easy implementation of a recursive algorithm -one that basically defines a function that calls itself with a certain control operation that prevents it from being called an infinite amount of times. The following example creates a simple 2d tree through lines. From a single point, two lines -representing branches- are created. The end point of the lines will be taken as starting point for the algorithm, which in turns creates 2n branches (being n the branch level). If you do not fully understand the logic of the branching, there are numerous sources online that explain this problem. The algorithm, therefore, consists of the following steps: - Pick a point - Draw two lines from that point - Rotate the lines slightly within a certain range by a random angle - Scale the lines with a certain scale factor (less than 1) - Get the end point of each of the recently created lines - Repeat the whole process, consider these end points the starting points of the aboce described steps. Since 2 branches are created per point in every iteration or level, the first iteration 2 branches are created, the second has 4 branches, the third 8 branches, and so on. As said above, the growth rate is 2n (being n the branch level). Be careful, since only 10 levels produce 1024 branches. You need to implement a control to prevent your algorithm from having too many levels. As an additional measure, you can also decide whether branches are created following a random value, this is, that some branches pop out while some others do not. We will leave this for your personal implementation. #you can also see http://atlv.org/education/ghpython/ for material properties 1 #import rhinoscriptsyntax to perform operations 2 #import rhino geometry to add geometry 3 import rhinoscriptsyntax as rss 4 import Rhino.Geometry as rg 5 import random 6 7 8 9
[ 65 ]
#import libraries that allow us to add objects to document #with changed attributes
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
import Rhino import System.Drawing as sd import Rhino.DocObjects as rd import scriptcontext as sc #define list for output #empty pts = [] vecPts = [] crvs = [] crvsTmp = [] #define a list to contain mapped values clrs = [] #obtain current document sc.doc = Rhino.RhinoDoc.ActiveDoc #definition def recBranching(line,currBranches): #object coordinates of start and end points pt1Coord = rss.CurveStartPoint(line) pt2Coord = rss.CurveEndPoint(line) #point object to DRAW pt1 = rg.Point3d(pt1Coord) pt2 = rg.Point3d(pt2Coord) #append points to the list to get the output pts.append(pt1) pts.append(pt2) #takes coordinates of the points vec1 = rss.VectorCreate(pt2Coord,pt1Coord) vec2 = rss.VectorCreate(pt2Coord,pt1Coord) #modify the vector userFactor1 = 0.8 userFactor2 = 0.6 scFactor1 = random.random()*(1-userFactor1)+(userFactor1) scFactor2 = random.random()*(1-userFactor2)+(userFactor2) vec1 = rss.VectorScale(vec1,scFactor1) vec2 = rss.VectorScale(vec2,scFactor2) rotateFactor = roFactor vec1 = rss.VectorRotate(vec1,-rotateFactor,rg.Vector3d.ZAxis) vec2 = rss.VectorRotate(vec2,rotateFactor,rg.Vector3d.ZAxis) vec1 = rss.VectorAdd(vec1,pt2Coord) vec2 = rss.VectorAdd(vec2,pt2Coord)
[ 66 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
[ 67 ]
#point object to DRAW vecPt1 = rg.Point3d(vec1) vecPt2 = rg.Point3d(vec2) vecPts.append(vecPt1) vecPts.append(vecPt2) #add the curves ptList1 = (pt2Coord,vec1) ptList2 = (pt2Coord,vec2) ptGeoList1 = (rg.Point3d(pt2Coord),rg.Point3d(vec1)) ptGeoList2 = (rg.Point3d(pt2Coord),rg.Point3d(vec2)) crv1 = rg.NurbsCurve.CreateControlPointCurve(ptGeoList1,1) crv2 = rg.NurbsCurve.CreateControlPointCurve(ptGeoList2,1) #crv1 = rss.AddCurve(ptList1,1) #crv2 = rss.AddCurve(ptList2,1) #add curves to output (fill the list) crvs.append(crv1) crvs.append(crv2) #implement control currLen1 = rss.CurveLength(crv1) currLen2 = rss.CurveLength(crv2) if((minLen<currLen1)and(currBranches>1)): recBranching(crv1,currBranches-1) if((minLen<currLen2)and(currBranches>1)): recBranching(crv2,currBranches-1) #calculate the minimum length of a list of curves def calculateMinLen(inCurves): minLen = 10000000 for i in range(len(inCurves)): currCurve = inCurves[i] currLen = rss.CurveLength(currCurve) if(currLen<minLen): minLen = currLen return minLen #calculate the maximum length of a list of curves def calculateMaxLen(inCurves): maxLen = 0 for i in range(len(inCurves)): currCurve = inCurves[i] currLen = rss.CurveLength(currCurve) if(currLen>maxLen): maxLen = currLen return maxLen
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
#map value between a given IN range and return in a given OUT range def mapValues(val,minRefIn,maxRefIn,minRefOut,maxRefOut): if((val>=minRefIn)and(val<=maxRefIn)): lenIn = maxRefIn-minRefIn lenOut = maxRefOut-minRefOut outVal = ((val-minRefIn)/lenIn)*lenOut+minRefOut else: return None return outVal def calculateColor(crvs): minBrLen = calculateMinLen(crvs) print (“The minimum length is “ + str(minBrLen)) maxBrLen = calculateMaxLen(crvs) print (“The maximum length is “ + str(maxBrLen)) #calculate the color of each branch #remap the length to obtain the channel value between 0 and 255 for i in range(len(crvs)): currCurve = crvs[i] currLen = rss.CurveLength(currCurve) clr = mapValues(currLen,minBrLen,maxBrLen,0,255) clrs.append(clr) #print (clr) #call definition recBranching(userLine,nrBranches) calculateColor(crvs) if(bake==True): #bake objects with changed attributes - color #add all curves to document print (“Vamos a cocinar!!”) for i in range(len(crvs)): #add Objects to current document #get object attributes attr = rd.ObjectAttributes() attr.ColorSource = rd.ObjectColorSource.ColorFromObject attr.ObjectColor = sd.Color.FromArgb(clrs[i],0,255-clrs[i]) sc.doc.Objects.AddCurve(crvs[i],attr)
You might have noticed the amount of inputs that these script requires. As usual, you need to match the inputs of the script and the ones defined in grasshopper both in type and accessibility. Please note that we are using the boolean toggle to decide when to bake geometry, and do so with the specified and desired attributes. Let us review the code by blocks of logic: · Lines 1-13: imports and comments. This script, as it happened to the “global version” of the point cube algorithm, requires libraries to import the current doc, the rhinoDoc library, and the system drawing library, which we will use in order to modify the object’s attributes. As usual, these libraries are referenced throughout the code by their names rd (RhinoDoc), sc (Script Context),and sd (System.Drawing). · Lines 15-26 declare all outputs that we need in the script. Pay special attention to the curves, which will define the actual tree geometry. Also, define the current document and store in the variable.
[ 68 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
· Lines 28-134 contain all definitions that we need in order for this algorithm to work. Most processing is done in the recBranching definition (that is called later in line 137), and that will make use of the other definitions contained in these lines, such as calculateColor (lines 121-134), calculateMaxLen (101-109), calculateMinLen (92-99), and mapValues (112119). We will cover each definition later, once we have gone through each block. · Lines 137 and 138 call the definitions, causing the script to run · Lines 140-151bake the geometry with defined attributes (color).
Summarizing, you will find the following blocks: - Imports (lines 1-13) - Declarations (lines 15-26) - Definitions (lines 28-134) - Sequential code (lines 137-151)
Let us see the algorithm line by line. · Lines 30: function signature. The function requires a base line (called line in this case) and the number of branches so far, in order to be able to control the maximum amount of levels for the algorithm. · Lines 31-32: get the point coordinates for the start and end point of each line. · Lines 35-36 create points on those coordinates. Points created using the rhino geometry library can be added to the document and will be visible in the grasshopper preview. Be careful when using the rhinoscriptsyntax methods as they are prone to errors regarding this particular issue. def recBranching(line,currBranches): #object coordinates of start and end points pt1Coord = rss.CurveStartPoint(line) pt2Coord = rss.CurveEndPoint(line) #point object to DRAW pt1 = rg.Point3d(pt1Coord) pt2 = rg.Point3d(pt2Coord) · Lines 39-40 store the points in the point list for representation purposes. As you can see, the list pts will store all points. We have defined the list previously in line17. We then need only to append the points to the list. Quite a simple way to do it, right? #append points to the list to get the output pts.append(pt1) pts.append(pt2) · Lines 43-44 create the vectors that will be our base for the lines (branches). Using vectors instead of lines actually gives us the opportunity to modify their orientation and magnitude quite easily, without having to worry too much about actual geometry. We create two vectors per line, as you can see. #takes coordinates of the points vec1 = rss.VectorCreate(pt2Coord,pt1Coord) vec2 = rss.VectorCreate(pt2Coord,pt1Coord) · Lines 47-52 modify the size of the vectors according to predefined scale factors. You can do the same using randomized values. The consequence of assigning different values to the scaling factors is quite obvious, and affects the overall shape of the tree: one side will be significantly longer than the other (please see figure 52 if you still doubt it). Once the vector has been changed in magnitude, we can proceed to modify its orientation by rotating it a certain amount. Since vectors in Rhino are by default based on (0,0,0), there is no need for us to define a rotation center. The vector will rotate automatically around its base point (so far we have not defined one that overwrites the default, therefore it
[ 69 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
will rotate around the origin of coordinates). As we did before, we use a certain roFactor (rotation factor) to define the rotation angle. This is an input of the component (lines 54 to 56). Finally, we place the vector where it should be, adding the base point to the vector we had calculated and modified up to this point (lines 58 and 59). This results in a vector with the same orientation, direction, and magnitude, but with a correct base point. #modify the vector userFactor1 = 0.8 userFactor2 = 0.6 scFactor1 = random.random()*(1-userFactor1)+(userFactor1) scFactor2 = random.random()*(1-userFactor2)+(userFactor2) vec1 = rss.VectorScale(vec1,scFactor1) vec2 = rss.VectorScale(vec2,scFactor2) rotateFactor = roFactor vec1 = rss.VectorRotate(vec1,-rotateFactor,rg.Vector3d.ZAxis) vec2 = rss.VectorRotate(vec2,rotateFactor,rg.Vector3d.ZAxis) vec1 = rss.VectorAdd(vec1,pt2Coord) vec2 = rss.VectorAdd(vec2,pt2Coord) · Lines 60-73 add the actual branch curve objects to the model. Lines 61 and 62 store the points in the vecPt1 and vecPt2 variables, while they are appended to the vecPts list in lines 64 and 65 (again, we have defined this list as a global variable in our “declarations” block). Finally, lines 68 to 73 create the actual nurbs curve objects. We create two lists (one for each branch) that contain the start/end points of the subsequent branches in lines 70 and 71 (note that lines 68 and 69 are not necessary). We then use this temporary lists in order to feed the rg.NurbsCurve.CreateControlPointCurve(listOfPoints,degree) method, which requires a list of points and a number indicating the degree of the curve. Note that this method belongs to the rhino geometry library (imported above and named rg) and that the rg library has the NurbsCurve object which itself contains the CreateControlPointCurve method.
#point object to DRAW vecPt1 = rg.Point3d(vec1) vecPt2 = rg.Point3d(vec2) vecPts.append(vecPt1) vecPts.append(vecPt2) #add the curves ptList1 = (pt2Coord,vec1) ptList2 = (pt2Coord,vec2) ptGeoList1 = (rg.Point3d(pt2Coord),rg.Point3d(vec1)) ptGeoList2 = (rg.Point3d(pt2Coord),rg.Point3d(vec2)) crv1 = rg.NurbsCurve.CreateControlPointCurve(ptGeoList1,1) crv2 = rg.NurbsCurve.CreateControlPointCurve(ptGeoList2,1) #crv1 = rss.AddCurve(ptList1,1) #crv2 = rss.AddCurve(ptList2,1)
· Lines 78-79 append these curves to our curve list, so that they can become actual rhino geometry when the component is baked. #add curves to output (fill the list)
[ 70 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
crvs.append(crv1) crvs.append(crv2) · Lines 81-89 do the recursion trick. Here we will be calling the function recBranching from within the function itself. In order to prevent possible infinite looping mistakes, we should implement some controls. In this case, we will only continue if the branches are long enough for us, and if we have not exceeded the maximum amount of branches. As you can see, the conditionals use two conditions: The first condition checks whether the branch length is higher than the minimum desired length. The second checks whether we have reached the maximum amount of branches, In fact, we start by passing the total amount, and reduce them by one in every step. #implement control currLen1 = rss.CurveLength(crv1) currLen2 = rss.CurveLength(crv2) if((minLen<currLen1)and(currBranches>1)): recBranching(crv1,currBranches-1) if((minLen<currLen2)and(currBranches>1)): recBranching(crv2,currBranches-1) The rest of the script is quite straight forward and requires no further explanation. Do you think you would be capable of creating a 3D tree?
[Fig 52 a. Recursive - grasshopper definition]
[ 71 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
[Fig 52 b. Recursive tree -color grading and grasshopper geometry]
2. Surface subdivision The last example we are going to provide in this particular series has to do with surface geometry. This is not a particularly interesting example, but it explains surface rationalization issues quite clearly. You can see more rhinopython and ghPython examples visiting our page http://archiologics.com/2011-10-20-15-32-52/python/rhinopython or http://archiologics.com/2011-10-20-15-32-52/python/ghpython.
The script creates a series of components on the surface. This is the code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
import rhinoscriptsyntax as rss import Rhino.Geometry as rg #everything that I want my component to recognize OUTSIDE THE DEFINITIONS (functions) #point list to store points on surface #empty list pointsSrf = [] baseSrfs = [] ptsNorm = [] tabSrfs = [] compPolys = [] def calcNormalWithSize(srf,uParam,vParam,tabSize,ptBase): ptNorm = rss.SurfaceNormal(srf,(uParam,vParam)) ptNorm = rss.VectorScale(ptNorm,tabSize) ptNorm = rss.VectorAdd(ptNorm,ptBase) return ptNorm def panelize(): #populate the surface with points #-surface domain domU = rss.SurfaceDomain(srf,0) domV = rss.SurfaceDomain(srf,1) print (domU) print (domV)
[ 72 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
[ 73 ]
#enter variables that contain surface domain #max will be the second value, therefore [1] domUMax = domU[1] domUMin = domU[0] domVMax = domV[1] domVMin = domV[0] #calculate the stepSize stepU = (domUMax-domUMin)/divU stepV = (domVMax-domVMin)/divV for i in range(divU+1): for j in range(divV+1): if((i>0)and(j>0)): #-evaluate surface uParam = i*stepU vParam = j*stepV pt1Coord = rss.EvaluateSurface(srf,uParam,vParam) pt2Coord = rss.EvaluateSurface(srf,uParam-stepU,vParam) pt3Coord = rss.EvaluateSurface(srf,uParam-stepU,vParam-stepV) pt4Coord = rss.EvaluateSurface(srf,uParam,vParam-stepV) #obtain middle points ptMid12Coord = rss.EvaluateSurface(srf,uParam-stepU/2,vParam) ptMid23Coord = rss.EvaluateSurface(srf,uParam-stepU,vParam-stepV/2) ptMid34Coord = rss.EvaluateSurface(srf,uParam-stepU/2,vParam-stepV) ptMid41Coord = rss.EvaluateSurface(srf,uParam,vParam-stepV/2) ptListTemp = (pt1Coord,pt2Coord,pt3Coord,pt4Coord) pointsSrf.extend(ptListTemp) #use the rg for these WEIRD cases #basePts = (pt1Coord,pt2Coord,pt3Coord,pt4Coord) baseSrf = rg.NurbsSurface.CreateFromCorners(pt1Coord,pt2Coord,pt3Coord,pt4Coord) baseSrfs.append(baseSrf ) #calculate normals pt1NCoord = calcNormalWithSize(srf,uParam,vParam,tabSize,pt1Coord) ptMidN12Coord = calcNormalWithSize(srf,uParam-stepU/2,vParam,tabSize,ptMid12Coord) ptMidN23Coord = calcNormalWithSize(srf,uParam-stepU,vParam-stepV/2,tabSize,ptMid23Coord) ptMidN34Coord = calcNormalWithSize(srf,uParam-stepU/2,vParam-stepV,tabSize,ptMid34Coord) ptMidN41Coord = calcNormalWithSize(srf,uParam,vParam-stepV/2,tabSize,ptMid41Coord) #turn the normals into actual point pt1N = rg.Point3d(pt1NCoord) ptMidN12 = rg.Point3d(ptMidN12Coord) ptMidN23 = rg.Point3d(ptMidN23Coord) ptMidN34= rg.Point3d(ptMidN34Coord) ptMidN41 = rg.Point3d(ptMidN41Coord) ptsNorm.extend((ptMidN12,ptMidN23,ptMidN34,ptMidN41))
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
#create tabs - > use rg because we had problems tab1Srf = rg.NurbsSurface.CreateFromCorners(pt1Coord,pt2Coord,ptMidN12) tab2Srf = rg.NurbsSurface.CreateFromCorners(pt2Coord,pt3Coord,ptMidN23) tab3Srf = rg.NurbsSurface.CreateFromCorners(pt3Coord,pt4Coord,ptMidN34) tab4Srf = rg.NurbsSurface.CreateFromCorners(pt4Coord,pt1Coord,ptMidN41) tabSrfs.extend((tab1Srf,tab2Srf,tab3Srf,tab4Srf )) #create polysurfaces tab1Tmp = rg.Brep.CreateFromSurface(tab1Srf ) tab2Tmp = rg.Brep.CreateFromSurface(tab2Srf ) tab3Tmp = rg.Brep.CreateFromSurface(tab3Srf ) tab4Tmp = rg.Brep.CreateFromSurface(tab4Srf ) baseTmp = rg.Brep.CreateFromSurface(baseSrf ) tabSrfsTmp = (tab1Tmp,tab2Tmp,tab3Tmp,tab4Tmp,baseTmp) #rss.JoinSurfaces(tabSrfsTmp,False) #JoinBreps returns an array (list) with a single object inside #we obtain it by adding [0] which refers to the first object in the list #this way we store a single object inside polyTmp polyTmp = rg.Brep.JoinBreps(tabSrfsTmp,0.001)[0] compPolys.append(polyTmp) #call function to be executed if (calculate==True): if(flip==True): tabSize = -tabSize print (“Calling panelize”) panelize() if (calculate==False): print (“I will do nothing”)
The script follows the same overall structure we have explained above (imports, declarations, definitions, sequential code). We will briefly explain the code, although you can find numerous sources on the internet that explain surface panelization. Let us review the most relevant lines of code: · Lines 19-101 contain the panelization definition. Although it is thoroughly explained with comments in the code, it is worth pointing out the process. Lines 22 and 23 calculate the surface domain in both directions U, and V. we will navigate the surface by small steps, so we need to obtain the number of steps in order to calculate the step size. This follows in lines 29 through 36. Once we do that, we proceed to “walk” the surface with two loops (one for each direction, please refer to the point cube example). The surface is 2D geometry, so we need only 2 loops. Lines 42 and 43 store the parameter on the surface for the current step. We use these pair of parameters in lines 44 to 47 to calculate the point coordinates on the surface, and 50 to 53 lines show the same for the mid points (points between the other ones). Line 55 stores those points in a list. We will use this list to create the components, especially if you deal with polyline-generation or surface-through-points methods. We then extend the ptListTmp with this list. The list.extend() method appends each of the elements of the list to the base list one by one. Lines 61 and 62 add a surface from corners to the model using the four defined corners. It then appends this surface to the surface list, that we will output as geometry to the rhino model. Lines 65 to 69 calculate the normals at each of the middle points using a scale factor for the tab size. In fact,
[ 74 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
they call the calcNormalWithSize definition. Once again, we need to turn those coordinates into actual points (geometry objects), and we do so in lines 72 to 76. The obtained points are stored in the ptsNorm list in line 78. Using all points, we can create the tabs. The tabs are created using the same surface creation method we have described above (from corners), which you can read in lines 80 to 83, and stored in line 84. The actual polysurfaces must be rhino geometry objects. We will create breps from the previous surfaces -lines 87 to 90. Finally, it is desirable to join all breps together into a single, open object -line 99. Add this brep to the brep list in line 101. · Line 103-111 controls the execution through the value of the calculate variable that you can access through the boolean toggle.
[Fig 53 a. Surface component script: geometry]
[ 75 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
[Fig 53 b. Surface component definition]
[ 76 ]
ARCHIOLOGICS DISEÑO PARAMÉTRICO EXPLÍCITO CON GRASSHOPPER ADOLFO NADAL SERRANO MAS. ARCHITECT - ARCHI [O] LOGICS
[ 77 ]