Welcome to the Craft Machine

Some months ago I found an article about an exhibition titled The Machine organized by the Design Hub Limburg that includes a fascinating tool for designers. This tool is the result of a project called  Computer Augmented Craft, which, the head of the project, Christian Feiberg, says, “is an attempt to utilise advanced technologies without sacrificing the unique qualities of craftsmanship.” The tool combines a set of sensors with Arduino and an interface created in Processing to enable a designer to have a real-time digital model of an artefact that they are physically producing. The software interface also provides “suggestions” at each step of construction to enable the designer to conform to an initial set of parameters or choose not to do so. The following video shows the system in use –

Reading about this tool and the exhibition reminded me of a student project I had done in the Command & Control design studio with Simon KimSkylar Tibbits and Juhong Park in 2009 at MIT. My project in this studio (focusing on using scripting as a design tool) included a script that created mass-customised joints for a post-earthquake shelter constructed as an irregular space frame out of found rubble. In this project the script did not dictate the form of the structure but only created fabrication data for a mass-customised joint component after the human builder had decided what member to use (from the post-earthquake rubble at hand) and where in the structure to place it. The script overcame the unpredictability of the materials at hand by taking inputs (on the length of a found member that the builder wanted to attach at a particular location) incrementally. The resulting digital model grew in tandem with the physical model allowing the builder to take independent design decisions while the script recorded the builder’s design moves and output fabrication data for the joint components needed at each step. If the builder got stuck and was unable to triangulate the space frame at any point then the script would suggest a method to triangulate.

My poster from the Command & Control studio in 2009. The green members in the main figure denote “found” members while the red members denote those added by the script to triangulate the space frame.

Re-visiting this old student project in the light of the “The Machine” exhibition resulted in a project for the Patterns and Performance 2nd year, B.Arch design studio I am teaching with Abhishek Bij at the University School of Architecture and Planning (USAP). For this exercise we collaborated with the studio taught by Malini Kochupillai and Kanishk Prasad to design a learning space for a group of 20 students. I wrote a new version of the script I had coded for my Command & Control project for use by the students. The new script did not focus on the joints and instead was designed for the specific design problem given to the students for this exercise – the design of a learning space housing 15 to 20 people using a space frame structure constructed from available members of irregular lengths. The students were given tutorials on space frames and introduced to the script written for them. They were introduced to different forms of education and their spatial implications – both interior and exterior.

After this, the students began their designs in groups of four, constructing 1:10 or 1:20 scale physical models of their structures and simultaneously using the script to “grow” a 3D computer model. This studio exercise exposed the students to the advantages and disadvantages of physical versus digital design processes, issues of error and tolerance in design and construction, the importance of improvisation and contingency and its incorporation into the design process.

2012-11-26 12.10.07
The physical and digital models growing side-by-side in the studio.

While my initial script written for the Command & Control studio focused on creating digitally fabricated joints, the script written for the USAP students included a panelization tool. This tool allowed the students to choose where to place panels on the space frame and have the script add these panels to the 3D model and also provide fabrication data for the panels so that they could be printed as a 2D triangle, cut and be added to the physical model.

The next step in the design studio will be to use what one has learnt constructing models and build a full-scale bamboo space frame as a learning space on the college campus. Observing the students work on the models I have updated the script for the full-scale structure. The updated script calculates the centre of gravity of the structure at each iteration of the design and construction process and gives a warning if the structure is likely to topple over without support. The reason for the addition of this feature in the script is that while it is easy to support a table-top model by hand if it is toppling over, this is not a trivial task during full-scale construction. This feature will allow the script to inform the steps in the physical construction process, while design decisions in response to space and material are taken in the physical world and fed into the script. Such a design process will aid in bringing the digital and physical worlds closer together to create a digitally augmented, craft-based, design process.

The latest version of the script written for the studio is given below (please note that there are still some bugs in calculating the centre of gravity) –

Option Explicit
‘Script written by <Ayodh Kamath>
‘Script version 18 November 2012 12:02:10

Call Main()
Sub Main()

Dim strStart, arrExist
Dim arrDots, arrLines, strExit
Dim i

Call Rhino.AddLayer(“uncut_members”, RGB(0,255,0))
Call Rhino.CurrentLayer(“uncut_members”)

Do

strStart = Rhino.GetString(“Start from scratch [S], use existing geometry[G], triangulate with a single member [T], panelize [P], or exit[X]?”,,Array(“S”,”G”,”T”,”P”,”X”))

arrExist = Rhino.AllObjects()

If strStart = “S” Then

If IsArray(arrExist) Then

Call Rhino.DeleteObjects(arrExist)

End If

arrDots = Triangle(arrLines)
Call Rhino.UnselectAllObjects
arrDots = Tetrahedron(arrDots, arrLines)
Call Rhino.UnselectAllObjects

ElseIf strStart = “G” Then

Call Rhino.ZoomExtents(,True)
arrDots = Rhino.GetObjects(“Please drag a selection box around the existing text dot points and ‘Click+Cntrl’ to de-select any unwanted geometry.”,8192)
arrLines = Rhino.GetObjects(“Please drag a selection box around the existing lines and ‘Click+Cntrl’ to de-select any unwanted geometry.”,4)
arrDots = Sort(arrDots)

strExit = “A”

Do

arrDots = Tetrahedron(arrDots, arrLines)
Call Rhino.UnselectAllObjects
strExit = Rhino.GetString(“Make another tetrahedron [A], or exit [X]?”,”A”, Array(“A”,”X”))

Loop While strExit <> “X”

ElseIf strStart = “T” Then

Call Triangulate(arrLines,arrDots)

ElseIf strStart = “P” Then

Call Panelize()

End If

Loop While strStart <> “X”

End Sub

Function Triangle(ByRef arrLines)

Dim dblLt1, dblLt2, dblLt3
Dim strLn0, strLn1, strLn2
Dim strDotP0, strDotP1, strDotP2a, strDotP2b, strDotP2
Dim strTempCirc1, strTempCirc2
Dim arrInt, arrIntPt0, arrIntPt1, strPt0, strPt1, strPt2
Dim strChoice, blnLoop

blnLoop = 0

Do While blnLoop = 0

dblLt1 = Rhino.GetReal(“Enter first lenght:”)

strLn0 = Rhino.AddLine(Array(0,0,0), Array(dblLt1,0,0))
strDotP0 = Rhino.AddTextDot(“P0”,Array(0,0,0))
strDotP1 = Rhino.AddTextDot(“P1”,Rhino.CurveEndPoint(strLn0))
Call Rhino.ZoomExtents(,True)

dblLt2 = Rhino.GetReal(“Enter second lenght:”)

strTempCirc1 = Rhino.AddCircle(Rhino.WorldXYPlane(),dblLt2)
strTempCirc1 = Rhino.MoveObject(strTempCirc1,Array(0,0,0),Array(dblLt1,0,0))

dblLt3 = Rhino.GetReal(“Enter third lenght:”)

strTempCirc2 = Rhino.AddCircle(Rhino.WorldXYPlane(),dblLt3)

arrInt = Rhino.CurveCurveIntersection(strTempCirc1,strTempCirc2)
Call Rhino.DeleteObject(strTempCirc1)
Call Rhino.DeleteObject(strTempCirc2)

If IsArray(arrInt) Then

arrIntPt0 = arrInt(0,1)
arrIntPt1 = arrInt(1,1)

strDotP2a = Rhino.AddTextDot(“P2a”,arrIntPt0)
strDotP2b = Rhino.AddTextDot(“P2b”,arrIntPt1)

Call Rhino.ZoomExtents(,True)
strChoice = Rhino.GetString(“Point P2a[A] or point P2b[B]?”,”A”, Array(“A”,”B”))

If strChoice = “A” Then

Call Rhino.DeleteObject(strDotP2b)
Call Rhino.DeleteObject(strDotP2a)
strDotP2 = Rhino.AddTextDot(“P2”,arrIntPt0)
strLn1 = Rhino.AddLine(Rhino.CurveEndPoint(strLn0),arrIntPt0)
strLn2 = Rhino.AddLine(arrIntPt0,Rhino.CurveStartPoint(strLn0))

ElseIf strChoice = “B” Then

Call Rhino.DeleteObject(strDotP2a)
Call Rhino.DeleteObject(strDotP2b)
strDotP2 = Rhino.AddTextDot(“P2”,arrIntPt1)
strLn1 = Rhino.AddLine(Rhino.CurveEndPoint(strLn0),arrIntPt1)
strLn2 = Rhino.AddLine(arrIntPt1,Rhino.CurveStartPoint(strLn0))

End If

Triangle = Array(strDotP0,strDotP1,strDotP2)
ReDim arrLines(2)
arrLines(0) = strLn0
arrLines(1) = strLn1
arrLines(2) = strLn2
blnLoop = 1

Else

Call Rhino.DeleteObject(strLn0)
Call Rhino.DeleteObject(strDotP0)
Call Rhino.DeleteObject(strDotP1)
Call Rhino.MessageBox(“The member lengths can not be triangulated. Please try a different set of members.”)

End If

Loop

End Function

Function Sort(arrTxtDot)

Dim strMin, blnSwap
Dim i, j, k

Do

blnSwap = False
For i = 1 To UBound(arrTxtDot)

If Rhino.TextDotText(arrTxtDot(i-1)) > Rhino.TextDotText(arrTxtDot(i)) Then

strMin = arrTxtDot(i)
arrTxtDot(i) = arrTxtDot(i-1)
arrTxtDot(i-1) = strMin
blnSwap = True

End If

Next

Loop While blnSwap = True

Sort = arrTxtDot

End Function

Function Tetrahedron(arrTxtDots, ByRef arrLines)

ReDim arrInPts(2)
ReDim arrTempDots(2)
ReDim arrInLts(2)
ReDim arrTempSphs(2)
Dim arrIntSrf, strJoinCrv, arrIntCrvSrf
ReDim arrIntDots(1)
Dim strChoice, arrIntPt
Dim i, j
Dim blnLoop
Dim intPtNum
Dim intCGChoice
ReDim arrTempLines(2)
Dim intLineCount
Dim blnGoal, arrGoalPt

blnLoop = 0

Do While blnLoop = 0

blnGoal = Rhino.GetString(“Select a goal point?”,,Array(“Y”,”N”))

If blnGoal = “Y” Then

arrGoalPt = Rhino.GetPoint(“Select goal point”)
Call GoalPoint(arrTxtDots,arrGoalPt)

End If

For i = 0 To 2

arrInPts(i) = Rhino.GetPoint(“Select point #”&CStr(i+1))
Call Rhino.UnselectAllObjects
arrTempDots(i) = Rhino.AddTextDot(CStr(i+1),arrInPts(i))
Call Rhino.SelectObject(arrTempDots(i))

arrInLts(i) = Rhino.GetReal(“Enter member length at this node:”)
arrTempSphs(i) = Rhino.AddSphere(arrInPts(i),arrInLts(i))
Call Rhino.ZoomExtents(,True)

If i = 1 Then

arrIntSrf = Rhino.SurfaceSurfaceIntersection(arrTempSphs(0),arrTempSphs(1),,True)

If IsArray(arrIntSrf) Then

If UBound(arrIntSrf,1)>0 Then

strJoinCrv = JoinCurve(arrIntSrf)

Else

strJoinCrv = arrIntSrf(0,1)

End If

Else

Call Rhino.DeleteObject(arrTempDots(0))
Call Rhino.DeleteObject(arrTempDots(1))
Call Rhino.DeleteObject(arrTempSphs(0))
Call Rhino.DeleteObject(arrTempSphs(1))
blnLoop = 0
Call Rhino.MessageBox(“The member lengths can not be triangulated. Please try a different set of members.”)
Exit For

End If

ElseIf i = 2 Then

arrIntCrvSrf = Rhino.CurveSurfaceIntersection(strJoinCrv,arrTempSphs(2))

If IsArray(arrIntCrvSrf) Then

blnLoop = 1
arrIntDots(0) = Rhino.AddTextDot(“IntA”,arrIntCrvSrf(0,1))
arrIntDots(1) = Rhino.AddTextDot(“IntB”,arrIntCrvSrf(1,1))
Call Rhino.SelectObjects(arrIntDots)

strChoice = Rhino.GetString(“Point IntA[A] or point IntB[B]?”,”A”, Array(“A”,”B”))

If strChoice = “A” Then

arrIntPt = arrIntCrvSrf(0,1)

ElseIf strChoice = “B” Then

arrIntPt = arrIntCrvSrf(1,1)

End If

Else

Call Rhino.DeleteObjects(arrTempDots)
Call Rhino.DeleteObjects(arrTempSphs)
Call Rhino.DeleteObject(strJoinCrv)
blnLoop = 0
Call Rhino.MessageBox(“The member lengths can not be triangulated. Please try a different set of members.”)
Exit For

End If

End If

Next

Loop

Call Rhino.DeleteObjects(arrIntDots)
Call Rhino.DeleteObjects(arrTempSphs)
Call Rhino.DeleteObjects(arrTempDots)
Call Rhino.DeleteObject(strJoinCrv)

For i = 0 To 2

arrTempLines(i) = Rhino.AddLine(arrInPts(i),arrIntPt)

Next

intLineCount = UBound(arrLines)
ReDim Preserve arrLines(intLineCount+3)
arrLines(intLineCount+1) = arrTempLines(0)
arrLines(intLineCount+2) = arrTempLines(1)
arrLines(intLineCount+3) = arrTempLines(2)

intPtNum = UBound(arrTxtDots)
ReDim Preserve arrTxtDots(intPtNum+1)
arrTxtDots(intPtNum+1) = Rhino.AddTextDot(“P”&CStr(intPtNum+1),arrIntPt)

intCGChoice = CG(arrTxtDots,arrLines)

If intCGChoice = 7 Then

Call Rhino.DeleteObjects(arrTempLines)
Call Rhino.DeleteObject(arrTxtDots(intPtNum+1))
ReDim Preserve arrTxtDots(intPtNum)
ReDim Preserve arrLines(intLineCount)

End If

Tetrahedron = arrTxtDots

End Function

Function JoinCurve(arrCrvs)
‘takes the two dimensional array resulting from a SurfaceSurfaceIntersection command and returns the string identifier of the joined intersection segments

Dim intSegments, arrJoin
Dim i

intSegments = UBound(arrCrvs,1)

ReDim arrJoinCrvs(intSegments)

For i = 0 To intSegments

arrJoinCrvs(i) = arrCrvs(i,1)

Next

arrJoin = Rhino.JoinCurves(arrJoinCrvs, True)
JoinCurve = arrJoin(0)

End Function

Function Triangulate(ByRef arrLines, arrTxtDots)

Dim arrInPt1, arrInPt2, strTempDot1, strTempDot2, strTempLine
Dim intMsg, intLineCount, intCGChoice

arrInPt1 = Rhino.GetPoint(“Select first point”)
strTempDot1 = Rhino.AddTextDot(“1”,arrInPt1)
Call Rhino.SelectObject(strTempDot1)

arrInPt2 = Rhino.GetPoint(“Select second point”)
strTempDot2 = Rhino.AddTextDot(“2”,arrInPt2)
Call Rhino.SelectObject(strTempDot2)

intMsg = Rhino.MessageBox(“The member length required between these points is “&CStr(Rhino.Distance(arrInPt1,arrInPt2)),4)

If intMsg = 6 Then

Call Rhino.AddLayer(“cut_members”, RGB(255,0,0))
Call Rhino.CurrentLayer(“cut_members”)
intLineCount = UBound(arrLines)
ReDim Preserve arrLines(intLineCount+1)
arrLines(intLineCount+1) = Rhino.AddLine(arrInPt1,arrInPt2)
Call Rhino.CurrentLayer(“uncut_members”)

End If

intCGChoice = CG(arrTxtDots,arrLines)

If intCGChoice = 7 Then

Call Rhino.DeleteObject(arrLines(intLineCount+1))
ReDim Preserve arrLines(intLineCount)

End If

Call Rhino.DeleteObject(strTempDot1)
Call Rhino.DeleteObject(strTempDot2)

End Function

Function Panelize

Dim strStart
Dim strDot1, strDot2, strDot3
ReDim arrPts(2)
Dim strSrf
ReDim arrEdges(2)
ReDim arrDgs(2)
Dim arrTempDiv
ReDim strDots(2)
Dim intMsg, arrFlat, arrFlatOrPt, arrBox

strDot1 = Rhino.GetObject(“Select first point”,8192)
Call Rhino.SelectObject(strDot1)

strDot2 = Rhino.GetObject(“Select second point”,8192)
Call Rhino.SelectObject(strDot2)

strDot3 = Rhino.GetObject(“Select third point”,8192)
Call Rhino.SelectObject(strDot3)

arrPts(0) = Rhino.TextDotPoint(strDot1)
arrPts(1) = Rhino.TextDotPoint(strDot2)
arrPts(2) = Rhino.TextDotPoint(strDot3)

strSrf = Rhino.AddSrfPt(arrPts)

arrEdges(0) = Rhino.AddLine(arrPts(0),arrPts(1))
arrEdges(1) = Rhino.AddLine(arrPts(1),arrPts(2))
arrEdges(2) = Rhino.AddLine(arrPts(2),arrPts(0))

arrDgs(0) = Rhino.AddLine(arrPts(0),Rhino.CurveMidPoint(arrEdges(1)))
arrDgs(1) = Rhino.AddLine(arrPts(1),Rhino.CurveMidPoint(arrEdges(2)))
arrDgs(2) = Rhino.AddLine(arrPts(2),Rhino.CurveMidPoint(arrEdges(0)))

arrTempDiv = Rhino.DivideCurve(arrDgs(0),4)
strDots(0) = Rhino.CopyObject(strDot1,arrPts(0),arrTempDiv(1))

arrTempDiv = Rhino.DivideCurve(arrDgs(1),4)
strDots(1) = Rhino.CopyObject(strDot2,arrPts(1),arrTempDiv(1))

arrTempDiv = Rhino.DivideCurve(arrDgs(2),4)
strDots(2) = Rhino.CopyObject(strDot3,arrPts(02),arrTempDiv(1))

Call Rhino.DeleteObjects(arrEdges)
Call Rhino.DeleteObjects(arrDgs)
Call Rhino.UnselectAllObjects()

intMsg = Rhino.MessageBox(“Flatten this panel?”,4)

If intMsg = 7 Then

Call Rhino.DeleteObject(strSrf)
Call Rhino.DeleteObjects(strDots)

ElseIf intMsg = 6 Then

If Not IsArray(arrFlatOrPt) Then

arrFlatOrPt = Rhino.GetPoint(“Select origin point for unroll”)

End If

arrFlat = RhinoUnrollSurface(strSrf,strDots,False,False)
arrFlat = Rhino.MoveObjects(arrFlat,Array(0,0,0),arrFlatOrPt)
arrBox = Rhino.BoundingBox(arrFlat)
arrFlatOrPt = arrBox(1)
Call Rhino.DeleteObjects(strDots)

End If

End Function

Function RhinoUnrollSurface(strSurface, arrCurves, blnExplode, blnLabels)

‘ Default return value
RhinoUnrollSurface = Null

‘ For speed, turn of screen redrawing
Call Rhino.EnableRedraw(False)

‘ Save any selected objects
Dim arrSaved : arrSaved = Rhino.SelectedObjects

‘ Unselect all objects
Rhino.UnSelectAllObjects

‘ Select the surface to unroll
Rhino.SelectObject strSurface

‘ Format curve string
Dim i : i = 0
Dim strCurves : strCurves = ” _Enter”
If IsArray(arrCurves) Then
strCurves = “”
For i = 0 To UBound(arrCurves)
strCurves = strCurves & ” _SelId ” & arrCurves(i)
Next
strCurves = strCurves & ” _Enter”
End If

‘ Format explode string
Dim strExplode : strExplode = ” _Explode=_Yes”
If (blnExplode = False) Then strExplode = ” _Explode=_No”

‘ Format labels string
Dim strLabels : strLabels = ” _Labels=_No”
If (blnLabels = True) Then strLabels = ” _Labels=_Yes”

‘ Script the command
Dim strCommand : strCommand = “_-UnrollSrf” & strExplode & strLabels & strCurves
Call Rhino.Command(strCommand, 0)

‘ Return the results
RhinoUnrollSurface = Rhino.LastCreatedObjects

‘ Unselect all objects
Rhino.UnSelectAllObjects

‘ If any objects were selected before calling
‘ this function, re-select them
If IsArray(arrSaved) Then Rhino.SelectObjects(arrSaved)

‘ Don’t forget to turn redrawing back on
Call Rhino.EnableRedraw(True)

End Function

Function CG(arrDots, ByRef arrLines)

ReDim arrDotPts(UBound(arrDots))
Dim dblSumLength, arrMidPt
Dim dblX, dblY, dblZ, dblLength
Dim i
ReDim arrMemCGPt(2)
ReDim arrJtCGPt(2)
ReDim arrCGPt(2)
Dim strTempCGPt, strTempCGLine, intCount
intCount = 0
ReDim arrBasePts(intCount)
Dim arrSortBasePts, strBaseCrv, intInside, arrCGBasePt
Dim intChoice

Dim dblMemDens, dblJtWt
dblMemDens = 1 ‘average bamboo density assumed to be 1kg/running meter = 1g/running mm
dblJtWt = 2000 ‘average weight per joint assumed to be 2kg = 2000g

For i = 0 To UBound(arrLines)

dblSumLength = dblSumLength+Rhino.CurveLength(arrLines(i))

Next

For i = 0 To UBound(arrLines)

arrMidPt = Rhino.CurveMidPoint(arrLines(i))
dblLength = Rhino.CurveLength(arrLines(i))
dblX = dblX+((arrMidPt(0))*(Rhino.CurveLength(arrLines(i))))
dblY = dblY+((arrMidPt(1))*(Rhino.CurveLength(arrLines(i))))
dblZ = dblZ+((arrMidPt(2))*(Rhino.CurveLength(arrLines(i))))

Next

arrMemCGPt(0) = dblX/dblSumLength
arrMemCGPt(1) = dblY/dblSumLength
arrMemCGPt(2) = dblZ/dblSumLength

dblX = 0
dblY = 0
dblZ = 0

For i = 0 To UBound(arrDots)

arrDotPts(i) = Rhino.TextDotPoint(arrDots(i))

dblX = dblX+arrDotPts(i)(0)
dblY = dblY+arrDotPts(i)(1)
dblZ = dblZ+arrDotPts(i)(2)

Next

arrJtCGPt(0) = dblX/i
arrJtCGPt(1) = dblY/i
arrJtCGPt(2) = dblZ/i

arrCGPt(0) = ((dblMemDens*dblSumLength*arrMemCGPt(0))+(dblJtWt*i*arrJtCGPt(0)))/((dblMemDens*dblSumLength)+(dblJtWt*i))
arrCGPt(1) = ((dblMemDens*dblSumLength*arrMemCGPt(1))+(dblJtWt*i*arrJtCGPt(1)))/((dblMemDens*dblSumLength)+(dblJtWt*i))
arrCGPt(2) = ((dblMemDens*dblSumLength*arrMemCGPt(2))+(dblJtWt*i*arrJtCGPt(2)))/((dblMemDens*dblSumLength)+(dblJtWt*i))

strTempCGPt = Rhino.AddPoint(arrCGPt)
strBaseCrv = Base(arrDotPts)

arrCGBasePt = arrCGPt
arrCGBasePt(2) = 0
strTempCGLine = Rhino.AddLine(arrCGPt,arrCGBasePt)
intInside = Rhino.PointInPlanarClosedCurve(arrCGBasePt,strBaseCrv)

Call Rhino.DeleteObject(strBaseCrv)

If intInside <> 1 Then

intChoice = Rhino.MessageBox(“The addition of this point will cause the structure to topple. Carry on anyway?”,4)

Else

intChoice = Rhino.MessageBox(“The addition of this point will not cause the structure to topple”)

End If

Call Rhino.DeleteObject(strTempCGPt)
Call Rhino.DeleteObject(strTempCGLine)
CG = intChoice

End Function

Function Base(arrAllPts)

Dim intPolyCount, intPtCount, intInOut
Dim i, j
Dim arrTempPoly, strTempPoly
Dim dblDist, dblMinDist, intMinPt, bolMin
Dim dblParam, arrClsPt
Dim arrTempPts
Dim arrJoin
intPtCount = 0
intPolyCount = 2
bolMin = 0
ReDim arrPts(intPtCount)
ReDim arrBasePts(intPolyCount)
ReDim arrBaseLns(intPolyCount)

For i = 0 To UBound(arrAllPts)

If arrAllPts(i)(2) = 0 Then

arrPts(intPtCount) = arrAllPts(i)
intPtCount = intPtCount+1
ReDim Preserve arrPts(intPtCount)

End If

Next

intPtCount = intPtCount-1
ReDim Preserve arrPts(intPtCount)

arrBasePts(0) = arrPts(0)
arrBasePts(1) = arrPts(1)
arrBasePts(2) = arrPts(2)

arrBaseLns(0) = Rhino.AddLine(arrBasePts(0),arrBasePts(1))
arrBaseLns(1) = Rhino.AddLine(arrBasePts(1),arrBasePts(2))
arrBaseLns(2) = Rhino.AddLine(arrBasePts(2),arrBasePts(0))

For i = 3 To intPtCount

arrTempPoly = Rhino.JoinCurves(arrBaseLns)
strTempPoly = arrTempPoly(0)

intInOut = Rhino.PointInPlanarClosedCurve(arrPts(i),strTempPoly)

Call Rhino.DeleteObject(strTempPoly)

If intInOut = 0 Then

For j = 0 To intPolyCount

dblDist = Rhino.Distance(arrBasePts(j),arrPts(i))

If j = 0 Then

dblMinDist = dblDist
intMinPt = j
bolMin = 0

Else

If dblDist < dblMinDist Then

dblMinDist = dblDist
intMinPt = j
bolMin = 0

End If

End If

Next

For j = 0 To intPolyCount

dblParam = Rhino.CurveClosestPoint(arrBaseLns(j),arrPts(i))
arrClsPt = Rhino.EvaluateCurve(arrBaseLns(j),dblParam)
dblDist = Rhino.Distance(arrClsPt,arrPts(i))

If dblDist < dblMinDist Then

dblMinDist = dblDist
intMinPt = j
bolMin = 1

End If

Next

If bolMin = 0 Then

arrBasePts(intMinPt) = arrPts(i)

Call Rhino.DeleteObjects(arrBaseLns)

For j = 0 To intPolyCount-1

arrBaseLns(j) = Rhino.AddLine(arrBasePts(j),arrBasePts(j+1))

Next

arrBaseLns(intPolyCount) = Rhino.AddLine(arrBasePts(intPolyCount),arrBasePts(0))

ElseIf bolMin = 1 Then

arrTempPts = arrBasePts
intPolyCount = intPolyCount+1
ReDim Preserve arrBasePts(intPolyCount)

arrBasePts(intMinPt+1) = arrPts(i)

For j = intMinPt+2 To intPolyCount

arrBasePts(j) = arrTempPts(j-1)

Next

Call Rhino.DeleteObjects(arrBaseLns)
ReDim Preserve arrBaseLns(intPolyCount)

For j = 0 To intPolyCount-1

arrBaseLns(j) = Rhino.AddLine(arrBasePts(j),arrBasePts(j+1))

Next

arrBaseLns(intPolyCount) = Rhino.AddLine(arrBasePts(intPolyCount),arrBasePts(0))

End If

End If

Next

arrJoin = Rhino.JoinCurves(arrBaseLns,True)
Base = arrJoin(0)

End Function

Function GoalPoint(arrTextDots,arrGoalPt)

Dim strMin, blnSwap
Dim i, j, k

ReDim arrDist(UBound(arrTextDots))

Do

blnSwap = False
For i = 1 To UBound(arrTextDots)

If Rhino.Distance(Rhino.TextDotPoint(arrTextDots(i-1)),arrGoalPt) > Rhino.Distance(Rhino.TextDotPoint(arrTextDots(i)),arrGoalPt) Then

strMin = arrTextDots(i)
arrTextDots(i) = arrTextDots(i-1)
arrTextDots(i-1) = strMin
blnSwap = True

End If

Next

Loop While blnSwap = True

Call Rhino.SelectObject(arrTextDots(0))
Call Rhino.MessageBox(“The closest node to the goal point has been selected”)

End Function

Advertisements

Curve Fitting Script

This is a very simple curve fitting script to find where a short curve fits best along longer curve. I’ve developed this script in the context of my on-going work with bamboo weaving.

The curve fitting script finds where a short curve fits best along longer curve.

The Nest Roof is a result of woven bamboo beams following paths of minimum curvature along a funicular surface.

A 1:50 scale model of the Nest Roof

While the bamboo beams may follow paths of minimum curvature, one is still left with the possibility to further optimize the selection of where along a beam profile to use each individual piece of bamboo. Bamboo being a natural material does not have uniform properties and every piece of bamboo is different. Every piece has a different shape and bends by a different amount. Therefore different pieces of bamboo will be suited to different parts of the roof with different beam curvature. Selecting the right piece of bamboo for a given segment of a beam is fundamentally finding where along the beam a given piece of bamboo fits best so that it will have to be bent a minimum amount, which is what this script does. However, it is far from clear how exactly, and if at all, the script can be used during construction. The principal problem is finding a work flow whereby the curvature of each individual piece of bamboo can be recorded and digitized so as to form an input for this script (or some version of it) during construction, and a way to convey the result of the script in real time to the craftspersons building the roof. These issues were tackled in my SMArchS thesis, but only at a theoretical level and at a table top model scale. I hope to be able to carry this forward to a building scale and a live project through the Nest Roof.

The script uses a very crude brute force algorithm that incrementally slides a short curve (representing an individual piece of bamboo) along a longer curve (the beam profile) while checking the deviation between the two curves at each increment. The length of the increment can be specified and the smaller the increment the more accurate the result will be. An interesting by-product of the brute force algorithm is the plant like shapes that it produces. “Leaves” appear to sprout as the script slides one curve along the other, and then the “leaves” are then shed as the script deletes all but the best-fit result.

"Leaves" formed during the running of the script

Below is a version of the script that works on planar curves, but the same idea can be expanded to apply to 3D curves as well –

Option Explicit
‘Script written by <Ayodh Kamath>
‘Script copyrighted by <Kamath Design Studio/PostScriptDesign>
‘Script version 08 April 2012 14:18:42

Call Main()
Sub Main()

Dim strCrv1, strCrv2, dblDivLength, arrDivPts1, arrDivPts2
Dim intCheckPt, strAlignCrv
Dim intCount, intMin
Dim j, i

strCrv1 = Rhino.GetObject(“Select guide curve to check against”,4)
strCrv2 = Rhino.GetObject(“Select curve to check”,4)

dblDivLength = (Rhino.CurveLength(strCrv2))/10
dblDivLength = Rhino.GetReal(“Enter division length:”, dblDivLength)

arrDivPts1 = Rhino.DivideCurveLength(strCrv1, dblDivLength)
arrDivPts2 = Rhino.DivideCurveLength(strCrv2, dblDivLength)

ReDim arrDot(UBound(arrDivPts1))

For i = 0 To UBound(arrDivPts1)

arrDot(i) = Rhino.AddTextDot(CStr(i), arrDivPts1(i))

Next

intCheckPt = Rhino.GetInteger(“Enter point number to check from”, 0, 0, (UBound(arrDivPts1) – UBound(arrDivPts2)))
Call Rhino.DeleteObjects(arrDot)

ReDim arrDev(((UBound(arrDivPts1) – UBound(arrDivPts2) – intCheckPt + 1)*(UBound(arrDivPts2))) – 1)
ReDim arrAlignCrvs(((UBound(arrDivPts1) – UBound(arrDivPts2) – intCheckPt + 1)*(UBound(arrDivPts2))) – 1)

intCount = 0

For i = intCheckPt To (UBound(arrDivPts1) – UBound(arrDivPts2))

For j = 1 To UBound(arrDivPts2)

arrAlignCrvs(intCount) = Rhino.OrientObject(strCrv2, Array( arrDivPts2(0), arrDivPts2(j)), Array(arrDivPts1(i), arrDivPts1(i + j)),1)

arrDev(intCount) = Deviation(strCrv1, arrAlignCrvs(intCount), i, dblDivLength, arrDivPts1)

intCount = intCount + 1

Next

Next

intCount = intCount – 1

intMin = Minimum(arrDev)

For i = 0 To UBound(arrDev)

If i <> intMin Then

Call Rhino.DeleteObject(arrAlignCrvs(i))

End If

Next

Call Rhino.SelectObject(arrAlignCrvs(intMin))

End Sub

Function Deviation(ByRef strCrv1, strAlignCrv, intFnCheckPt, ByRef dblDivLength, ByRef arrDivPts1)

End Function

Function Minimum(arrCheck)
End Function

Digitally Guided Weaving & Other Installations from the Spanz Workshop at SSAA.

“Floating Triads”Canopy Design from the Spanz Workshop at Atharva 2012. Photograph: Yogesh Verma
Lighting Module from the Spanz Workshop. Image: Pragya Vij, Saalanki Saraf

A part of this semester’s 3rd year design studio at Sushant School of Art & Architecture consisted of an introductory workshop on the use of digital design tools that we called Spanz. The workshop was taught by Abhishek Bij and me (both visiting design faculty at the 3rd year design studio) and supported by the rest of the 3rd year design faculty consisting of Shikha Doogar, Gaurav Shorey, Swati Singh, Thomas Oomen and Neeraj Khosla. The workshop started with a crash course on a variety of digital design tools including 3D modelling software, form finding tools, parametric modelling software and form rationalization tools – tools that are applicable to different stages of the digital design workflow, from conceptualization to fabrication. The tools developed tackled a range of structural systems (tensile membranes, funicular shells and folded plates) and fabrication methods (weaving, pipe bending, sheet metal forming, concrete casting etc.).

The students were encouraged to mix and matched these tools to suit their design goals and design styles and no specific methodology was imposed on them. The tools were applied to various real life campus development projects as a part of their annual college festival (Atharva 2012, 30th-31st March) and ranged from canopies to installations to lighting design. The nuances of the digital tools were mastered by them while using them for their developing their designs.

The Interlace Installation. Photo: Gauri Varshney

A student installation of specific interest here is “The Interlace” designed by Akshita, Anushree, Gauri, Parush, Sumit and Suvrita because it is the first time the weaving script I had developed and posted earlier has been applied to a large scale project. The weaving script simply takes a given fibre spacing and fibre thickness and uses the U- and V- curves of a surface to derive fabrication data for the weaving process. My interest in weaving comes from the way it allows complex 3D curved surfaces to be fabricated using only linear measurements (which I have discussed in detail in this earlier post). In “The Interlace” the students started out by using a form finding algorithm based on David Rutten’s classic mesh relaxation algorithm. The basic mesh relaxation algorithm was modified specifically for the workshop to allow the students to model the effects of gravity and thereby generate funicular forms in addition to tensile membranes (I will be explaining the modifications made to the algorithm in another blog post shortly). The relaxed mesh was converted to a NURBS surface and the weaving script was applied to it. The script outputted the lengths of individual fibres and the points of intersection with other fibres along their length. The fabrication data was used to first build a 1:10 scale construction model where the students could test the fabrication process and the steps involved.

The 1:10 Construction Model

Once the construction process was developed and understood in the studio, the students proceeded to build the final installation. Since the college festival is student-run and student-managed, the direct role of the design faculty ended at the construction model stage. Comparing the construction model to the final installation, it is interesting to note that the percentage error in the linear measurements was more in the construction model and less in the final installation, meaning that the final installation was a closer match to the computer model than the construction model. The properties of the pipe used to make the final installation were different from the construction model in terms of their ability to resist compression, resulting in a small amount of creasing of the surface of the final installation. Overall, however, this installation appears to verify the feasibility of using weaving to manually fabricate computer generated 3D curved surfaces at a 1:1 scale.

Physical-Digital Sphere Packing Experiment

In an earlier post I had discussed a script for packing a single layer of spheres on a complex curved surface. A script of this type that I developed was used in the Cradle project by Ball-Nogues Studio. However, in the Cradle project the script had been used only to generate possible simulations of the design for structural analysis (done by Buro Happold) and for estimating material quantities prior to the start of fabrication. The script was not used to generate the exact placement of spheres in the final project. A 3D model produced by the script was only used as an approximate guide for the placement of spheres during construction. The reason for this was that any small deviations between the physical construction and the digital model could cause large differences in the packing of the spheres. I had discussed this in detail in a post titled ‘Santa Monica Cradle: Reflections on Craft and Computation’.

The following script and physical experiment using the script bridges the gap between the digital model and the physical construction process by digitally simulating the packing of the spheres in parallel with their physical packing. This script allows for a digital tolerance in order to absorb imperfections in the physical model. This process does not allow the digital model to dictate the physical construction but instead allows the digital model to mimic the physical packing of imperfect spheres on an imperfect surface.

The physical experiment involved packing spherical ball-bearings (with rust on their surface creating imperfections) on a simple conical paper surface. The paper cone was made from regular letter paper by cutting out a circle with a scissors and folding it into a cone and taping it with sticky tape. The result was thus an imperfect conical surface. The following montage of screen shots from the running script and the steps in the physical sphere packing show how the script and physical construction run in parallel. The tolerance required here was 0.001″

The way in which I hope to make use of this script is to use the resulting 3D model of the physical sphere packing as a basis to design components that can be custom made to interface with a given physical configuration of spheres.

Below is the script used in this experiment:

Option Explicit
‘Script written by <Ayodh Kamath>
‘Script copyrighted by <Ayodh Kamath>
‘Script version Friday, March 25, 2011 10:15:38 PM

Call Main()
Sub Main()
 
 Dim arrSurf, arrSphere1, arrSphere2
 Dim arrBoundingBox, realRadius1, realRadius2, arrLine, arrCentre1, arrCentre2, binCarryOn, arrJoin, arrIntCrv, arrIntPt1, arrIntPt2, realDist1, realDist2, arrCentre
 Dim arrSphere, arrSurf1
 Dim realRadius, arrSidePt
 Dim arrIntSphere1, arrIntSphere2, arrInt
 Dim arrSpheres, intUnselect
 Dim i, j

 arrSurf = Rhino.GetObject(“Select surface to populate”)
 arrSphere1 = Rhino.GetObject(“Select first sphere”)
 arrSphere2 = Rhino.GetObject(“Select second sphere”)
 
 arrBoundingBox = Rhino.BoundingBox(arrSphere1)
 realRadius1 = (Rhino.Distance(arrBoundingBox(0), arrBoundingBox(1)))/2
 arrLine = Rhino.AddLine(arrBoundingBox(0), arrBoundingBox(6))
 arrCentre1 = Rhino.CurveMidPoint(arrLine)
 Rhino.DeleteObject arrLine
 
 arrBoundingBox = Rhino.BoundingBox(arrSphere2)
 realRadius2 = (Rhino.Distance(arrBoundingBox(0), arrBoundingBox(1)))/2
 arrLine = Rhino.AddLine(arrBoundingBox(0), arrBoundingBox(6))
 arrCentre2 = Rhino.CurveMidPoint(arrLine)
 Rhino.DeleteObject arrLine
 
 binCarryOn = 1
 
 Do While binCarryOn = 1
 
  realRadius = Rhino.GetReal(“Enter radius of tangent sphere:”)
  arrSidePt = Rhino.GetObject(“Select point in desired direction”)
 
  arrSphere1 = Rhino.AddSphere(arrCentre1, (realRadius1+realRadius))
  arrSphere2 = Rhino.AddSphere(arrCentre2, (realRadius2+realRadius))
 
  arrInt = Rhino.SurfaceSurfaceIntersection(arrSphere1, arrSphere2,, vbTrue)
  Rhino.DeleteObject arrSphere1
  Rhino.DeleteObject arrSphere2
  
  If UBound(arrInt)>0 Then
    
   ReDim arrSegments(UBound(arrInt))
    
   For j = 0 To UBound(arrInt)
     
    arrSegments(j) = arrInt(j,1)
     
   Next
    
   arrJoin = Rhino.JoinCurves(arrSegments, vbTrue)
     
   arrIntCrv = arrJoin(0)
    
  Else
    
   arrIntCrv = arrInt(0,1)
    
  End If
  
  arrSurf1 = Rhino.OffsetSurface(arrSurf, realRadius)
  arrInt = Rhino.CurveSurfaceIntersection(arrIntCrv, arrSurf1)
  Rhino.DeleteObject arrIntCrv
  Rhino.DeleteObject arrSurf1
  
  If IsArray(arrInt) Then
   
   If UBound(arrInt)>0 Then
    
    arrIntPt1 = arrInt(0,1)
    
    arrIntPt2 = arrInt(1,1)
    
    realDist1 = Rhino.Distance(arrIntPt1, Rhino.PointCoordinates(arrSidePt))
    
    realDist2 = Rhino.Distance(arrIntPt2, Rhino.PointCoordinates(arrSidePt))
    
    If realDist1>realDist2 Then arrCentre = arrInt(1,1)
    
    If realDist2>realDist1 Then arrCentre = arrInt(0,1)
    
   Else
    
    ‘arrCentre = arrInt(0,1)
    
   End If
   
  End If
  
  arrSphere = Rhino.AddSphere(arrCentre, realRadius)
  
  Rhino.Command “SelClosedSrf”
  
  arrSpheres = Rhino.SelectedObjects
  
  For i = 0 To UBound(arrSpheres)
   
   arrInt = Rhino.SurfaceSurfaceIntersection(arrSphere, arrSpheres(i), 0.001, vbTrue)
   
   If IsArray(arrInt) Then
    
    If (arrInt(0,0) <> 4) And (arrInt(0,0) <> 5) Then
    
     Rhino.DeleteObject arrSphere
     intUnselect = Rhino.UnselectAllObjects
     Rhino.Command “SelCrv”
     Rhino.Command “Delete”
     
    End If
    
   End If
   
  Next
  
  Call Rhino.Command(“_ViewCaptureToFile”)
   
  arrSphere1 = Rhino.GetObject(“Select first sphere”)
  
  If Not IsNull(arrSphere1) Then
   
   arrSphere2 = Rhino.GetObject(“Select second sphere”)
   
   arrBoundingBox = Rhino.BoundingBox(arrSphere1)
   realRadius1 = (Rhino.Distance(arrBoundingBox(0), arrBoundingBox(1)))/2
   arrLine = Rhino.AddLine(arrBoundingBox(0), arrBoundingBox(6))
   arrCentre1 = Rhino.CurveMidPoint(arrLine)
   Rhino.DeleteObject arrLine
 
   arrBoundingBox = Rhino.BoundingBox(arrSphere2)
   realRadius2 = (Rhino.Distance(arrBoundingBox(0), arrBoundingBox(1)))/2
   arrLine = Rhino.AddLine(arrBoundingBox(0), arrBoundingBox(6))
   arrCentre2 = Rhino.CurveMidPoint(arrLine)
   Rhino.DeleteObject arrLine
   
  Else
   
   binCarryOn = 0
   
  End If
 
 Loop
 
End Sub

Non-Planar Quad Sub-Division of a Surface Within a Given Quad Edge Length Range

This is a RhinoScript that allows a user to specify a range of the edge lengths for the non-planar quad sub-division of a surface. Where a quad sub-division in the given range of length is impossible the script creates a triangular sub-division instead.

I wrote this script while working on the design of the Table Cloth and was reminded of it when I was looking at files on my back-up hard drive today. I should add a disclaimer that the script is rather buggy since we decided not use it rather early in the design process and I never got a chance to refine it.

This script is probably not going to be of much direct use for panelization since it does not produce planar quads,  but I can see it having other uses. I’d love to hear of any uses people can think of for this script.

Option Explicit
‘Script written by <Ayodh Kamath>
‘Script copyrighted by <PostScript Design>
‘Script version Saturday, October 03, 2009 12:01:49 PM

Call Main()
Sub Main()
 
 Dim realMinLength, realMaxLength, realAvgLength, arrSurf, arrBoundingBox, arrCutPlane, arrCutSurf
 Dim arrIntersect, arrContour
 ReDim arrContours(1)
 Dim intDivNum, arrPoints1, arrPoints2
 Dim arrMovePoint, arrTempPoint, arrGreaterPoints, arrLesserPoints
 Dim i, j
 Dim realP2Pdist, realMaxP2Pdist, realMinP2Pdist
 Dim realPercentDiff, realMoveDist
 Dim arrLine
 Dim arrCutPlaneTemp, intEndCheck
 ReDim arrLines(0)
 Dim intLineCount, intLineCheck, arrEndPoint
 
 intLineCount = 0
 intEndCheck = 0
 realMinLength = Rhino.GetReal(“Enter minimum part length:”)
 realMaxLength = Rhino.GetReal(“Enter maximum part length:”)
 realAvgLength = Rhino.GetReal(“Enter average part length:”)
 arrSurf = Rhino.GetObject(“Select surface”,8 )
 ‘create plane for slicing plane
 arrBoundingBox = Rhino.BoundingBox(arrSurf)
 arrTempPoint = arrBoundingBox(4)
 arrCutPlane = Rhino.PlaneFromPoints(arrBoundingBox(5), arrBoundingBox(4), arrBoundingBox(7))
 arrCutSurf = Rhino.AddPlaneSurface(arrCutPlane, 20*(Rhino.Distance(arrBoundingBox(4), arrBoundingBox(5))), 20*(Rhino.Distance(arrBoundingBox(4), arrBoundingBox(7))))
 arrCutSurf = Rhino.MoveObject(arrCutSurf, arrBoundingBox(4), Rhino.CurveMidPoint(Rhino.AddLine(arrBoundingBox(5), arrBoundingBox(4))))
 ‘create first contour
 arrIntersect = Rhino.SurfaceSurfaceIntersection(arrSurf, arrCutSurf,, vbTrue)
 Rhino.Print arrIntersect(0,0)
 arrContours(0) = arrIntersect(0,1)
 ‘find number of divisions to divide the contour by
 intDivNum = Int(Rhino.CurveLength(arrContours(0))/realAvgLength)-1
 If intDivNum < 2 Then intDivNum = 2
 arrPoints1 = Rhino.DivideCurve(arrContours(0), intDivNum)
 ‘initialise the base point to move the slicing plane
 arrMovePoint = arrTempPoint
 
 Do
  ‘move the slicing surface base point by the average member length
  arrMovePoint(2) = arrMovePoint(2)-realAvgLength
  ‘move the slicing surface
  arrCutSurf = Rhino.MoveObject(arrCutSurf, arrTempPoint, arrMovePoint)
  arrTempPoint = arrMovePoint
  ‘intersect the slicing plane with the surface
  arrIntersect = Rhino.SurfaceSurfaceIntersection(arrSurf, arrCutSurf,, vbTrue)
  ‘check if the surface has been completely sliced
  If Not IsArray(arrIntersect) Then
   
   If intEndCheck > 1 Then
    ‘check if the last edge contour has already been made
    Rhino.DeleteObject arrCutSurf
    
    Exit Do
    
   Else
    ‘make the last contour on the edge of the surface
    Rhino.DeleteObject arrCutSurf
   
    arrCutPlaneTemp = Rhino.PlaneFromPoints(arrBoundingBox(0), arrBoundingBox(1), arrBoundingBox(3))
    arrCutSurf = Rhino.AddPlaneSurface(arrCutPlaneTemp, Rhino.Distance(arrBoundingBox(4), arrBoundingBox(5)), Rhino.Distance(arrBoundingBox(4), arrBoundingBox(7)))
    arrIntersect = Rhino.SurfaceSurfaceIntersection(arrSurf, arrCutSurf,, vbTrue)
    intEndCheck = intEndCheck + 1
   
   End If
   
  End If
  ‘divide the contour
  arrContours(1) = arrIntersect(0,1)
  intDivNum = Int(Rhino.CurveLength(arrContours(0))/realAvgLength)-1
  If intDivNum < 2 Then Exit Do
  arrPoints2 = Rhino.DivideCurve(arrContours(1), intDivNum)
  ‘find the contour with the greater number of points
  If UBound(arrPoints1)>UBound(arrPoints2) Then
   
   arrGreaterPoints = arrPoints1
   arrLesserPoints = arrPoints2
   
  Else
   
   arrGreaterPoints = arrPoints2
   arrLesserPoints = arrPoints1
   
  End If
  ‘connect the division points on adjacent contour lines
  For i = 0 To UBound(arrGreaterPoints)
   ‘find the fartherest division point on the contour with fewer division points for each division point on the contour with more division points
   realMaxP2Pdist = 0
   
   For j = 0 To UBound(arrLesserPoints)
    
    realP2Pdist = Rhino.Distance(arrGreaterPoints(i), arrLesserPoints(j))
    
    If realP2Pdist > realMaxP2Pdist Then realMaxP2Pdist = realP2Pdist
    
   Next
   ‘find the closest division point on the contour with fewer division points for each division point on the contour with more division points
   realMinP2Pdist = realMaxP2Pdist
   For j = 0 To UBound(arrLesserPoints)
    
    realP2Pdist = Rhino.Distance(arrGreaterPoints(i), arrLesserPoints(j))
    If realP2Pdist < realMinP2Pdist Then realMinP2Pdist = realP2Pdist
    
   Next
   ‘check if the closest point is farther than the maximum member length
   If realMinP2Pdist > realMaxLength Then
    
    Rhino.DeleteObject arrContours(1)
    ‘find the amount by which the closest member is longer than the maximum member length
    realPercentDiff = (realMinP2Pdist-realMaxLength)/realAvgLength
    ‘calculate the new position to move the slicing plane to
    realMoveDist = realPercentDiff*realAvgLength
    arrMovePoint(2) = arrMovePoint(2)+realMoveDist
    
    ‘arrMovePoint(2) = arrMovePoint(2)-realAvgLength
    ‘move the slicing plane
    arrCutSurf = Rhino.MoveObject(arrCutSurf, arrTempPoint, arrMovePoint)
    arrTempPoint = arrMovePoint
    ‘intersect the  new slicing plane with the surface 
    arrIntersect = Rhino.SurfaceSurfaceIntersection(arrSurf, arrCutSurf,, vbTrue)
    Rhino.Print arrIntersect(0,0)
  
    If Not IsArray(arrIntersect) Then
     ‘check if the last edge contour has already been made
     If intEndCheck > 1 Then
      
      Rhino.DeleteObject arrCutSurf
    
      Exit Do
    
     Else
      ‘make the last contour on the edge of the surface
      Rhino.DeleteObject arrCutSurf
   
      arrCutPlaneTemp = Rhino.PlaneFromPoints(arrBoundingBox(4), arrBoundingBox(5), arrBoundingBox(6))
      arrCutSurf = Rhino.AddPlaneSurface(arrCutPlane, 1.5*(Rhino.Distance(arrBoundingBox(0), arrBoundingBox(1))), 1.5*(Rhino.Distance(arrBoundingBox(0), arrBoundingBox(3))))
      arrIntersect = Rhino.SurfaceSurfaceIntersection(arrSurf, arrCutSurf,, vbTrue)
      intEndCheck = intEndCheck + 1
   
     End If
   
    End If
    ‘divide the contour
    arrContours(1) = arrIntersect(0,1)
    intDivNum = Int(Rhino.CurveLength(arrContours(0))/realAvgLength)-1
    If intDivNum < 2 Then Exit Do
    arrPoints2 = Rhino.DivideCurve(arrContours(1), intDivNum)
    ‘find the contour with the greater number of points
    If UBound(arrPoints1)>UBound(arrPoints2) Then
   
     arrGreaterPoints = arrPoints1
     arrLesserPoints = arrPoints2
   
    Else
   
     arrGreaterPoints = arrPoints2
     arrLesserPoints = arrPoints1
   
    End If
    
   End If
   
  Next
  ‘connect the division points on adjacent contour lines
  For i = 0 To UBound(arrGreaterPoints)
   ‘find the fartherest division point on the contour with fewer division points for each division point on the contour with more division points
   realMaxP2Pdist = 0
   
   For j = 0 To UBound(arrLesserPoints)
    
    realP2Pdist = Rhino.Distance(arrGreaterPoints(i), arrLesserPoints(j))
    
    If realP2Pdist > realMaxP2Pdist Then realMaxP2Pdist = realP2Pdist
    
   Next
   ‘find the closest division point on the contour with fewer division points for each division point on the contour with more division points and connect the points with a line
   realMinP2Pdist = realMaxP2Pdist
 
   arrLine = Rhino.AddLine(Array(0,0,0), Array(1,1,1))
   
   For j = 0 To UBound(arrLesserPoints)
    
    realP2Pdist = Rhino.Distance(arrGreaterPoints(i), arrLesserPoints(j))
    
    If realP2Pdist < realMinP2Pdist Then
     
     realMinP2Pdist = realP2Pdist
     Rhino.DeleteObject arrLine
     arrLine = Rhino.AddLine(arrGreaterPoints(i), arrLesserPoints(j))
     Rhino.AddPoint arrLesserPoints(j)
     
    End If
    
   Next
   
   arrLines(intLineCount) = arrLine
   intLineCount = intLineCOunt+1
   ReDim Preserve arrLines(intLineCount)
   
  Next
  
  For i = 0 To UBound(arrLesserPoints)
   
   intLineCheck = 0
   For j = 0 To intLineCount-1
     
    arrEndPoint = Rhino.CurveEndPoint(arrLines(j))
    If Rhino.PointCompare(arrEndPoint, arrLesserPoints(i)) = vbTrue Then intLineCheck = 1
    
   Next
   
   If intLineCheck = 0 Then
    
    Rhino.Print “No Line”
    realMaxP2Pdist = 0
    
    For j = 0 To UBound(arrGreaterPoints)
     
     realP2Pdist = Rhino.Distance(arrLesserPoints(i), arrGreaterPoints(j))
     If realP2Pdist > realMaxP2Pdist Then realMaxP2Pdist = realP2Pdist
     
    Next
    
    realMinP2Pdist = realMaxP2Pdist
    arrLine = Rhino.AddLine(Array(0,0,0), Array(1,1,1))
    
    For j = 0 To UBound(arrGreaterPoints)
     
     realP2Pdist = Rhino.Distance(arrLesserPoints(i), arrGreaterPoints(j))
     
     If realP2Pdist < realMinP2Pdist Then
     
      realMinP2Pdist = realP2Pdist
      Rhino.DeleteObject arrLine
      arrLine = Rhino.AddLine(arrLesserPoints(i), arrGreaterPoints(j))
     
     End If
     
    Next
    
    arrLines(intLineCount) = arrLine
    intLineCount = intLineCOunt+1
    ReDim Preserve arrLines(intLineCount)
    Rhino.Print intLineCount
    
   End If
   
  Next
  
  ‘polygonise the contour line
  For i = 0 To UBound(arrPoints1)-1
   
   Rhino.AddLine arrPoints1(i), arrPoints1(i+1)
   
  Next
  ‘prepare for the next iteration
  Rhino.DeleteObject arrContours(0)
  arrContours(0) = arrContours(1)
  arrPoints1 = arrPoints2
  
 Loop

End Sub

Digitally Guided Weaving

Here’s a very long overdue physical model based on this surface weaving script that I had posted earlier. I made the model with thumb-pins and the plastic tubing used in aquariums. It was actually quite magical to see the surface take a three-dimensional form as I  wove the tubes based on data from the script – a bit like watching an image form as you develop a print  in a dark-room.

A Screen-shot of the Digital Model
The Physical Model
The Physical Model

Fabrication Data For Surface Weaving

This RhinoScript takes an input surface and cross-section curves and weaves fibres with the given cross-sections along that surface. The user can specify the ‘thread count’ and the tightness of the weave (weave factor). If prompted by the user, the script can also generate fabrication data specifying the distances between intersections with other fibres along the length of each woven fibre. I hope to use this script to create some physical models in the near future.
A Screen Shot Showing the Use of Multiple Cross-Sections and the Fabrication Data Output
An Experiment With a Hexagonal Weaving Pattern
Option Explicit
‘Script written by <Ayodh Kamath>
‘Script copyrighted by <Ayodh Kamath>
‘Script version Thursday, 27 May 2010 10:30:20
Call Main()
Sub Main()
Dim strSrf, arrUDomain, arrVDomain, intUdiv, intVdiv, dblWeaveFactor, dblWeaveFactorU, dblWeaveFactorV
Dim i, j
Dim strSecCrvU, strSecCrvV, arrBox
Dim dblFabChoice, strLengthCount
ReDim arrOrientPts1(2)
ReDim arrOrientPts2(2)
arrOrientPts1(0) = Array(0,0,0)
arrOrientPts1(1) = Array(1,0,0)
arrOrientPts1(2) = Array(0,1,0)
strSrf = Rhino.GetObject(“Select surface”, 8 )
arrUDomain = Rhino.SurfaceDomain(strSrf, 0)
arrVDomain = Rhino.SurfaceDomain(strSrf, 1)
intUdiv = Rhino.GetInteger(“Enter number of threads in U:”)
intUdiv = intUdiv – 1
intVdiv = Rhino.GetInteger(“Enter number of threads in V:”)
intVdiv = intVdiv – 1
strSecCrvU = Rhino.GetObject(“Select cross section curve in the U direction”, 4)
arrBox = Rhino.BoundingBox(strSecCrvU)
dblWeaveFactorU = Rhino.Distance(arrBox(1), arrBox(2))/2
dblWeaveFactorU = Rhino.GetReal(“Enter the weave factor in the U direction:”, dblWeaveFactorU)
strSecCrvV = Rhino.GetObject(“Select cross section curve in the V direction”, 4)
arrBox = Rhino.BoundingBox(strSecCrvV)
dblWeaveFactorV = Rhino.Distance(arrBox(1), arrBox(2))/2
dblWeaveFactorV = Rhino.GetReal(“Enter the weave factor in the V direction:”, dblWeaveFactorV)
ReDim arrPts(intUdiv, intVdiv)
ReDim arrPtsU(intUdiv, intVdiv)
ReDim arrPtsV(intUdiv, intVdiv)
ReDim arrNorm(intUdiv, intVdiv)
ReDim arrNormU(intUdiv, intVdiv)
ReDim arrNormV(intUdiv, intVdiv)
ReDim arrParamsU(intUdiv)
ReDim arrParamsV(intVdiv)
ReDim arrCrvsU(intVdiv)
ReDim arrCrvsV(intUdiv)
ReDim arrPlinesU(intUdiv)
ReDim arrPlinesV(intVdiv)
For i = 0 To intUdiv
For j = 0 To intVdiv
arrParamsU(i) = i*(arrUDomain(0)+arrUDomain(1))/intUdiv
arrParamsV(j) = j*(arrVDomain(0)+arrVDomain(1))/intVdiv
arrPts(i,j) = Rhino.EvaluateSurface(strSrf, Array(arrParamsU(i), arrParamsV(j)))
arrNorm(i,j) = Rhino.SurfaceNormal(strSrf, Array(arrParamsU(i), arrParamsV(j)))
arrNorm(i,j) = Rhino.VectorUnitize(arrNorm(i,j))
If (i Mod 2) =  0 Or i = 0 Then
If (j Mod 2) = 0 Or j = 0 Then
arrNorm(i,j) = Rhino.VectorReverse(arrNorm(i,j))
End If
Else
If (j Mod 2) <> 0 And j <> 0 Then
arrNorm(i,j) = Rhino.VectorReverse(arrNorm(i,j))
End If
End If
arrNormU(i,j) = Rhino.VectorScale(arrNorm(i,j), dblWeaveFactorU)
arrPtsU(i,j) = Rhino.VectorAdd(arrPts(i,j), arrNormU(i,j))
Call Rhino.CurrentLayer(“Layer 01”)
Call Rhino.AddPoint(arrPtsU(i,j))
arrNormV(i,j) = Rhino.VectorScale(arrNorm(i,j), dblWeaveFactorV)
arrPtsV(i,j) = Rhino.VectorAdd(arrPts(i,j), Rhino.VectorReverse(arrNormV(i,j)))
Call Rhino.CurrentLayer(“Layer 02”)
Call Rhino.AddPoint(arrPtsV(i,j))
Next
Next
For i = 0 To intUdiv
ReDim arrPlinePts(intVdiv)
For j = 0 To intVdiv
arrPlinePts(j) = arrPtsU(i,j)
Next
Call Rhino.CurrentLayer(“Layer 03”)
arrPlinesU(i) = Ribbon(arrPlinePts, strSecCrvU)
Next
For i = 0 To intVdiv
ReDim arrPlinePts(intUdiv)
For j = 0 To intUdiv
arrPlinePts(j) = arrPtsV(j,i)
Next
Call Rhino.CurrentLayer(“Layer 04”)
arrPlinesV(i) = Ribbon(arrPlinePts, strSecCrvV)
Next
dblFabChoice = Rhino.MessageBox(“Do you want to create fabrication data?”,1)
If dblFabChoice = 1 Then
For i = 0 To intUdiv
Call Rhino.AddTextDot(“U”&CStr(i), arrPtsU(i,0))
strLengthCount = “U”&CStr(i)&”: “
For j = 0 To intVdiv-1
strLengthCount = strLengthCount&CStr(Unroll(arrPlinesU(i), arrPtsU(i,j), arrPtsU(i,j+1)))&”,”
Next
Call Rhino.TextOut(strLengthCount)
Next
For i = 0 To intVdiv
Call Rhino.AddTextDot(“V”&CStr(i), arrPtsV(0,i))
strLengthCount = “V”&CStr(i)&”: “
For j = 0 To intUdiv-1
strLengthCount = strLengthCount&CStr(Unroll(arrPlinesV(i), arrPtsV(j,i), arrPtsV(j+1,i)))&”,”
Next
Call Rhino.TextOut(strLengthCount)
Next
End If
End Sub
Function Ribbon(arrPts, strSectionCrv)
End Function
Function Unroll(strCrv, arrPt1, arrPt2)
End Function

TableCloth: Complexity From Layers of Simplicity

The TableCloth installation by Ball-Nogues Studio at the Schoenberg Hall courtyard in the UCLA Music Department is a functional installation. It is part art, part architecture, part structure and part furniture. The complexity of this project emerges from the layering of design decisions taken in response to the different functions that the installation fulfils. The installation is meant to enhance the usefulness of the courtyard to the members of the Music Department. It was commissioned as a temporary installation on the UCLA campus for only a part of the year.

Starting with the courtyard as a space a simple ‘drape-like’ surface hanging from one of the walls was proposed. This drape would embellish the building in the same way that a table cloth embellishes a table and marks is as a place for congregation. This drape surface was designed taking into account the circulation around the courtyard ‘relaxed’ by structural analysis so as to ensure that it draped the building smoothly.

The Relaxed Drape Surface in the Courtyard

A strategy was then devised to subdivide this drape surface into tectonic components that were differentiated based on their position on the surface.

The first step of the subdivision process was to populate the surface with points. The points were to act as seeds for the tectonic components. The density of the points was related to the varying curvature of the surface – the greater the curvature the greater the point density. This would enable the components (to be made of flat materials) to efficiently negotiate the curvature of the drape.  This was achieved using a script similar to one posted on this blog earlier.

Points Placed on the Surface Based on Surface Curvature

The points thus placed were used as the input for a Delaunay triangulation mesh which divided the curved drape surface into planar triangles which determine the orientation and periphery of each planar tectonic component. The density distribution of the points determined the density of the Delaunay mesh. A script for Delaunay triangulation was previously posted on this blog.

Delaunay Triangulation Using Surface Points

The temporary nature of the installation was in conflict with the durability and permanence of the materials and resources that would be required of an outdoor installation of the size of the drape surface. It was thus decided to give the individual tectonic components a functional life beyond that of the installation. The design of the components therefore became an exercise in cross-manufacturing where the same physical artefact performed the dual functions of a tectonic component when part of the installation, and a table/stool when independent. The triangular geometry of the Delaunay mesh dictated a three legged object but the function of a table/stool required minimizing sharp corners. Therefore  a Grasshopper definition was used to create irregular ‘ovals’ within each triangle of the Delaunay mesh which were to become the seats of the stools/tables making up the TableCloth.

Irregular 'Ovals' Created From the Delaunay Triangles

The edges of this field of tables/stools were modulated to allow it to act as an amphitheatre with audience seating, a performance area and a visual/acoustic backdrop. Further interstitial components and detailing were added based on structural and constructional considerations to create a structurally, visually and functionally complex installation.

The Complex Installation Incorporating Layers of Simple Design Decisions

Point Density Based on Surface Curvature

This RhinoScript places points on a surface with the density of points varying with the curvature of the surface. A version of this script has been used in the TableCloth project by Ball-Nogues Studio at the Schoenberg Hall courtyard in the UCLA Department of Music.

Option Explicit
‘Script written by <Ayodh Kamath>
‘Script copyrighted by <Ayodh Kamath>
‘Script version Tuesday, June 22, 2010 6:14:09 PM
Call Main()
Sub Main()
Dim strSrf, intUdiv, intVdiv, arrPts, arrHeightDiff, arrRange
Dim i, j
strSrf = Rhino.GetObject(“Select surface to populate with points”,8)
intUdiv = Rhino.GetInteger(“Enter number of U divisions”)
intVdiv = Rhino.GetInteger(“Enter number of V divisions”)
arrPts = IsoPoints(strSrf, intUdiv, intVdiv)
arrHeightDiff = HeightDifference(arrPts, intUdiv, intVdiv)
arrRange = HeightRange(arrHeightDiff, intUdiv, intVdiv)
Call SubDivide(strSrf, arrPts, intUdiv, intVdiv, arrHeightDiff, arrRange)
End Sub
Function IsoPoints(strSrf, intUdiv, intVdiv)
Dim arrUdomain, arrVdomain
Dim i,j
ReDim arrPts(intUdiv, intVdiv)
ReDim arrParamsU(intUdiv)
ReDim arrParamsV(intVdiv)
arrUdomain = Rhino.SurfaceDomain(strSrf, 0)
arrVdomain = Rhino.SurfaceDomain(strSrf, 1)
For i = 0 To intUdiv
For j = 0 To intVdiv
arrParamsU(i) = i*(arrUdomain(0)+arrUdomain(1))/intUdiv
arrParamsV(j) = j*(arrVdomain(0)+arrVdomain(1))/intVdiv
arrPts(i,j) = Rhino.EvaluateSurface(strSrf, Array(arrParamsU(i), arrParamsV(j)))
‘Call Rhino.AddPoint(arrPts(i,j))
Next
Next
IsoPoints = arrPts
End Function
Function HeightDifference(arrPts, intUdiv, intVdiv)
Dim arrPt1, arrPt2, arrPt3, arrPt4, arrSortHeights
Dim i, j
ReDim arrHeights(intUdiv-1, intVdiv-1)
ReDim arrY(3)
For i = 0 To intUdiv – 1
For j = 0 To intVdiv – 1
arrPt1 = arrPts(i,j)
arrPt2 = arrPts(i+1,j)
arrPt3 = arrPts(i+1,j+1)
arrPt4 = arrPts(i,j+1)
arrY(0) = arrPt1(1)
arrY(1) = arrPt2(1)
arrY(2) = arrPt3(1)
arrY(3) = arrPt4(1)
arrSortHeights = Rhino.SortNumbers(arrY)
arrHeights(i,j) = Round(arrSortHeights(3)-arrSortHeights(0), 2)
Next
Next
HeightDifference = arrHeights
End Function
Function HeightRange(arrHeights, intUdiv, intVdiv)
Dim i, j, intCount, arrSortHeight
ReDim arrHeightFlat((intUdiv*intVdiv)-1)
ReDim arrRange(3)
intCount = 0
For i = 0 To intUdiv-1
For j = 0 To intVdiv-1
arrHeightFlat(intCount) = arrHeights(i,j)
intCount = intCount+1
Next
Next
arrSortHeight = Rhino.SortNumbers(arrHeightFlat)
arrRange(0) = arrSortHeight(0)
arrRange(1) = arrSortHeight(Int(((intUdiv*intVdiv)-1)/3))
arrRange(2) = arrSortHeight(Int((2*((intUdiv*intVdiv)-1))/3))
arrRange(3) = arrSortHeight((intUdiv*intVdiv)-1)
HeightRange = arrRange
End Function
Function SubDivide(strSrf, arrPts, intUdiv, intVdiv, arrHeightDiff, arrRange)
Dim intPtNum, strTempSrf, arrTempPts, arrParams, arrPt, intBaseDensity
Dim i, j, k, l
intBaseDensity = Rhino.GetInteger(“Enter base density”,2,0)
For i = 0 To intUdiv-1
For j = 0 To intVdiv-1
If arrheightDiff(i,j)>= arrRange(0) And arrheightDiff(i,j)<= arrRange(1) Then
intPtNum = intBaseDensity*2
ElseIf arrheightDiff(i,j)> arrRange(1) And arrheightDiff(i,j)<= arrRange(2) Then
intPtNum = Int(intBaseDensity*1.5)
Else
intPtNum = intBaseDensity
End If
strTempSrf = Rhino.AddSrfPt(Array(arrPts(i,j), arrPts(i+1,j), arrPts(i+1,j+1), arrPts(i,j+1)))
arrTempPts = IsoPoints(strTempSrf, intPtNum, intPtNum)
For k = 0 To intPtNum
For l = 0 To intPtNum
arrParams = Rhino.SurfaceClosestPoint(strSrf, arrTempPts(k,l))
arrPt = Rhino.EvaluateSurface(strSrf, arrParams)
If k<>0 And k<>intPtNum And ((k/2)-Int(k/2))<>0 Then
If ((l/2)-Int(l/2))=0 Then
Call Rhino.AddPoint(arrPt)
End If
Else
If k<>0 And k<>intPtNum And ((l/2)-Int(l/2))<>0 Then
Call Rhino.AddPoint(arrPt)
End If
End If
Next
Next
Call Rhino.DeleteObject(strTempSrf)
Next
Next
End Function

Santa Monica Cradle: Reflections on Craft and Computation

While it does not strictly follow the Sensing-Evaluating-Shaping design methodology I developed in my SMArchS thesis (Integrating Digital Design and Fabrication with Craft Production), the Cradle sculpture by Ball-Nogues Studio in Santa Monica illustrates many of the issues addressed therein.

My thesis examined if methods of manual craft production can be utilised to overcome the indeterminacies of physical materials and processes that hinder Digital Design and Fabrication. The Cradle sculpture consists of a number of stainless steel spheres that are tangent to a complex curved surface as well as to each other.

The Cradle Sculpture Seen as Spheres Tangent to a Complex Curved Surface and to Each Other

I developed a script which is able to place spheres one at a time on a surface in such a manner. However, if one attempts to physically replicate the the results of the script (say, by placing balls in a bowl)  one will quickly encounter a number of discrepancies between the digital and the physical.

Balls in a Bowl

One of these discrepancies is the effect of gravity on the tangent spheres which will alter the positions of all the spheres every time a new tangent sphere is added. However, this can be overcome if each ball that is added is glued in place before the next one is added.

Another discrepancy which is much harder to overcome arises from the ‘imperfections’ of physical materials. The geometric nature of the system of tangencies between the surface and the spheres and between the spheres themselves means that the displacement of any one sphere or any modification of the surface will cause a displacement of all the spheres in the system. This means that the slightest discrepancy in form between the digital model of the surface and its physical counterpart, or any deviation of the balls from perfect sphereicality, will cause a mismatch between the digital model created by the script and a physical model.

One way to overcome this issue would be to build in a tolerance between all the components of the system (the surface and the spheres). However, this would have two drawbacks – firstly, it would mean that no two spheres in the physical model will actually be touching due to the tolerance between them, and it could be argued that this would unacceptably compromise the visual perception of the sculpture. Secondly, it would require a sophisticated joint at each point of tangency to absorb the tolerance built into the system which would increase the cost of the project significantly considering that there are approximately three hundred and fifty balls in the sculpture.

Instead, in this project, we chose to allow a deviation between the digital model and the physical artefact. The script was thus used only as a guide in the project to visualise the project through renderings, to estimate material costs and quantities, and to perform structural analysis.  This enabled the surface of the form-work to be constructed cheaply and rapidly without the pressure of having to conform to the digital model to a high level of accuracy and to have a very simple welded joint between spheres. The final configuration of balls in the project was guided by the digital model developed using the sphere packing script and also a result of the incomputable, indeterminable characteristics of the physical materials and processes of fabrication and subjective human design responses to these unique conditions. Proof of this idea lies in the impossibility of duplicating the exact placement of balls if one were to repeat this project once again.

The Surface of the Form-Work Fabricated with Two Layers of Curved Lauan
Welded Joints Between Spheres