EDUCATIONAL
RESOURCES


    Grasshopper Python

CONTENTS
0. GH Python References & Resources
1. GH Python Basics
2. GH Python: Inputs
3. GH Python: Function and Recursion
4. GH Python: Baking and Object Attributes
5. GH Python: Object-Oriented Programming
6. GH Python: Connective Module
7. GH Python: Custom Subdivisions


0. GH Python References & Resources

- RhinoPython 101 Primer
http://www.rhino3d.com/download/IronPython/5.0/RhinoPython101

- RhinoPython Reference
http://4.rhino3d.com/5/ironpython/
(In Rhinoceros menu) Tools > PythonScript > Edit
(In Rhino Python Editor menu) Help > Python Help

- RhinoCommon Reference
http://4.rhino3d.com/5/rhinocommon/





1. GH Python Basics

- 1-1. First Code   GH file

import rhinoscriptsyntax as rs

text = "hello world"
print text

# comment
a = rs.AddPoint(0,0,0)
- 1-2. For Loop   GH file

import rhinoscriptsyntax as rs

for i in range(10):
    print(i)
- 1-3. List   GH file

import rhinoscriptsyntax as rs

a = [] # set an empty list
for i in range(10):
    p = rs.AddPoint(i,0,0)
    a.append(p)
- 1-4. If Condition   GH file

import rhinoscriptsyntax as rs

a = []
for i in range(10):
    if(i%2 == 0):
        p = rs.AddPoint(i,0,0)
    else:
        p = rs.AddPoint(i,0,5)
    a.append(p)
- 1-5. If Condition (2)   GH file

import rhinoscriptsyntax as rs

a = []
for i in range(10):
    if(i < 5):
        p = rs.AddPoint(i,0,0)
    elif(i < 8):
        p = rs.AddPoint(i,0,6)
    else:
        p = rs.AddPoint(i,0,2)
    a.append(p)
- 1-6. Math Library   GH file

import rhinoscriptsyntax as rs
import math

a = []
for i in range(500):
    x = math.cos(i*0.1) * i*0.1
    y = math.sin(i*0.1) * i*0.08
    if(i < 200):
        z = i*0.05
    else:
        z = 20-i*0.05
    p = rs.AddPoint(x,y,z)
    a.append(p)
- 1-7. Random Number   GH file

import rhinoscriptsyntax as rs
import random
import math

a = []
for i in range(500):
    x = math.cos(i*0.2) * i*0.1
    y = math.sin(i*0.2) * i*0.08
    if(i<200):
        z = 0
    else:
        z = random.random() * (i-200)* 0.02
    p = rs.AddPoint(x,y,z)
    a.append(p)
- 1-8. 2D Nested For Loop   GH file

import rhinoscriptsyntax as rs
import math

a = []
for i in range(100):
    for j in range(50):
        x = i*0.5
        y = j*0.5
        z = math.sin(i*0.1)*1;
        if(j%20 < 10):
            z *= (j%20)*0.2;
        else:
            z *= (20 - j%20)*0.2;
        p = rs.AddPoint(x,y,z)
        a.append(p)
- 1-9. And/Or Condition in Loop   GH file

import rhinoscriptsyntax as rs
import math

a = []
for i in range(100):
    for j in range(100):
        x = i*0.5
        y = j*0.5
        if( i<20 and j<20 ):
            z = 2
        elif( i>=20 and i<40 and j>=60 and j<80):
            z = 1
        elif( i==50 or j==50):
            z = 2
        elif( i>=60 and i<80 and j>=20 and j<80):
            z = math.cos(j*0.2)+2
        elif( not i>60 and j>=25 and j<35):
            z = 8 - i*0.1
        elif( not (i<90 and j<90) ):
             z = math.sin(i*0.2) * math.cos(j*0.2)+2
        else:
            z = math.cos(i*0.1) * math.sin(j*0.1)
        p = rs.AddPoint(x,y,z)
        a.append(p)
- 1-10. Conditions and Patterning   GH file

import rhinoscriptsyntax as rs
import math

a = []
for i in range(100):
    for j in range(100):
        x = i*0.5
        y = j*0.5
        if( i%20<4 or j%20<4 ):
            z = 0
        elif( i%20+j%20 > 20):
            z = ((20 - i%20) * (j%20))*0.03
            z += math.cos(i*0.1) * math.sin(j*0.1)*2+2
        elif( i%20>=5 and j%20<10 ):
            z = ((i%20)*(i%20)/20 + (j%20)/2)*0.3
        else:
            z = ((i%20)*0.2 + (j%20)*0.3)
        p = rs.AddPoint(x,y,z)
        a.append(p)




2. GH Python: Inputs

- 2-1. Integer/Float Input   GH file

#building a point grid out of input parameters
#input type - x : int, y : int, z : float, u : float
import rhinoscriptsyntax as rs
import math

a = []
for i in range(x):
    for j in range(y):
        p = rs.AddPoint(i, j, math.sin(i*u) * math.sin(j*u) * z)
        a.append(p)
- 2-2. Point Input   GH file

# separate input points into 3 output groups
# input type - x : Point3d (ListAccess)
import rhinoscriptsyntax as rs
import math

a = []
b = []
c = []
for pt in x:
    if(pt.X % 3 < 0.5):
        a.append(pt)
    elif( math.sqrt(pt.Y) %0.5 < 0.1):
        b.append(pt)
    else:
        c.append(pt)
- 2-3. Point Input (2): Building Geometry Inside Script   GH file   3DM file

# This separates points and creates attractor controlled circles or rectangles 
# input type - pts : Point3d (List Access), attr : Point3d (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg       # Geometry library
import math

a = []
b = []
for pt in pts:
    if( pt.Y % 10 > 0 and pt.Y % 10 < 2):
        rect = rs.AddRectangle(rg.Plane.WorldXY, 1.5, 0.4)
        rs.MoveObject(rect, pt)
        rs.MoveObject(rect, rg.Vector3d(-0.75,-0.2,0))
        a.append(rect)
    else:
        dist = rs.Distance(pt, attr)
        circle = rs.AddCircle(pt, 0.02*dist)
        b.append(circle)
- 2-4. Point Input (3): Point Clustering   GH file

# This connects points within the input distance only if number of connection is more than a certain number
# input type - pts : Point3d (List Access), threshold : float
import rhinoscriptsyntax as rs

a = []
for pt in pts:
    closePts = []
    for pt2 in pts:
        dist = rs.Distance(pt, pt2)
        if(dist > 0 and dist < threshold): # exclude itself by > 0
            closePts.append(pt2)
    
    count = len(closePts)
    if(count >= 3):
        for cpt in closePts:
            zpt = rs.AddPoint(pt.X, pt.Y, 0.5*(count-2))
            line = rs.AddLine(zpt, cpt)
            a.append(line)
- 2-5. Curve Input: Lines Between Closest Division Points   GH file   3DM file

# draw lines between closest points on the input curves
# input type - curves : Curve (List Access), div : int
import rhinoscriptsyntax as rs

a = []
b = []
for crv1 in curves:
    for i in range(div+1):
        pt1 = crv1.PointAt(i/div)
        a.append(pt1)
        
        minDist = -1 #initialize
        for crv2 in curves:
            if(crv1 != crv2):
                for j in range(div+1):
                    pt2 = crv2.PointAt(j/div)
                    dist = rs.Distance(pt1, pt2)
                    if(minDist < 0): # first time to check
                        closest = pt2
                        minDist = dist
                    elif(dist < minDist):
                        closest = pt2
                        minDist = dist
                        
        line = rs.AddLine(pt1, closest)
        b.append(line)
- 2-6. Curve Input (2): Intersections   GH file   3DM file

# this takes intersection of all curves and build geometry around the intersection
# input type - curves : Curve (List Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg

a = []
b = []
for curve1 in curves:
    for curve2 in curves:
        if( curve1 != curve2):
            itxn = rg.Intersect.Intersection.CurveCurve(curve1, curve2, 0.0001, 0.0001)
            for item in itxn.Item:
                # intersection point
                ipt = item.PointA
                a.append(ipt)
                
                # build geometry at intersection
                [success, t] = curve1.ClosestPoint(ipt) # get t parameter
                if(success):
                    tan = curve1.TangentAt(t) # get tangent vector
                    tan *= 2
                    pt1 = rg.Point3d(ipt) # copy a new point
                    pt1 += tan
                    pt2 = rg.Point3d(ipt)
                    pt2 -= tan
                    pt3 = rg.Point3d(ipt)
                    pt3 += rg.Vector3d(0,0,10)
                    surf = rs.AddSrfPt([pt1, pt2, pt3])
                    b.append(surf)
- 2-7. Surface Input : Panelization   GH file   3DM file

# This makes surface panels by 4 points on an input surface
# input type - surf : Surface, unum : int, vnum : int
import Rhino.Geometry as rg

panels = []
for i in range(unum):
    for j in range(vnum):
        pt11 = surf.PointAt(i/unum, j/vnum)
        pt12 = surf.PointAt((i+1)/unum, j/vnum)
        pt21 = surf.PointAt(i/unum, (j+1)/vnum)
        pt22 = surf.PointAt((i+1)/unum, (j+1)/vnum)
        panel = rg.NurbsSurface.CreateFromCorners(pt11, pt12, pt22, pt21)
        panels.append(panel)
- 2-8. Surface Input (2) : Panelization   GH file   3DM file

# This makes surface panels by 3 points on an input surface
# input type - surf : Surface, unum : int, vnum : int
import Rhino.Geometry as rg

panels = []
for i in range(unum):
    for j in range(vnum):
        pt1 = surf.PointAt(i/unum, j/vnum)
        pt2 = surf.PointAt((i+0.9)/unum, (j+0.1)/vnum)
        pt3 = surf.PointAt((i+1.2)/unum, (j+1.0)/vnum)
        pt4 = surf.PointAt((i+0.3)/unum, (j+1)/vnum)
        pt5 = surf.PointAt((i+0.6)/unum, (j+0.3)/vnum)
        n1 = surf.NormalAt(i/unum, j/vnum)
        n2 = surf.NormalAt((i+0.9)/unum, (j+0.1)/vnum)
        n3 = surf.NormalAt((i+1.2)/unum, (j+1.0)/vnum)
        n4 = surf.NormalAt((i+0.3)/unum, (j+1)/vnum)
        n5 = surf.NormalAt((i+0.6)/unum, (j+0.3)/vnum)
        
        pt1 += n1*0.2
        pt2 += n2*0.2
        pt3 += n3*-0.1
        pt4 += n4*1.3
        pt5 += n5*1.5
        
        panel = rg.NurbsSurface.CreateFromCorners(pt2, pt3, pt5)
        panels.append(panel)
        panel = rg.NurbsSurface.CreateFromCorners(pt3, pt4, pt5)
        panels.append(panel)
        panel = rg.NurbsSurface.CreateFromCorners(pt4, pt1, pt5)
        panels.append(panel)
- 2-9. Surface Input (3) : Panelization   GH file   3DM file

# This makes surface panels by lofting on an input surface
# input type - surf : Surface, unum : int, vnum : int, panelWdith : float, panelDepth : float

import Rhino.Geometry as rg

panels = []
for i in range(unum):
    for j in range(vnum):
        pt11 = surf.PointAt(i/unum, j/vnum)
        pt12 = surf.PointAt((i+panelWidth)/unum, j/vnum)
        pt21 = surf.PointAt((i+0.5)/unum, (j+0.75)/vnum)
        pt22 = surf.PointAt((i+0.5+panelWidth)/unum, (j+0.75)/vnum)
        pt31 = surf.PointAt((i+1)/unum, (j+1.5)/vnum)
        pt32 = surf.PointAt((i+1+panelWidth)/unum, (j+1.5)/vnum)
        
        n21 = surf.NormalAt((i+0.25)/unum, (j+0.5)/vnum)
        n22 = surf.NormalAt((i+0.25+panelWidth)/unum, (j+0.5)/vnum)
        pt21 += n21*panelDepth
        pt22 += n22*panelDepth
        
        line1 = rg.LineCurve(pt11, pt12)
        line2 = rg.LineCurve(pt21, pt22)
        line3 = rg.LineCurve(pt31, pt32)
        panelSurf = rg.Brep.CreateFromLoft([line1, line2, line3], rg.Point3d.Unset, rg.Point3d.Unset, rg.LoftType.Straight, False)
        panels.extend(panelSurf)
- 2-10. Surface Input (4) : Panelization   GH file

# This makes surface panels by 6 points on an input surface
# input type: surf - Surface, unum - int, vnum - int, panelDepth - float

import Rhino.Geometry as rg
import math

panels = []
for i in range(unum):
    for j in range(vnum):
        if j % 4 == 0:
            pt11 = surf.PointAt((i+1)/unum, j/vnum)
            pt21 = surf.PointAt((i+1.5+math.sin((i+1)*0.5)*0.5)/unum, j/vnum)
            pt31 = surf.PointAt((i+2)/unum, j/vnum)
            pt12 = surf.PointAt(i/unum, (j+1)/vnum)
            pt22 = surf.PointAt((i+0.5+math.sin(i*0.5)*0.5)/unum, (j+1)/vnum)
            pt32 = surf.PointAt((i+1)/unum, (j+1)/vnum)
            
            n21 = surf.NormalAt((i+1.5+math.sin((i+1)*0.5)*0.5)/unum, j/vnum)
            n22 = surf.NormalAt((i+0.5+math.sin(i*0.5)*0.5)/unum, (j+1)/vnum)
            pt21 += n21*panelDepth * math.sin(j*0.4)
            pt22 += n22*panelDepth * math.sin((j+1)*0.4)
            
            panel = rg.NurbsSurface.CreateFromCorners(pt11,pt21,pt22,pt12)
            panels.append(panel)
            panel = rg.NurbsSurface.CreateFromCorners(pt21,pt31,pt32,pt22)
            panels.append(panel)
        else:
            pt11 = surf.PointAt(i/unum, j/vnum)
            pt21 = surf.PointAt((i+0.5+math.sin(i*0.5)*0.5)/unum, j/vnum)
            pt31 = surf.PointAt((i+1)/unum, j/vnum)
            pt12 = surf.PointAt((i+1)/unum, (j+1)/vnum)
            pt22 = surf.PointAt((i+1.5+math.sin((i+1)*0.5)*0.5)/unum, (j+1)/vnum)
            pt32 = surf.PointAt((i+2)/unum, (j+1)/vnum)
            
            n21 = surf.NormalAt((i+0.5+math.sin(i*0.5)*0.5)/unum, j/vnum)
            n22 = surf.NormalAt((i+1.5+math.sin((i+1)*0.5)*0.5)/unum, (j+1)/vnum)
            pt21 += n21*panelDepth * math.sin(j*0.4)
            pt22 += n22*panelDepth * math.sin((j+1)*0.4)
            
            panel = rg.NurbsSurface.CreateFromCorners(pt11,pt21,pt22,pt12)
            panels.append(panel)
            panel = rg.NurbsSurface.CreateFromCorners(pt21,pt31,pt32,pt22)
            panels.append(panel)
- 2-11. Surface Input (5) : Panelization   GH file

# This makes surface panels by 6 points on an input surface
# input type: surf - Surface, unum - int, vnum - int, panelDepth - float

import Rhino.Geometry as rg

panels = []
for i in range(unum):
    for j in range(vnum):
        ratio = (i+j)%6 / 6
        pt11 = surf.PointAt(i/unum, j/vnum)
        pt21 = surf.PointAt((i+1)/unum, j/vnum)
        pt12 = surf.PointAt(i/unum, (j+0.5)/vnum)
        pt22 = surf.PointAt((i+1+ratio)/unum, (j+0.5)/vnum)
        pt13 = surf.PointAt(i/unum, (j+1)/vnum)
        pt23 = surf.PointAt((i+1)/unum, (j+1)/vnum)
        
        n22 = surf.NormalAt((i+1)/unum, (j+0.5)/vnum)
        pt22 += n22* panelDepth * ratio
        
        panel = rg.NurbsSurface.CreateFromCorners(pt11,pt21,pt22,pt12)
        panels.append(panel)
        panel = rg.NurbsSurface.CreateFromCorners(pt12,pt22,pt23,pt13)
        panels.append(panel)
- 2-12. Surface Input (6) : Panelization   GH file

# This makes surface panels by lofting on an input surface
# input type: surf - Surface, unum - int, vnum - int, panelDepth - float

import Rhino.Geometry as rg
import random

panels = []
for i in range(unum):
    for j in range(vnum):
        pt1 = surf.PointAt(i/unum, j/vnum)
        pt2 = surf.PointAt((i+1)/unum, j/vnum)
        pt3 = surf.PointAt((i+1)/unum, (j+1)/vnum)
        pt4 = surf.PointAt(i/unum, (j+1)/vnum)
        
        if random.random() < 0.7:
            n1 = surf.NormalAt(i/unum, j/vnum)
            n2 = surf.NormalAt((i+1)/unum, j/vnum)
            n3 = surf.NormalAt((i+1)/unum, (j+1)/vnum)
            n4 = surf.NormalAt(i/unum, (j+1)/vnum)
            
            uvRatio = random.random()*0.3
            depthRatio = random.random()
            pt5 = surf.PointAt((i+0.5-uvRatio)/unum, (j+0.5-uvRatio)/vnum)
            pt6 = pt2 
            pt7 = surf.PointAt((i+0.5+uvRatio)/unum, (j+0.5+uvRatio)/vnum)
            pt8 = pt4 
            pt5 += n1 * panelDepth * depthRatio 
            pt6 += n2 * panelDepth * depthRatio * 0.5
            pt7 += n3 * panelDepth * depthRatio 
            pt8 += n4 * panelDepth * depthRatio * 0.5
            
            polyline1 = rg.PolylineCurve([pt1,pt2,pt3,pt4,pt1])
            polyline2 = rg.PolylineCurve([pt5,pt6,pt7,pt8,pt5])
            
            panelSurf = rg.Brep.CreateFromLoft([polyline1, polyline2], rg.Point3d.Unset, rg.Point3d.Unset, rg.LoftType.Straight, False)
            panels.extend(panelSurf)
        else:
            panel = rg.NurbsSurface.CreateFromCorners(pt1, pt2, pt3, pt4)
            panels.append(panel)
- 2-13. Tree Input: Iteration of All Elements   GH file

# this iterates a tree of points and put circles
# input type - ptTree : Point3d (Tree Access)
import rhinoscriptsyntax as rs

a = []
for ptList in ptTree.Branches:
    for pt in ptList:
        circle = rs.AddCircle(pt, 0.4)
        a.append(circle)
- 2-14. Tree Input (2): Iteration with Conditions   GH file

# this iterates a tree of points and put circles
# input type - ptTree : Point3d (Tree Access)
import rhinoscriptsyntax as rs

a = []
for i in range(ptTree.BranchCount):
    ptList = ptTree.Branch(i)
    ptNum = len(ptList)
    for j in range(ptNum):
        if( i==10 and j==23 or i==20 and j==2 or i==32 and j==18):
            circle = rs.AddCircle(ptList[j], 1)
        else:
            circle = rs.AddCircle(ptList[j], 0.2)
        a.append(circle)
- 2-15. Tree Input (3): Connecting Closest Tree Elements   GH file   3DM file

# this connects closest points between branches in a tree
# input type - ptTree : Curve (Tree Access)
import rhinoscriptsyntax as rs

a = []
for ptList in ptTree.Branches:
    for pt in ptList:
        minDist = -1 # initialize
        for ptList2 in ptTree.Branches:
            if(ptList!=ptList2):
                print len(ptList2)
                for pt2 in ptList2:
                    dist = rs.Distance(pt, pt2)
                    if(minDist < 0): # first time
                        minDist = dist
                        closest = pt2
                    elif(dist < minDist):
                        minDist = dist
                        closest = pt2
        line = rs.AddLine(pt, closest)
        a.append(line)




3. GH Python: Function and Recursion

- 3-1. Simple Function with One Argument   GH file   3DM file

# This draws a line pattern at each input point
# input type - pts : Point3d (List Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import math

# function definition
def drawWhorl(pt):
    r1 = 0.3
    r2 = 1
    num = 30
    lines = []
    for i in range(num):
        vec1 = rg.Vector3d(r1, 0, 0)
        vec1.Rotate(math.pi*2*i/num + math.pi/3, rg.Vector3d.ZAxis)
        vec2 = rg.Vector3d(r2, 0, 0)
        vec2.Rotate(math.pi*2*i/num, rg.Vector3d.ZAxis)
        vec1 += pt
        vec2 += pt
        line = rs.AddLine(vec1, vec2)
        lines.append(line)
    return lines

# main
a = []
for pt in pts:
    lines = drawWhorl(pt)
    a.extend(lines) # add all lines
- 3-2. Function with Three Arguments   GH file   3DM file

# this adds tubular surface on an input surface by a function
# input type - surf : Surface (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg

def addTube(pt, normal, radius):
    circle1 = rs.AddCircle(rg.Plane(pt, normal), radius)
    pt2 = pt + normal * 0.1
    circle2 = rs.AddCircle(rg.Plane(pt2, normal), radius*0.5)
    pt3 = pt + normal * 0.5
    circle3 = rs.AddCircle(rg.Plane(pt3, normal), radius*0.2)
    loftResult = rs.AddLoftSrf([circle1, circle2, circle3])
    return loftResult # loftResult is a list

a = []
vshift = 0
for i in range(1, udiv):
    vshift += 0.01
    for j in range(vdiv):
        u = i/udiv
        v = j/vdiv + vshift
        if(v > 1.0): v = v%1.0
        pt = surf.PointAt(u,v)
        nml = surf.NormalAt(u,v)
        result = addTube(pt, nml, 0.6-u*0.5)
        a.extend(result) # result is a list
- 3-3. Line Branch Function   GH file

# This adds curves on the input curve rotated from tis tangent direction
# input type - curve : Curve (Item Access), count : int (Item Access), axis : Vector3d (Item Access), angle : float (Item Access), length : float (Item Access), frequency : float (Item Access)
import Rhino.Geometry as rg
import math

def curveOnCurve(crv, u, length, angle, axis, rollAngle):
    pt = crv.PointAt(u)
    tan = crv.TangentAt(u)
    ax = rg.Vector3d(axis)
    ax.Rotate(rollAngle, tan)
    tan.Rotate(angle, ax)
    tan.Unitize()
    pt2 = pt + tan * length
    tan.Rotate(angle, axis)
    pt3 = pt2 + tan * length
    return rg.NurbsCurve.Create(False, 2, [pt, pt2, pt3])

a = []
for i in range(count+1):
    u = i/count
    ang = angle/180*math.pi + math.sin(frequency*u*math.pi)*0.5
    len = length * (math.cos(frequency*u*math.pi)+1)/2
    crv = curveOnCurve(curve, u, len, ang, axis, u*math.pi)
    a.append(crv)
- 3-4a. Recursion with Line   GH file   3DM file

# this creates fractal lines by recursive function
# input type - line : Line (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import math

def recursiveLine(line, depth, resultList):
    pt1 = line.PointAt(0)
    pt2 = line.PointAt(0.15)
    pt3 = line.PointAt(0.65)
    pt4 = line.PointAt(0.65)
    pt5 = line.PointAt(1.0)
    dir = rg.Vector3d(pt5.X-pt1.X, pt5.Y-pt1.Y, pt5.Z-pt5.Z)
    dir.Rotate(math.pi/2, rg.Vector3d.ZAxis)
    pt3 += dir * 0.5
    line1 = rg.Line(pt1, pt2)
    line2 = rg.Line(pt2, pt3)
    line3 = rg.Line(pt3, pt4)
    line4 = rg.Line(pt4, pt5)
    resultList.append(line1)
    resultList.append(line4)
    if(depth>0):
        recursiveLine(line2, depth-1, resultList) # recursion
        recursiveLine(line3, depth-1, resultList) # recursion
    else:
        resultList.append(line2)
        resultList.append(line3)

# main
a = []
recursiveLine(line, 9, a)
- 3-4b. Recursion with Line   GH file

# this creates fractal lines by recursive function
# input type - line : Line (Item Access), div : int (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import math

def recursiveLine(line, depth, resultList):
    pt1 = line.PointAt(0)
    pt2 = line.PointAt(0.27)
    pt3 = line.PointAt(0.75)
    pt4 = line.PointAt(0.6)
    pt5 = line.PointAt(1)
    dir = rg.Vector3d(pt5.X - pt1.X, pt5.Y - pt1.Y, pt5.Z - pt1.Z) 
    dir.Rotate(math.pi*0.42, rg.Vector3d.ZAxis)
    pt3 += dir * 0.27
    line1 = rg.Line(pt1, pt2)
    line2 = rg.Line(pt2, pt3)
    line3 = rg.Line(pt3, pt4)
    line4 = rg.Line(pt4, pt5)
    if depth > 0:
        recursiveLine(line1, depth-1, resultList)
        recursiveLine(line2, depth-1, resultList)
        recursiveLine(line3, depth-1, resultList)
        recursiveLine(line4, depth-1, resultList)
    else:
        resultList.append(line1)
        resultList.append(line2)
        resultList.append(line3)
        resultList.append(line4)

a = []
recursiveLine(line, div, a)
- 3-4c. Recursion with Line   GH file

# this creates fractal lines by recursive function
# input type - line : Line (Item Access), div : int (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import math

def recursiveLine(line, depth, axis, resultList):
    pt1 = line.PointAt(0)
    pt2 = line.PointAt(0.3)
    pt3 = line.PointAt(0.3)
    pt4 = line.PointAt(0.6)
    pt5 = line.PointAt(0.6)
    pt6 = line.PointAt(1)
    dir = pt6 - pt1 
    dir2 = rg.Vector3d(dir)
    
    dir2.Rotate(math.pi*0.5, axis)
    dir2.Rotate(math.pi*0.5, dir)
    pt3 += dir2 * 0.5
    pt4 += dir2 * 0.5
    line1 = rg.Line(pt1, pt2)
    line2 = rg.Line(pt2, pt3)
    line3 = rg.Line(pt3, pt4)
    line4 = rg.Line(pt4, pt5)
    line5 = rg.Line(pt5, pt6)
    if depth > 0:
        axis = rg.Vector3d.CrossProduct(dir, dir2)
        recursiveLine(line1, depth-1, axis, resultList)
        recursiveLine(line2, depth-1, axis, resultList)
        recursiveLine(line3, depth-1, axis, resultList)
        recursiveLine(line4, depth-1, axis, resultList)
        recursiveLine(line5, depth-1, axis, resultList)
    else:
        resultList.append(line1)
        resultList.append(line2)
        resultList.append(line3)
        resultList.append(line4)
        resultList.append(line5)

a = []
recursiveLine(line, div, rg.Vector3d.ZAxis, a)
- 3-4d. Recursion with Line   GH file

# this creates fractal lines and surfaces by recursive function
# input type - line : Line (Item Access), div : int (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import math

def recursiveLine(line, depth, axis, resultList):
    pt1 = line.PointAt(0)
    pt2 = line.PointAt(0.1)
    pt3 = line.PointAt(0.7)
    pt4 = line.PointAt(0.8)
    pt5 = line.PointAt(1)
    dir = pt5 - pt1 
    dir2 = rg.Vector3d(dir) # copy
    dir2.Rotate(math.pi*0.5, axis)
    dir2.Rotate(math.pi*0.25, dir)
    pt3 += dir2 * 0.45
    line1 = rg.Line(pt1, pt2)
    line2 = rg.Line(pt2, pt3)
    line3 = rg.Line(pt3, pt4)
    line4 = rg.Line(pt4, pt5)
    srf = rg.NurbsSurface.CreateFromCorners(pt2,pt3,pt4)
    resultList.append(srf)
    if depth > 0:
        axis = rg.Vector3d.CrossProduct(dir, dir2)
        recursiveLine(line1, depth-1, axis, resultList)
        recursiveLine(line2, depth-1, axis, resultList)
        recursiveLine(line3, depth-1, axis, resultList)
        recursiveLine(line4, depth-1, axis, resultList)
    else:
        resultList.append(line1)
        resultList.append(line2)
        resultList.append(line3)
        resultList.append(line4)

a = []
recursiveLine(line, div, rg.Vector3d.ZAxis, a)
- 3-5a. Branching Lines with Recursion   GH file   3DM file

# this creates random branching lines with recursion
# input type - line : Line (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import random

def recursiveLine(line, depth, resultList):
    pt1 = line.PointAt(0)
    pt2 = line.PointAt(1)
    dir1 = rg.Vector3d(pt2.X-pt1.X, pt2.Y-pt1.Y, pt2.Z-pt1.Z)
    dir2 = rg.Vector3d(dir1) # copy

    dir1.Rotate(random.random()*0.4+0.1, rg.Vector3d.ZAxis) # random angle
    dir2.Rotate(random.random()*-0.4-0.1, rg.Vector3d.ZAxis)
    dir1 *= random.random()*0.2 + 0.8; # random scale
    dir2 *= random.random()*0.2 + 0.8;
    line1 = rg.Line(pt2, pt2+dir1)
    line2 = rg.Line(pt2, pt2+dir2)
    resultList.append(line1)
    resultList.append(line2)
    if(depth>0):
        if(random.random()<0.9): # random omission
            recursiveLine(line1, depth-1, resultList)
        if(random.random()<0.8): # random omission
            recursiveLine(line2, depth-1, resultList)

# main
a = []
recursiveLine(line, 10, a)
- 3-5b. Branching Lines with Recursion   GH file

# this creates random branching lines with recursion
# input type: line - Line (Item Access), div - int (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import random
import math

def recursiveLine(line, depth, resultList):
    pt1 = line.PointAt(0)
    pt2 = line.PointAt(1)
    dir1 = pt2 - pt1
    dir2 = rg.Vector3d(dir1) # copy

    dir1.Rotate(math.pi/3, rg.Vector3d.ZAxis) # random angle
    dir2.Rotate(-math.pi/3, rg.Vector3d.ZAxis)
    dir1 *= random.random()*0.2 + 0.7; # random scale
    dir2 *= random.random()*0.2 + 0.7;
    line1 = rg.Line(pt2, pt2+dir1)
    line2 = rg.Line(pt2, pt2+dir2)
    resultList.append(line1)
    resultList.append(line2)
    if(depth>0):
        if(random.random()<0.9): # random omission
            recursiveLine(line1, depth-1, resultList)
        if(random.random()<0.8): # random omission
            recursiveLine(line2, depth-1, resultList)

a = []
recursiveLine(line, div, a)
- 3-6. Rectangular Surface Recursion   GH file

# Rectangular surface recursion
# input type - surf : Surface (Item Access), offset : float (Item Access), scale : float (Item Access), percent : float (Item Access), recursion : int (Item Access)
import Rhino.Geometry as rg
import random

def recursiveSurf(surf, offs, depth):
    for i in range(2):
        for j in range(2):
            if random.random()*100 < percent:
                pt1 = surf.PointAt(i/2, j/2)
                pt2 = surf.PointAt((i+1)/2, j/2)
                pt3 = surf.PointAt((i+1)/2, (j+1)/2)
                pt4 = surf.PointAt(i/2, (j+1)/2)
                
                center = (pt1+pt2+pt3+pt4)/4
                pt1 = (pt1-center)*scale+center
                pt2 = (pt2-center)*scale+center
                pt3 = (pt3-center)*scale+center
                pt4 = (pt4-center)*scale+center
                
                n = surf.NormalAt((i+0.5)/2, (j+0.5)/2)
                n.Unitize()
                n *= offset * random.random()
                pt1 += n 
                pt2 += n 
                pt3 += n 
                pt4 += n 
                
                s = rg.NurbsSurface.CreateFromCorners(pt1,pt2,pt3,pt4)
                a.append(s)
                if depth > 1 and random.random()*100 < percent:
                    s.SetDomain(0, rg.Interval(0,1))
                    s.SetDomain(1, rg.Interval(0,1))
                    recursiveSurf(s, offs/2, depth-1)

a = []
if surf is not None:
    recursiveSurf(surf, offset, recursion)
- 3-7. Branching Lines and Boxes with Recursion   GH file

# this creates random branching lines with recursion
# input type: line - Line (Item Access), div - int (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import random
import math

def recursiveLine(line, depth, axis, resultList):
    pt1 = line.PointAt(0)
    pt2 = line.PointAt(1)
    dir0 = pt2 - pt1
    dir1 = rg.Vector3d(dir0) # copy
    dir2 = rg.Vector3d(dir0) # copy
    dir1.Rotate(math.pi/2, axis)
    dir1.Rotate(math.pi/2, dir0)
    dir2.Rotate(-math.pi/2, axis)
    dir2.Rotate(math.pi/2, dir0)
    dir1 *= random.random()*0.2 + 0.7; # random scale
    dir2 *= random.random()*0.2 + 0.7;
    line1 = rg.Line(pt2, pt2+dir1)
    line2 = rg.Line(pt2, pt2+dir2)
    resultList.append(line1)
    resultList.append(line2)
    axis = rg.Vector3d.CrossProduct(dir0,dir1)
    axis.Unitize()
    box = rg.Box(rg.Plane.WorldXY, [pt1, pt2, pt2+dir1, pt2+dir2, pt2+axis*dir1.Length/2])
    resultList.append(box)
    if(depth>0):
        if(random.random()<0.9): # random omission
            
            recursiveLine(line1, depth-1, axis, resultList)
        if(random.random()<0.8): # random omission
            #axis = rg.Vector3d.CrossProduct(dir0,dir2)
            recursiveLine(line2, depth-1, axis, resultList)

a = []
recursiveLine(line, div, rg.Vector3d.ZAxis, a)
- 3-8. Box Subdivision on Surface   GH file

# this generates subdivided boxes on a surface recursively
# input type - surf : Surface (Item Access), unum : int (Item Access), vnum : int (Item Access), offset : float (Item Access), recursion : float (Item Access), percent : float (Item Access)
import Rhino.Geometry as rg
import random

def interpolate(pt1, pt2, ratio):
    return pt1*(1-ratio) + pt2*ratio

def boxPt(pts, u, v, w):
    upt11 = interpolate(pts[1][0][0], pts[0][0][0], u)
    upt21 = interpolate(pts[1][1][0], pts[0][1][0], u)
    upt12 = interpolate(pts[1][0][1], pts[0][0][1], u)
    upt22 = interpolate(pts[1][1][1], pts[0][1][1], u)
    vpt1 = interpolate(upt21,upt11, v)
    vpt2 = interpolate(upt22,upt12, v)
    return interpolate(vpt2,vpt1,w)

def divideBox(pts, depth):
    pts2 = []
    for i in range(2):
        for j in range(2):
            for k in range(2):
                if random.random()*100 <= percent:
                    p111 = boxPt(pts, i/2, j/2, k/2)
                    p112 = boxPt(pts, i/2, j/2, (k+1)/2)
                    p121 = boxPt(pts, i/2, (j+1)/2, k/2)
                    p122 = boxPt(pts, i/2, (j+1)/2, (k+1)/2)
                    p211 = boxPt(pts, (i+1)/2, j/2, k/2)
                    p212 = boxPt(pts, (i+1)/2, j/2, (k+1)/2)
                    p221 = boxPt(pts, (i+1)/2, (j+1)/2, k/2)
                    p222 = boxPt(pts, (i+1)/2, (j+1)/2, (k+1)/2)
                    bpts = [[[p111,p112],[p121,p122]],[[p211,p212],[p221,p222]]]
                    if depth > 1 and random.random()*100 <= percent:
                        bpts2 = divideBox(bpts, depth-1)
                        pts2.extend(bpts2)
                    else:
                        pts2.append(bpts)
    return pts2

a = []
for i in range(unum):
    for j in range(vnum):
        p11 = surf.PointAt(i/unum, j/vnum)
        p12 = surf.PointAt(i/unum, (j+1)/vnum)
        p21 = surf.PointAt((i+1)/unum, j/vnum)
        p22 = surf.PointAt((i+1)/unum, (j+1)/vnum)

        n11 = surf.NormalAt(i/unum, j/vnum)
        n12 = surf.NormalAt(i/unum, (j+1)/vnum)
        n21 = surf.NormalAt((i+1)/unum, j/vnum)
        n22 = surf.NormalAt((i+1)/unum, (j+1)/vnum)
        
        n11.Unitize()
        n12.Unitize()
        n21.Unitize()
        n22.Unitize()
        q11 = p11 + n11*offset
        q12 = p12 + n12*offset
        q21 = p21 + n21*offset
        q22 = p22 + n22*offset
        
        boxPts = divideBox([[[p11,q11],[p12,q12]],[[p21,q21],[p22,q22]]], recursion)
        
        for b in boxPts:
            box = rg.Brep.CreateFromBox([b[0][0][0],b[1][0][0],b[1][1][0],b[0][1][0],b[0][0][1],b[1][0][1],b[1][1][1],b[0][1][1]])
            a.append(box)
- 3-9. Hexagonal Branching by Recursion   GH file

# Hexagonal recursive branches
# input type - hexPolyline : Polyline (Item Access), length : float (Item Access), scale : float (Item Access), shift : float (Item Access), percent : float (Item Access), recursion : int (Item Access)
import Rhino.Geometry as rg

def calcCenter(pts):
    cnt = rg.Point3d(0,0,0)
    for p in pts:
        cnt += p
    return cnt / len(pts)

def createHex(center, hpt1, hpt2, hpt3, normal):
    p1 = (hpt1 + hpt2)/2
    p2 = hpt2
    p3 = (hpt2+hpt3)/2
    p4 = (hpt3 + center)/2
    p5 = center
    p6 = (hpt1 + center)/2
    
    shiftvec = (p2 - p5) * shift
    q1 = p1 + normal + shiftvec
    q2 = p2 + normal + shiftvec
    q3 = p3 + normal + shiftvec
    q4 = p4 + normal + shiftvec
    q5 = p5 + normal + shiftvec
    q6 = p6 + normal + shiftvec
    
    cnt = calcCenter([q1,q2,q3,q4,q5,q6])
    q1 = (q1-cnt)*scale + cnt
    q2 = (q2-cnt)*scale + cnt
    q3 = (q3-cnt)*scale + cnt
    q4 = (q4-cnt)*scale + cnt
    q5 = (q5-cnt)*scale + cnt
    q6 = (q6-cnt)*scale + cnt
    
    cap1 = rg.NurbsSurface.CreateFromCorners(cnt, q1, q2, q3)
    cap2 = rg.NurbsSurface.CreateFromCorners(cnt, q3, q4, q5)
    cap3 = rg.NurbsSurface.CreateFromCorners(cnt, q5, q6, q1)
    capsurf.append(cap1)
    capsurf.append(cap2)
    capsurf.append(cap3)
    
    side = rg.NurbsSurface.CreateFromPoints([p1,p2,p3,p4,p5,p6,p1, q1,q2,q3,q4,q5,q6,q1], 2, 7, 1, 1)
    sidesurf.append(side)
    return rg.Polyline([q1,q2,q3,q4,q5,q6,q1])

def recursiveHex(hex, offset, depth):
    center = calcCenter([hex[0],hex[1],hex[2],hex[3],hex[4],hex[5]])
    nml = rg.Vector3d.CrossProduct(hex[2]-hex[0], hex[4]-hex[2])
    nml.Unitize()
    nml *= offset
    for i in range(3):
        if random.random()*100 < percent:
            hpt1 = hex[i*2]
            hpt2 = hex[(i*2+1)%6]
            hpt3 = hex[(i*2+2)%6]
            hex2 = createHex(center, hpt1, hpt2, hpt3, nml)
            if depth > 1:
                recursiveHex(hex2, offset*scale, depth-1)

sidesurf = []
capsurf = []
if hexPolyline is not None and hexPolyline.Count > 6:
    recursiveHex(hexPolyline, length, recursion)
- 3-10. Subdividing Mesh with Recursion   GH file   3DM file

# this subdivided mesh faces by recursive function
# input type - Mesh (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import random
import math

def subdivide(pt1, pt2, pt3, depth, resultList):
    if(depth == 0 or random.random()<0.1): # random omission
        v1 = rg.Point3f(pt1.X, pt1.Y, pt1.Z);
        v2 = rg.Point3f(pt2.X, pt2.Y, pt2.Z);
        v3 = rg.Point3f(pt3.X, pt3.Y, pt3.Z);
        resultList.append([v1,v2,v3])
    else:
        npt1 = (pt1+pt2)/2
        npt2 = (pt2+pt3)/2
        npt3 = (pt3+pt1)/2
        npt4 = (npt1+npt2+npt3)/3
        normal = normalVector(pt1,pt2,pt3)
        npt4 += normal*0.06*math.sqrt(depth)
        subdivide(npt1, pt1, npt3, depth-1, resultList)
        subdivide(npt2, pt2, npt1, depth-1, resultList)
        subdivide(npt3, pt3, npt2, depth-1, resultList)
        subdivide(npt1, npt2, npt4, depth-1, resultList)
        subdivide(npt2, npt3, npt4, depth-1, resultList)
        subdivide(npt3, npt1, npt4, depth-1, resultList)

def normalVector(pt1, pt2, pt3):
    vec1 = rg.Vector3d.Subtract(rg.Vector3d(pt2),rg.Vector3d(pt1))
    vec2 = rg.Vector3d.Subtract(rg.Vector3d(pt3),rg.Vector3d(pt1))
    return rg.Vector3d.CrossProduct(vec1, vec2)

# main
a = []
vertices = []
for face in mesh.Faces:
    pt1 = rg.Vector3d(mesh.Vertices[face.A])
    pt2 = rg.Vector3d(mesh.Vertices[face.B])
    pt3 = rg.Vector3d(mesh.Vertices[face.C])
    subdivide(pt1, pt2, pt3, 5, vertices)
    if(face.IsQuad):
        pt4 = rg.Vector3d(mesh.Vertices[face.D])
        subdivide(pt1, pt4, pt3, 5, vertices) # reverse order

mesh2 = rg.Mesh()
for vtx in vertices:
    idx = mesh2.Vertices.Count
    mesh2.Vertices.Add(vtx[0])
    mesh2.Vertices.Add(vtx[1])
    mesh2.Vertices.Add(vtx[2])
    mesh2.Faces.AddFace(idx, idx+1, idx+2)
a = mesh2




4. GH Python: Baking and Object Attributes

- 4-1. Baking Points with Colors   GH file

# bake points with colors
# input type - bool (Item Access)
import scriptcontext as sc
import Rhino.DocObjects as rd
import Rhino.Geometry as rg
import System.Drawing as sd
import Rhino

sc.doc = Rhino.RhinoDoc.ActiveDoc
if bake:
    xnum = 100
    ynum = 100
    for i in range(xnum):
        for j in range(ynum):
            pt = rg.Point3d(i,j,0)
            attr = rd.ObjectAttributes()
            attr.ColorSource = rd.ObjectColorSource.ColorFromObject
            attr.ObjectColor = sd.Color.FromArgb(i/xnum*255, j/ynum*255, 0)
            sc.doc.Objects.AddPoint(pt, attr)
- 4-2. Baking Curves with Colors   GH file

# bake curves with colors
# input type - bool (Item Access)
import scriptcontext as sc
import System.Drawing as sd
import Rhino.DocObjects as rd
import Rhino.Geometry as rg
import Rhino
import math

sc.doc = Rhino.RhinoDoc.ActiveDoc
if bake:
    num = 100
    for i in range(num):
        pt1 = rg.Point3d(i, 0, math.sin(i*0.2)*2)
        pt2 = rg.Point3d(i, 5, 2)
        pt3 = rg.Point3d(i, 10, math.cos(i*0.1)*2)
        curve = rg.NurbsCurve.Create(False, 2, [pt1, pt2, pt3])
        attr = rd.ObjectAttributes()
        attr.ColorSource = rd.ObjectColorSource.ColorFromObject
        attr.ObjectColor = sd.Color.FromArgb(i/num*255, (num-i)/num*255, 255)
        sc.doc.Objects.AddCurve(curve, attr)
- 4-3. Baking Surfaces with Simple Materials   GH file

# bake surfaces with simple materials
# input type - bool (Item Access)
import scriptcontext as sc
import System.Drawing as sd
import Rhino.DocObjects as rd
import Rhino.Geometry as rg
import Rhino

sc.doc = Rhino.RhinoDoc.ActiveDoc
if bake:
    xnum = 20
    ynum = 20
    for i in range(xnum):
        for j in range(ynum):
            pt1 = rg.Point3d(i, j, 0)
            pt2 = rg.Point3d(i+1, j, 0)
            pt3 = rg.Point3d(i+1, j+1, i*j/100)
            pt4 = rg.Point3d(i, j+1, 0)
            surface = rg.NurbsSurface.CreateFromCorners(pt1,pt2,pt3,pt4)
            materialIndex = sc.doc.Materials.Add()
            material = sc.doc.Materials[materialIndex]
            material.DiffuseColor = sd.Color.FromArgb(i/xnum*255, j/ynum*255, 255)
            material.CommitChanges()
            attr = rd.ObjectAttributes()
            attr.MaterialSource = rd.ObjectMaterialSource.MaterialFromObject
            attr.MaterialIndex = materialIndex
            sc.doc.Objects.AddSurface(surface, attr)
- 4-4. Baking Breps with Simple Materials   GH file

# bake breps with simple materials
# input type - bool (Item Access)
import scriptcontext as sc
import System.Drawing as sd
import Rhino.DocObjects as rd
import Rhino.Geometry as rg
import Rhino

sc.doc = Rhino.RhinoDoc.ActiveDoc
if bake:
    xnum = 10
    ynum = 10
    znum = 10
    for i in range(xnum):
        for j in range(ynum):
            for k in range(znum):
                size = (i/xnum+j/ynum+k/znum)/3
                box = rg.Box( rg.Plane.WorldXY, rg.Interval(i, i+size), rg.Interval(j,j+size), rg.Interval(k, k+size) )
                brep = rg.Brep.CreateFromBox(box)
                materialIndex = sc.doc.Materials.Add()
                material = sc.doc.Materials[materialIndex]
                material.DiffuseColor = sd.Color.FromArgb(i/xnum*255, j/ynum*255, k/znum*255)
                material.CommitChanges()
                attr = rd.ObjectAttributes()
                attr.MaterialSource = rd.ObjectMaterialSource.MaterialFromObject
                attr.MaterialIndex = materialIndex
                sc.doc.Objects.AddBrep(brep, attr)
- 4-5. Setting Materials by Attributes   GH file

# material attributes
# input type - bool (Item Access)
import scriptcontext as sc
import System.Drawing as sd
import Rhino.DocObjects as rd
import Rhino.Geometry as rg
import Rhino

sc.doc = Rhino.RhinoDoc.ActiveDoc
if bake:
    xnum = 20
    ynum = 20
    for i in range(xnum):
        for j in range(ynum):
            pt = rg.Point3d(i, j, 0)
            sphere = rg.Sphere(pt, 0.4)
            surface = rg.NurbsSurface.CreateFromSphere(sphere)
            materialIndex = sc.doc.Materials.Add()
            material = sc.doc.Materials[materialIndex]
            material.AmbientColor = sd.Color.FromArgb(0, 0, 0)
            material.DiffuseColor = sd.Color.FromArgb(255, 0, 0)
            material.EmissionColor = sd.Color.FromArgb(0, j/ynum*255, 0)
            material.ReflectionColor = sd.Color.FromArgb(255, 255, 255)
            material.SpecularColor = sd.Color.FromArgb(255, 255, i/xnum*255)
            material.Transparency = i/xnum
            material.TransparentColor = sd.Color.FromArgb(0, 0, 255)
            material.Shine = 1
            material.CommitChanges()
            attr = rd.ObjectAttributes()
            attr.MaterialSource = rd.ObjectMaterialSource.MaterialFromObject
            attr.MaterialIndex = materialIndex
            sc.doc.Objects.AddSurface(surface, attr)
- 4-6. Setting Materials by Name   GH file

* The material need to be loded into Rhino beforehand.

# setting materials by name
# input type - materialName: str, bake: bool (Item Access)
import scriptcontext as sc
import System.Drawing as sd
import Rhino.DocObjects as rd
import Rhino.Geometry as rg
import Rhino

sc.doc = Rhino.RhinoDoc.ActiveDoc
if bake:
    xnum = 20
    ynum = 20
    for i in range(xnum):
        for j in range(ynum):
            pt = rg.Point3d(i, j, 0)
            sphere = rg.Sphere(pt, 0.4)
            surface = rg.NurbsSurface.CreateFromSphere(sphere)
            
            materialIndex = sc.doc.Materials.Find(materialName, True)
            attr = rd.ObjectAttributes()
            if materialIndex >= 0:
                attr.MaterialSource = rd.ObjectMaterialSource.MaterialFromObject
                attr.MaterialIndex = materialIndex
            else:
                print "material " + materialName + " not found"
            sc.doc.Objects.AddSurface(surface, attr)
- 4-7. Baking into Layers   GH file

# baking into layers
# input type - bool (Item Access)
import scriptcontext as sc
import System.Drawing as sd
import Rhino.DocObjects as rd
import Rhino.Geometry as rg
import Rhino
import math

sc.doc = Rhino.RhinoDoc.ActiveDoc
if bake:
    num = 40
    for i in range(num):
        pt1 = rg.Point3d(-math.sin(i*0.2), -math.sin(i*0.2), i*0.2)
        pt2 = rg.Point3d(math.cos(i*0.2), -math.sin(i*0.2), i*0.2)
        pt3 = rg.Point3d(math.sin(i*0.2), math.cos(i*0.2), i*0.2)
        pt4 = rg.Point3d(-math.cos(i*0.2), math.sin(i*0.2), i*0.2)
        curve = rg.NurbsCurve.Create(True, 1, [pt1, pt2, pt3, pt4])
        attr = rd.ObjectAttributes()
        attr.ColorSource = rd.ObjectColorSource.ColorFromObject
        attr.ObjectColor = sd.Color.FromArgb((num-i)/num*255, 255, i/num*255)
        layerName = "layer_" + str(i)
        layerIndex = sc.doc.Layers.Find(layerName, True)
        if layerIndex < 0:
            layer = rd.Layer()
            layer.Name = layerName
            layerIndex = sc.doc.Layers.Add(layer)
        attr.LayerIndex = layerIndex
        sc.doc.Objects.AddCurve(curve, attr)
- 4-8. Baking Input Objects with Attributes   GH file

# bake objects with attributes
# input type - obj: GeometryBase, objName: str, layerName: str, displayColor: Color, plotColor: Color, plotWeight: float, ambientColor: color, diffuseColor: Color, emissionColor: Color, specularColor: Color, transparency: float, shine: float, wireDensity: int, bake: bool (Item Access)
import scriptcontext as sc
import System.Drawing as sd
import Rhino.DocObjects as rd
import Rhino.Geometry as rg
import Rhino

sc.doc = Rhino.RhinoDoc.ActiveDoc
if bake and obj is not None:
    attr = rd.ObjectAttributes()
    
    if objName is not None: # setting object name
        attr.Name = objName
    
    if layerName is not None: # setting layer
        if rd.Layer.IsValidName(layerName):
            layerIndex = sc.doc.Layers.Find(layerName, True)
            if layerIndex < 0: # if the layer doesn't exist
                layer = rd.Layer()
                layer.Name = layerName
                layerIndex = sc.doc.Layers.Add(layer)
            attr.LayerIndex = layerIndex
     
    if displayColor is not None: # setting display color
        attr.ColorSource = rd.ObjectAttributes.ColorSource.PropertyType.ColorFromObject
        attr.ObjectColor = displayColor
    
    if plotColor is not None: # setting plot color
        attr.PlotColorSource = rd.ObjectAttributes.PlotColorSource.PropertyType.PlotColorFromObject
        attr.PlotColor = plotColor
    
    if plotWeight >= 0: # setting plot weight
        attr.PlotWeightSource = rd.ObjectAttributes.PlotWeightSource.PropertyType.PlotWeightFromObject
        attr.PlotWeight = plotWeight
    
    if ambientColor is not None or diffuseColor is not None or emissionColor is not None or specularColor is not None or transparency >= 0 or shine >=0: # setting material
        materialIndex = sc.doc.Materials.Add()
        material = sc.doc.Materials[materialIndex]
        if ambientColor is not None:
            material.AmbientColor = ambientColor
        if diffuseColor is not None:
            material.DiffuseColor = diffuseColor
        if emissionColor is not None:
            material.EmissionColor = emissionColor
        if specularColor is not None:
            material.SpecularColor = specularColor
        if transparency >= 0:
            material.Transparency = transparency
        if shine >= 0:
            material.Shine = shine
        material.CommitChanges()
        attr.MaterialSource = rd.ObjectMaterialSource.MaterialFromObject
        attr.MaterialIndex = materialIndex
    
    if wireDensity > 0 or wireDensity == -1: # setting wire density
        attr.WireDensity = wireDensity
    
    # bake by type
    type = obj.ObjectType
    if type == rd.ObjectType.Point:
        sc.doc.Objects.AddPoint(obj.Location, attr)
    elif type == rd.ObjectType.Curve:
        sc.doc.Objects.AddCurve(obj, attr)
    elif type == rd.ObjectType.Surface:
        sc.doc.Objects.AddSurface(obj, attr)
    elif type == rd.ObjectType.Brep:
        sc.doc.Objects.AddBrep(obj, attr)
    elif type == rd.ObjectType.Mesh:
        sc.doc.Objects.AddMesh(obj, attr)
    else:
        print "object type "+str(type)+ " is not baked"
- 4-9. Panelization with Materials (1)   GH file   3DM file

# Panelization with materials
# input type: surf : Surface, unum : int, vnum : int, bake : bool
import scriptcontext as sc
import System.Drawing as sd
import Rhino.DocObjects as rd
import Rhino.Geometry as rg
import Rhino
import math

sc.doc = Rhino.RhinoDoc.ActiveDoc
panels = []
for i in range(unum):
    for j in range(vnum):
        u = i/unum
        v = j/vnum
        pt1 = surf.PointAt(u, v)
        pt2 = surf.PointAt(u + (2-u)/unum, v + 0.1/vnum)
        pt3 = surf.PointAt(u + (1.5+u)/unum, v + 1/vnum)
        pt4 = surf.PointAt(u + 0.3/unum, v + 1/vnum)
        n1 = surf.NormalAt(u, v)
        n2 = surf.NormalAt(u + (2.5-u)/unum, v + 0.1/vnum)
        n3 = surf.NormalAt(u + (1.5+u)/unum, v + 1/vnum)
        n4 = surf.NormalAt(u + 0.3/unum, v + 1/vnum)
        
        pt1 += n1*0.2
        pt2 += n2*1.2
        pt3 += n3*1.8
        pt4 += n4*0.3
        
        panel = rg.NurbsSurface.CreateFromCorners(pt1, pt2, pt3, pt4)
        panels.append(panel)
        
        if bake:
            materialIndex = sc.doc.Materials.Add()
            material = sc.doc.Materials[materialIndex]
            material.AmbientColor = sd.Color.FromArgb(0, 0, 0)
            material.DiffuseColor = sd.Color.FromArgb(255-u*255, u*255, math.fabs(1-2*v)*255)
            material.EmissionColor = sd.Color.FromArgb(0, u*30, 0)
            material.SpecularColor = sd.Color.FromArgb(u*255, 0, u*255)
            material.Transparency = u
            material.Shine = 1-u
            material.CommitChanges()
            attr = rd.ObjectAttributes()
            attr.MaterialSource = rd.ObjectMaterialSource.MaterialFromObject
            attr.MaterialIndex = materialIndex
            sc.doc.Objects.AddSurface(panel, attr)
- 4-10. Panelization with Materials (2)   GH file   3DM file

# This makes surface panels by 3 points on an input surface
# input type: surf - Surface, unum - int, vnum - int
# This makes surface panels by 3 points on an input surface
# input type: surf - Surface, unum - int, vnum - int
import scriptcontext as sc
import System.Drawing as sd
import Rhino.DocObjects as rd
import Rhino.Geometry as rg
import Rhino
import math

def stretchTriangle(pt1, pt2, pt3, nml, stretchRatio):
    pt4 = pt2 + (pt2-pt1)*stretchRatio + nml
    return rg.NurbsSurface.CreateFromPoints([pt1,pt2,pt4, pt3,pt2,pt4], 2, 3, 1, 2)

sc.doc = Rhino.RhinoDoc.ActiveDoc
panels = []
for i in range(unum):
    for j in range(vnum):
        if (i+j)%2 == 0:
            u = i/unum
            v = j/vnum
            pt1 = surf.PointAt(u, v)
            pt2 = surf.PointAt(u + 1/unum, v - 1/vnum)
            pt3 = surf.PointAt(u + 1/unum, v + 1/vnum)
            pt4 = surf.PointAt(u + 1/unum, v + 0.5/vnum)
            nml = surf.NormalAt(u, v)
            
            panel1 = stretchTriangle(pt1, pt2, pt3+nml*0.5, nml*2*u, 4*u+0.5)
            panels.append(panel1)
            panel2 = stretchTriangle(pt1, pt4, pt2, nml*u, 2*u+0.5)
            panels.append(panel2)
            
            if bake:
                materialIndex1 = sc.doc.Materials.Add()
                material1 = sc.doc.Materials[materialIndex1]
                if (i+j)%4 == 0:
                    material1.DiffuseColor = sd.Color.FromArgb(255-u*255, u*255, math.fabs(1-2*v)*255)
                    material1.EmissionColor = sd.Color.FromArgb(0, u*30, 0)
                    material1.SpecularColor = sd.Color.FromArgb(u*255, 0, u*255)
                    material1.Shine = 0.25
                else:
                    material1.DiffuseColor = sd.Color.FromArgb(0,0,128)
                material1.Transparency = 0.25
                material1.CommitChanges()
                attr1 = rd.ObjectAttributes()
                attr1.MaterialSource = rd.ObjectMaterialSource.MaterialFromObject
                attr1.MaterialIndex = materialIndex1
                sc.doc.Objects.AddSurface(panel1, attr1)
                
                materialIndex2 = sc.doc.Materials.Add()
                material2 = sc.doc.Materials[materialIndex2]
                material2.DiffuseColor = sd.Color.FromArgb(math.fabs(1-2*v)*255, (1-math.fabs(1-2*v))*128, 0)
                material2.CommitChanges()
                attr2 = rd.ObjectAttributes()
                attr2.MaterialSource = rd.ObjectMaterialSource.MaterialFromObject
                attr2.MaterialIndex = materialIndex2
                sc.doc.Objects.AddSurface(panel2, attr2)




5. GH Python: Object-Oriented Programming

- 5-1. Defining A Class with A Constructor   GH file

# defining a custom class
# input type - Point3d (List Access)

class Class1:
    # __init__ is a special method to initialize a class instance (constructor)
    def __init__(self, pt):
        self.center = pt

for pt in pts:
    obj = Class1(pt)
- 5-2. Adding A Method   GH file

# defining a custom class
# input type - Point3d (List Access)
import rhinoscriptsyntax as rs

class Class1:
    def __init__(self, pt):
        self.center = pt
        self.radius = 0.5

    def getCircle(self):
        circle = rs.AddCircle(self.center, self.radius)
        a.append(circle)

a = []
for pt in pts:
    obj = Class1(pt)
    obj.getCircle()
- 5-3. Adding A Method with A Return Value   GH file

# defining a custom class
# input type - Point3d (List Access)
import rhinoscriptsyntax as rs

class Class1:
    def __init__(self, pt):
        self.center = pt
        self.radius = 0.5

    def getCircle(self):
        return rs.AddCircle(self.center, self.radius) # returns a circle

a = []
for pt in pts:
    obj = Class1(pt)
    a.append( obj.getCircle() )
- 5-4. Adding More Methods   GH file

# defining a custom class
# input type - Point3d (List Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg

class Class1:
    def __init__(self, pt):
        self.center = pt
        self.radius = 0.5

    def getCircle(self):
        return rs.AddCircle(self.center, self.radius)

    def getSquare(self):
        plane = rg.Plane(self.center + rg.Point3d(-self.radius,-self.radius,0),rg.Vector3d.ZAxis)
        return rs.AddRectangle(plane, self.radius*2, self.radius*2)

a = []
for pt in pts:
    obj = Class1(pt)
    a.append( obj.getCircle() )
    a.append( obj.getSquare() )
- 5-5. Class As Geometric Module(1)   GH file

# defining a custom class
# input type - Point3d (List Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg

class CircleSequence:
    def __init__(self, pt, dir):
        self.center = pt
        self.dir = dir
        self.radius = 0.2

    def generate(self):
        for i in range(10):
            circle = rs.AddCircle(self.center, self.radius)
            self.dir *= 0.9
            self.radius *= 0.8
            self.dir.Rotate(0.2, rg.Vector3d.ZAxis)
            self.center += self.dir
            a.append(circle)

a = []
for pt in pts:
    obj = CircleSequence(pt, rg.Vector3d(0,0.2,0))
    obj.generate()
- 5-6. Class As Geometric Module(2)   GH file

# defining a custom class
# input type - Point3d (List Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import random
import math

class CircleSequence:
    def __init__(self, pt, dir):
        self.center = pt
        self.dir = dir
        self.radius = 0.2

    def generate(self):
        for i in range(10):
            circle = rs.AddCircle(self.center, self.radius)
            self.dir *= 0.9
            self.radius *= 0.8
            self.dir.Rotate(0.2, rg.Vector3d.ZAxis)
            self.center += self.dir
            a.append(circle)

class SpiralCircles:
    def __init__(self, pt, size, count):
        self.center = pt
        dir = rg.Vector3d(0,size,0)
        self.arms = []
        for i in range(count):
            arm = CircleSequence(pt, rg.Vector3d(dir))
            self.arms.append(arm)
            dir.Rotate(math.pi*2/count, rg.Vector3d.ZAxis)
    
    def generate(self):
        for arm in self.arms:
            arm.generate() 

a = []
for pt in pts:
    size = random.random()*0.3 + 0.1
    count = int(random.random()*10)+2
    obj = SpiralCircles(pt, size, count)
    obj.generate()
- 5-7. Class As Geometric Module(3)   GH file

# defining a custom class
# input type - Point3d (List Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import random

class HierarchyBoxes:
    def __init__(self, pt, size, level):
        self.pt = pt
        self.size = size
        self.child = CornerBoxes(pt, size, size/2, level-1)
    def generate(self):
        box = rg.Box(rg.Plane.WorldXY, [self.pt, rg.Point3d(pt)+rg.Point3d(self.size,self.size,self.size)])
        a.append(box)
        self.child.generate()

class CornerBoxes:
    def __init__(self, pt, size1, size2, level):
        self.pt = pt
        self.size1 = size1
        self.size2 = size2
        self.children = []
        if(level>1):
            child1 = CornerBoxes(rg.Point3d(pt)+rg.Point3d(-size2,-size2,-size2), size2, size2/2, level-1)
            child2 = CornerBoxes(rg.Point3d(pt)+rg.Point3d(size1,-size2,-size2),  size2, size2/2, level-1)
            child3 = CornerBoxes(rg.Point3d(pt)+rg.Point3d(-size2,size1,-size2),  size2, size2/2, level-1)
            self.children.append(child1)
            self.children.append(child2)
            self.children.append(child3)
    
    def makeBox(self, corner):
        box = rg.Box(rg.Plane.WorldXY, [corner, rg.Point3d(corner)+rg.Point3d(-self.size2,-self.size2,-self.size2)])
        a.append(box)

    def generate(self):
        self.makeBox(self.pt)
        self.makeBox(rg.Point3d(self.pt) + rg.Point3d(self.size1+self.size2, 0, 0))
        self.makeBox(rg.Point3d(self.pt) + rg.Point3d(0, self.size1+self.size2, 0))
        for c in self.children:
            c.generate()

a = []
for pt in pts:
    level = int(random.random()*5)+1
    obj = HierarchyBoxes(pt, 0.5, level)
    obj.generate()
- 5-8. Class As Geometric Module(4)   GH file

# defining a custom class
# input type - Point3d (List Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import random

class AngleSequence:
    def __init__(self, pt, dir, width, count):
        self.pt = pt
        self.width = width
        self.dir = dir
        self.count = count

    def generate(self):
        ndir = rg.Vector3d.CrossProduct(self.dir, rg.Vector3d.ZAxis)
        wdir = rg.Vector3d.CrossProduct(self.dir, ndir)
        wdir.Unitize()
        wdir *= self.width/2
        pt1 = rg.Point3d(self.pt)-wdir
        pt2 = rg.Point3d(self.pt)
        pt3 = rg.Point3d(self.pt)+wdir
        plines = []
        for i in range(self.count):
            pline = rg.PolylineCurve([pt1,pt2,pt3])
            plines.append(pline)
            a.append(pline)
            pt1 += self.dir
            pt2 += self.dir*1.5
            pt3 += self.dir
        return plines

class TiltedExtrusion:
    def __init__(self, pt, dir, width, count):
        self.angles = AngleSequence(pt,dir,width,count)
        self.dir = rg.Vector3d(dir)
        
    def loft(self, pline, dir):
        pline2 = pline.Duplicate()
        pline2.Translate(dir.X, dir.Y, dir.Z)
        brep = rg.Brep.CreateFromLoft([pline,pline2], rg.Point3d.Unset, rg.Point3d.Unset, rg.LoftType.Straight, False)
        a.extend(brep)
        
    def generate(self):
        plines = self.angles.generate()
        for pl in plines:
            self.loft(pl, self.dir)
            self.dir.Rotate(0.1, rg.Vector3d.ZAxis)
            self.dir *= 0.95

class SpiralAggregation:
    def __init__(self, pt, count):
        self.children = []
        dir = rg.Vector3d(0.2, 0, 0.01)
        for i in range(count):
            len = int(random.random()*5)+10
            width = random.random()*1.5+1.0
            extrusion = TiltedExtrusion(rg.Point3d(pt), rg.Vector3d(dir)+rg.Vector3d(0,0,0.08), width, len)
            self.children.append(extrusion)
            dir.Rotate(0.5, rg.Vector3d.ZAxis)
            pt += dir*len
        
    def generate(self):
        for c in self.children:
            c.generate()

a = []
for pt in pts:
    obj = SpiralAggregation(pt, 100)
    obj.generate()




6. GH Python: Connective Module

- 6-1. Connective Module 1: Connection by Distance   GH file

# defining a connective module
# input type - pts : Point3d (List Access), threshold : float (Item Access)
import rhinoscriptsyntax as rs

class Module1:
    def __init__(self, pt):
        self.pt = pt
        self.links = []
    
    def connect(self, modules):
        for m in modules:
            if m is not self:
                dist = m.pt.DistanceTo(self.pt)
                if dist < threshold:
                    self.links.append(m)
    
    def generate(self):
        for m in self.links:
            line = rs.AddLine(self.pt, m.pt)
            lines.append(line)

lines = []
modules = []
# create modules
for pt in pts: 
    m = Module1(pt)
    modules.append(m)
# connect modules
for m in modules:
    m.connect(modules)
# generate geometries
for m in modules:
    m.generate()
- 6-2. Connective Module 2 : Sort and Maximum Link Count   GH file

# defining a connective module
# input type - pts : Point3d (List Access), threshold : float (Item Access), maxLink : int (Item Access)
# defining a connective module
import rhinoscriptsyntax as rs

class Module1:
    def __init__(self, pt):
        self.pt = pt
        self.links = []
    
    def connect(self, modules):
        #sort list by distance
        sorted(modules, key=lambda m: m.pt.DistanceTo(self.pt))
        for m in modules:
            if len(self.links) >= maxLink:
                break
            if m is not self and m not in self.links:
                dist = m.pt.DistanceTo(self.pt)
                if dist < threshold and len(m.links) < maxLink:
                    self.links.append(m)
                    m.links.append(self)
    
    def generate(self):
        for m in self.links:
            line = rs.AddLine(self.pt, m.pt)
            lines.append(line)

lines = []
modules = []
for pt in pts: 
    m = Module1(pt)
    modules.append(m)
for m in modules:
    m.connect(modules)
for m in modules:
    m.generate()
- 6-3. Connective Module 3 : Tree Output   GH file

# defining a connective module
# input type - pts : Point3d (List Access), threshold : float (Item Access), maxLink : int (Item Access)
import Rhino.Geometry as rg
# importing grasshopper tree classes
import clr
clr.AddReference("Grasshopper")
from Grasshopper.Kernel.Data import GH_Path
from Grasshopper import DataTree

class Module1:
    def __init__(self, pt):
        self.pt = pt
        self.links = []
    
    def connect(self, modules):
        sorted(modules, key=lambda m: m.pt.DistanceTo(self.pt))
        for m in modules:
            if len(self.links) >= maxLink:
                break # finish loop
            if m is not self and m not in self.links:
                dist = m.pt.DistanceTo(self.pt)
                if dist < threshold and len(m.links) < maxLink:
                    self.links.append(m)
                    m.links.append(self)
    
    def generate(self):
        id = center.BranchCount
        center.Add(self.pt, GH_Path(id)) # add one data in a branch
        linkNum = len(self.links)
        armPts = []
        for i in range(linkNum):
            pt2 = (self.pt + self.links[i].pt)/2 # mid point
            armPts.append(pt2)
        arm.AddRange(armPts, GH_Path(id)) # add multiple data in a branch

# initialize output points in tree structure
center = DataTree[rg.Point3d]()
arm = DataTree[rg.Point3d]()
modules = []
for pt in pts: 
    m = Module1(pt)
    modules.append(m)
for m in modules:
    m.connect(modules)
for m in modules:
    m.generate()
- 6-4. Connective Module 4 : 3 Points Output   GH file

# defining a connective module
# input type - pts : Point3d (List Access), threshold : float (Item Access), maxLink : int (Item Access)
import Rhino.Geometry as rg
import clr
clr.AddReference("Grasshopper")
from Grasshopper.Kernel.Data import GH_Path
from Grasshopper import DataTree

class Module1:
    def __init__(self, pt):
        self.pt = pt
        self.links = []
    
    def connect(self, modules):
        sorted(modules, key=lambda m: m.pt.DistanceTo(self.pt))
        for m in modules:
            if len(self.links) >= maxLink:
                break
            if m is not self and m not in self.links:
                dist = m.pt.DistanceTo(self.pt)
                if dist < threshold and len(m.links) < maxLink:
                    self.links.append(m)
                    m.links.append(self)
    
    def generate(self):
        id = center.BranchCount
        center.Add(self.pt, GH_Path(id))
        linkNum = len(self.links)
        for i in range(linkNum):
            pt2 = (self.pt + self.links[i].pt)/2 # mid point
            for j in range(i+1, linkNum):
                pt3 = (self.pt + self.links[j].pt)/2 # mid point
                arm3Pts.AddRange([pt2, self.pt, pt3], GH_Path(id,i,j)) # add 2 arm and center

center = DataTree[rg.Point3d]()
arm3Pts = DataTree[rg.Point3d]()
modules = []
for pt in pts: 
    m = Module1(pt)
    modules.append(m)
for m in modules:
    m.connect(modules)
for m in modules:
    m.generate()
- 6-5. Connective Module 5 : Face Points Output   GH file

# defining a connective module
# input type - pts : Point3d (List Access), threshold : float (Item Access), maxLink : int (Item Access)# importing grasshopper tree classes
import Rhino.Geometry as rg
import clr
clr.AddReference("Grasshopper")
from Grasshopper.Kernel.Data import GH_Path
from Grasshopper import DataTree

class Module1:
    def __init__(self, pt):
        self.pt = pt
        self.links = []
    
    def connect(self, modules):
        #sort list by distance
        sorted(modules, key=lambda m: m.pt.DistanceTo(self.pt))
        for m in modules:
            if len(self.links) >= maxLink:
                break # finish loop
            if m is not self and m not in self.links:
                dist = m.pt.DistanceTo(self.pt)
                if dist < threshold and len(m.links) < maxLink:
                    self.links.append(m)
                    m.links.append(self)
    
    def generate(self):
        id = center.BranchCount
        center.Add(self.pt, GH_Path(id))
        linkNum = len(self.links)
        for i in range(linkNum):
            pt2 = (self.pt + self.links[i].pt)/2
            for j in range(i+1, linkNum):
                pt3 = (self.pt + self.links[j].pt)/2
                for k in range(j+1, linkNum):
                    pt4 = (self.pt + self.links[k].pt)/2
                    armFacePts.AddRange([pt2, pt3, pt4], GH_Path(id,i,j,k)) # add 3 face pts

center = DataTree[rg.Point3d]()
armFacePts = DataTree[rg.Point3d]()
modules = []
for pt in pts: 
    m = Module1(pt)
    modules.append(m)
for m in modules:
    m.connect(modules)
for m in modules:
    m.generate()
- 6-6. Connective Module 6 : Point Output for Specific Geometry Component   GH file

# defining a connective module
# input type - pts : Point3d (List Access), threshold : float (Item Access), maxLink : int (Item Access), depthRatio : float (Item Access), shrinkRatio : float (Item Access)
import Rhino.Geometry as rg
import clr
clr.AddReference("Grasshopper")
from Grasshopper.Kernel.Data import GH_Path
from Grasshopper import DataTree

class Module1:
    def __init__(self, pt):
        self.pt = pt
        self.links = []
    
    def connect(self, modules):
        #sort list by distance
        sorted(modules, key=lambda m: m.pt.DistanceTo(self.pt))
        for m in modules:
            if len(self.links) >= maxLink:
                break 
            if m is not self and m not in self.links:
                dist = m.pt.DistanceTo(self.pt)
                if dist < threshold and len(m.links) < maxLink:
                    self.links.append(m)
                    m.links.append(self)
    
    def generate(self):
        linkNum = len(self.links)
        if linkNum != 3:
            return # do nothing if less than 3 links
        self.armLength = 0 # total distance to links
        for m in self.links:
            self.armLength += m.pt.DistanceTo(self.pt)
        tricenter = (self.links[0].pt+self.links[1].pt+self.links[2].pt)/3 # center of triangle 
        normal = rg.Vector3d.CrossProduct(self.links[1].pt-self.links[0].pt, self.links[2].pt-self.links[0].pt) # normal vector of triangle 
        normal.Unitize()
        normal *= self.armLength * depthRatio # normal depth is changed by link distance 
        for i in range(linkNum):
            spts = []
            spts.append( (self.links[i].pt + self.pt)/2 ) # mid point
            sp = self.links[i].pt * 0.4 + self.pt * 0.6  # first radial point
            spts.append(sp)
            for j in range(30): # generating spiral points
                vec = sp - self.pt 
                vec.Rotate(0.5, normal)
                sp = vec*shrinkRatio + self.pt + normal/30
                spts.append(sp)
            spiralPts.AddRange(spts, GH_Path(self.id, i)) # using id number

spiralPts = DataTree[rg.Point3d]()
modules = []
for pt in pts: 
    m = Module1(pt)
    m.id = len(modules) # setting id number
    modules.append(m)
for m in modules:
    m.connect(modules)
for m in modules:
    m.generate()
- 6-7. Connective Module 7 : Different Module Types and Links   GH file

# defining a connective module
# input type - pts : Point3d (List Access), threshold : float (Item Access), maxLink : int (Item Access)
import Rhino.Geometry as rg
import random
import clr
clr.AddReference("Grasshopper")
from Grasshopper.Kernel.Data import GH_Path
from Grasshopper import DataTree

class Module1:
    def __init__(self, pt):
        self.pt = pt
        self.links = []
        if random.random()<0.5:
            self.type = 1
        else:
            self.type = 2
    
    def connect(self, modules):
        #sort list by distance
        sorted(modules, key=lambda m: m.pt.DistanceTo(self.pt))
        for m in modules:
            if len(self.links) >= maxLink:
                break
            if m is not self and m not in self.links:
                dist = m.pt.DistanceTo(self.pt)
                if dist < threshold and len(m.links) < maxLink:
                    self.links.append(m)
                    m.links.append(self)
    
    def generate(self):
        linkNum = len(self.links)
        if self.type == 1:
            center1.Add(self.pt, GH_Path(self.id))
        else:
            center2.Add(self.pt, GH_Path(self.id))
        for i in range(linkNum):
            mpt = (self.pt + self.links[i].pt)/2
            if self.type == 1 and self.links[i].type == 1: # link 1-1
                arm1_1.AddRange([mpt, self.pt], GH_Path(self.id, i))
            elif self.type == 1 and self.links[i].type == 2: #link 1-2
                arm1_2.AddRange([mpt, self.pt], GH_Path(self.id, i))
            elif self.type == 2 and self.links[i].type == 1: #link 2-1
                arm2_1.AddRange([mpt, self.pt], GH_Path(self.id, i))
            elif self.type == 2 and self.links[i].type == 2: #link 2-2
                arm2_2.AddRange([mpt, self.pt], GH_Path(self.id, i))

center1 = DataTree[rg.Point3d]()
center2 = DataTree[rg.Point3d]()
arm1_1 = DataTree[rg.Point3d]()
arm1_2 = DataTree[rg.Point3d]()
arm2_1 = DataTree[rg.Point3d]()
arm2_2 = DataTree[rg.Point3d]()

modules = []
for pt in pts: 
    m = Module1(pt)
    m.id = len(modules) # setting id number
    modules.append(m)
for m in modules:
    m.connect(modules)
for m in modules:
    m.generate()




7. GH Python: Custom Subdivisions

- 7-1. Vertex Control 1: Offset Vertices   GH file

# offset each vertex randomly and create a new mesh
# input type - mesh : Mesh (Item Access), depth : float (Item Access)
import Rhino.Geometry as rg
import random

def offsetVertex(mesh):
    mesh2 = rg.Mesh() # create a new mesh

    vtx = mesh.Vertices.ToPoint3dArray() # get all vertices in a list
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * depth * random.random() #offset randomly
        mesh2.Vertices.Add(vtx2)
    
    mesh2.Faces.AddFaces(mesh.Faces) # add all faces at once
    mesh2.Normals.ComputeNormals()
    return mesh2

a = offsetVertex(mesh)
- 7-2. Vertex Control 2: Randomize Vertices   GH file

# move each vertex randomly and create a new mesh
# input type - mesh : Mesh (Item Access), depth : float (Item Access)
import Rhino.Geometry as rg
import random

def randomizeVertex(mesh):
    mesh2 = rg.Mesh() # create a new mesh
    
    vtx = mesh.Vertices.ToPoint3dArray() # get all vertices in a list
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(random.uniform(-1,1)*depth, random.uniform(-1,1)*depth, random.uniform(-1,1)*depth) #random xyz move
        mesh2.Vertices.Add(vtx2)
    
    mesh2.Faces.AddFaces(mesh.Faces) # add all faces at once
    mesh2.Normals.ComputeNormals()
    return mesh2

a = randomizeVertex(mesh)
- 7-3. Vertex Control 3: Move Vertices by Attractors   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), depth : float (Item Access), atractors : Point3d (List Access), threshold : float (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg

def moveVertexByAttractor(mesh):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + attractorVector(vtx[i]) * depth #add attractor vector
        mesh2.Vertices.Add(vtx2)
    
    mesh2.Faces.AddFaces(mesh.Faces)
    mesh2.Normals.ComputeNormals()
    return mesh2

def attractorVector(pos):
    minDist = -1
    for attr in attractors: # find closest attractor
        dist = rs.Distance(pos, attr)
        if( minDist < 0 or dist < minDist ):
            minDist = dist
            closest = attr
    if(minDist < threshold):
        vec = pos - closest
        vec *= (threshold - minDist)/(threshold) # length from 0 to 1
        vec.Unitize()
        return vec
    return rg.Vector3d(0,0,0) # return zero vector if out of threshold

a = moveVertexByAttractor(mesh)
- 7-4. Subdivision 1: Triangle Center Subdivision   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recureionLevel : int (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access)
import Rhino.Geometry as rg

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh() #create new mesh
    
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 #offset vertex
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): #quad mesh
            mesh2.Faces.AddFace(mesh.Faces[i]) #put original face
        else: #triangular mesh
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            v4 = (v1 + v2 + v3)/3 #center point
            v4 += rg.Vector3d(mesh.FaceNormals[i]) * offset2 #offset new vertex
            
            vidx4 = mesh2.Vertices.Add(v4) #add new vertex and get index number
            mesh2.Faces.AddFace(vidx1,vidx2,vidx4) #subdivided trianle
            mesh2.Faces.AddFace(vidx2,vidx3,vidx4) #subdivided trianle
            mesh2.Faces.AddFace(vidx3,vidx1,vidx4) #subdivided trianle
    
    mesh2.Normals.ComputeNormals()
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

a = subdivide(mesh,recursionLevel,offsetDepth1,offsetDepth2)
- 7-5. Subdivision 2: Triangle Subdivision with Vector Interpolation   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access)
import Rhino.Geometry as rg

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh() #create new mesh
    
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 #offset vertex
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): #quad mesh
            mesh2.Faces.AddFace(mesh.Faces[i]) #put original face
        else: #triangular mesh
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            v4 = interpolateTriangle(v1,v2,v3,0.4,0.5) # point inside triangle
            v4 += rg.Vector3d(mesh.FaceNormals[i]) * offset2 #offset new vertex
            
            vidx4 = mesh2.Vertices.Add(v4) #add new vertex and get index number
            mesh2.Faces.AddFace(vidx1,vidx2,vidx4) #subdivided trianle
            mesh2.Faces.AddFace(vidx2,vidx3,vidx4) #subdivided trianle
            mesh2.Faces.AddFace(vidx3,vidx1,vidx4) #subdivided trianle
    mesh2.Normals.ComputeNormals()
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

def interpolateTriangle(v1, v2, v3, ratio1, ratio2): # ratio1 > 0, ratio2 > 0, ratio1+ratio2 < 1 to be inside triangle
    return (v2-v1)*ratio1+(v3-v1)*ratio2+v1

a = subdivide(mesh,recursionLevel,offsetDepth1,offsetDepth2)
- 7-6. Subdivision 3: Quad Center Subdivision   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access)
import Rhino.Geometry as rg

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh()
    
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 #offset vertex
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): # quad face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            vidx4 = mesh.Faces[i].D
            
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            v4 = vtx[vidx4]
            v5 = (v1 + v2 + v3 + v4)/4
            v5 += rg.Vector3d(mesh.FaceNormals[i]) * offset2 #offset new vertex
            
            vidx5 = mesh2.Vertices.Add(v5) #add new vertex and get index number
            mesh2.Faces.AddFace(vidx1,vidx2,vidx5) #subdivided triangle
            mesh2.Faces.AddFace(vidx2,vidx3,vidx5) #subdivided triangle
            mesh2.Faces.AddFace(vidx3,vidx4,vidx5) #subdivided triangle
            mesh2.Faces.AddFace(vidx4,vidx1,vidx5) #subdivided triangle
        else: # triangular face
            mesh2.Faces.AddFace(mesh.Faces[i]) #put original face
    
    mesh2.Normals.ComputeNormals()
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2)
- 7-7. Subdivision 4: Quad Subdivision with Vector Interpolation   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), recursionProbability : float (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access)
import Rhino.Geometry as rg

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh()
    
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 #offset vertex
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): # quad face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            vidx4 = mesh.Faces[i].D
            
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            v4 = vtx[vidx4]
            v5 = interpolateQuad(v1,v2,v3,v4,0.7,0.9) # point inside quad
            v5 += rg.Vector3d(mesh.FaceNormals[i]) * offset2 #offset new vertex
            
            vidx5 = mesh2.Vertices.Add(v5) #add new vertex and get index number
            mesh2.Faces.AddFace(vidx1,vidx2,vidx5) #subdivided triangle
            mesh2.Faces.AddFace(vidx2,vidx3,vidx5) #subdivided triangle
            mesh2.Faces.AddFace(vidx3,vidx4,vidx5) #subdivided triangle
            mesh2.Faces.AddFace(vidx4,vidx1,vidx5) #subdivided triangle
        else: # triangular face
            mesh2.Faces.AddFace(mesh.Faces[i]) # put original face
    
    mesh2.Normals.ComputeNormals()
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

def interpolateQuad(v1, v2, v3, v4, ratio1, ratio2): # 0 < ratio1 < 1, 0 < ratio2 < 1 to be inside quad
    return (v1*(1-ratio1)+v2*ratio1)*(1-ratio2) + (v4*(1-ratio1)+v3*ratio1)*ratio2

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2)
- 7-8. Subdivision 5: 1D Fractal Quad Subdivision   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access)
import Rhino.Geometry as rg

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        if mesh.Faces[i].IsQuad : # quad face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            vidx4 = mesh.Faces[i].D
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            v4 = vtx[vidx4]
            n = rg.Vector3d(mesh.FaceNormals[i]) * offset2
            
            w1 = interpolateLine(v1,v2,0.2) # interpolation on edge
            w2 = interpolateLine(v1,v2,0.4) + n # interpolate on edge and offset
            w3 = interpolateLine(v1,v2,0.6) + n # interpolate on edge and offset
            w4 = interpolateLine(v1,v2,0.8) # interpolation on edge
            w5 = interpolateLine(v4,v3,0.2) # interpolation on edge
            w6 = interpolateLine(v4,v3,0.4) + n # interpolate on edge and offset
            w7 = interpolateLine(v4,v3,0.6) + n # interpolate on edge and offset
            w8 = interpolateLine(v4,v3,0.8) # interpolation on edge
            
            widx1 = mesh2.Vertices.Add(w1)
            widx2 = mesh2.Vertices.Add(w2)
            widx3 = mesh2.Vertices.Add(w3)
            widx4 = mesh2.Vertices.Add(w4)
            widx5 = mesh2.Vertices.Add(w5)
            widx6 = mesh2.Vertices.Add(w6)
            widx7 = mesh2.Vertices.Add(w7)
            widx8 = mesh2.Vertices.Add(w8)
            mesh2.Faces.AddFace(vidx1,widx1,widx5,vidx4)
            mesh2.Faces.AddFace(widx1,widx2,widx6,widx5)
            mesh2.Faces.AddFace(widx2,widx3,widx7,widx6)
            mesh2.Faces.AddFace(widx3,widx4,widx8,widx7)
            mesh2.Faces.AddFace(widx4,vidx2,vidx3,widx8)
        else: # triangular face
            mesh2.Faces.AddFace(mesh.Faces[i])
    
    mesh2.Normals.ComputeNormals()
    mesh2.Weld(180)
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

def interpolateLine(v1, v2, ratio): # 0 < ratio < 1
    return v1*(1-ratio) + v2*ratio

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2)
- 7-9. Subdivision 6: Triangular Probabilistic Subdivision   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), recursionProbability : float (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access)
import Rhino.Geometry as rg
import random

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh() #create new mesh
    
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 #offset vertex
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): #quad mesh
            mesh2.Faces.AddFace(mesh.Faces[i]) #put original face
        else: #triangular mesh
            if random.random()*100 < recursionProbability: # subdivided probabilistically
                vidx1 = mesh.Faces[i].A
                vidx2 = mesh.Faces[i].B
                vidx3 = mesh.Faces[i].C
                
                v1 = vtx[vidx1]
                v2 = vtx[vidx2]
                v3 = vtx[vidx3]
                v4 = (v1 + v2 + v3)/3 #center point
                v4 += rg.Vector3d(mesh.FaceNormals[i]) * offset2 #offset new vertex
                
                vidx4 = mesh2.Vertices.Add(v4) #add new vertex and get index number
                mesh2.Faces.AddFace(vidx1,vidx2,vidx4) #subdivided trianle
                mesh2.Faces.AddFace(vidx2,vidx3,vidx4) #subdivided trianle
                mesh2.Faces.AddFace(vidx3,vidx1,vidx4) #subdivided trianle
            else: 
                mesh2.Faces.AddFace(mesh.Faces[i]) #put original face
    
    mesh2.Normals.ComputeNormals()
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

a = subdivide(mesh,recursionLevel,offsetDepth1,offsetDepth2)
- 7-10. Subdivision 7: Frame Subdivision   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access)
import Rhino.Geometry as rg
import random

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 #offset vertex
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        vidx1 = mesh.Faces[i].A
        vidx2 = mesh.Faces[i].B
        vidx3 = mesh.Faces[i].C
        v1 = vtx[vidx1]
        v2 = vtx[vidx2]
        v3 = vtx[vidx3]
        n = rg.Vector3d(mesh.FaceNormals[i]) * offset2 * random.random() #random normal vector
        
        if(mesh.Faces[i].IsTriangle): # triangle face
            w1 = v1*0.6 + v2*0.2 + v3*0.2 + n #new vertex
            w2 = v1*0.2 + v2*0.6 + v3*0.2 + n #new vertex
            w3 = v1*0.2 + v2*0.2 + v3*0.6 + n #new vertex
            
            widx1 = mesh2.Vertices.Add(w1) #add new vertex and get index
            widx2 = mesh2.Vertices.Add(w2) #add new vertex and get index
            widx3 = mesh2.Vertices.Add(w3) #add new vertex and get index
            mesh2.Faces.AddFace(vidx1,vidx2,widx2,widx1) #subdivided face
            mesh2.Faces.AddFace(vidx2,vidx3,widx3,widx2) #subdivided face
            mesh2.Faces.AddFace(vidx3,vidx1,widx1,widx3) #subdivided face
            
        else: # quad face
            vidx4 = mesh.Faces[i].D
            v4 = vtx[vidx4]
            
            w1 = v1*0.4 + v2*0.2 + v3*0.2 + v4*0.2 + n #new vertex
            w2 = v1*0.2 + v2*0.4 + v3*0.2 + v4*0.2 + n #new vertex
            w3 = v1*0.2 + v2*0.2 + v3*0.4 + v4*0.2 + n #new vertex
            w4 = v1*0.2 + v2*0.2 + v3*0.2 + v4*0.4 + n #new vertex
            
            widx1 = mesh2.Vertices.Add(w1) #add new vertex and get index
            widx2 = mesh2.Vertices.Add(w2) #add new vertex and get index
            widx3 = mesh2.Vertices.Add(w3) #add new vertex and get index
            widx4 = mesh2.Vertices.Add(w4) #add new vertex and get index
            mesh2.Faces.AddFace(vidx1,vidx2,widx2,widx1) #subdivided face
            mesh2.Faces.AddFace(vidx2,vidx3,widx3,widx2) #subdivided face
            mesh2.Faces.AddFace(vidx3,vidx4,widx4,widx3) #subdivided face
            mesh2.Faces.AddFace(vidx4,vidx1,widx1,widx4) #subdivided face
    
    mesh2.Normals.ComputeNormals()
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2)
- 7-11. Subdivision 8: Frame Probabilistic Subdivision   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), recursionProbability : float (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access), holeRatio : float (Item Access)
import Rhino.Geometry as rg
import random

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 # offset vertex
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        vidx1 = mesh.Faces[i].A
        vidx2 = mesh.Faces[i].B
        vidx3 = mesh.Faces[i].C
        v1 = vtx[vidx1]
        v2 = vtx[vidx2]
        v3 = vtx[vidx3]
        n = rg.Vector3d(mesh.FaceNormals[i]) * offset2 * random.random() # random normal vector
        
        if random.random()*100 < recursionProbability :
            if mesh.Faces[i].IsTriangle: # triangle face
                w1 = interpolateTriangle(v1,v2,v3, holeRatio/3,holeRatio/3) + n # new vertex
                w2 = interpolateTriangle(v1,v2,v3, 1-holeRatio*2/3,holeRatio/3) + n # new vertex
                w3 = interpolateTriangle(v1,v2,v3, holeRatio/3,1-holeRatio*2/3) + n # new vertex
                widx1 = mesh2.Vertices.Add(w1) # add new vertex and get index
                widx2 = mesh2.Vertices.Add(w2) # add new vertex and get index
                widx3 = mesh2.Vertices.Add(w3) # add new vertex and get index
                mesh2.Faces.AddFace(vidx1,vidx2,widx2,widx1) # subdivided face
                mesh2.Faces.AddFace(vidx2,vidx3,widx3,widx2) # subdivided face
                mesh2.Faces.AddFace(vidx3,vidx1,widx1,widx3) # subdivided face
            else: # quad face
                vidx4 = mesh.Faces[i].D
                v4 = vtx[vidx4]
                
                w1 = interpolateQuad(v1,v2,v3,v4,holeRatio/2,holeRatio/2) + n # new vertex
                w2 = interpolateQuad(v1,v2,v3,v4,1-holeRatio/2,holeRatio/2) + n # new vertex
                w3 = interpolateQuad(v1,v2,v3,v4,1-holeRatio/2,1-holeRatio/2) + n # new vertex
                w4 = interpolateQuad(v1,v2,v3,v4,holeRatio/2,1-holeRatio/2) + n # new vertex
                widx1 = mesh2.Vertices.Add(w1) # add new vertex and get index
                widx2 = mesh2.Vertices.Add(w2) # add new vertex and get index
                widx3 = mesh2.Vertices.Add(w3) # add new vertex and get index
                widx4 = mesh2.Vertices.Add(w4) # add new vertex and get index
                mesh2.Faces.AddFace(vidx1,vidx2,widx2,widx1) # subdivided face
                mesh2.Faces.AddFace(vidx2,vidx3,widx3,widx2) # subdivided face
                mesh2.Faces.AddFace(vidx3,vidx4,widx4,widx3) # subdivided face
                mesh2.Faces.AddFace(vidx4,vidx1,widx1,widx4) # subdivided face
        else:
            mesh2.Faces.AddFace(mesh.Faces[i])
    
    mesh2.Normals.ComputeNormals()
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

def interpolateQuad(v1, v2, v3, v4, ratio1, ratio2): # 0 < ratio1 < 1, 0 < ratio2 < 1 to be inside quad
    return (v1*(1-ratio1)+v2*ratio1)*(1-ratio2) + (v4*(1-ratio1)+v3*ratio1)*ratio2

def interpolateTriangle(v1, v2, v3, ratio1, ratio2): # ratio1 > 0, ratio2 > 0, ratio1+ratio2 < 1 to be inside triangle
    return (v2-v1)*ratio1+(v3-v1)*ratio2+v1

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2)
- 7-12. Subdivision 9: Quad Subdivision   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access)
import Rhino.Geometry as rg

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh()
    
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 # offset vertex
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): # quad face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            vidx4 = mesh.Faces[i].D
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            v4 = vtx[vidx4]
            
            w1 = (v1+v2)/2 # mid point
            w2 = (v2+v3)/2 # mid point
            w3 = (v3+v4)/2 # mid point
            w4 = (v4+v1)/2 # mid point
            w5 = (v1+v2+v3+v4)/4 # center point
            w5 += rg.Vector3d(mesh.FaceNormals[i]) * offset2 #offset new vertex
            
            widx1 = mesh2.Vertices.Add(w1) # add new vertex and get index
            widx2 = mesh2.Vertices.Add(w2) # add new vertex and get index
            widx3 = mesh2.Vertices.Add(w3) # add new vertex and get index
            widx4 = mesh2.Vertices.Add(w4) # add new vertex and get index
            widx5 = mesh2.Vertices.Add(w5) # add new vertex and get index
            mesh2.Faces.AddFace(vidx1,widx1,widx5,widx4) # subdivided face
            mesh2.Faces.AddFace(vidx2,widx2,widx5,widx1) # subdivided face
            mesh2.Faces.AddFace(vidx3,widx3,widx5,widx2) # subdivided face
            mesh2.Faces.AddFace(vidx4,widx4,widx5,widx3) # subdivided face
            
        else: # triangular face
            mesh2.Faces.AddFace(mesh.Faces[i]) # put original face
    
    mesh2.Normals.ComputeNormals()
    mesh2.Weld(180) #weld vertices
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2)
- 7-13. Subdivision 10: Triangular Subdivision   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access)
import Rhino.Geometry as rg

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 #offset vertex
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count): # triangular face
        if(mesh.Faces[i].IsTriangle):
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            
            w1 = (v1+v2)/2 # mid point
            w2 = (v2+v3)/2 # mid point
            w3 = (v3+v1)/2 # mid point
            w4 = (v1+v2+v3)/3 # center point
            w4 += rg.Vector3d(mesh.FaceNormals[i]) * offset2 # offset new vertex
            
            widx1 = mesh2.Vertices.Add(w1) # add new vertex and get index
            widx2 = mesh2.Vertices.Add(w2) # add new vertex and get index
            widx3 = mesh2.Vertices.Add(w3) # add new vertex and get index
            widx4 = mesh2.Vertices.Add(w4) # add new vertex and get index
            mesh2.Faces.AddFace(vidx1,widx1,widx4,widx3) # subdivided face
            mesh2.Faces.AddFace(vidx2,widx2,widx4,widx1) # subdivided face
            mesh2.Faces.AddFace(vidx3,widx3,widx4,widx2) # subdivided face
        else: # quad face
            mesh2.Faces.AddFace(mesh.Faces[i]) # put original face
            
    mesh2.Weld(180) # weld vertices
    mesh2.Normals.ComputeNormals()
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2)
- 7-14. Subdivision 11: Quad Subdivision with Randomness   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access)
import Rhino.Geometry as rg
import random

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 * random.random() # offset randomly
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): # quad face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            vidx4 = mesh.Faces[i].D
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            v4 = vtx[vidx4]
            
            w1 = (v1+v2)/2 # mid point
            w2 = (v2+v3)/2 # mid point
            w3 = (v3+v4)/2 # mid point
            w4 = (v4+v1)/2 # mid point

            r1 = random.uniform(0.5-uvMove/2,0.5+uvMove/2) # random u ratio
            r2 = random.uniform(0.5-uvMove/2,0.5+uvMove/2) # random v ratio
            w5 = v1*r1*r2+v2*(1-r1)*r2+v3*(1-r1)*(1-r2)+v4*r1*(1-r2) # new point in quad
            w5 += rg.Vector3d(mesh.FaceNormals[i]) * offset2 * random.random() # offset randomly
            
            widx1 = mesh2.Vertices.Add(w1)
            widx2 = mesh2.Vertices.Add(w2)
            widx3 = mesh2.Vertices.Add(w3)
            widx4 = mesh2.Vertices.Add(w4)
            widx5 = mesh2.Vertices.Add(w5)
            mesh2.Faces.AddFace(vidx1,widx1,widx5,widx4)
            mesh2.Faces.AddFace(vidx2,widx2,widx5,widx1)
            mesh2.Faces.AddFace(vidx3,widx3,widx5,widx2)
            mesh2.Faces.AddFace(vidx4,widx4,widx5,widx3)
            
        else: # triangular face
            mesh2.Faces.AddFace(mesh.Faces[i]); # put original face
    
    mesh2.Normals.ComputeNormals()
    mesh2.Weld(180) # weld vertices
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2)
- 7-15. Subdivision 12: Quad Subdivision with Attractors   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access), attractors : Point3d (List Access), attractorRange: float (ItemAccess)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import random

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 * attractorValue(vtx[i]) # offset vertex
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): # quad face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            vidx4 = mesh.Faces[i].D
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            v4 = vtx[vidx4]
            
            w1 = (v1+v2)/2 # mid point
            w2 = (v2+v3)/2 # mid point
            w3 = (v3+v4)/2 # mid point
            w4 = (v4+v1)/2 # mid point
            w5 = (v1+v2+v3+v4)/4 # center point
            w5 += rg.Vector3d(mesh.FaceNormals[i]) * offset2 * attractorValue(w5) # offset new vertex
            
            widx1 = mesh2.Vertices.Add(w1)
            widx2 = mesh2.Vertices.Add(w2)
            widx3 = mesh2.Vertices.Add(w3)
            widx4 = mesh2.Vertices.Add(w4)
            widx5 = mesh2.Vertices.Add(w5)
            mesh2.Faces.AddFace(vidx1,widx1,widx5,widx4)
            mesh2.Faces.AddFace(vidx2,widx2,widx5,widx1)
            mesh2.Faces.AddFace(vidx3,widx3,widx5,widx2)
            mesh2.Faces.AddFace(vidx4,widx4,widx5,widx3)
        else: # triangular face
            mesh2.Faces.AddFace(mesh.Faces[i]) # put original face
    
    mesh2.Normals.ComputeNormals()
    mesh2.Weld(180) # weld vertices
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

def attractorValue(pos): # calculate value of 0-1 by attractors
    minDist = -1
    for attr in attractors: # find closest attractor
        dist = rs.Distance(pos, attr)
        if( minDist < 0 or dist < minDist ):
            minDist = dist
    if(minDist < attractorRange): # check range and scale value to 0 - 1
        return (attractorRange - minDist)/attractorRange # value from 0 to 1
    return 0 # when out of range

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2)
- 7-16. Subdivision 13: Quad Subdivision with Depth Control by Bitmap   GH file   3DM file

# subdivide quad mes. offset depth is controlled by bitmap pixel values
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access), imageProjectionSurface : Surface (Item Access), imagePixelValue : float (Tree Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 * pixelValue(vtx[i]) # offset vertex
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): # quad face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            vidx4 = mesh.Faces[i].D
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            v4 = vtx[vidx4]
            
            w1 = (v1+v2)/2 # mid point
            w2 = (v2+v3)/2 # mid point
            w3 = (v3+v4)/2 # mid point
            w4 = (v4+v1)/2 # mid point
            w5 = (v1+v2+v3+v4)/4 # center point
            w5 += rg.Vector3d(mesh.FaceNormals[i]) * offset2 * pixelValue(w5) # offset new vertex
            
            widx1 = mesh2.Vertices.Add(w1)
            widx2 = mesh2.Vertices.Add(w2)
            widx3 = mesh2.Vertices.Add(w3)
            widx4 = mesh2.Vertices.Add(w4)
            widx5 = mesh2.Vertices.Add(w5)
            mesh2.Faces.AddFace(vidx1,widx1,widx5,widx4)
            mesh2.Faces.AddFace(vidx2,widx2,widx5,widx1)
            mesh2.Faces.AddFace(vidx3,widx3,widx5,widx2)
            mesh2.Faces.AddFace(vidx4,widx4,widx5,widx3)
        else: # triangular face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            
            w1 = (v1+v2)/2 # mid point
            w2 = (v2+v3)/2 # mid point
            w3 = (v3+v1)/2 # mid point
            w4 = (v1+v2+v3)/3 # center point
            w4 += rg.Vector3d(mesh.FaceNormals[i]) * offset2 * pixelValue(w4) # offset new vertex
            
            widx1 = mesh2.Vertices.Add(w1)
            widx2 = mesh2.Vertices.Add(w2)
            widx3 = mesh2.Vertices.Add(w3)
            widx4 = mesh2.Vertices.Add(w4)
            mesh2.Faces.AddFace(vidx1,widx1,widx4,widx3)
            mesh2.Faces.AddFace(vidx2,widx2,widx4,widx1)
            mesh2.Faces.AddFace(vidx3,widx3,widx4,widx2)
    
    mesh2.Normals.ComputeNormals()
    mesh2.Weld(180) # weld vertices
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

def pixelValue(pos):
    [result, u, v] = imageProjectionSurface.ClosestPoint(pos) # find closest point on surface
    if not result : # if it fails, return 0
        return 0
    # pick a pixel value on the tree branch matrix
    branch = imagePixelValue.Branches[int((imagePixelValue.BranchCount-1)*u)] 
    return branch[int((len(branch)-1)*v)]

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2)
- 7-17. Subdivision 14: Frame Subdivision with Recursion Control by Bitmap   GH file   3DM file

# subdivide quad mes. offset depth is controlled by bitmap pixel values
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access), holeRatio : float (Item Access), imageProjectionSurface : Surface (Item Access), imagePixelValue : float (Tree Access)
import Rhino.Geometry as rg
import random

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1 #offset vertex
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        vidx1 = mesh.Faces[i].A
        vidx2 = mesh.Faces[i].B
        vidx3 = mesh.Faces[i].C
        v1 = vtx[vidx1]
        v2 = vtx[vidx2]
        v3 = vtx[vidx3]
        n = rg.Vector3d(mesh.FaceNormals[i]) * offset2 * random.random() #random normal vector
        
        if mesh.Faces[i].IsTriangle: # triangle face
            if random.random() < pixelValue((v1+v2+v3)/3) :
                w1 = interpolateTriangle(v1,v2,v3, holeRatio/3,holeRatio/3) + n #new vertex
                w2 = interpolateTriangle(v1,v2,v3, 1-holeRatio*2/3,holeRatio/3) + n #new vertex
                w3 = interpolateTriangle(v1,v2,v3, holeRatio/3,1-holeRatio*2/3) + n #new vertex
                widx1 = mesh2.Vertices.Add(w1) #add new vertex and get index
                widx2 = mesh2.Vertices.Add(w2) #add new vertex and get index
                widx3 = mesh2.Vertices.Add(w3) #add new vertex and get index
                mesh2.Faces.AddFace(vidx1,vidx2,widx2,widx1) # subdivided face
                mesh2.Faces.AddFace(vidx2,vidx3,widx3,widx2) # subdivided face
                mesh2.Faces.AddFace(vidx3,vidx1,widx1,widx3) # subdivided face
            else:
                mesh2.Faces.AddFace(mesh.Faces[i])
        else: # quad face
            vidx4 = mesh.Faces[i].D
            v4 = vtx[vidx4]
            if random.random() < pixelValue((v1+v2+v3+v4)/4) :
                w1 = interpolateQuad(v1,v2,v3,v4,holeRatio/2,holeRatio/2) + n # new vertex
                w2 = interpolateQuad(v1,v2,v3,v4,1-holeRatio/2,holeRatio/2) + n # new vertex
                w3 = interpolateQuad(v1,v2,v3,v4,1-holeRatio/2,1-holeRatio/2) + n # new vertex
                w4 = interpolateQuad(v1,v2,v3,v4,holeRatio/2,1-holeRatio/2) + n # new vertex
                widx1 = mesh2.Vertices.Add(w1) # add new vertex and get index
                widx2 = mesh2.Vertices.Add(w2) # add new vertex and get index
                widx3 = mesh2.Vertices.Add(w3) # add new vertex and get index
                widx4 = mesh2.Vertices.Add(w4) # add new vertex and get index
                mesh2.Faces.AddFace(vidx1,vidx2,widx2,widx1) # subdivided face
                mesh2.Faces.AddFace(vidx2,vidx3,widx3,widx2) # subdivided face
                mesh2.Faces.AddFace(vidx3,vidx4,widx4,widx3) # subdivided face
                mesh2.Faces.AddFace(vidx4,vidx1,widx1,widx4) # subdivided face
            else:
                mesh2.Faces.AddFace(mesh.Faces[i])
    
    mesh2.Normals.ComputeNormals()
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2)
    return mesh2

def interpolateQuad(v1, v2, v3, v4, ratio1, ratio2): # 0 < ratio1 < 1, 0 < ratio2 < 1 to be inside quad
    return (v1*(1-ratio1)+v2*ratio1)*(1-ratio2) + (v4*(1-ratio1)+v3*ratio1)*ratio2

def interpolateTriangle(v1, v2, v3, ratio1, ratio2): # ratio1 > 0, ratio2 > 0, ratio1+ratio2 < 1 to be inside triangle
    return (v2-v1)*ratio1+(v3-v1)*ratio2+v1

def pixelValue(pos):
    [result, u, v] = imageProjectionSurface.ClosestPoint(pos) # find closest point on surface
    if not result : # if it fails, return 0
        return 0
    # pick a pixel value on the tree branch matrix
    branch = imagePixelValue.Branches[int((imagePixelValue.BranchCount-1)*u)] 
    return branch[int((len(branch)-1)*v)]

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2)
- 7-18. Subdivision 15: Quad Probabilistic Subdivision with Depth Control by Area   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), recursionProbability : float (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access)
import Rhino.Geometry as rg
import random

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): # quad face
            if(random.random()*100 < recursionProbability): # probabilistic switch
                vidx1 = mesh.Faces[i].A
                vidx2 = mesh.Faces[i].B
                vidx3 = mesh.Faces[i].C
                vidx4 = mesh.Faces[i].D
                v1 = vtx[vidx1]
                v2 = vtx[vidx2]
                v3 = vtx[vidx3]
                v4 = vtx[vidx4]
                
                area = quadArea(v1,v2,v3,v4) # calculate area
                n = rg.Vector3d(mesh.FaceNormals[i]) * offset2 * area / 100 # control depth by area
                
                w1 = v1*0.5+v2*0.2+v3*0.1+v4*0.2 # some point on quad
                w2 = v1*0.2+v2*0.5+v3*0.2+v4*0.1 # some point on quad
                w3 = w1+n # offset out
                w4 = w2+n # offset out
                
                widx1 = mesh2.Vertices.Add(w1)
                widx2 = mesh2.Vertices.Add(w2)
                widx3 = mesh2.Vertices.Add(w3)
                widx4 = mesh2.Vertices.Add(w4)
                mesh2.Faces.AddFace(vidx1,vidx2,widx2,widx1)
                mesh2.Faces.AddFace(vidx3,vidx4,widx3,widx4)
                mesh2.Faces.AddFace(widx1,widx2,widx4,widx3)
                mesh2.Faces.AddFace(vidx2,vidx3,widx4,widx2)
                mesh2.Faces.AddFace(vidx4,vidx1,widx1,widx3)
            else:
                mesh2.Faces.AddFace(mesh.Faces[i])
        else: # triangular face
            mesh2.Faces.AddFace(mesh.Faces[i])
            
    mesh2.Normals.ComputeNormals()
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1*0.8, offset2*0.8) # offset amount is adjusted by recursion depth
    return mesh2

def triangleArea(v1, v2, v3): # area calculation by 3 points
    crossVec = rg.Vector3d.CrossProduct(v2-v1, v3-v1)
    return crossVec.Length/2

def quadArea(v1, v2, v3, v4): # area calculation by 4 points
    return triangleArea(v1,v2,v3) + triangleArea(v3,v4,v1)

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2)
- 7-19. Subdivision 16: Linear Subdivision   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), recursionProbability : float (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access), scale : float (Item Access), bend : float (Item Access)
import Rhino.Geometry as rg
import random

a = []

def subdivide(mesh, recursion, offset1, offset2):
    mesh2 = rg.Mesh() # new mesh
    mesh3 = rg.Mesh() # another new mesh
    vtx = mesh.Vertices.ToPoint3dArray()
    for i in range(len(vtx)):
        vtx2 = vtx[i] + rg.Vector3d(mesh.Normals[i]) * offset1
        mesh2.Vertices.Add(vtx2)
    
    for i in range(mesh.Faces.Count):
        if mesh.Faces[i].IsQuad : # quad face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            vidx4 = mesh.Faces[i].D
            if random.random()*100 < recursionProbability :
                v1 = vtx[vidx1]
                v2 = vtx[vidx2]
                v3 = vtx[vidx3]
                v4 = vtx[vidx4]
                
                n = rg.Vector3d(mesh.FaceNormals[i]) * offset2
                w1 = interpolateQuad(v1,v2,v3,v4, (1-scale)/4, (1-scale)/2) + n * (1+bend)
                w2 = interpolateQuad(v1,v2,v3,v4, (1+scale)/2, (3-3*scale)/8) + n * (1+bend)
                w3 = interpolateQuad(v1,v2,v3,v4, (3+scale)/4, (1+scale)/2) + n * (1-bend)
                w4 = interpolateQuad(v1,v2,v3,v4, (1-scale)/2, (5+3*scale)/8) + n * (1-bend)
                
                widx1 = mesh2.Vertices.Add(w1)
                widx2 = mesh2.Vertices.Add(w2)
                widx3 = mesh2.Vertices.Add(w3)
                widx4 = mesh2.Vertices.Add(w4)
                mesh2.Faces.AddFace(vidx1,vidx2,widx2,widx1) # tubular side face
                mesh2.Faces.AddFace(vidx2,vidx3,widx3,widx2) # tubular side face
                mesh2.Faces.AddFace(vidx3,vidx4,widx4,widx3) # tubular side face
                mesh2.Faces.AddFace(vidx4,vidx1,widx1,widx4) # tubular side face
                
                widx1 = mesh3.Vertices.Add(w1)
                widx2 = mesh3.Vertices.Add(w2)
                widx3 = mesh3.Vertices.Add(w3)
                widx4 = mesh3.Vertices.Add(w4)
                mesh3.Faces.AddFace(widx1,widx2,widx3,widx4) # offset face
            else:
                mesh2.Faces.AddFace(vidx1,vidx2,vidx3,vidx4) # original face
            
        else: # triangular face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            if random.random()*100 < recursionProbability :
                v1 = vtx[vidx1]
                v2 = vtx[vidx2]
                v3 = vtx[vidx3]
                
                n = rg.Vector3d(mesh.FaceNormals[i]) * offset2
                w1 = interpolateTriangle(v1,v2,v3, (1-scale)/2, (1-scale)/6) + n
                w2 = interpolateTriangle(v1,v2,v3, (1+scale)/2, (3-3*scale)/8) + n*(1+bend)
                w3 = interpolateTriangle(v1,v2,v3, (1-scale)/8, (1+scale)/2) + n*(1-bend)
                
                widx1 = mesh2.Vertices.Add(w1)
                widx2 = mesh2.Vertices.Add(w2)
                widx3 = mesh2.Vertices.Add(w3)
                mesh2.Faces.AddFace(vidx1,vidx2,widx2,widx1) # tubular side face
                mesh2.Faces.AddFace(vidx2,vidx3,widx3,widx2) # tubular side face
                mesh2.Faces.AddFace(vidx3,vidx1,widx1,widx3) # tubular side face
                
                widx1 = mesh3.Vertices.Add(w1)
                widx2 = mesh3.Vertices.Add(w2)
                widx3 = mesh3.Vertices.Add(w3)
                mesh3.Faces.AddFace(widx1,widx2,widx3) # offset face
            else:
                mesh2.Faces.AddFace(vidx1,vidx2,vidx3) # original face
    
    mesh2.Normals.ComputeNormals()
    mesh3.Normals.ComputeNormals()
    a.append(mesh2) # tubular side mesh
    
    if(recursion>1):
        return subdivide(mesh3, recursion-1, offset1, offset2) # subdivide offset mesh
    return mesh3 # return offset mesh

def interpolateQuad(v1, v2, v3, v4, ratio1, ratio2): # 0 < ratio1 < 1, 0 < ratio2 < 1 to be inside quad
    return (v1*(1-ratio1)+v2*ratio1)*(1-ratio2) + (v4*(1-ratio1)+v3*ratio1)*ratio2

def interpolateTriangle(v1, v2, v3, ratio1, ratio2): # ratio1 > 0, ratio2 > 0, ratio1+ratio2 < 1 to be inside triangle
    return (v2-v1)*ratio1+(v3-v1)*ratio2+v1

a.append( subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2) )
- 7-20. Subdivision 17: Tubular Subdivision   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), recursionProbability : float (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access), offsetDepth3 : float (Item Access), holeRatio : float (Item Access)
import Rhino.Geometry as rg
import random

def subdivide(mesh, recursion, offset1, offset2, offset3):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    offsetVtx = [] # another set of offset vertices
    offsetNml = [] # another set of normals
    for i in range(len(vtx)):
        n = rg.Vector3d(mesh.Normals[i]) 
        vtx2 = vtx[i] + n * offset1
        mesh2.Vertices.Add(vtx2)
        mesh2.Normals.Add(-n) # opposite normal
        vtx3 = vtx[i] + n * offset2
        offsetVtx.append(vtx3) # offset vertices
        offsetNml.append(n) # offset normals
    
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): # quad face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            vidx4 = mesh.Faces[i].D
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            v4 = vtx[vidx4]
            ov1 = offsetVtx[vidx1]
            ov2 = offsetVtx[vidx2]
            ov3 = offsetVtx[vidx3]
            ov4 = offsetVtx[vidx4]
            # add offset vertices to mesh
            ovidx1 = mesh2.Vertices.Add(ov1)
            ovidx2 = mesh2.Vertices.Add(ov2)
            ovidx3 = mesh2.Vertices.Add(ov3)
            ovidx4 = mesh2.Vertices.Add(ov4)
            mesh2.Normals.Add(offsetNml[vidx1])
            mesh2.Normals.Add(offsetNml[vidx2])
            mesh2.Normals.Add(offsetNml[vidx3])
            mesh2.Normals.Add(offsetNml[vidx4])
            
            if(random.random()*100 < recursionProbability):
                n = rg.Vector3d(mesh.FaceNormals[i]) * offset3
                # inner vertices
                v5 = interpolateQuad(v1,v2,v3,v4, holeRatio/2, holeRatio/2)
                v6 = interpolateQuad(v1,v2,v3,v4, 1-holeRatio/2, holeRatio/2)
                v7 = interpolateQuad(v1,v2,v3,v4, 1-holeRatio/2, 1-holeRatio/2)
                v8 = interpolateQuad(v1,v2,v3,v4, holeRatio/2, 1-holeRatio/2)
                vidx5 = mesh2.Vertices.Add(v5)
                vidx6 = mesh2.Vertices.Add(v6)
                vidx7 = mesh2.Vertices.Add(v7)
                vidx8 = mesh2.Vertices.Add(v8)
                mesh2.Normals.Add(calcNormal(-n, v8, v5, v6))
                mesh2.Normals.Add(calcNormal(-n, v5, v6, v7))
                mesh2.Normals.Add(calcNormal(-n, v6, v7, v8))
                mesh2.Normals.Add(calcNormal(-n, v7, v8, v5))
                # offset inner vertices
                ov5 = interpolateQuad(ov1,ov2,ov3,ov4, holeRatio/2, holeRatio/2)
                ov6 = interpolateQuad(ov1,ov2,ov3,ov4, 1-holeRatio/2, holeRatio/2)
                ov7 = interpolateQuad(ov1,ov2,ov3,ov4, 1-holeRatio/2, 1-holeRatio/2)
                ov8 = interpolateQuad(ov1,ov2,ov3,ov4, holeRatio/2, 1-holeRatio/2)
                ovidx5 = mesh2.Vertices.Add(ov5)
                ovidx6 = mesh2.Vertices.Add(ov6)
                ovidx7 = mesh2.Vertices.Add(ov7)
                ovidx8 = mesh2.Vertices.Add(ov8)
                mesh2.Normals.Add(calcNormal(n, ov8, ov5, ov6))
                mesh2.Normals.Add(calcNormal(n, ov5, ov6, ov7))
                mesh2.Normals.Add(calcNormal(n, ov6, ov7, ov8))
                mesh2.Normals.Add(calcNormal(n, ov7, ov8, ov5))
                # frame faces
                mesh2.Faces.AddFace(vidx1,vidx2,vidx6,vidx5)
                mesh2.Faces.AddFace(vidx2,vidx3,vidx7,vidx6)
                mesh2.Faces.AddFace(vidx3,vidx4,vidx8,vidx7)
                mesh2.Faces.AddFace(vidx4,vidx1,vidx5,vidx8)
                # offset frame faces
                mesh2.Faces.AddFace(ovidx1,ovidx2,ovidx6,ovidx5)
                mesh2.Faces.AddFace(ovidx2,ovidx3,ovidx7,ovidx6)
                mesh2.Faces.AddFace(ovidx3,ovidx4,ovidx8,ovidx7)
                mesh2.Faces.AddFace(ovidx4,ovidx1,ovidx5,ovidx8)
                # tubular faces
                mesh2.Faces.AddFace(vidx5, vidx6, ovidx6, ovidx5)
                mesh2.Faces.AddFace(vidx6, vidx7, ovidx7, ovidx6)
                mesh2.Faces.AddFace(vidx7, vidx8, ovidx8, ovidx7)
                mesh2.Faces.AddFace(vidx8, vidx5, ovidx5, ovidx8)
            else:
                mesh2.Faces.AddFace(vidx1,vidx2,vidx3,vidx4)
                mesh2.Faces.AddFace(ovidx1,ovidx2,ovidx3,ovidx4)
            
        else: # triangular face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            ov1 = offsetVtx[vidx1]
            ov2 = offsetVtx[vidx2]
            ov3 = offsetVtx[vidx3]
            # add offset vertices to mesh
            ovidx1 = mesh2.Vertices.Add(ov1)
            ovidx2 = mesh2.Vertices.Add(ov2)
            ovidx3 = mesh2.Vertices.Add(ov3)
            mesh2.Normals.Add(offsetNml[vidx1])
            mesh2.Normals.Add(offsetNml[vidx2])
            mesh2.Normals.Add(offsetNml[vidx3])
            
            if(random.random()*100 < recursionProbability):
                n = rg.Vector3d(mesh.FaceNormals[i]) * offset3
                # inner vertices
                v5 = interpolateTriangle(v1,v2,v3, holeRatio/3, holeRatio/3)
                v6 = interpolateTriangle(v1,v2,v3, 1-holeRatio*2/3, holeRatio/3)
                v7 = interpolateTriangle(v1,v2,v3, holeRatio/3, 1-holeRatio*2/3)
                vidx5 = mesh2.Vertices.Add(v5)
                vidx6 = mesh2.Vertices.Add(v6)
                vidx7 = mesh2.Vertices.Add(v7)
                mesh2.Normals.Add(calcNormal(-n, v7, v5, v6))
                mesh2.Normals.Add(calcNormal(-n, v5, v6, v7))
                mesh2.Normals.Add(calcNormal(-n, v6, v7, v5))
                # offset inner vertices
                ov5 = interpolateTriangle(ov1,ov2,ov3, holeRatio/3, holeRatio/3)
                ov6 = interpolateTriangle(ov1,ov2,ov3, 1-holeRatio*2/3, holeRatio/3)
                ov7 = interpolateTriangle(ov1,ov2,ov3, holeRatio/3, 1-holeRatio*2/3)
                ovidx5 = mesh2.Vertices.Add(ov5)
                ovidx6 = mesh2.Vertices.Add(ov6)
                ovidx7 = mesh2.Vertices.Add(ov7)
                mesh2.Normals.Add(calcNormal(n, ov7, ov5, ov6))
                mesh2.Normals.Add(calcNormal(n, ov5, ov6, ov7))
                mesh2.Normals.Add(calcNormal(n, ov6, ov7, ov5))
                # frame faces
                mesh2.Faces.AddFace(vidx1,vidx2,vidx6,vidx5)
                mesh2.Faces.AddFace(vidx2,vidx3,vidx7,vidx6)
                mesh2.Faces.AddFace(vidx3,vidx1,vidx5,vidx7)
                # offset frame faces
                mesh2.Faces.AddFace(ovidx1,ovidx2,ovidx6,ovidx5)
                mesh2.Faces.AddFace(ovidx2,ovidx3,ovidx7,ovidx6)
                mesh2.Faces.AddFace(ovidx3,ovidx1,ovidx5,ovidx7)
                # tubular faces
                mesh2.Faces.AddFace(vidx5, vidx6, ovidx6, ovidx5)
                mesh2.Faces.AddFace(vidx6, vidx7, ovidx7, ovidx6)
                mesh2.Faces.AddFace(vidx7, vidx5, ovidx5, ovidx7)
            else:
                mesh2.Faces.AddFace(vidx1,vidx2,vidx3)
                mesh2.Faces.AddFace(ovidx1,ovidx2,ovidx3)
    
    mesh2.Weld(180) # weld vertices
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2, offset3)
    mesh2.UnifyNormals()
    return mesh2

def calcNormal(faceNml, vtx1, vtx2, vtx3):
    vec1 = vtx1 - vtx2
    vec2 = vtx3 - vtx2
    vec1.Unitize()
    vec2.Unitize()
    faceNml.Unitize()
    n = vec1+vec2+faceNml
    n.Unitize()
    return n

def interpolateQuad(v1, v2, v3, v4, ratio1, ratio2): # 0 < ratio1 < 1, 0 < ratio2 < 1 to be inside quad
    return (v1*(1-ratio1)+v2*ratio1)*(1-ratio2) + (v4*(1-ratio1)+v3*ratio1)*ratio2

def interpolateTriangle(v1, v2, v3, ratio1, ratio2): # ratio1 > 0, ratio2 > 0, ratio1+ratio2 < 1 to be inside triangle
    return (v2-v1)*ratio1+(v3-v1)*ratio2+v1

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2, offsetDepth3)
- 7-21. Subdivision 18: Tubular Subdivision with Tab Faces   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), recursionProbability : float (Item Access), offsetDepth1 : float (Item Access), offsetDepth2 : float (Item Access), offsetDepth3 : float (Item Access), holeRatio : float (Item Access)
import Rhino.Geometry as rg
import random

def subdivide(mesh, recursion, offset1, offset2, offset3):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    offsetVtx = [] # another set of offset vertices
    offsetNml = [] # another set of normals
    for i in range(len(vtx)):
        n = rg.Vector3d(mesh.Normals[i]) 
        vtx2 = vtx[i] + n * offset1
        mesh2.Vertices.Add(vtx2)
        mesh2.Normals.Add(-n) # opposite normal
        vtx3 = vtx[i] + n * offset2
        offsetVtx.append(vtx3) # offset vertices
        offsetNml.append(n) # offset normals
    
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): # quad face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            vidx4 = mesh.Faces[i].D
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            v4 = vtx[vidx4]
            ov1 = offsetVtx[vidx1]
            ov2 = offsetVtx[vidx2]
            ov3 = offsetVtx[vidx3]
            ov4 = offsetVtx[vidx4]
            # add offset vertices to mesh
            ovidx1 = mesh2.Vertices.Add(ov1)
            ovidx2 = mesh2.Vertices.Add(ov2)
            ovidx3 = mesh2.Vertices.Add(ov3)
            ovidx4 = mesh2.Vertices.Add(ov4)
            mesh2.Normals.Add(offsetNml[vidx1])
            mesh2.Normals.Add(offsetNml[vidx2])
            mesh2.Normals.Add(offsetNml[vidx3])
            mesh2.Normals.Add(offsetNml[vidx4])
            
            if(random.random()*100 < recursionProbability):
                n = rg.Vector3d(mesh.FaceNormals[i]) * offset3
                # inner vertices
                v5 = interpolateQuad(v1,v2,v3,v4, holeRatio/2, holeRatio/2)
                v6 = interpolateQuad(v1,v2,v3,v4, 1-holeRatio/2, holeRatio/2)
                v7 = interpolateQuad(v1,v2,v3,v4, 1-holeRatio/2, 1-holeRatio/2)
                v8 = interpolateQuad(v1,v2,v3,v4, holeRatio/2, 1-holeRatio/2)
                vidx5 = mesh2.Vertices.Add(v5)
                vidx6 = mesh2.Vertices.Add(v6)
                vidx7 = mesh2.Vertices.Add(v7)
                vidx8 = mesh2.Vertices.Add(v8)
                mesh2.Normals.Add(calcNormal(-n, v8, v5, v6))
                mesh2.Normals.Add(calcNormal(-n, v5, v6, v7))
                mesh2.Normals.Add(calcNormal(-n, v6, v7, v8))
                mesh2.Normals.Add(calcNormal(-n, v7, v8, v5))
                # offset inner vertices
                ov5 = interpolateQuad(ov1,ov2,ov3,ov4, holeRatio/2, holeRatio/2)
                ov6 = interpolateQuad(ov1,ov2,ov3,ov4, 1-holeRatio/2, holeRatio/2)
                ov7 = interpolateQuad(ov1,ov2,ov3,ov4, 1-holeRatio/2, 1-holeRatio/2)
                ov8 = interpolateQuad(ov1,ov2,ov3,ov4, holeRatio/2, 1-holeRatio/2)
                # offset tab vertices
                ov9 = interpolateLine(ov1,ov2, holeRatio/2)
                ov10 = interpolateLine(ov2,ov1, holeRatio/2)
                ov11 = interpolateLine(ov2,ov3, holeRatio/2)
                ov12 = interpolateLine(ov3,ov2, holeRatio/2)
                ov13 = interpolateLine(ov3,ov4, holeRatio/2)
                ov14 = interpolateLine(ov4,ov3, holeRatio/2)
                ov15 = interpolateLine(ov4,ov1, holeRatio/2)
                ov16 = interpolateLine(ov1,ov4, holeRatio/2)
                # add offset vertices and normals
                ovidx5 = mesh2.Vertices.Add(ov5)
                ovidx6 = mesh2.Vertices.Add(ov6)
                ovidx7 = mesh2.Vertices.Add(ov7)
                ovidx8 = mesh2.Vertices.Add(ov8)
                ovidx9 = mesh2.Vertices.Add(ov9)
                ovidx10 = mesh2.Vertices.Add(ov10)
                ovidx11 = mesh2.Vertices.Add(ov11)
                ovidx12 = mesh2.Vertices.Add(ov12)
                ovidx13 = mesh2.Vertices.Add(ov13)
                ovidx14 = mesh2.Vertices.Add(ov14)
                ovidx15 = mesh2.Vertices.Add(ov15)
                ovidx16 = mesh2.Vertices.Add(ov16)
                mesh2.Normals.Add(calcNormal(n, ov8, ov5, ov6))
                mesh2.Normals.Add(calcNormal(n, ov5, ov6, ov7))
                mesh2.Normals.Add(calcNormal(n, ov6, ov7, ov8))
                mesh2.Normals.Add(calcNormal(n, ov7, ov8, ov5))
                mesh2.Normals.Add(offsetNml[vidx1])
                mesh2.Normals.Add(offsetNml[vidx2])
                mesh2.Normals.Add(offsetNml[vidx2])
                mesh2.Normals.Add(offsetNml[vidx3])
                mesh2.Normals.Add(offsetNml[vidx3])
                mesh2.Normals.Add(offsetNml[vidx4])
                mesh2.Normals.Add(offsetNml[vidx4])
                mesh2.Normals.Add(offsetNml[vidx1])
                # frame faces
                mesh2.Faces.AddFace(vidx1,vidx2,vidx6,vidx5)
                mesh2.Faces.AddFace(vidx2,vidx3,vidx7,vidx6)
                mesh2.Faces.AddFace(vidx3,vidx4,vidx8,vidx7)
                mesh2.Faces.AddFace(vidx4,vidx1,vidx5,vidx8)
                # offset tab faces
                mesh2.Faces.AddFace(ovidx9,ovidx10,ovidx6,ovidx5)
                mesh2.Faces.AddFace(ovidx11,ovidx12,ovidx7,ovidx6)
                mesh2.Faces.AddFace(ovidx13,ovidx14,ovidx8,ovidx7)
                mesh2.Faces.AddFace(ovidx15,ovidx16,ovidx5,ovidx8)
                # tubular faces
                mesh2.Faces.AddFace(vidx5, vidx6, ovidx6, ovidx5)
                mesh2.Faces.AddFace(vidx6, vidx7, ovidx7, ovidx6)
                mesh2.Faces.AddFace(vidx7, vidx8, ovidx8, ovidx7)
                mesh2.Faces.AddFace(vidx8, vidx5, ovidx5, ovidx8)
            else:
                mesh2.Faces.AddFace(vidx1,vidx2,vidx3,vidx4)
            
        else: # triangular face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            ov1 = offsetVtx[vidx1]
            ov2 = offsetVtx[vidx2]
            ov3 = offsetVtx[vidx3]
            # add offset vertices to mesh
            ovidx1 = mesh2.Vertices.Add(ov1)
            ovidx2 = mesh2.Vertices.Add(ov2)
            ovidx3 = mesh2.Vertices.Add(ov3)
            mesh2.Normals.Add(offsetNml[vidx1])
            mesh2.Normals.Add(offsetNml[vidx2])
            mesh2.Normals.Add(offsetNml[vidx3])
            
            if(random.random()*100 < recursionProbability):
                n = rg.Vector3d(mesh.FaceNormals[i]) * offset3
                # inner vertices
                v5 = interpolateTriangle(v1,v2,v3, holeRatio/3, holeRatio/3)
                v6 = interpolateTriangle(v1,v2,v3, 1-holeRatio*2/3, holeRatio/3)
                v7 = interpolateTriangle(v1,v2,v3, holeRatio/3, 1-holeRatio*2/3)
                vidx5 = mesh2.Vertices.Add(v5)
                vidx6 = mesh2.Vertices.Add(v6)
                vidx7 = mesh2.Vertices.Add(v7)
                mesh2.Normals.Add(calcNormal(-n, v7, v5, v6))
                mesh2.Normals.Add(calcNormal(-n, v5, v6, v7))
                mesh2.Normals.Add(calcNormal(-n, v6, v7, v5))
                # offset inner vertices
                ov5 = interpolateTriangle(ov1,ov2,ov3, holeRatio/3, holeRatio/3)
                ov6 = interpolateTriangle(ov1,ov2,ov3, 1-holeRatio*2/3, holeRatio/3)
                ov7 = interpolateTriangle(ov1,ov2,ov3, holeRatio/3, 1-holeRatio*2/3)
                # offset tab vertices
                ov9 = interpolateLine(ov1,ov2, holeRatio/2)
                ov10 = interpolateLine(ov2,ov1, holeRatio/2)
                ov11 = interpolateLine(ov2,ov3, holeRatio/2)
                ov12 = interpolateLine(ov3,ov2, holeRatio/2)
                ov13 = interpolateLine(ov3,ov1, holeRatio/2)
                ov14 = interpolateLine(ov1,ov3, holeRatio/2)
                # add offset vertices and normals
                ovidx5 = mesh2.Vertices.Add(ov5)
                ovidx6 = mesh2.Vertices.Add(ov6)
                ovidx7 = mesh2.Vertices.Add(ov7)
                ovidx9 = mesh2.Vertices.Add(ov9)
                ovidx10 = mesh2.Vertices.Add(ov10)
                ovidx11 = mesh2.Vertices.Add(ov11)
                ovidx12 = mesh2.Vertices.Add(ov12)
                ovidx13 = mesh2.Vertices.Add(ov13)
                ovidx14 = mesh2.Vertices.Add(ov14)
                mesh2.Normals.Add(calcNormal(n, ov7, ov5, ov6))
                mesh2.Normals.Add(calcNormal(n, ov5, ov6, ov7))
                mesh2.Normals.Add(calcNormal(n, ov6, ov7, ov5))
                mesh2.Normals.Add(offsetNml[vidx1])
                mesh2.Normals.Add(offsetNml[vidx2])
                mesh2.Normals.Add(offsetNml[vidx2])
                mesh2.Normals.Add(offsetNml[vidx3])
                mesh2.Normals.Add(offsetNml[vidx3])
                mesh2.Normals.Add(offsetNml[vidx1])
                # frame faces
                mesh2.Faces.AddFace(vidx1,vidx2,vidx6,vidx5)
                mesh2.Faces.AddFace(vidx2,vidx3,vidx7,vidx6)
                mesh2.Faces.AddFace(vidx3,vidx1,vidx5,vidx7)
                # offset tab faces
                mesh2.Faces.AddFace(ovidx9,ovidx10,ovidx6,ovidx5)
                mesh2.Faces.AddFace(ovidx11,ovidx12,ovidx7,ovidx6)
                mesh2.Faces.AddFace(ovidx13,ovidx14,ovidx5,ovidx7)
                # tubular faces
                mesh2.Faces.AddFace(vidx5, vidx6, ovidx6, ovidx5)
                mesh2.Faces.AddFace(vidx6, vidx7, ovidx7, ovidx6)
                mesh2.Faces.AddFace(vidx7, vidx5, ovidx5, ovidx7)
            else:
                mesh2.Faces.AddFace(vidx1,vidx2,vidx3)
    
    mesh2.Weld(180) # weld vertices
    if(recursion>1):
        return subdivide(mesh2, recursion-1, offset1/2, offset2/2, offset3)
    mesh2.UnifyNormals()
    return mesh2

def calcNormal(faceNml, vtx1, vtx2, vtx3):
    vec1 = vtx1 - vtx2
    vec2 = vtx3 - vtx2
    vec1.Unitize()
    vec2.Unitize()
    faceNml.Unitize()
    n = vec1+vec2+faceNml
    n.Unitize()
    return n

def interpolateQuad(v1, v2, v3, v4, ratio1, ratio2): # 0 < ratio1 < 1, 0 < ratio2 < 1 to be inside quad
    return (v1*(1-ratio1)+v2*ratio1)*(1-ratio2) + (v4*(1-ratio1)+v3*ratio1)*ratio2

def interpolateTriangle(v1, v2, v3, ratio1, ratio2): # ratio1 > 0, ratio2 > 0, ratio1+ratio2 < 1 to be inside triangle
    return (v2-v1)*ratio1+(v3-v1)*ratio2+v1

def interpolateLine(v1, v2, ratio): # 0 < ratio < 1
    return v1*(1-ratio) + v2*ratio

a = subdivide(mesh, recursionLevel, offsetDepth1, offsetDepth2, offsetDepth3)
- 7-22. Subdivision 19: Long Edge Subdivision   GH file   3DM file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), maxEdgeLength : float (Item Access)
import Rhino.Geometry as rg

def subdivide(mesh, recursion, threshold):
    mesh2 = rg.Mesh()
    vtx = mesh.Vertices.ToPoint3dArray()
    mesh2.Vertices.AddVertices(vtx)
    for i in range(mesh.Faces.Count):
        if(mesh.Faces[i].IsQuad): # quad face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            vidx4 = mesh.Faces[i].D
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            v4 = vtx[vidx4]
            widx1 = mesh2.Vertices.Add((v1+v2)/2) # mid point
            widx2 = mesh2.Vertices.Add((v2+v3)/2) # mid point
            widx3 = mesh2.Vertices.Add((v3+v4)/2) # mid point
            widx4 = mesh2.Vertices.Add((v4+v1)/2) # mid point
            widx5 = mesh2.Vertices.Add((v1+v2+v3+v4)/4) # center point
            dist12 = v1.DistanceTo(v2)
            dist23 = v2.DistanceTo(v3)
            dist34 = v3.DistanceTo(v4)
            dist41 = v4.DistanceTo(v1)
            if(dist12 > threshold and dist23>threshold and dist34 > threshold and dist41>threshold): # quad subdiv
                mesh2.Faces.AddFace(vidx1,widx1,widx5,widx4)
                mesh2.Faces.AddFace(vidx2,widx2,widx5,widx1)
                mesh2.Faces.AddFace(vidx3,widx3,widx5,widx2)
                mesh2.Faces.AddFace(vidx4,widx4,widx5,widx3)
            elif(dist12 > threshold and dist23 > threshold and dist34 > threshold): # 1 quad, 3 triangles
                mesh2.Faces.AddFace(vidx1,widx1,widx3,vidx4)
                mesh2.Faces.AddFace(widx1,vidx2,widx2)
                mesh2.Faces.AddFace(widx2,vidx3,widx3)
                mesh2.Faces.AddFace(widx1,widx2,widx3)
            elif(dist23 > threshold and dist34 > threshold and dist41 > threshold): # 1 quad, 3 triangles
                mesh2.Faces.AddFace(vidx2,widx2,widx4,vidx1)
                mesh2.Faces.AddFace(widx2,vidx3,widx3)
                mesh2.Faces.AddFace(widx3,vidx4,widx4)
                mesh2.Faces.AddFace(widx2,widx3,widx4)
            elif(dist34 > threshold and dist41 > threshold and dist12 > threshold): # 1 quad, 3 triangles
                mesh2.Faces.AddFace(vidx3,widx3,widx1,vidx2)
                mesh2.Faces.AddFace(widx3,vidx4,widx4)
                mesh2.Faces.AddFace(widx4,vidx1,widx1)
                mesh2.Faces.AddFace(widx3,widx4,widx1)
            elif(dist41 > threshold and dist12 > threshold and dist23 > threshold): # 1 quad, 3 triangles
                mesh2.Faces.AddFace(vidx4,widx4,widx2,vidx3)
                mesh2.Faces.AddFace(widx4,vidx1,widx1)
                mesh2.Faces.AddFace(widx1,vidx2,widx2)
                mesh2.Faces.AddFace(widx4,widx1,widx2)
            elif(dist12 > threshold and dist34 > threshold ): # 2 quads
                mesh2.Faces.AddFace(vidx1,widx1,widx3,vidx4)
                mesh2.Faces.AddFace(widx1,vidx2,vidx3,widx3)
            elif(dist23 > threshold and dist41 > threshold ): # 2 quads
                mesh2.Faces.AddFace(vidx1,vidx2,widx2,widx4)
                mesh2.Faces.AddFace(widx4,widx2,vidx3,vidx4)
            elif(dist12 > threshold and dist23 > threshold ): # 1 quad, 2 triangles
                mesh2.Faces.AddFace(vidx2,widx2,widx1)
                mesh2.Faces.AddFace(widx2,vidx3,vidx1,widx1)
                mesh2.Faces.AddFace(vidx3,vidx4,vidx1)
            elif(dist23 > threshold and dist34 > threshold ): # 1 quad, 2 triangles
                mesh2.Faces.AddFace(vidx3,widx3,widx2)
                mesh2.Faces.AddFace(widx3,vidx4,vidx2,widx2)
                mesh2.Faces.AddFace(vidx4,vidx1,vidx2)
            elif(dist34 > threshold and dist41 > threshold ): # 1 quad, 2 triangles
                mesh2.Faces.AddFace(vidx4,widx4,widx3)
                mesh2.Faces.AddFace(widx4,vidx1,vidx3,widx3)
                mesh2.Faces.AddFace(vidx1,vidx2,vidx3)
            elif(dist41 > threshold and dist12 > threshold ): # 1 quad, 2 triangles
                mesh2.Faces.AddFace(vidx1,widx1,widx4)
                mesh2.Faces.AddFace(widx1,vidx2,vidx4,widx4)
                mesh2.Faces.AddFace(vidx2,vidx3,vidx4)
            elif(dist12 > threshold): # 1 quad, 1 triangle
                mesh2.Faces.AddFace(vidx1,widx1,vidx3, vidx4)
                mesh2.Faces.AddFace(widx1,vidx2,vidx3)
            elif(dist23 > threshold): # 1 quad, 1 triangle
                mesh2.Faces.AddFace(vidx2,widx2,vidx4, vidx1)
                mesh2.Faces.AddFace(widx2,vidx3,vidx4)
            elif(dist34 > threshold): # 1 quad, 1 triangle
                mesh2.Faces.AddFace(vidx3,widx3,vidx1, vidx2)
                mesh2.Faces.AddFace(widx3,vidx4,vidx1)
            elif(dist41 > threshold): # 1 quad, 1 triangle
                mesh2.Faces.AddFace(vidx4,widx4,vidx2, vidx3)
                mesh2.Faces.AddFace(widx4,vidx1,vidx2)
            else:
                mesh2.Faces.AddFace(vidx1,vidx2,vidx3,vidx4)
            
        else: # triangular face
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            v1 = vtx[vidx1]
            v2 = vtx[vidx2]
            v3 = vtx[vidx3]
            widx1 = mesh2.Vertices.Add((v1+v2)/2) # mid point
            widx2 = mesh2.Vertices.Add((v2+v3)/2) # mid point
            widx3 = mesh2.Vertices.Add((v3+v1)/2) # mid point
            widx4 = mesh2.Vertices.Add((v1+v2+v3)/3) # center point
            dist12 = v1.DistanceTo(v2)
            dist23 = v2.DistanceTo(v3)
            dist31 = v3.DistanceTo(v1)
            if(dist12 > threshold and dist23>threshold and dist31 > threshold): # 3 quads
                mesh2.Faces.AddFace(vidx1,widx1,widx4,widx3)
                mesh2.Faces.AddFace(vidx2,widx2,widx4,widx1)
                mesh2.Faces.AddFace(vidx3,widx3,widx4,widx2)
            elif(dist12 > threshold and dist23 > threshold): # 1 triangle, 1 quad
                mesh2.Faces.AddFace(vidx2,widx2,widx1)
                mesh2.Faces.AddFace(widx2,vidx3,vidx1,widx1)
            elif(dist23 > threshold and dist31 > threshold): # 1 triangle, 1 quad
                mesh2.Faces.AddFace(vidx3,widx3,widx2)
                mesh2.Faces.AddFace(widx3,vidx1,vidx2,widx2)
            elif(dist31 > threshold and dist12 > threshold): # 1 triangle, 1 quad
                mesh2.Faces.AddFace(vidx1,widx1,widx3)
                mesh2.Faces.AddFace(widx1,vidx2,vidx3,widx3)
            elif(dist12 > threshold): # 2 triangles
                mesh2.Faces.AddFace(vidx1,widx1,vidx3)
                mesh2.Faces.AddFace(widx1,vidx2,vidx3)
            elif(dist23 > threshold): # 2 triangles
                mesh2.Faces.AddFace(vidx2,widx2,vidx1)
                mesh2.Faces.AddFace(widx2,vidx3,vidx1)
            elif(dist31 > threshold): # 2 triangles
                mesh2.Faces.AddFace(vidx3,widx3,vidx2)
                mesh2.Faces.AddFace(widx3,vidx1,vidx2)
            else:
                mesh2.Faces.AddFace(vidx1,vidx2,vidx3)
            
    mesh2.Normals.ComputeNormals()
    mesh2.Weld(180)
    if(recursion>1):
        return subdivide(mesh2, recursion-1, threshold)
    return mesh2

a = subdivide(mesh,recursionLevel,maxEdgeLength)
- 7-23. Subdivision 20: Mesh Face Offset with Attractors   GH file

# offset mesh faces scaled by attractors
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), 
# offsetDepth : float (Item Access), scaleFactor : float (Item Access), 
# attractors : Point3d (List Access), attractorRange : float (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import math

def offsetMeshFaces(mesh, depth, offset, scale, resultList):
    mesh.Normals.ComputeNormals() # in case FaceNormals are missing
    mesh2 = rg.Mesh() # create a new mesh
    vtx = mesh.Vertices.ToPoint3dArray() # get all vertices in a list
    for i in range(mesh.Faces.Count):
        if mesh.Faces[i].IsQuad:
            n = rg.Vector3d(mesh.FaceNormals[i])
            v1 = vtx[mesh.Faces[i].A]
            v2 = vtx[mesh.Faces[i].B]
            v3 = vtx[mesh.Faces[i].C]
            v4 = vtx[mesh.Faces[i].D]
            v5 = (v1 + v2 + v3 + v4)/4
            aval = attractorValue(v5)
            if aval*scale > 0.01:
                v1 = (v1 - v5)*scale*aval + v5 + n * offset * aval
                v2 = (v2 - v5)*scale*aval + v5 + n * offset * aval
                v3 = (v3 - v5)*scale*aval + v5 + n * offset * aval
                v4 = (v4 - v5)*scale*aval + v5 + n * offset * aval
                vidx1 = mesh2.Vertices.Add(v1)
                vidx2 = mesh2.Vertices.Add(v2)
                vidx3 = mesh2.Vertices.Add(v3)
                vidx4 = mesh2.Vertices.Add(v4)
                mesh2.Faces.AddFace(vidx1,vidx2,vidx3,vidx4)
        else:
            n = rg.Vector3d(mesh.FaceNormals[i])
            v1 = vtx[mesh.Faces[i].A]
            v2 = vtx[mesh.Faces[i].B]
            v3 = vtx[mesh.Faces[i].C]
            v4 = (v1 + v2 + v3)/3
            aval = attractorValue(v4)
            if aval*scale > 0.01:
                v1 = (v1 - v4)*scale*aval + v4 + n * offset * aval
                v2 = (v2 - v4)*scale*aval + v4 + n * offset * aval
                v3 = (v3 - v4)*scale*aval + v4 + n * offset * aval
                vidx1 = mesh2.Vertices.Add(v1)
                vidx2 = mesh2.Vertices.Add(v2)
                vidx3 = mesh2.Vertices.Add(v3)
                mesh2.Faces.AddFace(vidx1,vidx2,vidx3)
    mesh2.Weld(math.pi)
    resultList.append(mesh2)
    if depth > 1:
        offsetMeshFaces(mesh2, depth-1, offset, scale, resultList)
        
def attractorValue(pos): # calculate value of 0-1 by attractors
    minDist = -1
    for attr in attractors: # find closest attractor
        dist = rs.Distance(pos, attr)
        if( minDist < 0 or dist < minDist ):
            minDist = dist
    if(minDist < attractorRange): # check range and scale value to 0 - 1
        return (attractorRange - minDist)/attractorRange # value from 0 to 1
    return 0 # when out of range

a = []
offsetMeshFaces(mesh, recursionLevel, offsetDepth, scaleFactor, a)
- 7-24. Subdivision 21: Mesh Face Offset with Randomness   GH file

# offset mesh faces and scale random choice of opposite edges
# input type - mesh : Mesh (Item Access), level : int (Item Access), 
# offsetDepth : float (Item Access),  scaleFactor : float (Item Access) 
import Rhino.Geometry as rg
import math
import random

def offsetMeshFaces(mesh, offset, scale, resultList):
    mesh2 = rg.Mesh() # create a new mesh
    vtx = mesh.Vertices.ToPoint3dArray() # get all vertices in a list
    for i in range(mesh.Faces.Count):
        if mesh.Faces[i].IsQuad:
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            vidx4 = mesh.Faces[i].D
            v1 = vtx[vidx1] + rg.Vector3d(mesh.Normals[vidx1]) * offset
            v2 = vtx[vidx2] + rg.Vector3d(mesh.Normals[vidx2]) * offset
            v3 = vtx[vidx3] + rg.Vector3d(mesh.Normals[vidx3]) * offset
            v4 = vtx[vidx4] + rg.Vector3d(mesh.Normals[vidx4]) * offset
            randomVal = random.random()
            if randomVal < 1/4: # scaling random choice of edges
                v1 = (v1 - v4)*scale + v4
                v2 = (v2 - v3)*scale + v3
            elif randomVal < 1/2:
                v2 = (v2 - v1)*scale + v1
                v3 = (v3 - v4)*scale + v4
            elif randomVal < 3/4:
                v3 = (v3 - v2)*scale + v2
                v4 = (v4 - v1)*scale + v1
            else:
                v4 = (v4 - v3)*scale + v3
                v1 = (v1 - v2)*scale + v2
            vidx1 = mesh2.Vertices.Add(v1)
            vidx2 = mesh2.Vertices.Add(v2)
            vidx3 = mesh2.Vertices.Add(v3)
            vidx4 = mesh2.Vertices.Add(v4)
            mesh2.Faces.AddFace(vidx1,vidx2,vidx3,vidx4)
        else:
            vidx1 = mesh.Faces[i].A
            vidx2 = mesh.Faces[i].B
            vidx3 = mesh.Faces[i].C
            v1 = vtx[vidx1] + rg.Vector3d(mesh.Normals[vidx1]) * offset
            v2 = vtx[vidx2] + rg.Vector3d(mesh.Normals[vidx2]) * offset
            v3 = vtx[vidx3] + rg.Vector3d(mesh.Normals[vidx3]) * offset
            randomVal = random.random()
            if randomVal < 1/3: # scaling random choice of edges
                v1 = (v1 - v3)*scale + v3
                v2 = (v2 - v3)*scale + v3
            elif randomVal < 2/3:
                v2 = (v2 - v1)*scale + v1
                v3 = (v3 - v1)*scale + v1
            else:
                v3 = (v3 - v2)*scale + v2
                v1 = (v1 - v2)*scale + v2
            vidx1 = mesh2.Vertices.Add(v1)
            vidx2 = mesh2.Vertices.Add(v2)
            vidx3 = mesh2.Vertices.Add(v3)
            mesh2.Faces.AddFace(vidx1,vidx2,vidx3)
    mesh2.Normals.ComputeNormals()
    mesh2.Weld(math.pi)
    resultList.append(mesh2)

a = []
for i in range(level): # not recursion but repetition
    offsetMeshFaces(mesh, offsetDepth*(i+1), scaleFactor, a)
    scaleFactor *= scaleFactor
- 7-25. Subdivision 22: Extruded Face Offset with Attractors   GH file

# offset extruded closed mesh from faces
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), offsetDepth : float (Item Access),
# height : float (Item Access), scale : float (Item Access), attractors : Point3d (List Access), attractorRange : float (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import math

def offsetMesh(mesh, depth, offset, height, scale, resultList):
    mesh.Normals.ComputeNormals() # in case FaceNormals are missing
    vtx = mesh.Vertices.ToPoint3dArray() # get all vertices in a list
    for i in range(mesh.Faces.Count):
        mesh2 = rg.Mesh() # create a new mesh
        if mesh.Faces[i].IsQuad:
            n = rg.Vector3d(mesh.FaceNormals[i])
            v1 = vtx[mesh.Faces[i].A]
            v2 = vtx[mesh.Faces[i].B]
            v3 = vtx[mesh.Faces[i].C]
            v4 = vtx[mesh.Faces[i].D]
            v5 = (v1+v2+v3+v4)/4
            aval = attractorValue(v5)
            if scale*aval > 0.01: # ignore if too small
                v1a = (v1 - v5)*scale*aval + v5 + n * (offset - height) * aval
                v2a = (v2 - v5)*scale*aval + v5 + n * (offset - height) * aval
                v3a = (v3 - v5)*scale*aval + v5 + n * (offset - height) * aval
                v4a = (v4 - v5)*scale*aval + v5 + n * (offset - height) * aval
                v1b = (v1 - v5)*scale*aval + v5 + n * (offset + height) * aval
                v2b = (v2 - v5)*scale*aval + v5 + n * (offset + height) * aval
                v3b = (v3 - v5)*scale*aval + v5 + n * (offset + height) * aval
                v4b = (v4 - v5)*scale*aval + v5 + n * (offset + height) * aval
                vidx1a = mesh2.Vertices.Add(v1a) # bottom 4 vertices
                vidx2a = mesh2.Vertices.Add(v2a)
                vidx3a = mesh2.Vertices.Add(v3a)
                vidx4a = mesh2.Vertices.Add(v4a)
                vidx1b = mesh2.Vertices.Add(v1b) # top 4 vertices
                vidx2b = mesh2.Vertices.Add(v2b)
                vidx3b = mesh2.Vertices.Add(v3b)
                vidx4b = mesh2.Vertices.Add(v4b)
                mesh2.Faces.AddFace(vidx1a,vidx2a,vidx2b,vidx1b)
                mesh2.Faces.AddFace(vidx2a,vidx3a,vidx3b,vidx2b)
                mesh2.Faces.AddFace(vidx3a,vidx4a,vidx4b,vidx3b)
                mesh2.Faces.AddFace(vidx4a,vidx1a,vidx1b,vidx4b)
                mesh2.Faces.AddFace(vidx1a,vidx4a,vidx3a,vidx2a)
                mesh2.Faces.AddFace(vidx1b,vidx2b,vidx3b,vidx4b)
        else:
            n = rg.Vector3d(mesh.FaceNormals[i])
            v1 = vtx[mesh.Faces[i].A]
            v2 = vtx[mesh.Faces[i].B]
            v3 = vtx[mesh.Faces[i].C]
            v4 = (v1 + v2 + v3)/3
            aval = attractorValue(v4)
            if scale*aval > 0.01: # ignore if too small
                v1a = (v1 - v4)*scale*aval + v4 + n * (offset - height) * aval
                v2a = (v2 - v4)*scale*aval + v4 + n * (offset - height) * aval
                v3a = (v3 - v4)*scale*aval + v4 + n * (offset - height) * aval
                v1b = (v1 - v4)*scale*aval + v4 + n * (offset + height) * aval
                v2b = (v2 - v4)*scale*aval + v4 + n * (offset + height) * aval
                v3b = (v3 - v4)*scale*aval + v4 + n * (offset + height) * aval
                vidx1a = mesh2.Vertices.Add(v1a) # bottom 3 vertices
                vidx2a = mesh2.Vertices.Add(v2a)
                vidx3a = mesh2.Vertices.Add(v3a)
                vidx1b = mesh2.Vertices.Add(v1b) # top 3 vertices
                vidx2b = mesh2.Vertices.Add(v2b)
                vidx3b = mesh2.Vertices.Add(v3b)
                mesh2.Faces.AddFace(vidx1a,vidx2a,vidx2b,vidx1b)
                mesh2.Faces.AddFace(vidx2a,vidx3a,vidx3b,vidx2b)
                mesh2.Faces.AddFace(vidx3a,vidx1a,vidx1b,vidx3b)
                mesh2.Faces.AddFace(vidx1a,vidx3a,vidx2a)
                mesh2.Faces.AddFace(vidx1b,vidx2b,vidx3b)
        mesh2.Weld(math.pi)
        resultList.append(mesh2)
        if depth > 1:
            offsetMesh(mesh2, depth-1, offset*0.5, height*0.5, scale, resultList)

def attractorValue(pos): # calculate value of 0-1 by attractors
    minDist = -1
    for attr in attractors: # find closest attractor
        dist = rs.Distance(pos, attr)
        if( minDist < 0 or dist < minDist ):
            minDist = dist
    if(minDist < attractorRange): # check range and scale value to 0 - 1
        return (attractorRange - minDist)/attractorRange # value from 0 to 1
    return 0 # when out of range

a = []
offsetMesh(mesh, recursionLevel, offsetDepth, height, scale, a)
- 7-26. Subdivision 23: Polyhedron Face Offset with Randomness   GH file

# offset polyhedron mesh from faces
# input type - mesh : Mesh (Item Access), recursionLevel : int (Item Access), probability : float (Item Access), 
# offsetDepth : float (Item Access), height : float (Item Access), scale : float (Item Access)
import Rhino.Geometry as rg
import math
import random

def offsetMesh(mesh, depth, offset, height, scale, resultList):
    mesh.Normals.ComputeNormals() # in case FaceNormals are missing
    vtx = mesh.Vertices.ToPoint3dArray() # get all vertices in a list
    for i in range(mesh.Faces.Count):
        if random.random() * 100 < probability : # randomly adding faces
            mesh2 = rg.Mesh() # create a new mesh
            if mesh.Faces[i].IsQuad:
                n = rg.Vector3d(mesh.FaceNormals[i])
                v1 = vtx[mesh.Faces[i].A]
                v2 = vtx[mesh.Faces[i].B]
                v3 = vtx[mesh.Faces[i].C]
                v4 = vtx[mesh.Faces[i].D]
                v5 = (v1+v2+v3+v4)/4
                v1 = (v1 - v5)*scale + v5 + n * offset
                v2 = (v2 - v5)*scale + v5 + n * offset
                v3 = (v3 - v5)*scale + v5 + n * offset
                v4 = (v4 - v5)*scale + v5 + n * offset
                v5 = v5 + n * (offset - height)
                v6 = v5 + n * (offset + height)
                vidx1 = mesh2.Vertices.Add(v1) # 4 side vertices
                vidx2 = mesh2.Vertices.Add(v2)
                vidx3 = mesh2.Vertices.Add(v3)
                vidx4 = mesh2.Vertices.Add(v4)
                vidx5 = mesh2.Vertices.Add(v5) # bottom vertex
                vidx6 = mesh2.Vertices.Add(v6) # top vertex
                mesh2.Faces.AddFace(vidx2,vidx1,vidx5)
                mesh2.Faces.AddFace(vidx3,vidx2,vidx5)
                mesh2.Faces.AddFace(vidx4,vidx3,vidx5)
                mesh2.Faces.AddFace(vidx1,vidx4,vidx5)
                mesh2.Faces.AddFace(vidx1,vidx2,vidx6)
                mesh2.Faces.AddFace(vidx2,vidx3,vidx6)
                mesh2.Faces.AddFace(vidx3,vidx4,vidx6)
                mesh2.Faces.AddFace(vidx4,vidx1,vidx6)
            else:
                n = rg.Vector3d(mesh.FaceNormals[i])
                v1 = vtx[mesh.Faces[i].A]
                v2 = vtx[mesh.Faces[i].B]
                v3 = vtx[mesh.Faces[i].C]
                v4 = (v1 + v2 + v3)/3
                v1 = (v1 - v4)*scale + v4 + n * offset
                v2 = (v2 - v4)*scale + v4 + n * offset
                v3 = (v3 - v4)*scale + v4 + n * offset
                v4 = v4 + n * (offset - height)
                v5 = v4 + n * (offset + height)
                vidx1 = mesh2.Vertices.Add(v1) # 3 side vertices
                vidx2 = mesh2.Vertices.Add(v2)
                vidx3 = mesh2.Vertices.Add(v3)
                vidx4 = mesh2.Vertices.Add(v4) # bottom vertex
                vidx5 = mesh2.Vertices.Add(v5) # top vertex
                mesh2.Faces.AddFace(vidx2,vidx1,vidx4)
                mesh2.Faces.AddFace(vidx3,vidx2,vidx4)
                mesh2.Faces.AddFace(vidx1,vidx3,vidx4)
                mesh2.Faces.AddFace(vidx1,vidx2,vidx5)
                mesh2.Faces.AddFace(vidx2,vidx3,vidx5)
                mesh2.Faces.AddFace(vidx3,vidx1,vidx5)
            mesh2.Weld(math.pi)
            resultList.append(mesh2)
            if depth > 1:
                offsetMesh(mesh2, depth-1, offset/2, height/2, scale, resultList)

a = []
offsetMesh(mesh, recursionLevel, offsetDepth, height, scale, a)
- 7-27. Subdivision 24: Vertex-Based Subdivision 1   GH file

# subdivide mesh by adding faces around vertices
# input type - mesh : Mesh (Item Access), depth : int (Item Access), offset : float (Item Access)
import Rhino.Geometry as rg
import math

def subdivide(mesh, depth, offset):
    mesh2 = rg.Mesh() # create a new mesh
    vnum = mesh.TopologyVertices.Count
    vtx = []
    for i in range(vnum): # convert topology vertices into Point3d
        vtx.append( rg.Point3d(mesh.TopologyVertices[i]) )
    edgePts = {}
    for i in range(vnum):
        vidcs = mesh.TopologyVertices.ConnectedTopologyVertices(i, True) # vertex with connection info
        nml = mesh.Normals[mesh.TopologyVertices.MeshVertexIndices(i)[0]] # normal at topology vertex
        vidx1 = mesh2.Vertices.Add(vtx[i] + rg.Vector3d(nml) * offset)
        vidx2 = []
        edgePts[i] = {} # to remember mid points on edges
        for v in vidcs:
            if v in edgePts: # mid point already exists
                evidx = edgePts[v][i]
            else:
                evidx = mesh2.Vertices.Add( (vtx[i]+vtx[v])/2 ) # add new mid point
                edgePts[i][v] = evidx # add new vertex
            vidx2.append(evidx)
        for k in range(len(vidx2)): # add triangular faces around a vertex
            dir1 = rg.Point3d(mesh2.Vertices[vidx2[k]]) - vtx[i]
            dir2 = rg.Point3d(mesh2.Vertices[vidx2[(k+1)%len(vidx2)]]) - vtx[i]
            n = rg.Vector3d.CrossProduct(dir1, dir2)
            if rg.Vector3d.Multiply(nml, n) > 0: # exclude adding face on naked edges
                mesh2.Faces.AddFace( vidx1, vidx2[k], vidx2[(k+1)%len(vidx2)] )
    mesh2.Normals.ComputeNormals()
    mesh2.Weld(math.pi)
    if depth > 1:
        return subdivide(mesh2, depth-1, offset/2)
    return mesh2

a = subdivide(mesh, depth, offset)
- 7-28. Subdivision 25: Vertex-Based Subdivision 2   GH file

# subdivide mesh by adding faces around vertices
# input type - mesh : Mesh (Item Access), depth : float (Item Access), 
# offset : float (Item Access), ratio : float (Item Access)
import Rhino.Geometry as rg
import math

def subdivide(mesh, depth, ratio, offset):
    mesh2 = rg.Mesh() # create a new mesh
    vnum = mesh.TopologyVertices.Count
    vtx = []
    for i in range(vnum): # convert topology vertices into Point3d
        vtx.append( rg.Point3d(mesh.TopologyVertices[i]) )
    for i in range(vnum):
        vidcs = mesh.TopologyVertices.ConnectedTopologyVertices(i, True) # vertex with connection info
        nml = mesh.Normals[mesh.TopologyVertices.MeshVertexIndices(i)[0]] # normal at topology vertex
        vidx1 = mesh2.Vertices.Add(vtx[i] + rg.Vector3d(nml) * offset)
        vidx2 = []
        for v in vidcs:
            evidx = mesh2.Vertices.Add( (vtx[i]*(1-ratio)+vtx[v]*ratio ) ) # add vertex on edge
            vidx2.append(evidx)
        for k in range(len(vidx2)): # add triangular faces around a vertex
            dir1 = rg.Point3d(mesh2.Vertices[vidx2[k]]) - vtx[i]
            dir2 = rg.Point3d(mesh2.Vertices[vidx2[(k+1)%len(vidx2)]]) - vtx[i]
            n = rg.Vector3d.CrossProduct(dir1, dir2)
            if rg.Vector3d.Multiply(nml, n) > 0: # exclude adding face on naked edges
                mesh2.Faces.AddFace( vidx1, vidx2[k], vidx2[(k+1)%len(vidx2)] )
    mesh2.Normals.ComputeNormals()
    mesh2.Weld(math.pi)
    if depth > 1:
        return subdivide(mesh2, depth-1, ratio, offset/2)
    return mesh2

a = subdivide(mesh, depth, ratio, offset)
- 7-29. Subdivision 26: Vertex-Based Polyhedron Subdivision   GH file

# subdivide mesh by adding polyhedron faces around vertices
# input type - mesh : Mesh (Item Access), depth : int (Item Access), ratio : float (Item Access),
# offset1 : float (Item Access), offset2 : float (Item Access)
import Rhino.Geometry as rg
import math

def subdivide(mesh, depth, ratio, offset1, offset2):
    mesh2 = rg.Mesh() # create a new mesh
    vnum = mesh.TopologyVertices.Count
    vtx = []
    for i in range(vnum): # convert topology vertices into Point3d
        vtx.append( rg.Point3d(mesh.TopologyVertices[i]) )
    for i in range(vnum):
        vidcs = mesh.TopologyVertices.ConnectedTopologyVertices(i, True) # vertex with connection info
        nml = averageNormal(vtx, i, vidcs) # calculate normal from connected vertices
        vidx1a = mesh2.Vertices.Add(vtx[i] + rg.Vector3d(nml)*offset1) # new top vertex
        vidx1b = mesh2.Vertices.Add(vtx[i] - rg.Vector3d(nml)*offset2) # new bottom vertex
        vidx2 = []
        for v in vidcs:
            evidx = mesh2.Vertices.Add( vtx[i]*(1-ratio)+vtx[v]*ratio ) # add vertex on edge
            vidx2.append(evidx)
        for k in range(len(vidx2)):
            dir1 = rg.Point3d(mesh2.Vertices[vidx2[k]]) - vtx[i]
            dir2 = rg.Point3d(mesh2.Vertices[vidx2[(k+1)%len(vidx2)]]) - vtx[i]
            n = rg.Vector3d.CrossProduct(dir1, dir2)
            if rg.Vector3d.Multiply(nml, n) >= 0: # add polyhedron faces
                mesh2.Faces.AddFace( vidx1a, vidx2[k], vidx2[(k+1)%len(vidx2)] )
                mesh2.Faces.AddFace( vidx1b, vidx2[(k+1)%len(vidx2)], vidx2[k] )
            else: # close faces on naked edge
                mesh2.Faces.AddFace( vidx1a, vidx2[k], vidx1b )
                mesh2.Faces.AddFace( vidx1a, vidx1b, vidx2[(k+1)%len(vidx2)] )
    mesh2.Normals.ComputeNormals()
    mesh2.Weld(math.pi)
    if depth > 1:
        return subdivide(mesh2, depth-1, ratio, offset1/2, offset2/2)
    return mesh2

def averageNormal(vtx, centerIdx, linkIdx): # calculate normal from sorted linked vertices
    nml = rg.Vector3d(0,0,0)
    num = len(linkIdx)
    if num <= 2: # if only 2 vertices, calc only 1 normal
        num = 1
    for i in range(num):
        dir1 = vtx[linkIdx[i]] - vtx[centerIdx]
        dir2 = vtx[linkIdx[(i+1)%len(linkIdx)]] - vtx[centerIdx]
        n = rg.Vector3d.CrossProduct(dir1, dir2)
        n.Unitize()
        nml += n
    return nml

a = subdivide(mesh, depth, ratio, offset1, offset2)
- 7-30. Custom Mesh Tool 1: Dispatch Faces Randomly   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), probability : float (Item Access)
import Rhino.Geometry as rg
import random

def dispatch(mesh):
    mesh2 = rg.Mesh() # new mesh
    mesh3 = rg.Mesh() # another new mesh
    
    vtx = mesh.Vertices.ToPoint3dArray()
    mesh2.Vertices.AddVertices(vtx) # add all vertices
    mesh3.Vertices.AddVertices(vtx) # add all vertices
    
    for i in range(mesh.Faces.Count):
        if random.random()*100 < probability: # probabilistic switch with randomness
            mesh2.Faces.AddFace(mesh.Faces[i])
        else:
            mesh3.Faces.AddFace(mesh.Faces[i])
    
    mesh2.Normals.ComputeNormals()
    mesh3.Normals.ComputeNormals()
    return [mesh2, mesh3] returns two meshes

[a, b] = dispatch(mesh)
- 7-31. Custom Mesh Tool 2: Dispatch Faces by Attractors   GH file

# move each vertex by the closest attractor and create a new mesh
# input type - mesh : Mesh (Item Access), attractors : Point3d (List Access), attractorRange : float (Item Access)
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg

def dispatch(mesh):
    mesh2 = rg.Mesh() # new mesh
    mesh3 = rg.Mesh() # another new mesh
    vtx = mesh.Vertices.ToPoint3dArray()
    mesh2.Vertices.AddVertices(vtx) # add all vertices
    mesh3.Vertices.AddVertices(vtx) # add all vertices
    
    for i in range(mesh.Faces.Count):
        if isAttractorInRange(faceCenter(mesh, mesh.Faces[i])): # if face center is within attractor range
            mesh2.Faces.AddFace(mesh.Faces[i])
        else:
            mesh3.Faces.AddFace(mesh.Faces[i])
    
    mesh2.Normals.ComputeNormals()
    mesh3.Normals.ComputeNormals()
    return [mesh2, mesh3]

def faceCenter(mesh, face):
    vtx = mesh.Vertices.ToPoint3dArray()
    if face.IsTriangle:
        return (vtx[face.A] + vtx[face.B] + vtx[face.C])/3 # center of 3 vertices
    return (vtx[face.A] + vtx[face.B] + vtx[face.C] + vtx[face.D])/4 # center of 4 vertices

def isAttractorInRange(pos): # check if any attractor is in range
    for attr in attractors: 
        dist = rs.Distance(pos, attr)
        if dist < attractorRange :
            return True
    return False # when out of range

[a, b] = dispatch(mesh)
- 7-32. Custom Subdivision Combination Example 1   GH file

Randomize Vertices (7-2)
+ Dispatch Faces Randomly (7-30)
+ Triangulate (Grasshopper mesh command)
+ Mesh Join (Grasshopper mesh command)
+ Weld Mesh (Grasshopper mesh command)
+ Frame Probabilistic Subdivision (7-11)
+ Mesh Thicken (Weaverbird command)
+ Frame Probabilistic Subdivision (7-11)
+ Catmull-Clark Subdivision (Weaverbird command)
+ Mesh Thicken (Weaverbird command)
+ Quad Subdivision with Attractors (7-15)


- 7-33. Custom Subdivision Combination Example 2   GH file

Quad Subdivision with Randomness (7-14)
+ Tubular Subdivision (7-20)
+ Catmull-Clark Subdivision (Weaverbird command)
+ Dispatch Faces by Attractors (7-31)
+ Frame Probabilistic Subdivision (7-11)
+ Linear Subdivision (7-19)
+ Mesh Join (Grasshopper mesh command)
+ Weld Mesh (Grasshopper mesh command)
+ Quad Subdivision with Attractors (7-15)


- 7-34. Custom Subdivision Combination Example 3   GH file

1D Fractal Quad Subdivision (7-8)
+ 1D Fractal Quad Subdivision (7-8)
+ Move Vertices by Attractor (7-3)
+ Quad Probabilistic Subdivision with Depth Control by Area (7-18)
+ Long Edge Subdivision (7-22)
+ Quad Subdivision with Attractors (7-15)


- 7-35. Custom Subdivision Combination Example 4   GH file

Quad Subdivision with Randomness (7-14)
+ Tubular Subdivision with Tab Faces (7-21)
+ Catmull-Clark Subdivision (Weaverbird command)


- 7-36. Custom Subdivision Combination Example 5   GH file

Linear Subdivision (7-19)
+ Mesh Join (Grasshopper mesh command)
+ Weld Mesh (Grasshopper mesh command)
+ Linear Subdivision (7-19)
+ Mesh Join (Grasshopper mesh command)
+ Weld Mesh (Grasshopper mesh command)
+ Triangulate (Grasshopper mesh command)
+ Dispatch Faces Randomly (7-30)
+ Linear Subdivision (7-19)
+ Frame Subdivision (7-10)
+ Mesh Join (Grasshopper mesh command)
+ Weld Mesh (Grasshopper mesh command)