Select to view content in your preferred language

Determining the parameter that triggered updateParameters function within Python Toolbox

626
1
11-17-2022 03:16 PM
ccowin_odfw
Frequent Contributor

Hello,

So I'm having trouble figuring out python toolboxes and how things function. In my current project, I'm creating a toolbox with a number of tools to obfuscate sensitive data. One of those tools is basically the hexagonal binning that is available in Web Maps, I create a tessellation over the extent of an input layer, select all the hexagons without an intersecting feature and delete them, then count the remaining features that intersect each bin to produce a density level, and then finally normalize that density level so the actual count number is hidden.

I use pycharm to debug the execute function but 99% of the trouble I have with python toolboxes is the updateParameter function because I can't set a breakpoint to see what all the parameter values are and how best to programmatically divide different paths I need done during the function.

I wanted to allow my user to determine a unit (Acres, USFeetSquared, MetersSquared, or USMileSquared) in a parameter. Then I have a simple dictionary with appropriate values for each unit so it defaults to a reasonable size so folks know what scale to play around with. That works fine but now I can't figure out a way for the value to only be placed once. I can change the unit and the value changes but if I want to change the value it automatically reverts to the value in the simple dictionary. It really doesn't make any sense logically how the code is even getting to that point:

 

 

class Bin_By_Area(object):
    def __init__(self, launchedFrom=None):
        """Define the tool (tool name is the name of the class)."""
        self.label = "Bin By Area"
        self.description = ""
        self.canRunInBackground = False

        # Launch Program
        self.launchProgram = launchedFrom

        # Parameter Variables
        self.inPath = None
        self.outPath = None

        self.binUnit = None
        self.unitDict = {'Acre': 10, 'SquareFeetUS': 1000000, 'SquareMileUS': 1, 'SquareMeters': 100000}

    def getParameterInfo(self):
        """Define parameter definitions"""
        featureIn = arcpy.Parameter(
            name='Feature_to_Obfuscate_Input',
            displayName='Input',
            datatype='GPFeatureLayer',
            direction='Input',
            parameterType='Required')

        featureOut = arcpy.Parameter(
            name='Obfuscated_Feature_Output',
            displayName='Output',
            datatype='GPFeatureLayer',
            direction='Output',
            parameterType='Required')

        binBase = arcpy.Parameter(
            name='Bin_Base',
            displayName='Bin Base',
            datatype='GPLong',
            direction='input',
            parameterType='Required')

        binUnit = arcpy.Parameter(
            name='Unit_of_Bin_Base',
            displayName='Unit of Bin Base',
            datatype='GPString',
            direction='Input',
            parameterType='Required')

        binUnit.parameterDependencies = [binBase.name]
        binUnit.filter.type = "ValueList"
        binUnit.filter.list = ['Acre', 'SquareFeetUS', 'SquareMileUS', 'SquareMeters']
        binUnit.value = 'Acre'

        binType = arcpy.Parameter(
            name='Type_of_Bin',
            displayName='Type of Bin',
            datatype='GPString',
            direction='Input',
            parameterType='Required')

        binType.parameterDependencies = [binBase.name]
        binType.filter.type = "ValueList"
        binType.filter.list = ['HEXAGON', 'TRANSVERSE_HEXAGON', 'SQUARE', 'DIAMOND', 'TRIANGLE']
        binType.value = 'HEXAGON'

        densify = arcpy.Parameter(
            name='Generate_Density',
            displayName='Generate Density of Features?',
            datatype='GPString',
            direction='Input',
            parameterType='Required')

        densify.filter.type = "ValueList"
        densify.filter.list = ['Yes', 'No']
        densify.value = 'Yes'

        normalize = arcpy.Parameter(
            name='Normalize_Density',
            displayName='Normalize the Density of Features?',
            datatype='GPString',
            direction='Input',
            parameterType='Required')

        normalize.parameterDependencies = [densify.name]
        normalize.filter.type = "ValueList"
        normalize.filter.list = ['Yes', 'No']
        normalize.value = 'Yes'

        debugField = arcpy.Parameter(
            name='debugField',
            displayName='debugField',
            datatype='GPString',
            direction='Input',
            parameterType='Optional')

        parameters = [featureIn, featureOut, binBase, binUnit, binType, densify, normalize, debugField]

        return parameters

    def isLicensed(self):
        """Set whether tool is licensed to execute."""
        return True

    def updateParameters(self, parameters):
        """Modify the values and properties of parameters before internal
        validation is performed.  This method is called whenever a parameter
        has been changed."""

        if not self.binUnit:
            # Do this on initial instantiation
            self.binUnit = parameters[3].valueAsText
            binBase = self.unitDict.get(self.binUnit)
            parameters[2].value = binBase
            parameters[7].value = f'class: {self.binUnit}, para: {parameters[3].valueAsText}'

        elif self.binUnit == parameters[3].valueAsText:
            # If self.binUnit is not None and the binUnit hasn't been changed by the user dont do anything
            parameters[7].value = f'class: {self.binUnit}, para: {parameters[3].valueAsText}'

        elif self.binUnit != parameters[3].valueAsText:
            # If self.binUnit is not None and the binUnit has been changed by the user then change the binBase from the dictionary
            parameters[7].value = f'class: {self.binUnit}, para: {parameters[3].valueAsText}'

        else:
            parameters[7].value = f'class: {self.binUnit}, para: {parameters[3].valueAsText}'

        if parameters[5].valueAsText == 'No':
            parameters[6].enabled = False
        else:
            parameters[6].enabled = True

        return parameters

 

 

So I really dont understand how this part:

 

 

        if not self.binUnit:
            self.binUnit = parameters[3].valueAsText
            binBase = self.unitDict.get(self.binUnit)
            parameters[2].value = binBase
            parameters[7].value = f'class: {self.binUnit}, para: {parameters[3].valueAsText}'

 

 

is getting called on a second run of the function unless the entire class is getting reinstantiated because self.binUnit can't be None (and isn't based on the string I'm putting into parameter[7]). I don't think that is the case though so I'm not sure what I'm doing wrong.


To my limited understanding it seems like the most useful attribute to have in that function is which parameter was altered (and I know that the "altered" attribute exists, it is just useless because it doesn't differentiate between altered programmatically or by the user) that triggered the updateParameter function.

0 Kudos
1 Reply
by Anonymous User
Not applicable

You can use the altered property in your conditional to check if the property has been altered or not and proceed with your code.

Check this out for properties that you can use: customizing-tool-behavior-in-a-python-toolbox.htm