Python draw Fibonacci squares (and spiral)?

Hello,

Since nobody was able to help me with this problem, I just wanted to let the followers of this post know that I’ve finally figured it out myself. I guess being stubborn really helps from time to time. :joy:

For everybody interested, on how you can draw Fibonacci squares and the spiral with GHPython, here’s the script:

"""Pythonic generator of sequential Fibonacci squares and their corresponding nautilus spiral.
    Inputs:
        P: start point of the spiral and the sequence of squares 
        L: list of Fibonacci numbers, corresponding to the side length of the squares
    Output:
        V: tree with L-branches of 5 3d points, representing the squares vertices
        C: list of closed, planar polylines representing the squares as cells
        S: list of arcs, representing the spiral fragment per square
        F: numbers of the used Fibonacci sequence"""

__author__ = "P1r4t3b0y"

ghenv.Component.Name = "Pythonic Fibonacci Squares And Spiral Generator"
ghenv.Component.NickName = 'pyFibSpiral'


from Grasshopper import DataTree as Tree
from Grasshopper.Kernel.Data import GH_Path as Path
from System import Array
import rhinoscriptsyntax as rs
import math


def list_to_tree(input, none_and_holes=True, source=[0]):
    """Transforms nestings of lists or tuples to a Grasshopper DataTree"""
    # written by Giulio Piacentino, giulio@mcneel.com
    def proc(input,tree,track):
        path = Path(Array[int](track))
        if len(input) == 0 and none_and_holes: tree.EnsurePath(path); return
        for i,item in enumerate(input):
            if hasattr(item, '__iter__'): #if list or tuple
                track.append(i); proc(item,tree,track); track.pop()
            else:
                if none_and_holes: tree.Insert(item,path,i)
                elif item is not None: tree.Add(item,path)
    if input is not None: t=Tree[object]();proc(input,t,source[:]);return t


def tree_to_list(input, retrieve_base = lambda x: x[0]):
    """Returns a list representation of a Grasshopper DataTree"""
    # written by Giulio Piacentino, giulio@mcneel.com
    def extend_at(path, index, simple_input, rest_list):
        target = path[index]
        if len(rest_list) <= target: rest_list.extend([None]*(target-len(rest_list)+1))
        if index == path.Length - 1:
            rest_list[target] = list(simple_input)
        else:
            if rest_list[target] is None: rest_list[target] = []
            extend_at(path, index+1, simple_input, rest_list[target])
    all = []
    for i in range(input.BranchCount):
        path = input.Path(i)
        extend_at(path, 0, input.Branch(path), all)
    return retrieve_base(all)


def squares_sequence_vtx(start_pt, side_lengths, directions):
    """Returns a list of lists with vertices per square.
    
    	Keyword arguments:
    	- start_pt     : 3D Point (start point of the spiral and sequence) 
    	- side_lengths : List of float/integer numbers (fibonacci sequence of numbers)
    	- directions   : List of 3D points (directions for the vertices)"""
    tmp_next_vtx = 0
    rot_idx = 0 # rotation index
    square_vtx = []
    for s in range(len(side_lengths)): # loops over the Fibonacci sequence
        vtx = []
        for v in range(4): # the loop runs 4 times for 4 vtx per square
            new_vtx = rs.coerce3dpoint(start_pt) + (side_lengths[s] * directions[rot_idx][v])
            vtx.append(new_vtx)
            if v == 2:
                tmp_next_vtx = new_vtx
            elif v == 3: # append start point as last point for each square
                new_vtx = rs.coerce3dpoint(start_pt) + (side_lengths[s] * directions[rot_idx][0])
                vtx.append(new_vtx)
        start_pt = tmp_next_vtx
        rot_idx += 1
        square_vtx.append(vtx)
        
        if rot_idx > 3: 
            # resets rotation index for next row of directions
            rot_idx = 0
    return square_vtx


def pt_on_circle(center_pt, radius, angle):
    """Returns a 3d point on a circle and its guid.

    	Keyword arguments:
    	- center_pt : 3D Point (origin of the circle) 
    	- radius    : Float/Integer (radius of the circle)
    	- angle     : Float/Integer (angle in degrees)"""
    x = center_pt[0] + radius * math.cos(math.radians(angle))
    y = center_pt[1] + radius * math.sin(math.radians(angle))
    z = center_pt[2]
    pt_id = rs.AddPoint((x,y,z))
    pt = rs.coerce3dpoint(pt_id)
    return pt, pt_id


if __name__ == "__main__":
    # SQUARE SEQUENCE VERTICES GENERATION
    dirs_lt = [
        [(0,0,0), (1,0,0), (1,-1,0), (0,-1,0)],
        [(0,0,0), (0,-1,0), (-1,-1,0), (-1,0,0)],
        [(0,0,0), (-1,0,0), (-1,1,0), (0,1,0)],
        [(0,0,0), (0,1,0), (1,1,0), (1,0,0)]]

    # converts the list of coordinates to 3d points
    dir_pts = []
    for dt in dirs_lt: 
        dir_pt = []
        for s in dt:
            pt_id = rs.AddPoint(s)
            pt = rs.coerce3dpoint(pt_id)
            dir_pt.append(pt)
        dir_pts.append(dir_pt)

    fibo_nums = L[1:] # 0 at the beginning of the Fibonacci number sequence is omitted
    square_vtx = squares_sequence_vtx(P, fibo_nums, dir_pts)

    # SPIRAL ARCS GENERATION
    square_crvs = []
    spiral_arcs = []

    for i, vtx in enumerate(square_vtx):
        arc_pt1 = vtx[0]
        arc_pt2 = vtx[2]
        radius = fibo_nums[i]
        center_pt = rs.coerce3dpoint(vtx[3])
        crv = rs.AddPolyline(vtx)
        arc_pton, arc_pton_id = None, None
        # evaluates a point on each arc
        for i in range(4): 
            angle = 45 + (i * 90)
            test_pt_params = pt_on_circle(center_pt, radius, angle)
            test = rs.PointInPlanarClosedCurve(test_pt_params[0], crv)
            if test == 1: # point inside fibonacci box
                arc_pton, arc_pton_id = test_pt_params
                arc = rs.AddArc3Pt(arc_pt1, arc_pt2, arc_pton)
                spiral_arcs.append(arc)
                break
        square_crvs.append(crv)


    # OUTPUTS
    V = list_to_tree(square_vtx)
    C = square_crvs
    S = spiral_arcs
    F = fibo_nums

I’m by no means an expert programmer, but the script works like a charm. :raised_hands:

1 Like