Shape Grammar
Coursework for Design Computation II
Implementation of Shape Grammar using Rhino and GH Python. A shape grammar is a set of shape rules applied in discrete steps to generate designs of shapes. The example demostrated is based on the following Shape Grammar:
1. Every square has an anchor and a heading (as a vector). These two properties define the direction of rotation of the square (clockwise or counter-clockwise).
2. A new square is generated by rotating the original square by -90 or 90 degrees, according to its direction of rotation.
3. Generate a new square based on rule 2, but move the anchor to the center of the edge ad scale the size of the new square by 2/3.
4. Eliminate a square from the existing set, randomly choose another square from the set as the next origin.
Type: design computation study
Role: code implementation
Date: 2018
Demonstration of shape growth
GH Python Code:__author__ = "Vincent Mai"
__version__ = "2018.11.10"
"""
Design Computation II
Shape Grammar
"""
import random
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
class Shape(object):
def __init__(self):
self.squares = set()
self.frontier = set()
def initialShape(self, num, size):
"""
generate initial state of the shape object by the specified
square numbers and size, each with a random coner anchor,
a random rotational direction and a random heading.
"""
gridSize = int(num**0.5)+3
centerLocs = [(x*size, y*size) for x in range(gridSize) for y in range(gridSize)]
centers = random.sample(centerLocs, num)
for center in centers:
x, y = random.choice((-1,1)), random.choice((-1, 1))
anchorDir = (x*0.5*size, y*0.5*size)
anchor = (center[0]+anchorDir[0], center[1]+anchorDir[1])
rotation = random.choice(('cw', 'ccw'))
# generate heading based on clockwise or counter clockwise rotation
if anchorDir[0] == anchorDir[1]:
heading = (0, -anchorDir[1]) if rotation == 'cw' else (-anchorDir[0], 0)
else:
heading = (-anchorDir[0], 0) if rotation == 'cw' else (0, -anchorDir[1])
# initializing square objects
newSquare = Square(anchor, heading, rotation, center, size)
self.squares.add(newSquare)
self.frontier.add(newSquare)
def grow(self):
"""
grow frontier squares by random choice of shape grammar or delete
squares from existing squares
"""
tempFrontier = set()
for square in self.frontier:
grammar = random.randint(0,20)
if grammar < 10:
tempFrontier.add(shape.rule1(square))
elif grammar < 15:
tempFrontier.add(shape.rule3())
else:
tempFrontier.add(shape.rule2(square))
self.frontier = tempFrontier
self.squares |= self.frontier
print(len(tempFrontier))
def rule1(self, square):
"""
adding an adjacent square to an edge of the original square
based on the rotation of the original square
"""
hx, hy = square.heading
# rotate heading cw if original square is ccw, and vice versa
if square.rotation == 'cw':
newHeading = (-hy, hx)
else:
newHeading = (hy, -hx)
move = (square.heading[0]+newHeading[0], square.heading[1]+newHeading[1])
newCenter = (square.anchor[0]+move[0], square.anchor[1]+move[1])
newSquare = Square(square.anchor, newHeading, square.rotation, newCenter, square.size)
return newSquare
def rule2(self, square):
"""
adding an adjacent square scaled by 2/3 and anchored to the midpoint
of one of the edges
"""
hx, hy = square.heading
if square.rotation == 'cw':
newHeading = (-hy*2/3, hx*2/3)
else:
newHeading = (hy*2/3, -hx*2/3)
newAnchor = (square.anchor[0]+square.heading[0], square.anchor[1]+square.heading[1])
newSize = square.size*2/3
move = (square.heading[0]*2/3+newHeading[0], square.heading[1]*2/3+newHeading[1])
newCenter = (newAnchor[0]+move[0], newAnchor[1]+move[1])
newSquare = Square(newAnchor, newHeading, square.rotation, newCenter, newSize)
return newSquare
def rule3(self):
"""
eliminate
"""
self.squares.remove(random.sample(self.squares, 1)[0])
return random.sample(self.squares, 1)[0]
class Square(object):
def __init__(self, anchor, heading, rotation, center, size):
self.anchor = anchor
self.heading = heading
self.rotation = rotation
self.center = center
self.size = size
if reset:
centerList = []
anchorList = []
headingList = []
sizeList = []
random.seed(0)
shape = Shape()
shape.initialShape(num, size)
else:
centerList = []
anchorList = []
headingList = []
sizeList = []
for i in range(1):
shape.grow()
for square in shape.squares:
centerList.append(rs.CreatePoint(square.center[0], square.center[1]))
anchorList.append(rs.CreatePoint(square.anchor[0], square.anchor[1]))
headingList.append(rs.CreateVector(square.heading[0], square.heading[1]))
sizeList.append(square.size)
centers = centerList
anchors = anchorList
headings = headingList
sizes = sizeList