Skip to content

Commit

Permalink
Merge branch 'master' into NodeClusterVisual
Browse files Browse the repository at this point in the history
  • Loading branch information
QilongTang authored Feb 28, 2025
2 parents d9975e6 + cf4a00e commit 39f0cd9
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 14 deletions.
7 changes: 6 additions & 1 deletion src/DynamoCore/Graph/Nodes/PortModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
Expand Down Expand Up @@ -535,6 +535,11 @@ internal string GetOutPortType()

return null;
}

internal bool CanAutoCompleteInput()
{
return !(PortType == PortType.Input && Connectors?.FirstOrDefault()?.Start?.Owner != null);
}
}

/// <summary>
Expand Down
12 changes: 12 additions & 0 deletions src/DynamoCoreWpf/ViewModels/Core/PortViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,13 @@ private void AutoComplete(object parameter)

// Bail out from connect state
wsViewModel.CancelActiveState();

if (PortModel != null && !PortModel.CanAutoCompleteInput())
{
return;
}


wsViewModel.OnRequestNodeAutoCompleteSearch(ShowHideFlags.Show);
}

Expand All @@ -500,6 +507,11 @@ private void AutoCompleteCluster(object parameter)
// Bail out from connect state
wsViewModel.CancelActiveState();

if (PortModel != null && !PortModel.CanAutoCompleteInput())
{
return;
}

// Create mock nodes, currently Watch nodes (to avoid potential memory leak from Python Editor), and connect them to the input port
var targetNodeSearchEle = wsViewModel.NodeAutoCompleteSearchViewModel.DefaultResults.ToList()[5];
targetNodeSearchEle.CreateAndConnectCommand.Execute(wsViewModel.NodeAutoCompleteSearchViewModel.PortViewModel.PortModel);
Expand Down
154 changes: 141 additions & 13 deletions src/DynamoCoreWpf/ViewModels/Search/NodeSearchElementViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using Dynamo.Configuration;
using Dynamo.Controls;
using Dynamo.Graph.Nodes;
using Dynamo.Graph.Workspaces;
using Dynamo.Models;
using Dynamo.Search.SearchElements;
using Dynamo.Selection;
using Dynamo.Utilities;
using Dynamo.ViewModels;
using FontAwesome5;
using Prism.Commands;
Expand Down Expand Up @@ -255,7 +257,10 @@ public ImageSource LargeIcon
/// <param name="parameter">Port model to connect to</param>
protected virtual void CreateAndConnectToPort(object parameter)
{
var portModel = (PortModel) parameter;
var portModel = parameter as PortModel;

if (portModel is null) return;

var dynamoViewModel = searchViewModel.dynamoViewModel;

// Initialize a new undo action group before calling
Expand All @@ -264,9 +269,11 @@ protected virtual void CreateAndConnectToPort(object parameter)
{
undoRecorderGroup = dynamoViewModel.CurrentSpace.UndoRecorder.BeginActionGroup();

// Node auto layout can be performed correctly only when the positions and sizes
// of nodes are known, which is possible only after the node views are ready.
dynamoViewModel.NodeViewReady += AutoLayoutNodes;
// Node AutoLayout can be performed correctly only when the positions and sizes of the nodes are known,
// which is possible only after the node views have been updated. Note that the NodeView ready event is
// not sufficient for AutoLayout, as node customization has not yet been applied.
// Therefore, we postpone the AutoLayout execution to the next Idling event.
dynamoViewModel.NodeViewReady += PostAutoLayoutNodes;
}

var initialNode = portModel.Owner;
Expand Down Expand Up @@ -300,6 +307,13 @@ protected virtual void CreateAndConnectToPort(object parameter)
}
else
{
//Add the node that is about to be replaced in the selection in order to be considered while AutoLayout
NodeModel prevNode = portModel?.Connectors?.FirstOrDefault()?.Start?.Owner; //upstream we only have one node to check
if (prevNode != null)
{
DynamoSelection.Instance.Selection.Add(prevNode);
}

// Placing the new node to the left of initial node
adjustedX -= initialNode.Width + spacingBetweenNodes;

Expand All @@ -323,35 +337,149 @@ protected virtual bool CanCreateAndConnectToPort(object parameter)
{
// Do not auto connect code block node since default code block node do not have output port
if (Model.CreationName.Contains("Code Block")) return false;

// Avoid triggering auto complete for an already connected input.
PortModel portModel = parameter as PortModel;
return parameter as PortModel != null ? portModel.CanAutoCompleteInput() : true;
}

private Rect2D GetNodesBoundingBox(IEnumerable<NodeModel> nodes)
{
if (nodes is null || nodes.Count() == 0)
return Rect2D.Empty;

double minX = nodes.Min(node => node.Rect.TopLeft.X);
double maxX = nodes.Max(node => node.Rect.BottomRight.X);
double minY = nodes.Min(node => node.Rect.TopLeft.Y);
double maxY = nodes.Max(node => node.Rect.BottomRight.Y);

return new Rect2D(minX, minY, maxX - minX, maxY - minY);
}

// Determines whether an AutoLayout operation is needed for originalNode and its connected inputs or outputs.
// This is based on whether the input or output nodes connected to the originalNode intersect with other nodes in the model.
// If intersections occur, the function identifies the newly intersected nodes and returns true considering
// an additional AutoLayout operation is needed.
private bool AutoLayoutNeeded(NodeModel originalNode, IEnumerable<NodeModel> allNodes, bool newInput, out List<NodeModel> intersectedNodes)
{
//Collect all connected input or output nodes from the original node.
List<NodeModel> connectedNodesToConsider = new List<NodeModel>();
HashSet<Guid> connectedNodesGuids = new HashSet<Guid>();
connectedNodesGuids.Append(originalNode.GUID);
var originalPorts = newInput ? originalNode.InPorts : originalNode.OutPorts;

foreach (var port in originalPorts)
{
foreach (var connector in port.Connectors)
{
var otherNode = newInput ? connector?.Start?.Owner : connector?.End?.Owner;
if (otherNode != null)
{
connectedNodesToConsider.Add(otherNode);
connectedNodesGuids.Add(otherNode.GUID);
}
}
}

Rect2D connectedNodesBBox = GetNodesBoundingBox(connectedNodesToConsider);

//See if there are other nodes that intersect with our bbbox.
//If there are, check to see if they actually intersect with one of the
//connected nodes and select them for auto layout.
intersectedNodes = new List<NodeModel>();
bool realIntersection = false;
foreach (var node in allNodes)
{
if (connectedNodesGuids.Contains(node.GUID))
continue;

if (connectedNodesBBox.IntersectsWith(node.Rect) ||
connectedNodesBBox.Contains(node.Rect))
{
intersectedNodes.Add(node);
if (!realIntersection)
{
foreach (var connectedNode in connectedNodesToConsider)
{
if (node.Rect.IntersectsWith(connectedNode.Rect) ||
node.Rect.Contains(connectedNode.Rect) ||
connectedNode.Rect.Contains(node.Rect))
{
realIntersection = true;
break;
}
}
}
}
}

return true;
return realIntersection;
}

// We want to perform an AutoLayout operation only after all nodes have updated their UI.
// Therefore, we will queue the AutoLayout operation to execute during the next idle event.
private void PostAutoLayoutNodes(object sender, EventArgs e)
{
var nodeView = sender as NodeView;
if (nodeView is null)
return;

if (Application.Current?.Dispatcher != null)
{
Application.Current.Dispatcher.BeginInvoke(() => AutoLayoutNodes(sender, e), DispatcherPriority.ApplicationIdle);
}

var dynamoViewModel = nodeView.ViewModel.DynamoViewModel;

dynamoViewModel.NodeViewReady -= PostAutoLayoutNodes;
}

private void AutoLayoutNodes(object sender, EventArgs e)
{
var nodeView = (NodeView) sender;
var nodeView = sender as NodeView;
if (nodeView is null)
return;

var dynamoViewModel = nodeView.ViewModel.DynamoViewModel;

if (nodeView.ViewModel.NodeModel.OutputNodes.Count > 0)
Guid originalNodeId = Guid.Empty;
bool newInput = false;
//The new node was just placed and can only have one input or output node.
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);
originalNodeId = nodeView.ViewModel.NodeModel.OutputNodes.Values.SelectMany(s => s.Select(t => t.Item2)).Distinct().FirstOrDefault().GUID;
newInput = true;
}
else if (nodeView.ViewModel.NodeModel.InputNodes.Count > 0)
{
var originalNodeId = nodeView.ViewModel.NodeModel.InputNodes.Values.Select(s => s.Item2).Distinct().FirstOrDefault().GUID;
originalNodeId = nodeView.ViewModel.NodeModel.InputNodes.Values.Select(s => s.Item2).Distinct().FirstOrDefault().GUID;
}

if (originalNodeId != Guid.Empty)
{
dynamoViewModel.CurrentSpace.DoGraphAutoLayout(true, true, originalNodeId);

NodeModel originalNode = dynamoViewModel.Model.CurrentWorkspace.Nodes.FirstOrDefault(x => x.GUID == originalNodeId);

bool redoAutoLayout = AutoLayoutNeeded(originalNode,
dynamoViewModel.Model.CurrentWorkspace.Nodes,
newInput,
out List<NodeModel> intersectedNodes);

if (redoAutoLayout)
{
DynamoSelection.Instance.Selection.AddRange(intersectedNodes);
dynamoViewModel.CurrentSpace.DoGraphAutoLayout(true, true, originalNodeId);
}
}

DynamoSelection.Instance.ClearSelection();

// Close the undo action group once the node is created, connected and placed.
if (undoRecorderGroup != null)
{
undoRecorderGroup.Dispose();
undoRecorderGroup = null;

dynamoViewModel.NodeViewReady -= AutoLayoutNodes;
}
}

Expand Down

0 comments on commit 39f0cd9

Please sign in to comment.