COMPUTER AIDED ARCHITECTURAL DESIGN
Workshop
9 Notes, Week of November 4, 2013

INTRODUCTION TO PYTHON SCRIPT COMPONENTS INSIDE GRASSHOPPER

1. Hello World and Simple Python Scripts

In some circumstances it may be more effective to use a Python scripting component in conjunction with other Grasshopper components. We begin with a few simple Python scripts. These first  scripts are for introductory purposes and don't necessarily offer an advantage over other Grasshopper components. We will then move onto Python scripts which offer advantages over other Grasshopper options in terms of conciseness and clarity of organization. Within these examples we take advantage of libraries and related function calls to perform some tasks, such as:

a = rs.AddLine(point1, point2)   


This expression defines a line "a" from the rhinoscript library abbreviated as "rs" and the procedure "AddLine" which creates a line from two points. More complete documentation on Python, examples, and associated libraries are located at the web site http://wiki.mcneel.com/developer/python. On the web site, the manual http://download.rhino3d.com/IronPython/5.0/RhinoPython101/ provides a comprehensive introdexample to establish a basic understanding .  Launch Rhino and Grasshopper, and load the file helloWorld.gh.  The file shows two Python scripts, one that echoes to text strings and the other that adds two numbers.

First, we demonstrate a script that outputs the text string "Hello World!". This follows a tradition of introducing programming languages with a "Hello World!"  back the text string "Hello World!" and a second that returns the sum of two numbers.

hello world

Note that the input text strings "Hello" and "World" are connected to two "Str" (string) components. The two "Str" components are in turn connected to the Python script input parameters "x" and "y".  The Python script in turn returns the parameter "A", which is the text string "Hello World!".

If you modify either of the input text strings, then the output text string is modified accordingly, such as replacing the text string "World" with the text string "Virginia" in the example below:

hello virginia

To see the Python Script in detail, double click on "VB" icon in the Grasshopper window to launch the script editor. 

hello world script editor

Note that the two input arguments "x" and "y" are passed into the Python script component. The "String" designation  means that both "x" and "y" are a text expression of alphanumeric characters. Note too that any change to the value of an input variable (such as "x" or "y")  inside the Python Script component  is limited in scope to be internal to the script function.  That is, the modification to the value of the variable internal to script function would not change its value outside the script.  Thus if we added the line to the above script   y = "virginia film festival". The value of x outside of the script would be unchanged from x = "Virginia".  


Lets break down the statements on lines 94 and 95 in the Script Editor above.

a = str(x + " " + y + "!")

The statement assigns a string value to variable "a" by joining together 1. the value of the string x, 2. a blank space " ", 3. the string y, and 4. the exclamation point "!". Note that the use of the "+" symbol is used to add together (concatenate) the larger string from the the strings "Hello", " ", World, "!", similar to how one might add several numbers together.


The variable "a" is also the output variable of the script. Thus, it in turn is directed to the input port of the yellow panel that displays the result "Hello World!".

Now we will reconstruct the script from scratch. Go to File/New Script. Go to the "Params" tab  and in the "Input" area, select the yellow panel.

yellow panel

Drag two yellow panels with the left-mouse button into the Grasshopper view window.

two yellow panels

Double click on each panel, and edit the text string  to become "Hello" on on panel and to become  "World" on  the other panel.

hello panel

The result is that the panels should transform as follows.

yellow panels modified

Continuing with  the "Params" tab, go to the  "Primitive" area and select the string component symbolized by the letter "A".

select string component from paramaters tabl


Drag two string components with the left-mouse button into the Grasshopper view window, and connect their input ports to the output ports of the two yellow panels.


connect string components to yellow panels

Now go the the "Math" tab and in the "Input" area, select the blue and yello P.

pick script

Drag on Python component  with the left-mouse button into the Grasshopper view window and connect its  input ports to the output ports of the two string components. Resize the yellow panels by moving the mouse over any of its edges, depressing the left-mouse button, and dragging the edge to the desired size.



Zoom into the Python component ,  right-click on the letter "x", go the the "Type hint" item, and change the type to "String". Do the same for the letter "y" variable.


Now, double-click on the Python component symbol in order to open the editor. Note that it is essentially an emptry text file.

new editor

Type in the single expression needed to complete the script.

a = str(x + " " + y + "!")

hello world script editor


Select the "OK" button on the lower right hand corner of the script editor to conclude editing the script. Go back to the "Params" tab and to  "Input" area, select the yellow panel , add it to the right of the Python script within the Grasshopper view window.

add panel

Connect the output port "a" of the Python script to the yellow panel to see the output.

connect panel

Note that it is generally quite useful to document the purpose, input and output functions of the Python script with comment lines. Comment lines are not executed and are preceded by a number symbol # (e.g., #This is a comment line).   For example, double-click on the Python component, and add the comment line

#Returns "Hello World!
#INPUT: string x "hello" and y "world"
#OUTPUT: a concatenates x and y


add comment line

The Python script to construct two numbers is constructed in a similar way. Go to the "Params" tab and from the "Input" and a number slider.

number slider

Drag two number sliders into the Grasshopper view window. Now, similar to the "Hello World" example, go to the Math "Tab" and from the "Script" area drag Python component  with the left-mouse button into the Grasshopper view window and connect its input ports to the output ports of the two number slider components.



Zoom into the Python component ,  right-click on the letter "x", go the the "Type hint" item, and change the type to "Double". Do the same for the letter "y" variable. Double click on the Python symbol in order to open the editor. Note once again that the editor
is essentially an empty file.

new editor


The following comment lines and expressions are now added to complete the script and return the result in the variable a.

#Adds Two Numbers
#INPUT: numbers x and y
#OUTPUT: sum of x and y


a = x + y

script to add two nums


Finally,
select the "OK" button on the lower right hand corner of the script editor to conclude editing the script. Go back to the "Params" tab and to  "Input" area, select the yellow panel , add it to the right of the Python script within the Grasshopper view window. Connect the output port "A" of the script to the yellow panel to obtain the final result of the script.

connect to panel

2. Scripts to Draw a Few Graphic Primitives

Within the previous set of Grasshopper workshop notes (workshop 8), we covered how to generate a line from two points. Here we perform the same task by using a Python script to produce a grasshopper file makeSimpleLine.gh.

Initiate the script within Rhino by creating two points in the x, y ground plane. 

two points in plan

Within Grasshopper, go to the "Params" tab and within the "Geometry" area, select and drag two point icons into the work area.

add point params to grasshopper window

Right-click on each of the two points and use the "set on point" option to connect them to the points created on the x, y plane.

two point inside GH window

Next, similarly to "Hello World" example above, go the "Math" tab and within the "Script" area drag a "Python" component into the Grasshopper View window.

pick script

Connect the the points to the " x"  and "y"  input ports of the Python script component. Zoom up to "x" variable, right-click on it and in the top line of the dialog box that follows, change the name to " point1".  Right click on "point1" again and change the Type hint option to "Point3d".  Similarly, rename the "y" variable to be "point2" and change the Type hint option to "Point3d"

 change input names

Double-click on "Python" of the Python scripting component,  go to the script editor, and add the following expressions:

Add the following lines to load relevant software libraries

import rhinoscriptsyntax as rs
import Rhino as rc
import math

Add the following comment lines at the beginning of the script:

 #Draws a linefrom two points
 #sINPUTS: Two points
 #OUTPUT: A line determined by the two points

Add the following statements after the comment lines:

a = rs.AddLine(point1, point2)
  

Within the script editor we see the following result:

 draw line script

This completes the script and resulting line should now appear inside Rhino.

two points determine a line

Move either of the two points and the line is modified accordingly. 

move point to redetermine line

The same script can be modified to draw a rectangle, makeSimpleRectangle.gh,  from the initial end points. 

First, note that we can extract the coordinates x1, y1, z1 from point1 and the coordinate x2, y2, and z2 from point2. We can combine the two sets of coordinate  to get the four points cornerPt1, cornerPt2, cornerPt3 and cornerPt4, that  detemine the rectangle. Assuming that both point1 and point2 are in the x-y plane, also note that z1 = z2. That is, we have the following way to establish the values of the four corner points. 

cornerPt1 - based upon  x1, y1, z1
cornerPt2 - based upon x2, y1,  z1
cornerPt3 - based upon x2, y2,  z1
cornerPt4 - based upon x1, y2,  z1.

With this in mind, we can edit the Python script as indicated below to get a rectangle. Note that comment lines added to the script describe what is achieved by each step.  

#'Draws a rectangle from two points
#'INPUTS: Two points
#OUTPUT: A polyline (rectangle) determined by the two points    


   
 #Create the corner points from the x, y, and z coordinates of point1 and point2
 cpt1 = rc.Geometry.Point3d(point1.X, point1.Y, point1.Z)
 cpt2 = rc.Geometry.Point3d(point2.X, point1.Y, point1.Z)
 cpt3 = rc.Geometry.Point3d(point2.X, point2.Y, point1.Z)
 cpt4 = rc.Geometry.Point3d(point1.X, point2.Y, point1.Z)

#Add the first corner point from the x, y, and z coordinates of point1 to complete the polyline
 cpt5 = rc.Geometry.Point3d(point1.X, point1.Y, point1.Z)
   
   
  #Create the polyline from the list of points
  line1 = rs.AddPolyline([cpt1, cpt2, cpt3, cpt4, cpt5])
   
   #Return the polyline from the Python script
  a = line1

The script editor is now modified as follows.

python script window

The script as now completed draws a rectangle. Note that moving the lower left corner point from within Rhino dynamically redraws the rectangle.

draw rectangle from two points

Looking at this script in greater detail, observe that we have embedded a point list [cpt1, cpt2, cpt3, cpt4, cpt5] withing the statement

line1 = rs.AddPolyline([cpt1, cpt2, cpt3, cpt4, cpt5])

We add the  lower-left point to both the beginning and the end of the list to ensure that we get a closed polyline..

Note too that we introduced a polyline object variable with the statement line1 = rs.AddPolyline

The polyline object line1 is thus initialized with the listing of points. 

3. Script That Uses Iteration to Express Recurring Relationship

To draw a series of inwardly rotating polylines makeInwardlyTurningRectangle.gh from the outer rectangle, with each new rectangle in the series stepping upward in elevation, we need to repeat a similar operation a specified number of times. 

series of inner rotating polylines

Note that in the figure above, each inner polyline is calculated from point that is 40% along along each of the four  sides of the next outer polyline. In the Python script used to generate this figure, a technique referred to as iteration is used to repeat the same process a given number of times. A number of such iterative techniques exist within the Python scripting language. Within the current example, we use a "while" expression that loops continuously through a series of indented expressions during the time that some integer " i"  increments in value from 1 to the number times we  we want to repeat loop. In "pseudo-code" (not Python actual code, but more general words), the "while" statement has the following format :

while (i <= iterations) :
.... Create a new polyline from the four points
.... Add the polyline to a list of polylines
....Calculate four points of each new polyline     

Note that the "while" statement only  includes the indented statements which follow it.

First, lets begin by adding to new parameters to the Grasshopper Script from Part 3. Go to the "Params" tab and in the "Input" area add two numerical sliders to the Grasshopper view window.

add numerical slider

Double-click on the word "slider" and rename the numerical sliders iterations,  param Dist, and  stepSize. Adjust the iterations slider so that it ranges in value from 0 to 20, and adjust the type to"N" (integer numbers).The paramDist slider by default ranges in value from 0.0 to 1.0, which we can use to represent 0% to 100% of the distance along any side of the last outer polygon. Similarly, the stepSize slider by default ranges in value from 0.0 to 1.0, which we can use to represent the elevation change of each inner rectangle above the outer rectangle that preceded it..

add number sliders

Zoom up on the  Python script symbol until "+" symbols appear below the input variables "point1" and "point2".

zoom up on Python script

Select the "+" symbol below  "point2" to add a variable "paramDist".  Change the default name of "x" to "paramDist" and change the Type hint option to "Double". Similarly, select the "+" symbol below  "paramDist".  Change the default name of "x" to "numTimes" and change the Type hint option to "Integer".  

two variables added to input of Python script


Next, connect the output ports of the two numerical sliders to the new input variables of the Python script.

connect new parameters to Python script

Now, to create the series of rectangles we edit the Python script to incorporate the following text. Comment lines preceded by a # describe what each step of the Python script does.

import rhinoscriptsyntax as rs
import Rhino as rc
import math
#Draws a series of rotating rectangles from two points
#INPUTS: Two points
#OUTPUT: A line determined by the two points

#initiate a polyline list
lines = [ ]

#derive x,y,z values of four corner points from two input points
x1 = CornerPt1.X
y1 = CornerPt1.Y
z1 = CornerPt1.Z

x2 = CornerPt2.X
y2 = CornerPt1.Y
z2 = CornerPt1.Z

x3 = CornerPt2.X
y3 = CornerPt2.Y
z3 = CornerPt2.Z

x4 = CornerPt1.X
y4 = CornerPt2.Y
z4 = CornerPt2.Z

#initiate four corner points
cpt1 = rc.Geometry.Point3d(x1, y1, z1)
cpt2 = rc.Geometry.Point3d(x2, y2, z2)
cpt3 = rc.Geometry.Point3d(x3, y3, z3)
cpt4 = rc.Geometry.Point3d(x4, y4, z4)
cpt5 = rc.Geometry.Point3d(x1, y1, z1)

#initiate counter
i = 0

while (i < iterations):
    #create a polyline from the four corner points
    line1 = rs.AddPolyline([cpt1, cpt2, cpt3, cpt4, cpt5])
   
    #append the polyline to the polyline list
    lines.append(line1)
   
    #get x, y, z values of interior points along each segement of the polyline 
    xm1 = cpt1.X + (cpt2.X - cpt1.X) * t
    ym1 = cpt1.Y + (cpt2.Y - cpt1.Y) * t
    zm1 = stepSize + cpt1.Z + (cpt2.Z - cpt1.Z) * t
    xm2 = cpt2.X + (cpt3.X - cpt2.X) * t
    ym2 = cpt2.Y + (cpt3.Y - cpt2.Y) * t
    zm2 = stepSize + cpt2.Z + (cpt3.Z - cpt2.Z) * t
    xm3 = cpt3.X + (cpt4.X - cpt3.X) * t
    ym3 = cpt3.Y + (cpt4.Y - cpt3.Y) * t
    zm3 = stepSize + cpt3.Z + (cpt4.Z - cpt3.Z) * t
    xm4 = cpt4.X + (cpt1.X - cpt4.X) * t
    ym4 = cpt4.Y + (cpt1.Y - cpt4.Y) * t
    zm4 = stepSize + cpt4.Z + (cpt1.Z - cpt4.Z) * t
   
    #reset corner points to the new set of points
    cpt1 = rc.Geometry.Point3d(xm1, ym1, zm1)
    cpt2 = rc.Geometry.Point3d(xm2, ym2, zm2)
    cpt3 = rc.Geometry.Point3d(xm3, ym3, zm3)
    cpt4 = rc.Geometry.Point3d(xm4, ym4, zm4)
    cpt5 = rc.Geometry.Point3d(xm1, ym1, zm1)
   
    #advance counter for loop
    i = i + 1

   #return the list of polylines from the Python script
   a = lines


The  Script Editor now appears as follows.

script editor for rotating polylines

The set of polylines output of the Python script can be input onto a surfaces component in order to convert the series of rotating rectangles into a seres of rotating surfaces.  Go to the "Surface" tab in grasshopper, and select the "Boundary Surfaces" component.

add boundary surf component

Connect the output port  "a" to the input port "b" of the bounary surface conmponent to generate the series of rotating surfaces.

rotating rectangular surfaces

3. Script That Uses Iteration to Generate A Helix

To draw a series of inwardly rotating polylines form the outer rectangle, we needed to repeat a similar operation a specified number of times. Similar to the rotating figure above, a helix, such as determined by, adjustableHelixPython.gh, can also be described by a loop. The vertices that are used to define the spiral are also created in a repeated operation for a specified number of times. 

helix

Using tools described previously, initiate the helix by placing two point parameters with a Grasshopper window, and by also adding four numerical slider parameters. One point will be used to identify the center of the spiral. The second point will be used to define the radius. The following sliders should have the following value types and ranges:

Name of Slider Description Type Min Value Max Value
numRev number of revolutions in the spiral Integer (N) 1 25
numSide number of sides in each revolution of the sprial Integer (N) 3 360
heightIncrement height increment in each revolution of the spiral Floating Point (R) 0 25
radiaIncrement radius increment in each revolution of the sprial Floating Poing(R) -25 25


Next, connect the points in  Rhino  to the point input parameters, as done in previous examples. The two points will serve as the center point and radial point of the sprial.

helix parameters

Now, add a Python script component to the Grasshopper View window and add the following input variables and types (use the Type hint feature as done in earlier examples):

Name of Variable Description Type hint
centerPt center point of helix Point3d
radiusPt radial point of helix Point3d
numRev number of revolutions in the spiral int
numSide number of sides in each revolution of the sprial int
heightIncrement height increment in each revolution of the spiral float
radiaIncrement radius increment in each revolution of the sprial float

Note that the sliders for numRev and numSide ares specified at int (integers), but that the input variables numRev and numSide are specified as float (i.e. floating point numbers). Thus the values are converted from integers to floating point numbers when connected to the Python script. The integer designation for the sliders ensures that only whole numbers are selected. This conversion to floating point numbers ensures that floating point accuracy is maintained in the Python script where needed in some operations.

add Python script component for helix

Finally, double-click on the Phthon script and add the following expression into the script editor. 


import rhinoscriptsyntax as rs
import Rhino as rc
import math
#draws a helix
#INPUTS:
#centerPt and radiusPt of helix
#numRev = number of revolutions in helix
#numSide = number of sides per revolution
#heightIncrement = height increment per revolution
#radialIncrement = radius increment per revolution
#OUTPUT:
#a = pline = polyline approximation of helix curve


#Declare variables for spiral point list, vector from center pt to radius pt, and radius
ptList = []
radialVector = rs.VectorCreate(radiusPt, centerPt)
radius = rs.VectorLength(radialVector)

#Declare variable to hold incremental values of rotation angle, height and radius for each point in the spiral
angleIncrement = 360.0/numSide
ptHeightIncrement = heightIncrement/numSide
ptRadialIncrement = radialIncrement/numSide

#Declare variable to hold the initial values for the angle, height and radius
curAngle = 0
curHeight = 0
curRadius = radius

#Loop to generate points in the spiral
i = 1 #initiate counter for loop
while ( i < numRev * numSide) :
    x = centerPt.X + math.cos(math.radians(curAngle)) * curRadius
    y = centerPt.Y + math.sin(math.radians(curAngle)) * curRadius
    z = centerPt.Z + curHeight
    curPt = rs.AddPoint(x, y, z)
    #Add each successive point to the point list
    ptList.append(curPt)
    
    #Advance the values of the current angle, current height and current radius for the next point in the spiral
    curAngle = curAngle + angleIncrement
    curHeight = curHeight + ptHeightIncrement
    curRadius = curRadius + ptRadialIncrement
    i = i + 1 #increment loop counter

#Determine a polyline from the point list
pline = rs.AddPolyline(ptList, replace_id=None)

#Return the polyline to Rhino
a = pline

Select the "OK" button in the editor and the Grasshopper file will produce the helix as depicted in the following image.

helix curve

4. Script That Uses Iteration to Generate A Ramp

The helix script can be enhanced to become todraw two spirals that form a ramp, as illustrated by the Grasshopper file adjustableRampPython.gh.The essential strategy for looping through vertices is the same. Begin by adding a radial point within Rhino to determine the location of the second helix.


Next, within Grasshopper add a new point input parameter radialPt2 and connect it to the recently added radial point, and a corresponding Point3D variable "radiusPt2" to the Python script.

add second radial point parameter to Grasshopper

Within the Python script, we now double up on the points so as to produce two spirals rather than one spiral. Furthermore, rather than use the points to form a polyline as in the previous example of the helix, we use them to form two bspline curves. Next, the bspline curves are used to generate a ruled surface. Whereas polylines can not be used to generate a ruled surface, the bspline curves can be used to generate one.

The Python scrip is modified as follows. See the comment lines below for details:

import rhinoscriptsyntax as rs
import Rhino as rc
import math
#draws a helix
#INPUTS:
#centerPt and radiusPt of helix
#numRev = number of revolutions in helix
#numSide = number of sides per revolution
#heightIncrement = height increment per revolution
#radialIncrement = radius increment per revolution
#OUTPUT:
#a = pline = polyline approximation of helix curve


#Declare variables for spiral point list, vector from center pt to radius pt, and radius
ptList = []
ptList2 = []
radialVector = rs.VectorCreate(radiusPt1, centerPt)
radius1 = rs.VectorLength(radialVector)
radialVector2 = rs.VectorCreate(radiusPt2, centerPt)
radius2 = rs.VectorLength(radialVector2)


#Declare variable to hold incremental values of rotation angle, height and radius for each point in the spiral
angleIncrement = 360.0/numSide
ptHeightIncrement = heightIncrement/numSide
ptRadialIncrement = radialIncrement/numSide

#Declare variable to hold the initial values for the angle, height and radius
curAngle = 0
curHeight = 0
curRadius = radius1
curRadius2 = radius2

#Loop to generate points in the spirals
i = 1 #initiate counter for loop
while ( i < numRev * numSide) :
    x = centerPt.X + math.cos(math.radians(curAngle)) * curRadius
    y = centerPt.Y + math.sin(math.radians(curAngle)) * curRadius
    z = centerPt.Z + curHeight
    curPt = rs.AddPoint(x, y, z)
    x2 = centerPt.X + math.cos(math.radians(curAngle)) * curRadius2
    y2 = centerPt.Y + math.sin(math.radians(curAngle)) * curRadius2
    z2 = centerPt.Z + curHeight
    curPt2 = rs.AddPoint(x2, y2, z2)
   
    #Add each successive point to the point list
    ptList.append(curPt)
    ptList2.append(curPt2)
   
    #Advance the values of the current angle, current height and current radius for the next point in the spiral
    curAngle = curAngle + angleIncrement
    curHeight = curHeight + ptHeightIncrement
    curRadius = curRadius + ptRadialIncrement
    curRadius2 = curRadius2 + ptRadialIncrement
    i = i + 1 #increment loop counter

#Determine a polyline from the point list
pline = rs.AddPolyline(ptList, replace_id=None)
pline2 = rs.AddPolyline(ptList2, replace_id=None)

#Build lofted surface between two curves
rampSurf = rs.AddLoftSrf([pline, pline2])

a = rampSurf

Note that we used some new statements to determine a surface.

 f#Build lofted surface between two curves
rampSurf = rs.AddLoftSrf([pline, pline2])

 

In particular we used a function library "rs.AddLoftSrf". A number of functions have been created for Grasshopper to generate geometry and handle other requirements.  The current listing of functions and related examples can be found in the web site: http://wiki.mcneel.com/developer/python

5. Python Scripts That Include Sub Functions

The previous examples rely upon scripts that contain one main function each. However, in some circumstances,  the logic of a Python script may be more concisely organized into to include sub functions to perform more specific tasks.  This is the case of a script, makeQuadripartiteVaultPython.gh,  that generates a quadripartite vault from four corner points on the x-y ground plane.

quadripartite valut

The basic components of the script are the same as what was used in previous examples for this set of workshop notes. Four "pt" components are linked to the four points on the ground in counter-clockwise order. A numerical slider that ranges in value from -5 to 5 is used to determine the height difference if any between the central crossing arches and the side archest of the vault. The Python script accepts the four ground points and height difference number as inputs. It returns the vault surfaces as output.

gh def for vault

Note that on the right-hand side of the Grasshopper window, a yellow panel is used to echo back the output of the function. To place such as yellow panel inside the Grasshopper window,  go to the "Params Tab"and the "Input Area", select a yellow panel and connect to the output of the Python script 

Within the Python script  There are  "def"  functions to perform more of the explicit tasks called upon by the main body of code. In this example, the construction of the quadripartite vault follows a seven step sequence of:

1. Start with the four corner points on the ground in counter-clockwise order (lower-left, lower-right, upper-right, upper-left)
2. Determine the center of the vault on the ground
3. Determine the center arches apex point
4. Determine the side arches apex points
5. Determine the central crossing arches
6. Determine the side crossing arches
7. Loft vault surfaces between corresponding arches

The main body of code meta logic of what is done at an abstract level. Note too that all the "def" statements must precede the main body of code which relies upon them. The is due to the fact the Python scripts are compiled at runtime, a detail beyond the scope of the workshop.  
 
 import rhinoscriptsyntax as rs
 import Rhino as rc
 import math

#Creates A Quadripartite Vault from four points on the the Ground (x-y plane)
#INPUTS: corner points on ground LlowerLeftPt, lowerRightPt, upperRightPt, upperLeftPt
#deltaHeightCtr = difference betweend apex height of central crossing arch and apex of side arches
#OUTPUTS: Surflist = list of eight quadripartite surfaces


def GetCentroid(lowerLeftPt, lowerRightPt, upperRightPt, upperLeftPt):
    #Determine centroid of four point
    x = (lowerLeftPt.X + lowerRightPt.X + upperRightPt.X + upperLeftPt.X) / 4.0
    y = (lowerLeftPt.Y + lowerRightPt.Y + upperRightPt.Y + upperLeftPt.Y) / 4.0
    z = (lowerLeftPt.Z + lowerRightPt.Z + upperRightPt.Z + upperLeftPt.Z) / 4.0
    centroidPt = rc.Geometry.Point3d(x, y, z)
    return centroidPt

def GetCtrApexPt(lowerLeftPt,lowerRightPt,upperRightPt,upperLeftPt,CentroidPt):
    #Determine central apex point based upon corner point of vault, centroid on ground and CtrApexPt
    #Determine vectors between opposite corner points on ground and get their respective lengths
    vect1 = rs.VectorCreate(upperRightPt, lowerLeftPt)
    dist1 = rs.VectorLength(vect1)
    vect2 = rs.VectorCreate(upperLeftPt, lowerRightPt)
    dist2 = rs.VectorLength(vect1)
    #Determine the radius of the central cross arches based upon the average distance between opposite ground corner points
    radius = (dist1 + dist2) / 4.0
    CtrApexPt = rc.Geometry.Point3d(CentroidPt.X, CentroidPt.Y, CentroidPt.Z + radius)
    return CtrApexPt

def GetSideApexPt(EndPoint1, EndPoint2, CtrApexPt, deltaHeightCtr):
    #Determine side apex point based upon springing points of side arches, central apex point, and height difference if any with central apex point
    midPt = rc.Geometry.Point3d((EndPoint1.X + EndPoint2.X) / 2.0, (EndPoint1.Y + EndPoint2.Y) / 2.0, (EndPoint1.Z + EndPoint2.Z) / 2.0)
    SideApexPt = rc.Geometry.Point3d(midPt.X, midPt.Y, midPt.Z + CtrApexPt.Z + deltaHeightCtr)
    return SideApexPt


 def MakeArcs(EndPoint1, ApexPt, EndPoint2):
    #Determine crossing arch halves CrossArcA and CrossArcB
    #Determine up vector tangent to arc at springing point
    UpVec = rc.Geometry.Vector3d(0,0,1)
    #Determine arches from endpoints and tangent vector
    Arc1 = rc.Geometry.Arc(EndPoint1, UpVec, ApexPt)
    Arc2 = rc.Geometry.Arc(EndPoint2, UpVec, ApexPt)
    #Convert arc to arc-curve for compatibility with loft tool
    Arcc1 = rc.Geometry.ArcCurve(Arc1)
    Arcc2 = rc.Geometry.ArcCurve(Arc2)
    arcs = []
    arcs.append(Arcc1)
    arcs.append(Arcc2)
    return arcs

 def GenVaultSurf(Arc1, Arc2):
    #Loft surface between corresponding arccurves
    vaultSurf = rs.AddLoftSrf([Arc1, Arc2])
    #return first element of vaultSurf list - the actual surface generated
return vaultSurf[0]

#Declare list variable for all surfaces
SurfList = [ ]

#Get the center of the vault on the ground
CentroidPt = GetCentroid(lowerLeftPt, lowerRightPt, upperRightPt, upperLeftPt)

#Determine the center arches apex point
CtrApexPt = GetCtrApexPt(lowerLeftPt, lowerRightPt, upperRightPt, upperLeftPt, CentroidPt)

#Determine each side arch apex point point
SideApexPt1 = GetSideApexPt(lowerLeftPt, lowerRightPt, CtrApexPt, deltaHeightCtr)
SideApexPt2 = GetSideApexPt(lowerRightPt, upperRightPt, CtrApexPt, deltaHeightCtr)
SideApexPt3 = GetSideApexPt(upperRightPt, upperLeftPt, CtrApexPt, deltaHeightCtr)
SideApexPt4 = GetSideApexPt(upperLeftPt, lowerLeftPt, CtrApexPt, deltaHeightCtr)

#Determine the central crossing arch halves
arcList1 = MakeArcs(lowerLeftPt, CtrApexPt, upperRightPt)
CrossArc1a = arcList1[0]
CrossArc1b = arcList1[1]
arcList2 = MakeArcs(upperLeftPt, CtrApexPt, lowerRightPt)
CrossArc2a = arcList2[0]
CrossArc2b = arcList2[1]

#Determine the side arch halves
arcList3 = MakeArcs(lowerLeftPt, SideApexPt1, lowerRightPt)
EndArc1a = arcList3[0]
EndArc1b = arcList3[1]
arcList4 = MakeArcs(lowerRightPt, SideApexPt2, upperRightPt)
EndArc2a = arcList4[0]
EndArc2b = arcList4[1]
arcList5 = MakeArcs(upperRightPt, SideApexPt3, upperLeftPt)
EndArc3a = arcList5[0]
EndArc3b = arcList5[1]
arcList6 = MakeArcs(upperLeftPt, SideApexPt4, lowerLeftPt)
EndArc4a = arcList6[0]
EndArc4b = arcList6[1]

#Loft surfaces between corresponding side-arch half and central crossing arch-half and add to SurfList
vaultSurf = GenVaultSurf(EndArc1a, CrossArc1a)
SurfList.append(vaultSurf)
vaultSurf = GenVaultSurf(EndArc1b, CrossArc2b)
SurfList.append(vaultSurf)
vaultSurf = GenVaultSurf(EndArc2a, CrossArc2b)
SurfList.append(vaultSurf)
vaultSurf = GenVaultSurf(EndArc2b, CrossArc1b)
SurfList.append(vaultSurf)
vaultSurf = GenVaultSurf(EndArc3a, CrossArc1b)
SurfList.append(vaultSurf)
vaultSurf = GenVaultSurf(EndArc3b, CrossArc2a)
SurfList.append(vaultSurf)
vaultSurf = GenVaultSurf(EndArc4a, CrossArc2a)
SurfList.append(vaultSurf)
vaultSurf = GenVaultSurf(EndArc4b, CrossArc1a)
SurfList.append(vaultSurf)

a = SurfList

nd Sub

Sub functions are defined to:

1. GetCentroid: Finds the centroid of four points.
2. GetCtrApexPt: Find the central apex point of the vault.
3. GetSideApexPt: Find the apex point of the side arches relative to the central apex point.
4. MakeArcs: Make arcs for each of the side and central crossing arches
5. GenVaultSurf: Generate vault surfaces between each corresponding side-arch half and central crossing arch half.

Note that each of the sub functions (defined forllowing  the key word "def")  has input data passed to them by means of variables, and returns data by means of a return statement, similar how the main body of the script has input data passed to it and returns data.



The main body of the script is somewhat like a general contractor that oversees overall workflow, and indeed can be conceived of without being focused on the details. The sub functions are like sub-contractors and employed multiple times within the overall process where needed. Given how the geometry has been defined, it is possible to move any of the ground points and determine a more asymmetrical vault.

assymetrical vault

Or, by entering a series of points, it is possible to replicate the arrangement of a nave, crossing, or ambulatory.

pseudo ambulatory