Skip to content

Straight Skeleton Extrusion (Roofs)ยค

Straight Skeleton Extrusion

This example demonstrates how to extrude a 2D polygon into a closed 3D roof mesh using the straight skeleton, powered by CGAL's extrude_skeleton (see also this overview of the improved straight skeleton). This is particularly useful for quickly generating roof geometry from building footprints.

The example builds a small "street" of three roofs, covering a few corner cases:

  • a simple footprint without holes (an L-shaped hip roof),
  • a footprint with a single hole (a courtyard),
  • a footprint with multiple holes (two courtyards).

Key Features:

  • Turning a building footprint (polygon, optionally with holes) into roof geometry
  • Controlling the roof pitch with a taper angle (in degrees) or straight skeleton weights
  • Optionally capping the roof with a maximum_height to create a truncated roof

extrude returns everything needed to visualize the roof: a triangulated, ready-to-display mesh (so even non-convex roof faces render correctly) and the roof outline as a list of lines (the eaves, hips and ridges). The example simply adds the mesh with hidden edges and draws the outline on top:

mesh, lines = extrude(footprint, holes=holes, angles=45.0)
viewer.scene.add(mesh, show_lines=False)
for line in lines:
    viewer.scene.add(line)

The propagation speed of each contour edge determines the slope of the corresponding roof face. A uniform taper angle of 45 degrees (equivalent to a weight of 1) produces a standard hip roof. Smaller angles give flatter roofs, larger angles steeper ones. Providing a maximum_height (required for vertical or outward slopes) truncates the roof at that height.

from compas.geometry import Polygon
from compas.geometry import Translation
from compas_viewer import Viewer
from compas_viewer.config import Config

from compas_cgal.straight_skeleton_2 import extrude

# ==============================================================================
# Three building footprints, covering a few corner cases
# ==============================================================================

# House 1: a simple footprint without holes (an L-shaped hip roof).
footprint_1 = Polygon([(0, 0, 0), (8, 0, 0), (8, 3, 0), (3.5, 3, 0), (3.5, 7, 0), (0, 7, 0)])
holes_1 = []

# House 2: a footprint with a single hole (a courtyard).
footprint_2 = Polygon([(0, 0, 0), (9, 0, 0), (9, 7, 0), (0, 7, 0)])
holes_2 = [[(3, 2.5, 0), (6, 2.5, 0), (6, 4.5, 0), (3, 4.5, 0)]]

# House 3: a footprint with multiple holes (two courtyards).
footprint_3 = Polygon([(0, 0, 0), (11, 0, 0), (11, 7, 0), (0, 7, 0)])
holes_3 = [
    [(2, 2.5, 0), (4, 2.5, 0), (4, 4.5, 0), (2, 4.5, 0)],
    [(7, 2.5, 0), (9, 2.5, 0), (9, 4.5, 0), (7, 4.5, 0)],
]

# ==============================================================================
# Extrude each footprint into a roof. `extrude` returns a ready-to-display mesh
# and the roof outline (eaves, hips and ridges).
# ==============================================================================

houses = [
    (footprint_1, holes_1, -17),
    (footprint_2, holes_2, -6),
    (footprint_3, holes_3, 6),
]

# ==============================================================================
# Visualize
# ==============================================================================

config = Config()
config.renderer.show_grid = False
config.camera.target = [0.0, 1.0, 0.5]
config.camera.position = [-3.0, -22.0, 24.0]

viewer = Viewer(config=config)

for footprint, holes, offset in houses:
    mesh, lines = extrude(footprint, holes=holes, angles=45.0)

    move = Translation.from_vector([offset, -3.5, 0])
    mesh.transform(move)

    viewer.scene.add(mesh, show_points=False, show_lines=False, facecolor=(0.85, 0.45, 0.35))
    for line in lines:
        line.transform(move)
        viewer.scene.add(line, linecolor=(0.25, 0.1, 0.05), linewidth=2)

viewer.show()