Python CIM access

The arcpy.mp module is a coarse-grained Python API that is designed to provide access to many common map automation tasks. It includes a diverse set of exposed classes, class properties and methods, and helper functions, but it does not provide access to all properties, settings, and capabilities available to ArcGIS Pro. One reason is to keep the API streamlined, simple, and manageable. Another reason is that ArcGIS Pro is being developed at such a rapid pace that the arcpy.mp API can't keep up. It may take a release or more before you have access through the publicly exposed API, if at all. Starting with ArcGIS Pro 2.4, Python developers have fine-grained access to the Cartographic Information Model (CIM) and can access many more settings, properties, and capabilities that are persisted in a project or an external document such as a layer file (.lyrx). The .NET SDK development community has had CIM access since ArcGIS Pro 1.1 and now it is available to the Python development community.

Precaución:

The following sections describe what you can do using the CIM. Before modifying the CIM, it is important to understand what you shouldn't do. For more information, see the section below titled Use caution when modifying the CIM.

What is the CIM and how to view it

The CIM is the Esri Cartographic Information Model. It is a map content specification used to document how the information that describes various project components are persisted when reading or writing a project. Regardless of its acronym, don't think of the CIM as limited to only cartographic settings. The capabilities exposed to these classes through the CIM extend well beyond, and the examples below demonstrate a tiny fraction of those extended capabilities.

The specification is represented as JSON and can be viewed when maps, layouts, layers, styles, and other elements are exported to *.*x files such as *.mapx, *.pagx, *.lyrx, *.stylx, and more. The easiest way to view and learn how the class properties are persisted in the JSON specification is to open one of these files in a JSON viewer such as Notepad++, for example. The structure within the JSON file parallels the CIM object model exposed to the arcpy.mp module, so it is necessary to view these files to get an understanding of where in the structure the property exists and what the property is called in the CIM because it may not match what it is called in the application.

Sugerencia:

Renaming one of these JSON files from *.pagx, for example, to *.json may appear easier to read in your editor because it may be set up to color different parts of the code depending on what it represents; otherwise, all the text may appear the same.

The following is a JSON representation of a modified and abbreviated list of root-level properties available to CIMLayerDocument (*.lyrx). Some of the items below, such as featureTable, can be expanded to expose more, indented settings.

{
  "type" : "CIMLayerDocument",
  "version" : "3.2.0",
  "layers" : [
    "CIMPATH=map/greatlakes.xml"
  ],
  "layerDefinitions" : [
    {
      "type" : "CIMFeatureLayer",
      "name" : "GreatLakes",
      "uRI" : "CIMPATH=map/greatlakes.xml",
      "useSourceMetadata" : true,
      "description" : "GreatLakes",
      "layerType" : "Operational",
      "showLegends" : true,
      "visibility" : true,
      "displayCacheType" : "Permanent",
      "maxDisplayCacheAge" : 5,
      "showPopups" : true,
      "serviceLayerID" : -1,
      "autoGenerateFeatureTemplates" : true,
      "featureElevationExpression" : "0",
      "featureTable" : {
      "htmlPopupEnabled" : true,
      "selectable" : true,
      "featureCacheType" : "Session",
      "scaleSymbols" : true,
      "snappable" : true

In the screenshot above, basic metadata is displayed at the top of the file that describes the document and layer. There are also many root-level layerDefinitions properties. The list will vary depending on the settings persisted with the object. It is important to understand that the properties exposed with your IDE code completion will show many more properties than what you would typically see in your JSON file. This is because the JSON file only stores current project settings and not all the possible default settings. This is done to improve performance and unnecessary bloat. One example that illustrates how the JSON file doesn't show all possibly properties is with Boolean values. Only the properties that are currently set to true appear in the JSON file. For example, your code completion in an IDE for a layer CIM object should show additional attributes such as expanded or showMapTips, but because they are currently set to false in the screenshot above, they don't appear in the above JSON file.

Modify a CIM definition

The primary access points are through the following arcpy.mp classes: Layer, Layout, Map, Report, and Table. Each of these classes can be saved to a JSON file format: .lyrx, .mapx, .pagx, and .rptx. The basic workflow is to return one of these object's CIM definition using the getDefinition() method, make the appropriate CIM API changes, and push the changes back to the same object using the setDefinition() method.

When you want to retrieve an object's CIM definition, you must specify a cim_version. Esri follows the semantic versioning specification. This means that at major releases, such as 2.0 or 3.0, breaking API changes are allowed. This gives Python script authors control over which version of the CIM to use when a script is run to avoid possible breaking changes. If you are authoring scripts for ArcGIS Pro 2.x, specify the cim_version to be 'V2'. If you are authoring scripts for ArcGIS Pro 3.x, specify the cim_version to be 'V3'. Scripts authored using cim_version 'V2' will continue to work in ArcGIS Pro 3.x but will use the 2x version of the CIM.

Once a CIM definition is returned, you can try to navigate its structure by viewing its code completion in an IDE, or you can use the technique mentioned above by viewing the JSON file directly. It is most helpful to read the JSON structure in a separate viewer while navigating the code completion in an IDE.

Precaución:
Do not edit these files directly. Always use the API to make changes.

Example 1: Basic Boolean, root-level properties

The following arcpy.mp script will modify a few root-level attributes for a CIM layer object named l_cim. The selectable attribute appears in the JSON file above because its current value is true, but it will be set to False in the Python script. The showMapTips and expanded attributes don't appear in the JSON file currently because their values are false, but they will be set to True. If you save the layer to another layer file (*.lyrx), the appropriate changes appear in the JSON file.

# Reference a project, map, and layer using arcpy.mp
p = arcpy.mp.ArcGISProject('current')
m = p.listMaps('Map')[0]
l = m.listLayers('GreatLakes')[0]

# Return the layer's CIM definition
l_cim = l.getDefinition('V3')

# Modify a few boolean properties
l_cim.showMapTips = True  #Turn on map tips for bubble tips to appear
l_cim.selectable = False  #Set the layer to not be selectable
l_cim.expanded = True     #Expand the Layer in the Contents pane

# Push the changes back to the layer object
l.setDefinition(l_cim)

# Save changes
p.save()

Nota:

At ArcGIS Pro 3.2, the number of classes that support the getDefinition and setDefinition methods were expanded by providing a number of constructor functions. This improves performance and makes it easier to modify a single layout element, for example, instead of having to get the entire layout definition, find the element, make the change, and push back the changes to the layout. The new classes that support the getDefinition and setDefinition methods are: BookmarkMapSeries, GraphicElement, GroupElement, LegendElement, MapFrame, MapSeries, MapSurroundElement, PictureElement, and TextElement.

Example 2: Modify field properties

The JSON and Python examples above are relatively straightforward because only root-level attributes were modified. The CIM object model can be nested based on the complexity of the definition of each CIM class and its nested set of capabilities. One CIM class can have zero to many members that are set to another CIM class, and those classes can have dependencies on other classes as well. Each dependent CIM class is indented within the JSON structure. Everything that's indented to the same level is available as a property to that CIM class. Viewing the JSON structure in a JSON editor allows you to expand and collapse that class hierarchy. Another advantage of working with the JSON file is that you can use search for properties and determine how to access the nested structure using the CIM API.

Sugerencia:

While navigating the CIM structure in an IDE, the code completion only goes four levels deep, so if you need to see the code completion beyond the fourth level, create an intermediate variable.

This example drills into the object model deeper than the root level. If the featureTable member in the JSON screenshot example above is expanded, you will see it has additional members: type (which represents the CIMClass name), displayField, editable, and fieldDescriptions. The fieldDescriptions property represents a list of CIMFieldDescription classes and depending on the field type, that class may have a numberFormat property that takes a CIMNumericFormat class as a value. Notice how nicely each level of the dependent classes are organized using indentation.

     "featureTable" : {
        "type" : "CIMFeatureTable",
        "displayField" : "NAME",
        "editable" : true,
        "fieldDescriptions" : [
          {
            "type" : "CIMFieldDescription",
            "alias" : "OBJECTID",
            "fieldName" : "OBJECTID",
            "numberFormat" : {
              "type" : "CIMNumericFormat",
              "alignmentOption" : "esriAlignRight",
              "alignmentWidth" : 0,
              "roundingOption" : "esriRoundNumberOfDecimals",
              "roundingValue" : 0
            },
            "readOnly" : true,
            "visible" : true,
            "searchMode" : "Exact"
          },

A common request is for the ability to modify a table's field alias or visibility, especially when creating a table on the fly using MakeFeatureLayer, for example. This cannot be done using the managed arcpy.mp API. The following script uses the CIM to access a feature layer's featureTable object and its fieldDescriptions object from which the alias and visible attributes can be set. The Python syntax follows the CIM object model where each dot exposes the next object's set of properties based on the indentation of the JSON structure.

# Reference a project, map, and layer using arcpy.mp
p = arcpy.mp.ArcGISProject('current') 
m = p.listMaps('Map')[0]
lyr = m.listLayers('GreatLakes')[0]

# Get the layer's CIM definition
cim_lyr = lyr.getDefinition('V3')

# Make changes to field properties
for fd in cim_lyr.featureTable.fieldDescriptions:
    if fd.fieldName == "OBJECTID":
        fd.visible = False            #Do not display this field
    if fd.fieldName == "Shape_Area":
        fd.alias = "Area (hectares)"  #Change field alias

# Push the changes back to the layer object
lyr.setDefinition(cim_lyr)

Nota:

The previous code example is perfect at demonstrating how to navigate the CIM object model but it also demonstrates the point made earlier that not all properties and classes are persisted in the CIM. Chances are that if you add a new feature class or table to a map, the CIM will not have any fieldDescriptions and therefore, you won't be able to modify the field properties. Again, the CIM only persists nondefault properties, so if no changes were previously made to the field descriptions, the fieldDescriptions property will return an empty list. With this scenario, there are two approaches. The first approach is to create the CIM classes needed, and that will be discussed in the Creating CIM classes section below. The second approach for this specific example is to force the CIM to be updated with those field descriptions by using a function called arcpy.AlterField. In most cases, there are no available workarounds, so you would be required to create the CIM classes.

The following script demonstrates how to force field descriptions into the CIM definition. If the layer does not have field descriptions, it calls the AlterField function on the first field, which forces the information into the CIM without making changes to the field. The script should show a different field count after running against a feature class.

p = arcpy.mp.ArcGISProject('current')
m = p.listMaps()[0]
lyr = m.listLayers('NE_State_Boundaries')[0]

lyr_cim = lyr.getDefinition('V3')
featTab = lyr_cim.featureTable
if len(featTab.fieldDescriptions) == 0: #No CIM field descriptions
  print(f'LYR desc count PRE mod: {len(featTab.fieldDescriptions)}')
  lyrFld = arcpy.ListFields(lyr)[0]             #First field
  arcpy.management.AlterField(lyr, lyrFld.name) #Force CIM update  

lyr_cim = lyr.getDefinition('V3')
featTab = lyr_cim.featureTable
print(f'LYR desc count POST mod: {len(featTab.fieldDescriptions)}')

Example 3: Modify layer symbology

This example demonstrates how further nested the CIM object model can be and also shows the advantage of CIM-level access. The managed arcpy.mp API has limited access to renderers and depth of properties. It can only modify simple properties and only for the default layer of a symbol. However, the CIM can access a symbol with multiple layers. The following screenshot shows settings in the application that are not available to the arcpy.mp API:

The Symbology pane for a polygon feature layer

Below is an edited and simplified JSON file that only displays the renderer information for a layer's symbology. The renderer, which is a CIMSimpleRenderer, has a symbol property that represents a CIMSymbolReference and has a symbol property which is a CIMPolygonSymbol and it has two symbolLayers: CIMSolidStroke and CIMSolidFill, each of which displays the properties that are available in the Symbology pane.

  "layerDefinitions" : [
    {
      "renderer" : {
        "type" : "CIMSimpleRenderer",
        "patch" : "Default",
        "symbol" : {
          "type" : "CIMSymbolReference",
          "symbol" : {
            "type" : "CIMPolygonSymbol",
            "symbolLayers" : [
              {
                "type" : "CIMSolidStroke",
                "effects" : [
                  {
                    "type" : "CIMGeometricEffectDashes",
                    "dashTemplate" : [ 5, 5],
                    "lineDashEnding" : "NoConstraint",
                    "controlPointEnding" : "NoConstraint"
                  }
                ],
                "enable" : true,
                "capStyle" : "Round",
                "joinStyle" : "Round",
                "lineStyle3D" : "Strip",
                "miterLimit" : 10,
                "width" : 3,
                "color" : {
                  "type" : "CIMRGBColor",
                  "values" : [0, 0, 0, 100]
                }
              },
              {
                "type" : "CIMSolidFill",
                "enable" : true,
                "color" : {
                  "type" : "CIMRGBColor",
                  "values" : [ 255, 127, 0, 100 ]
                }
              }

The following Python code uses CIM access to modify the layer's symbology. Both symbol layers are modified.

# Reference a project, map, and layer using arcpy.mp
p = arcpy.mp.ArcGISProject('current')
m = p.listMaps('Trail Routes')[0]
lyr = m.listLayers('Loops')[0]

# Return the layer's CIM definition
cim_lyr = lyr.getDefinition('V3')

# Modify the color, width and dash template for the SolidStroke layer
symLvl1 = cim_lyr.renderer.symbol.symbol.symbolLayers[0]
symLvl1.color.values = [250, 250, 40, 50]
symLvl1.width = 8
ef1 = symLvl1.effects[0]    #Note, deeper indentation 
ef1.dashTemplate = [20, 30] #Only works if there is an existing dash template

# Modify the color/transparency for the SolidFill layer
symLvl2 = cim_lyr.renderer.symbol.symbol.symbolLayers[1]
symLvl2.color.values = [140, 70, 20, 20]

# Push the changes back to the layer object
lyr.setDefinition(cim_lyr)

Use caution when modifying the CIM

The CIM exposes many useful capabilities, but things could go wrong if you are not careful. The application and managed APIs are designed to block you from making changes that can place the application in a compromised state. The CIM exposes everything, so it's possible to make conflicting changes that may not be possible in the application. It is important that you test scripts that modify the CIM. Ensure that the application doesn't respond oddly after making such changes. Limit yourself to changing settings that don't have dependencies on other settings.

Example 1: You can't change the spatial reference

You may think that changing a map's spatial reference is straightforward, since the JSON description includes a tag called spatialReference.

  "mapDefinition" : {
    "type" : "CIMMap",
    "name" : "Map",
    "uRI" : "CIMPATH=map/map.xml",
    "metadataURI" : "CIMPATH=Metadata/a7afc904584d1037910b2cfe65fe94f8.xml",
    "useSourceMetadata" : true,
    "illumination" : {
    "layers" : [
    "standaloneTables" : [
    "defaultViewingMode" : "Map",
    "mapType" : "Map",
    "datumTransforms" : [
    "defaultExtent" : {
    "elevationSurfaces" : [
    "spatialReference" : {
      "wkid" : 4326,
      "latestWkid" : 4326
    },

If you attempt to modify only the map's wkid and latestWkid properties, you won't get the expected results. This is because there are many other parts of the application that are associated with the spatial reference, and many changes would need to be made to the map's CIM to get the change to work properly. For example, a change to the spatial reference also affects datum transformations, a number of extents, clip geometries, and so on. This type of operation should be made in the application or with a managed API, where all the appropriate changes will be made. This is why we will continue to extend the arcpy.mp API. For this scenario, rather than using the CIM, you should use the spatialReference property on the Map class.

Example 2: Don't get the application into an odd state

In this example, the expression engine associated with a layer's labeling properties is modified in the CIM. In the screenshot below, the expression engine is changed from Arcade, the default, to Python. Below are graphics of what it looks like in the UI and how the JSON portion appears.

The Label Class expression pane for a feature layer

      "labelClasses" : [
        {
          "type" : "CIMLabelClass",
          "expression" : "$feature.NAME",
          "expressionEngine" : "Arcade",
          "featuresToLabel" : "AllVisibleFeatures",
          "maplexLabelPlacementProperties" : {
          "name" : "Class 1",
          "priority" : -1,
          "standardLabelPlacementProperties" : {
          "textSymbol" : {
          "useCodedValue" : true,
          "visibility" : true,
          "iD" : -1
        }

The following is a Python script that changes only the expressionEngine and not the expression. This causes the application to behave erratically. For example, after running the code below, when you review the label properties for the layer, the expressionEngine is properly set to Python, but the expression still appears in Arcade format. In the user interface, when you change the expression engine back to Arcade, the expression displays in Python format, the opposite of what it should be. To avoid this, it is important that you update both the expressionEngine and expression properties.

# Update the label expression engine from Arcade, the default, to Python.
# You must also update the expression otherwise the UI won't behave correctly after.

p = arcpy.mp.ArcGISProject('current')
m = p.listMaps('Map')[0]
l = m.listLayers()[0]

l_cim = l.getDefinition('V3')

lc = l_cim.labelClasses[0]
lc.expressionEngine = 'Python'    #From 'Arcade'
lc.expression = '[STATE_NAME]'    #From '$feature.STATE_NAME'

l.setDefinition(l_cim)

Tips for modifying CIM properties

Sometimes the property label in the JSON file isn't easy to find because it isn't intuitive, it doesn't match the application, or it can be nested in the object model. One trick to approach this is to set a value to something truly unique, and search on that value in the JSON file. For example, you set a width of a layout element to 0.7777 or an RGB color to 111.

You may see in the JSON file a value that represents an enumeration constant that you want to change, but you don't know what the new, proper value should be. A solution is to set the correct value in the application, and save to a JSON export file and evaluate the updated value.

Another tip is to create a pre and post modified JSON file and compare the changes using an application such as WinMerge, DeltaJSON, JSON Diff, or others. This is important when you create CIM classes, which is discussed further in the next section.

The ArcGIS.Core.CIM Namespace .NET SDK API Reference help topic provides a list of CIM objects and documentation for each class member.

Creating CIM classes

In versions earlier than 2.5, Python CIM API only allowed you to change existing properties. Starting at version 2.5, there is a new technique for creating CIM classes that can be used to expand the capabilities exposed in an object's CIM definition. The name of the function is called CreateCIMObjectFromClassName and it is located in the arcpy.cim module. The cim_class_name parameter is the name of the CIM class the way it appears in the type property, and the cim_version parameter is used the same way as with the getDefiniton function defined in the sections above. CreateCIMObjectFromClassName will return an object that contains the appropriate members but it will not automatically create additional object dependencies. For example, if the class created has a member that requires another class as its value, you will need to run the function again to also generate that class. That process must be carried out for all dependent CIM classes and subclasses and can require a fair amount of effort. This will be addressed in later examples but you can start with a straightforward scenario in which only one object needs to be created to accomplish a solution.

Example 1: Single Class - RGB Color

If you insert a new, default map and return the map's CIM backgroundColor, the value will be a NoneType because the value is not defined in the CIM. By default, a new map's background color is set to No color". You won't even see that member defined in the JSON file. A straightforward way to determine what needs to be done is to author the map with the background color and save the before and after changes as map files (.mapx) and compare the differences. The graphic below, on the left, represents a screenshot of the JSON before changes were made, and the after changes on the right. They are being viewed by a text comparison application called WinMerge. Notice, on the right, that the backgroundColor member has been inserted between the mapType and bookmarks properties. To set a map's backgroundColor, you need to create a color object—in this example, a CIMRGBColor object. The color could have been defined using other color models and the CIM object type, which represents the class type, would be different.

A screenshot of the before and after results of background color being inserted into a JSON file

The following code will create a CIMRGBColor class that is used to set the background color for a map. Note, you may need to turn off your basemap or other layers to see the changes.

p = arcpy.mp.ArcGISProject('current')
m = p.listMaps()[0]
m_cim = m.getDefinition('V3')        #Get the map's CIM definition

#Check to see if a background color exists, if not create a color
if m_cim.backgroundColor is None:
  RGBColor = arcpy.cim.CreateCIMObjectFromClassName('CIMRGBColor', 'V3')
  RGBColor.values = [115, 178, 255, 100]
  m_cim.backgroundColor = RGBColor   #Set the property to the new object
m.setDefinition(m_cim)               #Set the map's CIM definition

Example 2: Single Class - Spatial Map Series

Creating a spatial map series using the CIM is almost as straightforward as creating an RGB color. A spatial map series is also a single object but with more properties. The graphic below represents a screenshot of how a spatial map series is persisted in a JSON file. The indexLayerURI is a concept that is unique to the CIM that is not exposed as a setting in the user interface. The URI is a unique identifier for an object persisted in the CIM to assure reference uniqueness. In the case of the layer, the URI won't change even if you change the layer name. The only way to get the indexLayerURI value is through the layer's CIM definition.

A screenshot of the results of a spatial map series being inserted into a JSON file

The following code will create a CIMSpatialMapSeries class that is used to author a new spatial map series for a layout. The example includes the extra lines of code to get the layer's URI value. Note, the get/setDefinition functions need to be called a second time to ensure the Map Series Pages tab refreshes in the Contents pane.

p = arcpy.mp.ArcGISProject('current')
m = p.listMaps('GreatLakes')[0]
l = m.listLayers('GreatLakes')[0]

l_cim = l.getDefinition('V3')         #Get layer's CIM / Layer URI
lURI = l_cim.uRI                      #Needed to specific the index layer 

lyt = p.listLayouts('GreatLakes')[0]
lyt_cim = lyt.getDefinition('V3')     #Get Layout's CIM definition

#Create CIM Spatial Map Series Object and populate its properties
ms = arcpy.cim.CreateCIMObjectFromClassName('CIMSpatialMapSeries', 'V3')
ms.enabled = True
ms.mapFrameName = "Great Lakes MF"
ms.startingPageNumber = 1
ms.currentPageID = 2
ms.indexLayerURI = lURI               #Index layer URI from Layer's CIM 
ms.nameField = "NAME"
ms.sortField = "NAME"
ms.sortAscending = True
ms.scaleRounding = 1000
ms.extentOptions = "BestFit"
ms.marginType = "Percent"
ms.margin = 10

lyt_cim.mapSeries = ms                #Set new map series to layout
lyt.setDefinition(lyt_cim)            #Set the Layout's CIM definition

#Force a refresh of the layout and its associated panes
lyt_cim = lyt.getDefinition('V3')
lyt.setDefinition(lyt_cim)

Example 3: One Dependent Class - Bookmark Map Series

The previous two examples showed CIM classes with simple properties. As stated earlier in this topic, there will be times when a newly created CIM object has property values that are dependent on other CIM objects. The CreateCIMObjectFromClassName function does not automatically create all dependent subclasses, and it is up to you to use the same method to create those dependent objects as well. The CIMBookmarkMapSeries class is an example. It has a property called pages, which is a collection of individual CIMBookmarkMapSeriesPage objects. The following is a screenshot of how a Bookmark Map Series is persisted in a JSON file. You should notice that the CIMBookmarkMapSeriesPage class is nested inside of the CIMBookmarkMapSeries class. Each page is defined by its bookmarkName and each bookmark has a mapURI that is a unique identifier for the map with which the bookmark is associated.

A screenshot of the results of a bookmark map series being inserted into a JSON file

The following code will create a CIMBookmarkMapSeries class and multiple CIMBookmarkMapSeriesPage classes that are used to author a new Bookmark Map Series for a layout. A bookmark map series requires a reference to a map's URI and similar to a layer's URI, you can only get that value from the map's CIM definition.

p = arcpy.mp.ArcGISProject('current')
m = p.listMaps('Map')[0]

#Get map URI from map CIM
m_cim = m.getDefinition('V3')           
mURI = m_cim.uRI

#Get Layout CIM
lyt = p.listLayouts('No Map Series')[0] 
lyt_cim = lyt.getDefinition('V3')

#Create the CIMBookmarkMapSeriesPage(s) necessary for creating the CIMBookmarkMapSeries
#Iterate through each bookmark in the order you want them added to the map series
bkmkNames = ["Northeast", "Southeast", "Northwest",]  
pageList = []
for bkmk in bkmkNames:
  bkmkPage = arcpy.cim.CreateCIMObjectFromClassName('CIMBookmarkMapSeriesPage', 'V3')
  bkmkPage.bookmarkName = bkmk
  bkmkPage.mapURI = mURI                
  pageList.append(bkmkPage)             

#Create a Bookmark Object and populate
bmMS = arcpy.cim.CreateCIMObjectFromClassName('CIMBookmarkMapSeries', 'V3')
bmMS.enabled = True
bmMS.mapFrameName = "Map Frame"
bmMS.startingPageNumber = 1
bmMS.currentPageID = 0
bmMS.extentOptions = "BestFit"
bmMS.marginType = "Percent"
bmMS.pages = pageList                   

#Set map series to layout and set the layout CIM definition
lyt_cim.mapSeries = bmMS                
lyt.setDefinition(lyt_cim)              

#Force a refresh of the layout and its associated panes
lyt_cim = lyt.getDefinition('V3')
lyt.setDefinition(lyt_cim)

Example 4: Many Dependent Classes - Create a Polygon Element on a Layout

Depending on the object you are trying to create, the number of nested, dependent objects can get complex. When possible, it is recommended that you use the managed API before trying to create objects using the CIM. The next code example is a typical scenario. You will see that creating something as simple as a polygon element on a layout has many object dependencies. Again, the most straightforward approach is to create a simple layout with a single polygon element and export the layout to a .pagx file and review the JSON structure. Another approach to tackling these more complex scenarios is to build the objects in reverse order. Start with the most indented, dependent subclasses and work your way to the final resulting object. The sample code below uses that pattern.

The following code will ultimately create a polygon CIMGraphicElement on a layout. It uses the CreateCIMObjectFromClassName multiple times for all the dependent CIM classes needed to construct a polygon graphic element.

# A simplified JSON structure outlining the objects needed to be generated for
# creating a polygon graphic element on a layout.
#
# CIMGraphicElement
#   CIMPolygonGraphic
#     CIMSymbolReference
#       CIMPolygonSymbol
#         CIMSolidStroke
#           CIMRGBColor
#         CIMSolidFill
#           CIMRGBColor

#Reference layout and its CIM definition
p = arcpy.mp.ArcGISProject('current')
lyt = p.listLayouts()[0]
lyt_cim = lyt.getDefinition('V3')       

#CIMSolidStoke/CIMRGBColor 
strokeRGBColor = arcpy.cim.CreateCIMObjectFromClassName('CIMRGBColor', 'V3')
strokeRGBColor.values = [0, 0, 0, 100]

symLyr1 = arcpy.cim.CreateCIMObjectFromClassName('CIMSolidStroke', 'V3')
symLyr1.capStyle = "Round"
symLyr1.joinStyle = "Round"
symLyr1.width = 1
symLyr1.color = strokeRGBColor

#CIMSolidFill/CIMRGBColor
fillRGBColor = arcpy.cim.CreateCIMObjectFromClassName('CIMRGBColor', 'V3')
fillRGBColor.values = [130, 130, 130, 100] 

symLyr2 = arcpy.cim.CreateCIMObjectFromClassName('CIMSolidFill', 'V3')
symLyr2.color = fillRGBColor

#CIMPolygonSymbol
polySym = arcpy.cim.CreateCIMObjectFromClassName('CIMPolygonSymbol', 'V3')
polySym.symbolLayers = [symLyr1, symLyr2]

#CIMSymbolReference
symRef = arcpy.cim.CreateCIMObjectFromClassName('CIMSymbolReference', 'V3')
symRef.symbol = polySym

#CIMPolygonGraphic
polyGraphic = arcpy.cim.CreateCIMObjectFromClassName('CIMPolygonGraphic', 'V3')
polyGraphic.symbol = symRef
polyGraphic.blendingMode = "Alpha"
polyGraphic.placement = "Unspecified"
polyGraphic.polygon =  {"hasZ": True, "rings": [[[5, 6, None], [1, 6, None], [1, 9, None], [5, 9, None], [5, 6, None]]]}

#CIMGraphicElement
graphicElm = arcpy.cim.CreateCIMObjectFromClassName('CIMGraphicElement', 'V3')
graphicElm.anchor = "BottomLeftCorner"
graphicElm.name = "New Rectangle Graphic"
graphicElm.visible = True
graphicElm.rotationCenter = {"x" : 1, "y": 6}
graphicElm.graphic = polyGraphic

#Add element to EMPTY layout element list and set CIM definition
lyt_cim.elements = [graphicElm]
lyt.setDefinition(lyt_cim)

Additional resources and sample scripts

For a detailed CIM specification and GIT repository, see Cartographic Information Model (cim-spec).

For a collection of 30 packaged sample scripts that use Python CIM access in a variety of ways, see ArcGIS Pro Python CIM Samples.

Python CIM access can also be useful in updating data source workflows. For more CIM examples, see the Updating and fixing data sources help topic.