animsmith_core/lib.rs
1//! Engine-agnostic animation linting primitives for Rust pipelines.
2//!
3//! This crate is the embedding boundary for animsmith. It owns the core
4//! data model ([`Document`], [`Skeleton`], [`Clip`], [`Track`]), rig-role
5//! resolution ([`detect_profile`], [`ResolvedRoles::from_names`]),
6//! typed configuration ([`Config`]), measurement generation
7//! ([`measure::measure_document`]), versioned result envelopes
8//! ([`contract::MeasureEnvelope`], [`contract::LintEnvelope`]), measurement diffs
9//! ([`diff::diff_measurements`]), structured findings ([`Finding`]), and
10//! check execution ([`CheckCtx`], [`all_checks`], [`evaluate_checks`]).
11//! The opt-in [`bake_static_mesh_transforms`] operation canonicalizes supported
12//! unanimated, unskinned mesh scenes into identity-root geometry and returns
13//! deterministic producer evidence.
14//! The opt-in [`transform::prune_constant_tracks`] helper removes only
15//! interpolation-aware constant-track candidates whose sampled local and
16//! model-space pose evidence remains within its documented tolerances.
17//! The [`scale`] module owns the format-neutral plan/proof contracts for the
18//! two distinct DESIGN.md Appendix D scale operations —
19//! [`scale::ScaleOperation::WholeDocumentLinearUnits`] and
20//! [`scale::ScaleOperation::RestBindUniformScale`] — through pure, fail-closed
21//! [`scale::plan_scale`] and independent [`scale::prove_scale`]. A format
22//! frontend owns exact source rewriting and hands the reloaded emitted
23//! document back through [`scale::ScaleCandidate::from_document`]; core does
24//! not expose a production candidate builder, decide selectors, publish
25//! artifacts, or write files.
26//! The [`animsmith-gltf`] and [`animsmith-fbx`] loader crates translate file
27//! formats into this model; their docs.rs pages continue the library path for
28//! format-specific loading and, for glTF, writing.
29//!
30//! The [embedding guide] explains crate selection and integration
31//! boundaries. The [pipeline scenario guide] shows where an embedded gate
32//! fits in marketplace intake, mocap cleanup, outsourced acceptance, and CI.
33//! A [runnable example] exercises the complete library flow.
34//!
35//! [embedding guide]: https://github.com/mmannerm/animsmith/blob/main/docs/embedding.md
36//! [pipeline scenario guide]: https://github.com/mmannerm/animsmith/blob/main/docs/pipeline-scenarios.md
37//! [runnable example]: https://github.com/mmannerm/animsmith/blob/main/crates/animsmith/examples/embed.rs
38//! [`animsmith-gltf`]: https://docs.rs/animsmith-gltf
39//! [`animsmith-fbx`]: https://docs.rs/animsmith-fbx
40//!
41//! # Quick start
42//!
43//! After a format crate has loaded a [`Document`], resolve rig roles, build
44//! a [`Config`] from the host pipeline's contract, and share one
45//! [`MetricGrids`] between measurements, checks, and optional report
46//! generation:
47//!
48//! ```
49//! use animsmith_core::{
50//! CheckCtx, CheckSelection, Config, Document, MetricGrids, all_checks,
51//! evaluate_checks, resolve_configured_roles,
52//! };
53//! use animsmith_core::measure::measure_document;
54//!
55//! let doc = Document::default();
56//! let config = Config::default();
57//! let roles = resolve_configured_roles(&doc.skeleton, &config.rig);
58//! let grids = MetricGrids::new(&doc);
59//!
60//! let measurements = measure_document(&grids, &roles, &config);
61//! let ctx = CheckCtx::new(&grids, &roles, &config);
62//! let results = evaluate_checks(&ctx, &all_checks(), CheckSelection::All)?;
63//!
64//! assert!(measurements.is_empty());
65//! assert!(results.iter().all(|result| result.findings().is_empty()));
66//! # Ok::<(), animsmith_core::EvaluationError>(())
67//! ```
68//!
69//! [`CheckCtx::new`] consumes already-resolved roles; it does not interpret
70//! [`Config::rig`] automatically. Frontends may use [`detect_profile`],
71//! [`resolve_configured_roles`] for the same named-profile plus inline-override
72//! policy as the CLI. Missing prerequisites are represented as typed coverage
73//! gaps rather than false findings.
74//!
75//! # API status
76//!
77//! The Rust API is pre-1.0 and may still change before the first stable
78//! release. The intended extension points are the data model,
79//! configuration types, measurement and diff APIs, rig-profile APIs, the
80//! [`Check`] trait for custom checks, and the check catalog functions
81//! re-exported from this crate root. Built-in check ids, CLI exit-code
82//! semantics, and the shared versioned JSON envelope/schema ids are treated
83//! as the most stable automation contracts. The [`contract`] module owns the
84//! same envelope types and immutable identities for CLI and embedded
85//! producers. The scene-asset
86//! structs in [`model`] and the pipeline-mechanical helpers in
87//! [`transform`] and [`static_bake`] are public so the loader, writer, and CLI crates can
88//! share the same model, but they are less settled than the
89//! measurement/check embedding flow while the crate is pre-1.0. Metric
90//! formulas and individual Rust symbols are still subject to pre-1.0
91//! refinement.
92//!
93//! Public APIs that return [`Result`] document their `# Errors` cases.
94//! Index-based accessors and transform helpers that rely on
95//! loader-established invariants document their `# Panics` contracts.
96//! Loader-valid documents from the format crates should flow through
97//! checking, sampling, and measurement without panicking on untrusted
98//! input.
99
100#![warn(missing_docs)]
101
102pub mod assembly;
103pub mod check;
104mod checks;
105pub mod config;
106pub mod contract;
107pub mod diff;
108pub mod evaluation;
109pub mod finding;
110#[cfg(feature = "fixtures")]
111pub mod fixtures;
112pub mod measure;
113pub mod metrics;
114pub mod model;
115pub mod profile;
116pub mod sample;
117pub mod scale;
118pub mod skinned_canonical;
119pub mod static_bake;
120pub mod transform;
121
122pub use check::{Check, CheckCtx, all_checks, mechanical_checks};
123pub use config::{
124 ClipExpectations, Config, ConfigValidationError, GaitGroup, Pinned, SeveritySetting, SyncGroup,
125 TimeComplementSettings,
126};
127pub use contract::{
128 DiffEnvelope, InputIdentity, LintEnvelope, LintFileReport, MEASUREMENTS_SCHEMA_ID,
129 MEASUREMENTS_SCHEMA_VERSION, MeasureEnvelope, MeasureFileReport, MeasurementContract,
130 MeasurementContractError, MeasurementFileError, MeasurementReportError, MeasurementReportFile,
131 MeasurementReportInput, OUTPUT_SCHEMA_ID, OUTPUT_SCHEMA_VERSION, RigInfo, RigInfoError,
132 ToolInfo, ToolSource,
133};
134pub use evaluation::{
135 Applicability, BUILTIN_COVERAGE_GAP_CODES, BUILTIN_EVALUATION_SCOPE_CODES, CheckEvaluation,
136 CheckOutput, CheckSelection, ConfigurationState, CoverageGap, CoverageGapCode, EvaluationError,
137 EvaluationScope, EvaluationScopeCode, EvaluationState, SelectionState, evaluate_checks,
138};
139pub use finding::{Finding, MemberMeasurement, Severity, Value};
140/// Re-export of the exact `glam` version used by animsmith's public math
141/// types, so embedders can construct [`Transform`] values without a
142/// cross-version type mismatch.
143pub use glam;
144pub use metrics::MetricGrids;
145pub use model::{
146 AdditionalInfluenceSet, AffineDomainViolation, Bone, BoneId, Clip, DecodedImageColorType,
147 Document, DocumentShapeError, ImageContainerFormat, ImageSourceKind, ImageUnavailableReason,
148 Interpolation, MaterialResourceAssets, MaterialResourceCoverage, MaterialTextureSlot,
149 MeshInstanceShapeViolation, Property, Skeleton, SourceImageAsset, SourceImageInspection,
150 SourceInfo, SourceInverseBindAccessor, SourceInverseBindAccessorStatus, SourceMaterialAsset,
151 SourceMaterialTextureBinding, SourceNodeAsset, SourceNodeLocalRest, SourceProjectionViolation,
152 SourceSkeletonAssets, SourceSkeletonCoverage, SourceSkinAsset, SourceSkinAttachment,
153 SourceTextureAsset, Track, TrackShapeViolation, TrackValues, Transform,
154 validate_document_shape,
155};
156pub use profile::{
157 ResolvedRoles, RigProfile, Role, builtin_profiles, detect_profile, resolve_configured_roles,
158};
159pub use sample::{PoseGrid, TrackSample, default_frame_count, sample_clip, sample_track};
160pub use scale::{
161 ProofResidualKind, ScaleBoneRestField, ScaleCandidate, ScaleCapabilityCoverage,
162 ScaleCapabilityFacts, ScaleError, ScaleFieldDisposition, ScaleFieldPlan, ScaleFieldTarget,
163 ScaleOperation, ScalePayloadShapeRow, ScalePlan, ScalePlanLedger, ScaleProjectedRole,
164 ScaleProof, ScaleProofObligation, ScaleProofResidual, ScaleRequest, ScaleRewriteRule,
165 ScaleSourceNodeKind, ScaleSourceRestField, ScaleSourceTopologyRow, ScaleTolerancePolicy,
166 plan_scale, prove_scale,
167};
168pub use skinned_canonical::{
169 SkinnedBindPoseCanonicalization, SkinnedBindPoseCanonicalizationError,
170 SkinnedBindPoseCanonicalizationOptions, SkinnedBindPosePlacement,
171 canonicalize_skinned_bind_pose,
172};
173pub use static_bake::{
174 StaticMeshBake, StaticMeshBakeError, StaticMeshBakeEvidence, StaticMeshBakeInstanceEvidence,
175 bake_static_mesh_transforms,
176};