Skip to content

Procedural modelling

Describe a model by its spaces and what connects them. The topology engine builds the structure, routes the systems, and finds every place a system crosses a wall or deck. The detail at each crossing is a plain Python function of yours.

The smallest case: two cells that share one wall, and a pipe from a pump in one cell to a tank in the other, so it has to cross that wall. The detail function cuts a circular hole in the wall and reinforces it with a thin-walled sleeve:

import ada
from ada.topo_model import SteelStru, create_pump, create_tank
from ada.topology import CellGrid, DesignRules, TopologyBuilder, run_design

# Two 5 x 5 x 3 m cells sharing one (stiffened) wall at x = 5
cells = [ada.PrimBox("Cell1", (0, 0, 0), (5, 5, 3)), ada.PrimBox("Cell2", (5, 0, 0), (10, 5, 3))]
builder = TopologyBuilder.from_prim_boxes(cells, blueprint=SteelStru(reinforce_internal_walls=True))
structure = builder.build()

# A pump in one cell, a tank in the other, and a pipe between them
pump = create_pump("Pump", origin=(2.5, 2.5, 0.0))
tank = create_tank("Tank", origin=(7.5, 2.5, 0.0))
pipe = ada.PipingSystem("Service", medium="water").connect(pump, "discharge").connect(tank, "inlet")


# The penetration detail: a circular cut through the wall, reinforced with a thin-walled sleeve
def penetration_detail(pen, name, clearance=0.02, wall_thickness=0.008, length=0.3):
    r = pen.system.pipe_radius + clearance
    p1, p2 = pen.point - pen.normal * length / 2, pen.point + pen.normal * length / 2

    for plate in pen.face.associated_part.get_all_physical_objects(by_type=ada.Plate):
        plate.add_boolean(ada.PrimCyl(f"{name}_cut", p1, p2, r))

    sleeve = ada.PrimCyl(f"{name}_sleeve", p1, p2, r + wall_thickness, color="red")
    sleeve.add_boolean(ada.PrimCyl(f"{name}_bore", p1, p2, r))
    return ada.Part(name) / sleeve


# Route the pipe through the cells, find where it crosses a wall, and detail each crossing
grid = CellGrid.from_bounds((0, 0, 0), (10, 5, 3), spacing=0.5)
rules = DesignRules(model_penetration=penetration_detail)
result = run_design([pipe], cell_graph=builder.cell_graph, grid=grid, rules=rules)

piping = ada.Part("Piping") / result.route_geometry[pipe.name]
a = ada.Assembly("Penetration") / [structure, ada.Part("Equipment") / [pump, tank], piping, *result.penetration_parts]

The shared wall, the pump and tank, the pipe and its sleeve: drag to orbit, scroll to zoom, click an object to inspect it. The roof deck is left out so it doesn't hide the crossing.

  • TopologyBuilder.from_prim_boxes turns boxes into cells, and the faces they share into internal walls. The SteelStru blueprint builds them as steel: columns, girders, decks and a stiffened plate for the shared wall.
  • run_design routes the pipe through the cell grid and finds where it crosses a wall. For each crossing it calls model_penetration with a Penetration: the system, the point, the face normal and the face, whose associated_part is the wall that was built for it.
  • penetration_detail is the whole rule. It cuts the wall plates and returns the detail part. Swap in another function for another standard, or use the reference one, ada.topo_model.standard_design_rules(), which also handles cable trays and ducts.

Run it with python examples/penetration_detail.py, which opens the model in the viewer. Topology engine covers blueprints, routing rules, equipment and whole models built from a document.