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. You will report information of instances distributed in our scene, and use it to generate an instance map in a simple text file.

For more information, see the Script-based export.

Rendered view of atoll

Report information of instanced buildings

In the first part, you 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.

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

Use 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 you 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

You 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. Download the Tutorial_12_Scripted_Report_Export tutorial into your CityEngine workspace.
  2. Open the Tutorial_12_Scripted_Report_Export/scenes/reportInstances_01.cej scene.

Prepare a generic report rule in an external CGA file

Although you could add all the necessary reporting commands directly in the CGA file instance_city_01.cga, you will write an external CGA file with a generic reporting rule. This way, you 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 InstanceReport rule . You need a rule parameter asset to be able to report the asset identifier.

    InstanceReport(asset) -->

Report transformation data

Asset Identifier

You can 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. Therefore, you 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

You 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, you 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 you 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))

You have now reported all the required values. To make sure no undesired geometry is displayed in the viewport, you 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

Use the report rule

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

import instanceReporting:"instanceReporting.cga"

Now you simply add the InstanceReport rule to the end of our building rule. Make sure to also add the Asset. leaf rule 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

Export script

You will now process the prepared report data using the Script Based Exporter. For this, you need to create a new Python script which will do the job. For more information, see Script-based export.

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. You 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), you 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. You therefore need to loop over all the reported datasets per shape by first getting the length of the 'asset' array. As a first test, you 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 exportInstances.py export script 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': [-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, you now add a new processInstance() function that will process and collect the report values. Additionally you 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. You 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 exportInstances.py export script and run the exporter by clicking Finish.

    The resulting instanceMap.txt file 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.

Additional elements

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

  • Assign the instance_city_02.cga rule 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.

Use 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

...

...