Skip to content

Commit 0186c82

Browse files
BogdanZavuzavub
andauthored
DYN-8542 : dna cluster layout (#16009)
Co-authored-by: Bogdan Zavu <bogdan.zavu@autodesk.com>
1 parent ff82e54 commit 0186c82

File tree

3 files changed

+181
-170
lines changed

3 files changed

+181
-170
lines changed

src/DynamoCoreWpf/DynamoCoreWpf.csproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<UILib>true</UILib>
44
</PropertyGroup>
@@ -604,6 +604,7 @@
604604
<Compile Include="Utilities\GroupStyleItemSelector.cs" />
605605
<Compile Include="Utilities\LibraryDragAndDrop.cs" />
606606
<Compile Include="Utilities\MessageBoxUtilities.cs" />
607+
<Compile Include="Utilities\NodeAutoCompleteUtilities.cs" />
607608
<Compile Include="Utilities\NodeContextMenuBuilder.cs" />
608609
<Compile Include="Utilities\OnceDisposable.cs" />
609610
<Compile Include="TestInfrastructure\ConnectorMutator.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
using Dynamo.Graph.Nodes;
2+
using Dynamo.Graph.Workspaces;
3+
4+
using Dynamo.Selection;
5+
using Dynamo.Utilities;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Windows;
10+
using System.Windows.Threading;
11+
12+
namespace Dynamo.Wpf.Utilities
13+
{
14+
internal static class NodeAutoCompleteUtilities
15+
{
16+
// We want to perform an AutoLayout operation only after all nodes have updated their UI.
17+
// Therefore, we will queue the AutoLayout operation to execute during the next idle event.
18+
internal static void PostAutoLayoutNodes(WorkspaceModel wsModel,
19+
NodeModel queryNode,
20+
IEnumerable<NodeModel> misplacedNodes,
21+
bool clusterLayout,
22+
bool checkWorkspaceNodes,
23+
Action finalizer)
24+
{
25+
if (Application.Current?.Dispatcher != null)
26+
{
27+
Application.Current.Dispatcher.BeginInvoke(() => AutoLayoutNodes(wsModel,
28+
queryNode,
29+
misplacedNodes,
30+
clusterLayout,
31+
checkWorkspaceNodes,
32+
finalizer), DispatcherPriority.ApplicationIdle);
33+
}
34+
}
35+
36+
internal static Rect2D GetNodesBoundingBox(IEnumerable<NodeModel> nodes)
37+
{
38+
if (nodes is null || nodes.Count() == 0)
39+
return Rect2D.Empty;
40+
41+
double minX = nodes.Min(node => node.Rect.TopLeft.X);
42+
double maxX = nodes.Max(node => node.Rect.BottomRight.X);
43+
double minY = nodes.Min(node => node.Rect.TopLeft.Y);
44+
double maxY = nodes.Max(node => node.Rect.BottomRight.Y);
45+
46+
return new Rect2D(minX, minY, maxX - minX, maxY - minY);
47+
}
48+
49+
// Determines whether an AutoLayout operation is needed for a query node and other relevant nodes around it.
50+
// This is based on whether the relevant nodes intersect with other nodes in the model.
51+
// If intersections occur, the function identifies the newly intersected nodes and returns true considering
52+
// an additional AutoLayout operation is needed.
53+
internal static bool AutoLayoutNeeded(WorkspaceModel wsModel, NodeModel originalNode, IEnumerable<NodeModel> nodesToConsider, out List<NodeModel> intersectedNodes)
54+
{
55+
//Collect all connected input or output nodes from the original node.
56+
57+
var nodesGuidsToConsider = nodesToConsider.Select(n => n.GUID).ToHashSet();
58+
nodesGuidsToConsider.Append(originalNode.GUID);
59+
60+
Rect2D connectedNodesBBox = GetNodesBoundingBox(nodesToConsider);
61+
62+
//See if there are other nodes that intersect with our bbbox.
63+
//If there are, check to see if they actually intersect with one of the
64+
//connected nodes and select them for auto layout.
65+
intersectedNodes = new List<NodeModel>();
66+
bool realIntersection = false;
67+
foreach (var node in wsModel.Nodes)
68+
{
69+
if (nodesGuidsToConsider.Contains(node.GUID))
70+
continue;
71+
72+
if (connectedNodesBBox.IntersectsWith(node.Rect) ||
73+
connectedNodesBBox.Contains(node.Rect))
74+
{
75+
intersectedNodes.Add(node);
76+
if (!realIntersection)
77+
{
78+
foreach (var connectedNode in nodesToConsider)
79+
{
80+
if (node.Rect.IntersectsWith(connectedNode.Rect) ||
81+
node.Rect.Contains(connectedNode.Rect) ||
82+
connectedNode.Rect.Contains(node.Rect))
83+
{
84+
realIntersection = true;
85+
break;
86+
}
87+
}
88+
}
89+
}
90+
}
91+
92+
return realIntersection;
93+
}
94+
95+
/// <summary>
96+
/// Automatically arranges misplaced nodes based on the specified parameters.
97+
/// </summary>
98+
/// <param name="wsModel">The workspace model containing the nodes to be arranged.</param>
99+
/// <param name="queryNode">The node used as a starting point for the layout operation.</param>
100+
/// <param name="misplacedNodes">A collection of nodes that are not properly positioned and need to be arranged.</param>
101+
/// <param name="clusterLayout">Ensures misplaced nodes are positioned downstream. Nodes will be moved if necessary.</param>
102+
/// <param name="checkWorkspaceNodes">Specifies whether to consider existing nodes in the workspace during the layout operation.</param>
103+
/// <param name="finalizer">An action to be executed after the layout operation is complete, typically for cleanup or further adjustments.</param>
104+
internal static void AutoLayoutNodes(WorkspaceModel wsModel,
105+
NodeModel queryNode,
106+
IEnumerable<NodeModel> misplacedNodes,
107+
bool clusterLayout,
108+
bool checkWorkspaceNodes,
109+
Action finalizer)
110+
{
111+
DynamoSelection.Instance.Selection.AddRange(misplacedNodes);
112+
wsModel.DoGraphAutoLayout(true, true, queryNode.GUID);
113+
114+
// For large clusters of nodes, auto-layout may place nodes on both sides of the query node.
115+
// While the arrangement is technically fine, we move the entire group downstream for better consistency.
116+
if (clusterLayout)
117+
{
118+
double offset = -1;
119+
foreach (var node in misplacedNodes)
120+
{
121+
if (node.X < queryNode.X)
122+
{
123+
offset = Math.Max(offset, queryNode.X - node.X);
124+
}
125+
}
126+
127+
double balast = 50;
128+
if (offset > 0)
129+
{
130+
foreach (var node in misplacedNodes)
131+
{
132+
node.X = node.X + offset + queryNode.Width + balast;
133+
}
134+
135+
wsModel.DoGraphAutoLayout(true, true, queryNode.GUID);
136+
}
137+
}
138+
139+
// Check if the newly added nodes are still intersecting with other nodes in the workspace.
140+
// If so, perform an additional auto-layout pass. We only do this once in order to minimize
141+
// disruption to the user's workspace.
142+
if (checkWorkspaceNodes)
143+
{
144+
bool redoAutoLayout = AutoLayoutNeeded(wsModel, queryNode, misplacedNodes, out List<NodeModel> intersectedNodes);
145+
if (redoAutoLayout)
146+
{
147+
DynamoSelection.Instance.Selection.AddRange(intersectedNodes);
148+
wsModel.DoGraphAutoLayout(true, true, queryNode.GUID);
149+
}
150+
}
151+
152+
DynamoSelection.Instance.ClearSelection();
153+
154+
if (finalizer != null)
155+
{
156+
finalizer();
157+
}
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)