Tutorial 12: Scripted report export

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

Rendered view of atoll

This tutorial shows you how to use CGA report variables in combination with the Python-based exporter. You'll work with a scene of a future city on an island. In this setting, you'll learn how to report information of instances distributed in a scene and use it to generate an instance map in a simple text file. The text file contains the necessary data to load the distributed building instances directly into arbitrary follow-up applications.

For every instance, you will write the following values:

  • Asset identifier
  • Position in 3 axes
  • Rotation in 3 axes
  • Scale in 3 axes

The values should be written 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

The tutorial is structured in three parts:

  • In the first part, you'll extend an existing CGA file that distributes instanced buildings with a generic rule, to report variables about the distributed buildings.
  • Next, you’ll create a Python script to write the reported variables to a text file using the Script-Based Exporter.
  • Finally, you'll reuse the rule created in the first part of the tutorial to report and export variables for additional assets.

This tutorial requires a basic knowledge of CGA shape grammar as well as the basics of Python scripting. For more information, see Script-based export.

Report information of instanced buildings

First, you will prepare an external CGA file used for reporting.

Create a generic rule for reporting

To create the rule for reporting, do the following:

  1. Expand the Tutorial_12_Scripted_Report_Export tutorial folder in the Navigator window.
  2. Double-click the reportInstances_01.cej scene in the scenes folder to open the scene in the Viewport window.
  3. Click File > New > CityEngine > CGA Rule File to create a rule file.
  4. Name the rule instanceReporting.cga.

Report transformation data

Next, you'll build the reporting rule.

  1. Add an InstanceReport rule:

    InstanceReport(asset) -->
    	report("asset", asset)

    You need to pass on the asset's path as a rule parameter to be able to report it as the identifier of the asset.

  2. Add the report commands for the scale:

    report("xscale", scope.sx/assetInfo(asset, "sx"))
    report("yscale", scope.sy/assetInfo(asset, "sy")) 
    report("zscale", scope.sz/assetInfo(asset, "sz"))

    In most cases, you need a scale relative to the original asset size. You cannot report the scope size only; you must divide it by the asset size. You can query the original asset size using the assetInfo() command; for example, assetInfo(asset, "sx") queries the size in the x-axis.

  3. To report the rotation in world coordinates, you need to convert the scope rotation in CityEngine using the convert() command:

    report("xrot", convert(x, scope, world, orient, 0,0,0))
    report("yrot", convert(y, scope, world, orient, 0,0,0))
    report("zrot", convert(z, scope, world, orient, 0,0,0))

  4. Look at the pivot and positions of the assets:

    To be able to instance your assets correctly in your follow-up application, it is important to pay attention to the pivot and the position of the assets that are used. In this example, the assets have their pivot in their center on the ground plane and are located on the world origin:

    Pivot in center in world coordinate system

    Pivot in center in world coordinate system, front

    This ensures that the reported position corresponds to the pivot of the asset.

  5. Convert the position to world coordinates:

    report("xpos", convert(x, scope, world, pos, scope.sx/2,0,scope.sz/2))
    report("ypos", convert(y, scope, world, pos, scope.sx/2,0,scope.sz/2))
    report("zpos", convert(z, scope, world, pos, scope.sx/2,0,scope.sz/2))

    You have now reported all the required values.

  6. To ensure that no unwanted geometry is displayed, add a NIL command to the end of the 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, scope, world, orient, 0,0,0))
    	report("yrot", convert(y, scope, world, orient, 0,0,0))
    	report("zrot", convert(z, scope, world, orient, 0,0,0))
    
    	## report position in world coords
    	report("xpos", convert(x, scope, world, pos, scope.sx/2,0,scope.sz/2))
    	report("ypos", convert(y, scope, world, pos, scope.sx/2,0,scope.sz/2))
    	report("zpos", convert(z, scope, world, pos, scope.sx/2,0,scope.sz/2))
    
    	NIL

    Open the instanceReporting_dist.cga file to see the final rule.

Use the report rule

Next, you'll open a rule file and use the prepared report rule.

  1. Double-click the instance_city_01.cga file in the rules folder to open the rule in the CGA Editor window.
  2. 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"

  3. Add the InstanceReport rule to the end of the Building rule.

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

    The Asset. leaf rule after the insert command ensures that the asset is generated.

  4. Generate a building, and review the Reports section of the Inspector window, which should look similar to the following example:
    Report variables shown in the Inspector window

Create a script to export the reports

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

Create a module based on the export reporting template

Next, you'll create a Python module.

  1. Click File > New > Python > Python Module.
  2. For Source Folder, click Browse to select the scripts folder:

    Python module wizard

    Name the script exportInstances.

  3. Click Finish to open the Template dialog box.
  4. Click Module Export(Reporting) and click OK.

    Python template dialog box

    The exportInstances module opens in the Python editor.

    The template contains four functions, but you only need the finishModel() and finishExport() functions in this tutorial, so you can delete the others.

Access report data in finishModel()

Next, you'll access report variables.

  1. Add the following line to the bottom of the finishModel() function:

    model.getReports()['asset']

    You can access the array of report variables of the currently processed shape, in which asset is the name of the report variable.

  2. Modify the line to check for the presence of report data:

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

    Since not all the generated shapes have an instance (for example, empty ground shapes or street shapes), you check above for the presence of report data using one of the report variables, in this case, 'asset'.

  3. Add the following lines to loop over the reported datasets:

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

    If you look at the CGA rules of the generated buildings in detail, you'll notice that some shapes contain more than one instance. You can loop over the reported datasets by shape by first getting the length of the 'asset' array.

    As a first test, you'll print the report data array to the Python console.

Test the script by printing to the console

To print the report data array to the Python console, do the following:

  1. Select a small set of buildings or lot shapes.
  2. Click File > Export Models to open the model export dialog box.
  3. Click Script Based Exporter (Python) and click Next.
  4. Browse to the exportInstances.py export script in the scripts folder:

    Script Based Export dialog box with the export script set

  5. Click Finish to run the exporter.
  6. Click Window > Console to open the Python console.

    The Python console should read similar to the following output:

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

    You can select output consoles with the Display Selected Console drop-down menu on the Console toolbar.

Add global variables

You'll now add a new processInstance() function that processes and collects the report values. You'll also add two global variables (above the finishModel function) that collect the data and keep track of the instances count:

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

finishModel() function

Update the finishModel() function to the following snippet:

# 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

processInstance() function

Add the processInstance() function, which 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

finishExport() function

Add the finishExport() function. This function is called after the generation of all shapes is finished. You define the file name of the text file containing the instance map 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"

writeFile() function

Add to the bottom the writeFile() function, which 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()

Save the exportInstances.py script.

Run the final script

You'll now run the final script and create an export file.

  1. Select a set of buildings or lot shapes.
  2. Click File > Export Models.
  3. Click Script Based Exporter (Python) and click Next.
  4. Click Browse to select the exportInstances.py export script.
  5. Click Finish to run the exporter.

    The resulting instanceMap.txt file is saved in the models folder.

  6. Double-click the instanceMap.txt file to open it:

    instanceMap.txt file

    Note:

    Instead of writing a tab-separated text file, you can process the reported data using some other methods, for example, writing a Mel script for Maya that creates the instances, writing position information to a database, or writing a custom ASCII-based format.

    Open the exportInstances_dist.py file in the scripts folder to see the final script.

Report and export information of the additional assets

With the reporting rule, you can now extend your CGA rule set with additional instance elements for reporting and exporting.

  1. Open the reportInstances_02.cej scene and the instance_city_02.cga rule.
  2. Zoom to street level and notice the futuristic bridges in the shape of arcs:

    Bridge asset

    Bridge asset distributed along streets

  3. In the instance_city_02.cga rule, export the bridge instances to the instanceMap.txt file by adding the InstanceReport rule at the bottom of 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)

  4. Save the rule.
  5. Make a new selection including some streets in the scene.
  6. Run the script exporter again.
  7. Open the instanceMap.txt file again:

    InstanceMap.txt file

    The previous instanceMap.txt file is overwritten and the new one also lists instances of the bridge assets.

    Open the reportInstances_03.cej scene and the instance_city_03.cga rule to see the shapes with the final rule assigned to them.

In this tutorial, you learned how to do the following:

  • Report information of instanced assets.
  • Create a script to export reports to a text file.
  • Run the export script through the script-based exporter.

To continue your learning with CityEngine, see the complete CityEngine tutorial catalog.