Editor/TextToAnimation/Inference/UniMate/UniMateNameTable.cs

Editor helper class that stores UniMate's canonical mapping of raw bone names to labels and per-rig facing info. It lazy-loads a large built-in table from embedded Entries and RigFaceEntries strings, exposes Lookup(name) to get UniMate's label, and RigFacing(...) to identify per-rig left/right facing joints using a SHA1 signature of the rig's normalized names.

File AccessNative Interop
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace TextToAnimation.Editor.Inference.UniMate;

/// <summary>
/// UniMate's own labels for raw bone names it has seen: every raw name in its published data (UniML3D: 6149 Objaverse
/// rigs, 74 Truebones animals) that its annotation - rule and language model - labelled consistently. A rig whose
/// bones carry such names (Bip01_*, mixamorig:*, Blender .L/.R conventions and the like) gets exactly the label
/// UniMate gave them; the rest go through the rule and <see cref="UniMateNaming"/>. Generated by
/// dev/tools/upstream_prep/make_name_table.py.
/// </summary>
public static partial class UniMateNameTable
{
	static Dictionary<string, string> _table;

	/// <summary>Replaces the table (tests score the naming on rigs the table was built without).</summary>
	public static void Use( Dictionary<string, string> table ) => _table = table;

	static Dictionary<string, string> Table
	{
		get
		{
			if ( _table is not null ) return _table;
			var table = new Dictionary<string, string>( 40000, StringComparer.Ordinal );
			foreach ( var line in Entries.Split( '\n' ) )
			{
				var tab = line.IndexOf( '\t' );
				if ( tab > 0 ) table[line[..tab]] = Labels[int.Parse( line[(tab + 1)..] )];
			}
			return _table = table;
		}
	}

	static Dictionary<string, (string Right, string Left, bool BodyAxis)> _rigFaces;

	/// <summary>Turns the per-rig facing off (tests scoring the rules on rigs the table contains).</summary>
	public static void NoRigFaces() => _rigFaces = new Dictionary<string, (string, string, bool)>();

	static Dictionary<string, (string Right, string Left, bool BodyAxis)> RigFaces
	{
		get
		{
			if ( _rigFaces is not null ) return _rigFaces;
			var faces = new Dictionary<string, (string, string, bool)>( StringComparer.Ordinal );
			foreach ( var line in RigFaceEntries.Split( '\n' ) )
			{
				var parts = line.Split( '\t' );
				if ( parts.Length == 4 ) faces[parts[0]] = (parts[1], parts[2], parts[3] == "1");
			}
			return _rigFaces = faces;
		}
	}

	/// <summary>
	/// When the prepared rig is one of UniMate's own (the same set of joint names), the facing UniMate gave it, as
	/// indices into <paramref name="raw"/> (-1, -1 = none); null for a rig UniMate doesn't have.
	/// </summary>
	public static (int Right, int Left, bool BodyAxis)? RigFacing( IReadOnlyList<string> raw )
	{
		// the engine's form of each name (its compiler turns symbols other than '_' into '_'); the rig is identified by
		// them with index suffixes stripped, its facing joints by their full names
		var engine = raw.Select( name => string.Concat( (name ?? "").Select( c => char.IsLetterOrDigit( c ) || c == '_' ? c : '_' ) ) ).ToList();
		var keys = engine.Select( name => IndexSuffix.Replace( name, "" ) ).ToList();
		var joined = string.Join( "\n", new SortedSet<string>( keys, StringComparer.Ordinal ) );
		var hash = System.Security.Cryptography.SHA1.HashData( System.Text.Encoding.UTF8.GetBytes( joined ) );
		var signature = Convert.ToHexString( hash ).ToLowerInvariant()[..16];
		if ( !RigFaces.TryGetValue( signature, out var face ) ) return null;
		if ( face.Right.Length == 0 || face.Left.Length == 0 ) return (-1, -1, false);
		int r = engine.IndexOf( face.Right ), l = engine.IndexOf( face.Left );
		return r >= 0 && l >= 0 ? (r, l, face.BodyAxis) : null;
	}

	static readonly Regex IndexSuffix = new( @"(_\d+)+$", RegexOptions.Compiled );

	/// <summary>The label UniMate gave this raw bone name, or null when it never saw it (or labelled it inconsistently).</summary>
	public static string Lookup( string raw )
	{
		var key = string.IsNullOrEmpty( raw ) ? "" : IndexSuffix.Replace( raw, "" );
		return key.Length > 0 && Table.TryGetValue( key, out var label ) ? label : null;
	}
}