diff --git a/FeatureRenderer.pyt b/FeatureRenderer.pyt index 4f288f3..e25d464 100644 --- a/FeatureRenderer.pyt +++ b/FeatureRenderer.pyt @@ -2,6 +2,8 @@ import arcpy # type: ignore +DEFAULT_LINE_WIDTH = 0.7 + class Toolbox: def __init__(self): @@ -45,7 +47,7 @@ class FeatureRenderer: table_param = arcpy.Parameter( displayName="Select Legend Table", name="table", - datatype="DETable", + datatype="GPTableView", parameterType="Required", direction="Input", ) @@ -74,9 +76,9 @@ class FeatureRenderer: field_param_2.parameterDependencies = [table_param.name] - # Define parameter for selecting a stroke symbol code field + # Define parameter for selecting a line symbol code field field_param_3 = arcpy.Parameter( - displayName="Select Stroke Symbol Code Field", + displayName="Select Line Symbol Code Field", name="line_symbol", datatype="Field", parameterType="Required", @@ -169,18 +171,31 @@ class FeatureRenderer: heading_field.parameterDependencies = [table_param.name] - # Define Boolean parameter + # Define parameter to choose to draw outlines draw_outlines = arcpy.Parameter( displayName="Draw outlines", name="draw_outlines", datatype="GPBoolean", parameterType="Required", direction="Input", + category="Define Outlines", ) # Default value draw_outlines.value = True + # Define parameter for outline field + outline_field = arcpy.Parameter( + name="outline_field", + displayName="Select Outline Field", + datatype="Field", + direction="Input", + parameterType="Optional", + category="Define Outlines", + ) + + outline_field.parameterDependencies = [table_param.name] + # Return parameter definitions as a list return [ style_files, @@ -195,6 +210,7 @@ class FeatureRenderer: label_field_2, heading_field, draw_outlines, + outline_field, ] def isLicensed(self): @@ -223,6 +239,7 @@ class FeatureRenderer: label_field_2 = parameters[9].valueAsText heading_field = parameters[10].valueAsText draw_outlines = parameters[11].value + outline_field = parameters[12].valueAsText # Retrieve currently active map active_map = project.activeMap @@ -296,9 +313,10 @@ class FeatureRenderer: for row in search_cursor: custom_labels[row[0]] = f"{row[1]}{label_delimieter}{row[2]}" + # Retrieve headings + headings = {} if heading_field: labels = [primary_key_field, heading_field] - headings = {} with arcpy.da.SearchCursor( table, labels, @@ -306,6 +324,16 @@ class FeatureRenderer: for row in search_cursor: headings[str(row[0])] = row[1] + # Retrieve outline color codes + outline_codes = {} + if outline_field: + with arcpy.da.SearchCursor( + table, + [primary_key_field, outline_field], + ) as search_cursor: + for row in search_cursor: + outline_codes[str(row[0])] = row[1] + # Start rendering process for layer in map_layers: if layer.name in layers_to_render: @@ -317,42 +345,6 @@ class FeatureRenderer: # Check if UniqueValueRenderer is defined if sym.renderer == "UniqueValueRenderer": - - # Regroup items according to heading field - if heading_field: - new_item_groups = {} - old_item_groups = {} - - for group in sym.renderer.groups: - old_item_groups[group.heading] = group.items - - for group in sym.renderer.groups: - for item in group.items: - if item.values[0][0] != "": - leg_id = str(item.values[0][0]) - else: - leg_id = None - - if leg_id in headings: - heading = headings[leg_id] - if not heading: - heading = "Other" - else: - heading = "Other" - - if heading in new_item_groups: - new_item_groups[heading].append(item) - else: - new_item_groups[heading] = [item] - - # Remove old item groups - sym.renderer.removeValues(old_item_groups) - layer.symbology = sym - - # Add new item groups - sym.renderer.addValues(new_item_groups) - layer.symbology = sym - # Apply symbols from gallery for group in sym.renderer.groups: for item in group.items: @@ -376,11 +368,19 @@ class FeatureRenderer: symbols_from_gallery = ( item.symbol.listSymbolsFromGallery(symbol_key) ) + symbol_found = False for symbol_from_gallery in symbols_from_gallery: if symbol_from_gallery.name == symbol_key: + symbol_found = True item.symbol = symbol_from_gallery break + # Print message if symbol could not be found + if not symbol_found: + messages.AddMessage( + f"Could not find symbol in gallery {symbol_key}" + ) + # Add user defined labels if add_labels: if leg_id in custom_labels: @@ -392,39 +392,98 @@ class FeatureRenderer: cim_lyr = layer.getDefinition("V3") # Retrieve stroke symbol properties - stroke_symbol_props = {"color": {}, "width": 0} - if draw_outlines: - for group in cim_lyr.renderer.groups: - for unique_value_class in group.classes: - for ( - symbol_layer - ) in unique_value_class.symbol.symbol.symbolLayers: - if isinstance( - symbol_layer, - arcpy.cim.CIMSymbols.CIMSolidStroke, - ): - stroke_symbol_props["color"] = ( - symbol_layer.color + stroke_symbol_props = {} + for group in cim_lyr.renderer.groups: + for unique_value_class in group.classes: + if unique_value_class.values[0].fieldValues[0] != "": + leg_id = str( + unique_value_class.values[0].fieldValues[0] + ) + else: + leg_id = None + + stroke_symbol_props[leg_id] = { + "color": None, + "width": None, + } + + for ( + symbol_layer + ) in unique_value_class.symbol.symbol.symbolLayers: + if isinstance( + symbol_layer, + arcpy.cim.CIMSymbols.CIMSolidStroke, + ): + if leg_id in outline_codes: + # Set user defined outline color + outline_code = outline_codes[leg_id] + color_value = get_symbol_color(outline_code) + color = arcpy.cim.CreateCIMObjectFromClassName( + "CIMCMYKColor", "V3" ) - stroke_symbol_props["width"] = ( - symbol_layer.width - ) - break + color.values = color_value["CMYK"] + stroke_symbol_props[leg_id]["color"] = color + else: + if symbol_layer.color: + # Set color as it was before + stroke_symbol_props[leg_id][ + "color" + ] = symbol_layer.color + else: + # Set default color + color = ( + arcpy.cim.CreateCIMObjectFromClassName( + "CIMCMYKColor", "V3" + ) + ) + color.values = [40, 40, 40, 10, 100] + stroke_symbol_props[leg_id]["color"] = color - if stroke_symbol_props["color"]: - break + if draw_outlines: + if symbol_layer.width: + # Set width as it was before + stroke_symbol_props[leg_id][ + "width" + ] = symbol_layer.width + else: + # Set default width + stroke_symbol_props[leg_id][ + "width" + ] = DEFAULT_LINE_WIDTH + else: + stroke_symbol_props[leg_id]["width"] = 0 - # Set default stroke width and color if unset - if draw_outlines and stroke_symbol_props["width"] == 0: - stroke_symbol_props["width"] = 0.7 + break - if not stroke_symbol_props["color"]: - color = arcpy.cim.CreateCIMObjectFromClassName( - "CIMCMYKColor", "V3" - ) - color.values = [40, 40, 40, 10, 100] - stroke_symbol_props["color"] = color + # In case the layer did not have a stroke symbol layer or outline color + if not stroke_symbol_props[leg_id]["color"]: + if leg_id in outline_codes: + messages.AddMessage("Add color") + # Set user defined outline color + outline_code = outline_codes[leg_id] + color_value = get_symbol_color(outline_code) + color = arcpy.cim.CreateCIMObjectFromClassName( + "CIMCMYKColor", "V3" + ) + color.values = color_value["CMYK"] + stroke_symbol_props[leg_id]["color"] = color + else: + # Set default color + color = arcpy.cim.CreateCIMObjectFromClassName( + "CIMCMYKColor", "V3" + ) + color.values = [40, 40, 40, 10, 100] + stroke_symbol_props[leg_id]["color"] = color + if not stroke_symbol_props[leg_id]["width"]: + if draw_outlines: + stroke_symbol_props[leg_id][ + "width" + ] = DEFAULT_LINE_WIDTH + else: + stroke_symbol_props[leg_id]["width"] = 0 + + new_groups = [] for group in cim_lyr.renderer.groups: for unique_value_class in group.classes: @@ -471,10 +530,10 @@ class FeatureRenderer: arcpy.cim.CIMSymbols.CIMSolidStroke, ): symbol_layer.width = ( - stroke_symbol_props["width"] + stroke_symbol_props[leg_id]["width"] ) symbol_layer.color = ( - stroke_symbol_props["color"] + stroke_symbol_props[leg_id]["color"] ) elif layer_shape_type == "Polyline": @@ -495,7 +554,7 @@ class FeatureRenderer: # Draw symbol background colors if not has_color and color_value: if layer_shape_type == "Polygon": - # Add fill and stroke symbol layers + # Add fill symbol layer fill_symbol = arcpy.cim.CIMSolidFill() update_color(fill_symbol, color_value) @@ -504,17 +563,14 @@ class FeatureRenderer: ) # Add stroke symbol layer - if ( - draw_outlines - and stroke_symbol_props["color"] - ): + if stroke_symbol_props[leg_id]: stroke_symbol = arcpy.cim.CIMSolidStroke() stroke_symbol.color = stroke_symbol_props[ - "color" - ] + leg_id + ]["color"] stroke_symbol.width = stroke_symbol_props[ - "width" - ] + leg_id + ]["width"] unique_value_class.symbol.symbol.symbolLayers.append( stroke_symbol ) @@ -533,6 +589,36 @@ class FeatureRenderer: marker_symbol ) + # Regroup items + if heading_field: + if leg_id in headings: + heading = headings[leg_id] + if not heading: + heading = "Other" + else: + heading = "Other" + + matched_group = next( + ( + group + for group in new_groups + if group.heading == heading + ), + None, + ) + if matched_group: + matched_group.classes.append(unique_value_class) + else: + new_group = arcpy.cim.CreateCIMObjectFromClassName( + "CIMUniqueValueGroup", "V3" + ) + new_group.heading = heading + new_group.classes.append(unique_value_class) + new_groups.append(new_group) + + if heading_field: + cim_lyr.renderer.groups = new_groups + # Push changes back to layer object layer.setDefinition(cim_lyr) @@ -633,7 +719,10 @@ def get_percentage_from_letter(letter): elif letter == "X": return 0 else: - return int(letter) * 10 + try: + return int(letter) * 10 + except ValueError as _: + raise ValueError(f"Execution aborted: unknown color code {letter}") def get_symbol_color(color_string):