Friedrich-M/UniMatePublic

[SIGGRAPH Asia 2026] UniMate: One Unified Model to Animate Diverse Skeletons

AI summary: A unified machine learning model capable of animating diverse character skeletons.

Stars
1.3K
+119 today
Forks
110
Watchers
36
Open issues
7
Open PRs
2
Contributors
~1
Commits
22
Branches
1

PythonMITCreated Jul 22, 2026Last push 3d ago+200 stars this week+200 this month

Quick answers

What is UniMate?
A unified machine learning model capable of animating diverse character skeletons.
What does UniMate do?
UniMate is a research project and unified machine learning model presented at SIGGRAPH Asia 2026. It is designed to tackle the complex problem of animating highly diverse 3D character skeletons using a single architecture. Traditionally, animation models are tightly coupled to specific skeletal rigs, requiring retraining for new characters. UniMate abstracts this process, allowing motion data to be applied across varying topologies and bone structures. The repository hosts the code, interactive demos, and paper links detailing this breakthrough in character animation.
Who is UniMate for?
Machine learning researchers, technical animators, and game developers exploring advanced 3D motion generation. It is highly technical and intended for academic review and experimental integration.
How popular is UniMate on GitHub?
Friedrich-M/UniMate has 1,250 stars and 110 forks on GitHub, and gained 200 stars in the last 7 days.
What license does UniMate use?
Friedrich-M/UniMate is released under the MIT license.

Star history

since Oct 1, 2026
05001KOct 2026Oct 2026Oct 2026Oct 2026
1.3K stars as of Oct 3, 2026. Measured daily since Oct 1, 2026; GitHub no longer exposes earlier star timestamps.

Contribution activity

commits per day, last 52 weeks
OctNovDecJanFebMarAprMayJunJulAugSepMonWedFri2025-10-05: 0 commits2025-10-06: 0 commits2025-10-07: 0 commits2025-10-08: 0 commits2025-10-09: 0 commits2025-10-10: 0 commits2025-10-11: 0 commits2025-10-12: 0 commits2025-10-13: 0 commits2025-10-14: 0 commits2025-10-15: 0 commits2025-10-16: 0 commits2025-10-17: 0 commits2025-10-18: 0 commits2025-10-19: 0 commits2025-10-20: 0 commits2025-10-21: 0 commits2025-10-22: 0 commits2025-10-23: 0 commits2025-10-24: 0 commits2025-10-25: 0 commits2025-10-26: 0 commits2025-10-27: 0 commits2025-10-28: 0 commits2025-10-29: 0 commits2025-10-30: 0 commits2025-10-31: 0 commits2025-11-01: 0 commits2025-11-02: 0 commits2025-11-03: 0 commits2025-11-04: 0 commits2025-11-05: 0 commits2025-11-06: 0 commits2025-11-07: 0 commits2025-11-08: 0 commits2025-11-09: 0 commits2025-11-10: 0 commits2025-11-11: 0 commits2025-11-12: 0 commits2025-11-13: 0 commits2025-11-14: 0 commits2025-11-15: 0 commits2025-11-16: 0 commits2025-11-17: 0 commits2025-11-18: 0 commits2025-11-19: 0 commits2025-11-20: 0 commits2025-11-21: 0 commits2025-11-22: 0 commits2025-11-23: 0 commits2025-11-24: 0 commits2025-11-25: 0 commits2025-11-26: 0 commits2025-11-27: 0 commits2025-11-28: 0 commits2025-11-29: 0 commits2025-11-30: 0 commits2025-12-01: 0 commits2025-12-02: 0 commits2025-12-03: 0 commits2025-12-04: 0 commits2025-12-05: 0 commits2025-12-06: 0 commits2025-12-07: 0 commits2025-12-08: 0 commits2025-12-09: 0 commits2025-12-10: 0 commits2025-12-11: 0 commits2025-12-12: 0 commits2025-12-13: 0 commits2025-12-14: 0 commits2025-12-15: 0 commits2025-12-16: 0 commits2025-12-17: 0 commits2025-12-18: 0 commits2025-12-19: 0 commits2025-12-20: 0 commits2025-12-21: 0 commits2025-12-22: 0 commits2025-12-23: 0 commits2025-12-24: 0 commits2025-12-25: 0 commits2025-12-26: 0 commits2025-12-27: 0 commits2025-12-28: 0 commits2025-12-29: 0 commits2025-12-30: 0 commits2025-12-31: 0 commits2026-01-01: 0 commits2026-01-02: 0 commits2026-01-03: 0 commits2026-01-04: 0 commits2026-01-05: 0 commits2026-01-06: 0 commits2026-01-07: 0 commits2026-01-08: 0 commits2026-01-09: 0 commits2026-01-10: 0 commits2026-01-11: 0 commits2026-01-12: 0 commits2026-01-13: 0 commits2026-01-14: 0 commits2026-01-15: 0 commits2026-01-16: 0 commits2026-01-17: 0 commits2026-01-18: 0 commits2026-01-19: 0 commits2026-01-20: 0 commits2026-01-21: 0 commits2026-01-22: 0 commits2026-01-23: 0 commits2026-01-24: 0 commits2026-01-25: 0 commits2026-01-26: 0 commits2026-01-27: 0 commits2026-01-28: 0 commits2026-01-29: 0 commits2026-01-30: 0 commits2026-01-31: 0 commits2026-02-01: 0 commits2026-02-02: 0 commits2026-02-03: 0 commits2026-02-04: 0 commits2026-02-05: 0 commits2026-02-06: 0 commits2026-02-07: 0 commits2026-02-08: 0 commits2026-02-09: 0 commits2026-02-10: 0 commits2026-02-11: 0 commits2026-02-12: 0 commits2026-02-13: 0 commits2026-02-14: 0 commits2026-02-15: 0 commits2026-02-16: 0 commits2026-02-17: 0 commits2026-02-18: 0 commits2026-02-19: 0 commits2026-02-20: 0 commits2026-02-21: 0 commits2026-02-22: 0 commits2026-02-23: 0 commits2026-02-24: 0 commits2026-02-25: 0 commits2026-02-26: 0 commits2026-02-27: 0 commits2026-02-28: 0 commits2026-03-01: 0 commits2026-03-02: 0 commits2026-03-03: 0 commits2026-03-04: 0 commits2026-03-05: 0 commits2026-03-06: 0 commits2026-03-07: 0 commits2026-03-08: 0 commits2026-03-09: 0 commits2026-03-10: 0 commits2026-03-11: 0 commits2026-03-12: 0 commits2026-03-13: 0 commits2026-03-14: 0 commits2026-03-15: 0 commits2026-03-16: 0 commits2026-03-17: 0 commits2026-03-18: 0 commits2026-03-19: 0 commits2026-03-20: 0 commits2026-03-21: 0 commits2026-03-22: 0 commits2026-03-23: 0 commits2026-03-24: 0 commits2026-03-25: 0 commits2026-03-26: 0 commits2026-03-27: 0 commits2026-03-28: 0 commits2026-03-29: 0 commits2026-03-30: 0 commits2026-03-31: 0 commits2026-04-01: 0 commits2026-04-02: 0 commits2026-04-03: 0 commits2026-04-04: 0 commits2026-04-05: 0 commits2026-04-06: 0 commits2026-04-07: 0 commits2026-04-08: 0 commits2026-04-09: 0 commits2026-04-10: 0 commits2026-04-11: 0 commits2026-04-12: 0 commits2026-04-13: 0 commits2026-04-14: 0 commits2026-04-15: 0 commits2026-04-16: 0 commits2026-04-17: 0 commits2026-04-18: 0 commits2026-04-19: 0 commits2026-04-20: 0 commits2026-04-21: 0 commits2026-04-22: 0 commits2026-04-23: 0 commits2026-04-24: 0 commits2026-04-25: 0 commits2026-04-26: 0 commits2026-04-27: 0 commits2026-04-28: 0 commits2026-04-29: 0 commits2026-04-30: 0 commits2026-05-01: 0 commits2026-05-02: 0 commits2026-05-03: 0 commits2026-05-04: 0 commits2026-05-05: 0 commits2026-05-06: 0 commits2026-05-07: 0 commits2026-05-08: 0 commits2026-05-09: 0 commits2026-05-10: 0 commits2026-05-11: 0 commits2026-05-12: 0 commits2026-05-13: 0 commits2026-05-14: 0 commits2026-05-15: 0 commits2026-05-16: 0 commits2026-05-17: 0 commits2026-05-18: 0 commits2026-05-19: 0 commits2026-05-20: 0 commits2026-05-21: 0 commits2026-05-22: 0 commits2026-05-23: 0 commits2026-05-24: 0 commits2026-05-25: 0 commits2026-05-26: 0 commits2026-05-27: 0 commits2026-05-28: 0 commits2026-05-29: 0 commits2026-05-30: 0 commits2026-05-31: 0 commits2026-06-01: 0 commits2026-06-02: 0 commits2026-06-03: 0 commits2026-06-04: 0 commits2026-06-05: 0 commits2026-06-06: 0 commits2026-06-07: 0 commits2026-06-08: 0 commits2026-06-09: 0 commits2026-06-10: 0 commits2026-06-11: 0 commits2026-06-12: 0 commits2026-06-13: 0 commits2026-06-14: 0 commits2026-06-15: 0 commits2026-06-16: 0 commits2026-06-17: 0 commits2026-06-18: 0 commits2026-06-19: 0 commits2026-06-20: 0 commits2026-06-21: 0 commits2026-06-22: 0 commits2026-06-23: 0 commits2026-06-24: 0 commits2026-06-25: 0 commits2026-06-26: 0 commits2026-06-27: 0 commits2026-06-28: 0 commits2026-06-29: 0 commits2026-06-30: 0 commits2026-07-01: 0 commits2026-07-02: 0 commits2026-07-03: 0 commits2026-07-04: 0 commits2026-07-05: 0 commits2026-07-06: 0 commits2026-07-07: 0 commits2026-07-08: 0 commits2026-07-09: 0 commits2026-07-10: 0 commits2026-07-11: 0 commits2026-07-12: 0 commits2026-07-13: 0 commits2026-07-14: 0 commits2026-07-15: 0 commits2026-07-16: 0 commits2026-07-17: 0 commits2026-07-18: 0 commits2026-07-19: 0 commits2026-07-20: 0 commits2026-07-21: 0 commits2026-07-22: 6 commits2026-07-23: 0 commits2026-07-24: 0 commits2026-07-25: 0 commits2026-07-26: 0 commits2026-07-27: 0 commits2026-07-28: 0 commits2026-07-29: 0 commits2026-07-30: 0 commits2026-07-31: 0 commits2026-08-01: 1 commit2026-08-02: 0 commits2026-08-03: 0 commits2026-08-04: 0 commits2026-08-05: 0 commits2026-08-06: 0 commits2026-08-07: 0 commits2026-08-08: 2 commits2026-08-09: 0 commits2026-08-10: 0 commits2026-08-11: 0 commits2026-08-12: 0 commits2026-08-13: 0 commits2026-08-14: 0 commits2026-08-15: 0 commits2026-08-16: 0 commits2026-08-17: 0 commits2026-08-18: 0 commits2026-08-19: 0 commits2026-08-20: 0 commits2026-08-21: 0 commits2026-08-22: 0 commits2026-08-23: 0 commits2026-08-24: 0 commits2026-08-25: 0 commits2026-08-26: 0 commits2026-08-27: 0 commits2026-08-28: 0 commits2026-08-29: 0 commits2026-08-30: 1 commit2026-08-31: 1 commit2026-09-01: 2 commits2026-09-02: 0 commits2026-09-03: 0 commits2026-09-04: 0 commits2026-09-05: 0 commits2026-09-06: 4 commits2026-09-07: 0 commits2026-09-08: 0 commits2026-09-09: 0 commits2026-09-10: 0 commits2026-09-11: 1 commit2026-09-12: 0 commits2026-09-13: 0 commits2026-09-14: 0 commits2026-09-15: 0 commits2026-09-16: 0 commits2026-09-17: 0 commits2026-09-18: 0 commits2026-09-19: 0 commits2026-09-20: 0 commits2026-09-21: 0 commits2026-09-22: 0 commits2026-09-23: 0 commits2026-09-24: 0 commits2026-09-25: 0 commits2026-09-26: 0 commits2026-09-27: 2 commits2026-09-28: 0 commits2026-09-29: 0 commits2026-09-30: 2 commits2026-10-01: 0 commits2026-10-02: 0 commits2026-10-03: 0 commits
22 commits in the last yearLessMore

Signals and awards

derived from tracked data
  • Rising fast

    +200 stars this week

  • Permissive license

    MIT

  • Continuous integration

    Automated checks passing

What UniMate does

UniMate is a research project and unified machine learning model presented at SIGGRAPH Asia 2026. It is designed to tackle the complex problem of animating highly diverse 3D character skeletons using a single architecture. Traditionally, animation models are tightly coupled to specific skeletal rigs, requiring retraining for new characters. UniMate abstracts this process, allowing motion data to be applied across varying topologies and bone structures. The repository hosts the code, interactive demos, and paper links detailing this breakthrough in character animation.

Machine learning researchers, technical animators, and game developers exploring advanced 3D motion generation. It is highly technical and intended for academic review and experimental integration.

  • Unified architecture: Employs a single model to handle animation tasks across widely differing skeletal structures.
  • Skeleton agnostic: Eliminates the need to retrain or heavily fine-tune models for every new character rig.
  • Interactive demo integration: Provides resources to test the model's capabilities via a web-based interface.
  • Academic implementation: Serves as the official codebase for the associated SIGGRAPH Asia 2026 research paper.

Where teams use it

Game development pipelines

Apply a single set of generated animations to multiple distinct enemy types with different skeletal rigs.

Animation research

Study the model's architecture to understand advancements in topology-agnostic motion retargeting.

Automated rigging workflows

Integrate the model to automatically animate diverse user-generated 3D characters in metaverses or social apps.

README

main branch

UniMate

One Unified Model to Animate Diverse Skeletons

Project Page arXiv Interactive Demo Hugging Face Dataset Hugging Face Checkpoints

Linzhan Mou · Jiahui Lei · Zhiyang Dou · Chenyue Cai · Chaoyue Song · Adam Finkelstein · Szymon Rusinkiewicz

Princeton · UC Berkeley · MIT · NTU

UniMate teaser

🔥 News

  • [2026-09-27] Preview checkpoints are released at HuggingFace; new checkpoints will be synced there. 📦
  • [2026-09-06] The training and inference code is released. 🚀
  • [2026-08-30] The UniML3D dataset and its data-processing pipeline are released. 🚀
  • [2026-08-01] Our Interactive Demo is live — browse our animation results in 3D. 🎮
  • [2026-07-18] UniMate is accepted to SIGGRAPH Asia 2026! 🎉

[TODO] We will release an official preprocessing pipeline for new (out-of-distribution) rigs by this week.

🛠️ Environment Setup

All components share a single conda environment, specified in requirements.txt:

conda create -n unimate python=3.10 -y
conda activate unimate
pip install "setuptools<81"
pip install -r requirements.txt --no-build-isolation

📊 Dataset & Data Processing

We introduce UniML3D, a large-scale dataset of 13,006 text-paired motion sequences covering diverse skeletal topologies — bipedal, quadrupedal, avian, marine, insectoid, serpentine, and articulated rigid objects — all brought into a unified canonicalization.

The raw source assets are available on the Hugging Face Hub (collected under UniMate): Mixamo-Animations-Characters, Objaverse-XL-Rigged-Animated and Truebones-ZOO-Annotations (prompts, metadata and renders only). The Truebones ZOO animal motions themselves are a commercial asset pack whose license does not permit redistribution — please purchase the pack directly from Truebones; our pipeline consumes the stock Truebone_Z-OO folder layout as-is.

UniML3D dataset overview

See data_process/README.md for the full data processing pipeline that turns the raw assets into UniML3D (download → export → rendering → captioning → joint annotation → feature extraction → animation).

🏋️ Training

Training reads the canonicalized clips under dataset/features/<dataset>/, produced by stage 4 of the data-processing pipeline.

Runs are configured by the JSON files in configs/.

Config naming — {dataset}_{frames}frames_{attention}_{text_cond}.json

8 configs: 4 data combinations x 2 model variants, all at 60 frames.

Prefix Training data
uniml3d_* Full UniML3D dataset (Truebones + Mixamo + Objaverse)
truebones_* / mixamo_* / objaverse_* A single source
Length dataset.max_motion_length
60frames 60 frames per clip
Suffix model.attention x model.text_cond
_graph_adaln graph x adaln — attention factored into spatial (per frame) and temporal (per joint) passes with graph-distance, edge-type and depth biases; the caption is folded into the adaLN modulation
_full_cross_attn full x cross_attn — one attention over the flattened joint x time tokens; the caption enters every block as cross-attention keys/values

The two axes are independent and all four combinations are implemented, so full x adaln and graph x cross_attn also run if you set them in a config; the two shipped pairings are the ones the paper compares.

Not shared across configs: training.batch_size, training.num_steps, model.num_layers and dataset.max_joints are tuned per data combination (GPU memory tracks batch x frames x joints; depth grows with the data: 6 layers for a single Truebones / Mixamo source, 8 for Objaverse, 10 for the full mixture). dataset.max_joints is 100 for truebones_* / mixamo_* and 60 for objaverse_* / uniml3d_*; dataset.min_joints is 5 everywhere. Together they bound the skeleton sizes a run admits (object types outside the range are dropped) and, through what survives, the joint-axis padding width. Mixamo is a single skeleton, so its configs also turn off the object-type balancing (a no-op with one type) and, by choice, the topology augmentations. Every other setting is identical.

Launch with 🤗 Accelerate. Single GPU:

accelerate launch -m unimate.training.train --config configs/uniml3d_60frames_graph_adaln.json

Multi-GPU on one node (e.g. 8 GPUs):

accelerate launch --num_processes 8 -m unimate.training.train --config configs/uniml3d_60frames_graph_adaln.json

scripts/run_train.sh <config> [-- extra args] wraps the single-GPU command with the conda environment activated, the GPU with the most free memory selected, and anything after -- forwarded to the training module.

--output_dir, --batch_size, --num_workers and --resume <checkpoint.pt> override the config from the command line. Resuming restores model, EMA, optimizer, LR-scheduler and step counter, so a run continues exactly where it stopped.

Each run writes to outputs/<experiment name>/:

Path Content
config.json Resolved config, including the auto-computed max_joints / max_depth; inference reads it back to rebuild the model
dataset_stats.npy Normalization statistics, reused at inference
checkpoints/checkpoint_step_*.pt Model, EMA, optimizer and LR-scheduler state, every training.save_interval steps
debug/ Sample visualizations, rendered once before training and at every checkpoint (EMA weights, sampling.cfg_scale)
logs/ TensorBoard scalars (tensorboard --logdir outputs/<experiment name>/logs)
What a training step does

Clips are drawn by a power-law-balanced sampler when training.balanced is set — a type with n clips is sampled in proportion to n^(1-sampler_alpha), so at the default sampler_alpha = 0.5 a species with 100 clips is seen ten times as often as one with a single clip rather than a hundred times — then augmented on the fly — joint addition, leaf removal, chain pooling and per-bone length perturbation (dataset.use_*_aug) — so the model sees more topologies than the data literally contains. Every clip is padded to max_joints on the joint axis and max_motion_length on the time axis, with masks carried alongside; nothing padded ever contributes to attention or to the loss.

Training is flow matching (training.diff_model = "flow"): the network predicts the velocity of a linear interpolant between noise and data, under a masked L2 loss plus two auxiliary terms computed on the reconstructed clean motion — a geodesic rotation loss (training.lambda_geo) and a velocity-smoothness loss (training.lambda_smooth). Conditioning is dropped with probability model.cond_mask_prob so the same weights serve the conditional and unconditional branches that classifier-free guidance interpolates at sampling time. AdamW with a cosine schedule and warmup, gradient clipping at training.max_grad_norm, and an EMA copy of the weights (training.use_ema) — the copy inference loads by default.

Pre-computing text embeddings

The text encoder (google/flan-t5-base by default) is fetched from the Hugging Face Hub on first use. Every run loads it once to embed all captions and joint names; pre-computing those embeddings beside the features keeps it out of the run entirely:

python -m unimate.tools.precompute_text_emb --config configs/uniml3d_60frames_graph_adaln.json

This writes caption_emb_cache.npz and joint_emb_cache.npz into each dataset/features/<dataset>/ the config uses. Captions are cached per token (the sequence cross_attn attends; adaln mean-pools it), joint names as one pooled vector each, keyed by the cleaned joint vocabulary that stage 3 produces — the shared naming is what lets the same anatomical joint embed identically across rigs. Re-run it after regenerating captions or joint names: anything the cache misses is still encoded at load time, so a stale cache costs speed rather than correctness.

Troubleshooting — unstable training on Objaverse

A non-trivial share of the Objaverse-XL rigs and clips are defective: rest poses that lie flat, are rotated or are inverted, and clips that stitch several unrelated actions together. Training on them can destabilize or collapse a run, and isolated spikes in the training loss are usually a symptom of bad data rather than of optimization.

To localize the problem, first train on Mixamo and Truebones alone — set dataset.dataset_list to ["truebones", "mixamo"] in a copy of a config. If that run is healthy, the fault is on the Objaverse side. From there, inspect the skeleton preview videos of the suspect object types under dataset/features/objaverse/videos/, by eye or with an automated pass, and add the offending rigs and clips to the stage-4 skip lists that tools/patch_annotations.py maintains.

📌 Note

The processed UniML3D dataset is being prepared for open release. Its captions were re-processed for this release, so they do not necessarily match the prompts shown on the project page or in the paper. See the released caption style in Truebones · Mixamo · Objaverse. New prompts start with "An object" to generalize across objects.

UniMate is an early step toward text-to-animation for any skeleton, and many motions and skeletons still fail. We believe that scaling up training data — distilled from agents or generated from videos — is a promising direction to close this gap. If you run into failure cases, please open an issue or contact us; they help us improve.

🎬 Inference

Given a rigged 3D asset and a text prompt, UniMate generates articulated motion for arbitrary skeletons in real time — with no per-skeleton retraining and no test-time optimization.

Qualitative results

Sampling starts from the output directory of a training run (config.json, dataset_stats.npy, checkpoints/) — the released checkpoints use the same layout. The target skeleton — T-pose and topology conditioning — is taken from the dataset, so the dataset/features/<dataset>/ directory the model was trained on must be present.

python -m unimate.inference.sample \
    --exp_dir outputs/uniml3d_60frames_graph_adaln \
    --test_cases_json test_cases.json \
    --num_repetitions 3
Test cases, flags and outputs

Test cases are a JSON map from <object_type>-<case_id> to a prompt. object_type must exist in the dataset; case_id is a free-form tag that names the output files:

{
  "Dog-walk": "a dog walks forward at a steady pace",
  "Dragon-takeoff": "a dragon flaps its wings and takes off"
}

--test_cases_json is itself optional: without it every clip of the dataset's eval split is enumerated as a test case (falling back to unique (object_type, caption) pairs on train when there is no eval split). --test_cases_txt (one object_type per line) drives unconditional sampling, which requires --cfg_scale 1.0.

Flag Effect
--cfg_scale Classifier-free guidance scale (>= 1.0); defaults to the value saved in the run's config
--model_path A specific checkpoint; defaults to the latest step
--output_dir Defaults to <exp_dir>/samples
--num_repetitions Samples generated per test case
--batch_size Per-chunk inference batch; caps GPU memory regardless of how many cases there are
--seed Fixes the sampling noise
--only_save_motion Skip the MP4 renders, write only the .npy features
--save_ric Add the RIC-recovered render beside the FK one

Each run writes:

Path Content
motions/<case_id>-rep_<r>-<i>.npy Generated motion features (T, J, 12), one file per repetition
animations/<case_id>-rep_<r>-sample<i>_fk.mp4 Skeleton render of each sample (_ric.mp4 variants with --save_ric)
animations/<object_type>_tpos.png The conditioning T-pose
captions.json Prompt used for every saved .npy

scripts/run_sample_motion_text.sh <exp_dir> [test_cases_json] [cfg_scale] runs the command above with the conda environment activated, the GPU with the most free memory selected, and an output directory named after the test-case file. Run it with -h for its options.

Driving a rigged mesh

To animate the original mesh with a generated motion, hand the .npy files to stage 5 of the data pipeline, which exports an animated GLB + FBX:

bash scripts/run_animate_motion.sh objaverse \
    outputs/uniml3d_60frames_graph_adaln/samples/motions/Dog-walk-rep_0-0.npy \
    outputs/animated

It accepts several files or a directory, and reads the rig from dataset/features/<dataset>/cond.npy by default.

🎨 Applications

The same trained model does three more tasks with no extra training. Each is replacement-style sampling: part of the motion is pinned to a known signal and the flow ODE denoises only the rest at every step, so the constraint holds exactly rather than being encouraged by a loss.

Motion in-betweening

Hold chosen keyframes at their ground truth and generate the transitions between them.

Motion in-betweening
How to run it

--keep_frames takes signed indices (negatives count back from the generation window), so "0,-1" fills in everything between a clip's first and last pose.

{ "mixamo-Squat-000": "A human squats and then rises back up" }
KEEP_FRAMES="0,-1" bash scripts/run_sample_motion_inbetween.sh \
    outputs/uniml3d_60frames_graph_adaln cases.json

Text-guided motion editing

Hold chosen joints at their ground-truth motion for every frame and regenerate the rest under a new prompt — keep what should stay, re-animate the rest.

Text-guided motion editing
How to run it

--keep_joints matches case-insensitively against either the rig's own bone names or the cleaned vocabulary.

{ "<objaverse_uid>-turn-head-000": "The robot walks forward." }
KEEP_JOINTS="Hips,Spine,Neck,Head" bash scripts/run_sample_motion_edit.sh \
    outputs/uniml3d_60frames_graph_adaln cases.json

Motion expansion

Chain several prompts into one long motion. The first segment is generated freely; every later one pins its first few frames to the previous segment's tail, and the segments are stitched at the seam.

Motion expansion
How to run it

Test-case values become lists of prompts, one per segment; --expand_overlap sets how many frames consecutive segments share.

{ "mixamo-sequence": ["A human stands up.", "A human walks forward.", "A human turns around in place."] }
EXPAND_OVERLAP=10 bash scripts/run_sample_motion_expand.sh \
    outputs/uniml3d_60frames_graph_adaln cases.json
Shared behaviour

Each mode writes into its own subdirectory of --output_dir (inbetween/, motion_edit/, motion_expand/) alongside a small JSON recording the constraint that produced it, and each has a wrapper in scripts/ — run any of them with -h for the full option list.

In-betweening and editing clamp against a real clip, so their test-case keys must be <object_type>-<clip_id> naming a clip the dataset actually holds; that clip's motion is saved beside the result as <case_id>-gt_rep_<r>-<i>.npy for side-by-side comparison. --gt_start_frame pins which window of the clip is used instead of a random one. Editing trims both the sample and the GT to the clip's true length, while in-betweening generates the full window and trims only the GT — so align the two on frame 0 rather than assuming equal lengths. All three modes need --cfg_scale > 1.0 and are mutually exclusive with each other.

📝 Citation

If you find UniMate useful in your research, please consider citing our work:

@article{mou2026unimate,
  title   = {UniMate: One Unified Model to Animate Diverse Skeletons},
  author  = {Mou, Linzhan and Lei, Jiahui and Dou, Zhiyang and Cai, Chenyue and Song, Chaoyue and Finkelstein, Adam and Rusinkiewicz, Szymon},
  journal = {arXiv preprint arXiv:2609.05415},
  year    = {2026}
}

⚖️ License

The code in this repository is released under the MIT License.

The datasets remain governed by the licenses of their original sources: the Mixamo assets by Adobe's Mixamo terms of use, the Objaverse-XL assets by the license attached to each original object, and the Truebones ZOO motions by Truebones' commercial license. Please review and comply with the respective source licenses before using the data.

🤝 Acknowledgement

We thank the authors of AnyTop for open-sourcing their codebase, on which parts of this repository build.

View on GitHub

Recent activity

commits and pull requests

Recent open issues

view all

Commits per week

last 52 weeks
60Week of 2025-10-05: 0 commitsWeek of 2025-10-12: 0 commitsWeek of 2025-10-19: 0 commitsWeek of 2025-10-26: 0 commitsWeek of 2025-11-02: 0 commitsWeek of 2025-11-09: 0 commitsWeek of 2025-11-16: 0 commitsWeek of 2025-11-23: 0 commitsWeek of 2025-11-30: 0 commitsWeek of 2025-12-07: 0 commitsWeek of 2025-12-14: 0 commitsWeek of 2025-12-21: 0 commitsWeek of 2025-12-28: 0 commitsWeek of 2026-01-04: 0 commitsWeek of 2026-01-11: 0 commitsWeek of 2026-01-18: 0 commitsWeek of 2026-01-25: 0 commitsWeek of 2026-02-01: 0 commitsWeek of 2026-02-08: 0 commitsWeek of 2026-02-15: 0 commitsWeek of 2026-02-22: 0 commitsWeek of 2026-03-01: 0 commitsWeek of 2026-03-08: 0 commitsWeek of 2026-03-15: 0 commitsWeek of 2026-03-22: 0 commitsWeek of 2026-03-29: 0 commitsWeek of 2026-04-05: 0 commitsWeek of 2026-04-12: 0 commitsWeek of 2026-04-19: 0 commitsWeek of 2026-04-26: 0 commitsWeek of 2026-05-03: 0 commitsWeek of 2026-05-10: 0 commitsWeek of 2026-05-17: 0 commitsWeek of 2026-05-24: 0 commitsWeek of 2026-05-31: 0 commitsWeek of 2026-06-07: 0 commitsWeek of 2026-06-14: 0 commitsWeek of 2026-06-21: 0 commitsWeek of 2026-06-28: 0 commitsWeek of 2026-07-05: 0 commitsWeek of 2026-07-12: 0 commitsWeek of 2026-07-19: 6 commitsWeek of 2026-07-26: 1 commitsWeek of 2026-08-02: 2 commitsWeek of 2026-08-09: 0 commitsWeek of 2026-08-16: 0 commitsWeek of 2026-08-23: 0 commitsWeek of 2026-08-30: 4 commitsWeek of 2026-09-06: 5 commitsWeek of 2026-09-13: 0 commitsWeek of 2026-09-20: 0 commitsWeek of 2026-09-27: 4 commitsOct 5, 2025Sep 27, 2026
22 commits in the last 52 weeks.

When work happens

weekday and hour
SunMonTueWedThuFriSat036912151821Sun 0:00 — 0 commitsSun 1:00 — 0 commitsSun 2:00 — 0 commitsSun 3:00 — 0 commitsSun 4:00 — 0 commitsSun 5:00 — 0 commitsSun 6:00 — 1 commitsSun 7:00 — 2 commitsSun 8:00 — 0 commitsSun 9:00 — 0 commitsSun 10:00 — 0 commitsSun 11:00 — 0 commitsSun 12:00 — 0 commitsSun 13:00 — 0 commitsSun 14:00 — 0 commitsSun 15:00 — 2 commitsSun 16:00 — 0 commitsSun 17:00 — 0 commitsSun 18:00 — 0 commitsSun 19:00 — 0 commitsSun 20:00 — 0 commitsSun 21:00 — 0 commitsSun 22:00 — 2 commitsSun 23:00 — 0 commitsMon 0:00 — 0 commitsMon 1:00 — 0 commitsMon 2:00 — 1 commitsMon 3:00 — 0 commitsMon 4:00 — 0 commitsMon 5:00 — 0 commitsMon 6:00 — 0 commitsMon 7:00 — 0 commitsMon 8:00 — 0 commitsMon 9:00 — 0 commitsMon 10:00 — 0 commitsMon 11:00 — 0 commitsMon 12:00 — 0 commitsMon 13:00 — 0 commitsMon 14:00 — 0 commitsMon 15:00 — 0 commitsMon 16:00 — 0 commitsMon 17:00 — 0 commitsMon 18:00 — 0 commitsMon 19:00 — 0 commitsMon 20:00 — 0 commitsMon 21:00 — 0 commitsMon 22:00 — 0 commitsMon 23:00 — 0 commitsTue 0:00 — 0 commitsTue 1:00 — 0 commitsTue 2:00 — 2 commitsTue 3:00 — 0 commitsTue 4:00 — 0 commitsTue 5:00 — 0 commitsTue 6:00 — 0 commitsTue 7:00 — 0 commitsTue 8:00 — 0 commitsTue 9:00 — 0 commitsTue 10:00 — 0 commitsTue 11:00 — 0 commitsTue 12:00 — 0 commitsTue 13:00 — 0 commitsTue 14:00 — 0 commitsTue 15:00 — 0 commitsTue 16:00 — 0 commitsTue 17:00 — 0 commitsTue 18:00 — 0 commitsTue 19:00 — 0 commitsTue 20:00 — 0 commitsTue 21:00 — 0 commitsTue 22:00 — 0 commitsTue 23:00 — 0 commitsWed 0:00 — 0 commitsWed 1:00 — 0 commitsWed 2:00 — 0 commitsWed 3:00 — 0 commitsWed 4:00 — 0 commitsWed 5:00 — 0 commitsWed 6:00 — 0 commitsWed 7:00 — 0 commitsWed 8:00 — 0 commitsWed 9:00 — 0 commitsWed 10:00 — 0 commitsWed 11:00 — 0 commitsWed 12:00 — 0 commitsWed 13:00 — 0 commitsWed 14:00 — 0 commitsWed 15:00 — 1 commitsWed 16:00 — 0 commitsWed 17:00 — 0 commitsWed 18:00 — 0 commitsWed 19:00 — 7 commitsWed 20:00 — 0 commitsWed 21:00 — 0 commitsWed 22:00 — 0 commitsWed 23:00 — 0 commitsThu 0:00 — 0 commitsThu 1:00 — 0 commitsThu 2:00 — 0 commitsThu 3:00 — 0 commitsThu 4:00 — 0 commitsThu 5:00 — 0 commitsThu 6:00 — 0 commitsThu 7:00 — 0 commitsThu 8:00 — 0 commitsThu 9:00 — 0 commitsThu 10:00 — 0 commitsThu 11:00 — 0 commitsThu 12:00 — 0 commitsThu 13:00 — 0 commitsThu 14:00 — 0 commitsThu 15:00 — 0 commitsThu 16:00 — 0 commitsThu 17:00 — 0 commitsThu 18:00 — 0 commitsThu 19:00 — 0 commitsThu 20:00 — 0 commitsThu 21:00 — 0 commitsThu 22:00 — 0 commitsThu 23:00 — 0 commitsFri 0:00 — 0 commitsFri 1:00 — 0 commitsFri 2:00 — 0 commitsFri 3:00 — 0 commitsFri 4:00 — 0 commitsFri 5:00 — 0 commitsFri 6:00 — 0 commitsFri 7:00 — 0 commitsFri 8:00 — 0 commitsFri 9:00 — 0 commitsFri 10:00 — 0 commitsFri 11:00 — 0 commitsFri 12:00 — 0 commitsFri 13:00 — 0 commitsFri 14:00 — 0 commitsFri 15:00 — 0 commitsFri 16:00 — 0 commitsFri 17:00 — 0 commitsFri 18:00 — 0 commitsFri 19:00 — 1 commitsFri 20:00 — 0 commitsFri 21:00 — 0 commitsFri 22:00 — 0 commitsFri 23:00 — 0 commitsSat 0:00 — 0 commitsSat 1:00 — 0 commitsSat 2:00 — 0 commitsSat 3:00 — 0 commitsSat 4:00 — 0 commitsSat 5:00 — 0 commitsSat 6:00 — 0 commitsSat 7:00 — 0 commitsSat 8:00 — 0 commitsSat 9:00 — 0 commitsSat 10:00 — 1 commitsSat 11:00 — 1 commitsSat 12:00 — 0 commitsSat 13:00 — 0 commitsSat 14:00 — 0 commitsSat 15:00 — 0 commitsSat 16:00 — 1 commitsSat 17:00 — 0 commitsSat 18:00 — 0 commitsSat 19:00 — 0 commitsSat 20:00 — 0 commitsSat 21:00 — 0 commitsSat 22:00 — 0 commitsSat 23:00 — 0 commits
Commit volume by weekday and hour (UTC). Larger dots mean more commits.

Who is committing

last 52 weeks
Maintainer commits22 (100%)
Community commits0 (0%)

22 commits in total over the last year.

DateListRankStars gained
Oct 2, 2026daily#11+225
Oct 1, 2026daily#11+225