Skip to content

Commit 75f24d4

Browse files
authored
Enable Node Autocompletion for output ports of Dynamo nodes. (#11638)
* Node autocompletion for output nodes. * add test * Skip default suggestions for output ports. * Update the test * Addressing some comments * Adding try catch block * Adding a new class for Auto-completion node elements * Modify NodeAutoCompleteSearchViewModel.cs * Update AutoCompletionNodeSearchElement.cs * Move the autocompletion info into an internal class to avoid API break.
1 parent 9740b24 commit 75f24d4

File tree

7 files changed

+296
-50
lines changed

7 files changed

+296
-50
lines changed

src/DynamoCore/Graph/Nodes/PortModel.cs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Dynamo.Graph.Connectors;
1010
using Dynamo.Graph.Nodes.ZeroTouch;
1111
using Dynamo.Graph.Workspaces;
12+
using Dynamo.Logging;
1213
using Dynamo.Utilities;
1314
using Newtonsoft.Json;
1415
using ProtoCore.AST.AssociativeAST;
@@ -403,8 +404,7 @@ internal string GetInputPortType()
403404
{
404405
if (PortType == PortType.Output) return null;
405406

406-
var ztNode = Owner as DSFunction;
407-
if (ztNode != null)
407+
if (Owner is DSFunction ztNode)
408408
{
409409
var fd = ztNode.Controller.Definition;
410410
string type;
@@ -430,13 +430,58 @@ internal string GetInputPortType()
430430
return type;
431431
}
432432

433-
var nmNode = Owner as NodeModel;
434-
if (nmNode != null)
433+
if (Owner is NodeModel nmNode)
435434
{
436435
var classType = nmNode.GetType();
437436
var inPortAttribute = classType.GetCustomAttributes().OfType<InPortTypesAttribute>().FirstOrDefault();
438437

439-
return inPortAttribute?.PortTypes.ElementAt(Index);
438+
try
439+
{
440+
return inPortAttribute?.PortTypes.ElementAt(Index);
441+
}
442+
catch (Exception e)
443+
{
444+
Log(e.Message);
445+
}
446+
}
447+
return null;
448+
}
449+
450+
/// <summary>
451+
/// Returns the string representation of the fully qualified typename
452+
/// where possible for the port if it's an output port. This method currently
453+
/// returns a valid type for only Zero Touch, Builtin and NodeModel nodes,
454+
/// and returns null otherwise. The string representation of the type also
455+
/// contains the rank information of the type, e.g. Point[], or var[]..[].
456+
/// </summary>
457+
/// <returns>output port type</returns>
458+
internal string GetOutPortType()
459+
{
460+
if (PortType == PortType.Input) return null;
461+
462+
if (Owner is DSFunction ztNode)
463+
{
464+
var fd = ztNode.Controller.Definition;
465+
466+
string type = fd.ReturnType.ToString();
467+
468+
return type;
469+
}
470+
471+
if (Owner is NodeModel nmNode)
472+
{
473+
var classType = nmNode.GetType();
474+
475+
var outPortAttribute = classType.GetCustomAttributes().OfType<OutPortTypesAttribute>().FirstOrDefault();
476+
477+
try
478+
{
479+
return outPortAttribute?.PortTypes.ElementAt(Index);
480+
}
481+
catch(Exception e)
482+
{
483+
Log(e.Message);
484+
}
440485
}
441486
return null;
442487
}

src/DynamoCore/Search/SearchElements/NodeSearchElement.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public abstract class NodeSearchElement : ISearchEntry, ISource<NodeModel>
2020
private string assembly;
2121
private bool isVisibleInSearch = true;
2222

23+
internal AutoCompletionNodeElementInfo AutoCompletionNodeElementInfo { get; set; } = new AutoCompletionNodeElementInfo();
24+
2325
/// <summary>
2426
/// Event is fired when a node visibility in library search was changed.
2527
/// </summary>
@@ -283,6 +285,14 @@ protected virtual IEnumerable<Tuple<string, string>> GenerateInputParameters()
283285
}
284286
}
285287

288+
/// <summary>
289+
/// This class will contain the information related to the node elements of Auto-completion feature.
290+
/// </summary>
291+
internal class AutoCompletionNodeElementInfo
292+
{
293+
internal int PortToConnect { get; set; }
294+
}
295+
286296
/// <summary>
287297
/// This class returns <see cref="NodeSearchElement"/> which is used
288298
/// for creating a node, when the element is drag and dropped.

src/DynamoCoreWpf/ViewModels/Core/PortViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ private bool CanAutoComplete(object parameter)
418418
{
419419
DynamoViewModel dynamoViewModel = _node.DynamoViewModel;
420420
// If the feature is enabled from Dynamo experiment setting and if user interaction is on input port.
421-
return dynamoViewModel.EnableNodeAutoComplete && this.PortType == PortType.Input;
421+
return dynamoViewModel.EnableNodeAutoComplete;
422422
}
423423

424424
/// <summary>

src/DynamoCoreWpf/ViewModels/Search/NodeAutoCompleteSearchViewModel.cs

Lines changed: 86 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.Linq;
5+
using Dynamo.Engine;
6+
using Dynamo.Graph.Nodes;
57
using Dynamo.Properties;
68
using Dynamo.Search.SearchElements;
79
using Dynamo.Wpf.ViewModels;
@@ -39,7 +41,7 @@ private void InitializeDefaultAutoCompleteCandidates()
3941
var candidates = new List<NodeSearchElementViewModel>();
4042
// TODO: These are basic input types in Dynamo
4143
// This should be only served as a temporary default case.
42-
var queries = new List<string>(){"String", "Number Slider", "Integer Slider", "Number", "Boolean" };
44+
var queries = new List<string>(){"String", "Number Slider", "Integer Slider", "Number", "Boolean", "Watch", "Watch 3D", "Python Script"};
4345
foreach (var query in queries)
4446
{
4547
var foundNode = Search(query).FirstOrDefault();
@@ -57,27 +59,34 @@ internal void PopulateAutoCompleteCandidates()
5759

5860
searchElementsCache = GetMatchingSearchElements().ToList();
5961

60-
// If node match searchElements found, use default suggestions
62+
// If node match searchElements found, use default suggestions.
63+
// These default suggestions will be populated based on the port type.
6164
if (!searchElementsCache.Any())
6265
{
63-
searchElementsCache = DefaultResults.Select(e => e.Model).ToList();
64-
switch (PortViewModel.PortModel.GetInputPortType())
66+
if (PortViewModel.PortModel.PortType == PortType.Input)
6567
{
66-
case "int":
67-
FilteredResults = DefaultResults.Where(e => e.Name == "Number Slider" || e.Name == "Integer Slider").ToList();
68-
break;
69-
case "double":
70-
FilteredResults = DefaultResults.Where(e => e.Name == "Number Slider" || e.Name == "Integer Slider").ToList();
71-
break;
72-
case "string":
73-
FilteredResults = DefaultResults.Where(e => e.Name == "String").ToList();
74-
break;
75-
case "bool":
76-
FilteredResults = DefaultResults.Where(e => e.Name == "Boolean").ToList();
77-
break;
78-
default:
79-
FilteredResults = DefaultResults;
80-
break;
68+
switch (PortViewModel.PortModel.GetInputPortType())
69+
{
70+
case "int":
71+
FilteredResults = DefaultResults.Where(e => e.Name == "Number Slider" || e.Name == "Integer Slider").ToList();
72+
break;
73+
case "double":
74+
FilteredResults = DefaultResults.Where(e => e.Name == "Number Slider" || e.Name == "Integer Slider").ToList();
75+
break;
76+
case "string":
77+
FilteredResults = DefaultResults.Where(e => e.Name == "String").ToList();
78+
break;
79+
case "bool":
80+
FilteredResults = DefaultResults.Where(e => e.Name == "Boolean").ToList();
81+
break;
82+
default:
83+
FilteredResults = DefaultResults.Where(e => e.Name == "String" || e.Name == "Number Slider" || e.Name == "Integer Slider" || e.Name == "Number" || e.Name == "Boolean");
84+
break;
85+
}
86+
}
87+
else
88+
{
89+
FilteredResults = DefaultResults.Where(e => e.Name == "Watch" || e.Name == "Watch 3D" || e.Name == "Python Script").ToList();
8190
}
8291
}
8392
else
@@ -126,55 +135,95 @@ internal void SearchAutoCompleteCandidates(string input)
126135
internal IEnumerable<NodeSearchElement> GetMatchingSearchElements()
127136
{
128137
var elements = new List<NodeSearchElement>();
129-
var inputPortType = PortViewModel.PortModel.GetInputPortType();
138+
139+
var portType = String.Empty;
140+
141+
if (PortViewModel.PortModel.PortType == PortType.Input)
142+
{
143+
portType = PortViewModel.PortModel.GetInputPortType();
144+
}
145+
else if (PortViewModel.PortModel.PortType == PortType.Output)
146+
{
147+
portType = PortViewModel.PortModel.GetOutPortType();
148+
}
130149

131150
//List of input types that are skipped temporarily, and will display list of default suggestions instead.
132151
var skippedInputTypes = new List<string>() { "var", "object", "string", "bool", "int", "double" };
133152

134-
if (inputPortType == null)
153+
if (portType == null)
135154
{
136155
return elements;
137156
}
138157

139158
var core = dynamoViewModel.Model.LibraryServices.LibraryManagementCore;
140159

141160
//if inputPortType is an array, use just the typename
142-
var parseResult = ParserUtils.ParseWithCore($"dummyName:{ inputPortType};", core);
161+
var parseResult = ParserUtils.ParseWithCore($"dummyName:{ portType};", core);
143162
var ast = parseResult.CodeBlockNode.Children().FirstOrDefault() as IdentifierNode;
144163
//if parsing the type failed, revert to original string.
145-
inputPortType = ast != null ? ast.datatype.Name : inputPortType;
164+
portType = ast != null ? ast.datatype.Name : portType;
146165

147166
//check if the input port return type is in the skipped input types list
148-
if (skippedInputTypes.Any(s => s == inputPortType))
167+
if (skippedInputTypes.Any(s => s == portType))
149168
{
150169
return elements;
151170
}
152171

153172
//gather all ztsearchelements that are visible in search and filter using inputPortType and zt return type name.
154173
var ztSearchElements = Model.SearchEntries.OfType<ZeroTouchSearchElement>().Where(x => x.IsVisibleInSearch);
155-
foreach (var ztSearchElement in ztSearchElements)
174+
175+
if (PortViewModel.PortModel.PortType == PortType.Input)
156176
{
157-
//for now, remove rank from descriptors
158-
var returnTypeName = ztSearchElement.Descriptor.ReturnType.Name;
177+
foreach (var ztSearchElement in ztSearchElements)
178+
{
179+
//for now, remove rank from descriptors
180+
var returnTypeName = ztSearchElement.Descriptor.ReturnType.Name;
159181

160-
var descriptor = ztSearchElement.Descriptor;
161-
if ((returnTypeName == inputPortType)
162-
|| DerivesFrom(inputPortType, returnTypeName, core))
182+
var descriptor = ztSearchElement.Descriptor;
183+
if ((returnTypeName == portType) || DerivesFrom(portType, returnTypeName, core))
184+
{
185+
elements.Add(ztSearchElement);
186+
}
187+
}
188+
189+
// NodeModel nodes, match any output return type to inputport type name
190+
foreach (var element in Model.SearchEntries.OfType<NodeModelSearchElement>())
163191
{
164-
elements.Add(ztSearchElement);
192+
if (element.OutputParameters.Any(op => op == portType))
193+
{
194+
elements.Add(element);
195+
}
165196
}
166197
}
167-
168-
// NodeModel nodes, match any output return type to inputport type name
169-
foreach (var element in Model.SearchEntries.OfType<NodeModelSearchElement>())
198+
else if (PortViewModel.PortModel.PortType == PortType.Output)
170199
{
171-
if (element.OutputParameters.Any(op => op == inputPortType))
200+
foreach (var ztSearchElement in ztSearchElements)
172201
{
173-
elements.Add(element);
202+
foreach (var inputParameter in ztSearchElement.Descriptor.Parameters.Select((value, index) => new { value, index }))
203+
{
204+
if (inputParameter.value.Type.ToString() == portType || DerivesFrom(inputParameter.value.Type.ToString(), portType, core))
205+
{
206+
ztSearchElement.AutoCompletionNodeElementInfo.PortToConnect = ztSearchElement.Descriptor.Type == FunctionType.InstanceMethod ? inputParameter.index + 1 : inputParameter.index;
207+
elements.Add(ztSearchElement);
208+
break;
209+
}
210+
}
211+
}
212+
213+
// NodeModel nodes, match any output return type to inputport type name
214+
foreach (var element in Model.SearchEntries.OfType<NodeModelSearchElement>())
215+
{
216+
foreach (var inputParameter in element.InputParameters)
217+
{
218+
if (inputParameter.Item2 == portType)
219+
{
220+
elements.Add(element);
221+
}
222+
}
174223
}
175224
}
176225

177-
var comparer = new NodeSearchElementComparer(inputPortType, core);
226+
var comparer = new NodeSearchElementComparer(portType, core);
178227

179228
//first sort by type distance to input port type
180229
elements.Sort(comparer);

src/DynamoCoreWpf/ViewModels/Search/NodeSearchElementViewModel.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -275,16 +275,20 @@ protected virtual void CreateAndConnectToPort(object parameter)
275275
{
276276
// Placing the new node to the right of initial node
277277
adjustedX += initialNode.Width + 50;
278+
279+
// Create a new node based on node creation name and connection ports
280+
dynamoViewModel.ExecuteCommand(new DynamoModel.CreateAndConnectNodeCommand(id, initialNode.GUID,
281+
Model.CreationName, 0, Model.AutoCompletionNodeElementInfo.PortToConnect, adjustedX, 0, createAsDownStreamNode, false, true));
278282
}
279283
else
280284
{
281285
// Placing the new node to the left of initial node
282286
adjustedX -= initialNode.Width + 50;
283-
}
284287

285-
// Create a new node based on node creation name and connection ports
286-
dynamoViewModel.ExecuteCommand(new DynamoModel.CreateAndConnectNodeCommand(id, initialNode.GUID,
287-
Model.CreationName, 0, portModel.Index, adjustedX, 0, createAsDownStreamNode, false, true));
288+
// Create a new node based on node creation name and connection ports
289+
dynamoViewModel.ExecuteCommand(new DynamoModel.CreateAndConnectNodeCommand(id, initialNode.GUID,
290+
Model.CreationName, 0, portModel.Index, adjustedX, 0, createAsDownStreamNode, false, true));
291+
}
288292

289293
var inputNodes = initialNode.InputNodes.Values.Where(x => x != null).Select(y => y.Item2);
290294

@@ -306,10 +310,18 @@ private void AutoLayoutNodes(object sender, EventArgs e)
306310
{
307311
var nodeView = (NodeView) sender;
308312
var dynamoViewModel = nodeView.ViewModel.DynamoViewModel;
309-
var originalNodeId = nodeView.ViewModel.NodeModel.OutputNodes.Values.SelectMany(s => s.Select(t => t.Item2)).Distinct().FirstOrDefault().GUID;
310-
311-
dynamoViewModel.CurrentSpace.DoGraphAutoLayout(true, true, originalNodeId);
312313

314+
if (nodeView.ViewModel.NodeModel.OutputNodes.Count() > 0)
315+
{
316+
var originalNodeId = nodeView.ViewModel.NodeModel.OutputNodes.Values.SelectMany(s => s.Select(t => t.Item2)).Distinct().FirstOrDefault().GUID;
317+
dynamoViewModel.CurrentSpace.DoGraphAutoLayout(true, true, originalNodeId);
318+
}
319+
else if (nodeView.ViewModel.NodeModel.InputNodes.Count() > 0)
320+
{
321+
var originalNodeId = nodeView.ViewModel.NodeModel.InputNodes.Values.Select(s => s.Item2).Distinct().FirstOrDefault().GUID;
322+
dynamoViewModel.CurrentSpace.DoGraphAutoLayout(true, true, originalNodeId);
323+
}
324+
313325
DynamoSelection.Instance.ClearSelection();
314326

315327
// Close the undo action group once the node is created, connected and placed.

test/DynamoCoreWpfTests/NodeAutoCompleteSearchTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,29 @@ public void NodeSuggestions_InputPortBuiltInNode_AreCorrect()
210210
Assert.AreEqual(0, suggestions.Count());
211211
}
212212

213+
[Test]
214+
public void NodeSuggestions_OutputPortBuiltInNode_AreCorrect()
215+
{
216+
Open(@"UI\builtin_outputport_suggestion.dyn");
217+
218+
// Get the node view for a specific node in the graph
219+
NodeView nodeView = NodeViewWithGuid(Guid.Parse("a3412b9b1de54205a1fe6904697ffd5f").ToString());
220+
221+
// Get the output port type for the node.
222+
var outPorts = nodeView.ViewModel.OutPorts;
223+
Assert.AreEqual(1, outPorts.Count());
224+
225+
var port = outPorts[0].PortModel;
226+
var type = port.GetOutPortType();
227+
Assert.IsTrue(type.Contains("Line"));
228+
229+
// Trigger node autocomplete on the output port and verify the results.
230+
var searchViewModel = ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel;
231+
searchViewModel.PortViewModel = outPorts[0];
232+
var suggestions = searchViewModel.GetMatchingSearchElements();
233+
Assert.AreEqual(29, suggestions.Count());
234+
}
235+
213236
[Test]
214237
public void NodeSearchElementComparerSortsBasedOnTypeDistance()
215238
{

0 commit comments

Comments
 (0)