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
}
}
Solved! Go to Solution.
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();
}
});
}
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
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?
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.
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.
Than Aung - could you please email me your python toolbox and the .Net solution as a zip file?
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();
}
});
}
ThankWolfgang Kaiser for this highlight,
Look like I should change my code with callback for robustness, I have used a lot with IGPResult style.
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.
Hi all
I did not tested it but maybe you should use arcpy.SetParameter in your python code to return a value?