Facade that runs a multi-stage analysis pipeline for a RigMesh: validate, segment into parts, find part contacts, detect symmetry, classify, and assemble an AnalysisResult. It treats failures in intermediate stages as degradations (falls back to single-part mesh, empty contacts, null symmetry, or default classification) and marks the summary as partial when any stage failed.
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,
};
}
}