Limit Blender animation export to authored clips

This commit is contained in:
2026-09-21 17:29:47 +02:00
parent d430eed641
commit 4a15b089ac
2 changed files with 77 additions and 3 deletions
+37
View File
@@ -17,6 +17,7 @@ use bevy::{
}; };
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
collections::HashMap,
fs, io, fs, io,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Command, process::Command,
@@ -38,6 +39,11 @@ struct ExportManifest {
dependencies: Vec<String>, dependencies: Vec<String>,
} }
#[derive(Debug, Deserialize)]
struct AnimationManifest {
animations: HashMap<String, String>,
}
impl AssetLoader for BlendSourceLoader { impl AssetLoader for BlendSourceLoader {
type Asset = BlendSource; type Asset = BlendSource;
type Settings = (); type Settings = ();
@@ -63,6 +69,12 @@ impl AssetLoader for BlendSourceLoader {
}; };
let output = temp.path().join("export.gltf"); let output = temp.path().join("export.gltf");
let script = temp.path().join("export.py"); let script = temp.path().join("export.py");
let actions = referenced_actions(load_context, &asset_root()).await?;
let actions_path = temp.path().join("actions.json");
fs::write(
&actions_path,
serde_json::to_vec(&actions).map_err(io::Error::other)?,
)?;
fs::write(&script, EXPORT_SCRIPT)?; fs::write(&script, EXPORT_SCRIPT)?;
let result = Command::new(std::env::var_os("BLENDER").unwrap_or_else(|| "blender".into())) let result = Command::new(std::env::var_os("BLENDER").unwrap_or_else(|| "blender".into()))
@@ -72,6 +84,7 @@ impl AssetLoader for BlendSourceLoader {
.arg(&script) .arg(&script)
.arg("--") .arg("--")
.arg(&output) .arg(&output)
.arg(&actions_path)
.output() .output()
.map_err(|error| io::Error::other(format!("failed to start Blender: {error}")))?; .map_err(|error| io::Error::other(format!("failed to start Blender: {error}")))?;
if !result.status.success() { if !result.status.success() {
@@ -127,6 +140,30 @@ impl AssetLoader for BlendSourceLoader {
} }
} }
async fn referenced_actions(
load_context: &mut LoadContext<'_>,
root: &Path,
) -> io::Result<Vec<String>> {
let model_path = load_context.path().path();
let Some(stem) = model_path.file_stem().and_then(|stem| stem.to_str()) else {
return Ok(Vec::new());
};
let manifest_path = model_path
.parent()
.unwrap_or_else(|| Path::new(""))
.join(format!("{stem}.fighter.ron"));
if !root.join(&manifest_path).is_file() {
return Ok(Vec::new());
}
let bytes = load_context
.read_asset_bytes(manifest_path)
.await
.map_err(|error| io::Error::other(error.to_string()))?;
let manifest: AnimationManifest = ron::de::from_bytes(&bytes)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
Ok(manifest.animations.into_values().collect())
}
#[derive(Asset, TypePath)] #[derive(Asset, TypePath)]
struct ProcessedBlend { struct ProcessedBlend {
bytes: Vec<u8>, bytes: Vec<u8>,
+37
View File
@@ -6,6 +6,41 @@ import sys
def main(): def main():
output = sys.argv[sys.argv.index("--") + 1] output = sys.argv[sys.argv.index("--") + 1]
actions_path = sys.argv[sys.argv.index("--") + 2]
with open(actions_path) as file:
actions_to_keep = set(json.load(file))
for obj in bpy.data.objects:
if not obj.animation_data:
continue
for track in list(obj.animation_data.nla_tracks):
if track.name.startswith("[Action Stash]"):
obj.animation_data.nla_tracks.remove(track)
nla_tracks = [
track
for obj in bpy.data.objects
if obj.animation_data
for track in obj.animation_data.nla_tracks
]
use_nla_tracks = bool(nla_tracks)
# For blends without NLA tracks, use the actions referenced by the fighter
# manifest instead of sampling every source action.
if not use_nla_tracks and actions_to_keep:
from io_scene_gltf2 import GLTF2_filter_action
if not hasattr(bpy.types.Scene, "gltf_action_filter"):
bpy.types.Scene.gltf_action_filter = bpy.props.CollectionProperty(
type=GLTF2_filter_action
)
scene = bpy.context.scene
scene.gltf_action_filter.clear()
for action in bpy.data.actions:
item = scene.gltf_action_filter.add()
item.action = action
item.keep = action.name in actions_to_keep
bpy.ops.export_scene.gltf( bpy.ops.export_scene.gltf(
filepath=output, filepath=output,
export_format="GLTF_SEPARATE", export_format="GLTF_SEPARATE",
@@ -15,6 +50,8 @@ def main():
export_skins=True, export_skins=True,
export_morph=True, export_morph=True,
export_yup=True, export_yup=True,
export_action_filter=False,
export_animation_mode="NLA_TRACKS" if use_nla_tracks else "ACTIONS",
) )
dependencies = [] dependencies = []