Tutorial: Writing a CGA Rule in ArcGIS CityEngine

This is the second of two ECCE blog posts covering ArcGIS CityEngine, created for the course GEOG 647: Advanced GIS at the University of Calgary as a final term project.
The interactive ArcGIS StoryMap version of this project can be accessed here:
This tutorial demonstrates with code snippets and operation explanations how to generate a small residential house, garage, and lawn with fences and trees using CGA code in ArcGIS CityEngine.
The rule package for this code is available through ArcGIS Online:

Creating an Initial CGA Rule File
To begin creating a CGA rule, in CityEngine, navigate to your project’s rules folder and right click on it. Select New -> CGA Rule, and name it Residential.cga.
The first stage is to define the starting rule. Here, we will call it LowDensity, and in the line above the rule, we will add @StartRule to tell CityEngine that this is the starting point of the code.
# ---------------------------------------------------------------- #
# Start - Split into building and lawn, generate building skeleton #
# ---------------------------------------------------------------- #
@StartRule // <-- Tells CityEngine that this rule is the starting rule
LowDensity --> // <-- Rule definition syntax
// First steps here
Splitting our Lot Into Components
Our first operation we will be performing will be the split operation. Before this, we will need to begin to define new attributes that allow for the user to provide inputs from CityEngine’s UI for various variables in our code. When we define attributes, we can initialize them and call the rand function to randomly generate a number in between a range of two other numbers. Using this will give our houses varying looks when they generate models on the lots of a subdivided block in CityEngine. Shapes in CityEngine have texture coordinates along a U axis and a V axis, dubbed UV coordinates. In our subdivided lots, the U axis runs perpendicular to the street in the front of the lot, as seen in the image below, with the V axis running parallel to the street. To set up the components of the house, garage, and lawn, we will be splitting our lot first along the V axis. Each component that is specified in splits or other functions will call a new rule to define what happens to the component.
# ---------------------------------------------------------------- #
# Start - Split into building and lawn, generate building skeleton #
# ---------------------------------------------------------------- #
// Attributes can be set in CityEngine by the user!
attr FrontSetback = 10 // <-- The front setback of the house from the street
attr Length = rand(10, 15) // <-- The length of the house
attr GarageLength = rand(5, 10) // <-- The length of the garage
@StartRule // <-- Tells CityEngine that this rule is the starting rule
LowDensity --> // <-- Rule definition syntax
split(v, unitSpace, 0) // <-- Split into lawns and footprints along the v axis
{ FrontSetback : FrontLawn // <-- Front lawn rule
| GarageLength : GarageFootprint // <-- Garage building rule
| Length : Footprint // <-- Main house building rule
| 100 : BackLawn } // <-- Back lawn rule

Next, we will split our footprints for the house and garage into left and right lawn setbacks and the buildings themselves. This will be done along the U axis, with the following code snippet below and diagram to the right.
attr SideSetback = rand(2, 3) // <-- Side setback of the house from property line
attr Width = rand(15, 20) // <-- Width of the house
attr GarageWidth = rand(5, 15) // <-- Width of the garage
// Creates building footprint and turns rest of space into a lawn
Footprint -->
split(u, unitSpace, 0) // <-- Split along U axis
{ SideSetback : LeftLawn
| Width : Building
| 100 : RightLawn }
// Creates garage footprint and lawn
GarageFootprint -->
split(u, unitSpace, 0) // <-- Split along U axis
{ SideSetback : FrontLeftLawn
| GarageWidth : Garage
| 100 : FrontRightLawn }

Creating 3D Components with the Comp Operation
With our lot split into the sections we want to model, we will being to build the house and garage. Before this, we will need to define new attributes that the user can provide inputs for the building height and garage height. Once we have the attributes defined, we can call the extrude function to raise the shapes of the house and garage to their attribute heights.
Next, we will call the comp function to split these new 3D shapes into front, side, and roof rules. This way, we can have a rule for the top of the shape, the front of the shape which faces the roadway, and the other sides of the shape that are not the bottom.
attr Height = rand(5,10) // <-- Height of the house
attr GarageHeight = rand(2,4) // <-- Height of the garage
// Creates Building
Building -->
extrude(Height)
comp(f) { front : Front | side : Side | top: Roof } // <-- Front, side and roof rules
// Creates garage
Garage -->
extrude(GarageHeight)
comp(f) { front : GarageFront | side : Side | top: Roof } // <-- Garage front, generic side, and roof rules

Modelling a Roof and Creating Floors
For the front, side, and top rules, we will further split the front and sides of the house along the Y axis (shapes also have X, Y, and Z axis). While each floor could be defined as a separate rule, we will only define a front floor that respects the width of the garage and a floor to use on the sides. For the top, we will create a roof by calling the roofGable function to create a sloped roof at 30 degrees, and call a rule to handle the trimming and texturing of the roof.
// Creates front of building
Front -->
split(y)
{ (Height/2) : FrontFloor
| Height : FrontFloor }
// Creates building sides
Side -->
split(y)
{ (Height/2) : Floor
| Height : Floor }
// Creates Roof Structure
Roof -->
roofGable(30, 2, 1) RoofTrim // <-- Roof at 30 degrees, with 2 and 1m overhangs
In our RoofTrim rule, we will set the roof to have horizontal trim using the set function, perform another comp function to split these into wall and roof texture rules, and set up a projection and material properties such as how shiny and reflective the texture is. We will also point to an existing roof texture through an attribute by providing a path to the file system.
// Creates Roof Trim
RoofTrim -->
set(trim.horizontal, true)
comp(f)
{ top : RoofTexture // <-- Texture the roof
| side : WallTexture } // <-- Texture the wall
// Handles Roof Texture
RoofTexture -->
setupProjection(0, scope.xy, scope.sx, scope.sy) // <-- Set up texture on these "scopes"
texture(RoofTex) // <-- Add texture
projectUV(0) // <-- Project UV
set(material.specular.r, 0.1)
set(material.specular.g, 0.1)
set(material.specular.b, 0.1)
set(material.shininess, 25) // <-- Shiny roof
// Aligns wall textures
WallTexture -->
setMaterial(WallTex)
projectUV(0) projectUV(2) // <-- Project UV
For our roof and wall textures, we want to have a variety of textures from Esri’s library to have our houses choose from. When defining constant variables, probabilities or cases can be assigned like below:
const WallTex =
25%: readMaterial("/ESRI.lib/assets/Materials/Generic/Wood/Wood_Weathered_10x10.cgamat")
15%: readMaterial("/ESRI.lib/assets/Materials/Architectural/Cladding/Wood/WoodStackedCladding_Weathered_10x10.cgamat")
15%: readMaterial("/ESRI.lib/assets/Materials/Architectural/Cladding/Terracotta/TerracottaPanels_0.5x0.5_cPigeonBlue.cgamat")
15%: readMaterial("/ESRI.lib/assets/Materials/Architectural/Cladding/Terracotta/TerracottaPanels_0.5x1.5_cBlackBlue.cgamat")
15%: readMaterial("/ESRI.lib/assets/Materials/Architectural/Cladding/Terracotta/TerracottaPanels_0.125x1.5_cCopperBrown.cgamat")
else: readMaterial("/ESRI.lib/assets/Materials/Architectural/Cladding/Terracotta/TerracottaPanels_0.25x1.5_cOliveBrown.cgamat")
const RoofTex =
15%: "/ESRI.lib/assets/Roofs/Sloped/tileRoof_5.jpg"
15%: "/ESRI.lib/assets/Roofs/Sloped/tileRoof_6.jpg"
25%: "/ESRI.lib/assets/Roofs/Sloped/tileRoof_7.jpg"
25%: "/ESRI.lib/assets/Roofs/Sloped/tileRoof_8.jpg"
else: "/ESRI.lib/assets/Roofs/Sloped/tileRoof_9.jpg"

Tiling Floors
For the floor rules, we will further split the floors the house into solid walls and wall tiles that will support windows. We will define different widths for the floors to be split on depending on front or side floors due to the need to accommodate the garage width and create a garage door opening rule.
// Splits each floor into tiles
Floor -->
split(x)
{ 0.5 : SolidWall
| { ~5 : WallTile }*
| 0.5 : SolidWall }
// Front floor - handles garage case
FrontFloor -->
split(x)
{ GarageWidth : SolidWall // <-- Garage Case
| { ~5 : WallTile }*
| 0.5 : SolidWall }
// Create garage front
GarageFront -->
split(x) { 1 : SolidWall
| GarageWidth-2 : split(y) { 3 : GarageDoor | ~2 : SolidWall }
| GarageWidth-1 : SolidWall }
Our wall tiles will then be split into solid walls and windows. Solid walls will call the primitiveCube rule to draw a simple cube shape after defining the wall thickness with the s (scope) command and calling the Wall Texture rule. Windows will split further and create a Window Frame and Glass.
// Splits tiles into walls and windows
WallTile -->
split(x) { 1 : SolidWall
| ~1.5 : split(y) { 0.4 : SolidWall | ~1.5 : Window | 0.4: SolidWall }
| 1 : SolidWall }
// Creates solid wall
SolidWall -->
s('1, '1, -0.4)
primitiveCube()
comp(f) { side : WallTexture | all : setupProjection(0, scope.xy, 1.5, 1.5, 0, '1) WallTexture
// Creates window frame
Window -->
t(0, 0, -0.2)
split(y) { 0.1 : WindowFrame
| ~1 : split(x) { 0.1 : WindowFrame | { ~1 : Glass | 0.1 : WindowFrame }* }
| 0.1 : WindowFrame }

Adding Windows and a Garage Door
Finally, we will add the last window frame, glass, and garage door details. The Garage Door will be created by splitting into different window frames. The Window Frame will be created by calling the color operation and defining a grey color in hex code. The Glass will be created by calling the color operation and setting specular, reflectiveness, and opacity to mimic a window.
// Create garage door
GarageDoor -->
t(0, 0, -0.4)
split(y) { ~1 : split(x) { 0.15 : WindowFrame
| ~1 : WindowFrame
| 0.15 : WindowFrame }
| 0.15 : WindowFrame }
// Creates window frame
WindowFrame -->
color("#444444")
// Creates window glass
Glass -->
color("#85acd6")
set(material.specular.r, 0.5)
set(material.specular.g, 0.5)
set(material.specular.b, 0.5)
set(material.reflectivity, 0.5)
set(material.shininess, 50)
set(material.opacity, 0.9)

Detailing the lawn
The last step in the CGA code is to deal with the front and back lawns. First, the front lawn is split into a driveway and grassy areas. All other lawn tiles call the setback operation to create an exterior fence on the side and back lawns. Front right and back lawns feature trees.
# -------------------- #
# Lawn Detail Creation #
# -------------------- #
// Split front lawn and driveway
FrontLawn -->
split(u, unitSpace, 0)
{ SideSetback : FrontLeftLawn
| GarageWidth : Driveway
| 100 : FrontRightLawn }
// Create driveway
Driveway -->
extrude(0.1)
color("#909090")
// Front, Left, Top, Bottom, and Corner lawn creation
FrontLeftLawn -->
setback(0.1) { left : Fence }
Grass // <-- Only grass
FrontRightLawn -->
setback(0.1) { right : Fence }
GrassWithTree // <-- Generate some trees
LeftLawn -->
setback(0.1) { left : Fence }
Grass
RightLawn -->
setback(0.1) { right : Fence }
Grass
BackLawn -->
setback(0.1) { right : Fence }
setback(0.1) { left : Fence }
setback(0.1) { back : Fence }
GrassWithManyTrees // <-- Generate many trees
The last components needed are the Fence, Grass, and Grass with Tree Rules. The Fence can be constructed by extruding, texturing with a defined constant fence texture, and projecting. The Grass adds the defined constant grass texture and tiles the texture. The Grass with Tree calls the previous grass rule but also calls the scatter function to randomly scatter trees on these segments of the lawn, using a Generate Tree rule to import Esri’s Plant Loader rule and set Tree properties to be low heights, lower levels of detail, and call a defined TreeType constant with 4 randomly selected trees.
const GrassTex = readMaterial("/ESRI.lib/assets/Materials/Vegetation/Grass/Grass_Healthy_10x10_Green.cgamat")
const FenceTex = "/ESRI.lib/assets/Fences/Textures/wood_03.jpg"
const TreeType =
25%: "White Ash"
25%: "Douglas Fir"
25%: "Lodgepole Pine"
else: "Sassafras"
// Some trees
GrassWithTree -->
scatter(surface, rand(0,2), uniform) { GenerateTree }
setMaterial(GrassTex)
Grass
// Many trees
GrassWithManyTrees -->
scatter(surface, rand(4,10), uniform) { GenerateTree }
setMaterial(GrassTex)
Grass
// Setback lawn with only textures
Grass -->
setMaterial(GrassTex)
tileUV(0, 5, 5)
// Imports Esri Tree Generator rule and create a tree at random
import Tree : "/ESRI.lib/rules/Plants/Plant_Loader.cga"
GenerateTree -->
s(0,0,0)
set(Tree.Name, TreeType)
set(Tree.Height, rand(5,15))
set(Tree.Radius, rand(1,3))
set(Tree.Representation, "Fan") // <-- IMPORTANT: Sets the Level of Detail (LOD) to be lower, drastically improves performance
Tree.Generate
// Generates a fence
Fence -->
extrude(3)
texture(FenceTex)
projectUV(0) projectUV(2)

BE SURE TO CONSTANTLY SAVE AND REAPPLY THE RULE WHEN ANY CHANGES ARE MADE TO THE CGA CODE!
With the rule now finished, it can be packaged in CityEngine as a Rule Package by right clicking on the CGA rule in the Navigator window and clicking Share As and choosing Rule Package. From here, you can save it to a local location on your computer or upload it to your ArcGIS Online account.
References
Esri. (2025a). Introduction to CityEngine—ArcGIS CityEngine Resources | Documentation. https://doc.arcgis.com/en/cityengine/latest/get-started/get-started-about-cityengine.htm
Esri. (2025b). CityEngine Help—ArcGIS CityEngine Resources | Documentation. https://doc.arcgis.com/en/cityengine/latest/help/cityengine-help-intro.htm
Esri. (2025c). CGA Shape Grammar Reference—ArcGIS CityEngine Resources | Documentation. https://doc.arcgis.com/en/cityengine/latest/cga/cityengine-cga-introduction.htm
Müller P, Wonka P, Haegler S, Ulmer A, Van Gool L. (2006). Procedural modeling of buildings. ACM Trans Graph 25(3):614–623
Müller P. (2010). Procedural modeling of buildings. Thesis, University of Zürich. https://doi.org/10.3929/ethz-a-006397747
Parish YI, Müller P. (2001). Procedural modeling of cities. In: Proceedings of the 28th annual conference on computer graphics and interactive techniques. ACM, pp 301–308
Shi, W., Goodchild, M. F., Batty, M., Kwan, M.-P., & Zhang, A. (Eds.). (2021). Urban Informatics. Springer Singapore. https://doi.org/10.1007/978-981-15-8983-6
Wasserman, D. (2015) Complete Street Rule. GitHub repository. https://github.com/d-wasserman/Complete_Street_Rule .
