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.
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