@@ -19,10 +19,7 @@ public static class LayoutExtensions
1919 /// to set up and run the graph layout algorithm.
2020 /// </summary>
2121 /// <param name="workspace">Workspace on which graph layout will be performed.</param>
22- /// <param name="reuseUndoRedoGroup">If true, skip initializing new undo action group.</param>
23- /// <param name="isNodeAutoComplete"></param>
24- /// <param name="originalNodeGUID"></param>
25- internal static List < GraphLayout . Graph > DoGraphAutoLayout ( this WorkspaceModel workspace , bool reuseUndoRedoGroup = false , bool isNodeAutoComplete = false , Guid ? originalNodeGUID = null )
22+ internal static List < GraphLayout . Graph > DoGraphAutoLayout ( this WorkspaceModel workspace )
2623 {
2724 if ( workspace . Nodes . Count ( ) < 2 ) return null ;
2825
@@ -39,36 +36,117 @@ public static class LayoutExtensions
3936 GenerateCombinedGraph ( workspace , isGroupLayout , out layoutSubgraphs , out subgraphClusters ) ;
4037
4138 //only record graph layout undo when it is not node autocomplete
42- if ( ! isNodeAutoComplete )
39+ RecordUndoGraphLayout ( workspace , isGroupLayout , false ) ;
40+
41+ // Generate subgraphs separately for each cluster
42+ subgraphClusters . ForEach (
43+ x => GenerateSeparateSubgraphs ( new HashSet < GraphLayout . Node > ( x ) , layoutSubgraphs ) ) ;
44+
45+ // Deselect all nodes
46+ subgraphClusters . ForEach ( c => c . ForEach ( x => x . IsSelected = false ) ) ;
47+
48+ var activeComponents = layoutSubgraphs . Skip ( 1 ) . ToList ( ) ;
49+ // Run layout algorithm for each subgraph
50+ activeComponents . ForEach ( g => RunLayoutSubgraph ( g , isGroupLayout ) ) ;
51+
52+ AvoidSubgraphOverlap ( layoutSubgraphs ) ;
53+
54+ SaveLayoutGraph ( workspace , layoutSubgraphs ) ;
55+
56+ // Restore the workspace model selection information
57+ selection . ToList ( ) . ForEach ( x => x . Select ( ) ) ;
58+
59+ return layoutSubgraphs ;
60+ }
61+ /// <summary>
62+ /// This function wraps a few methods on the workspace model layer
63+ /// to set up and run the graph layout algorithm.
64+ /// </summary>
65+ /// <param name="workspace">Workspace on which graph layout will be performed.</param>
66+ /// <param name="originalNodeGUID"></param>
67+ /// <param name="newNodes"></param>
68+ /// <param name="misplacedNodes"></param>
69+ /// <param name="portType"></param>
70+ internal static List < GraphLayout . Graph > DoGraphAutoLayoutAutocomplete ( this WorkspaceModel workspace ,
71+ Guid originalNodeGUID ,
72+ IEnumerable < NodeModel > newNodes ,
73+ IEnumerable < NodeModel > misplacedNodes , PortType portType )
74+ {
75+ if ( workspace . Nodes . Count ( ) < 2 ) return null ;
76+
77+ const bool isGroupLayout = false ;
78+
79+ List < GraphLayout . Graph > layoutSubgraphs ;
80+ List < List < GraphLayout . Node > > subgraphClusters ;
81+
82+ GenerateCombinedGraph ( workspace , isGroupLayout , out layoutSubgraphs , out subgraphClusters ) ;
83+
84+ var misplacedGuidSet = misplacedNodes . Select ( x => x . GUID ) . ToHashSet ( ) ;
85+ foreach ( var node in layoutSubgraphs . First ( ) . Nodes )
4386 {
44- RecordUndoGraphLayout ( workspace , isGroupLayout , reuseUndoRedoGroup ) ;
87+ //this is a virtual IsSelected - it doesnt change the workspace node models but marks it as touchable to the algorithm
88+ node . IsSelected = node . Id == originalNodeGUID || misplacedGuidSet . Contains ( node . Id ) ;
4589 }
46-
90+
4791 // Generate subgraphs separately for each cluster
4892 subgraphClusters . ForEach (
4993 x => GenerateSeparateSubgraphs ( new HashSet < GraphLayout . Node > ( x ) , layoutSubgraphs ) ) ;
5094
5195 // Deselect all nodes
5296 subgraphClusters . ForEach ( c => c . ForEach ( x => x . IsSelected = false ) ) ;
5397
98+ var activeComponents = layoutSubgraphs . Skip ( 1 ) . ToList ( ) ;
99+ var targetSubgraph = activeComponents . FirstOrDefault ( x => x . Nodes . Any ( x => x . Id == originalNodeGUID ) ) ;
100+ var targetNode = targetSubgraph . Nodes . First ( x => x . Id == originalNodeGUID ) ;
101+
54102 // Run layout algorithm for each subgraph
55- layoutSubgraphs . Skip ( 1 ) . ToList ( ) . ForEach ( g => RunLayoutSubgraph ( g , isGroupLayout ) ) ;
56- AvoidSubgraphOverlap ( layoutSubgraphs ) ;
103+ activeComponents . ForEach ( g => RunLayoutSubgraph ( g , isGroupLayout ) ) ;
57104
58- if ( isNodeAutoComplete )
105+ //original node shouldn't move: move others around
106+ var diffX = targetNode . X - targetNode . InitialX ;
107+ var diffY = targetNode . Y - targetNode . InitialY ;
108+ foreach ( var relatedNode in targetSubgraph . Nodes )
59109 {
60- SaveLayoutGraphForNodeAutoComplete ( workspace , layoutSubgraphs , originalNodeGUID ) ;
110+ relatedNode . X -= diffX ;
111+ relatedNode . Y -= diffY ;
61112 }
62- else {
63- SaveLayoutGraph ( workspace , layoutSubgraphs ) ;
113+
114+ if ( portType == PortType . Output )
115+ {
116+ double minX = double . MaxValue ;
117+ foreach ( var relatedNode in targetSubgraph . Nodes )
118+ {
119+ if ( newNodes . Any ( x => x . GUID == relatedNode . Id ) )
120+ {
121+ minX = Math . Min ( minX , relatedNode . X ) ;
122+ }
123+ }
124+ double targetMinX = targetNode . X + targetNode . Width + GraphLayout . Graph . HorizontalNodeDistance ;
125+ var displacement = minX == double . MaxValue ? 0 : targetMinX - minX ;
126+
127+ foreach ( var relatedNode in targetSubgraph . Nodes )
128+ {
129+ if ( newNodes . Any ( x => x . GUID == relatedNode . Id ) )
130+ {
131+ relatedNode . X += displacement ;
132+ }
133+ }
64134 }
65135
66- // Restore the workspace model selection information
67- selection . ToList ( ) . ForEach ( x => x . Select ( ) ) ;
136+ // right now cluster nodes only work on outputs, single nodes dont need to be relayouted as above
137+
138+
139+ AvoidSubgraphOverlap ( layoutSubgraphs , originalNodeGUID ) ;
140+
141+ var maybeUpdateSet = workspace . Nodes . Any ( x => x . IsTransient ) ?
142+ workspace . Nodes . Where ( x => x . GUID == originalNodeGUID || misplacedGuidSet . Contains ( x . GUID ) ) :
143+ workspace . Nodes ;
144+ SaveLayoutGraphForNodeAutoComplete ( workspace , maybeUpdateSet . ToList ( ) , layoutSubgraphs , originalNodeGUID ) ;
68145
69146 return layoutSubgraphs ;
70147 }
71148
149+
72150 /// <summary>
73151 /// <param name="workspace">A <see cref="WorkspaceModel"/>.</param>
74152 /// This method extracts all models from the workspace and puts them
@@ -77,7 +155,7 @@ public static class LayoutExtensions
77155 /// <param name="layoutSubgraphs"></param>
78156 /// <param name="subgraphClusters"></param>
79157 /// </summary>
80- private static void GenerateCombinedGraph ( this WorkspaceModel workspace , bool isGroupLayout ,
158+ private static void GenerateCombinedGraph ( this WorkspaceModel workspace , bool isGroupLayout ,
81159 out List < GraphLayout . Graph > layoutSubgraphs , out List < List < GraphLayout . Node > > subgraphClusters )
82160 {
83161 layoutSubgraphs = new List < GraphLayout . Graph >
@@ -135,8 +213,8 @@ private static void GenerateCombinedGraph(this WorkspaceModel workspace, bool is
135213 combinedGraph . AddNode ( pin . GUID ,
136214 pin . Width ,
137215 pin . Height ,
138- pin . CenterX ,
139- pin . CenterY ,
216+ pin . CenterX ,
217+ pin . CenterY ,
140218 pin . IsSelected || DynamoSelection . Instance . Selection . Count == 0 ) ;
141219 }
142220 }
@@ -154,12 +232,12 @@ private static void GenerateCombinedGraph(this WorkspaceModel workspace, bool is
154232 var groupsFiltered = workspace . Annotations . Where ( g => ! workspace . Annotations . ContainsModel ( g ) ) ;
155233
156234 startGroup = groupsFiltered
157- . Where ( g => g . ContainsModel ( edge . Start . Owner ) ||
235+ . Where ( g => g . ContainsModel ( edge . Start . Owner ) ||
158236 g . Nodes . OfType < AnnotationModel > ( ) . SelectMany ( x => x . Nodes ) . Contains ( edge . Start . Owner ) )
159237 . FirstOrDefault ( ) ;
160238
161239 endGroup = groupsFiltered
162- . Where ( g => g . ContainsModel ( edge . End . Owner ) ||
240+ . Where ( g => g . ContainsModel ( edge . End . Owner ) ||
163241 g . Nodes . OfType < AnnotationModel > ( ) . SelectMany ( x => x . Nodes ) . Contains ( edge . End . Owner ) )
164242 . FirstOrDefault ( ) ;
165243
@@ -272,11 +350,11 @@ private static void AddConnectorEdgesIncludingPinEdges(GraphLayout.Graph combine
272350
273351 // Add an edge between the left-most (start) node
274352 // (its corresponding port) to which this connector connects, and the first connectorPin.
275- combinedGraph . AddEdge ( startGuid ,
353+ combinedGraph . AddEdge ( startGuid ,
276354 connector . ConnectorPinModels [ 0 ] . GUID ,
277- connector . Start . Center . X ,
278- connector . Start . Center . Y ,
279- connector . ConnectorPinModels [ 0 ] . CenterX ,
355+ connector . Start . Center . X ,
356+ connector . Start . Center . Y ,
357+ connector . ConnectorPinModels [ 0 ] . CenterX ,
280358 connector . ConnectorPinModels [ 0 ] . CenterY ) ;
281359
282360 // Add an edge between all other connectorPins that follow,
@@ -285,11 +363,11 @@ private static void AddConnectorEdgesIncludingPinEdges(GraphLayout.Graph combine
285363 {
286364 if ( i != connector . ConnectorPinModels . Count - 1 )
287365 {
288- combinedGraph . AddEdge ( connector . ConnectorPinModels [ i ] . GUID ,
366+ combinedGraph . AddEdge ( connector . ConnectorPinModels [ i ] . GUID ,
289367 connector . ConnectorPinModels [ i + 1 ] . GUID ,
290- connector . ConnectorPinModels [ i ] . CenterX ,
291- connector . ConnectorPinModels [ i ] . CenterY ,
292- connector . ConnectorPinModels [ i + 1 ] . CenterX ,
368+ connector . ConnectorPinModels [ i ] . CenterX ,
369+ connector . ConnectorPinModels [ i ] . CenterY ,
370+ connector . ConnectorPinModels [ i + 1 ] . CenterX ,
293371 connector . ConnectorPinModels [ i + 1 ] . CenterY ) ;
294372 }
295373 }
@@ -298,9 +376,9 @@ private static void AddConnectorEdgesIncludingPinEdges(GraphLayout.Graph combine
298376 // (its corresponding port) to which this connector connects.
299377 combinedGraph . AddEdge ( connector . ConnectorPinModels [ connector . ConnectorPinModels . Count - 1 ] . GUID ,
300378 endGuid ,
301- connector . ConnectorPinModels [ connector . ConnectorPinModels . Count - 1 ] . CenterX ,
302- connector . ConnectorPinModels [ connector . ConnectorPinModels . Count - 1 ] . CenterY ,
303- connector . End . Center . X ,
379+ connector . ConnectorPinModels [ connector . ConnectorPinModels . Count - 1 ] . CenterX ,
380+ connector . ConnectorPinModels [ connector . ConnectorPinModels . Count - 1 ] . CenterY ,
381+ connector . End . Center . X ,
304382 connector . End . Center . Y ) ;
305383 }
306384
@@ -444,7 +522,7 @@ private static void RunLayoutSubgraph(GraphLayout.Graph graph, bool isGroupLayou
444522 /// This method repeatedly shifts subgraphs away vertically from each other
445523 /// when there are any two nodes from different subgraphs overlapping.
446524 /// </summary>
447- private static void AvoidSubgraphOverlap ( List < GraphLayout . Graph > layoutSubgraphs )
525+ private static void AvoidSubgraphOverlap ( List < GraphLayout . Graph > layoutSubgraphs , Guid ? anchorNodeGuid = null )
448526 {
449527 bool done ;
450528
@@ -454,8 +532,10 @@ private static void AvoidSubgraphOverlap(List<GraphLayout.Graph> layoutSubgraphs
454532
455533 foreach ( var g1 in layoutSubgraphs . Skip ( 1 ) )
456534 {
535+ var g1HasAnchor = anchorNodeGuid != null && g1 . Nodes . Any ( x => x . Id == anchorNodeGuid . Value ) ;
457536 foreach ( var g2 in layoutSubgraphs . Skip ( 1 ) )
458537 {
538+ var g2HasAnchor = anchorNodeGuid != null && g2 . Nodes . Any ( x => x . Id == anchorNodeGuid . Value ) ;
459539 // The first subgraph's center point must be higher than the second subgraph
460540 if ( ! g1 . Equals ( g2 ) && ( g1 . GraphCenterY + g1 . OffsetY <= g2 . GraphCenterY + g2 . OffsetY ) )
461541 {
@@ -472,8 +552,9 @@ private static void AvoidSubgraphOverlap(List<GraphLayout.Graph> layoutSubgraphs
472552 ( ( node2 . X <= node1 . X ) && ( node2 . X + node2 . Width + GraphLayout . Graph . HorizontalNodeDistance > node1 . X ) ) ) )
473553 {
474554 // Shift the first subgraph to the top and the second subgraph to the bottom
475- g1 . OffsetY -= 5 ;
476- g2 . OffsetY += 5 ;
555+ // both cant have anchors simultaneously, since are different groups and there is only one anchor
556+ if ( ! g1HasAnchor ) g1 . OffsetY -= 5 ;
557+ if ( ! g2HasAnchor ) g2 . OffsetY += 5 ;
477558 done = false ;
478559 }
479560 if ( ! done ) break ;
@@ -586,10 +667,10 @@ private static void SaveLayoutGraph(this WorkspaceModel workspace, List<GraphLay
586667 /// This method pushes changes from the GraphLayout.Graph objects
587668 /// back to the workspace models, but only for nodes placed by NodeAutocomplete.
588669 /// </summary>
589- private static void SaveLayoutGraphForNodeAutoComplete ( this WorkspaceModel workspace , List < GraphLayout . Graph > layoutSubgraphs , Guid ? originalNodeGUID )
670+ private static void SaveLayoutGraphForNodeAutoComplete ( WorkspaceModel workspace , List < NodeModel > nodes , List < GraphLayout . Graph > layoutSubgraphs , Guid ? originalNodeGUID )
590671 {
591672 // Assign coordinates to nodes outside groups
592- foreach ( var node in workspace . Nodes )
673+ foreach ( var node in nodes )
593674 {
594675 GraphLayout . Graph graph = layoutSubgraphs
595676 . FirstOrDefault ( g => g . FindNode ( node . GUID ) != null ) ;
0 commit comments