I originally wrote this as a python add-in but I could really use some type of GUI or user interface. Since Tkinter and wx are not supported nor stable when used inside ArcMap I am trying to either build the entire functionality in a python toolbox or python add-in although it looks like I will have to use a combination of both.
All I want is to be able to do is loop through a directory and return a list of folders within that directory. These folders all contain different layer files(*.lyr)
When the user would choose a folder, a list of values would be presented to the user in something like a listbox or pick list. After user selects layer it adds the layer to the TOC.
How can I get the code below to return a list in a python toolbox and present it to the end user as a drop down or combobox.
I would have thought there would have been some type of parameter to have a list presented to the end user but I cannot seem to find one that will work for this scenario.
wrkspc = '//someserver/GIS/Layers/'
folders = []
for dirpath, dirnames, filenames in arcpy.da.Walk(wrkspc,topdown=True):
if '.' not in str(dirpath):
folder = str(os.path.basename(dirpath))
folders.append(folder)
return folders
Solved! Go to Solution.
We already answered this offline as well but to not leave the geonet community in the dark, we did the following:
1) Add a button to the addin.
2) Add the PYT to the install folder in the addin.
3) Use the pythonaddins.GPToolDialog command to add the tool wired to the relative path of the file using syntax to that found below:
x = os.path.dirname(sys.argv[0])
z = os.path.join(x, "KenFold.pyt")
I hope this helps anyone else running into this issue.
Hey Ken!
I have written a quick sample that should do what you are looking for it to do.
Enter in a directory in the first combo box and then press the drop down in the second to select the layer. Finally, press the button to add the layer to the map document.
Does this help?
Sample Code:
import arcpy
import pythonaddins
import os
global layerlist
global dataLocation
global buttonSelection
class ButtonClass5(object):
"""Implementation for Ken3_addin.button (Button)"""
def __init__(self):
self.enabled = True
self.checked = False
def onClick(self):
mxd = arcpy.mapping.MapDocument("CURRENT")
lyrToAdd = buttonSelection
indexPosition = layerList.index(lyrToAdd)
lyr = arcpy.mapping.Layer(dataLocation[indexPosition])
arcpy.mapping.AddLayer(mxd.activeDataFrame, lyr)
class ComboBoxClass1(object):
"""Implementation for Ken3_addin.combobox (ComboBox)"""
def __init__(self):
self.items = []
self.editable = True
self.enabled = True
self.dropdownWidth = 'WWWWWW'
self.width = 'WWWWWW'
def onSelChange(self, selection):
pass
def onEditChange(self, text):
path = text
global layerList
global dataLocation
layerList = []
dataLocation = []
for root, dirs, files in os.walk(path):
for name in files:
if name.endswith(".lyr"):
layerList.append(name)
dataLocation.append(os.path.join(root,name))
print layerList
print dataLocation
def onFocus(self, focused):
pass
def onEnter(self):
pass
def refresh(self):
pass
class ComboBoxClass4(object):
"""Implementation for Ken3_addin.combobox_1 (ComboBox)"""
def __init__(self):
self.items = []
self.editable = True
self.enabled = True
self.dropdownWidth = 'WWWWWW'
self.width = 'WWWWWW'
def onSelChange(self, selection):
global buttonSelection
buttonSelection = selection
def onEditChange(self, text):
pass
def onFocus(self, focused):
self.items = layerList
def onEnter(self):
pass
def refresh(self):
pass
Hi Ken,
If you were interested in doing this as a Python Toolbox, here is another way to do the same functionality as listed above.
Thanks!
import arcpy
import os
import sys
global layerList
global dataLocation
class Toolbox(object):
def __init__(self):
self.label = "Folder Toolbox"
self.alias = "folders"
# List of tool classes associated with this toolbox
self.tools = [findFolders]
class findFolders(object):
def __init__(self):
self.label = "Finds Layer Files"
self.description = "This tool will find layer files in a folder and will update that folder with those files."
def getParameterInfo(self):
#Define parameter definitions
# Input Features parameter
in_workspace = arcpy.Parameter(
displayName="Input Workspace",
name="in_workspace",
datatype="DEFolder",
parameterType="Required",
direction="Input")
# Sinuosity Field parameter
layers = arcpy.Parameter(
displayName="Layers",
name="layers",
datatype="String",
parameterType="Optional",
direction="Input",
multiValue = True)
parameters = [in_workspace, layers]
return parameters
def isLicensed(self): #optional
return True
def updateParameters(self, parameters): #optional
if parameters[0].altered:
global layerList
global dataLocation
path = str(parameters[0].value)
layerList = []
dataLocation = []
for root, dirs, files in os.walk(path):
for name in files:
if name.endswith(".lyr"):
layerList.append(name)
dataLocation.append(os.path.join(root,name))
parameters[1].filter.list = layerList
return
def updateMessages(self, parameters): #optional
return
def execute(self, parameters, messages):
for i in parameters[1].values:
arcpy.AddMessage("Adding: " + str(i))
mxd = arcpy.mapping.MapDocument("CURRENT")
lyrToAdd = i
indexPosition = layerList.index(lyrToAdd)
lyr = arcpy.mapping.Layer(dataLocation[indexPosition])
arcpy.mapping.AddLayer(mxd.activeDataFrame, lyr)
Alexander,
Thank you for the help
Is it possible to populate the value or list for this parameter dynamically?
# Input Features parameter in_workspace = arcpy.Parameter( displayName="Input Workspace", name="in_workspace", datatype="DEFolder", parameterType="Required", direction="Input")
I have tried doing this but when passing in a list I get an error. When I set the list to be hard coded it works just fine.
import arcpy, os global folders 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): """Define the tool (tool name is the name of the class).""" self.label = "Tool" self.description = "" self.canRunInBackground = False self.getLyrFolders() def getParameterInfo(self): global folders #Define parameter definitions # First parameter param0 = arcpy.Parameter(displayName="Input Features",name="in_features",datatype="GPFeatureLayer",parameterType="Required",direction="Input") # Second parameter param1 = arcpy.Parameter(displayName="Sinuosity Field",name="sinuosity_field",datatype="GPString",parameterType="Required",direction="Input") param1.filter.type = "Value List" #param1.filter.list = ['test','test1','test2'] param1.filter.list = folders # Third parameter param2 = arcpy.Parameter(displayName="Output Features",name="out_features",datatype="GPFeatureLayer",parameterType="Derived",direction="Output") param2.parameterDependencies = [param0.name] param2.schema.clone = True params = [param0, param1, param2] return params 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.""" return def updateMessages(self, parameters): """Modify the messages created by internal validation for each tool parameter. This method is called after internal validation.""" return def execute(self, parameters, messages): """The source code of the tool.""" return def getLyrFolders(self): global folders wrkspc = '//bauerdb1/GIS/GIS/Layers/' folders=[] for dirpath, dirnames, filenames in arcpy.da.Walk(wrkspc,topdown=True): if '.' not in str(dirpath): folder = os.path.basename(dirpath) folders.append(folder)
I think I may have gotten it to work as I was hoping, working off of Alexander's example I finally got this to work by ensuring no null values were in my list. I was noticing that the first item in my list was an empty string. I added some logic to getLyrFolders function, which ensures only strings that were not null were appended to the list. After doing this the tool began working. I can only assume this is expected behavior from a GP perspective but I think the error being returned could be more descriptive as it mentioned nothing about null values.
Originally I thought it might have something to do with encoding in utf-8 but in testing, it appears a null string or item in a list was causing the issue, I did not test whether an empty string somewhere other than in the beginning of the list would cause issues but in my case it was the first string in the list.
Here is the code I was able to get working, not a finished product but merely an example.
import arcpy, os global folders 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): """Define the tool (tool name is the name of the class).""" self.label = "Tool" self.description = "" self.canRunInBackground = False self.getLyrFolders() def getParameterInfo(self): global folders #Define parameter definitions # First parameter param0 = arcpy.Parameter(displayName="Input Features",name="in_features",datatype="GPFeatureLayer",parameterType="Required",direction="Input") # Second parameter param1 = arcpy.Parameter(displayName="Sinuosity Field",name="sinuosity_field",datatype="GPString",parameterType="Required",direction="Input") param1.filter.type = "Value List" #param1.filter.list = ['test','test1','test2'] b = [str(item) for item in folders] param1.filter.list = b # Third parameter param2 = arcpy.Parameter(displayName="Output Features",name="out_features",datatype="GPFeatureLayer",parameterType="Derived",direction="Output") param2.parameterDependencies = [param0.name] param2.schema.clone = True params = [param0, param1, param2] return params 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.""" return def updateMessages(self, parameters): """Modify the messages created by internal validation for each tool parameter. This method is called after internal validation.""" return def execute(self, parameters, messages): """The source code of the tool.""" return def getLyrFolders(self): global folders wrkspc = '//bauerdb1/GIS/GIS/Layers/' folders=[] for dirpath, dirnames, filenames in arcpy.da.Walk(wrkspc,topdown=True): if '.' not in str(dirpath): folder = os.path.basename(dirpath) if len(folder) != 0: folders.append(folder)
Alexander,
I guess the next big question is how to marry or get python toolboxes to work with add-ins, I think I have seen it is possible but if you have any suggestions on how to call this tool from a add-in button that would be helpful.
We already answered this offline as well but to not leave the geonet community in the dark, we did the following:
1) Add a button to the addin.
2) Add the PYT to the install folder in the addin.
3) Use the pythonaddins.GPToolDialog command to add the tool wired to the relative path of the file using syntax to that found below:
x = os.path.dirname(sys.argv[0])
z = os.path.join(x, "KenFold.pyt")
I hope this helps anyone else running into this issue.