I write a c# custom node (dll) that queries web pages and is supposed to create 2 lists as a result - assigned to output ports 0 + 1.
I understand that you usually create ONE function call per output and then assign the result with BuildAssignment.
However, it makes no sense for me to query the web pages TWO times - this would take way too long and duplicate a lot of the code and querying for no reason!
I want to do this ONE time only and afterwards take the result and assign to both ports.
e.g.
var functionCall = AstFactory.BuildFunctionCall(
new Func<string, List< List < object > >(Function_GetWebpages),
new List() { inputAstNodes[0] });
return new[] {
AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), functionCall[0]),
AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), functionCall[1])
};
The above obviously doesn’t work since the function returns an AssociativeNode and not a List with 2 nested Lists.
How can this work with just ONE function call? How can I assign the nested lists to the 2 outputs?
Why can’t this be a zero touch node instead you are using a NodeModel. Please look at GitHub queries that I did here: https://github.com/ksobon/archilab/blob/master/archilab/GitHub/GitHub.cs Pay attention to MultiReturn class attributes and how they are used with Dictionaries. That should work just fine.
Thanks for the good examples Konrad.
This would usually work… however, my node requires a custom UI as well, hence I don’t think I can get around the AstFactory.
What if the function needs to return 2 different types of lists, e.g. a list of objects and a list of dictionaries?
Returning them from the function would work with using a Tuple, e.g.
public static Tuple<List<object>,List<Dictionary<string, object>>> GetObjects(string s) { }
However, how can those 2 lists be “extracted” from the Tuple in BuildOutputAst and assigned to port 0 + 1?
The solution mentioned for 1.3 (see below) doesn’t work for this as it’s no List / Array.
public override IEnumerable<AssociativeNode> BuildOutputAst(List<AssociativeNode> inputAstNodes)
{
string packedId = "__temp" + AstIdentifierGuid;
return new[] {
AstFactory.BuildAssignment(AstFactory.BuildIdentifier(packedId), AstFactory.BuildFunctionCall(
new Func<string, Tuple<List<object>,List<Dictionary<string, object>>>>(GetObjects),
new List<AssociativeNode>() { inputAstNodes[0] } )),
AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), new IdentifierNode(packedId) {
ArrayDimensions = new ArrayNode { Expr = AstFactory.BuildIntNode(0) } }),
AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(1), new IdentifierNode(packedId) {
ArrayDimensions = new ArrayNode { Expr = AstFactory.BuildIntNode(1) } })
};
}
return a list<list<object>>- I don’t think designscript has tuple as a type or a conversion from .net tuple to a designscript type. Another option is to return a dictionary … but getting keys in a dictionary is kind of tricky, and has changed a lot between 1.3 and 2.0. I would use the nested list of objects.
That’s what I tried first and it works with simple types of different format, e.g. list 0 = string, list 1 = int but as soon as I use a dictionary as list 1 it breaks it and output port 1 remains null.
The warning message displayed on the node is: Warning: Converting an array to var would cause array rank reduction and is not permitted
So I am not sure this nested list approach is working for dictionaries?
Or is something else wrong / needs to be fixed?
FUNCTION:
public static List<List<object>> GetObjects(string s)
{
List<List<object>> r = new List<List<object>>();
List<object> r0 = new List<object>();
List<object> r1 = new List<object>();
r0.Add("string1");
r0.Add("string2");
r0.Add("string3");
r1.Add(new Dictionary<string, string>() { { "1", "A" }, { "2", "B" }, { "3", "C" } }); // this breaks it
r.Add(r0);
r.Add(r1);
return r;
}
AST:
public override IEnumerable<AssociativeNode> BuildOutputAst(List<AssociativeNode> inputAstNodes)
{
string packedId = "__temp" + AstIdentifierGuid;
return new[] {
AstFactory.BuildAssignment(AstFactory.BuildIdentifier(packedId), AstFactory.BuildFunctionCall(
new Func<string, List<List<object>>>(GetObjects),
new List<AssociativeNode>() { inputAstNodes[0] } )),
AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), new IdentifierNode(packedId) {
ArrayDimensions = new ArrayNode { Expr = AstFactory.BuildIntNode(0) } }),
AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(1), new IdentifierNode(packedId) {
ArrayDimensions = new ArrayNode { Expr = AstFactory.BuildIntNode(1) } })
};
}
this actually works in dynamo 2.0 where .net dictionary marshalling to the designScript dictionary type is supported. Still need to mess with it in 1.3.
one thing you could do is wrap your dictionary in your own type, like myDictionary() to test if it’s some marshaling problem when dynamo encounters a .net dictionary. (my guess)