I have a symbology set (unique values) with the default point symbols, based on a name field. I would like to change them all to be Picture Markers using a CIM definition revision, but I can't get to the "type" attribute of the symbols. As I get each level deeper in the CIM, there is no "type" but the ones listed below (bold means the next level into which I dig):
I would just like to do this process, in a loop, to get through all 150 symbols:
Many thanks!
Quite tricky but not impossible .. looks like only by using the CIM can stuff like this be done as you have investigated, methods on the arcpy Symbology class dont seem to have the power. Assuming that you want to change the CIM on the left to be like the CIM on the right in the attached image .. when doing anything like this it always helps to export before and after layer files done manually and investigate the CIM Json in a text editor
To get the marker type this seems to be deep down in the layer Renderer on, basically based on the type of CIM object there e.g. CIMVectorMarker, CIMPictureMarker cim.renderer.groups[n].classes[n].symbol.symbol.symbolLayers[n] (yes symbol dot symbol as you also found) The script below will swap out any existing symbols on point layers with the according picture marker tweak the pictureMarkers config for your own use. Note that setting URL on any new CIMPictureMarker as a path seems not to work, you have to convert the image to a base 64 encoded string 😕 obvs error handling could be improved etc etc but this script worked for myself
import base64
import arcpy
pictureMarkers = [
{
"layerName": "POINT_DATA1",
"valueImageMarkers" : [
{
"uniqueValue": "JCN20",
"path": "C:\\temp\\twenty.png",
"fmt": "image/png",
"size": 40
},
{
"uniqueValue": "JCN24",
"path": "C:\\temp\\twenty-four.png",
"fmt": "image/png",
"size": 40
}
]
}
]
proj = arcpy.mp.ArcGISProject("CURRENT")
map = proj.activeMap
for lx in map.listLayers() :
try :
if (not(lx.supports("DEFINITIONQUERY"))) : continue # datasets only thanks
# see if there is a config for this layer
pm_cfg = None
for cfg1 in pictureMarkers :
if (cfg1["layerName"] == lx.name) : # case sensitive
pm_cfg = cfg1
break
if (pm_cfg == None) : continue # no config for this layer
print(lx.name)
# get the CIM object
lx_cim = lx.getDefinition('V3')
chgd = False
grp_idx = 0
for gx in lx_cim.renderer.groups :
cls_idx = 0
for cx in gx.classes :
# check its as we expect? ..
##if (not(hasattr(cx.symbol, "symbol"))) : raise Exception("CIM hierarchy is not as expected")
## print ( type(cx.symbol.symbol.symbolLayers[0]) )
# class.symbol.symbol.symbolLayers exposes a list of the symbols, and types e.g. CIMVectorMarker, CIMPictureMarker
for sym in cx.symbol.symbol.symbolLayers :
print(f'Layer {lx.name} (Sym. Group {grp_idx}, Sym Class Index {cls_idx}) Marker Type ={type(sym)}') # print the current symbol types
# class.values[0].fieldValues exposes the actual unique values .. again as a list :^/
for value_set in cx.values :
print(f'Layer {lx.name} (Sym. Group {grp_idx}, Sym Class Index {cls_idx}) Unique Value="{value_set.fieldValues}"')
# ! assume one symbol and one unique value ..
# find a match :)
val_img_cfg = None
for value_set in cx.values :
for cfg_val in pm_cfg["valueImageMarkers"] :
# see if our config matches ANY value set on the renderer group class
if (cfg_val["uniqueValue"] in value_set.fieldValues) : # case sensitive
val_img_cfg = cfg_val
break # gotcha
if (not(val_img_cfg == None)) :
# create a picture marker
new_sym = arcpy.cim.CreateCIMObjectFromClassName("CIMPictureMarker", "V3")
new_sym.size = val_img_cfg['size']
# new_sym.url = img_cfg['path'] doesnt work :''(
# convert to an encoded string with the image data does
with open(val_img_cfg['path'], 'rb') as img_fdata :
data_s = None
data_s_enc = None
try :
data_s = img_fdata.read() # read the image data as binary
data_s_enc = base64.b64encode(data_s).decode('utf-8') # encode as base 64
new_sym.url = f"data:{val_img_cfg['fmt']};base64,{data_s_enc}"
finally :
# clean up ..
if (not(data_s == None)) : del data_s
if (not(data_s_enc == None)) : del data_s_enc
# assuming one so just replace all marker symbols for this class ..
cx.symbol.symbol.symbolLayers = [new_sym] # .. with the new one
chgd = True
cls_idx += 1
if (chgd) :
# ok make the change :)
lx.setDefinition(lx_cim)
print("cim updated")
except Exception as ex :
print(f"ERROR: failed on layer {lx.name} ({str(ex)})")