Tutorial 12: Scripted report export

To access the tutorials in CityEngine, click Help > Download Tutorials and Examples.... After choosing a tutorial or example, the project is automatically downloaded and added to your workspace.

This tutorial shows how to use CGA report variables in combination with the Python-based exporter. We will report information of instances distributed in our scene, and use it to generate an instance map in a simple text file.

More information on the CityEngine-specific Python command set can be found in the CityEngine help: Help > Help Contents > Manual > Python Scripting.

Rendered view of atoll
Note:

The Python Scripting Interface is not available in all CityEngine versions.

Part 1: Report Information of Instanced Buildings

In the first part, we will use an existing CGA file that distributes instanced buildings in our scene and add report variables to prepare additional information of the instances used for the Script Based Exporter in part 2.

This tutorial requires a basic knowledge of CGA Shape Grammar as well as the basics of the Scripting API.

Using the Script Based Exporter

The Script Based Exporter is basically a CGA generation process that is triggered via export. The Python script that is set in the export settings and runs along the export/generation process can process each model during and after generation.

In this example we are going to:

  • Prepare CGA commands that report the required values.
  • Query the values reported in CGA rules for each generated model.
  • Write a text file with the collected report values after all models are generated.

Goal

We want to write out a text file that contains the necessary data to be able to load the distributed building instances in arbitrary follow-up applications.

For every instance, the following values should be written:

  • asset identifier
  • position in 3 axes
  • rotation in 3 axes
  • scale in 3 axes

In the following form:

nrassetxposxrotxscale

0

scifi_building_9.obj

-529.4803009

0

1.051229466

1

scifi_building_17.obj

236.6141357

0

0.933861537

2

scifi_building_5.obj

499.4240112

0

1.256899709

Tutorial Setup

  1. Import the project Tutorial_12_Scripted_Report_Export into your CityEngine workspace.
  2. Open scene Tutorial_12_Scripted_Report_Export/scenes/reportInstances_01.cej.

Preparing a Generic Report Rule in an External CGA File

Although we could add all the necessary reporting commands directly in the CGA file instance_city_01.cga, we will write an external CGA file with a generic reporting rule. This way, we will be able to use the reporting rule for arbitrary CGA files.

  1. Create a new rule file File > New ... > CityEngine > CGA Rule File.
  2. And name it instanceReporting.cga.

    Create a new rule InstanceReport. We need a rule parameter asset to be able to report the asset identifier.

    InstanceReport(asset) -->

Reporting Transformation Data

Asset Identifier

We simply report the rule parameter asset.

## report asset identifier
	report("asset", asset)

Scale

In most cases one needs a scale relative to the original asset size. We therefore cannot report the scope size only, but need to divide it by the asset size. The original asset size can be queried using the assetInfo() command. assetInfo(asset, "sx") queries the size in x-axis.

The report commands for the scale are therefore:

## report scale values relative to asset
	report("xscale", scope.sx/assetInfo(asset, "sx"))
	report("yscale", scope.sy/assetInfo(asset, "sy")) 
	report("zscale", scope.sz/assetInfo(asset, "sz"))

Rotation

We want to report the rotation in world coordinates, and therefore need to convert the pivot rotation in CityEngine using the convert() command:

## report rotation in world coords
	report("xrot", convert(x, pivot, world, orient, 0,0,0))
	report("yrot", convert(y, pivot, world, orient, 0,0,0))
	report("zrot", convert(z, pivot, world, orient, 0,0,0))

Position

Position is the trickiest part. To be able to instance your assets correctly in your follow-up application, it is crucial to pay attention to the pivot and the position of the used assets. In our case the assets have their pivot in their center on the ground plane, and are located on the world origin. See the Maya screenshot below for clarification:

Building asset in Maya, displaying pivot and position

Before reporting the position, we will therefore modify the asset scope in the following way: The scope is scaled to a small "needle", and centered in x and z. This way we make sure the reported position corresponds to the pivot of the asset in Maya.

## scale and center scope
	s(0.0001,'1,0.0001) center(xz)

Again, the position needs to be converted to world coordinates:

## report position in world coords
	report("xpos", convert(x, scope, world, pos, 0,0,0))
	report("ypos", convert(y, scope, world, pos, 0,0,0))
	report("zpos", convert(z, scope, world, pos, 0,0,0))

We have now reported all the required values. To make sure no undesired geometry is displayed in the viewport, we add a NIL command to the end for the reporting rule. The final reporting rule:

InstanceReport(asset) -->

	## report instance ID
	report("asset", asset)	
	
	## report scale values relative to asset
	report("xscale", scope.sx/assetInfo(asset, "sx"))
	report("yscale", scope.sy/assetInfo(asset, "sy")) 
	report("zscale", scope.sz/assetInfo(asset, "sz"))

	## report rotation in world coords
	report("xrot", convert(x, pivot, world, orient, 0,0,0))
	report("yrot", convert(y, pivot, world, orient, 0,0,0))
	report("zrot", convert(z, pivot, world, orient, 0,0,0))

	## scale and center scope
	s(0.001,'1,0.001) center(xz)
	
	## report position in world coords
	report("xpos", convert(x, scope, world, pos, 0,0,0))
	report("ypos", convert(y, scope, world, pos, 0,0,0))
	report("zpos", convert(z, scope, world, pos, 0,0,0))

	NIL

Using the Report Rule

Let's get back to our original CGA rule file and use the prepared report rule. At the beginning of the file instance_city_01.cga, add the following line to import the prepared report rule file with the id instanceReporting.

import instanceReporting:"instanceReporting.cga"

Now we simply add the InstanceReport rule to the end of our building rule. Make sure to also add the leaf rule “Asset.” after the insert command to make sure the asset is generated.

Building(asset) --> 
	s('1,0,'1) 
	i(asset) Asset.
	instanceReporting.InstanceReport(asset)

Generate a building now, and look into the Reports pane of the Inspector, which should look similar to this:

Report variables shown in the Inspector

Note:

The Reports pane in the Inspector is only for statistical reports, and does not display all values (e.g. does not display strings). However, it can be used in our case to make sure all required values are reported.

Part 2: The Export Script

We will now process the prepared report data using the Script Based Exporter. For this, we need to create a new Python script which will do the job.

Python Script Export Template

  1. Create a new python export script from template File > New ... > Python > Python Module.
  2. Choose the project's script folder, name it exportInstances and choose Module: Export (Reporting) as template.
    The Python module wizard

The template script contains four functions. We only need finishModel() and finishExport(), so delete the other two.

Access Report Data in finishModel()

The array of report variables of the currently processed shape can be accessed in the following way:

model.getReports()['asset']

Where asset is the name of the report variable.

Because not all generated shapes have an instance on them (e.g. empty ground shapes or street shapes), we first need to check for the presence of report data using one of the report variables, namely 'asset':

if(model.getReports().has_key('asset')):

If you look at the CGA rules of the generated buildings in detail, you will notice that some shapes contain more than one instance. We therefore need to loop over all the reported datasets per shape by first getting the length of the 'asset' array. As a first test, we will print out the report data array to the console:

l = len(model.getReports()['asset'])
		for i in range(0,l): 
			print model.getReports()

Running the Script-Based Exporter

  1. Select a small set of buildings or lot shapes
  2. Start the script based exporter: File > Export ... > CityEngine > Export Models of selected Shapes and choose Script Based Exporter (Python)
  3. In the Misc. Options tab, browse to the export script exportInstances.py and run the exporter by clicking Finish.
    The Script Based Export dialog, with the export script set

    The Python console output should read something of the form:

    {'zpos': [-362.6108093261719], 'yrot': [-69.42008209228516], 'asset': ...

    {'zpos': [-412.1033630371094], 'yrot': [165.30718994140625], 'asset': ...

    ...

    Note:

    The Python output console can be opened via Menu Window > Show Console.

    Select output consoles with the console switch dropdown on the toolbar.

Adding Globals

Instead of printing the data to the console, we now add a new function processInstance() that will process and collect the report values. Additionally we add two global variables (outside the finish model function) that collect the data and keep track of the instance count:

# Globals
gInstanceData = "" # global string that collects all data to be written
gInstanceCount = 0 # global count to enumerate all instances

The finishModel() Function

The completed function finishModel():

# Called for each initial shape after generation.
def finishModel(exportContextOID, initialShapeOID, modelOID):
	global gInstanceData, gInstanceCount
	model = Model(modelOID)
	if(model.getReports().has_key('asset')): # only write t3d entry if report data available
		# there might be more than one asset per model, therefore loop
		l = len(model.getReports()['asset'])
		for i in range(0,l):
			instanceData = processInstance(model.getReports(),gInstanceCount, i-1)
			gInstanceData = gInstanceData+instanceData
			gInstanceCount = gInstanceCount+1

The processInstance() Function

This function simply returns all the report variables in a tab-separated string:

''' Collect the instance information in a tab-separated string''' 
def processInstance(reports, count, index):

	## remove path from asset string
	asset = reports['asset'][index]
	asset = asset.rpartition("/")[2]

	## prepare the string for the instance map
	text  = "%d\t" % count;
	text += "%s\t" % asset;
	text += "%.3f\t%.3f\t%.3f\t" % (reports['xpos'][index],reports['ypos'][index], reports['zpos'][index])
	text += "%.3f\t%.3f\t%.3f\t" % (reports['xrot'][index],reports['yrot'][index], reports['zrot'][index])
	text += "%.3f\t%.3f\t%.3f\n" % (reports['xscale'][index], reports['yscale'][index], reports['zscale'][index])
	return text

The finishExport() Function

This function is called after the generation of all shapes is finished. We define the filename of the text file containing the instance map here, and call the writeFile() function:

# Called after all initial shapes are generated.
def finishExport(exportContextOID):
	global gInstanceData, gInstanceCount

	## path of the output file
	file = ce.toFSPath("models")+"/instanceMap.txt"


	## write collected data to file
	writeFile(file, gInstanceData)
	print str(gInstanceCount)+"instances written to "+file +"\n"

The writeFile() Function

... adds the header information and writes the collected report string to disk:

''' combining data (header and content) and writing file '''
def writeFile(file, content):

	## prepare the head string
	head = "nr\tasset\txpos\typos\tzpos\txrot\tyrot\tzrot\txscale\tyscale\tzscale\n"

	## combine header and content
	content = head + content

	## write data to the file
	report = open(file, "w")
	report.write(content)
	report.close()

Running the Final Script

  1. Select a set of buildings or lot shapes.
  2. Start the script based exporter: File > Export ... > CityEngine > Export Models of selected Shapes and choose Script Based Exporter (Python).
  3. In the Misc. Options tab, browse to the export script exportInstances.py and run the exporter by clicking Finish.

    The resulting file instanceMap.txt is saved in the model directory of the current project.

    nrassetxposxrotxscale

    0

    scifi_building_9.obj

    -529.4803009

    0

    1.051229466

    1

    scifi_building_17.obj

    236.6141357

    0

    0.933861537

    2

    scifi_building_5.obj

    499.4240112

    0

    1.256899709

    Instead of writing a tab-separated text file, you can process the reported data in arbitrary ways: E.g. writing a Mel script for Maya that creates the instances, writing position information to a database or writing your custom ascii-based format.

Part 3: Additional Elements

Thanks to the generic reporting rule, we can now easily extend our CGA rule set with additional instance elements for reporting and export.

  • Assign instance_city_02.cga to all shapes in the scene.
  • Generate some street shapes

This rule file includes futuristic arc elements on the streets.

Bridge asset
Bridge asset distributed along streets

Open the CGA rule file instance_city_02.cga.

Using the Report Rule

In order to export the bridge instances to the instanceMap as well, simply add the InstanceReport rule to the Bridge rule.

Bridge --> 
	s(1,8,scope.sz+3.2) center(xz) 
	i(bridge_asset) 
	s(calcHeight2(scope.sx),calcHeight2(scope.sy),'1)
	Bridge.
	instanceReporting.InstanceReport(bridge_asset)

nrassetxpos...

0

bridge3.obj

-363.586952209

...

1

bridge3.obj

-313.587295532

...

...