Want to run my Python script as a script tool in a custom toolbox

4035
16
Jump to solution
04-29-2014 06:30 PM
PierreKurth
New Contributor II
Hi there,

I wrote a Python script which allows me to select a layer out of my TOC and change the source to a new data source.
The script I wrote works fine and does the job.

Now I'd like to create a script tool with a user interface working out of a custom tool box.
I cannot get my head around with rewriting the script with variables and use it in conjunction with parameters as a script tool.

When I run my script tool it does not spill out any errors nor the script runs as I want it to. (It saves a new *.mxd but without any changes to the file).:confused:

There must be something wrong the way I set up the variables in my Python script and/or the way I setup the parameter in the script tool properties.

- Please find attached my original Python script which runs perfectly fine.
- Also a revised script for the script tool running within a custom tool box.
- ... and a screenshot of the parameter setup. (I did not make any changes to the parameter properties)

Any help would be terrific.
Thanks
Pierre
0 Kudos
16 Replies
PierreKurth
New Contributor II
I tried the code you sent.

The first bit turned a "y". See screenshot attached. I had to change the line from (there was a " missing and the "0" was not in the right bracket style?

if targetLyr is not None:  print "your selected layer is {0}.format(targetLyr.longName)

to
print "your selected layer is" [0].format(targetLyr.longName)


I still kept going the results you can also find in the attached screenshot.

Hope that helps.

Thanks
0 Kudos
by Anonymous User
Not applicable
Original User: Wayne_Whitley

...no, that's not right, the replaceDataSource line should be a single line.  You should have also corrected the indention - if/elif/else statements part of the same logical block should be identically indented.  And the [0] is wrong.  Try this again, and the only reason I'm directing you to do this is to get more informative messages back - I suspect something's happening like you aren't making a valid SDE connection or an improper datasource is being applied.  This time, I'm attaching a file containing the same lines for you to load into the ArcMap Python window, hopefully to avoid the indention/parsing error.

1- Right-click in the Python window of ArcMap, select 'load'...
2- Browse to the location of the saved py file (file attached) and select it...
3- Fill in the 3 parameters (1st 3 lines), similar to what you did before...
4- Make sure the indention appears correct, etc...
5- Press 'Enter' to execute...
6- Report the returned messages...

This may help isolate the source of error, depending on what msgs you get back.  If the datasource change works this way, then you'll need to work on how the tool params are passed to the script to get that working as well.

Good luck!  If I have time, I'll attach later a 'fully-functioning' zip including a map doc showing the change of a file gdb datasource.
I tested this on both file and SDE data sources, and by the way this version is 10.2.x.  ...a few simple modifications make it compatible for 10.0, in case that's necessary.

Wayne


PS- If for some reason you're still getting errors with indention and so forth, you could optionally make a temporary script tool and run it within the current map doc.  Make it with no parameters (fill in those params as directed in the script file itself and save the file).
0 Kudos
T__WayneWhitley
Frequent Contributor
oops, I see I failed to attach the script file I meant to at the last post. Regardless, I think this script was due for a slight re-write, so I did the below revised script file (and it is reflected in the attached zip which I will remember to attach, to include the same script tool interface w/ validation -- with the new map doc param added).

It occurred to me that maybe you're not providing the pathname to the new map document, so I made the script form the path to write the new file at the same root location as the orig mxd. Otherwise, with the behavior of my 10.2.1, it attempts to write to the current workspace (which may not be a regular 'file' directory). After I inadvertently wrote a map doc copy inside the Default.gdb, yes I said 'inside' my default workspace, I decided to override that behavior. There was no error, and ArcMap didn't seem to mind referencing a layer in the gdb from an mxd within the same gdb. Maybe that is by design, but I decided to 'fix' that with the following, and it also prints the file path you should check the mxd for your datasource update. If for some reason you don't provide a unique name, the save is simply not executed, and you get a warning message. This is made to handle only file or SDE gdb sources, just to keep this bare-bones simple, and there's no handling for feature datasets...so beware of that.

mxdLocation = arcpy.GetParameterAsText(0)
layer = arcpy.GetParameterAsText(1)
serverConnection = arcpy.GetParameterAsText(2)
newSource = arcpy.GetParameterAsText(3)
newMXDname = arcpy.GetParameterAsText(4)

def gdbType(connPath):
        connType = os.path.splitext(connPath)[1]
        if connType == '.sde':
          arcpy.AddMessage('\nsde source.')
          wsType = 'SDE_WORKSPACE'
        elif connType == '.gdb':
          arcpy.AddMessage('\nfile gdb source:')
          wsType = 'FILEGDB_WORKSPACE'
        else:
          arcpy.AddMessage('\nunhandled source type')
          wsType = ''
        return wsType

arcpy.env.workspace = serverConnection
if arcpy.Exists(newSource):
        wsType = gdbType(serverConnection)
        arcpy.AddMessage('the workspace for the new source is {0}\n'.format(serverConnection))
        
MXDdir = os.path.dirname(mxdLocation)
newMXDname = os.path.join(MXDdir, newMXDname + '.mxd')
mxd = arcpy.mapping.MapDocument(mxdLocation)

lyr = arcpy.mapping.ListLayers(mxd, layer)[0] # single-layer datasource replacement, 1st one listed from TOC.

if lyr:
        if lyr.supports("DATASOURCE") and wsType != '':
                arcpy.AddMessage('DATASOURCE property is supported for this layer.\n')
                lyr.replaceDataSource(serverConnection, wsType, newSource)
                if not arcpy.Exists(newMXDname):
                        arcpy.AddMessage('Please check the {0} layer properties for the source change in your new map doc:'.format(lyr.name))
                        arcpy.AddMessage('{0}.\n'.format(newMXDname))
                        mxd.saveACopy(newMXDname)
                else:
                        arcpy.AddWarning('mxd name is not unique and will not be saved...\n')
        else:
                arcpy.AddMessage('DATASOURCE property is either not supported or handling by this script is not provided.\n')


arcpy.AddMessage('The End.')


Let me know if you have questions. Test it out. If you need it to handle feature datasets, that handling can be fairly easily included, just I thought this approach was fair enough to show the interaction between the script tool interface (with modified Validator) and the main script itself.

Just hope it runs successfully for you this time! It's ready to go if you will download the attached, extract the folder (probably advisable to delete the prior similarly named download to avoid confusion), and run the tool as-is. Report back with any error messages you receive...

Wayne
0 Kudos
by Anonymous User
Not applicable
Original User: pierrek

Thank you Wayne for being so patient and helpful.

I ran your script and get the following error message. Please find attached a screen shot.

I noticed that you select the first layer in my TOC. I am working on a multiple scale map and my *.mxd document has layers with the same naming multiple times (up to 15). So there would be no need to select the most top layer as you listed in code line 47. ("lyr = arcpy.mapping.ListLayers(mxd, layer)[0] # single-layer datasource replacement, 1st one listed from TOC.") I would need all layers with the same naming.

Anyway you also mentioned another ArcMap version. I am running the latest 10.2.1. I am not sure whether that is of any help.

As I mentioned earlier I am not a coder and the script becomes now even heavier and I kind get even more lost.

Could you please have another look for me at the script you have provided?

Thanks alot.

Pierre
0 Kudos
T__WayneWhitley
Frequent Contributor
That means ListLayers did not find the layer by name...I'm not in the office to troubleshoot, but it appears that layer is within a group layer?  So you may have to add additional code to also list those layers inside groups - to confirm, take the target layer outside the group and save the mxd, and rerun the script.

Also, the for loop can be added back...once this problem is handled.  If that's not the problem, then there may be something different about the way the layers listed by the Validator are being passed to the ListLayers function --- the reason why you need the ListLayers function is to get a handle on the layer object.  The list within the validator class is just a text list for display purposes....so from there, the proper text chosen from the tool interface has to be passed into the main script.

Question:  Is the layer name in the TOC exactly the same as the input appears in the script dialog?  ...looks like the text FME may be missing on the end?  Not sure if that means anything.
0 Kudos
T__WayneWhitley
Frequent Contributor
I duplicated your error - the cause is my ListLayers line in the ToolValidator code.  Long story short, when layers are embedded within group layers, the returned names take on a 'longName' string, so I substituted this original line that works for non-grouped layers:

lyrList = [str(lyr) for lyr in arcpy.mapping.ListLayers(mxd)]


...with this line that works for layers, grouped or not:

lyrList = sorted(set([os.path.basename(str(lyr)) for lyr in arcpy.mapping.ListLayers(mxd) if not lyr.isGroupLayer]))


Also the main script has been corrected to loop on all the layers by the 'short' name, in other words, the layer parameter (text list generated from the validator code) are unique values passed to the main scripts ListLayers function which in turn returns all layer objects (in a group layer or not) by that name to change the datasource.

Here's the modified main script (also in the newly attached toolbox folder [remember to delete any other toolbox versions to avoid confusion]):
# Script Name: Change_datasource # Description: This script allows you to select a specific layer #              in your table of content and change the data source. # Created By:  Pierre # Date:        29/04/2014   import arcpy import os  # Read the parameter values: #  1: Input workspace #  2: Input layer #  3: Set new server connection #  4: Set new source dataset #  mxdLocation = arcpy.GetParameterAsText(0) layer = arcpy.GetParameterAsText(1) serverConnection = arcpy.GetParameterAsText(2) newSource = arcpy.GetParameterAsText(3) newMXDname = arcpy.GetParameterAsText(4)  def gdbType(connPath):         connType = os.path.splitext(connPath)[1]         if connType == '.sde':           arcpy.AddMessage('\nsde source.')           wsType = 'SDE_WORKSPACE'         elif connType == '.gdb':           arcpy.AddMessage('\nfile gdb source:')           wsType = 'FILEGDB_WORKSPACE'         else:           arcpy.AddMessage('\nunhandled source type')           wsType = ''         return wsType  arcpy.env.workspace = serverConnection if arcpy.Exists(newSource):         wsType = gdbType(serverConnection)         arcpy.AddMessage('the workspace for the new source is {0}\n'.format(serverConnection))          MXDdir = os.path.dirname(mxdLocation) newMXDname = os.path.join(MXDdir, newMXDname + '.mxd') mxd = arcpy.mapping.MapDocument(mxdLocation)  # single-layer datasource replacement, 1st one listed from TOC. #lyr = arcpy.mapping.ListLayers(mxd, layer)[0]  # multiple-layer datasource replacement... for lyr in arcpy.mapping.ListLayers(mxd, layer):         if lyr.supports("DATASOURCE") and wsType != '':                 arcpy.AddMessage('DATASOURCE property is supported for {0}.\n'.format(lyr.longName))                 lyr.replaceDataSource(serverConnection, wsType, newSource)         else:                 arcpy.AddMessage('DATASOURCE property is either not supported or handling by this script is not provided.\n')  if not os.path.exists(newMXDname):         arcpy.AddMessage('Please check the layer properties for ALL layers named \'{0}\', checking the source change in your new map doc:'.format(layer))         arcpy.AddMessage('{0}.\n'.format(newMXDname))         mxd.saveACopy(newMXDname) else:         arcpy.AddWarning('mxd name is not unique and will not be saved...\n')  arcpy.AddMessage('The End.')


Enjoy - let me know if you have any further problems....it's very interesting working with the ToolValidator class and interacting/debugging with it.


-Wayne

PS- Another thing I didn't mention that you may need depending on how you've named your layers in the TOC.  You have the option to use a wildcard for the layer name you're searching for....for example, not that the following is practical for you to do, but you could form this valid wildcard combination:

arcpy.mapping.ListLayers(mxd, 'BIOTA_*_ABORIGINAL_LAND*')
0 Kudos
by Anonymous User
Not applicable
Original User: pierrek

Thank you. The script now works perfectly. I made minor adjustments so the script allows me to make changes to my currently open ArcMap document rather saving a copy.

Thanks again. You have been of great help I really appreciate.

Pierre
0 Kudos