Code/AutoRig/Analyze/MeshAnalyzer.cs

High-level analyzer facade for rig meshes. It validates a RigMesh, runs segmentation, contact finding, symmetry detection and classification, tolerates failures by degrading gracefully, and returns an AnalysisResult summarizing parts, contacts, symmetry and classification.

File Access
using AutoRig.Mesh;

namespace AutoRig.Analyze;

/// <summary>
/// Analysis façade: Validate → Segment → Contacts → Symmetry → Classify → assemble.
/// Never throws for a mesh that passes <see cref="RigMesh.Validate"/> — failed stages
/// degrade (empty contacts, no symmetry) and the summary notes the partial analysis.
/// </summary>
public static class MeshAnalyzer
{
    public static AnalysisResult Analyze( RigMesh mesh )
    {
        ArgumentNullException.ThrowIfNull( mesh );
        mesh.Validate();

        var partial = false;

        IReadOnlyList<MeshPart> parts;
        try
        {
            parts = MeshSegmenter.Segment( mesh, 0f );
        }
        catch ( Exception )
        {
            // Segmentation is the foundation; without it treat the mesh as one part.
            parts = [ FallbackPart( mesh ) ];
            partial = true;
        }

        IReadOnlyList<PartContact> contacts;
        try
        {
            contacts = parts.Count > 1 ? PartContacts.Find( mesh, parts, 0f ) : [];
        }
        catch ( Exception )
        {
            contacts = [];
            partial = true;
        }

        SymmetryPlane? symmetry;
        try
        {
            symmetry = SymmetryDetector.Detect( mesh );
        }
        catch ( Exception )
        {
            symmetry = null;
            partial = true;
        }

        Classification classification;
        try
        {
            classification = MeshClassifier.Classify( mesh, parts, symmetry );
        }
        catch ( Exception )
        {
            classification = new Classification
            {
                Kind = MeshKind.Organic,
                Confidence = 0.05f,
                Explanation = "Classification failed - defaulting to organic.",
            };
            partial = true;
        }

        var summary = classification.Kind switch
        {
            MeshKind.Mechanical => $"Mechanical · {parts.Count} rigid part(s)",
            MeshKind.Hybrid => $"Hybrid · {parts.Count} part(s)",
            _ => symmetry is not null ? "Organic · symmetric" : "Organic",
        };
        if ( partial )
            summary += " (partial analysis)";

        return new AnalysisResult
        {
            Mesh = mesh,
            Parts = parts,
            Contacts = contacts,
            Symmetry = symmetry,
            Classification = classification,
            Category = OrganicCategory.Unknown,
            Summary = summary,
        };
    }

    static MeshPart FallbackPart( RigMesh mesh )
    {
        var triangles = new int[mesh.TriangleCount];
        for ( var i = 0; i < triangles.Length; i++ ) triangles[i] = i;
        return new MeshPart
        {
            Index = 0,
            TriangleIndices = triangles,
            Bounds = mesh.ComputeBounds(),
            SurfaceArea = mesh.SurfaceArea(),
            Name = mesh.Tags.Length > 0 ? mesh.Tags[0] : "mesh",
            VertexCount = mesh.Positions.Length,
        };
    }
}