6

I'm creating a Python Toolbox. One input is a workspace and the second is a list of feature classes and tables. The list of feature classes and tables is generated once the workspace is input by the user, using a series of ListFeatureClasses and ListTables. Annoyingly, once the workspace is input the tool will re-run this code when the user updates ANY of the parameters. This can freeze up ArcGIS for minutes if the workspace contains lots of feature classes/tables and is located on a network. Is there a way to only run code when a specifc parameter is altered? You'd think parameter.altered == True would do the trick but this seems no different than if parameter.valueAsText returns True in a logic test (has a value).

Making the feature class/tables parameter dependent of the workspace doesn't work because feature classes in datasets are ignored.

Code:

class Tool(object):
 def __init__(self):
 self.label = "Copy Feature Classes And Relationship Classes"
 self.description = ""
 self.canRunInBackground = False
 def getParameterInfo(self):
 inGdb = arcpy.Parameter(
 displayName = "Input Geodatabase",
 name = "ingdb",
 datatype = "Workspace",
 parameterType = "Required",
 direction = "Input")
 outGdb = arcpy.Parameter(
 displayName = "Output Geodatabase",
 name = "outgdb",
 datatype = "Workspace",
 parameterType = "Required",
 direction = "Input")
 inFcs = arcpy.Parameter (
 displayName = "Feature Classes And Tables",
 name = "infcs",
 datatype = "GPString",
 parameterType = "Required",
 direction = "Input",
 multiValue = True)
 return [inGdb, outGdb, inFcs]
 def updateParameters(self, parameters):
 if not parameters [0].valueAsText:
 parameters [2].enabled = False
 ###below code runs whenever any new input is input!!!
 if parameters [0].valueAsText and parameters [0].altered: ##altered doesn't work! :(
 try: wst = arcpy.Describe (parameters [0].valueAsText).workspaceType
 except:
 parameters [2].filter.list = []
 parameters [2].value = None
 return
 if wst == "LocalDatabase":
 parameters [2].enabled = True
 gdb = parameters [0].valueAsText
 arcpy.env.workspace = gdb
 fcs = []
 for typ in ["Point", "Polygon", "Polyline", "Multipoint"]:
 fcs += ["{} (Feature Class)".format (fc) for fc in arcpy.ListFeatureClasses (feature_type = typ)]
 fcs += ["{} (Table)".format (tab) for tab in arcpy.ListTables ()]
 datasets = arcpy.ListDatasets ()
 for dataset in datasets:
 for typ in ["Point", "Polygon", "Polyline", "Multipoint"]:
 dsFcs = arcpy.ListFeatureClasses (None, typ, dataset)
 for dsFc in dsFcs:
 fc = os.path.join (dataset, dsFc)
 fcs += [fc]
 parameters [2].filter.list = fcs
 else:
 parameters [2].filter.list = []
 parameters [2].value = None
asked Oct 25, 2019 at 0:50
7
  • Can you introduce a global variable (currentValue) then in the sub if valueAsText != currentValue then return ? Commented Oct 25, 2019 at 0:53
  • @MichaelStimson Thanks for the response Michael. I tried globals but they didn't seem to work. I'll give it another go. Commented Oct 25, 2019 at 0:54
  • @MichaelStimson That did it! I just wasn't using globals correctly. Thanks. Please feel free to answer and I'll accept. Commented Oct 25, 2019 at 1:01
  • did you specify global currentValue in the def? Python is a bit finicky about that sort of thing, if you don't tell it you want to use the existing global variable it will assume that it's a new local variable (which has a value of None). Commented Oct 25, 2019 at 1:03
  • @MichaelStimson that’s exactly what I did wrong. I defined it as global at the start of the script but not in the def. Commented Oct 25, 2019 at 1:39

2 Answers 2

7

The "arcpy way ™" is to check if parameters[0].altered and not parameters[0].hasBeenValidated:

According to the documentation:

altered

altered is true if the value of a parameter is changed... Once a parameter has been altered, it remains altered until the user empties (blanks out) the value, in which case it returns to being unaltered.

hasBeenValidated

hasBeenValidated is false if a parameter's value has been modified by the user since the last time updateParameters and internal validate were called. Once internal validate has been called, geoprocessing automatically sets hasBeenValidated to true for every parameter.

hasBeenValidated is used to determine whether the user has changed a value since the last call to updateParameters.

For example, a simplified version of your code (doesn't disable/enable, just empties/populates parameters[2]):

import arcpy
class Toolbox(object):
 def __init__(self):
 """Define the toolbox (the name of the toolbox is the name of the
 .pyt file)."""
 self.label = "Toolbox"
 self.alias = ""
 # List of tool classes associated with this toolbox
 self.tools = [Tool]
class Tool(object):
 def __init__(self):
 self.label = "Tool"
 self.description = "A Tool"
 self.canRunInBackground = False
 def getParameterInfo(self):
 inGdb = arcpy.Parameter(
 displayName = "Input Geodatabase",
 name = "ingdb",
 datatype = "DEWorkspace",
 parameterType = "Required",
 direction = "Input")
 # Set the filter to accept only local geodatabases
 inGdb.filter.list = ["Local Database"]
 outGdb = arcpy.Parameter(
 displayName = "Output Geodatabase",
 name = "outgdb",
 datatype = "DEWorkspace",
 parameterType = "Required",
 direction = "Input")
 # Set the filter to accept only local geodatabases
 outGdb.filter.list = ["Local Database"]
 inFcs = arcpy.Parameter(
 displayName = "Feature Classes And Tables",
 name = "infcs",
 datatype = "GPString",
 parameterType = "Required",
 direction = "Input",
 multiValue = True)
 return [inGdb, outGdb, inFcs]
 def updateParameters(self, parameters):
 if not parameters[0].altered:
 parameters[2].filter.list = []
 parameters[2].value = None
 elif parameters[0].altered and not parameters[0].hasBeenValidated:
 gdb = parameters[0].valueAsText
 arcpy.env.workspace = gdb
 fcs = []
 for typ in ["Point", "Polygon", "Polyline", "Multipoint"]:
 fcs += ["{} (Feature Class)".format(fc) for fc in arcpy.ListFeatureClasses(feature_type = typ)]
 fcs += ["{} (Table)".format(tab) for tab in arcpy.ListTables()]
 datasets = arcpy.ListDatasets()
 for dataset in datasets:
 for typ in ["Point", "Polygon", "Polyline", "Multipoint"]:
 dsFcs = arcpy.ListFeatureClasses(None, typ, dataset)
 for dsFc in dsFcs:
 fc = os.path.join(dataset, dsFc)
 fcs += [fc]
 parameters[2].filter.list = fcs
answered Oct 28, 2019 at 0:30
2

For this one a use of a global variable will help:

currentValue = '' # to avoid problems later give it an empty string
def updateParameters(self, parameters):
 global currentValue
 if parameters[0].valueAsText != currentValue:
 currentValue = parameters[0].valueAsText
 # the rest of your code

Python is a bit finicky about that sort of thing, if you don't tell it you want to use the existing global variable it will assume that it's a new local variable (which has a value of None).

Global variables are handy and have a purpose but I would warn future readers of this post to not over-use global variables.. I have read some articles that suggest that globals in python chew up memory needlessly.

answered Oct 25, 2019 at 1:07
1
  • 4
    Alternatively, you could create a class instance variable (ie, a property) in the __init__(self) function as self.currentValue. And check/update its value in updateParameters() as above. Commented Oct 25, 2019 at 2:30

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.