From 4fab6d97470fb44c279a5c88ce322d1d3ef7223c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:04:50 +0000 Subject: [PATCH] Separate graph layers and state machine state sub-graphs into distinct containers - Add StateSubGraphs dictionary to AnimGraphViewModel keyed by root node property name - BuildLayers Pass 2 now stores _StateResult sub-graphs in StateSubGraphs via AddStateSubGraph - PrefixStateMachineLayerNames iterates StateSubGraphs instead of Layers for renaming - BuildStateMachineOverviewLayers no longer removes from Layers (sub-graphs are separate) - AnimGraphViewer uses StateSubGraphs.TryGetValue for direct dictionary lookup by node index Co-authored-by: LoogLong <86428208+LoogLong@users.noreply.github.com> --- FModel/ViewModels/AnimGraphViewModel.cs | 55 ++++++++++++++++++------- FModel/Views/AnimGraphViewer.xaml.cs | 24 +++++------ 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/FModel/ViewModels/AnimGraphViewModel.cs b/FModel/ViewModels/AnimGraphViewModel.cs index e4e8cda1..61f7789d 100644 --- a/FModel/ViewModels/AnimGraphViewModel.cs +++ b/FModel/ViewModels/AnimGraphViewModel.cs @@ -77,7 +77,15 @@ public class AnimGraphViewModel public string PackageName { get; set; } = string.Empty; public List Nodes { get; } = []; public List Connections { get; } = []; + /// + /// Animation blueprint graph layers, each defined by a unique AnimGraphNode_Root. + /// public List Layers { get; } = []; + /// + /// State machine state sub-graphs, keyed by the _StateResult root node's property name + /// (derived from StateRootNodeIndex) for unique identification. + /// + public Dictionary StateSubGraphs { get; } = new(); /// /// Extracts animation graph node information from a UAnimBlueprintGeneratedClass. @@ -203,6 +211,7 @@ public class AnimGraphViewModel // Pass 2: Build state machine state sub-graphs from AnimGraphNode_StateResult nodes. // Each _StateResult node defines a state's sub-graph within a state machine. + // These are stored in StateSubGraphs keyed by the root node's property name. var stateResultRoots = vm.Nodes .Where(n => n.ExportType.EndsWith("_StateResult", StringComparison.OrdinalIgnoreCase)) .ToList(); @@ -211,7 +220,7 @@ public class AnimGraphViewModel { if (!assigned.Add(stateResultNode)) continue; var layerNodes = CollectUpstream(stateResultNode, upstreamOf, assigned); - AddLayer(vm, layerNodes, layerIndex++); + AddStateSubGraph(vm, layerNodes, stateResultNode.Name, layerIndex++); } // Fallback: any remaining unassigned nodes go into connected-component layers @@ -304,6 +313,27 @@ public class AnimGraphViewModel vm.Layers.Add(layer); } + /// + /// Creates an for a state machine state sub-graph + /// and stores it in keyed by the + /// root node's property name (from StateRootNodeIndex). + /// + private static void AddStateSubGraph(AnimGraphViewModel vm, List nodes, string rootNodePropName, int index) + { + var nodeSet = new HashSet(nodes); + var layer = new AnimGraphLayer { Name = GetLayerName(nodes, index) }; + layer.Nodes.AddRange(nodes); + + foreach (var conn in vm.Connections) + { + if (nodeSet.Contains(conn.SourceNode) && nodeSet.Contains(conn.TargetNode)) + layer.Connections.Add(conn); + } + + LayoutLayerNodes(layer); + vm.StateSubGraphs[rootNodePropName] = layer; + } + /// /// Renames state machine internal layers with a parent path prefix /// (e.g., "AnimGraph > Locomotion" for the overview, or @@ -322,8 +352,8 @@ public class AnimGraphViewModel } } - // Rename layers whose nodes belong to a state machine - foreach (var layer in vm.Layers) + // Rename state sub-graphs whose nodes belong to a state machine + foreach (var (key, layer) in vm.StateSubGraphs) { var smName = string.Empty; foreach (var node in layer.Nodes) @@ -342,17 +372,11 @@ public class AnimGraphViewModel if (!smParentLayer.TryGetValue(smName, out var parentName)) continue; - // Per-state layers are named by the _StateResult root node's property name (unique); - // use the _StateResult node's Name additional property for the display portion - if (!layer.Name.Equals(smName, StringComparison.OrdinalIgnoreCase)) - { - var stateResultNode = layer.Nodes.FirstOrDefault(n => - n.ExportType.EndsWith("_StateResult", StringComparison.OrdinalIgnoreCase)); - var stateName = stateResultNode?.AdditionalProperties.GetValueOrDefault("Name") ?? layer.Name; - layer.Name = $"{parentName}{SubGraphPathSeparator}{smName}{SubGraphPathSeparator}{stateName}"; - } - else - layer.Name = $"{parentName}{SubGraphPathSeparator}{smName}"; + // Per-state layers: use the _StateResult node's Name additional property for display + var stateResultNode = layer.Nodes.FirstOrDefault(n => + n.ExportType.EndsWith("_StateResult", StringComparison.OrdinalIgnoreCase)); + var stateName = stateResultNode?.AdditionalProperties.GetValueOrDefault("Name") ?? layer.Name; + layer.Name = $"{parentName}{SubGraphPathSeparator}{smName}{SubGraphPathSeparator}{stateName}"; } } @@ -383,8 +407,7 @@ public class AnimGraphViewModel var parentName = smParentLayer.GetValueOrDefault(sm.MachineName, "AnimGraph"); var overviewLayerName = $"{parentName}{SubGraphPathSeparator}{sm.MachineName}"; - // Remove existing internal layers with this name (they'll be replaced by the overview) - vm.Layers.RemoveAll(l => l.Name.Equals(overviewLayerName, StringComparison.OrdinalIgnoreCase)); + // State sub-graphs are now in StateSubGraphs (no need to remove from Layers) var overviewLayer = new AnimGraphLayer { Name = overviewLayerName }; var stateNodes = new List(); diff --git a/FModel/Views/AnimGraphViewer.xaml.cs b/FModel/Views/AnimGraphViewer.xaml.cs index 0cc4f840..d51faf1c 100644 --- a/FModel/Views/AnimGraphViewer.xaml.cs +++ b/FModel/Views/AnimGraphViewer.xaml.cs @@ -563,27 +563,23 @@ public partial class AnimGraphViewer } else if (node.IsStateMachineState) { - // State nodes within an overview: find the per-state layer by StateRootNodeIndex + // State nodes within an overview: find the per-state sub-graph by StateRootNodeIndex // The root node's property name is stored on the overview state node if (node.AdditionalProperties.TryGetValue("StateRootNodeName", out var rootNodeName) && - !string.IsNullOrEmpty(rootNodeName)) + !string.IsNullOrEmpty(rootNodeName) && + _viewModel.StateSubGraphs.TryGetValue(rootNodeName, out var stateLayer)) { - var stateLayer = _viewModel.Layers.FirstOrDefault(l => - l.Nodes.Any(n => n.Name == rootNodeName)); - if (stateLayer != null) + // If tab already exists, just select it + foreach (System.Windows.Controls.TabItem tab in LayerTabControl.Items) { - // If tab already exists, just select it - foreach (System.Windows.Controls.TabItem tab in LayerTabControl.Items) + if (tab.Tag == stateLayer) { - if (tab.Tag == stateLayer) - { - LayerTabControl.SelectedItem = tab; - return; - } + LayerTabControl.SelectedItem = tab; + return; } - AddLayerTab(stateLayer); - return; } + AddLayerTab(stateLayer); + return; } } else if (node.ExportType.Contains("StateMachine", StringComparison.OrdinalIgnoreCase))