How to get return value from python tool

1817
11
Jump to solution
09-25-2020 12:07 AM
ThanHtetAung_EsriAu
Esri Regular Contributor

Hi Guys,

I am currently trying to find out how to get return back value from python tool box.

Below is the snippet of Python and C#, not sure why when I run the code whole ArcGIS pro hang.

I have done similar way before but working properly but not sure why it is happening with these.

Python snippet (ouputLayerInfo value is the one want to return to c# side)

# -*- coding: utf-8 -*-

import arcpy
import json
import sys

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 = [GetLayerDataConnections]


class GetLayerDataConnections(object):
    def __init__(self):
        """Define the tool (tool name is the name of the class)."""
        self.label = "GetLayerDataConnections"
        self.description = "Get Layer Data Connections"
        self.canRunInBackground = True

    def getParameterInfo(self):
        """Define parameter definitions"""
        # First parameter
        param0 = arcpy.Parameter(
            displayName="Layer File Location",
            name="Layerfile_location",
             datatype="GPString",
            parameterType="Required",
            direction="Input")

        params = [param0]
        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):
        layerfileLocation = parameters[0].valueAsText
        
        ouputLayerInfo = []
        aLayerFile = arcpy.mp.LayerFile(layerfileLocation)
        layerList = aLayerFile.listLayers()
        
        for lyr in layerList:
            if lyr.supports("DATASOURCE"):
                conProp = lyr.connectionProperties
                name = lyr.name
                tmpStr = ("{LayerName:'"+name+"',ConnectionProperties:"+ json.dumps(conProp)+"}")
                ouputLayerInfo.append(tmpStr)
        return
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

c# snippet

 private void GetLayerDataSourceInfo(string layerfilePath, string toolboxPath)
        {
            try
            {
                var ps = new ProgressorSource("Start retrieving datasource.....");


                var arguments = Geoprocessing.MakeValueArray(layerfilePath);

                var gpResult = Geoprocessing.ExecuteToolAsync(toolboxPath, arguments);

                using (var progress = new ProgressDialog("Retrieving datasources"))
                {
                    var status = new ProgressorSource(progress);

                    progress.Show();
                    gpResult.Wait();
                    progress.Hide();
                }


                IGPResult ProcessingResult = gpResult.Result;
                if (ProcessingResult.IsFailed)
                {
                    string errorMessage = "";
                    foreach (IGPMessage gpMessage in ProcessingResult.Messages)
                    {
                        errorMessage += $"{{Error Code: {gpMessage.ErrorCode}, Text :  {gpMessage.Text} }}";
                    }

                    MessageBox.Show($"Geoprocessing fail {Environment.NewLine}{errorMessage}");
                }
                else
                {
                    string outvalue = ProcessingResult.ReturnValue;
                    Console.WriteLine(outvalue);
                    MessageBox.Show($"Return value {Environment.NewLine}" + outvalue);
                }

            }
            catch (Exception exce)
            {
               //log
                
            }
        }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Uma Harano‌, Wolfgang Kaiser

1 Solution

Accepted Solutions
Wolf
by Esri Regular Contributor
Esri Regular Contributor

Hi Than Aung‌,

 I can't speak to your tools and I think that Amir is answering your questions to that regard, but as far as calling a geoprocessing tool from .Net I noticed that IGPResult doesn't return all warnings/error strings in all cases and in some cases i get a wrong success status.  So I call ExecuteToolAsync using the callback parameter to examine 'OnValidate' message types in the call back.  In some cases, like for example in the sample below errors are only coming back via the 'OnValidate' callback.

protected override async void OnClick()
{
  // get the testpoint layer
  try
  {
    var layerName = "TestPoints";
    var field = "Description";
    var expression = "'Test'";
    var featureLayer = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().Where(fl => fl.Name.Contains(layerName)).FirstOrDefault();
    var msg = await CalcFieldGeoprocessingAsync(featureLayer, field, expression);
    MessageBox.Show($@"Calc field returned: {msg}");
  }
  catch (Exception ex)
  {
    MessageBox.Show($@"{ex}");
  }
}

// cancellation token
private System.Threading.CancellationTokenSource _cts = new System.Threading.CancellationTokenSource();

public async Task<string> CalcFieldGeoprocessingAsync(FeatureLayer selectionLayer,
                                                                                                                                                                                          string field, string expression)
{
  return await QueuedTask.Run<string>(async () =>
  {
    var stringBuilder = new StringBuilder();
    using (FeatureClass featureClass = selectionLayer.GetFeatureClass())
    {
      string in_table = featureClass.GetPath().ToString();
      IReadOnlyList<string> calculateParameters = Geoprocessing.MakeValueArray(new object[] { in_table, field, expression });

      var stopWatch = new Stopwatch();

      stopWatch.Start();
      IGPResult result = await Geoprocessing.ExecuteToolAsync("management.CalculateField",
                      calculateParameters, null, _cts.Token,
                    (eventName, o) =>
                    {
                      System.Diagnostics.Debug.WriteLine($@"GP event: {eventName}");
                      switch (eventName)
                      {
                        case "OnValidate": // stop execute if any warnings
                                                    if ((o as IGPMessage[]).Any(it => it.Type == GPMessageType.Warning))
                            _cts.Cancel();
                          foreach (var str in (o as IGPMessage[]))
                          {
                            stringBuilder.Append($@"Warning: {str}");
                          }
                          break;
                        case "OnProgressMessage":
                          string msg = $@"{eventName}: {(string)o}";
                          break;
                        case "OnProgressPos":
                          string msg2 = $@"{eventName}: {(int)o} %";
                          stringBuilder.Append(msg2);
                          break;
                      }
                    });

      stopWatch.Stop();
      // Report the results
      if (result.IsFailed)
      {
        stringBuilder.Append("GP Tool failed: ");
        foreach (IGPMessage message in result.Messages)
        {
          stringBuilder.AppendLine(message.ToString());
        }
      }
      else
      {
        stringBuilder.Append("Updating rows without an edit operation took: ");
        stringBuilder.Append($@"{Math.Round(stopWatch.ElapsedMilliseconds / 1000.0, 0)} seconds");
      }
      return stringBuilder.ToString();
    }
  });
}

View solution in original post

11 Replies
NobbirAhmed
Esri Regular Contributor

In the execute method of the Python toolbox you are just using return statement (line# 66) which means you are returning void (or nothing). 

I think the return statement should be (WARNING: I think my below statement is wrong - will update the correction soon. 9/28/2020)

return ouputLayerInfo

 

In this call to an Async method, use an "await":

var gpResult = await Geoprocessing.ExecuteToolAsync(toolboxPath, arguments);

Give it a try and let us know how it goes

ThanHtetAung_EsriAu
Esri Regular Contributor

Thank Nobbir Ahmed‌,

Python tool seem to run properly from gptool dialog box. however It is quite annoying that from C# it is just displayed progress bar and never end. And it is totally hang.

I am using ArcGIS Pro 2.5 , and I have other python tool and having same style of calling from C#, only that python tool seem freezing the whole arcgis pro. 

How shall I proceed?

0 Kudos
NobbirAhmed
Esri Regular Contributor

Do you get any return value when you just run the Python toolbox in ArcGIS Pro app? I don't see any - the pyt tool does not return any value from Pro App - thus .Net call also doesn't get any value. 

Let me work on this - will get back with a working answer.

0 Kudos
ThanHtetAung_EsriAu
Esri Regular Contributor

Thank for your replyNobbir Ahmed‌,

No I didn't see any return message or result in python toolbox, (ArcGIS pro app gptask toolbox result window) 

For .net call, I still stick with using gpResult.await() method, rather than await expression because, not sure why I can't retrieve IGPResult with await expression.

As mentioned earlier, It didn't process father from gpResult.Wait() statement. in .net side.

0 Kudos
NobbirAhmed
Esri Regular Contributor

Than Aung‌ - could you please email me your python toolbox and the .Net solution as a zip file? 

0 Kudos
Wolf
by Esri Regular Contributor
Esri Regular Contributor

Hi Than Aung‌,

 I can't speak to your tools and I think that Amir is answering your questions to that regard, but as far as calling a geoprocessing tool from .Net I noticed that IGPResult doesn't return all warnings/error strings in all cases and in some cases i get a wrong success status.  So I call ExecuteToolAsync using the callback parameter to examine 'OnValidate' message types in the call back.  In some cases, like for example in the sample below errors are only coming back via the 'OnValidate' callback.

protected override async void OnClick()
{
  // get the testpoint layer
  try
  {
    var layerName = "TestPoints";
    var field = "Description";
    var expression = "'Test'";
    var featureLayer = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().Where(fl => fl.Name.Contains(layerName)).FirstOrDefault();
    var msg = await CalcFieldGeoprocessingAsync(featureLayer, field, expression);
    MessageBox.Show($@"Calc field returned: {msg}");
  }
  catch (Exception ex)
  {
    MessageBox.Show($@"{ex}");
  }
}

// cancellation token
private System.Threading.CancellationTokenSource _cts = new System.Threading.CancellationTokenSource();

public async Task<string> CalcFieldGeoprocessingAsync(FeatureLayer selectionLayer,
                                                                                                                                                                                          string field, string expression)
{
  return await QueuedTask.Run<string>(async () =>
  {
    var stringBuilder = new StringBuilder();
    using (FeatureClass featureClass = selectionLayer.GetFeatureClass())
    {
      string in_table = featureClass.GetPath().ToString();
      IReadOnlyList<string> calculateParameters = Geoprocessing.MakeValueArray(new object[] { in_table, field, expression });

      var stopWatch = new Stopwatch();

      stopWatch.Start();
      IGPResult result = await Geoprocessing.ExecuteToolAsync("management.CalculateField",
                      calculateParameters, null, _cts.Token,
                    (eventName, o) =>
                    {
                      System.Diagnostics.Debug.WriteLine($@"GP event: {eventName}");
                      switch (eventName)
                      {
                        case "OnValidate": // stop execute if any warnings
                                                    if ((o as IGPMessage[]).Any(it => it.Type == GPMessageType.Warning))
                            _cts.Cancel();
                          foreach (var str in (o as IGPMessage[]))
                          {
                            stringBuilder.Append($@"Warning: {str}");
                          }
                          break;
                        case "OnProgressMessage":
                          string msg = $@"{eventName}: {(string)o}";
                          break;
                        case "OnProgressPos":
                          string msg2 = $@"{eventName}: {(int)o} %";
                          stringBuilder.Append(msg2);
                          break;
                      }
                    });

      stopWatch.Stop();
      // Report the results
      if (result.IsFailed)
      {
        stringBuilder.Append("GP Tool failed: ");
        foreach (IGPMessage message in result.Messages)
        {
          stringBuilder.AppendLine(message.ToString());
        }
      }
      else
      {
        stringBuilder.Append("Updating rows without an edit operation took: ");
        stringBuilder.Append($@"{Math.Round(stopWatch.ElapsedMilliseconds / 1000.0, 0)} seconds");
      }
      return stringBuilder.ToString();
    }
  });
}
ThanHtetAung_EsriAu
Esri Regular Contributor

ThankWolfgang Kaiser‌ for this highlight,

Look like I should change my code with callback for robustness, I have used a lot with IGPResult style.

0 Kudos
ThanHtetAung_EsriAu
Esri Regular Contributor

And the update Wolfgang Kaiser‌,

With callback usage, it does not hang, not sure why gpResult.Wait(); usage is causing that issue, I thought these two usage should be logically the same.

ModyBuchbinder
Esri Regular Contributor

Hi all

I did not tested it but maybe you should use arcpy.SetParameter in your python code to return a value?