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:

The transformed residential lot we will be constructing.

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
The lot is first split along the V axis to create four components using specified attributes for lengths and widths.

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 }
The lot is next split along the U axis to create four components using specified attributes for lengths and widths.

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
Two extruded footprints for the house and garage.

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"
An added roof and 2 floors on the main house.

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 }
A garage door and a mix of solid wall and window tiles are created from tiling.

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)
Our house now has randomly assigned wall and roof textures, along with a textured garage door and glass windows!

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)
With a driveway, wooden fence, and random trees added, along with a grass texture, our CGA rule is complete!

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 .