Why Python in CityEngine

1657
7
10-07-2016 09:44 AM
KristianMortensen1
New Contributor III

I'm a 99% noob when it comes to CityEngine and the whole programming world, but it looks to me as there is the possibility to use python scripts within CityEngine, which have made me curious, why would you do that?.

I have encourtered some problems when writing cga rule files, one of them is that I want to split a shape in two, and then extrude number 1 and split number two into two new "shapes". one part of the secound split will then be extruded so it allways has the same volume as extrusion one, see picture.

But this i have not been able to do as fare as i understand it it is becouse it calculates volume one in one branche of the tree and then it is not able to use the data in another branch, is this something you would use python to or what?

I now that for this particular exmaple there is some matematiks that can solve the problem, i think what i'm looking for is a way to store some data in an attribute and then use it again but in another branch of the tree. 

Another question is I want to import a shapefiles into cityengine, which countain some building footprints, I then want to draw a shape under an amount of building footprints, and then see if a footprint is on top of the shape and if it is then calculate the sqm which is not covered by the footprint. 

thise a basically the tasks at hand and i quess the question is, is this something you do with python and what task can python do in cityengine?. Would it improve my skills in cityengine to understand python? 

Sorry for the long post

7 Replies
DanPatterson_Retired
MVP Esteemed Contributor

programming anything, regardless of the language is only useful if...

  • you have repetitive tasks performed on a regular basis
  • your workflows are long and you want the ability to just vary a few parameters at some point in the process without having to reinvent the wheel every time.
  • access to certain tasks has no equivalent in the user interface, but can only be accessed at the code level
  • you just like to code

These are a few points to consider

0 Kudos
DavidWasserman
Regular Contributor

Hey Kristian, 
So what you are hinting at is actually really complicated and detailed matter for the CityEngine Team. First Why Python: 
Python can help you control global variables of your scene and your data in ways you could not with just CGA alone. This means adjusting camera angles and rule parameters based on data dynamically such as what is done in this video (you can find the code here), or even splitting parcels appropriately for Transit Oriented Development simulations (seen here).  I have used python in CityEngine to batch render and export thousands of 3D street cross sections for multimodal planning projects where we wanted to visualize baseline conditions right next to improvements. It can be incredibly useful. The python script below as an example batch exports every layer in the CityEngine Scene to KML, which is useful if you have a lot of time layers you want to see in Google Earth or ArcGIS Earth. 

In terms of the problem at hand, you are touching on something that is a known limitation of CGA. CGA is a hierarchical rules based language, and as a result as you create new leafs in CGA (new leafs being new subdivided shapes) those shapes CANNOT communicate with previous shapes or shapes in other parts of the rule. This was a hard limitation for the Complete Street Rule, which had to make choices about which shapes take up scarce geometry on the road in the rule. To allocate shapes in that rule, I to precalculate street widths with functions GLOBALLY rather than have the resulting shapes send important information to other shapes (my bike lanes don't know how large their buffers are, but some global function declared before the first leaf can pass that information onto them). Python and smart math can get past this limitation in some cases, but other cases would require CGA shapes to be able to "talk" to each other. This is being talked about within the CE community, but so far we are just finding other ways around it. 
Cheryl Lau might have more to add on that point. 

# --------------------------------
# Name: CEBatchWebSceneExport.py
# Purpose: Batch export of CE layers to web scenes.
# Current Owner: David Wasserman
# Last Modified: 12/22/2015
# Copyright:   (c) Co-Adaptive
# CityEngine Vs: 2015.2
# Python Version:   2.7
# License
# Copyright 2015 David J. Wasserman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# --------------------------------

# Import Modules

from scripting import *

# get a CityEngine instance
ce = CE()

outputFolder = "/BatchExport"
generateBoolean = False
deleteBoolean = False


# Turn off User Interface updates, or script might take forever.
@noUIupdate
def main():
    # This function will export in batch web scenes to the input outputFolder
    layers = ce.getObjectsFrom(ce.scene, ce.isLayer)
    print("There are " + str(len(layers)) + " layers in the current scene.")
    counter = 0
    for layer in layers:
        try:
            ce.setSelection(layer)
            OID = ce.getOID(layer)
            layerName = ce.getName(layer)
            if generateBoolean:
                # generate models on selected shapes (assumes size of file is too big)
                ce.generateModels(ce.selection())
                ce.waitForUIIdle()
            print ("Setting export settings for layer named: " + str(layerName))
            exportSettings = KMLExportModelSettings()
            exportSettings.setOutputPath(ce.toFSPath("models") + str(outputFolder))
            exportSettings.setBaseName(layerName)
            exportSettings.setCompression(False)
            exportSettings.setTerrainLayers(exportSettings.TERRAIN_NONE)
            ce.export(ce.selection()[0], exportSettings)
            print ("Exported layer named: " + str(layerName) + "to models/BatchExport3WS")
            counter += 1
            if deleteBoolean:
                ce.delete(ce.selection())
                pass
            print("Exported web scene for layer named:" + str(layerName))
            # Change this to an absolute path that points to your KML files.
        except:
            print("Could not execute on counter " + str(counter))
            counter += 1
            pass


# Call
if __name__ == '__main__':
    print("Batch Layer script started.")
    main()
    print("Script completed.")‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
David Wasserman, AICP
KristianMortensen1
New Contributor III

Thanks for the answer. Then I have to find a workaround maybe by defining the size of the footprint as an attribute and the split should be a function of that. 

0 Kudos
CherylLau
Esri Regular Contributor

It is not possible for a shape to get information about another shape in another branch of the shape tree (except in the case of occlusion testing which happens behind the scenes when you use the occlusion functions).  As David mentions, you can however try to set some attributes that you can access in the same branch of the tree.  Or, you can globally set variables if you know the information ahead of time.  Sometimes python gives you more functionality than what you can get with just CGA, but in this case I don't see a straightforward way to do this with python.  By setting variables in the same branch of the tree, I rewrote your code so that FP3 has the same volume as FP.

@Hidden

attr b1Area = 0

 

const lotArea = geometry.area

const b1Height = 9

 

Lot -->

      split(x) { ~1: FP2 | ~1: FP }

     

FP -->

      extrude(b1Height) B1.

      print(geometry.volume)

     

FP2 -->

      set(b1Area, lotArea - geometry.area)

      split(z) { ~1: FP3 | ~1: OpenSpace }

     

FP3 -->

      extrude((b1Area*b1Height)/geometry.area)

      print(geometry.volume)

     

OpenSpace -->

      color(0,1,0)

Your second question about getting the area of the polygon minus the area of the footprints is also a difficult problem. In general, I don't think this is possible because CityEngine cannot do boolean intersections.  Python won't help with this.

DavidWasserman
Regular Contributor

Hey Cheryl, 

I think in the case of python, I was thinking there might be a few limited cases where you could use a combination of 1) getReports, 2) setAttributes, and 3) GenerateModels in the python API to iteratively retrieve report information to make decisions on particular aspects of of the models geometry. I was not entirely sure if it would actually work though. 

I want to test an example, but I was largely thinking along the lines of a block like  (pseudoish code, not checking api explicitly):

depth=startValue

while model.getReports()['setback_area'] <100:

      depth+=1

      i.setAttribute('depth', depth)

      model.GenerateModels()
It is not getting information from the same branch like you mentioned, so it is not efficient, you are just telling the whole generation process to get close to 100 SQ Meters for a particular leaf based on its report. 
David

David Wasserman, AICP
0 Kudos
CherylLau
Esri Regular Contributor

Yes, perhaps there is a way with python and reports and such.  I just haven't tried anything in this direction, so I don't have a workflow to recommend.

0 Kudos
DavidWasserman
Regular Contributor

It is likely not the best approach for the problem at hand...

David Wasserman, AICP
0 Kudos