教程 10:Python 脚本

要访问 ArcGIS CityEngine 中的教程工程,请打开 CityEngine,然后在主菜单中单击帮助 > 下载教程和示例。 在选择教程或示例后,系统会自动下载工程并将其添加到 CityEngine 工作空间。

模块示例

在本教程中,您将学习 Python 控制台和编辑器的基本用法,并探索多个 CityEngine 任务自动化的示例。

Python 脚本接口扩展了 CityEngine 的可能性。

使用 Python 控制台和编辑器

在本部分中,您将使用 Python 控制台和编辑器运行一个简单的选择。

打开 Python 控制台

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

  1. 导航器窗口中展开 Tutorial_10_Python_Scripting 教程文件夹。
  2. 打开 scenes 文件夹中的 01_PythonScripting.cej 场景。
  3. 单击主菜单中的窗口 > 控制台以打开控制台窗口。
  4. 单击右侧的下拉箭头,然后单击 Python 控制台

    控制台上的快捷菜单

  5. 键入 ce.setS

    控制台中的命令完成(Ctrl+空格键)

    您会注意到,当您开始键入时,将出现一个命令列表,您可以从中进行选择:您的第一个 CityEngine Python 命令可帮助您快速选择具有特定名称的场景对象。

    如果命令完成弹出窗口尚未打开,则按 Ctrl+Space 键使其显示。

  6. 双击列表中的 ce.setSelection,即可完成控制台中的条目。
  7. ce.setSelection 扩展为以下完整命令:

    >>> ce.setSelection(ce.getObjectsFrom(ce.scene, ce.withName("*Broadway*")))

  8. Enter 键选择场景中所有名称中包含单词“Broadway”的对象。

    所选 Broadway 街道形状

在编辑器中创建 Python 模块

如果您希望使用更长和更高级的 Python 命令或一组命令,在 CityEngine 中使用 Python 编辑器编写脚本会很有帮助。

要创建新 Python 模块,请执行以下操作:

  1. 选择文件 > 新建 > Python > Python 模块,然后单击下一步,以打开创建新 Python 模块对话框。
  2. 创建新 Python 模块对话框中,对于源文件夹,浏览至 \Tutorial_10_Python_Scripting\scripts\ 文件夹并单击确定
  3. 名称框中,键入 myHelpers

    “创建新

  4. 单击完成以打开模板窗口。
  5. 选择模块:主模板。

    “模块:主”模板

  6. 单击确定
    myHelpers Python 模块将作为 myHelpers.py 文件在 scripts 文件夹中创建,并在 Python 编辑器中打开:

    MyHelpers.py 脚本

  7. 在行 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)

    该函数循环遍历场景中的所有对象,并读取作为第一个参数传递的属性的值。 然后,将该值与作为第二个参数传递的值进行比较;如果匹配,则将对象添加到选择集中。

  8. 在场景中选择一个街道形状,并在检查器窗口中单击对象属性以探索可用的属性:

    街道属性

  9. 由于您希望脚本选择所有连接到交汇点的街道,因此需将具有特定参数的函数调用添加到脚本的主体部分:

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

  10. Ctrl+S 以保存该模块。
  11. 要运行该脚本,请在 Python 编辑器中按 F9

    将选择具有 Junction 交叉点的街道。

    所选 Junction 街道形状

从控制台运行脚本

或者,您可以直接在 Python 控制台中调用脚本中的函数。

  1. Python 控制台中,将刚刚创建的模块所在的路径添加到系统路径中。

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

  2. 然后,导入 myHelpers 模块。

    >>> import myHelpers

  3. 最后,使用任意参数调用帮助程序函数:

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

    调用自定义选择函数

使用 scripting.py 自动加载模块

要在启动 CityEngine 时创建并加载 scripting.py 脚本,请执行以下操作:

  1. 可以使用操作系统的文件浏览器在 CityEngine 工作空间中创建一个名为 scripting.py 的文件。

    CityEngine 工作空间根文件夹(例如 \{CityEngine Workspace}\scripting.py)中保存 scripting.py 文件。

    提示:

    可以在文件 > 工作空间 > 其他中找到该路径,或者在导航器窗口中右键单击工程,然后选择在文件管理器中显示

  2. 添加以下行以在启动时自动映射帮助程序脚本:

    from scripting import *
    
    # get a CityEngine instance
    ce = CE()
    
    import sys
     
    sys.path.append(ce.toFSPath("/Tutorial_10_Python_Scripting__2021_1/scripts"))
    import myHelpers

  3. 重新启动 CityEngine 并打开 01_PythonScripting.cej 场景,将自动加载 myHelpers 模块。
  4. Python 控制台直接调用帮助程序函数,以验证自动加载是否正常工作:

    >>> myHelpers.selectByAttribute("sidewalkWidthLeft", 4)

    选择所有 sidewalkWidthLeft 对象属性值为 4 的街道。

注:

您可以向 scripting.py 文件添加任意代码。 当打开新的 Python 控制台或从 Python 编辑器运行脚本时,脚本模块将自动运行。

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

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

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

更改街道宽度

通常,您需要增加许多路段的街道宽度属性。 如果您无法在 GUI 中有效地执行此操作,则使用 Python 脚本会有所帮助。

incrementStreetWidths() 函数

在本部分中,您将创建一个函数,以使用用户指定值增加所有选定路段的 streetWidths 属性。

  1. 打开 02_PythonScripting.cej 场景。
  2. 创建一个名为 setStreetWidths 的新 Python 模块。

    要创建一个新的模块,请按照上面的"在编辑器中创建 Python 模块部分中的步骤 1 至 6 进行操作。

  3. 首先,添加函数定义:

    def incrementStreetWidths(increment):

  4. 在此函数中,获取所有选定的路段并将其循环:

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

  5. for 循环中,要计算新的街道宽度,请首先使用 ce.getAttribute() 命令获取当前值。

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

    请注意,带有前缀 /ce/street/ 的属性名称的语法将访问对象的用户属性。 有关详细信息,请参阅参数和属性

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

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

    整个函数如下所示:

    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)

  7. 在脚本的主要块中,添加函数调用,然后选择增量:

    if __name__ == '__main__':
       incrementStreetWidths(10)

  8. 保存文件。
  9. 选择一些街段。
  10. F9 以运行 Python 脚本。

    增量前的街道宽度
    增量前的街道宽度
    街道宽度以 10 递增
    街道宽度以 10 递增

使用 @noUIupdate 修饰符

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

要使运行时间更快,请在函数定义上方添加 @noUIupdate 修饰符:

@noUIupdate
def incrementStreetWidths(increment):

使用这种方式标记的函数将在执行期间阻止 GUI 更新,并且根据其功能,将成倍地加快运行速度。

警告:

脚本命令与 @noUIupdate 修饰符的某种组合可能会冻结用户界面。

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

multiplySegmentWidths() 函数

multiplySegmentWidths() 函数同时设置多个属性,具体为 streetWidthsidewalkWidthLeftsidewalkWidthRight 属性。

  1. 将以下函数添加到 setStreetWidths 脚本中:

    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)
    
    def multiplyAttribute(object, attrname, factor):
        oldval = ce.getAttribute(object, attrname)
        newval = oldval*factor
        ce.setAttribute(object, attrname, newval)

  2. 使用以下内容替换 main 块中的函数调用:

    multiplySegmentWidths(1.5)

  3. 保存该模块。
  4. 选择一些街段。
  5. F9 以运行脚本。

    相乘前的路段宽度
    相乘前的路段宽度
    所有路段宽度乘以 1.5
    所有路段宽度乘以 1.5

    请注意,街道宽度更改速度更快,因为每次更改后不会刷新界面。

动画:生成建筑物

您可以使用 Python 脚本自动化生成或导出过程。 此工作流介绍了如何通过设置建筑物属性并导出结果模型集来生成建筑物动画。

生成建筑物

要生成建筑物,请执行以下操作:

  1. 打开 03_PythonScripting.cej 场景。

    当系统询问您“是否要重新生成这些模型?”时,请单击

  2. 选择建筑物。

    如果未生成建筑物,请选择地块并单击工具栏中的生成 生成 (Ctrl+G)

    使用未经修改的属性生成的建筑物

    规则文件包含用于更改建筑物维度的属性。 您将编写一个脚本来更改这些值并批量生成模型的不同版本,而非在检查器窗口中手动设置这些值。

动画脚本

要编写动画脚本,请执行以下操作:

  1. 创建一个名为 growBuilding 的新 Python 模块。
  2. 添加 growBuilding 函数以提供一个在两个范围内循环的时间线,并调用 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)

  3. 使用 doStep 函数以修改地块对象的高度和宽度属性:

    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)

  4. 添加 Generate 函数以生成建筑物:

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

  5. 在脚本的 main 部分中,调用 growBuilding 函数:

    if __name__ == '__main__':
       growBuilding()

  6. 保存该模块。
  7. F9 运行脚本以批量生成建筑物:

    模块示例

  8. 要批量导出模型,请添加 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)

  9. 替换 doStep() 函数中的 Generate 调用:

    #Generate(object)
    Export(i, object)

  10. 保存该模块。
  11. F9 以运行该脚本。

    随即将模型批量导出到工程中的 models 文件夹。

编写资产库规则文件

如果您拥有大量资产,则同时显示所有资产可能会有所帮助。 本部分向您展示了如何编写自动生成显示工程资产的 CGA 规则文件的 Python 脚本。

  1. 如果尚未打开 03_PythonScripting.cej 场景,请打开该场景。

    Python 脚本将编写具有以下结构的规则文件:

    Lot -->  Geometries Textures
    
    Geometries --> 
    	 Geometry(assetpath)
    	 Geometry(assetpath)
    	 ...
    
    Geometry(asset) --> i(asset)

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

  2. 创建一个名为 asset_lib 的新 Python 模块。
  3. 添加新函数 writeCGALib

    def writeCGAlib():

  4. 编写标题信息和 Lot 起始规则:

    cga = "/*Asset Library Loader : Generated by asset_lib.py*/\n version \"2023.0\"\n\n"
    
    cga += "Lot -->  Geometries Textures"

  5. 编写 Geometries 规则,并获取 asset 文件夹中的所有 .obj 文件;为各个资产准备 Geometry(assetpath) 规则调用:

    cga += "\n\nGeometries --> "
    for obj in ce.getObjectsFrom("/", ce.isFile, ce.withName("/Tutorial_10*/assets/*.obj")):   
        cga += "\n\t t(2,0,0)  Geometry(\""+obj+"\")"

  6. 为纹理资产编写类似的规则:

    cga+="\n\nTextures --> \n\ts(1,0,0) set(scope.ty,3) set(scope.tz,0) i(\"facades/xy-plane.obj\")"   
    for jpg in ce.getObjectsFrom("/", ce.isFile, ce.withName("/Tutorial_10*/assets/*.jpg")):
        cga += "\n\tt(2,0,0)  Texture(\""+jpg+"\")"

  7. 编写资产加载程序规则:

    cga += "\n\n Geometry(asset) --> s(1,0,0) i(asset) set(scope.ty,0) set(scope.tz,0)"
    
    cga += "\n\n Texture(asset) --> set(material.colormap, asset)"

  8. 打开 .cga 文件的文件句柄,然后编写 CGA 内容:

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

  9. 添加新的 assignAndGenerateLib() 函数:

    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)

    此函数可将生成的 .cga 文件分配给场景地块并生成模型。

  10. 最后,在主要子句中调用两个函数:

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

  11. 保存该模块。
  12. F9 键生成库模型。

    资产库

使用 startup.py 自动执行 CityEngine 任务

您可以使用 Python 自动执行大型任务或重复性任务。 例如,您可能想根据整个县的宗地信息自动生成模型。

要自动执行任务,请执行以下操作:

  1. 创建一个名为 automationJob 的新 Python 模块。
  2. 插入一个包含自动化作业任务的函数:

    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 Scene
        ce.waitForUIIdle()
        ce.closeFile()

    为了进行测试,请在 '__main__' 部分中添加对此函数的调用。 所提供的 fgdbToKml 示例从 fileGDB 导入形状以生成模型并将其写出到 KML:

    if __name__ == '__main__':
        fgdbToKml("data/CityData.gdb", "Footprints", 
                  "/ESRI.lib/rules/Buildings/Building_From_Footprint.cga", "Generate")
        pass

  3. 保存并按下 F9 键测试是否一切正常。 完成后,models 文件夹中包含 Footprints.kmz 文件。

    删除 Footprints.kmz 文件,因为您将在后面的步骤中重新创建它。

  4. 要自动化该过程,您需要将用于测试上述函数的所有函数参数放入一个配置文件中。

    教程工程包含位于 data 文件夹中的示例 jobConfig.cfg 文件:

    [config]
    pathFGDB=data/CityData.gdb
    layerName=Footprints
    ruleName=/ESRI.lib/rules/Buildings/Building_From_Footprint.cga
    startRule=Generate

  5. automationJob.py 脚本中的 fgdbToKml 函数下方添加 run(cfg)getCfgValue(cfg,name) 函数,以便使用存储在配置文件中的参数运行自动化作业:
    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)

    作为参考,您可以在 scripts 文件夹中打开 automationJob_dist.py 文件。

  6. 接下来,单击文件 > 切换工作空间 > 其他,创建一个单独的 CityEngine 自动化工作空间。

    将新工作空间命名为 C:\Automation Workspace

  7. 将教程工程中的 \scripts\startup.py 脚本复制到新的 C:\Automation Workspace\ 根目录中:

    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()

    启动 CityEngine 时会自动运行此 Python 脚本的 '__startup__' 部分中的命令。 第一个启动参数定义包含自动化作业的 CityEngine 工程。 它将链接到自动化工作空间。 第二个参数包含 config 文件。 它将被解析并以(名称,值)对列表的形式移交给自动化作业。 作业完成后,CityEngine 将安全地关闭。

  8. 打开命令行并在自动化工作空间中启动 CityEngine,然后移交作业定义和参数:

    <Path_to_CityEngine.exe> -data <Workspace_Folder> -vmargs 
    -DprojectFolder=<Project_Folder> -DconfigFilePath=<Configuration_FilePath>
     
    @ Example:
    > "C:\Program Files\Esri\CityEngine2023.0\CityEngine.exe" -data 
    "C:\Automation Workspace" -vmargs 
    -DprojectFolder="C:\CE_Workspace\Tutorial_10_Python_Scripting__2021_1" 
    -DconfigFilePath="C:\CE_Workspace\Tutorial_10_Python_Scripting__2021_1\data\jobConfig.cfg"

    作业完成后,models 文件夹中包含 Footprints.kmz 输出文件,您可以在 ArcGIS Earth 中打开它(如果需要):

    ArcGIS Earth 中的结果

注:
automationJob.py 文件包含一个基本作业。 使用 CityEngine Python 参考对其进行调整以适应您的需求。

在本教程中,您执行了以下操作:

  • 学习了 Python 控制台和编辑器的基本用法。
  • 探索了几个自动化 CityEngine 任务的示例。

有关详细信息,请参阅 CityEngine Python 参考

要继续学习 CityEngine,请参阅完整的 CityEngine 教程目录