Select to view content in your preferred language

Create Subtypes from a Domain

808
4
07-22-2025 07:43 PM
Status: Open
Labels (1)
LizAbbey
Regular Contributor

Problem: Creating subtypes for feature classes is manual and time-consuming. Doing it for one feature class is annoying, but doing it for multiple feature classes that use the same subtypes is extremely inefficient. 

The Data Management tool "Add Subtype" only allows the user to add a single subtype at a time. In batch mode, it allows a batch addition of codes, but not descriptions. Subtypes must be integers, so the description is important.

The Manage Subtypes dialog box also only allows you to manually type in one new subtype at a time. The paste option, which would be another great time saver if you could paste in directly from excel or a domain, does not support pasting more than one cell of data at a time.

Existing Idea (Give it a kudo!): Allow pasting subtypes from one feature class to another

This Idea: My idea is to allow using a domain stored within the geodatabase to populate the subtype codes and descriptions.

 

4 Comments
AlfredBaldenweck

Ngl I kind of assumed this functionality was already there. Weird.

SSWoodward

Thanks for this Idea @LizAbbey 

This functionality is not available as a single click button or GP tool but can be achieved with a few quick lines of python.  Below I've included a sample that will allow you to use any integer domain to create subtypes on a feature class. It could be adapted with minor changes to suit non-integer domains as well. 

workspace: str = "<insert workspace path here>"
domain_name: str = "<inser name of domain in workspace>"
target_table: str = "<path to target table>"
subtype_field: str = "<name of integer subtype field in target_table>"

st_tbl =arcpy.management.DomainToTable(
    workspace, domain_name, 'memory\\temp_out_tbl', 'code', 'desc'
)
arcpy.management.SetSubtypeField(
    target_table, subtype_field
)
with arcpy.da.SearchCursor(st_tbl, ['code', 'desc']) as cursor:
    for row in cursor:
        arcpy.management.AddSubtype(target_table, row[0], row[1])

del cursor
arcpy.management.Delete(st_tbl)

 

LizAbbey

Thanks for the code, @SSWoodward. As written it doesn't prevent the user from nominating a non-integer domain, or from selecting a subtype field that is not an integer type - both scenarios make the script fail.

I was able to save this as a python script inside a toolbox and added in some validation to only allow selecting an integer field for the target subtype field. I wasn't able to limit the domains to integer domains though (limited python knowledge). My workaround is to prefix subtype domain names with "subtype_" so I know they're formatted correctly.

This is an excellent workaround for me and I have posted the entire script with screenshots below.


Parameters:

LizAbbey_0-1753320886789.png

Execution:

Screenshot

LizAbbey_0-1753401217843.png

 

Code:

import arcpy

def add_subtypes_from_domain(workspace: str, domain_name: str, target_table: str, subtype_field: str):
    """
    Adds subtypes to a feature class or table based on a coded value domain.

    Parameters:
        workspace (str): Path to the geodatabase containing the domain.
        domain_name (str): Name of the domain to extract codes from.
        target_table (str): Path to the feature class or table to update.
        subtype_field (str): Name of the integer field to use for subtypes.
    """
    arcpy.env.workspace = workspace
    temp_table = "memory\\temp_out_tbl"

    try:
        # Convert domain to table
        st_tbl = arcpy.management.DomainToTable(workspace, domain_name, temp_table, 'code', 'desc')

        # Set the subtype field
        arcpy.management.SetSubtypeField(target_table, subtype_field)

        # Add subtypes from the domain table
        with arcpy.da.SearchCursor(st_tbl, ['code', 'desc']) as cursor:
            for code, desc in cursor:
                arcpy.management.AddSubtype(target_table, code, desc)

    finally:
        # Clean up
        if 'cursor' in locals():
            del cursor
        if arcpy.Exists(temp_table):
            arcpy.management.Delete(temp_table)

if __name__ == "__main__":
    workspace = arcpy.GetParameterAsText(0)
    domain_name = arcpy.GetParameterAsText(1)
    target_table = arcpy.GetParameterAsText(2)
    subtype_field = arcpy.GetParameterAsText(3)

    add_subtypes_from_domain(workspace, domain_name, target_table, subtype_field)

Validation:

Screenshot

LizAbbey_2-1753320978799.png

Code:

import arcpy

class ToolValidator:
    def __init__(self):
        self.params = arcpy.GetParameterInfo()

    def initializeParameters(self):
        return

    def updateParameters(self):
        # Populate domain names from workspace (no type filtering)
        if self.params[0].altered and self.params[0].value:
            workspace = self.params[0].valueAsText
            arcpy.env.workspace = workspace
            try:
                domains = arcpy.da.ListDomains(workspace)
                domain_names = [d.name for d in domains]
                self.params[1].filter.list = domain_names
            except Exception:
                self.params[1].filter.list = []

        # Populate subtype field list with only Integer or SmallInteger fields
        if self.params[2].altered and self.params[2].value:
            target_table = self.params[2].valueAsText
            try:
                fields = arcpy.ListFields(target_table)
                int_fields = [
                    f.name for f in fields
                    if f.type in ["Integer", "SmallInteger"]
                ]
                self.params[3].filter.list = int_fields
            except Exception:
                self.params[3].filter.list = []

        return

    def updateMessages(self):
        return

 

SSWoodward

@LizAbbey Great improvements!

Adding that validation really takes this to the next level. I'm glad you were able to turn my simple example into a full fledged GP tool.  We love to see it.