Python draw Fibonacci squares (and spiral)?

Hello,

I’m currently trying to come up with a pythonic way in Grasshopper to draw the Fibonacci squares.
My plan is to feed a Fibonacci sequence (i.e. 0, 1, 1, 3, 4, 7, etc.) into a Python container. For each Fibonacci number in the sequence, the vertices of the square will be produced by starting at an initial point, placing the next point at a given direction x-units (where x is a Fibonacci number) from the start point and changing the direction by 90° to the right. This is going to be repeated 4 times producing 4 vertices that will form a square per Fibonacci number.
The starting point of the next number will always be the 3 vertex of the previous square, from which a new square will be drawn for the next Fibonacci number in the sequence.
(The circle fragments of each square will produce a Fibonacci spiral.)

So far I’ve come up with this:


def draw_square(start_pt, dir, side_length):
    vtx = []
    vtx.append(start_pt)
    rot_dir = dir # rotated direction
    for i in range(2):
        # find next point by moving x-units (side_length) in a given direction (dir) 
        vtx.append(next_pt)
        # rotate the direction by 90° to the right and save it to rot_dir for the next iteration
    return vtx

If the function draw_square is called it should return a list of vertices vtx, representing a square with a side length of side_length. Its first point is the start_pt. Its next points are drawn at a distance side_length and a direction dir from their previous points. The direction is than, at each generation, rotated by 90° to the right an so fourth.


squares = []
for i, num in enumerate(fibonacci_sequence):
		if i == 0: # first square
				start_pt = (0,0,0)
		    start_dir = (1, 0, 0)
        square = draw_square(start_pt, start_dir, num)
				squares.append(square)
		else: # other squares
				start_pt = squares[-1][2] # 3 vertex of last square
		    dir = rs.VectorUnitize(rs.VectorCreate(squares[-1][1], start_pt))
        square = draw_square(start_pt, dir, num)
				squares.append(square)

The function draw_square is called for each number in the Fibonacci sequence and produces a square with the side length of the number.
The first square is produced with a changeable start point and direction. The following squares heavily depend on the first one. Their start point is the 3rd vertex of the previous square, and their direction is calculated by normalising the vector between the 3rd and 2nd vertex of the previous square.

The square thus produced are saved in a list of lists and output as tree.

In the function draw_square, I don’t get how I could calculate the next point by side length and direction only?
Is the rest of the code plausible?

Any help is greatly welcome.

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

Hey great work, i’m just trying to do something similar, but i’m a beginner.
i get a wrong msg in line 120, in defining fibo_nums = L[1:]
Should L be defined with range from 1 to 100 or how it works?
could you help me out!!
ty.

@dineshpathmaraj you should feed it a fibonacci sequence. See attached

But also: L should be set to list input and either get type hint int or make sure it gets to eat integers.

fibonacci.gh (8.9 KB)

1 Like

As @Gijs indicated above, there must be a component input L that is set to list input. Defining its type hint is not required in this case, if you leave it at its default setting.
L must be a Fibonacci number sequence/range (e.g. list of Fibonacci numbers). You can construct it with the vanilla Grasshopper Fibonacci component or @Gijs’ Python example.

I usually document what needs to go where at appropriate places in the script.
For instance, at the top of the script, all inputs and outputs of the GHPython component are listed. Under “Inputs”, you can note that you need an two inputs, P and L, where P is a “start point of the spiral and the sequence of squares”, and L a “list of Fibonacci numbers, corresponding to the side length of the squares”.
You also need 4 component outputs: V, C, S, and F.

At function/method level, you can find instructions about what the function does and which items you have to provide to it, to be processed internally.

At line 120, the first number is omitted from the Fibonacci sequence, provided by component input L, since it corresponds to the start point of the sequence that we need separately, and stored in the variable fibo_nums.

fibo_nums = L[1:]

At line 121, the start point P, as well as the Fibonacci sequence fibo_nums, where the start point was previously omitted from, and a list of dir_pts that inform about the vertex positions of the squares are passed, as arguments to the function squares_sequence_vtx.
This function then returns a nested list of current square vertices that are stored in the variable square_vtx.

square_vtx = squares_sequence_vtx(P, fibo_nums, dir_pts)

As a learning exercise, I’ve made another go at it, this time using faster code for the fibonacci sequence:
fibonacci.gh (9.3 KB)

1 Like