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
-
Can you introduce a global variable (currentValue) then in the sub if valueAsText != currentValue then return ?Michael Stimson– Michael Stimson2019年10月25日 00:53:21 +00:00Commented 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.Emil Brundage– Emil Brundage2019年10月25日 00:54:03 +00:00Commented 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.Emil Brundage– Emil Brundage2019年10月25日 01:01:30 +00:00Commented 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).Michael Stimson– Michael Stimson2019年10月25日 01:03:00 +00:00Commented 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.Emil Brundage– Emil Brundage2019年10月25日 01:39:08 +00:00Commented Oct 25, 2019 at 1:39
2 Answers 2
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 timeupdateParameters
and internal validate were called. Once internal validate has been called, geoprocessing automatically setshasBeenValidated
to true for every parameter.
hasBeenValidated
is used to determine whether the user has changed a value since the last call toupdateParameters
.
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
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.
-
4Alternatively, you could create a class instance variable (ie, a property) in the
__init__(self)
function asself.currentValue
. And check/update its value inupdateParameters()
as above.Son of a Beach– Son of a Beach2019年10月25日 02:30:19 +00:00Commented Oct 25, 2019 at 2:30
Explore related questions
See similar questions with these tags.