I'm following an ESRI online learning tutorial: Creating Python Toolboxes Using ArcGIS 10.1.
I copy the script and test on my data but the new tool only recognises 'Street' as an input feature.
Can anyone tell me where is wrong with the codes below?
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 = "Calculate Geometry"
self.alias = "geometry"
# List of tool classes associated with this toolbox
self.tools = [CalculateGeometry]
class CalculateGeometry(object):
def __init__(self):
"""Define the tool (tool name is the name of the class)."""
self.label = "CalculateGeometry"
self.description = ""
self.canRunInBackground = True
def getParameterInfo(self):
# first parameter
"""Define parameter definitions"""
in_features = arcpy.Parameter(
displayName = 'Input Feature',
name = 'in_features',
datatype = 'Feature Layer',
parameterType = 'Required',
direction = 'Input')
in_features.filter.list = ['Point','Polyline','Polygon']
# second parameter
field = arcpy.Parameter(
displayName = 'Field name',
name = 'field_name',
datatype = 'Field',
parameterType = 'Required',
direction = 'Input')
field.parameterDependencies = [in_features.name]
# only show field belongs to the input feature
in_features.filter.list = ['Short','Long','Double','Float','Text']
# third parameter
geomProperty = arcpy.Parameter(
displayName = 'Property',
name = 'geomProperty',
datatype = 'String',
parameterType = 'Required',
direction = 'Input')
# fourth parameter
units = arcpy.Parameter(
displayName = 'Units',
name = 'units',
datatype = 'String',
parameterType = 'optional',
direction = 'Input',
enabled = False)
# fifth parameter
out_features = arcpy.Parameter(
displayName = 'Output Features',
name = 'out_features',
datatype = 'Feature Layer',
parameterType = 'Derived',
direction = 'Output')
field.parameterDependencies = [in_features.name]
out_features.schema.clone = True
params = [in_features, field, geomProperty, units, out_features]
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."""
# Get Inputs
in_features = parameters[0]
geomProperty = parameters[2]
units = parameters[3]
# Geometry Property Filter Lists
pointPropertyList = ['X Coordinate of Point',
'Y Coordinate of Point']
linePropertyList = ['Length', 'X Coordinate of Line Start',
'Y Coordinate of Line Start',
'X Coordinate of Line End',
'Y Coordinate of Line End']
polygonPropertyList = ['Area','Perimeter',
'X Coordinate of Centroid',
'Y Coordinate of Centroid']
# Get shape type of input to determine
# filter list for geometry property parameter
if in_features.value:
desc = arcpy.Describe(in_features.valueAsText)
if desc.shapeType == 'Point':
geomProperty.filter.list = pointPropertyList
elif desc.shapeType == 'Polyline':
geomProperty.filter.list = linePropertyList
elif desc.shapeType == 'Polygon':
geomProperty.filter.list = polygonPropertyList
# Unit Filter Lists
areaUnitList = ['Acres','Ares','Hectares','Square Centimeters','Square Inches','Square Feet','Square Kilometers','Square Meters','Square Miles','Square Millimeters','Square Yard','Square Decimeters']
lengthUnitList = ['Centimeters','Feet','Inches','Kilometers','Meters','Miles','Millimeters','Nautical Miles','Yards','Decimal Degrees']
# Get geometry property input to determine filter list for unit parameter
if geomProperty.value:
if geomProperty.valueAsText == 'Area':
units.enabled = True
units.filter.list = areaUnitList
elif geomProperty.valueAsText in ['Length','Perimeter']:
units.enabled =True
units.filter.list = lengthUnitList
else:
units.value = ''
units.filter.list = []
units.enabled = False
return
def updateMessages(self, parameters):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""
in_features = parameters[0]
geomProperty= parameters[2]
units = parameters[3]
# Check is input is Multipoint or Multipatch and if so set an error
if in_features.value:
desc = arcpy.Describe(in_features.valueAsText)
if desc.shapeType in ['Multipoint','Multipatch']:
in_features.setErrorMessage('{0} features are not supported.'.format(desc.shapeType))
# Check if certain geometry property value is set with no units and add error
if geomProperty.valueAsText in ['Length','Perimeter','Area']:
if not units.value:
units.setErrorMessage('Units required for {0} property'.format(geomProperty.valueAsText))
return
def execute(self, parameters, messages):
"""The source code of the tool."""
# Get Input
in_features = parameters[0].valueAsText
field = parameters[1].valueAsText
geomProperty= parameters[2].valueAsText
units = parameters[3].valueAsText
shapeFieldName = arcpy.Describe(in_features).shapeFieldName
# Create the expression
exp = '!' + shapeFieldName
if geomProperty == 'Area':
exp += '.area@'+ units.replace('','')
elif geomProperty in ['Length','Perimeter']:
exp += '.length@'+ units.replace('','')
else:
propertyList = geomProperty.split('')
coord = propertyList[0]
if propertyList[-1] in ['Point','Start']:
exp += '.firstPoint.' + coord
elif propertyList[-1] == 'End':
exp += '.lastPoint.'+ coord
elif propertyList[-1] == 'Centroid':
exp += '.centroid.'+ coord
exp+= '!'
messages.addMessage(
'\nExpression used for field calculation: {0}\n'.format(exp))
#Calculate Field
arcpy.CalculateField_management(in_features,field,exp,'PYTHON_9.3')
return
Solved! Go to Solution.
Hey Golden, it seems like quite a challenge to me! Is this tutorial part of a class? A couple thoughts:
Have you looked at regular custom script tools stored in a .tbx? I don't use Python Toolbox .pyts. I think it's a lot of extra lines of code and I generally get all I need from regular script tools where the parameter properties are all set up within the GUI.
Also, if posting a lengthy script for others to scrutinize, it's worth the time spent on formatting, commenting, spacing out so folks can understand it better and get you some more worthwhile feedback.
Best,
Micah
Yes, this is what they use as an example for ESRI online learning tutorial.
You are right and there is a lot of extra lines in define parameter and validation that may not be necessary.
I generally use exiting tools for map automation but no idea how to develop my own tools.
Anyway, I still couldn't find what it is wrong. I've thought it would be somewhere during validation. But I have compared the codes very carefully and it seems to be the same as the tutorial.
I've gotten stuck on Esri tutorials before. It's frustrating! One thought: are you using the right tutorial data? It looks like you are using data for an ArcGIS 10.0 tutorial:
However, Python toolboxes are only supported starting at version 10.1. I'm not sure if this is the issue but maybe verify you are using the correct tutorial data. Also, I am still not clear on the purpose of this line:
It looks like you are filtering a list of possible input features with different types of attribute columns? Seems a little odd, but again I am not very familiar with Python toolbox syntax and setup.
Here's a final thought for you, Golden: the Esri documentation for Defining parameter data types in a Python toolbox, it looks like the proper syntax for a feature layer input would actually be:
datatype = 'GPFeatureLayer'
You might give it a try with that modified syntax.
Good on you for taking on Python toolboxes! Here's some more info on Comparing custom and Python toolboxes.
Micah
Thanks, I don't have the example data they use for tutorial so I just use mine.
I tried the codes on another data but it still doesn't read polygon.
I tried `datatype = 'GPFeatureLayer'` but it still doesn't work.
I reposted my code above as I also had some indentation errors. Lines 19-27 limit the type of feature layers the tool will use - points, lines and polygons. For some reason the filter on line 27 is limiting the feature type to lines. If you comment the line out, it will then allow points and polygons.
# in_features.filter.list = ['Point','Polyline','Polygon']
But this may cause the code to fail later on. In the training seminar video, POINT etc. is in all caps, but it doesn't seem to be a problem with capitalization alone. This will take some further investigation.
Regarding Micah's question about line 37:
in_features.filter.list = ['Short','Long','Double','Float','Text']
This line is limiting the choice display of the selected layer's fields to the specific types in the list. Fields of type Date, Blob, Raster, Guid, etc. are not displayed.
Thanks Randy, it works when I commented out line 27 but I still don't understand why.
Can you please explain a little more?
Golden, I don't know exactly why it is happening. My first guess would be a bug in version 10.2.1, because I don't seem have the issue with 10.4.1. (I have only tested with these versions of ArcMap.) I have been able to get a listing of point, line and polygon layers with just 'Point' in the list on line 27. Perhaps one of the items in the list is incorrect, but from the documentation all looks ok. I am also wondering if line 37 is correctly filtering the fields as well, but it doesn't seem to be causing a serious problem.
In place of commenting out the line, it is also possible to use an empty list such as:
in_features.filter.list = []
Since the filter class is built into the Python toolbox, I'm not quite sure how to debug it. There is some documentation here and here.
I am also using ArcGIS 10.4. OK then this is weird ... ...
I think commenting is the same as deleting it. So this line is not required?
in_feature.filter.list = ['Polygon', 'Polyline', 'Points']
Commenting out the line is like deleting it. The purpose of the line is to limit the layers to types that the tool is designed to work with. But for some reason, the filter class in the Python toolbox code does not seem to be working properly, and I'm not sure how to debug it. If you limit the input layers to these three types, the tool will probably work.
Would that relate to the environment the input features are in? For example, if the file is under a folder instead of a Geodatabase.