Rotating rectangles based on an artpiece by Vera Molnar



  • Intro

    This is based on an art work by Vera Molnar (a pioneer in the field of generative art).
    This script will generate a pattern with randomly rotated rectangles – using two nested loops.

    Code and explanation

    Lets start with the default settings of DrawBot:
    fill color is black, a white background and a canvas with a width and height of 1000 units.

    First we define how many rows and columns we want to have in the drawing and assign this value to the variable amount

    amount = 24
    

    Next we want to define the size of each cell in the grid. We want to calculate this value and not define a fixed value so it updates automatically. To calculate the width of the cell we call the width() function which returns the width of the canvas (in our case the default value of 1000). We divide this value by the amount of cells (stored in the variable amount). Since we want to have a margin around the drawing we just add 2 to the amount.

    cell_s = width() / (amount + 2)
    

    Since we want the squares rotated by 45 degrees to touch at the corners we need to calculate the size of the rectangles.
    With a rotation of 45 degrees this can be done quite easily by dividing the cell size by the square root of 2.

    rect_s = cell_s / sqrt(2)
    

    Now we should have all the numbers ready to start drawing the pattern. To apply the margin we first shift the origin of the drawing one cell size to the right and to the top. We do this by calling the translate() function with the parameters of one cell size in the x direction and the y direction

    translate(cell_s, cell_s)
    

    Now lets start to loop in the x direction as many times as defined in the amount variable

    for x in range(amount):
    

    And then for each x value do the same in the y direction. So our total amount of cells will be the square of the amount (24 * 24 = 576 cells).

        for y in range(amount):
    

    Before we call the rotate() function lets first shift the origin with the translate() function. Since we want to revert back to the default state (with no rotation) after we have drawn a rectangle let’s call the savedState() function first:

            with savedState():
    

    Now let’s do the shifting: Shift x times the cell size to the left and y times the cell size to the top. Since we want the rotation to be happening in the center of the cell we need to add half a cell size in each direction.

                translate( x * cell_s + cell_s/2, y * cell_s + cell_s/2 )
    

    We only want to do the rotation on some of the cells. So we just call the random() function which returns a randomly generated value between 0 and 1. If this value is higher than 0.5 (which on average should be in half of the cases) let’s do the rotation:

                if random() > .5: 
                	rotate(45)
    

    Finally lets draw a rectangle by calling the rect() function. The rectangle should be drawn half a cell size to the left and bottom to compensate for the extra translation from before:

    0_1519853152104_rotate_c.gif

                rect(-rect_s/2, -rect_s/2, rect_s, rect_s)
    

    To save the image lets call the saveImage() function and give a path and file name:

    saveImage('~/Desktop/Vera_Molnar_rotating_rects.png')
    

    Everything put together

    amount = 24
    cell_s = width() / (amount + 2)
    
    rect_s = cell_s / sqrt(2)
    translate( cell_s, cell_s)
    
    for x in range(amount):
        for y in range(amount):
            with savedState():
                translate( x * cell_s + cell_s/2, y * cell_s + cell_s/2 )
                if random() > .5: 
                	rotate(45)
                rect(-rect_s/2, -rect_s/2, rect_s, rect_s)
    
    # saveImage('~/Desktop/Vera_Molnar_rotating_rects.png')
    

    Image

    0_1518885365555_Vera_Molnar_rotating_rects27.png



  • Nicely done! It's about time I started explaining some of the stuff I've been making. You set a high bar.



  • @mauricemeilleur
    thanks!
    not sure what to explain and what is obvious.
    i guess explanations should be as precise as code needs to be.



  • A small extension to the original code.
    The above Version (with the rectangle size of cell size / sqrt(2)) is only one of a series of four. Depending on the size of the rectangles they touch in different ways.
    So here is the updated code to use the same rotation pattern on four pages with different rectangle sizes.

    First let’s store the size of the canvas (1000) in the variable canvas_s.
    The amount variable and calculation of the cell size stays the same:

    canvas_s = 1000
    amount = 24
    
    cell_s = canvas_s / (amount + 2)
    

    The four different rectangle sizes are stored in a list.

    rect_sizes = [ cell_s / 2, cell_s / sqrt(2), 2 * cell_s / ( 1 + sqrt(2) ), cell_s]
    

    The sizes are calculated to get the various ‘touchings’. Just add other rectangle sizes to the list and see that you get.

    • cell_s / 2: no touching
    • cell_s / sqrt(2): the rotated rectangles tough on the corners
    • 2 * cell_s / (1 + sqrt(2)): a rotated and default rectangle touch on the corner and side
    • cell_s: the not rotated rectangles touch on the side

    To use the same rotation grid we have to generate and store it somehow.
    Let’s use the nested loops to store this information in the grid variable.
    It is a list of lists (rows or columns whatever you prefere).

    Declare the variable and store an empty list:

    grid = []
    

    Loop though the amount

    for y in range(amount):
    

    For every row we assign a new empty list to the newRow variable

        newRow = []
    

    Then loop through the amount and append either True or False to the newRow

        for x in range(amount):
            if random() > .5:
                newRow.append(True)            
            else:
                newRow.append(False)
    

    When we are finished with the row loop let’s append the newRow to the grid

        grid.append( newRow )
    

    The above grid generation could actually be done in one line as well.
    So this:

    grid = []
    for y in range(amount):
        newRow = []
        for x in range(amount):
            if random() > .5:
                newRow.append(True)            
            else:
                newRow.append(False)
        grid.append( newRow )
    

    Will do the same as this:

    grid = [[True if random() > .5 else False for x in range(amount)] for y in range(amount)]
    

    List comprehension and ternary operators are great and will save you some lines but they can be a bit confusing.

    The rest of the script basically stays the same.
    There is one more loop added to generate the pattern for each size:

    for rect_s in rect_sizes:
    

    For each size we need a new canvas so we call newPage() and use the variable canvas_s to make them all the same size:

        newPage(canvas_s, canvas_s)
    

    Translation for the margin

        translate( cell_s, cell_s)
    

    To loop through the stored grid let’s use the enumerate() function.
    This is very helpful if you need the index and the actual element of a list.

    for index, value in enumerate(['a', 'b', 'c']):
        print index, value
    

    will print (in python 2.7)
    0 a
    1 b
    2 c

    We will use the index for the transformation and the element (True or False) for the rotation.

        for r, row in enumerate(grid):
            for c, cell in enumerate(row):
                with savedState():
                    translate( c * cell_s + cell_s/2, r * cell_s + cell_s/2 )
                    if cell: rotate(45)
                    rect(-rect_s/2, -rect_s/2, rect_s, rect_s)
    

    If you want to store seperate images (.jpg or .png) we call the saveImage() inside the size loop.

        saveImage('~/Desktop/Vera_Molnar_rotating_rects_%f.png' % rect_s)
    

    For a multipage pdf or gif the saveImage() should be outside of the loops.

    The full code

    canvas_s = 1000
    amount = 24
    
    cell_s = canvas_s / (amount + 2)
    rect_sizes = [ cell_s / 2, cell_s / sqrt(2), 2 * cell_s / ( 1 + sqrt(2) ), cell_s]
    
    grid = [[True if random() > .5 else False for x in range(amount)] for y in range(amount)]
    
    
    for rect_s in rect_sizes:
    
        newPage(canvas_s, canvas_s)
        translate( cell_s, cell_s)
    
        for r, row in enumerate(grid):
            for c, cell in enumerate(row):
                with savedState():
                    translate( c * cell_s + cell_s/2, r * cell_s + cell_s/2 )
                    if cell: rotate(45)
                    rect(-rect_s/2, -rect_s/2, rect_s, rect_s)
    
        # saveImage('~/Desktop/Vera_Molnar_rotating_rects_f.png' % rect_s)
    

    You should get something like this

    0_1518957475617_Screen Shot 2018-02-18 at 13.23.31.png