Enhance AnimGraphNode and AnimGraphPin with ChildPropertyIndex and LinkIndex properties

This commit is contained in:
LoogLong 2026-04-24 09:45:57 +08:00
parent 3bdfcd6b26
commit 152a23e83e

View File

@ -12,6 +12,7 @@ public class AnimGraphNode
{ {
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string ExportType { get; set; } = string.Empty; public string ExportType { get; set; } = string.Empty;
public int ChildPropertyIndex { get; set; } = -1;
public string NodeComment { get; set; } = string.Empty; public string NodeComment { get; set; } = string.Empty;
public int NodePosX { get; set; } public int NodePosX { get; set; }
public int NodePosY { get; set; } public int NodePosY { get; set; }
@ -29,6 +30,7 @@ public class AnimGraphPin
public bool IsOutput { get; set; } public bool IsOutput { get; set; }
public string PinType { get; set; } = string.Empty; public string PinType { get; set; } = string.Empty;
public string DefaultValue { get; set; } = string.Empty; public string DefaultValue { get; set; } = string.Empty;
public int? LinkIndex { get; set; }
public AnimGraphNode OwnerNode { get; set; } = null!; public AnimGraphNode OwnerNode { get; set; } = null!;
} }
@ -112,27 +114,32 @@ public class AnimGraphViewModel
return vm; return vm;
// Collect all anim node struct properties from the class definition // Collect all anim node struct properties from the class definition
var animNodeProps = new List<(string name, string structType)>(); var animNodeProps = new List<(string name, string structType, int childPropertyIndex)>();
foreach (var field in childProps) for (var childPropertyIndex = 0; childPropertyIndex < childProps.Length; childPropertyIndex++)
{ {
var field = childProps[childPropertyIndex];
if (field is not FStructProperty structProp) continue; if (field is not FStructProperty structProp) continue;
var structName = structProp.Struct.ResolvedObject?.Name.Text ?? string.Empty; var structName = structProp.Struct.ResolvedObject?.Name.Text ?? string.Empty;
// Animation node structs typically start with "FAnimNode_" or "AnimNode_" // Animation node structs may be prefixed by plugin/vendor identifiers
// (e.g. ACAnimNode_CopyParentPose), but LinkID still targets their original
// ChildProperties index. We therefore preserve any field/struct name that
// contains the anim node markers instead of only accepting strict prefixes.
if (!IsAnimNodeStruct(structName) && !IsAnimNodeStruct(field.Name.Text)) if (!IsAnimNodeStruct(structName) && !IsAnimNodeStruct(field.Name.Text))
continue; continue;
animNodeProps.Add((field.Name.Text, structName)); animNodeProps.Add((field.Name.Text, structName, childPropertyIndex));
} }
// Build nodes from the collected properties // Build nodes from the collected properties
var nodeByName = new Dictionary<string, AnimGraphNode>(); var nodeByName = new Dictionary<string, AnimGraphNode>();
foreach (var (propName, structType) in animNodeProps) foreach (var (propName, structType, childPropertyIndex) in animNodeProps)
{ {
var node = new AnimGraphNode var node = new AnimGraphNode
{ {
Name = propName, Name = propName,
ExportType = structType ExportType = structType,
ChildPropertyIndex = childPropertyIndex
}; };
// Try to extract property values from the CDO // Try to extract property values from the CDO
@ -907,7 +914,7 @@ public class AnimGraphViewModel
/// state root nodes, and collect state/transition metadata for overview layers. /// state root nodes, and collect state/transition metadata for overview layers.
/// </summary> /// </summary>
private static void AssociateStateMachineNames(UClass animBlueprintClass, UObject? cdo, private static void AssociateStateMachineNames(UClass animBlueprintClass, UObject? cdo,
List<(string name, string structType)> animNodeProps, List<(string name, string structType, int childPropertyIndex)> animNodeProps,
Dictionary<string, AnimGraphNode> nodeByName, Dictionary<string, AnimGraphNode> nodeByName,
List<StateMachineMetadata> smMetadata) List<StateMachineMetadata> smMetadata)
{ {
@ -942,7 +949,7 @@ public class AnimGraphViewModel
// Associate FAnimNode_StateMachine nodes that reference this machine index // Associate FAnimNode_StateMachine nodes that reference this machine index
var machineIdxStr = machineIdx.ToString(); var machineIdxStr = machineIdx.ToString();
foreach (var (propName, structType) in animNodeProps) foreach (var (propName, structType, _) in animNodeProps)
{ {
if (!structType.Contains("StateMachine", StringComparison.OrdinalIgnoreCase)) if (!structType.Contains("StateMachine", StringComparison.OrdinalIgnoreCase))
continue; continue;
@ -1117,9 +1124,8 @@ public class AnimGraphViewModel
private static bool IsAnimNodeStruct(string name) private static bool IsAnimNodeStruct(string name)
{ {
return name.StartsWith("FAnimNode_", StringComparison.OrdinalIgnoreCase) || return name.Contains("AnimNode_", StringComparison.OrdinalIgnoreCase) ||
name.StartsWith("AnimNode_", StringComparison.OrdinalIgnoreCase) || name.Contains("AnimGraphNode_", StringComparison.OrdinalIgnoreCase);
name.StartsWith("AnimGraphNode_", StringComparison.OrdinalIgnoreCase);
} }
private static bool IsSaveCachedPoseNode(AnimGraphNode node) private static bool IsSaveCachedPoseNode(AnimGraphNode node)
@ -1145,6 +1151,18 @@ public class AnimGraphViewModel
case "NodeComment": case "NodeComment":
node.NodeComment = value; node.NodeComment = value;
break; break;
case "BoneToModify":
// Store additional properties for display
if (value.Length <= MaxPropertyValueDisplayLength)
{
var a = prop.Tag?.GenericValue?.ToString();
var V = prop.Tag?.GenericValue;
var vv = V as CUE4Parse.UE4.Assets.Objects.FScriptStruct;
var vvv = vv?.StructType as CUE4Parse.UE4.Assets.Objects.FStructFallback;
var vvvv = vvv?.Properties[0].Tag.ToString();
node.AdditionalProperties[name] = vvvv is not null ? vvvv : value;
}
break;
default: default:
// Store additional properties for display // Store additional properties for display
if (value.Length <= MaxPropertyValueDisplayLength) if (value.Length <= MaxPropertyValueDisplayLength)
@ -1156,22 +1174,48 @@ public class AnimGraphViewModel
// Add input pins based on struct properties that reference other poses/nodes // Add input pins based on struct properties that reference other poses/nodes
foreach (var prop in structValue.Properties) foreach (var prop in structValue.Properties)
{ {
var name = prop.Name.Text; AddInputPins(node, prop.Name.Text, prop.Tag);
// Properties referencing other animation poses are connections
if (IsPoseProperty(name) || IsLinkedNodeProperty(name))
{
node.Pins.Add(new AnimGraphPin
{
PinName = name,
IsOutput = false,
PinType = "pose",
OwnerNode = node
});
}
} }
} }
private static void AddInputPins(AnimGraphNode node, string pinName, FPropertyTagType? tag)
{
if (!IsPoseProperty(pinName) && !IsLinkedNodeProperty(pinName))
return;
if (tag?.GenericValue is UScriptArray array)
{
for (var i = 0; i < array.Properties.Count; i++)
{
AddPinIfMissing(node, $"{pinName}[{i}]", i);
}
if (array.Properties.Count == 0)
{
AddPinIfMissing(node, pinName);
}
return;
}
AddPinIfMissing(node, pinName);
}
private static void AddPinIfMissing(AnimGraphNode node, string pinName, int? linkIndex = null)
{
if (node.Pins.Any(pin => !pin.IsOutput && pin.PinName.Equals(pinName, StringComparison.OrdinalIgnoreCase)))
return;
node.Pins.Add(new AnimGraphPin
{
PinName = pinName,
IsOutput = false,
PinType = "pose",
LinkIndex = linkIndex,
OwnerNode = node
});
}
private static bool IsPoseProperty(string name) private static bool IsPoseProperty(string name)
{ {
return name.Contains("Pose", StringComparison.OrdinalIgnoreCase) && return name.Contains("Pose", StringComparison.OrdinalIgnoreCase) &&
@ -1187,7 +1231,7 @@ public class AnimGraphViewModel
name.Contains("LinkedAnimGraph", StringComparison.OrdinalIgnoreCase); name.Contains("LinkedAnimGraph", StringComparison.OrdinalIgnoreCase);
} }
private static void ResolveConnections(UObject cdo, List<(string name, string structType)> animNodeProps, private static void ResolveConnections(UObject cdo, List<(string name, string structType, int childPropertyIndex)> animNodeProps,
Dictionary<string, AnimGraphNode> nodeByName, AnimGraphViewModel vm) Dictionary<string, AnimGraphNode> nodeByName, AnimGraphViewModel vm)
{ {
// Animation node connections in cooked assets are encoded via // Animation node connections in cooked assets are encoded via
@ -1195,7 +1239,7 @@ public class AnimGraphViewModel
// These contain a "LinkID" integer that maps to the index of the target node // These contain a "LinkID" integer that maps to the index of the target node
// in the class's animation node property list. // in the class's animation node property list.
foreach (var (propName, _) in animNodeProps) foreach (var (propName, _, _) in animNodeProps)
{ {
if (!cdo.TryGetValue(out FStructFallback structValue, propName)) if (!cdo.TryGetValue(out FStructFallback structValue, propName))
continue; continue;
@ -1215,7 +1259,7 @@ public class AnimGraphViewModel
} }
private static void TryResolvePoseLink(FPropertyTagType tag, string pinName, private static void TryResolvePoseLink(FPropertyTagType tag, string pinName,
AnimGraphNode sourceNode, List<(string name, string structType)> animNodeProps, AnimGraphNode sourceNode, List<(string name, string structType, int childPropertyIndex)> animNodeProps,
Dictionary<string, AnimGraphNode> nodeByName, AnimGraphViewModel vm) Dictionary<string, AnimGraphNode> nodeByName, AnimGraphViewModel vm)
{ {
// Handle arrays of pose links (e.g., BlendPose TArray<FPoseLink>) // Handle arrays of pose links (e.g., BlendPose TArray<FPoseLink>)