CityEngine Experiments, Part 3

Python Scripting in CityEngine

I've been meaning to learn Python, so I looked into how CityEngine uses it to script various workflows and how it can interact with the CGA procedural modeling.

Using CGA alone, I don't think that the neighboring building attributes can be accessed, meaning that each building is operating in a sort of vacuum. Using Python, multiple lots can be analyzed. We can read and write the CGA attributes, which means that we can do global analysis based on existing information, and then alter CGA attributes to reflect contextual constraints.

The example shown here adjusts the "buildingHeight" attribute of the buildings based on proximity to two special buildings (attractors), which appear white in the images above. The Python script reads the "lotType" attribute to determine which buildings are attractors, and which are influenced by them. The heights are the result of an additive waveform. Imagine each attractor is emitting a sine wave - this is just like dropping two stones into a pond and watching their ripples meet. The waveforms from each attractor are added to determine the heights of the buildings around them. I've colored the buildings on a gradient (red=tall, black=short), so that you can see where the waves meet and either amplify or negate each other.

 

Python Script for Waveform Example

'''
Created on Sep 26, 2012

@author: Chris Wilkins
'''

from scripting import *

# Import math for square root and trig functions.
import math

# Get a CityEngine instance.
ce = CE()

# Assuming user has selected the shapes in question.
shapes = ce.getObjectsFrom(ce.selection, ce.isShape)

# Waveform controls:
peakHeight = 100
wavelength = 200

# For every shape we will check whether it is an 
# attractor or a building. For buildings we will add the effects
# of each attractor, which determines the building height.

# Divide the selected shapes into two lists.
buildings = list()
attractors = list()
for shape in shapes:
    lotType = ce.getAttribute(shape, "/ce/rule/lotType")
    if lotType == 1:
        buildings.append(shape)
    else:
        attractors.append(shape)

# Use a dictionary to store key-value pairs for building heights.
buildingHeights = dict()

# Loop through each building, finding influence of each attractor.
for building in buildings:
    buildingHeights[building] = 0

    # Find distance to each attractor, ingoring Y.
    for attractor in attractors:
        attractorCentroid = ce.getPosition(attractor)
        aX = attractorCentroid[0]
        aZ = attractorCentroid[2]
        buildingCentroid = ce.getPosition(building)
        bX = buildingCentroid[0]
        bZ = buildingCentroid[2]

		# Use Pythagorean distance formula.
        dist = math.sqrt((aX - bX)**2 + (aZ - bZ)**2) 
        buildingHeight = (peakHeight / 2) + ((peakHeight / 2) * math.cos(dist * math.pi / wavelength))
        
        # Add to cumulative height.
        buildingHeights[building] += buildingHeight

    # Set the buildingHeight attribute.
    ce.setAttribute(building, "/ce/rule/buildingHeight", buildingHeights[building])
    ce.setAttributeSource(building,"/ce/rule/buildingHeight", "USER")    

	# Generate the buildings as the script runs.
    ce.generateModels(building)


 

CGA Rules for Waveform Example

/**
 * File:    neighbors.cga
 * Created: 26 Sep 2012 17:35:48 GMT
 * Author:  Chris Wilkins
 */

version "2011.2"

# lotType 1 is building, lotType 2 is attractor.
attr lotType = 1

# buildingHeight will be overridden by Python script.
attr buildingHeight = 1

Lot --> 
	s('0.9,'1,'0.9) center(xz) alignScopeToAxes(y) s('1,0,'1)
	Mass
	
Mass -->
	extrude(buildingHeight) AddColor
	
AddColor -->
	case lotType == 2: color("#ffffff") 
	else: color(buildingHeight / 200, 0, 0)

Continue to Part 4: Recursion / Fractals