Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable Node Autocompletion for output ports of Dynamo nodes. #11638

Merged
merged 10 commits into from
Apr 30, 2021
34 changes: 34 additions & 0 deletions src/DynamoCore/Graph/Nodes/PortModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,40 @@ internal string GetInputPortType()
}
return null;
}

/// <summary>
/// Returns the string representation of the fully qualified typename
/// where possible for the port if it's an output port. This method currently
/// returns a valid type for only Zero Touch, Builtin and NodeModel nodes,
/// and returns null otherwise. The string representation of the type also
/// contains the rank information of the type, e.g. Point[], or var[]..[].
/// </summary>
/// <returns>output port type</returns>
internal string GetOutPortType()
{
if (PortType == PortType.Input) return null;

var ztNode = Owner as DSFunction;
reddyashish marked this conversation as resolved.
Show resolved Hide resolved
if (ztNode != null)
{
var fd = ztNode.Controller.Definition;

string type = fd.ReturnType.ToString();

return type;
}

var nmNode = Owner as NodeModel;
if (nmNode != null)
{
var classType = nmNode.GetType();

var outPortAttribute = classType.GetCustomAttributes().OfType<OutPortTypesAttribute>().FirstOrDefault();

return outPortAttribute?.PortTypes.ElementAt(Index);
QilongTang marked this conversation as resolved.
Show resolved Hide resolved
}
return null;
}
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions src/DynamoCore/Search/SearchElements/NodeSearchElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ private void OnVisibilityChanged()
if (VisibilityChanged != null)
VisibilityChanged();
}
internal int PortToConnect { get; set; }
QilongTang marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Specifies whether or not this entry should appear in search.
Expand Down
2 changes: 1 addition & 1 deletion src/DynamoCoreWpf/ViewModels/Core/PortViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ private bool CanAutoComplete(object parameter)
{
DynamoViewModel dynamoViewModel = _node.DynamoViewModel;
// If the feature is enabled from Dynamo experiment setting and if user interaction is on input port.
return dynamoViewModel.EnableNodeAutoComplete && this.PortType == PortType.Input;
return dynamoViewModel.EnableNodeAutoComplete;
reddyashish marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Dynamo.Engine;
using Dynamo.Graph.Nodes;
using Dynamo.Properties;
using Dynamo.Search.SearchElements;
using Dynamo.Wpf.ViewModels;
Expand Down Expand Up @@ -57,8 +59,9 @@ internal void PopulateAutoCompleteCandidates()

searchElementsCache = GetMatchingSearchElements().ToList();

// If node match searchElements found, use default suggestions
if (!searchElementsCache.Any())
// If node match searchElements found, use default suggestions.
// These default suggestions will be populated for input ports only.
if (!searchElementsCache.Any() && PortViewModel.PortModel.PortType == PortType.Input)
{
searchElementsCache = DefaultResults.Select(e => e.Model).ToList();
switch (PortViewModel.PortModel.GetInputPortType())
Expand Down Expand Up @@ -136,55 +139,95 @@ private bool QuerySearchElements(NodeSearchElement e, string input)
internal IEnumerable<NodeSearchElement> GetMatchingSearchElements()
{
var elements = new List<NodeSearchElement>();
var inputPortType = PortViewModel.PortModel.GetInputPortType();

var portType = String.Empty;

if (PortViewModel.PortModel.PortType == PortType.Input)
{
portType = PortViewModel.PortModel.GetInputPortType();
}
else if (PortViewModel.PortModel.PortType == PortType.Output)
{
portType = PortViewModel.PortModel.GetOutPortType();
}

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

if (inputPortType == null)
if (portType == null)
{
return elements;
}

var core = dynamoViewModel.Model.LibraryServices.LibraryManagementCore;

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

//check if the input port return type is in the skipped input types list
if (skippedInputTypes.Any(s => s == inputPortType))
if (skippedInputTypes.Any(s => s == portType))
{
return elements;
}

//gather all ztsearchelements that are visible in search and filter using inputPortType and zt return type name.
var ztSearchElements = Model.SearchEntries.OfType<ZeroTouchSearchElement>().Where(x => x.IsVisibleInSearch);
foreach (var ztSearchElement in ztSearchElements)

if (PortViewModel.PortModel.PortType == PortType.Input)
{
//for now, remove rank from descriptors
var returnTypeName = ztSearchElement.Descriptor.ReturnType.Name;
foreach (var ztSearchElement in ztSearchElements)
{
//for now, remove rank from descriptors
var returnTypeName = ztSearchElement.Descriptor.ReturnType.Name;

var descriptor = ztSearchElement.Descriptor;
if ((returnTypeName == portType) || DerivesFrom(portType, returnTypeName, core))
{
elements.Add(ztSearchElement);
}
}

var descriptor = ztSearchElement.Descriptor;
if ((returnTypeName == inputPortType)
|| DerivesFrom(inputPortType, returnTypeName, core))
// NodeModel nodes, match any output return type to inputport type name
foreach (var element in Model.SearchEntries.OfType<NodeModelSearchElement>())
{
elements.Add(ztSearchElement);
if (element.OutputParameters.Any(op => op == portType))
{
elements.Add(element);
}
}
}

// NodeModel nodes, match any output return type to inputport type name
foreach (var element in Model.SearchEntries.OfType<NodeModelSearchElement>())
else if (PortViewModel.PortModel.PortType == PortType.Output)
{
if (element.OutputParameters.Any(op => op == inputPortType))
foreach (var ztSearchElement in ztSearchElements)
{
elements.Add(element);
foreach (var inputParameter in ztSearchElement.Descriptor.Parameters.Select((value, index) => new { value, index }))
{
if (inputParameter.value.Type.ToString() == portType || DerivesFrom(inputParameter.value.Type.ToString(), portType, core))
{
ztSearchElement.PortToConnect = ztSearchElement.Descriptor.Type == FunctionType.InstanceMethod ? inputParameter.index + 1 : inputParameter.index;
elements.Add(ztSearchElement);
break;
}
}
}

// NodeModel nodes, match any output return type to inputport type name
foreach (var element in Model.SearchEntries.OfType<NodeModelSearchElement>())
{
foreach (var inputParameter in element.InputParameters)
{
if (inputParameter.Item2 == portType)
{
elements.Add(element);
}
}
}
}

var comparer = new NodeSearchElementComparer(inputPortType, core);
var comparer = new NodeSearchElementComparer(portType, core);

//first sort by type distance to input port type
elements.Sort(comparer);
Expand Down
26 changes: 19 additions & 7 deletions src/DynamoCoreWpf/ViewModels/Search/NodeSearchElementViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,16 +275,20 @@ protected virtual void CreateAndConnectToPort(object parameter)
{
// Placing the new node to the right of initial node
adjustedX += initialNode.Width + 50;

// Create a new node based on node creation name and connection ports
dynamoViewModel.ExecuteCommand(new DynamoModel.CreateAndConnectNodeCommand(id, initialNode.GUID,
Model.CreationName, 0, Model.PortToConnect, adjustedX, 0, createAsDownStreamNode, false, true));
}
else
{
// Placing the new node to the left of initial node
adjustedX -= initialNode.Width + 50;
}

// Create a new node based on node creation name and connection ports
dynamoViewModel.ExecuteCommand(new DynamoModel.CreateAndConnectNodeCommand(id, initialNode.GUID,
Model.CreationName, 0, portModel.Index, adjustedX, 0, createAsDownStreamNode, false, true));
// Create a new node based on node creation name and connection ports
dynamoViewModel.ExecuteCommand(new DynamoModel.CreateAndConnectNodeCommand(id, initialNode.GUID,
Model.CreationName, 0, portModel.Index, adjustedX, 0, createAsDownStreamNode, false, true));
}

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

Expand All @@ -306,10 +310,18 @@ private void AutoLayoutNodes(object sender, EventArgs e)
{
var nodeView = (NodeView) sender;
var dynamoViewModel = nodeView.ViewModel.DynamoViewModel;
var originalNodeId = nodeView.ViewModel.NodeModel.OutputNodes.Values.SelectMany(s => s.Select(t => t.Item2)).Distinct().FirstOrDefault().GUID;

dynamoViewModel.CurrentSpace.DoGraphAutoLayout(true, true, originalNodeId);

if (nodeView.ViewModel.NodeModel.OutputNodes.Count() > 0)
{
var originalNodeId = nodeView.ViewModel.NodeModel.OutputNodes.Values.SelectMany(s => s.Select(t => t.Item2)).Distinct().FirstOrDefault().GUID;
dynamoViewModel.CurrentSpace.DoGraphAutoLayout(true, true, originalNodeId);
}
else if (nodeView.ViewModel.NodeModel.InputNodes.Count() > 0)
{
var originalNodeId = nodeView.ViewModel.NodeModel.InputNodes.Values.Select(s => s.Item2).Distinct().FirstOrDefault().GUID;
dynamoViewModel.CurrentSpace.DoGraphAutoLayout(true, true, originalNodeId);
}

DynamoSelection.Instance.ClearSelection();

// Close the undo action group once the node is created, connected and placed.
Expand Down
23 changes: 23 additions & 0 deletions test/DynamoCoreWpfTests/NodeAutoCompleteSearchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,29 @@ public void NodeSuggestions_InputPortBuiltInNode_AreCorrect()
Assert.AreEqual(0, suggestions.Count());
}

[Test]
public void NodeSuggestions_OutputPortBuiltInNode_AreCorrect()
{
Open(@"UI\builtin_outputport_suggestion.dyn");

// Get the node view for a specific node in the graph
NodeView nodeView = NodeViewWithGuid(Guid.Parse("a3412b9b1de54205a1fe6904697ffd5f").ToString());

// Get the output port type for the node.
var outPorts = nodeView.ViewModel.OutPorts;
Assert.AreEqual(1, outPorts.Count());

var port = outPorts[0].PortModel;
var type = port.GetOutPortType();
Assert.IsTrue(type.Contains("Line"));

// Trigger node autocomplete on the output port and verify the results.
var searchViewModel = ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel;
searchViewModel.PortViewModel = outPorts[0];
var suggestions = searchViewModel.GetMatchingSearchElements();
Assert.AreEqual(29, suggestions.Count());
}

[Test]
public void NodeSearchElementComparerSortsBasedOnTypeDistance()
{
Expand Down
107 changes: 107 additions & 0 deletions test/UI/builtin_outputport_suggestion.dyn
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{
"Uuid": "87442094-a012-4db1-9432-789dd1c20ba8",
"IsCustomNode": false,
"Description": null,
"Name": "builtin_outputport_suggestion",
"ElementResolver": {
"ResolutionMap": {}
},
"Inputs": [],
"Outputs": [],
"Nodes": [
{
"ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore",
"NodeType": "FunctionNode",
"FunctionSignature": "Autodesk.DesignScript.Geometry.Line.ByStartPointDirectionLength@Autodesk.DesignScript.Geometry.Point,Autodesk.DesignScript.Geometry.Vector,double",
"Id": "a3412b9b1de54205a1fe6904697ffd5f",
"Inputs": [
{
"Id": "c91dc446d4c743d0a2b7b6c6f7242b0e",
"Name": "startPoint",
"Description": "Point",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
},
{
"Id": "75074097aebb48019669c3b0d01713b4",
"Name": "direction",
"Description": "Vector",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
},
{
"Id": "e54ffcac0df44a5e8c7ab2969ea74efc",
"Name": "length",
"Description": "double\nDefault value : 1",
"UsingDefaultValue": true,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Outputs": [
{
"Id": "76e51c15f7c6442e920a58323848531d",
"Name": "Line",
"Description": "Line",
"UsingDefaultValue": false,
"Level": 2,
"UseLevels": false,
"KeepListStructure": false
}
],
"Replication": "Auto",
"Description": "Create a straight Line starting at start Point, extending in Vector direction by specified length.\n\nLine.ByStartPointDirectionLength (startPoint: Point, direction: Vector, length: double = 1): Line"
}
],
"Connectors": [],
"Dependencies": [],
"NodeLibraryDependencies": [],
"Thumbnail": null,
"GraphDocumentationURL": null,
"ExtensionWorkspaceData": [],
"Author": "None provided",
"Bindings": [],
"View": {
"Dynamo": {
"ScaleFactor": 1.0,
"HasRunWithoutCrash": true,
"IsVisibleInDynamoLibrary": true,
"Version": "2.12.0.4765",
"RunType": "Automatic",
"RunPeriod": "1000"
},
"Camera": {
"Name": "Background Preview",
"EyeX": -17.0,
"EyeY": 24.0,
"EyeZ": 50.0,
"LookX": 12.0,
"LookY": -13.0,
"LookZ": -58.0,
"UpX": 0.0,
"UpY": 1.0,
"UpZ": 0.0
},
"NodeViews": [
{
"ShowGeometry": true,
"Name": "Line.ByStartPointDirectionLength",
"Id": "a3412b9b1de54205a1fe6904697ffd5f",
"IsSetAsInput": false,
"IsSetAsOutput": false,
"Excluded": false,
"X": 389.0,
"Y": 255.5
}
],
"Annotations": [],
"X": 0.0,
"Y": 0.0,
"Zoom": 1.0
}
}