mirror of
https://github.com/4sval/FModel.git
synced 2026-03-31 06:06:25 -05:00
Add StateMachine double-click to open sub-graph tab
Read BakedStateMachines from the animation blueprint class to extract machine names. Associate FAnimNode_StateMachine nodes with their machine name via StateMachineIndexInClass, and mark internal state root nodes with BelongsToStateMachine for correct layer naming. Extend TryOpenSubGraph to handle both LinkedAnimLayer and StateMachine node types for double-click tab navigation. Co-authored-by: LoogLong <86428208+LoogLong@users.noreply.github.com>
This commit is contained in:
parent
1d2ef5905e
commit
08a9253f4e
|
|
@ -131,6 +131,9 @@ public class AnimGraphViewModel
|
|||
ResolveConnections(cdo, animNodeProps, nodeByName, vm);
|
||||
}
|
||||
|
||||
// Associate state machine nodes with their baked machine names
|
||||
AssociateStateMachineNames(animBlueprintClass, cdo, animNodeProps, nodeByName);
|
||||
|
||||
// Group nodes into layers (connected subgraphs)
|
||||
BuildLayers(vm);
|
||||
|
||||
|
|
@ -225,6 +228,14 @@ public class AnimGraphViewModel
|
|||
!string.IsNullOrEmpty(rootName))
|
||||
return rootName;
|
||||
|
||||
// Check if any node belongs to a baked state machine
|
||||
var smNode = nodes.FirstOrDefault(n =>
|
||||
n.AdditionalProperties.TryGetValue("BelongsToStateMachine", out _));
|
||||
if (smNode != null &&
|
||||
smNode.AdditionalProperties.TryGetValue("BelongsToStateMachine", out var smName) &&
|
||||
!string.IsNullOrEmpty(smName))
|
||||
return smName;
|
||||
|
||||
var stateMachine = nodes.FirstOrDefault(n =>
|
||||
n.ExportType.Contains("StateMachine", StringComparison.OrdinalIgnoreCase));
|
||||
if (stateMachine != null)
|
||||
|
|
@ -250,6 +261,84 @@ public class AnimGraphViewModel
|
|||
return exportType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads BakedStateMachines from the animation blueprint class to associate
|
||||
/// FAnimNode_StateMachine nodes with their machine names and mark internal
|
||||
/// state root nodes so they can be grouped into correctly named layers.
|
||||
/// </summary>
|
||||
private static void AssociateStateMachineNames(UClass animBlueprintClass, UObject? cdo,
|
||||
List<(string name, string structType)> animNodeProps,
|
||||
Dictionary<string, AnimGraphNode> nodeByName)
|
||||
{
|
||||
// BakedStateMachines is a UPROPERTY on UAnimBlueprintGeneratedClass
|
||||
// Try reading from both the class and CDO
|
||||
UScriptArray? bakedMachines = null;
|
||||
if (animBlueprintClass.TryGetValue(out UScriptArray classBaked, "BakedStateMachines"))
|
||||
bakedMachines = classBaked;
|
||||
else if (cdo != null && cdo.TryGetValue(out UScriptArray cdoBaked, "BakedStateMachines"))
|
||||
bakedMachines = cdoBaked;
|
||||
|
||||
if (bakedMachines == null || bakedMachines.Properties.Count == 0)
|
||||
return;
|
||||
|
||||
for (var machineIdx = 0; machineIdx < bakedMachines.Properties.Count; machineIdx++)
|
||||
{
|
||||
if (bakedMachines.Properties[machineIdx].GetValue(typeof(FStructFallback)) is not FStructFallback machineStruct)
|
||||
continue;
|
||||
|
||||
// Extract MachineName
|
||||
var machineName = string.Empty;
|
||||
foreach (var prop in machineStruct.Properties)
|
||||
{
|
||||
if (prop.Name.Text == "MachineName")
|
||||
{
|
||||
machineName = prop.Tag?.GenericValue?.ToString() ?? string.Empty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (string.IsNullOrEmpty(machineName))
|
||||
continue;
|
||||
|
||||
// Associate FAnimNode_StateMachine nodes that reference this machine index
|
||||
var machineIdxStr = machineIdx.ToString();
|
||||
foreach (var (propName, structType) in animNodeProps)
|
||||
{
|
||||
if (!structType.Contains("StateMachine", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
if (!nodeByName.TryGetValue(propName, out var smNode))
|
||||
continue;
|
||||
if (!smNode.AdditionalProperties.TryGetValue("StateMachineIndexInClass", out var idxStr))
|
||||
continue;
|
||||
if (idxStr == machineIdxStr)
|
||||
smNode.AdditionalProperties["StateMachineName"] = machineName;
|
||||
}
|
||||
|
||||
// Mark state root nodes with BelongsToStateMachine so their layers get the machine name
|
||||
foreach (var prop in machineStruct.Properties)
|
||||
{
|
||||
if (prop.Name.Text != "States") continue;
|
||||
if (prop.Tag?.GenericValue is not UScriptArray states) break;
|
||||
|
||||
foreach (var stateProp in states.Properties)
|
||||
{
|
||||
if (stateProp.GetValue(typeof(FStructFallback)) is not FStructFallback stateStruct)
|
||||
continue;
|
||||
|
||||
if (!stateStruct.TryGetValue(out int stateRootIndex, "StateRootNodeIndex"))
|
||||
continue;
|
||||
|
||||
if (stateRootIndex < 0 || stateRootIndex >= animNodeProps.Count)
|
||||
continue;
|
||||
|
||||
var rootPropName = animNodeProps[stateRootIndex].name;
|
||||
if (nodeByName.TryGetValue(rootPropName, out var rootNode))
|
||||
rootNode.AdditionalProperties["BelongsToStateMachine"] = machineName;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arranges nodes within a layer in a left-to-right flow layout
|
||||
/// based on connection topology (sinks on the left, sources on the right).
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ public partial class AnimGraphViewer
|
|||
{
|
||||
if (e.ClickCount == 2)
|
||||
{
|
||||
TryOpenLinkedLayer(node);
|
||||
TryOpenSubGraph(node);
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
|
@ -402,16 +402,21 @@ public partial class AnimGraphViewer
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a LinkedAnimLayer node is double-clicked, opens its corresponding
|
||||
/// layer sub-graph in a new tab. The layer is identified by matching the
|
||||
/// node's "Layer" property with a layer whose Root node has the same "Name".
|
||||
/// When a LinkedAnimLayer or StateMachine node is double-clicked, opens its
|
||||
/// corresponding sub-graph in a new tab.
|
||||
/// - LinkedAnimLayer: matches "Layer" property → layer name from Root node's "Name"
|
||||
/// - StateMachine: matches "StateMachineName" property → layer name from BakedStateMachines
|
||||
/// </summary>
|
||||
private void TryOpenLinkedLayer(AnimGraphNode node)
|
||||
private void TryOpenSubGraph(AnimGraphNode node)
|
||||
{
|
||||
if (!node.ExportType.Contains("LinkedAnimLayer", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
string? layerName = null;
|
||||
|
||||
if (!node.AdditionalProperties.TryGetValue("Layer", out var layerName) || string.IsNullOrEmpty(layerName))
|
||||
if (node.ExportType.Contains("LinkedAnimLayer", StringComparison.OrdinalIgnoreCase))
|
||||
node.AdditionalProperties.TryGetValue("Layer", out layerName);
|
||||
else if (node.ExportType.Contains("StateMachine", StringComparison.OrdinalIgnoreCase))
|
||||
node.AdditionalProperties.TryGetValue("StateMachineName", out layerName);
|
||||
|
||||
if (string.IsNullOrEmpty(layerName))
|
||||
return;
|
||||
|
||||
var targetLayer = _viewModel.Layers.FirstOrDefault(l =>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user