要访问教程CityEngine中,单击 帮助>下载教程和例子…。选择教程或示例后,项目会自动下载并添加到您的工作区。

Python 脚本接口极大地增强了 CityEngine 的可能性。本教程解释了 Python 控制台和编辑器的基本用法,并给出了几个有关 CityEngine 任务自动化的示例。

有关更多信息,请参阅 CityEngine Python 参考

GUID-669A98AA-8F6A-4C0D-9B62-CC7F7804A88B-web

Python 控制台和编辑器

教程设置

要开始,请完成以下步骤:

  1. Tutorial_10_Python_Scripting项目导入 CityEngine 工作区。
  2. 打开Tutorial_10_Python_Scripting/scenes/01_PythonScripting.cej场景。

Python控制台

完成以下步骤以打开新的 Python 控制台:

    1. 单击Window > Show Console打开控制台窗口。
    2. 使用工具栏右侧的小三角形打开 Python 控制台。
      GUID-2B127CEA-2079-4537-B5AF-27AE7B8CB0A7-web
您的第一个 CityEngine Python 命令是选择具有特定名称的场景元素的快速方法。
    1. 键入ce.setSelection
    2. Ctrl+Space以显示命令完成弹出窗口。
    3. 输入ce.setSelection(ce.getObjectsFrom(ce.scene, ce.withName(“*Broadway*”)))命令。
    4. Enter
这将选择名称中包含“百老汇”一词的所有场景元素。

GUID-C7E89028-15DD-4187-A1BD-A1249B0C7773-web
GUID-69B5BBE7-97FD-47F8-94CC-3B50C891EA1D-web

Python 编辑器

一旦您计划使用更长、更高级的 Python 命令或一组命令,使用 CityEngine 中的 Python 编辑器会很有帮助。

    1. 要创建新的 Python 脚本,请单击文件>新建 > Python 模块

      出现 Python 模块对话框。

    2. 在 Python 模块对话框中,浏览到项目的脚本文件夹。
    3. 键入myHelpers作为新 Python 模块的名称。
    4. 选择模块:主模板。
    5. 单击完成
      GUID-54D8448D-2104-4BD6-88BA-A8BB12AC9626-web
新的 Python 模块 myHelpers 在 CityEngine 的 Python 编辑器中打开。

在 ce = CE() 行之后添加新的 selectByAttribute(attr, value) 函数。

def selectByAttribute(attr, value):
    objects = ce.getObjectsFrom(ce.scene)
    selection = []
    for o in objects:
        attrvalue = ce.getAttribute(o, attr)
        if attrvalue  ==  value:
            selection.append(o)
        
    ce.setSelection(selection)

在脚本的主要子句中使用特定参数调用它。确保主块位于文件末尾。

if __name__ == '__main__':
    selectByAttribute("connectionStart","JUNCTION")

要执行脚本,请单击菜单中的Python >运行脚本,或在 Python 编辑器中按F9

GUID-1C5BF334-D210-47D2-B70E-5A88F604DA6E-web

 

从控制台运行脚本

或者,您可以通过完成以下步骤,通过 Python 控制台调用您的帮助程序脚本:

  1. 在 Python 控制台中,将模块的路径添加到系统路径。
  2. 导入您的模块。

    >>> sys.path.append(ce.toFSPath("scripts"))
    >>> import myHelpers

     

  3. 通过以下方式在控制台中使用任意参数调用您的辅助函数:

    myHelpers.selectByAttribute("connectionEnd", "JUNCTION")

     

    GUID-A3B8ABEC-3A47-4E0D-AF76-AA7B3013A01B-web

扩展 scripting.py 脚本

要扩展 scripting.py,请完成以下步骤:

  1. 使用操作系统的文件浏览器在 CityEngine 工作区中创建一个新文件scripting.py
  2. 添加以下行以在启动时自动映射您的帮助程序脚本:

    import sys
    
    sys.path.append({PATH_TO_YOUR_SCRIPTS_DIRECTORY})
    // e.g. sys.path.append("C:userCityEngineMyProjectscripts")
    import myHelpers

     

CityEngine 重新启动后,您的myHelpers模块将自动加载。您可以通过以下方式调用控制台中的选择函数:

>>> myHelpers.selectByAttribute("connectionEnd", "JUNCTION")
笔记:

您可以向scripting.py文件中添加任意代码。当打开新的 Python 控制台并从 Python 编辑器运行脚本时,启动模块会在 CityEngine 启动期间自动执行。

确保您的scripting.py文件有效并正确执行;否则无法执行 CityEngine 中的 Python 代码。创建或修改scripting.py文件后,在 CityEngine 中打开 Python 控制台;那里显示执行脚本文件的问题。

scripting.py在 CityEngine 启动时只读取一次。如果您修改了该文件,请确保重新启动 CityEngine。

如果在 CityEngine 启动时脚本未正确更新,请删除 Python 缓存目录$USER_DIR/.cityengine/$CEVERSION_DIR/pythonCache/

改变街道宽度

通常,您可能希望增加许多路段的街道宽度属性。如果这不能在 GUI 中轻松完成,Python 脚本可以提供帮助。

教程设置

打开Tutorial_10_Python_Scripting/scenes/02_PythonScripting.cej场景。

创建新的 Python 脚本

  1. 通过单击File > New > Python > Python Module创建一个新的规则文件。
  2. 选择项目的脚本文件夹,将其命名为setStreetWidths,然后选择Module: Main模板。

incrementStreetWidths() 函数

此函数使用用户指定的值增加所有选定街道段的 streetWidths 属性。

一、函数定义:

def incrementStreetWidths(increment):

您需要获取所有选定的段并循环遍历它们。

selectedSegments = ce.getObjectsFrom(ce.selection, ce.isGraphSegment)
    for segment in selectedSegments:

要计算新的街道宽度,首先使用 ce.getAttribute() 命令获取当前值。请注意带有前缀 /ce/street/ 的属性名称的语法;这将访问对象的用户属性。

oldWidth = ce.getAttribute(segment, "/ce/street/streetWidth")

最后,通过添加用户提供的参数增量并将新值分配给路段来计算新的街道宽度。

newWidth = oldWidth+increment
ce.setAttribute(segment, "/ce/street/streetWidth", newWidth)

整个功能。

''' increment the street width parameter of all selected street segments'''
def incrementStreetWidths(increment):
    selectedSegments = ce.getObjectsFrom(ce.selection, ce.isGraphSegment)
    for segment in selectedSegments:
        oldWidth = ce.getAttribute(segment, "/ce/street/streetWidth")
        newWidth = oldWidth+increment
        ce.setAttribute(segment, "/ce/street/streetWidth", newWidth)
  • 在脚本的主块中,添加函数调用,并选择一个增量。
if __name__ == '__main__':
   incrementStreetWidths(10)

选择一组街道段。

运行 Python 脚本(菜单Python >运行脚本),或在 Python 编辑器中按F9

GUID-44443C7A-EA02-4012-AD5A-C5B3AC65D8A5-web
GUID-253E984C-07D7-4D4F-804B-38F9C327A324-web

 

使用@noUIupdate 加快速度

执行之前的脚本可能需要一些时间。这是因为 CityEngine 中的脚本执行在单独的线程中运行,并在每个命令之后更新 GUI 和 3D 视口。在这种情况下,在每次setAttribute()调用之后,街道网络都会更新,并且 3D 视口会重新绘制。

虽然这对于本示例来说很方便,但正常执行需要更快。这可以通过在函数定义上方添加@noUIupdate标记来实现。

@noUIupdate
def incrementStreetWidths(increment):

以这种方式标记的函数将在执行期间阻止 GUI 更新,但根据它们的作用,执行速度会因因素而异。

警告:

脚本命令与@noUIupdate标记的某些组合可能会冻结用户界面。

如果您在使用@noUIupdate时遇到 UI 冻结或其他意外行为,请修改您的脚本,以便@noUIupdate只标记一个小的特定函数,而不是标记整个脚本。

multiplySegmentWidths() 函数

该函数同时设置了几个属性,即streetWidth、sidewalkWidthLeft和sidewalkWidthRight。用户可以指定乘以宽度的因子。

@noUIupdate
def multiplySegmentWidths(factor):
    selectedSegments = ce.getObjectsFrom(ce.selection, ce.isGraphSegment)
    for segment in selectedSegments:

辅助函数multiplyAttribute对不同的属性进行乘法运算。

multiplyAttribute(segment, "/ce/street/streetWidth", factor)
multiplyAttribute(segment, "/ce/street/sidewalkWidthLeft", factor)
multiplyAttribute(segment, "/ce/street/sidewalkWidthRight", factor)

def multiplyAttribute(object, attrname, factor):
    oldval = ce.getAttribute(object, attrname)
    newval = oldval*factor
    ce.setAttribute(object, attrname, newval)

multiplySegmentWidths 和 multiplyAttribute

''' multiply street and sidewalk widths of all selected street segments by factor '''
@noUIupdate
def multiplySegmentWidths(factor):
    selectedSegments = ce.getObjectsFrom(ce.selection, ce.isGraphSegment)
    for segment in selectedSegments:
        multiplyAttribute(segment, "/ce/street/streetWidth", factor)
        multiplyAttribute(segment, "/ce/street/sidewalkWidthLeft", factor)
        multiplyAttribute(segment, "/ce/street/sidewalkWidthRight", factor)

''' multiply attribute of object by factor '''
def multiplyAttribute(object, attrname, factor):
    oldval = ce.getAttribute(object, attrname)
    newval = oldval*factor
    ce.setAttribute(object, attrname, newval)

在脚本的主块中,添加函数调用,并选择乘法因子。

if __name__ == '__main__':
   multiplySegmentWidths(1.5)

选择一组街道段。

单击Python > Run Script 运行Python脚本,或在 Python 编辑器中按F9

GUID-3726EE17-1175-451A-8BDF-5800856B34A7-web
GUID-91004B08-3C0C-4C68-B8DA-354280B1543B-web

从控制台运行

可以在导入脚本模块后从 Python 控制台调用上述函数,而不是在 Python 编辑器中设置函数参数。

>> scriptpath = ce.toFSPath("scripts")
>> sys.path.append(scriptpath)
>> import setStreetWidths
>> setStreetWidths.multiplySegmentWidths(0.5)

从 FBX 文件设置相机

本节介绍如何通过 Maya 的 FBX 导出将静态相机数据导入 CityEngine。

教程设置

打开Tutorial_10_Python_Scripting/scenes/02_PythonScripting.cej场景。

将相机导出到 FBX (Maya)

如果您没有 Maya,您可以跳过以下步骤,并使用现有的data/camera.fbx文件。

  1. 在 Maya 中,选择要导出的摄影机。
  2. 单击文件>导出选择

在导出对话框中,确保按照以下屏幕截图进行设置:

GUID-53A2F28D-60E7-4813-AC7D-A2172CE166BA-web

 

相机导入脚本

  1. 通过单击File > New > Python > Python Module创建一个新的规则文件。
  2. 选择项目的脚本文件夹,将其命名为importFBXCamera,然后选择Module: Main模板。

解析 FBX 文件

  1. 解析行并查找 ID。
  2. 准备阵列中的相机数据。

    Nongeneric 仅适用于特定的 .fbx 文件。

  3. 解析存储相机数据的 .fbx 文件中的行。
def parseLine(lines, id):
    data = False
    for line in lines:
        if line.find(id) >=0 :
            data = line.partition(id)[2]
            break
    if data:
        data = data[:len(data)-1] # strip n
        data = data.split(",")        
    return data

def parseFbxCam(filename):
    f=open(filename)
    lines = f.readlines()
    cnt = 0
    loc =  parseLine(lines, 'Property: "Lcl Translation", "Lcl Translation", "A+",')
    rot =  parseLine(lines, 'Property: "Lcl Rotation", "Lcl Rotation", "A+",')
    return [loc,rot]

设置 CityEngine 相机

获取 CityEngine 视口,并调用位置和旋转设置函数。

def setCamData(data):
    viewport = ce.get3DViews()[0]
    setCamPosV(viewport, data[0])
    setCamRotV(viewport, data[1])
def setCamPosV(v, vec):
    v.setCameraPosition(vec[0], vec[1], vec[2])
    
def setCamRotV(v, vec):
    v.setCameraRotation(vec[0], vec[1], vec[2])

主功能

def importFbxCamera(fbxfile):
   
    data = parseFbxCam(fbxfile)
    if(data[0] and data[1]) :
        setCamData(data)
        print "Camera set to "+str(data)
    else:
        print "No camera data found in file "+fbxfile

调用主块

if __name__ == '__main__':
    camfile = ce.toFSPath("data/camera.fbx")
    importFbxCamera(camfile)

单击Python > Run Script 运行Python脚本,或在 Python 编辑器中按F9

您的相机应按以下屏幕截图所示定位:

GUID-2A210897-BB2A-4B93-8431-FB7F6BFD8169-web

 

笔记:

不读取动画曲线;仅读取导出帧处的变换相机。

相机需要作为单个对象导出。

动画:种植建筑物

Python 脚本可用于自动生成或导出过程。以下示例展示了如何通过设置建筑属性并导出生成的模型集来生成建筑动画。

教程设置

打开Tutorial_10_Python_Scripting/scenes/03_PythonScripting.cej场景。

生成建筑物

  1. 在场景中选择很多。
  2. GrowingBuilding.cga规则文件分配给该地块。
  3. 要生成建筑物,请单击形状>生成模型
    GUID-0643E84D-87F8-40E0-8C99-3084BFD1325A-web

规则文件包括用于更改建筑物尺寸的属性。与其手动设置这些值,不如编写一个脚本来更改这些值并批量生成模型的不同版本。

动画脚本

创建一个新的 Python 主模块my_grow_building.py

def增长建筑

该函数提供了一个时间线,它在两个范围内循环并调用 setAttribute 函数。

def growBuilding():
    for i in range(1,14):
        height = 20+i
        doStep(i,height,1)

    for i in range(15,35):
        height = 34
        width = i-14
        doStep(i,height,width)

def doStep

在 lot 对象上,修改了两个属性的高度和宽度。

def doStep(i,height,width):    
    object = ce.getObjectsFrom(ce.scene, ce.withName("'Lot1'"))
    ce.setAttributeSource(object, "height", "OBJECT")
    ce.setAttributeSource(object, "width", "OBJECT")
    ce.setAttribute(object, "height", height)
    ce.setAttribute(object, "width", width)
    
    Generate(object)

定义生成

以下生成建筑物:

def Generate(object):
    ce.generateModels(object)

主要的

在脚本的主要子句中调用growBuilding。

if __name__ == '__main__':
   growBuilding()

批量生成建筑物

  1. 选择场景中的地块。
  2. 在 Python 编辑器中按F9以运行脚本。

GUID-29C1D9F0-1145-49CA-A046-17F395DCB8C4-web

 

批量导出

一旦您对生成的模型充满信心,请添加一个名为Export的附加函数。

def Export(i, object):
    dir = ce.toFSPath("models")
    file = "building_merge_" + str(i)
    // prepare export settings
    settings = OBJExportModelSettings()
    settings.setBaseName(file)
    settings.setOutputPath(dir)
    //do export
    ce.export(object, settings)

替换doStep() 中的 Generate 调用。

//Generate(object)
	Export(i, object)

在模型文件夹中找到导出的模型。确保导出函数在主子句之前。

编写资产库规则文件

如果您拥有大量资产,查看所有资产可能会有所帮助。本节展示了如何自动生成 CGA 规则文件,其中显示了项目的资产。

教程设置

打开Tutorial_10_Python_Scripting/scenes/03_PythonScripting.cej场景。

您要编写的规则文件应具有以下结构:

Lot -->  Geometries Textures

Geometries --> 
	 Geometry(assetpath)
	 Geometry(assetpath)
	 ...

Geometry(asset) --> i(asset)

这是用于几何资产和纹理图像。

  • 创建一个新的 Python 主模块asset_lib.py
  • 添加新函数writeCGALib
def writeCGAlib():

写入标题信息、起始规则 Lot 和几何规则。

...
    cga = "/*Asset Library Loader : Generated by asset_lib.py*/n version "2011.1"nn"

    // write start rule
    cga += "Lot -->  Geometries Textures"
    
    // write rule showing geometries
    cga += "nnGeometries --> "

遍历资产文件夹中的所有 .obj 文件,并为每个资产准备规则调用Geometry(assetpath)

...
    // get all .obj files from asset directory, and call their loader
    for obj in ce.getObjectsFrom("/", ce.isFile, ce.withName("/Tutorial_10*/assets/*.obj")):     
        // and write 
        cga += "nt t(2,0,0)  Geometry(""+obj+"")"

为纹理资产编写了类似的规则。

...
    // write rule showing jpg textures
    cga+="nnTextures-->nts(1,0,0) set(scope.ty,-2) set(scope.tz,0) i("facades/xy-plane.obj")"   
    
    // get all .jpg files from asset directory, and call their loader
    for jpg in ce.getObjectsFrom("/", ce.isFile, ce.withName("/Tutorial_10*/assets/*.jpg")):   
        cga += "ntt(2,0,0)  Texture(""+jpg+"")"

编写资产加载器规则。

...
    //write geometry loader rule
    cga += "nn Geometry(asset) --> s(1,0,0) i(asset) set(scope.ty,0) set(scope.tz,0)"
    
    //write texture loader rule
    cga += "nn Texture(asset) --> set(material.colormap, asset)"

打开 .cga 文件的文件句柄,并写入 cga 内容。

...
    cgafile = ce.toFSPath("rules/asset_lib.cga")
    CGA = open(cgafile, "w")
    CGA.write(cga)
    CGA.close()
    print "written file "+cgafile

添加新的assignAndGenerateLib()函数。它将生成的 .cga 文件分配给场景批次并生成模型。

def assignAndGenerateLib():
    object = ce.getObjectsFrom(ce.scene, ce.withName("'Lot2'"))
    ce.refreshWorkspace()
    ce.setRuleFile(object, "asset_lib.cga")
    ce.setStartRule(object, "Lot")
    ce.generateModels(object)

最后,调用main子句中的两个函数:

if __name__ == '__main__':
    writeCGAlib() 
    assignAndGenerateLib()

生成库模型

在 Python 编辑器中,打开asset_lib.py文件,按F9

GUID-5C19106D-F072-469C-B981-E654BEAB8CF2-web

 

使用 startup.py自动化CityEngine任务

您可以使用 python 来自动化更大或重复的任务。例如,您可能希望根据整个县的宗地信息自动生成模型。为此,您将执行以下操作:

  1. 创建一个项目或使用现有的项目。我们将使用现有的 Tutorial_10_Python_Scripting__2018_0教程项目。
  2. 在项目的脚本文件夹中创建一个 Python 主模块。本教程包含一个名为automationJob.py的最小作业。
  3. 插入带有自动化作业任务的函数。为了进行测试,请在“__main__”部分中添加对此函数的调用。提供的fgdbToKml示例从fileGDB导入形状以生成模型并将它们写出到 KML:
    自动化作业.py

    from scripting import *
    // get a CityEngine instance
    ce = CE()
     
    def fgdbToKml(pathFGDB,layerName,ruleName,startRule = "Generate"):
        // open scene in the automation project
        ce.newFile('/scenes/emptyScene.cej')
         
        // load a database
        importSettings = FGDBImportSettings()
        importSettings.setDatasetFilter(['/'+layerName])
        ce.importFile(ce.toFSPath(pathFGDB), importSettings)
         
        // assign rule file based on the layer name
        layer = ce.getObjectsFrom(ce.scene, ce.isShapeLayer, ce.withName(layerName))[0]
        shapes = ce.getObjectsFrom(layer, ce.isShape)
        ce.setRuleFile(shapes, ruleName)
        ce.setStartRule(shapes, startRule)
         
        // export models to KML
        exportSettings = KMLExportModelSettings()
        exportSettings.setOutputPath(ce.toFSPath("models"))
        exportSettings.setBaseName(layerName)
        exportSettings.setCompression(True)
        ce.export(shapes, exportSettings)
         
        // close CityEngine
        ce.waitForUIIdle()
        ce.closeFile()
    
    if __name__ == '__main__':
        fgdbToKml("data/CityData.gdb", "NewShapes", "/ESRI.lib/rules/Buildings/Building_From_Footprint.cga", "Generate")
        pass
  4. 创建配置文件以定义作业参数。本教程包含一个位于datajobConfig.cfg 中的示例
    作业配置文件

    [config]
    pathFGDB=data/CityData.gdb
    layerName=NewShapes
    ruleName=/ESRI.lib/rules/Buildings/Building_From_Footprint.cga
    startRule=Generate
  5. 将函数run(cfg)getCfgValue(cfg,name) 添加automationJob.py,以便使用存储在配置文件中的参数运行自动化作业。
    自动化作业.py

    ...
    def getCfgValue(cfg,name):
        for c in cfg:
            if  c[0] == name: return c[1]
        return None
     
    def run(cfg):
        pathFGDB = getCfgValue(cfg,'pathfgdb')
        layerName = getCfgValue(cfg,'layername')
        ruleName = getCfgValue(cfg,'rulename')
        startRule = getCfgValue(cfg,'startrule')
         
        fgdbToKml(pathFGDB, layerName, ruleName, startRule)
  6. 建议使用单独的CityEngine工作空间进行自动化。在您的系统上创建一个新的C:Automation Workspace文件夹。
  7. /scripts/startup.py文件从教程项目复制到新的C:Automation Workspace根目录。

    在命令“__startup__”这个Python脚本的部分获得在启动时自动执行CityEngine中。第一个启动参数定义包含自动化作业的CityEngine项目。它将链接到自动化工作区。第二个参数包含配置文件。它被解析并作为(名称,值)对列表移交给自动化作业。作业完成后,CityEngine get 安全关闭。

    启动文件

    from scripting import *
    from java import lang
    import ConfigParser, sys
     
    if __name__ == '__startup__':
       // get a CityEngine instance
        ce = CE()
         
        // get startup arguments
        projectFolder = lang.System.getProperty("projectFolder")
        configFilePath = lang.System.getProperty("configFilePath")
         
        // link the automation project into automation workspace
        if "automationProject" in ce.listProjects(): ce.removeProject("automationProject")
        ce.importProject(projectFolder, False, "automationProject")
         
        // read configuration file
        cp = ConfigParser.ConfigParser()
        cp.read(configFilePath)
        cfg = cp.items('config') # list of (name,value) pairs
         
        // run automation job
        sys.path.append(ce.toFSPath("/automationProject/scripts"))
        import automationJob
        automationJob.run(cfg)
         
        // safely shut down CityEngine
        ce.exit()
  8. 在自动化工作空间中打开命令行并启动CityEngine并移交作业定义和参数:
    命令

    <Path_to_CityEngine.exe> -data <Workspace_Folder> -vmargs -DprojectFolder=<Project_Folder> -DconfigFilePath=<Configuration_FilePath>
     
    // Example:
    > "C:Program FilesEsriCityEngine2018.0CityEngine.exe" -data "C:Automation Workspace" -vmargs -DprojectFolder="C:CE_WorkspaceTutorial_10_Python_Scripting__2018_0" -DconfigFilePath="C:CE_WorkspaceTutorial_10_Python_Scripting__2018_0datajobConfig.cfg"
  9. 作业完成后,models文件夹包含如下所示的ArcGIS Earth中的Footprints.kmz输出文件 。

    GUID-F2A24687-D8DB-40CE-B305-D1EC2C7F6754-web

笔记:

automationJob.py文件包含了最小的作业,请使用 CityEngine中的Python参考裁缝到您的实际需要。