Using Python to create an object attribute from a rule attribute

1757
5
07-27-2017 12:18 PM
BrendanBuchanan_Dee
New Contributor II

I am trying to write a python script that will create a new object attribute containing the parcel area for any number of parcels I’ve selected in the scene. I know I need to use Export (Reporting) python script to fetch the value of a rule attribute.

Proposed method:

  1. Select the desired parcels in the scene
  2. Run a python script that assigns the CGA rule “calculateParcelArea.cga” to each of the parcels, generates the models using “calculateParcelArea.cga”, and reports the area in the Parcel_Area row, under the sum column.
  3. The python script then fetches the reports for each of the parcels, creates a new object attribute named ‘parcelArea’ for each parcel, and assigns the reported value.

So far I’ve written a regular python (Module: Main) script that assigns the “calculateParcelArea.cga” to the selected parcels, generates the models with areas saved to rule attribute ‘parcelArea_Calc’, and reports the value.

 

I’m having difficulty with the Export (Reporting) python module. Can someone help me make sense of this? How do I bring the existing script into the Export (Reporting) python module to complete the task?  

Thanks!

 

Python script and CGA rule below:

_____________________________________________________________________________________

'''

Created on 2017-07-26

@author: bcbd

'''

from scripting import *

 

# get a CityEngine instance

ce = CE()

 

def parcelArea():      

    shapes = ce.getObjectsFrom(ce.selection, ce.isShape)

    for shape in shapes:

        ce.setRuleFile(ce.selection(), 'calculateParcelArea.cga')

  # assign CGA rule calcParcelArea

        ce.setStartRule(ce.selection(), 'Lot')

        ce.generateModels(ce.selection())

       

if __name__ == '__main__':

    parcelArea()

    pass

_____________________________________________________________________________________

/**

 * File:    calculateParcelArea.cga

             */

 

version “2017.0”

attr parcelArea_Calc = geometry.area

 

Lot -->

      report("Parcel_Area", parcelArea_Calc)

print("Parcel_Area: " + parcelArea_Calc)

_____________________________________________________________________________________

0 Kudos
5 Replies
CherylLau
Esri Regular Contributor

I don't think you want to try to get the rule attribute.  In Python, you could either get the initial value set in the cga file for this attribute or the user set value (e.g. if you want to get the value the user sets in the Inspector), but you don't want either of these values, and instead you want a value that is calculated within a rule.  Therefore, I think you want to get the reported value for the parcel area.

For each parcel shape, you can get the reported value for parcel area and then assign this to an object attribute of the parcel shape.  To do this, create a Python Main script and a Python Export script (right click on scripts -> New -> Python Module).  The following code assumes that a cga rule that reports the parcel area to "ParcelArea" has been applied to the selected parcel shapes.  The script reads the value for "ParcelArea" and sets an object attribute called "parcelArea" to have this value.

In your Python Main script, run the export script on your desired set of shapes.

    # get list of selected shapes
    shapeList = ce.getObjectsFrom(ce.selection, ce.isShape)

    # run export script to get reported values and set obj attr
    expSettings = ScriptExportModelSettings()
    expSettings.setScript("exportScript.py")
    ce.export(shapeList, expSettings)

In your Python Export script (called exportScript.py in above code), you only need to add two lines (8 and 11) to the end of the finishModel() method.

# Called for each shape after generation.
def finishModel(exportContextOID, shapeOID, modelOID):
    ctx = ScriptExportModelSettings(exportContextOID)
    shape = Shape(shapeOID)
    model = Model(modelOID)
    
    # get reported value for ParcelArea
    r = model.getReports()["ParcelArea"][0]
    
    # set object attr to reported parcel area
    ce.setAttribute(shape, "parcelArea", r)
    
    # print to console
    print(str(shape) + ":  ParcelArea = " + str(r))

Beware that if you change your parcel shapes (e.g. resize them), then the object attribute won't have the correct parcel area anymore, which also means that your buildings will not be receiving the correct parcel area, and you'll have to rerun the python script to update the object attribute.

NguyenLong
New Contributor

Dear cheryl, 

I have read your tutorial carefully and try to export value from rule to object attribute but it can not. The value report is not exported to console.

Warning
Thu Apr 19 02:29:34 ICT 2018
The batch export completed but at least one error occurred. See export logfile for detailed error information. - [main]

java.lang.Exception: Export script execution failed.
at com.esri.prt.export.ScriptModelExportContext$4.run(Unknown Source)
at org.eclipse.jface.operation.ModalContext$ModalContextThread.run(ModalContext.java:121)
Caused by: Traceback (most recent call last):
File "<string>", line 1, in <module>
File "C:\Users\nguyenq\Documents\City engine\930\scripts\export.py", line 27, in finishModel
height = model.getReports()["Height"][0]
KeyError: Height

at org.python.core.Py.KeyError(Py.java:221)
at org.python.core.PyObject.__getitem__(PyObject.java:671)
at org.python.pycode._pyx687.finishModel$3(C:\Users\nguyenq\Documents\City engine\930\scripts\export.py:33)
at org.python.pycode._pyx687.call_function(C:\Users\nguyenq\Documents\City engine\930\scripts\export.py)
at org.python.core.PyTableCode.call(PyTableCode.java:165)
at org.python.core.PyBaseCode.call(PyBaseCode.java:166)
at org.python.core.PyFunction.__call__(PyFunction.java:368)
at org.python.pycode._pyx748.f$0(<string>:1)
at org.python.pycode._pyx748.call_function(<string>)
at org.python.core.PyTableCode.call(PyTableCode.java:165)
at org.python.core.PyCode.call(PyCode.java:18)
at org.python.core.Py.runCode(Py.java:1312)
at org.python.core.Py.exec(Py.java:1356)
at org.python.util.PythonInterpreter.exec(PythonInterpreter.java:206)
at com.esri.prt.export.ScriptModelExportContext.finishModel(Unknown Source)
at com.esri.prt.export.ScriptModelExportContext.finishScriptExport(Unknown Source)
at com.esri.prt.export.ScriptModelExportContext.finishScriptExport(Unknown Source)
... 2 more

Can you explain to me what error happened here, although I have checked the object already has "Height" via inspector tool. Or can you guide me how to get value from rule-cga (not user defined)

0 Kudos
CherylLau
Esri Regular Contributor

First, values need to be reported.  In my example above, the value for parcel area is reported in the CGA code.  The name of the report in "ParcelArea".  This value is visible in the Inspector under the Reports section.  Check that your reports are correct.

report("ParcelArea", geometry.area)

Second, the object attribute names are case sensitive.  The python code gets the reported value and then sets the object attribute called "parcelArea".  Object attributes can be seen in the Inspector under the Object Attributes section.

Third, object attributes are different from rule attributes.  In your screenshot, you circled the rule attributes in the Inspector, but the object attributes are being changed in the python code.

0 Kudos
timrobinson
New Contributor III

Hi Cheryl,

Thanks for your input on this thread already!

I've followed what I think is the approach outlined above, looking to embed multiple report values into Object Attributes. I'm not having any success - first time wrangling Python. My scripts look like this at present, both made via the Python template modules - are you able to help?

Much appreciated,

Tim

Main script:

'''

Created on 20/01/2020

@author: Tim.Robinson

'''

from scripting import *

# get list of selected shapes

shapeList = ce.getObjectsFrom(ce.selection, ce.isShape)

# run export script to get reported values and set obj attr

expSettings = ScriptExportModelSettings()

expSettings.setScript("GISexportScript.py")

ce.export(shapeList, expSettings)

Export script (GISexportScript.py):

'''

Created on 20/01/2020

@author: Tim.Robinson

'''

from scripting import *

# Get a CityEngine instance

ce = CE()

# Called before the export start.

def initExport(exportContextOID):

ctx = ScriptExportModelSettings(exportContextOID)

# Called for each shape before generation.

def initModel(exportContextOID, shapeOID):

ctx = ScriptExportModelSettings(exportContextOID)

shape = Shape(shapeOID)

# Called for each shape after generation.

def finishModel(exportContextOID, shapeOID, modelOID):

ctx = ScriptExportModelSettings(exportContextOID)

shape = Shape(shapeOID)

model = Model(modelOID)

# get reported value for New Dwellings.

r = model.getReports()["X_Dwellings@Av"][0]

# set object attr to reported New Dwellings.

ce.setAttribute(shape, "X_Dwellings@Av", r)

# get reported value for GFA GF.

r = model.getReports()["GFA.GF"][0]

# set object attr to reported GFA GF.

ce.setAttribute(shape, "GFA.GF", r)

# get reported value for GFA Resi.

r = model.getReports()["GFA.Resi"][0]

# set object attr to reported GFA Resi.

ce.setAttribute(shape, "GFA.Resi", r)

# get reported value for GFA Comm.

r = model.getReports()["GFA.Comm"][0]

# set object attr to reported GFA Comm.

ce.setAttribute(shape, "GFA.Comm", r)

# get reported value for GFA Retained.

r = model.getReports()["GFAExtgGIS_Retained"][0]

# set object attr to reported GFA Retained.

ce.setAttribute(shape, "GFAExtgGIS_Retained", r)

# Called after all shapes are generated.

def finishExport(exportContextOID):

ctx = ScriptExportModelSettings(exportContextOID)

0 Kudos
CherylLau
Esri Regular Contributor

This line is missing from the main script:

ce = CE()

Otherwise, the python script works for me.  I created a test shape that reported values for all the reports you read in the export script.

My guess is that maybe your shapes don't have values for some reports.  In this case, you'll get an error message in the Log (Window -> Log) which will tell you which line failed.  You can check if the report exists before trying to get it:

if(model.getReports().has_key("GFA.Comm")):
     r = model.getReports()["GFA.Comm"][0]