Editor utility for editing a rig skeleton used by the AutoRig preview. Provides pure-skeleton operations that return a new RigResult: DeleteJoint removes a joint and reparents children while remapping bone indices, RenameJoint returns a copy with one joint renamed.
using System.Collections.Generic;
using AutoRig.Rig;
namespace AutoRig.Editor;
/// <summary>
/// Pure skeleton edits used by the preview's realtime editing (delete, rename,
/// move). Each returns a NEW <see cref="RigResult"/> so the caller can swap it
/// onto the entry and re-render; the mesh is never touched.
/// </summary>
public static class JointEdits
{
/// <summary>Removes a joint: its children reparent to its parent, and its
/// skin influence is folded onto that parent (weights renormalize for free
/// because the bone index simply remaps). The root cannot be removed.</summary>
public static RigResult DeleteJoint( RigResult rig, int removed )
{
var joints = rig.Skeleton.Joints;
if ( removed < 0 || removed >= joints.Count
|| joints.Count <= 1 || joints[removed].Parent < 0 )
return rig;
var parentOfRemoved = joints[removed].Parent;
var skeleton = new RigSkeleton();
var remap = new int[joints.Count];
for ( var i = 0; i < joints.Count; i++ )
{
if ( i == removed )
{
remap[i] = remap[parentOfRemoved];
continue;
}
var parent = joints[i].Parent;
if ( parent == removed )
parent = parentOfRemoved;
remap[i] = skeleton.Joints.Count;
skeleton.Joints.Add( new RigJoint
{
Name = joints[i].Name,
Parent = parent < 0 ? -1 : remap[parent],
Position = joints[i].Position,
HingeAxis = joints[i].HingeAxis,
} );
}
var boneIndices = (int[])rig.Weights.BoneIndices.Clone();
for ( var k = 0; k < boneIndices.Length; k++ )
boneIndices[k] = remap[boneIndices[k]];
var weights = new SkinWeights
{
BoneIndices = boneIndices,
Weights = (float[])rig.Weights.Weights.Clone(),
};
return new RigResult
{
Skeleton = skeleton,
Weights = weights,
SolverName = rig.SolverName,
Degraded = rig.Degraded,
Explanation = rig.Explanation,
};
}
/// <summary>Renames a joint into a NEW rig (so the old one stays intact for
/// undo). No-op - returns the same rig - on a blank name or a collision.</summary>
public static RigResult RenameJoint( RigResult rig, int index, string name )
{
name = name?.Trim();
if ( index < 0 || index >= rig.Skeleton.Joints.Count || string.IsNullOrEmpty( name ) )
return rig;
if ( rig.Skeleton.IndexOf( name ) is var existing and >= 0 && existing != index )
return rig;
var skeleton = new RigSkeleton();
var source = rig.Skeleton.Joints;
for ( var i = 0; i < source.Count; i++ )
skeleton.Joints.Add( new RigJoint
{
Name = i == index ? name : source[i].Name,
Parent = source[i].Parent,
Position = source[i].Position,
HingeAxis = source[i].HingeAxis,
} );
return new RigResult
{
Skeleton = skeleton,
Weights = rig.Weights, // rename doesn't touch weights
SolverName = rig.SolverName,
Degraded = rig.Degraded,
Explanation = rig.Explanation,
};
}
}