I am upgrading a script that heavily relied on the arcpy.mapping package to work with ArcGIS Pro. I have gotten everything working okay EXCEPT for updating the visible fields in the map layers. I have tried a bunch of different suggestions, but none that have worked. The simplest approach seems to be using Python cim access: https://pro.arcgis.com/en/pro-app/latest/arcpy/mapping/python-cim-access.htm
My code runs okay, but when I open my .aprx, none of the fields have been hidden that are supposed to be hidden. Anyone have suggestions?
visible_fields = ['list', 'of', 'fields']
aprx = arcpy.mp.ArcGISProject(aprx_path)
map_doc = aprx.listMaps(map_frame_name)[0]
for lyr in map_doc.listLayers():
if arcpy.Describe(lyr).dataType == "FeatureLayer":
print(lyr.name)
# Get the layer's CIM definition - using Pro 2.9
cim_lyr = lyr.getDefinition('V2')
# Make changes to field properties
for fd in cim_lyr.featureTable.fieldDescriptions:
if fd.fieldName not in visible_fields:
fd.visible = False # Do not display this field
# Push the changes back to the layer object
lyr.setDefinition(cim_lyr)
aprx.save()
You wrote everything correctly, I can't really see a better way to do it. Went ahead and just added some hinting and an overridden print function so you can monitor exactly what the code is doing depending on where you're running it from:
from builtins import print as _print
from typing import Literal
from _typeshed import SupportsWrite
from arcpy import AddMessage
from arcpy.mp import ArcGISProject
from arcpy._mp import Map, Layer
from arcpy.cim import CIMFeatureLayer, CIMFeatureTable, CIMFieldDescription
# Define a custom print function that also writes to the ArcGIS Pro messages
def print(
*values: object,
sep: str | None = " ",
end: str | None = "\n",
file: SupportsWrite[str] | None = None,
flush: Literal[False] = False,
) -> None:
_print(*values, sep=sep, end=end, file=file, flush=flush)
AddMessage(sep.join(map(str, values)) + end if end != '\n' else '')
def update_field_visibility(
aprx_path: str,
map_frame_name: str,
*,
visible_fields: tuple[str]= (),
use_alias: bool = False):
# Open the project and get the layers
aprx = ArcGISProject(aprx_path)
map_doc: Map = aprx.listMaps(map_frame_name)[0]
layers: list[Layer] = [layer for layer in map_doc.listLayers() if layer.isFeatureLayer]
# Update the field visibility for each layer
for layer in layers:
# Get CIM objects
cim_lyr: CIMFeatureLayer = layer.getDefinition('V2')
cim_feature_table: CIMFeatureTable = cim_lyr.featureTable
cim_field_descriptions: list[CIMFieldDescription] = cim_feature_table.fieldDescriptions
# Update field visibility
for fd in cim_field_descriptions:
name = (use_alias and fd.alias) or fd.fieldName
if name not in visible_fields and fd.visible:
fd.visible = False
print(f"Field {name} for {layer.name} is now hidden")
elif name in visible_fields and not fd.visible:
fd.visible = True
print(f"Field {name} for {layer.name} is now visible")
# Set the definition
layer.setDefinition(cim_lyr)
# Save the project
aprx.save()
if __name__ == "__main__":
aprx = r"Path\To\APRX"
map_frame_name = "Map"
visible_fields = ("Field1", "Field2", "Field3")
update_field_visibility(aprx, map_frame_name, visible_fields=visible_fields)
That should at least tell you what fields are getting switched on and off. I've used this exact method before to great success, so I'm not sure exactly what could be happening.
@MollyMoore is it possible that the field properties were never modified? In other words, if you add a new data source as a layer and try to use Python CIM Access to modified the field properties, it won't work. The reason is that because NO properties were previously altered. The layer JSON only persists changes. If no changes are made, then you won't see the properties. All you have to do is modify one property for one layer, save, and then you will see everything.
Here is a basic example that checks to see if the field descriptions properties exist in the CIM. If not, it uses a temporary makeFeatureLayer work around to force those properties into the CIM.
def updateCIMFields(l, cimLyr):
fList = ["SQKM", "POP2001", "Shape_Length", "Shape_Area"]
for fd in cimLyr.featureTable.fieldDescriptions:
if fd.fieldName in fList:
fd.numberFormat.roundingOption = "esriRoundNumberOfDecimals"
fd.numberFormat.roundingValue = 0
fd.numberFormat.zeroPad = True
l.setDefinition(cimLyr)
return cimLyr
p = arcpy.mp.ArcGISProject('current')
m = p.listMaps()[0]
lyr = m.listLayers('Provinces')[0]
lyr_cim = lyr.getDefinition('V3')
if len(lyr_cim.featureTable.fieldDescriptions) == 0: #NO CIM field info present
print('No CIM Field Info')
mkLyr = arcpy.management.MakeFeatureLayer(lyr)[0]
mkLyr_cim = mkLyr.getDefinition('V3')
mkLyr_cim = updateCIMFields(mkLyr, mkLyr_cim)
#Paste CIM information and remove temporary layer
lyr.pasteProperties(mkLyr, 'FIELD_PROPERTIES')
m.removeLayer(mkLyr)
else: #CIM field info present
print('CIM Field Info Pre-Exists')
lyr_cim = updateCIMFields(lyr, lyr_cim)
lyr.setDefinition(lyr_cim)
Jeff - arcpy.mp team