diff --git a/FeatureRenderer.pyt b/FeatureRenderer.pyt index c1f4330..cdde665 100644 --- a/FeatureRenderer.pyt +++ b/FeatureRenderer.pyt @@ -2,10 +2,7 @@ import arcpy # type: ignore -# Define constants -# 1 point = 1/72 inch -# 1 inch = 25.4 mm -DEFAULT_LINE_WIDTH = 0.23 +DEFAULT_LINE_WIDTH = 0.7 DEFAULT_COLOR = [ 0, 0, @@ -65,7 +62,7 @@ class FeatureRenderer: # Define parameter for selecting a primary key field_param_1 = arcpy.Parameter( - displayName="Select Primary Key Field", + displayName="Select Primary Key Field (LEG_ID)", name="primary_key", datatype="Field", parameterType="Required", @@ -207,18 +204,6 @@ class FeatureRenderer: outline_field.parameterDependencies = [table_param.name] - # Define parameter for outline width - outline_width = arcpy.Parameter( - name="outline_width", - displayName="Set outline width in pt", - datatype="GPDouble", - direction="Input", - parameterType="Optional", - category="Define Outlines", - ) - - outline_field.parameterDependencies = [table_param.name] - # Return parameter definitions as a list return [ style_files, @@ -234,7 +219,6 @@ class FeatureRenderer: heading_field, draw_outlines, outline_field, - outline_width, ] def isLicensed(self): @@ -264,7 +248,6 @@ class FeatureRenderer: heading_field = parameters[10].valueAsText draw_outlines = parameters[11].value outline_field = parameters[12].valueAsText - outline_width = parameters[13].value # Retrieve currently active map active_map = project.activeMap @@ -281,20 +264,15 @@ class FeatureRenderer: layers_to_render = [] for input_layer in input_layers_list: present = False - - # Test if layer is a group layer and extract layer name from path - if "\\" in input_layer: - input_layer = input_layer.split("\\")[-1] - for layer in map_layers: if layer.name == input_layer: - layers_to_render.append(layer) + layers_to_render.append(layer.name) present = True break if not present: added_layer = active_map.addDataFromPath(input_layer) - layers_to_render.append(added_layer) + layers_to_render.append(added_layer.name) # Retrieve styles that are currently in the project project_styles = project.styles @@ -327,49 +305,32 @@ class FeatureRenderer: # Retrieve label texts for custom labels add_labels = False - custom_labels = {} if label_field_1 or label_field_2: add_labels = True - fields = [primary_key_field] - if label_field_1 and label_field_1 not in fields: - fields.append(label_field_1) - if label_field_2 and label_field_2 not in fields: - fields.append(label_field_2) + custom_labels = {} + labels = [primary_key_field] + if label_field_1: + labels.append(label_field_1) + if label_field_2: + labels.append(label_field_2) with arcpy.da.SearchCursor( table, - fields, + labels, ) as search_cursor: for row in search_cursor: - id = str(row[0]) - if id not in custom_labels: - if len(fields) == 3: - custom_labels[id] = f"{row[1]}{label_delimieter}{row[2]}" - elif len(fields) == 2: - if label_field_1 == primary_key_field: - custom_labels[id] = ( - f"{row[0]}{label_delimieter}{row[1]}" - ) - else: - custom_labels[id] = f"{row[1]}" - elif len(fields) == 1: - custom_labels[id] = f"{row[0]}" + custom_labels[row[0]] = f"{row[1]}{label_delimieter}{row[2]}" # Retrieve headings headings = {} if heading_field: - fields = [primary_key_field] - if heading_field not in fields: - fields.append(heading_field) + labels = [primary_key_field, heading_field] with arcpy.da.SearchCursor( table, - fields, + labels, ) as search_cursor: for row in search_cursor: - if len(fields) == 2: - headings[str(row[0])] = row[1] - elif len(fields) == 1: - headings[str(row[0])] = row[0] + headings[str(row[0])] = row[1] # Retrieve outline color codes outline_codes = {} @@ -382,95 +343,153 @@ class FeatureRenderer: outline_codes[str(row[0])] = row[1] # Start rendering process - for layer in layers_to_render: - messages.AddMessage(f"Rendering layer: {layer.name}") + for layer in map_layers: + if layer.name in layers_to_render: + messages.AddMessage(f"Rendering layer: {layer.name}") - sym = layer.symbology - layer_desc = arcpy.Describe(layer) - layer_shape_type = layer_desc.featureClass.shapeType + sym = layer.symbology + layer_desc = arcpy.Describe(layer) + layer_shape_type = layer_desc.featureClass.shapeType - # Check if UniqueValueRenderer is defined - if sym.renderer == "UniqueValueRenderer": - # Apply symbols from gallery - 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 symbol_codes: - symbol_code = get_symbol_code_for_shape( - layer_shape_type, symbol_codes[leg_id] - ) - - if symbol_code == "#": - continue - - code_components = get_code_components(symbol_code, leg_id) - symbol_key = code_components["symbol_key"] - - if symbol_key: - 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: - arcpy.AddWarning( - f"Could not find symbol in gallery {symbol_key}" - ) - else: - arcpy.AddWarning(f"{leg_id} is not in legend table") - - # Add user defined labels - if add_labels: - if leg_id in custom_labels: - item.label = custom_labels[leg_id] - - layer.symbology = sym - - # Retrieve CIM to add colors - cim_lyr = layer.getDefinition("V3") - - # Retrieve stroke symbol properties - stroke_symbol_props = {} - if layer_shape_type == "Polygon": - 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] - ) + # Check if UniqueValueRenderer is defined + if sym.renderer == "UniqueValueRenderer": + # Apply symbols from gallery + 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 - stroke_symbol_props[leg_id] = { - "color": None, - "width": None, - } + if leg_id in symbol_codes: + symbol_code = get_symbol_code_for_shape( + layer_shape_type, symbol_codes[leg_id] + ) - for ( - symbol_layer - ) in unique_value_class.symbol.symbol.symbolLayers: - if isinstance( - symbol_layer, - arcpy.cim.CIMSymbols.CIMSolidStroke, + if symbol_code == "#": + continue + + code_components = get_code_components(symbol_code) + symbol_key = code_components["symbol_key"] + + if symbol_key: + 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}" + ) + else: + messages.AddMessage( + f"{leg_id} is not in legend table yet" + ) + + # Add user defined labels + if add_labels: + if leg_id in custom_labels: + item.label = custom_labels[leg_id] + + layer.symbology = sym + + # Retrieve CIM to add colors + cim_lyr = layer.getDefinition("V3") + + # Retrieve stroke symbol properties + stroke_symbol_props = {} + if layer_shape_type == "Polygon": + 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] + + if ( + not outline_code + ) or outline_code == "#": + outline_code = CODE_BLK + + 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: + 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 = DEFAULT_COLOR + stroke_symbol_props[leg_id][ + "color" + ] = color + + 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 + + break + + # 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: # Set user defined outline color outline_code = outline_codes[leg_id] - if outline_code and outline_code != "#": - color_value = get_symbol_color(outline_code) - else: - color_value = {"CMYK": DEFAULT_COLOR} + if (not outline_code) or outline_code == "#": + outline_code = CODE_BLK + + color_value = get_symbol_color(outline_code) color = arcpy.cim.CreateCIMObjectFromClassName( "CIMCMYKColor", "V3" ) @@ -484,156 +503,130 @@ class FeatureRenderer: color.values = DEFAULT_COLOR stroke_symbol_props[leg_id]["color"] = color - break - - # In case the layer did not have a stroke symbol layer - if not stroke_symbol_props[leg_id]["color"]: - if leg_id in outline_codes: - # Set user defined outline color - outline_code = outline_codes[leg_id] - if outline_code and outline_code != "#": - color_value = get_symbol_color(outline_code) - else: - color_value = {"CMYK": DEFAULT_COLOR} - - 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 = DEFAULT_COLOR - stroke_symbol_props[leg_id]["color"] = color - - # Set outline width - if draw_outlines: - if outline_width: - stroke_symbol_props[leg_id]["width"] = outline_width - else: - if not stroke_symbol_props[leg_id]["width"]: + 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: + + if unique_value_class.values[0].fieldValues[0] != "": + leg_id = str( + unique_value_class.values[0].fieldValues[0] + ) else: - stroke_symbol_props[leg_id]["width"] = 0 + leg_id = None - new_groups = [] - for group in cim_lyr.renderer.groups: - for unique_value_class in group.classes: + if leg_id in symbol_codes: + symbol_code = get_symbol_code_for_shape( + layer_shape_type, symbol_codes[leg_id] + ) + if symbol_code == "#": + continue - if unique_value_class.values[0].fieldValues[0] != "": - leg_id = str(unique_value_class.values[0].fieldValues[0]) - else: - leg_id = None + code_components = get_code_components(symbol_code) + color_value = code_components["color"] + symbol_color_value = code_components["symbol_color"] - if leg_id in symbol_codes: - symbol_code = get_symbol_code_for_shape( - layer_shape_type, symbol_codes[leg_id] - ) - if symbol_code == "#": - continue + has_background_color = False + for ( + symbol_layer + ) in unique_value_class.symbol.symbol.symbolLayers: - code_components = get_code_components(symbol_code, leg_id) - color_value = code_components["color"] - symbol_color_value = code_components["symbol_color"] - - has_background_color = False - for ( - symbol_layer - ) in unique_value_class.symbol.symbol.symbolLayers: - if symbol_color_value: - update_symbol_layer_colors( - symbol_layer, symbol_color_value - ) - - if color_value: - # Update background colors - if layer_shape_type == "Polygon": - if isinstance( - symbol_layer, - arcpy.cim.CIMSymbols.CIMSolidFill, - ): - update_color(symbol_layer, color_value) - has_background_color = True - - if isinstance( - symbol_layer, - arcpy.cim.CIMSymbols.CIMSolidStroke, - ): - symbol_layer.width = stroke_symbol_props[ - leg_id - ]["width"] - symbol_layer.color = stroke_symbol_props[ - leg_id - ]["color"] - - # Draw symbol background colors - if not has_background_color and color_value: - if layer_shape_type == "Polygon": - # Add fill symbol layer - fill_symbol = arcpy.cim.CIMSolidFill() - update_color(fill_symbol, color_value) - - unique_value_class.symbol.symbol.symbolLayers.append( - fill_symbol - ) - - # Add stroke symbol layer - if stroke_symbol_props[leg_id]: - stroke_symbol = arcpy.cim.CIMSolidStroke() - stroke_symbol.color = stroke_symbol_props[ - leg_id - ]["color"] - stroke_symbol.width = stroke_symbol_props[ - leg_id - ]["width"] - - # Insert at the beginning such that it appears on top of solid fill layer - unique_value_class.symbol.symbol.symbolLayers.insert( - 0, stroke_symbol + if symbol_color_value: + update_symbol_layer_colors( + symbol_layer, symbol_color_value ) - # Regroup items - if heading_field: - if leg_id in headings: - heading = headings[leg_id] - if not heading: + if color_value: + # Update background colors + if layer_shape_type == "Polygon": + if isinstance( + symbol_layer, + arcpy.cim.CIMSymbols.CIMSolidFill, + ): + update_color(symbol_layer, color_value) + has_background_color = True + + if isinstance( + symbol_layer, + arcpy.cim.CIMSymbols.CIMSolidStroke, + ): + symbol_layer.width = ( + stroke_symbol_props[leg_id]["width"] + ) + symbol_layer.color = ( + stroke_symbol_props[leg_id]["color"] + ) + + # Draw symbol background colors + if not has_background_color and color_value: + if layer_shape_type == "Polygon": + # Add fill symbol layer + fill_symbol = arcpy.cim.CIMSolidFill() + update_color(fill_symbol, color_value) + + unique_value_class.symbol.symbol.symbolLayers.append( + fill_symbol + ) + + # Add stroke symbol layer + if stroke_symbol_props[leg_id]: + stroke_symbol = arcpy.cim.CIMSolidStroke() + stroke_symbol.color = stroke_symbol_props[ + leg_id + ]["color"] + stroke_symbol.width = stroke_symbol_props[ + leg_id + ]["width"] + + # Insert at the beginning such that it appears on top of solid fill layer + unique_value_class.symbol.symbol.symbolLayers.insert( + 0, stroke_symbol + ) + + # Regroup items + if heading_field: + if leg_id in headings: + heading = headings[leg_id] + if not heading: + heading = "Other" + else: 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" + matched_group = next( + ( + group + for group in new_groups + if group.heading == heading + ), + None, ) - new_group.heading = heading - new_group.classes.append(unique_value_class) - new_groups.append(new_group) + 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 + if heading_field: + cim_lyr.renderer.groups = new_groups - # Push changes back to layer object - layer.setDefinition(cim_lyr) + # Push changes back to layer object + layer.setDefinition(cim_lyr) - else: - raise ValueError( - f"Execution aborted: please apply a Unique Value Renderer to layer {layer.name}" - ) + else: + raise ValueError( + f"Execution aborted: please apply a Unique Value Renderer to layer {layer.name}" + ) def postExecute(self, _): return @@ -649,12 +642,6 @@ def update_symbol_layer_colors(symbol_layer, symbol_color_value): if isinstance(symbol_layer, arcpy.cim.CIMSymbols.CIMSolidStroke): update_color(symbol_layer, symbol_color_value) - if isinstance(symbol_layer, arcpy.cim.CIMSymbols.CIMHatchFill) and hasattr( - symbol_layer, "lineSymbol" - ): - for sub_symbol_layer in symbol_layer.lineSymbol.symbolLayers: - update_color(sub_symbol_layer, symbol_color_value) - if isinstance( symbol_layer, arcpy.cim.CIMSymbols.CIMCharacterMarker, @@ -684,12 +671,11 @@ def get_symbol_code_for_shape(shape_type, symbol_codes): ) -# code has three components: fill color, symbol key, symbol color -def get_code_components(code, leg_id): +def get_code_components(code): code_len = len(code) if code_len == 4: return { - "color": decode_color(code, leg_id), + "color": decode_color(code), "symbol_key": "", "symbol_color": {}, } @@ -709,13 +695,13 @@ def get_code_components(code, leg_id): } elif code_len == 11: return { - "color": decode_color(code[:4], leg_id), + "color": decode_color(code[:4]), "symbol_key": code[4:12], "symbol_color": {}, } elif code_len == 14: return { - "color": decode_color(code[:4], leg_id), + "color": decode_color(code[:4]), "symbol_key": code[4:11], "symbol_color": get_symbol_color(code[11:]), } @@ -723,29 +709,19 @@ def get_code_components(code, leg_id): raise ValueError(f"Execution aborted: unknown symbol code {code}") -def decode_color(color_string, leg_id): - if color_string[3] == "X": - return { - "CMYK": [ - 0, - 0, - 0, - 0, - ], - } - else: - return { - "CMYK": [ - get_percentage_from_letter(color_string[0], leg_id), - get_percentage_from_letter(color_string[1], leg_id), - get_percentage_from_letter(color_string[2], leg_id), - get_percentage_from_letter(color_string[3], leg_id), - 100, - ], - } +def decode_color(color_string): + return { + "CMYK": [ + get_percentage_from_letter(color_string[0]), + get_percentage_from_letter(color_string[1]), + get_percentage_from_letter(color_string[2]), + get_percentage_from_letter(color_string[3]), + 100, + ], + } -def get_percentage_from_letter(letter, leg_id): +def get_percentage_from_letter(letter): if letter == "F": return 15 elif letter == "V": @@ -757,10 +733,8 @@ def get_percentage_from_letter(letter, leg_id): else: try: return int(letter) * 10 - except Exception as _: - raise ValueError( - f"Execution aborted: unknown color code {letter} for {leg_id}" - ) + except ValueError as _: + raise ValueError(f"Execution aborted: unknown color code {letter}") def get_symbol_color(color_string): @@ -804,7 +778,5 @@ def get_symbol_color(color_string): return { "CMYK": [100, 100, 100, 0, 100], } - elif color_string == "GRA": - return {"CMYK": [10, 10, 10, 21, 100]} else: raise ValueError(f"Execution aborted: unknown color code {color_string}") diff --git a/README.md b/README.md index e2882c1..40db242 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Feature Renderer -This is a Python toolbox for rendering of geologic features in ArcGIS Pro. The toolbox is based on [ArcPy](https://pro.arcgis.com/en/pro-app/latest/arcpy/main/arcgis-pro-arcpy-reference.htm). Specifically, it uses the Cartographic Information Model [CIM](https://pro.arcgis.com/en/pro-app/latest/arcpy/mapping/python-cim-access.htm) to manipulate symbol layers and their properties. +This is a Python toolbox for rendering geologic features in ArcGIS Pro. The toolbox is based on [ArcPy](https://pro.arcgis.com/en/pro-app/latest/arcpy/main/arcgis-pro-arcpy-reference.htm) modules. Specifically, it is based on the Cartographic Information Model [CIM](https://pro.arcgis.com/en/pro-app/latest/arcpy/mapping/python-cim-access.htm) to manipulate symbol layers and their properties. ## Usage