Mesh Gen with Shape Grammars

Project Description:

This project uses Shape Grammars to generate meshes. Shape Grammars similar to L-systems where you have variables and a set of rules. The rules usually say that a variable will change into other variables during one iteration of the generation process. In Shape Grammars the variables or nodes correspond to 3D geometric shapes.

My engine has the following characteristics:

  • Flexible. Since the mesh is generated based on rules, changing the rule will affect all the corresponding nodes. Variables can be passed to nodes that allows rules to be customized.
  • Structured. The system creates a tree of nodes, this gives the designer a hierarchical view of the nodes.
  • Powerful. As opposed to simply having parameters the data drive meshes, complex functions can be implemented in Shape Grammars. Recursion is trivial.
  • Fast. Since I embedding a scripting language into C++, this meant that iteration was really fast when making rules, as no recompilation of the program was needed. The meshes, transforms, rendering and all the other heavy lifting was all done on the C++ side, which meant that the program was also fast to run.

The Shape Grammar Rules are written in Python. I embedded Python into my personal engine and exposed C++ functions to Python using pybind11.

Development Info:

Engine: Personal Custom Engine

Languages: C++, Python

Third Party tools: pybind11

Platform: PC

Individual Project

Github repo

Responsibilities:

  • Embedding Python
  • Binding Python functions
  • Design and implementation of Shape Grammar system
  • Implementation of node tree view visualization output
  • Implementation of example Shape Grammar rule sets

Example rules:

International Space Station

ISS created using Shape Grammars

Reference Image, Source: https://en.wikipedia.org/wiki/File:ISS_March_2009.jpg

This is a rough recreation of the International Space Station. The large solar panels can be turned through changing the sun_angle parameter. The contractible solar panels can be folded/extended by changing the contractible_angle variable.

iss_rules.py
from zzz.shape_func import *

max_layers = 20
max_nodes = 1000


sun_angle = 20
contractible_angle = 130

zeros = [0, 0, 0]
ones = [1, 1, 1]


def Root():
    Cube([1, 1, 1])
    AddNode(MainMod)


def MainMod():
    Cube([15, 1, 1])
    AddNode(WingBase, dir=1)
    AddNode(WingBase, dir=-1)

    for xpos in [-7, -5, -3, 3, 5, 7]:
        AddNode(SolarContractibleR, 8, [260, 0, 0], [xpos, 0, 0])

    AddNode(MechArm, [0, 20, -40], [0, 0, 0], 4, 0.15)
    AddNode(MechArm, [0, 180, -20], [-3, 0, 0], 3, 0.15)


def MechArm(r, t, length, width):
    Cube([width, length, width], [0, -1, 0])
    Translate(*t)
    Rotate(*r)
    AddNode(MechArmSeg, [0, 20, -80], [0, length, 0], 3, 0.1)


def MechArmSeg(r, t, length, width):
    Cube([width, length, width], [0, -1, 0])
    Translate(*t)
    Rotate(*r)


def WingBase(dir):
    Cube([7, 0.7, 0.7], [-dir, 0, 0])
    Translate(7.5 * dir, 0, 0)

    AddNode(SolarRod, [0, sun_angle, 0], [dir * 1, 0.5, 0])
    AddNode(SolarRod, [0, sun_angle, 0], [dir * 5, 0.5, 0])
    AddNode(SolarRod, [0, sun_angle, 180], [dir * 1, -0.5, 0])
    AddNode(SolarRod, [0, sun_angle, 180], [dir * 5, -0.5, 0])

    AddNode(SolarContractibleR, 8, [90, 0, 0], [dir * 0, 0, 0])
    AddNode(SolarContractibleR, 8, [90, 0, 0], [dir * 6, 0, 0])


def SolarRod(r, t, s=[1, 1, 1]):
    Cube([0.2, 11, 0.2], [0, -1, 0])
    Scale(*s)
    Translate(*t)
    Rotate(*r)
    AddNode(SolarPane, 1.3)
    AddNode(SolarPane, -1.3)


def SolarPane(dir):
    Cube([2.5, 10, 0.1], [dir, 0, 0])
    Translate(0, 5.95, 0)
    pass


def SolarContractibleR(iter, r=[0, 0, 0], t=[0, 0, 0], s=[1, 1, 1]):
    Cube([1.3, 1, 0.1], [0, -1, 0])
    Scale(*s)
    Translate(*t)
    Rotate(*r)
    iter -= 1
    if iter <= 0:
        return
    if iter % 2 == 0:
        dir = 1
    else:
        dir = -1
    child_rot = [contractible_angle * dir, 0, 0]
    child_t = [0, 1, 0]
    AddNode(SolarContractibleR, iter, child_rot, child_t)

The program generates a txt file that shows a tree view of the Shape nodes. It clearly conveys the relationship between nodes. This is very useful for debugging and understanding of the shape rules.

iss_tree.txt
Root
└── MainMod
    ├── WingBase
    │   ├── SolarRod
    │   │   ├── SolarPane
    │   │   └── SolarPane
    │   ├── SolarRod
    │   │   ├── SolarPane
    │   │   └── SolarPane
    │   ├── SolarRod
    │   │   ├── SolarPane
    │   │   └── SolarPane
    │   ├── SolarRod
    │   │   ├── SolarPane
    │   │   └── SolarPane
    │   ├── SolarContractibleR
    │   │   └── SolarContractibleR
    │   │       └── SolarContractibleR
    │   │           └── SolarContractibleR
    │   │               └── SolarContractibleR
    │   │                   └── SolarContractibleR
    │   │                       └── SolarContractibleR
    │   │                           └── SolarContractibleR
    │   └── SolarContractibleR
    │       └── SolarContractibleR
    │           └── SolarContractibleR
    │               └── SolarContractibleR
    │                   └── SolarContractibleR
    │                       └── SolarContractibleR
    │                           └── SolarContractibleR
    │                               └── SolarContractibleR
    ├── WingBase
    │   ├── SolarRod
    │   │   ├── SolarPane
    │   │   └── SolarPane
    │   ├── SolarRod
    │   │   ├── SolarPane
    │   │   └── SolarPane
    │   ├── SolarRod
    │   │   ├── SolarPane
    │   │   └── SolarPane
    │   ├── SolarRod
    │   │   ├── SolarPane
    │   │   └── SolarPane
    │   ├── SolarContractibleR
    │   │   └── SolarContractibleR
    │   │       └── SolarContractibleR
    │   │           └── SolarContractibleR
    │   │               └── SolarContractibleR
    │   │                   └── SolarContractibleR
    │   │                       └── SolarContractibleR
    │   │                           └── SolarContractibleR
    │   └── SolarContractibleR
    │       └── SolarContractibleR
    │           └── SolarContractibleR
    │               └── SolarContractibleR
    │                   └── SolarContractibleR
    │                       └── SolarContractibleR
    │                           └── SolarContractibleR
    │                               └── SolarContractibleR
    ├── SolarContractibleR
    │   └── SolarContractibleR
    │       └── SolarContractibleR
    │           └── SolarContractibleR
    │               └── SolarContractibleR
    │                   └── SolarContractibleR
    │                       └── SolarContractibleR
    │                           └── SolarContractibleR
    ├── SolarContractibleR
    │   └── SolarContractibleR
    │       └── SolarContractibleR
    │           └── SolarContractibleR
    │               └── SolarContractibleR
    │                   └── SolarContractibleR
    │                       └── SolarContractibleR
    │                           └── SolarContractibleR
    ├── SolarContractibleR
    │   └── SolarContractibleR
    │       └── SolarContractibleR
    │           └── SolarContractibleR
    │               └── SolarContractibleR
    │                   └── SolarContractibleR
    │                       └── SolarContractibleR
    │                           └── SolarContractibleR
    ├── SolarContractibleR
    │   └── SolarContractibleR
    │       └── SolarContractibleR
    │           └── SolarContractibleR
    │               └── SolarContractibleR
    │                   └── SolarContractibleR
    │                       └── SolarContractibleR
    │                           └── SolarContractibleR
    ├── SolarContractibleR
    │   └── SolarContractibleR
    │       └── SolarContractibleR
    │           └── SolarContractibleR
    │               └── SolarContractibleR
    │                   └── SolarContractibleR
    │                       └── SolarContractibleR
    │                           └── SolarContractibleR
    ├── SolarContractibleR
    │   └── SolarContractibleR
    │       └── SolarContractibleR
    │           └── SolarContractibleR
    │               └── SolarContractibleR
    │                   └── SolarContractibleR
    │                       └── SolarContractibleR
    │                           └── SolarContractibleR
    ├── MechArm
    │   └── MechArmSeg
    └── MechArm
        └── MechArmSeg

Recursion

It is really easy to implement recursion in this system. You simply call yourself in a function.

recursion_rules.py
def BranchR(size, iter, dir, offest=[0, 0, 0]):
    Cube([0.2*size, 3*size, 1*size], [0, -1, 0])
    Translate(0, 3*size, 0)
    Translate(*offest)
    Rotate(4, 0, 30 * dir)
    iter -= 1
    if iter <= 0:
        return
    AddNode(BranchR, size * 0.9, iter, 1)
    AddNode(BranchR, size * 0.9, iter, -0.2)
recursion_tree.txt
Root
└── BranchR
    ├── BranchR
    │   ├── BranchR
    │   │   ├── BranchR
    │   │   │   ├── BranchR
    │   │   │   │   ├── BranchR
    │   │   │   │   │   ├── BranchR
    │   │   │   │   │   │   ├── BranchR
    │   │   │   │   │   │   └── BranchR
    │   │   │   │   │   └── BranchR
    │   │   │   │   │       ├── BranchR
    │   │   │   │   │       └── BranchR
    │   │   │   │   └── BranchR
    │   │   │   │       ├── BranchR
    │   │   │   │       │   ├── BranchR
    │   │   │   │       │   └── BranchR
    │   │   │   │       └── BranchR
    │   │   │   │           ├── BranchR
    │   │   │   │           └── BranchR
    │   │   │   └── BranchR
    │   │   │       ├── BranchR
    │   │   │       │   ├── BranchR
    │   │   │       │   │   ├── BranchR
    │   │   │       │   │   └── BranchR
    │   │   │       │   └── BranchR
    │   │   │       │       ├── BranchR
    │   │   │       │       └── BranchR
    │   │   │       └── BranchR
    │   │   │           ├── BranchR
    │   │   │           │   ├── BranchR
    │   │   │           │   └── BranchR
    │   │   │           └── BranchR
    │   │   │               ├── BranchR
    │   │   │               └── BranchR
    │   │   └── BranchR
    │   │       ├── BranchR
    │   │       │   ├── BranchR
    │   │       │   │   ├── BranchR
    │   │       │   │   │   ├── BranchR
    │   │       │   │   │   └── BranchR
    │   │       │   │   └── BranchR
    │   │       │   │       ├── BranchR
    │   │       │   │       └── BranchR
    │   │       │   └── BranchR
    │   │       │       ├── BranchR
    │   │       │       │   ├── BranchR
    │   │       │       │   └── BranchR
    │   │       │       └── BranchR
    │   │       │           ├── BranchR
    │   │       │           └── BranchR
    │   │       └── BranchR
    │   │           ├── BranchR
    │   │           │   ├── BranchR
    │   │           │   │   ├── BranchR
    │   │           │   │   └── BranchR
    │   │           │   └── BranchR
    │   │           │       ├── BranchR
    │   │           │       └── BranchR
    │   │           └── BranchR
    │   │               ├── BranchR
    │   │               │   ├── BranchR
    │   │               │   └── BranchR
    │   │               └── BranchR
    │   │                   ├── BranchR
    │   │                   └── BranchR
    │   └── BranchR
    │       ├── BranchR
    │       │   ├── BranchR
    │       │   │   ├── BranchR
    │       │   │   │   ├── BranchR
    │       │   │   │   │   ├── BranchR
    │       │   │   │   │   └── BranchR
    │       │   │   │   └── BranchR
    │       │   │   │       ├── BranchR
    │       │   │   │       └── BranchR
    │       │   │   └── BranchR
    │       │   │       ├── BranchR
    │       │   │       │   ├── BranchR
    │       │   │       │   └── BranchR
    │       │   │       └── BranchR
    │       │   │           ├── BranchR
    │       │   │           └── BranchR
    │       │   └── BranchR
    │       │       ├── BranchR
    │       │       │   ├── BranchR
    │       │       │   │   ├── BranchR
    │       │       │   │   └── BranchR
    │       │       │   └── BranchR
    │       │       │       ├── BranchR
    │       │       │       └── BranchR
    │       │       └── BranchR
    │       │           ├── BranchR
    │       │           │   ├── BranchR
    │       │           │   └── BranchR
    │       │           └── BranchR
    │       │               ├── BranchR
    │       │               └── BranchR
    │       └── BranchR
    │           ├── BranchR
    │           │   ├── BranchR
    │           │   │   ├── BranchR
    │           │   │   │   ├── BranchR
    │           │   │   │   └── BranchR
    │           │   │   └── BranchR
    │           │   │       ├── BranchR
    │           │   │       └── BranchR
    │           │   └── BranchR
    │           │       ├── BranchR
    │           │       │   ├── BranchR
    │           │       │   └── BranchR
    │           │       └── BranchR
    │           │           ├── BranchR
    │           │           └── BranchR
    │           └── BranchR
    │               ├── BranchR
    │               │   ├── BranchR
    │               │   │   ├── BranchR
    │               │   │   └── BranchR
    │               │   └── BranchR
    │               │       ├── BranchR
    │               │       └── BranchR
    │               └── BranchR
    │                   ├── BranchR
    │                   │   ├── BranchR
    │                   │   └── BranchR
    │                   └── BranchR
    │                       ├── BranchR
    │                       └── BranchR
    └── BranchR
        ├── BranchR
        │   ├── BranchR
        │   │   ├── BranchR
        │   │   │   ├── BranchR
        │   │   │   │   ├── BranchR
        │   │   │   │   │   ├── BranchR
        │   │   │   │   │   └── BranchR
        │   │   │   │   └── BranchR
        │   │   │   │       ├── BranchR
        │   │   │   │       └── BranchR
        │   │   │   └── BranchR
        │   │   │       ├── BranchR
        │   │   │       │   ├── BranchR
        │   │   │       │   └── BranchR
        │   │   │       └── BranchR
        │   │   │           ├── BranchR
        │   │   │           └── BranchR
        │   │   └── BranchR
        │   │       ├── BranchR
        │   │       │   ├── BranchR
        │   │       │   │   ├── BranchR
        │   │       │   │   └── BranchR
        │   │       │   └── BranchR
        │   │       │       ├── BranchR
        │   │       │       └── BranchR
        │   │       └── BranchR
        │   │           ├── BranchR
        │   │           │   ├── BranchR
        │   │           │   └── BranchR
        │   │           └── BranchR
        │   │               ├── BranchR
        │   │               └── BranchR
        │   └── BranchR
        │       ├── BranchR
        │       │   ├── BranchR
        │       │   │   ├── BranchR
        │       │   │   │   ├── BranchR
        │       │   │   │   └── BranchR
        │       │   │   └── BranchR
        │       │   │       ├── BranchR
        │       │   │       └── BranchR
        │       │   └── BranchR
        │       │       ├── BranchR
        │       │       │   ├── BranchR
        │       │       │   └── BranchR
        │       │       └── BranchR
        │       │           ├── BranchR
        │       │           └── BranchR
        │       └── BranchR
        │           ├── BranchR
        │           │   ├── BranchR
        │           │   │   ├── BranchR
        │           │   │   └── BranchR
        │           │   └── BranchR
        │           │       ├── BranchR
        │           │       └── BranchR
        │           └── BranchR
        │               ├── BranchR
        │               │   ├── BranchR
        │               │   └── BranchR
        │               └── BranchR
        │                   ├── BranchR
        │                   └── BranchR
        └── BranchR
            ├── BranchR
            │   ├── BranchR
            │   │   ├── BranchR
            │   │   │   ├── BranchR
            │   │   │   │   ├── BranchR
            │   │   │   │   └── BranchR
            │   │   │   └── BranchR
            │   │   │       ├── BranchR
            │   │   │       └── BranchR
            │   │   └── BranchR
            │   │       ├── BranchR
            │   │       │   ├── BranchR
            │   │       │   └── BranchR
            │   │       └── BranchR
            │   │           ├── BranchR
            │   │           └── BranchR
            │   └── BranchR
            │       ├── BranchR
            │       │   ├── BranchR
            │       │   │   ├── BranchR
            │       │   │   └── BranchR
            │       │   └── BranchR
            │       │       ├── BranchR
            │       │       └── BranchR
            │       └── BranchR
            │           ├── BranchR
            │           │   ├── BranchR
            │           │   └── BranchR
            │           └── BranchR
            │               ├── BranchR
            │               └── BranchR
            └── BranchR
                ├── BranchR
                │   ├── BranchR
                │   │   ├── BranchR
                │   │   │   ├── BranchR
                │   │   │   └── BranchR
                │   │   └── BranchR
                │   │       ├── BranchR
                │   │       └── BranchR
                │   └── BranchR
                │       ├── BranchR
                │       │   ├── BranchR
                │       │   └── BranchR
                │       └── BranchR
                │           ├── BranchR
                │           └── BranchR
                └── BranchR
                    ├── BranchR
                    │   ├── BranchR
                    │   │   ├── BranchR
                    │   │   └── BranchR
                    │   └── BranchR
                    │       ├── BranchR
                    │       └── BranchR
                    └── BranchR
                        ├── BranchR
                        │   ├── BranchR
                        │   └── BranchR
                        └── BranchR
                            ├── BranchR
                            └── BranchR

Error Handling

The Shape Grammar Script is reloaded every time the user saves to disk, this feature allows for short iteration times. If the user does make a mistake that causes Python to raise an exception, the exception is handled properly preventing the program from crashing. The error message and trace back is output to the engine console to help the user identify the error. After the user fixed the error, the program resumes normal operation and the Mesh is generated again.

Postmortem

What Went Well

  • Implementing tools and scaffolding to help with debugging early on paid dividends throughout the project.
  • Embedding Python opened up other possibilities beyond this project. Now all my future projects can be script driven.

What Went Wrong

  • Searching for the perfect third party library for binding Python functions turned out longer than I expected. I tried out several before finally settling on pybind11.
  • Did not finish some of the stretch goal for the project.

What I Learned

  • Learned the different third libraries for binding Python and when to use which. Although I decided not to use Cython in this project, I now know that in the future that if I have certain functions in Python already implemented, and want to make faster, that I can use Cython.
  • I learned to choose my battles. and focus on what is important.There is always more to do, and never enough time to do all of it.