mirror of
https://github.com/kaosat-dev/Blender_bevy_components_workflow.git
synced 2024-11-23 04:10:53 +00:00
Merge 49dd0bc536
into 6c34ab8bd6
This commit is contained in:
commit
f1f1ed9cfc
@ -3,8 +3,8 @@ use bevy::utils::HashMap;
|
|||||||
|
|
||||||
#[derive(Component, Reflect, Default, Debug)]
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
/// storage for animations for a given entity (hierarchy), essentially a clone of gltf's `named_animations`
|
/// storage for animations for a given entity's BLUEPRINT (ie for example a characters animations), essentially a clone of gltf's `named_animations`
|
||||||
pub struct Animations {
|
pub struct BlueprintAnimations {
|
||||||
pub named_animations: HashMap<String, Handle<AnimationClip>>,
|
pub named_animations: HashMap<String, Handle<AnimationClip>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,4 +13,70 @@ pub struct Animations {
|
|||||||
/// so that the root entity knows which of its children contains an actualy `AnimationPlayer` component
|
/// so that the root entity knows which of its children contains an actualy `AnimationPlayer` component
|
||||||
/// this is for convenience, because currently , Bevy's gltf parsing inserts `AnimationPlayers` "one level down"
|
/// this is for convenience, because currently , Bevy's gltf parsing inserts `AnimationPlayers` "one level down"
|
||||||
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
|
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
|
||||||
pub struct AnimationPlayerLink(pub Entity);
|
pub struct BlueprintAnimationPlayerLink(pub Entity);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// storage for animations for a given entity (hierarchy), essentially a clone of gltf's `named_animations`
|
||||||
|
pub struct InstanceAnimations {
|
||||||
|
pub named_animations: HashMap<String, Handle<AnimationClip>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
/// Stop gap helper component : this is inserted into a "root" entity (an entity representing a whole gltf file)
|
||||||
|
/// so that the root entity knows which of its children contains an actualy `AnimationPlayer` component
|
||||||
|
/// this is for convenience, because currently , Bevy's gltf parsing inserts `AnimationPlayers` "one level down"
|
||||||
|
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
|
||||||
|
pub struct InstanceAnimationPlayerLink(pub Entity);
|
||||||
|
|
||||||
|
/// Stores Animation information: name, frame informations etc
|
||||||
|
#[derive(Reflect, Default, Debug)]
|
||||||
|
pub struct AnimationInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub frame_start: f32,
|
||||||
|
pub frame_end: f32,
|
||||||
|
pub frames_length: f32,
|
||||||
|
pub frame_start_override: f32,
|
||||||
|
pub frame_end_override: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores information about animations, to make things a bit easier api wise:
|
||||||
|
/// these components are automatically inserted by gltf_auto_export on entities that have animations
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct AnimationInfos {
|
||||||
|
pub animations: Vec<AnimationInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AnimationMarker {
|
||||||
|
pub frame: u32,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores information about animation markers: practical for adding things like triggering events at specific keyframes etc
|
||||||
|
/// it is essentiall a hashmap of AnimationName => HashMap<FrameNumber, Vec of marker names>
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct AnimationMarkers(pub HashMap<String, HashMap<u32, Vec<String>>>);
|
||||||
|
|
||||||
|
// FIXME: ugh, ugly, there has to be a better way to do this ?
|
||||||
|
#[derive(Component, Default, Debug)]
|
||||||
|
pub struct AnimationMarkerTrackers(pub HashMap<String, HashMap<u32, Vec<AnimationMarkerTracker>>>);
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct AnimationMarkerTracker {
|
||||||
|
// pub frame:u32,
|
||||||
|
// pub name: String,
|
||||||
|
// pub processed_for_cycle: bool,
|
||||||
|
pub prev_frame: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Event that gets triggered once a specific marker inside an animation has been reached (frame based)
|
||||||
|
/// Provides some usefull information about which entity , wich animation, wich frame & which marker got triggered
|
||||||
|
#[derive(Event, Debug)]
|
||||||
|
pub struct AnimationMarkerReached {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub animation_name: String,
|
||||||
|
pub frame: u32,
|
||||||
|
pub marker_name: String,
|
||||||
|
}
|
||||||
|
@ -123,9 +123,16 @@ impl Plugin for BlueprintsPlugin {
|
|||||||
.register_type::<BlueprintName>()
|
.register_type::<BlueprintName>()
|
||||||
.register_type::<MaterialInfo>()
|
.register_type::<MaterialInfo>()
|
||||||
.register_type::<SpawnHere>()
|
.register_type::<SpawnHere>()
|
||||||
.register_type::<Animations>()
|
.register_type::<BlueprintAnimations>()
|
||||||
|
.register_type::<InstanceAnimations>()
|
||||||
|
.register_type::<AnimationInfo>()
|
||||||
|
.register_type::<AnimationInfos>()
|
||||||
|
.register_type::<Vec<AnimationInfo>>()
|
||||||
|
.register_type::<AnimationMarkers>()
|
||||||
|
.register_type::<HashMap<u32, Vec<String>>>()
|
||||||
|
.register_type::<HashMap<String, HashMap<u32, Vec<String>>>>()
|
||||||
|
.add_event::<AnimationMarkerReached>()
|
||||||
.register_type::<BlueprintsList>()
|
.register_type::<BlueprintsList>()
|
||||||
.register_type::<Vec<String>>()
|
|
||||||
.register_type::<HashMap<String, Vec<String>>>()
|
.register_type::<HashMap<String, Vec<String>>>()
|
||||||
.insert_resource(BluePrintsConfig {
|
.insert_resource(BluePrintsConfig {
|
||||||
format: self.format,
|
format: self.format,
|
||||||
|
@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use bevy::{gltf::Gltf, prelude::*, utils::HashMap};
|
use bevy::{gltf::Gltf, prelude::*, utils::HashMap};
|
||||||
|
|
||||||
use crate::{Animations, BluePrintsConfig};
|
use crate::{BluePrintsConfig, BlueprintAnimations};
|
||||||
|
|
||||||
/// this is a flag component for our levels/game world
|
/// this is a flag component for our levels/game world
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
@ -279,11 +279,12 @@ pub(crate) fn spawn_from_blueprints(
|
|||||||
transform: transforms,
|
transform: transforms,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Animations {
|
|
||||||
named_animations: gltf.named_animations.clone(),
|
|
||||||
},
|
|
||||||
Spawned,
|
Spawned,
|
||||||
OriginalChildren(original_children),
|
OriginalChildren(original_children),
|
||||||
|
BlueprintAnimations {
|
||||||
|
// these are animations specific to the inside of the blueprint
|
||||||
|
named_animations: gltf.named_animations.clone(),
|
||||||
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
if add_to_world.is_some() {
|
if add_to_world.is_some() {
|
||||||
|
@ -5,7 +5,7 @@ use bevy::prelude::*;
|
|||||||
use bevy::scene::SceneInstance;
|
use bevy::scene::SceneInstance;
|
||||||
// use bevy::utils::hashbrown::HashSet;
|
// use bevy::utils::hashbrown::HashSet;
|
||||||
|
|
||||||
use super::{AnimationPlayerLink, Animations};
|
use super::{BlueprintAnimationPlayerLink, BlueprintAnimations};
|
||||||
use super::{SpawnHere, Spawned};
|
use super::{SpawnHere, Spawned};
|
||||||
use crate::{
|
use crate::{
|
||||||
AssetsToLoad, BlueprintAssetsLoaded, CopyComponents, InBlueprint, NoInBlueprint,
|
AssetsToLoad, BlueprintAssetsLoaded, CopyComponents, InBlueprint, NoInBlueprint,
|
||||||
@ -24,7 +24,7 @@ pub(crate) fn spawned_blueprint_post_process(
|
|||||||
Entity,
|
Entity,
|
||||||
&Children,
|
&Children,
|
||||||
&OriginalChildren,
|
&OriginalChildren,
|
||||||
&Animations,
|
&BlueprintAnimations,
|
||||||
Option<&NoInBlueprint>,
|
Option<&NoInBlueprint>,
|
||||||
Option<&Name>,
|
Option<&Name>,
|
||||||
),
|
),
|
||||||
@ -85,7 +85,9 @@ pub(crate) fn spawned_blueprint_post_process(
|
|||||||
// FIXME: stopgap solution: since we cannot use an AnimationPlayer at the root entity level
|
// FIXME: stopgap solution: since we cannot use an AnimationPlayer at the root entity level
|
||||||
// and we cannot update animation clips so that the EntityPaths point to one level deeper,
|
// and we cannot update animation clips so that the EntityPaths point to one level deeper,
|
||||||
// BUT we still want to have some marker/control at the root entity level, we add this
|
// BUT we still want to have some marker/control at the root entity level, we add this
|
||||||
commands.entity(original).insert(AnimationPlayerLink(added));
|
commands
|
||||||
|
.entity(original)
|
||||||
|
.insert(BlueprintAnimationPlayerLink(added));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3373,6 +3373,17 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"typeInfo": "Struct"
|
"typeInfo": "Struct"
|
||||||
},
|
},
|
||||||
|
"bevy_gltf_blueprints::animation::Animated": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"isComponent": true,
|
||||||
|
"isResource": false,
|
||||||
|
"properties": {},
|
||||||
|
"required": [],
|
||||||
|
"short_name": "Animated",
|
||||||
|
"title": "bevy_gltf_blueprints::animation::Animated",
|
||||||
|
"type": "object",
|
||||||
|
"typeInfo": "Struct"
|
||||||
|
},
|
||||||
"bevy_gltf_blueprints::animation::Animations": {
|
"bevy_gltf_blueprints::animation::Animations": {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"isComponent": true,
|
"isComponent": true,
|
||||||
@ -3433,6 +3444,22 @@
|
|||||||
"type": "array",
|
"type": "array",
|
||||||
"typeInfo": "TupleStruct"
|
"typeInfo": "TupleStruct"
|
||||||
},
|
},
|
||||||
|
"bevy_gltf_blueprints::spawn_from_blueprints::BlueprintsList": {
|
||||||
|
"isComponent": true,
|
||||||
|
"isResource": false,
|
||||||
|
"items": false,
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"short_name": "BlueprintsList",
|
||||||
|
"title": "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintsList",
|
||||||
|
"type": "array",
|
||||||
|
"typeInfo": "TupleStruct"
|
||||||
|
},
|
||||||
"bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": {
|
"bevy_gltf_blueprints::spawn_from_blueprints::SpawnHere": {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"isComponent": true,
|
"isComponent": true,
|
||||||
@ -10691,6 +10718,19 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"typeInfo": "Value"
|
"typeInfo": "Value"
|
||||||
},
|
},
|
||||||
|
"bevy_utils::hashbrown::HashMap<alloc::string::String, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/alloc::vec::Vec<alloc::string::String>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isComponent": false,
|
||||||
|
"isResource": false,
|
||||||
|
"short_name": "HashMap<String, Vec<String>, DefaultHashBuilder>",
|
||||||
|
"title": "bevy_utils::hashbrown::HashMap<alloc::string::String, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>",
|
||||||
|
"type": "object",
|
||||||
|
"typeInfo": "Map"
|
||||||
|
},
|
||||||
"bevy_utils::smallvec::SmallVec<[bevy_ecs::entity::Entity; 8]>": {
|
"bevy_utils::smallvec::SmallVec<[bevy_ecs::entity::Entity; 8]>": {
|
||||||
"isComponent": false,
|
"isComponent": false,
|
||||||
"isResource": false,
|
"isResource": false,
|
||||||
|
@ -14,3 +14,4 @@ bevy_rapier3d = { version = "0.25.0", features = ["serde-serialize", "debug-rend
|
|||||||
bevy_asset_loader = { version = "0.20", features = ["standard_dynamic_assets"] }
|
bevy_asset_loader = { version = "0.20", features = ["standard_dynamic_assets"] }
|
||||||
bevy_editor_pls = { version = "0.8" }
|
bevy_editor_pls = { version = "0.8" }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
json-writer ="0.3"
|
@ -110,6 +110,19 @@
|
|||||||
"type": "array",
|
"type": "array",
|
||||||
"typeInfo": "List"
|
"typeInfo": "List"
|
||||||
},
|
},
|
||||||
|
"alloc::vec::Vec<bevy_gltf_blueprints::animation::AnimationInfo>": {
|
||||||
|
"isComponent": false,
|
||||||
|
"isResource": false,
|
||||||
|
"items": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/bevy_gltf_blueprints::animation::AnimationInfo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"short_name": "Vec<AnimationInfo>",
|
||||||
|
"title": "alloc::vec::Vec<bevy_gltf_blueprints::animation::AnimationInfo>",
|
||||||
|
"type": "array",
|
||||||
|
"typeInfo": "List"
|
||||||
|
},
|
||||||
"alloc::vec::Vec<bevy_render::color::Color>": {
|
"alloc::vec::Vec<bevy_render::color::Color>": {
|
||||||
"isComponent": false,
|
"isComponent": false,
|
||||||
"isResource": false,
|
"isResource": false,
|
||||||
@ -2947,6 +2960,39 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"typeInfo": "Struct"
|
"typeInfo": "Struct"
|
||||||
},
|
},
|
||||||
|
"bevy_example::game::animation::Marker1": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"isComponent": true,
|
||||||
|
"isResource": false,
|
||||||
|
"properties": {},
|
||||||
|
"required": [],
|
||||||
|
"short_name": "Marker1",
|
||||||
|
"title": "bevy_example::game::animation::Marker1",
|
||||||
|
"type": "object",
|
||||||
|
"typeInfo": "Struct"
|
||||||
|
},
|
||||||
|
"bevy_example::game::animation::Marker2": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"isComponent": true,
|
||||||
|
"isResource": false,
|
||||||
|
"properties": {},
|
||||||
|
"required": [],
|
||||||
|
"short_name": "Marker2",
|
||||||
|
"title": "bevy_example::game::animation::Marker2",
|
||||||
|
"type": "object",
|
||||||
|
"typeInfo": "Struct"
|
||||||
|
},
|
||||||
|
"bevy_example::game::animation::Marker3": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"isComponent": true,
|
||||||
|
"isResource": false,
|
||||||
|
"properties": {},
|
||||||
|
"required": [],
|
||||||
|
"short_name": "Marker3",
|
||||||
|
"title": "bevy_example::game::animation::Marker3",
|
||||||
|
"type": "object",
|
||||||
|
"typeInfo": "Struct"
|
||||||
|
},
|
||||||
"bevy_example::test_components::AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut": {
|
"bevy_example::test_components::AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut": {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"isComponent": true,
|
"isComponent": true,
|
||||||
@ -3516,7 +3562,91 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"typeInfo": "Struct"
|
"typeInfo": "Struct"
|
||||||
},
|
},
|
||||||
"bevy_gltf_blueprints::animation::Animations": {
|
"bevy_gltf_blueprints::animation::AnimationInfo": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"isComponent": false,
|
||||||
|
"isResource": false,
|
||||||
|
"properties": {
|
||||||
|
"frame_end": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/f32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"frame_end_override": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/f32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"frame_start": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/f32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"frame_start_override": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/f32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"frames_length": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/f32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/alloc::string::String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"frame_start",
|
||||||
|
"frame_end",
|
||||||
|
"frames_length",
|
||||||
|
"frame_start_override",
|
||||||
|
"frame_end_override"
|
||||||
|
],
|
||||||
|
"short_name": "AnimationInfo",
|
||||||
|
"title": "bevy_gltf_blueprints::animation::AnimationInfo",
|
||||||
|
"type": "object",
|
||||||
|
"typeInfo": "Struct"
|
||||||
|
},
|
||||||
|
"bevy_gltf_blueprints::animation::AnimationInfos": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"isComponent": true,
|
||||||
|
"isResource": false,
|
||||||
|
"properties": {
|
||||||
|
"animations": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/alloc::vec::Vec<bevy_gltf_blueprints::animation::AnimationInfo>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"animations"
|
||||||
|
],
|
||||||
|
"short_name": "AnimationInfos",
|
||||||
|
"title": "bevy_gltf_blueprints::animation::AnimationInfos",
|
||||||
|
"type": "object",
|
||||||
|
"typeInfo": "Struct"
|
||||||
|
},
|
||||||
|
"bevy_gltf_blueprints::animation::AnimationMarkers": {
|
||||||
|
"isComponent": true,
|
||||||
|
"isResource": false,
|
||||||
|
"items": false,
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"short_name": "AnimationMarkers",
|
||||||
|
"title": "bevy_gltf_blueprints::animation::AnimationMarkers",
|
||||||
|
"type": "array",
|
||||||
|
"typeInfo": "TupleStruct"
|
||||||
|
},
|
||||||
|
"bevy_gltf_blueprints::animation::BlueprintAnimations": {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"isComponent": true,
|
"isComponent": true,
|
||||||
"isResource": false,
|
"isResource": false,
|
||||||
@ -3530,8 +3660,27 @@
|
|||||||
"required": [
|
"required": [
|
||||||
"named_animations"
|
"named_animations"
|
||||||
],
|
],
|
||||||
"short_name": "Animations",
|
"short_name": "BlueprintAnimations",
|
||||||
"title": "bevy_gltf_blueprints::animation::Animations",
|
"title": "bevy_gltf_blueprints::animation::BlueprintAnimations",
|
||||||
|
"type": "object",
|
||||||
|
"typeInfo": "Struct"
|
||||||
|
},
|
||||||
|
"bevy_gltf_blueprints::animation::InstanceAnimations": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"isComponent": true,
|
||||||
|
"isResource": false,
|
||||||
|
"properties": {
|
||||||
|
"named_animations": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_asset::handle::Handle<bevy_animation::AnimationClip>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"named_animations"
|
||||||
|
],
|
||||||
|
"short_name": "InstanceAnimations",
|
||||||
|
"title": "bevy_gltf_blueprints::animation::InstanceAnimations",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"typeInfo": "Struct"
|
"typeInfo": "Struct"
|
||||||
},
|
},
|
||||||
@ -10863,6 +11012,32 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"typeInfo": "Map"
|
"typeInfo": "Map"
|
||||||
},
|
},
|
||||||
|
"bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isComponent": false,
|
||||||
|
"isResource": false,
|
||||||
|
"short_name": "HashMap<String, HashMap<u32, Vec<String>, DefaultHashBuilder>, DefaultHashBuilder>",
|
||||||
|
"title": "bevy_utils::hashbrown::HashMap<alloc::string::String, bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>",
|
||||||
|
"type": "object",
|
||||||
|
"typeInfo": "Map"
|
||||||
|
},
|
||||||
|
"bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/$defs/alloc::vec::Vec<alloc::string::String>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isComponent": false,
|
||||||
|
"isResource": false,
|
||||||
|
"short_name": "HashMap<u32, Vec<String>, DefaultHashBuilder>",
|
||||||
|
"title": "bevy_utils::hashbrown::HashMap<u32, alloc::vec::Vec<alloc::string::String>, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>",
|
||||||
|
"type": "object",
|
||||||
|
"typeInfo": "Map"
|
||||||
|
},
|
||||||
"bevy_utils::smallvec::SmallVec<[bevy_ecs::entity::Entity; 8]>": {
|
"bevy_utils::smallvec::SmallVec<[bevy_ecs::entity::Entity; 8]>": {
|
||||||
"isComponent": false,
|
"isComponent": false,
|
||||||
"isResource": false,
|
"isResource": false,
|
||||||
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 626 KiB After Width: | Height: | Size: 644 KiB |
278
testing/bevy_example/src/game/animation.rs
Normal file
278
testing/bevy_example/src/game/animation.rs
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy_gltf_blueprints::{
|
||||||
|
AnimationInfos, AnimationMarkerReached, AnimationMarkerTrackers, AnimationMarkers,
|
||||||
|
BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintName, BlueprintsList,
|
||||||
|
GltfBlueprintsSet, InstanceAnimationPlayerLink, InstanceAnimations,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bevy::{gltf::Gltf, prelude::*};
|
||||||
|
use bevy_gltf_worlflow_examples_common_rapier::{AppState, GameState};
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// flag component for testing
|
||||||
|
pub struct Marker1;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// flag component for testing
|
||||||
|
pub struct Marker2;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
/// flag component for testing
|
||||||
|
pub struct Marker3;
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct AnimTest(Handle<Gltf>);
|
||||||
|
|
||||||
|
pub fn setup_main_scene_animations(asset_server: Res<AssetServer>, mut commands: Commands) {
|
||||||
|
commands.insert_resource(AnimTest(asset_server.load("models/World.glb")));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn animations(
|
||||||
|
added_animation_players: Query<(Entity, &Name, &AnimationPlayer)>,
|
||||||
|
added_animation_infos: Query<(Entity, &Name, &AnimationInfos), (Added<AnimationInfos>)>,
|
||||||
|
animtest: Res<AnimTest>,
|
||||||
|
mut commands: Commands,
|
||||||
|
assets_gltf: Res<Assets<Gltf>>,
|
||||||
|
parents: Query<&Parent>,
|
||||||
|
) {
|
||||||
|
for (entity, name, animation_infos) in added_animation_infos.iter() {
|
||||||
|
//println!("animated stuf {:?} on entity {}", animation_infos, name);
|
||||||
|
let gltf = assets_gltf.get(&animtest.0).unwrap();
|
||||||
|
let mut matching_data = true;
|
||||||
|
for animation_info in &animation_infos.animations {
|
||||||
|
if !gltf.named_animations.contains_key(&animation_info.name) {
|
||||||
|
matching_data = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matching_data {
|
||||||
|
println!(
|
||||||
|
"inserting Animations components into {} ({:?})",
|
||||||
|
name, entity
|
||||||
|
);
|
||||||
|
println!("Found match {:?}", gltf.named_animations);
|
||||||
|
commands.entity(entity).insert(InstanceAnimations {
|
||||||
|
named_animations: gltf.named_animations.clone(),
|
||||||
|
});
|
||||||
|
for ancestor in parents.iter_ancestors(entity) {
|
||||||
|
if added_animation_players.contains(ancestor) {
|
||||||
|
// println!("found match with animationPlayer !! {:?}",names.get(ancestor));
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.insert(InstanceAnimationPlayerLink(ancestor));
|
||||||
|
}
|
||||||
|
// info!("{:?} is an ancestor of {:?}", ancestor, player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_animations(
|
||||||
|
animated_marker1: Query<
|
||||||
|
(&InstanceAnimationPlayerLink, &InstanceAnimations),
|
||||||
|
(With<AnimationInfos>, With<Marker1>),
|
||||||
|
>,
|
||||||
|
animated_marker2: Query<
|
||||||
|
(&InstanceAnimationPlayerLink, &InstanceAnimations),
|
||||||
|
(With<AnimationInfos>, With<Marker2>),
|
||||||
|
>,
|
||||||
|
animated_marker3: Query<
|
||||||
|
(
|
||||||
|
&InstanceAnimationPlayerLink,
|
||||||
|
&InstanceAnimations,
|
||||||
|
&BlueprintAnimationPlayerLink,
|
||||||
|
&BlueprintAnimations,
|
||||||
|
),
|
||||||
|
(With<AnimationInfos>, With<Marker3>),
|
||||||
|
>,
|
||||||
|
|
||||||
|
mut animation_players: Query<&mut AnimationPlayer>,
|
||||||
|
keycode: Res<ButtonInput<KeyCode>>,
|
||||||
|
) {
|
||||||
|
if keycode.just_pressed(KeyCode::KeyM) {
|
||||||
|
for (link, animations) in animated_marker1.iter() {
|
||||||
|
println!("animations {:?}", animations.named_animations);
|
||||||
|
let mut animation_player = animation_players.get_mut(link.0).unwrap();
|
||||||
|
let anim_name = "Blueprint1_move";
|
||||||
|
animation_player
|
||||||
|
.play_with_transition(
|
||||||
|
animations
|
||||||
|
.named_animations
|
||||||
|
.get(anim_name)
|
||||||
|
.expect("animation name should be in the list")
|
||||||
|
.clone(),
|
||||||
|
Duration::from_secs(5),
|
||||||
|
)
|
||||||
|
.repeat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if keycode.just_pressed(KeyCode::KeyJ) {
|
||||||
|
for (link, animations) in animated_marker1.iter() {
|
||||||
|
println!("animations {:?}", animations.named_animations);
|
||||||
|
let mut animation_player = animation_players.get_mut(link.0).unwrap();
|
||||||
|
let anim_name = "Blueprint1_jump";
|
||||||
|
animation_player
|
||||||
|
.play_with_transition(
|
||||||
|
animations
|
||||||
|
.named_animations
|
||||||
|
.get(anim_name)
|
||||||
|
.expect("animation name should be in the list")
|
||||||
|
.clone(),
|
||||||
|
Duration::from_secs(5),
|
||||||
|
)
|
||||||
|
.repeat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycode.just_pressed(KeyCode::KeyA) {
|
||||||
|
for (link, animations) in animated_marker2.iter() {
|
||||||
|
println!("animations {:?}", animations.named_animations);
|
||||||
|
let mut animation_player = animation_players.get_mut(link.0).unwrap();
|
||||||
|
let anim_name = "Blueprint1_move";
|
||||||
|
animation_player
|
||||||
|
.play_with_transition(
|
||||||
|
animations
|
||||||
|
.named_animations
|
||||||
|
.get(anim_name)
|
||||||
|
.expect("animation name should be in the list")
|
||||||
|
.clone(),
|
||||||
|
Duration::from_secs(5),
|
||||||
|
)
|
||||||
|
.repeat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if keycode.just_pressed(KeyCode::KeyB) {
|
||||||
|
for (link, animations) in animated_marker2.iter() {
|
||||||
|
println!("animations {:?}", animations.named_animations);
|
||||||
|
let mut animation_player = animation_players.get_mut(link.0).unwrap();
|
||||||
|
let anim_name = "Blueprint1_jump";
|
||||||
|
animation_player
|
||||||
|
.play_with_transition(
|
||||||
|
animations
|
||||||
|
.named_animations
|
||||||
|
.get(anim_name)
|
||||||
|
.expect("animation name should be in the list")
|
||||||
|
.clone(),
|
||||||
|
Duration::from_secs(5),
|
||||||
|
)
|
||||||
|
.repeat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// play instance animation
|
||||||
|
if keycode.just_pressed(KeyCode::KeyW) {
|
||||||
|
for (link, animations, _, _) in animated_marker3.iter() {
|
||||||
|
println!("animations {:?}", animations.named_animations);
|
||||||
|
let mut animation_player = animation_players.get_mut(link.0).unwrap();
|
||||||
|
let anim_name = "Blueprint8_move";
|
||||||
|
animation_player
|
||||||
|
.play_with_transition(
|
||||||
|
animations
|
||||||
|
.named_animations
|
||||||
|
.get(anim_name)
|
||||||
|
.expect("animation name should be in the list")
|
||||||
|
.clone(),
|
||||||
|
Duration::from_secs(5),
|
||||||
|
)
|
||||||
|
.repeat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// play blueprint animation
|
||||||
|
if keycode.just_pressed(KeyCode::KeyX) {
|
||||||
|
for (_, _, link, animations) in animated_marker3.iter() {
|
||||||
|
println!("animations {:?}", animations.named_animations);
|
||||||
|
let mut animation_player = animation_players.get_mut(link.0).unwrap();
|
||||||
|
let anim_name = "Walk";
|
||||||
|
animation_player
|
||||||
|
.play_with_transition(
|
||||||
|
animations
|
||||||
|
.named_animations
|
||||||
|
.get(anim_name)
|
||||||
|
.expect("animation name should be in the list")
|
||||||
|
.clone(),
|
||||||
|
Duration::from_secs(5),
|
||||||
|
)
|
||||||
|
.repeat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trigger_event_based_on_animation_marker(
|
||||||
|
animation_infos: Query<(
|
||||||
|
Entity,
|
||||||
|
&AnimationMarkers,
|
||||||
|
&InstanceAnimationPlayerLink,
|
||||||
|
&InstanceAnimations,
|
||||||
|
&AnimationInfos,
|
||||||
|
)>,
|
||||||
|
animation_players: Query<&AnimationPlayer>,
|
||||||
|
animation_clips: Res<Assets<AnimationClip>>,
|
||||||
|
mut animation_marker_events: EventWriter<AnimationMarkerReached>,
|
||||||
|
) {
|
||||||
|
for (entity, markers, link, animations, animation_infos) in animation_infos.iter() {
|
||||||
|
let animation_player = animation_players.get(link.0).unwrap();
|
||||||
|
let animation_clip = animation_clips.get(animation_player.animation_clip());
|
||||||
|
|
||||||
|
if animation_clip.is_some() {
|
||||||
|
// if marker_trackers.0.contains_key(k)
|
||||||
|
// marker_trackers.0
|
||||||
|
// println!("Entity {:?} markers {:?}", entity, markers);
|
||||||
|
// println!("Player {:?} {}", animation_player.elapsed(), animation_player.completions());
|
||||||
|
|
||||||
|
// FIMXE: yikes ! very inneficient ! perhaps add boilerplate to the "start playing animation" code so we know what is playing
|
||||||
|
let animation_name = animations.named_animations.iter().find_map(|(key, value)| {
|
||||||
|
if value == animation_player.animation_clip() {
|
||||||
|
Some(key)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if animation_name.is_some() {
|
||||||
|
let animation_name = animation_name.unwrap();
|
||||||
|
|
||||||
|
let animation_length_seconds = animation_clip.unwrap().duration();
|
||||||
|
let animation_length_frames = animation_infos
|
||||||
|
.animations
|
||||||
|
.iter()
|
||||||
|
.find(|anim| &anim.name == animation_name)
|
||||||
|
.unwrap()
|
||||||
|
.frames_length;
|
||||||
|
// TODO: we also need to take playback speed into account
|
||||||
|
let time_in_animation = animation_player.elapsed()
|
||||||
|
- (animation_player.completions() as f32) * animation_length_seconds;
|
||||||
|
let frame_seconds =
|
||||||
|
(animation_length_frames as f32 / animation_length_seconds) * time_in_animation;
|
||||||
|
let frame = frame_seconds as u32;
|
||||||
|
|
||||||
|
let matching_animation_marker = &markers.0[animation_name];
|
||||||
|
if matching_animation_marker.contains_key(&frame) {
|
||||||
|
let matching_markers_per_frame = matching_animation_marker.get(&frame).unwrap();
|
||||||
|
// println!("FOUND A MARKER {:?} at frame {}", matching_markers_per_frame, frame);
|
||||||
|
//emit an event , something like AnimationMarkerReached(entity, animation_name, frame, marker_name)
|
||||||
|
// FIXME: problem, this can fire multiple times in a row, depending on animation length , speed , etc
|
||||||
|
for marker_name in matching_markers_per_frame {
|
||||||
|
animation_marker_events.send(AnimationMarkerReached {
|
||||||
|
entity: entity,
|
||||||
|
animation_name: animation_name.clone(),
|
||||||
|
frame: frame,
|
||||||
|
marker_name: marker_name.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn react_to_animation_markers(
|
||||||
|
mut animation_marker_events: EventReader<AnimationMarkerReached>,
|
||||||
|
) {
|
||||||
|
for event in animation_marker_events.read() {
|
||||||
|
println!("animation marker event {:?}", event)
|
||||||
|
}
|
||||||
|
}
|
@ -1,52 +1,47 @@
|
|||||||
|
pub mod animation;
|
||||||
pub mod in_game;
|
pub mod in_game;
|
||||||
use std::{
|
pub use animation::*;
|
||||||
fs::{self},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use bevy_gltf_blueprints::{AnimationPlayerLink, BlueprintName, BlueprintsList};
|
|
||||||
pub use in_game::*;
|
pub use in_game::*;
|
||||||
|
|
||||||
|
use std::{collections::HashMap, fs, time::Duration};
|
||||||
|
|
||||||
|
use bevy_gltf_blueprints::{
|
||||||
|
AnimationInfos, AnimationMarkerReached, AnimationMarkerTrackers, AnimationMarkers,
|
||||||
|
BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintName, BlueprintsList,
|
||||||
|
GltfBlueprintsSet, InstanceAnimationPlayerLink, InstanceAnimations,
|
||||||
|
};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::*, render::view::screenshot::ScreenshotManager, time::common_conditions::on_timer,
|
ecs::query, gltf::Gltf, prelude::*, render::view::screenshot::ScreenshotManager,
|
||||||
window::PrimaryWindow,
|
time::common_conditions::on_timer, window::PrimaryWindow,
|
||||||
};
|
};
|
||||||
use bevy_gltf_worlflow_examples_common_rapier::{AppState, GameState};
|
use bevy_gltf_worlflow_examples_common_rapier::{AppState, GameState};
|
||||||
|
|
||||||
use crate::{TupleTestF32, UnitTest};
|
use crate::{TupleTestF32, UnitTest};
|
||||||
|
use json_writer::to_json_string;
|
||||||
|
|
||||||
fn start_game(mut next_app_state: ResMut<NextState<AppState>>) {
|
fn start_game(mut next_app_state: ResMut<NextState<AppState>>) {
|
||||||
next_app_state.set(AppState::AppLoading);
|
next_app_state.set(AppState::AppLoading);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the export from Blender worked correctly, we should have animations (simplified here by using AnimationPlayerLink)
|
// if the export from Blender worked correctly, we should have animations (simplified here by using AnimationPlayerLink)
|
||||||
// if the export from Blender worked correctly, we should have an Entity called "Cylinder" that has two components: UnitTest, TupleTestF32
|
|
||||||
// if the export from Blender worked correctly, we should have an Entity called "Blueprint4_nested" that has a child called "Blueprint3" that has a "BlueprintName" component with value Blueprint3
|
// if the export from Blender worked correctly, we should have an Entity called "Blueprint4_nested" that has a child called "Blueprint3" that has a "BlueprintName" component with value Blueprint3
|
||||||
// if the export from Blender worked correctly, we should have a blueprints_list
|
// if the export from Blender worked correctly, we should have a blueprints_list
|
||||||
|
// if the export from Blender worked correctly, we should have the correct tree of entities
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn validate_export(
|
fn validate_export(
|
||||||
parents: Query<&Parent>,
|
parents: Query<&Parent>,
|
||||||
children: Query<&Children>,
|
children: Query<&Children>,
|
||||||
names: Query<&Name>,
|
names: Query<&Name>,
|
||||||
blueprints: Query<(Entity, &Name, &BlueprintName)>,
|
blueprints: Query<(Entity, &Name, &BlueprintName)>,
|
||||||
animation_player_links: Query<(Entity, &AnimationPlayerLink)>,
|
animation_player_links: Query<(Entity, &BlueprintAnimationPlayerLink)>,
|
||||||
exported_cylinder: Query<(Entity, &Name, &UnitTest, &TupleTestF32)>,
|
|
||||||
empties_candidates: Query<(Entity, &Name, &GlobalTransform)>,
|
empties_candidates: Query<(Entity, &Name, &GlobalTransform)>,
|
||||||
|
|
||||||
blueprints_list: Query<(Entity, &BlueprintsList)>,
|
blueprints_list: Query<(Entity, &BlueprintsList)>,
|
||||||
|
root: Query<(Entity, &Name, &Children), (Without<Parent>, With<Children>)>,
|
||||||
) {
|
) {
|
||||||
let animations_found = !animation_player_links.is_empty();
|
let animations_found = !animation_player_links.is_empty();
|
||||||
|
|
||||||
let mut cylinder_found = false;
|
|
||||||
if let Ok(nested_cylinder) = exported_cylinder.get_single() {
|
|
||||||
let parent_name = names
|
|
||||||
.get(parents.get(nested_cylinder.0).unwrap().get())
|
|
||||||
.unwrap();
|
|
||||||
cylinder_found = parent_name.to_string() == *"Cube.001"
|
|
||||||
&& nested_cylinder.1.to_string() == *"Cylinder"
|
|
||||||
&& nested_cylinder.3 .0 == 75.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut nested_blueprint_found = false;
|
let mut nested_blueprint_found = false;
|
||||||
for (entity, name, blueprint_name) in blueprints.iter() {
|
for (entity, name, blueprint_name) in blueprints.iter() {
|
||||||
if name.to_string() == *"Blueprint4_nested" && blueprint_name.0 == *"Blueprint4_nested" {
|
if name.to_string() == *"Blueprint4_nested" && blueprint_name.0 == *"Blueprint4_nested" {
|
||||||
@ -71,14 +66,46 @@ fn validate_export(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// check if there are blueprints_list components
|
||||||
let blueprints_list_found = !blueprints_list.is_empty();
|
let blueprints_list_found = !blueprints_list.is_empty();
|
||||||
|
|
||||||
|
// there should be no entity named xxx____bak as it means an error in the Blender side export process
|
||||||
|
let mut exported_names_correct = true;
|
||||||
|
for name in names.iter() {
|
||||||
|
if name.to_string().ends_with("___bak") {
|
||||||
|
exported_names_correct = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate parent/child "tree"
|
||||||
|
if !root.is_empty() {
|
||||||
|
let root = root.single();
|
||||||
|
let mut tree: HashMap<String, Vec<String>> = HashMap::new();
|
||||||
|
|
||||||
|
for child in children.iter_descendants(root.0) {
|
||||||
|
let child_name: String = names
|
||||||
|
.get(child)
|
||||||
|
.map_or(String::from("no_name"), |e| e.to_string()); //|e| e.to_string(), || "no_name".to_string());
|
||||||
|
//println!(" child {}", child_name);
|
||||||
|
let parent = parents.get(child).unwrap();
|
||||||
|
let parent_name: String = names
|
||||||
|
.get(parent.get())
|
||||||
|
.map_or(String::from("no_name"), |e| e.to_string()); //|e| e.to_string(), || "no_name".to_string());
|
||||||
|
tree.entry(parent_name)
|
||||||
|
.or_default()
|
||||||
|
.push(child_name.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let hierarchy = to_json_string(&tree);
|
||||||
|
fs::write("bevy_hierarchy.json", hierarchy).expect("unable to write hierarchy file")
|
||||||
|
}
|
||||||
|
|
||||||
fs::write(
|
fs::write(
|
||||||
"bevy_diagnostics.json",
|
"bevy_diagnostics.json",
|
||||||
format!(
|
format!(
|
||||||
"{{ \"animations\": {}, \"cylinder_found\": {} , \"nested_blueprint_found\": {}, \"empty_found\": {}, \"blueprints_list_found\": {} }}",
|
"{{ \"animations\": {}, \"nested_blueprint_found\": {}, \"empty_found\": {}, \"blueprints_list_found\": {}, \"exported_names_correct\": {} }}",
|
||||||
animations_found, cylinder_found, nested_blueprint_found, empty_found, blueprints_list_found
|
animations_found, nested_blueprint_found, empty_found, blueprints_list_found, exported_names_correct
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.expect("Unable to write file");
|
.expect("Unable to write file");
|
||||||
@ -100,15 +127,28 @@ fn exit_game(mut app_exit_events: ResMut<Events<bevy::app::AppExit>>) {
|
|||||||
pub struct GamePlugin;
|
pub struct GamePlugin;
|
||||||
impl Plugin for GamePlugin {
|
impl Plugin for GamePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(Update, (spawn_test).run_if(in_state(GameState::InGame)))
|
app.register_type::<Marker1>()
|
||||||
|
.register_type::<Marker2>()
|
||||||
|
.register_type::<Marker3>()
|
||||||
|
|
||||||
|
.add_systems(Update, (spawn_test).run_if(in_state(GameState::InGame)))
|
||||||
.add_systems(Update, validate_export)
|
.add_systems(Update, validate_export)
|
||||||
.add_systems(OnEnter(AppState::MenuRunning), start_game)
|
.add_systems(OnEnter(AppState::MenuRunning), start_game)
|
||||||
.add_systems(OnEnter(AppState::AppRunning), setup_game)
|
.add_systems(OnEnter(AppState::AppRunning), setup_game)
|
||||||
.add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once
|
|
||||||
|
.add_systems(OnEnter(AppState::MenuRunning), setup_main_scene_animations)
|
||||||
|
.add_systems(Update, (animations, trigger_event_based_on_animation_marker)
|
||||||
|
.run_if(in_state(AppState::AppRunning))
|
||||||
|
.after(GltfBlueprintsSet::AfterSpawn)
|
||||||
|
)
|
||||||
|
.add_systems(Update, play_animations)
|
||||||
|
.add_systems(Update, react_to_animation_markers)
|
||||||
|
|
||||||
|
/* .add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
exit_game.run_if(on_timer(Duration::from_secs_f32(0.5))),
|
exit_game.run_if(on_timer(Duration::from_secs_f32(0.5))),
|
||||||
) // shut down the app after this time
|
) // shut down the app after this time*/
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
|
from .get_standard_exporter_settings import get_standard_exporter_settings
|
||||||
from .preferences import (AutoExportGltfPreferenceNames)
|
from .preferences import (AutoExportGltfPreferenceNames)
|
||||||
|
|
||||||
def generate_gltf_export_preferences(addon_prefs):
|
def generate_gltf_export_preferences(addon_prefs):
|
||||||
@ -37,10 +39,31 @@ def generate_gltf_export_preferences(addon_prefs):
|
|||||||
export_optimize_animation_size=False
|
export_optimize_animation_size=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for key in addon_prefs.__annotations__.keys():
|
for key in addon_prefs.__annotations__.keys():
|
||||||
if str(key) not in AutoExportGltfPreferenceNames:
|
if str(key) not in AutoExportGltfPreferenceNames:
|
||||||
#print("overriding setting", key, "value", getattr(addon_prefs,key))
|
#print("overriding setting", key, "value", getattr(addon_prefs,key))
|
||||||
gltf_export_preferences[key] = getattr(addon_prefs,key)
|
gltf_export_preferences[key] = getattr(addon_prefs, key)
|
||||||
|
|
||||||
|
|
||||||
|
"""standard_gltf_exporter_settings = get_standard_exporter_settings()
|
||||||
|
print("standard settings", standard_gltf_exporter_settings)
|
||||||
|
|
||||||
|
constant_keys = [
|
||||||
|
'export_cameras',
|
||||||
|
'export_extras', # For custom exported properties.
|
||||||
|
'export_lights',
|
||||||
|
]
|
||||||
|
|
||||||
|
# a certain number of essential params should NEVER be overwritten , no matter the settings of the standard exporter
|
||||||
|
for key in standard_gltf_exporter_settings.keys():
|
||||||
|
if str(key) not in constant_keys:
|
||||||
|
gltf_export_preferences[key] = standard_gltf_exporter_settings.get(key)
|
||||||
|
|
||||||
|
print("final export preferences", gltf_export_preferences)"""
|
||||||
|
|
||||||
|
|
||||||
return gltf_export_preferences
|
return gltf_export_preferences
|
||||||
|
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
def get_standard_exporter_settings():
|
||||||
|
settings_key = 'glTF2ExportSettings'
|
||||||
|
for scene in bpy.data.scenes:
|
||||||
|
if settings_key in scene:
|
||||||
|
settings = scene[settings_key]
|
||||||
|
#print("standard exporter settings", settings, dict(settings))
|
||||||
|
return dict(settings)
|
@ -20,49 +20,77 @@ def remove_unwanted_custom_properties(object):
|
|||||||
for component_name in object.keys():
|
for component_name in object.keys():
|
||||||
if not is_component_valid(object, component_name):
|
if not is_component_valid(object, component_name):
|
||||||
to_remove.append(component_name)
|
to_remove.append(component_name)
|
||||||
|
|
||||||
for cp in custom_properties_to_filter_out + to_remove:
|
for cp in custom_properties_to_filter_out + to_remove:
|
||||||
if cp in object:
|
if cp in object:
|
||||||
del object[cp]
|
del object[cp]
|
||||||
|
|
||||||
def duplicate_object(object):
|
# TODO: rename actions ?
|
||||||
obj_copy = object.copy()
|
# reference https://github.com/KhronosGroup/glTF-Blender-IO/blob/main/addons/io_scene_gltf2/blender/exp/animation/gltf2_blender_gather_action.py#L481
|
||||||
if object.data:
|
def copy_animation_data(source, target):
|
||||||
data = object.data.copy()
|
if source.animation_data and source.animation_data:
|
||||||
obj_copy.data = data
|
ad = source.animation_data
|
||||||
if object.animation_data and object.animation_data.action:
|
|
||||||
obj_copy.animation_data.action = object.animation_data.action.copy()
|
|
||||||
return obj_copy
|
|
||||||
|
|
||||||
#also removes unwanted custom_properties for all objects in hiearchy
|
blender_actions = []
|
||||||
def duplicate_object_recursive(object, parent, collection):
|
blender_tracks = {}
|
||||||
original_name = object.name
|
|
||||||
object.name = original_name + "____bak"
|
|
||||||
copy = duplicate_object(object)
|
|
||||||
copy.name = original_name
|
|
||||||
collection.objects.link(copy)
|
|
||||||
|
|
||||||
remove_unwanted_custom_properties(copy)
|
# TODO: this might need to be modified/ adapted to match the standard gltf exporter settings
|
||||||
|
for track in ad.nla_tracks:
|
||||||
|
non_muted_strips = [strip for strip in track.strips if strip.action is not None and strip.mute is False]
|
||||||
|
for strip in non_muted_strips: #t.strips:
|
||||||
|
# print(" ", source.name,'uses',strip.action.name, "active", strip.active, "action", strip.action)
|
||||||
|
blender_actions.append(strip.action)
|
||||||
|
blender_tracks[strip.action.name] = track.name
|
||||||
|
|
||||||
if parent:
|
# Remove duplicate actions.
|
||||||
copy.parent = parent
|
blender_actions = list(set(blender_actions))
|
||||||
|
# sort animations alphabetically (case insensitive) so they have a defined order and match Blender's Action list
|
||||||
|
blender_actions.sort(key = lambda a: a.name.lower())
|
||||||
|
|
||||||
for child in object.children:
|
markers_per_animation = {}
|
||||||
duplicate_object_recursive(child, copy, collection)
|
animations_infos = []
|
||||||
return copy
|
|
||||||
|
for action in blender_actions:
|
||||||
|
animation_name = blender_tracks[action.name]
|
||||||
|
animations_infos.append(
|
||||||
|
f'(name: "{animation_name}", frame_start: {action.frame_range[0]}, frame_end: {action.frame_range[1]}, frames_length: {action.frame_range[1] - action.frame_range[0]}, frame_start_override: {action.frame_start}, frame_end_override: {action.frame_end})'
|
||||||
|
)
|
||||||
|
markers_per_animation[animation_name] = {}
|
||||||
|
|
||||||
|
for marker in action.pose_markers:
|
||||||
|
if marker.frame not in markers_per_animation[animation_name]:
|
||||||
|
markers_per_animation[animation_name][marker.frame] = []
|
||||||
|
markers_per_animation[animation_name][marker.frame].append(marker.name)
|
||||||
|
|
||||||
|
"""if target.animation_data == None:
|
||||||
|
target.animation_data_create()
|
||||||
|
target.animation_data.action = source.animation_data.action.copy()"""
|
||||||
|
# alternative method, using the built-in link animation operator
|
||||||
|
with bpy.context.temp_override(active_object=source, selected_editable_objects=[target]):
|
||||||
|
bpy.ops.object.make_links_data(type='ANIMATION')
|
||||||
|
# we add an "AnimationInfos" component
|
||||||
|
target['AnimationInfos'] = f'(animations: {animations_infos})'.replace("'","")
|
||||||
|
|
||||||
|
markers_formated = '{'
|
||||||
|
for animation in markers_per_animation.keys():
|
||||||
|
markers_formated += f'"{animation}":'
|
||||||
|
markers_formated += "{"
|
||||||
|
for frame in markers_per_animation[animation].keys():
|
||||||
|
markers = markers_per_animation[animation][frame]
|
||||||
|
markers_formated += f"{frame}:{markers}, ".replace("'", '"')
|
||||||
|
markers_formated += '}, '
|
||||||
|
markers_formated += '}'
|
||||||
|
target["AnimationMarkers"] = f'( {markers_formated} )'
|
||||||
|
|
||||||
|
"""print("copying animation data for", source.name, target.animation_data)
|
||||||
|
properties = [p.identifier for p in source.animation_data.bl_rna.properties if not p.is_readonly]
|
||||||
|
for prop in properties:
|
||||||
|
print("copying stuff", prop)
|
||||||
|
setattr(target.animation_data, prop, getattr(source.animation_data, prop))"""
|
||||||
|
|
||||||
|
|
||||||
# copies the contents of a collection into another one while replacing library instances with empties
|
|
||||||
def copy_hollowed_collection_into(source_collection, destination_collection, parent_empty=None, filter=None, library_collections=[], addon_prefs={}):
|
|
||||||
collection_instances_combine_mode = getattr(addon_prefs, "collection_instances_combine_mode")
|
|
||||||
legacy_mode = getattr(addon_prefs, "export_legacy_mode")
|
|
||||||
collection_instances_combine_mode= collection_instances_combine_mode
|
|
||||||
for object in source_collection.objects:
|
|
||||||
if filter is not None and filter(object) is False:
|
|
||||||
continue
|
|
||||||
#check if a specific collection instance does not have an ovveride for combine_mode
|
|
||||||
combine_mode = object['_combine'] if '_combine' in object else collection_instances_combine_mode
|
|
||||||
|
|
||||||
|
def duplicate_object(object, parent, combine_mode, destination_collection, library_collections, legacy_mode, nester=""):
|
||||||
|
copy = None
|
||||||
if object.instance_type == 'COLLECTION' and (combine_mode == 'Split' or (combine_mode == 'EmbedExternal' and (object.instance_collection.name in library_collections)) ):
|
if object.instance_type == 'COLLECTION' and (combine_mode == 'Split' or (combine_mode == 'EmbedExternal' and (object.instance_collection.name in library_collections)) ):
|
||||||
#print("creating empty for", object.name, object.instance_collection.name, library_collections, combine_mode)
|
#print("creating empty for", object.name, object.instance_collection.name, library_collections, combine_mode)
|
||||||
collection_name = object.instance_collection.name
|
collection_name = object.instance_collection.name
|
||||||
@ -79,28 +107,62 @@ def copy_hollowed_collection_into(source_collection, destination_collection, par
|
|||||||
root_node = CollectionNode()
|
root_node = CollectionNode()
|
||||||
root_node.name = "root"
|
root_node.name = "root"
|
||||||
children_per_collection = {}
|
children_per_collection = {}
|
||||||
print("collection stuff", original_name)
|
|
||||||
get_sub_collections([object.instance_collection], root_node, children_per_collection)
|
get_sub_collections([object.instance_collection], root_node, children_per_collection)
|
||||||
empty_obj["BlueprintsList"] = f"({json.dumps(dict(children_per_collection))})"
|
empty_obj["BlueprintsList"] = f"({json.dumps(dict(children_per_collection))})"
|
||||||
#empty_obj["Assets"] = {"Animations": [], "Materials": [], "Models":[], "Textures":[], "Audio":[], "Other":[]}
|
|
||||||
|
|
||||||
|
# empty_obj["AnimationMarkers"] = '({"animation_name": {5: "Marker_1"} })'
|
||||||
|
|
||||||
|
#'({5: "sdf"})'#.replace('"',"'") #f"({json.dumps(dict(animation_foo))})"
|
||||||
|
#empty_obj["Assets"] = {"Animations": [], "Materials": [], "Models":[], "Textures":[], "Audio":[], "Other":[]}
|
||||||
|
|
||||||
# we copy custom properties over from our original object to our empty
|
# we copy custom properties over from our original object to our empty
|
||||||
for component_name, component_value in object.items():
|
for component_name, component_value in object.items():
|
||||||
if component_name not in custom_properties_to_filter_out and is_component_valid(object, component_name): #copy only valid properties
|
if component_name not in custom_properties_to_filter_out and is_component_valid(object, component_name): #copy only valid properties
|
||||||
empty_obj[component_name] = component_value
|
empty_obj[component_name] = component_value
|
||||||
if parent_empty is not None:
|
copy = empty_obj
|
||||||
empty_obj.parent = parent_empty
|
|
||||||
else:
|
else:
|
||||||
|
# for objects which are NOT collection instances
|
||||||
# we create a copy of our object and its children, to leave the original one as it is
|
# we create a copy of our object and its children, to leave the original one as it is
|
||||||
if object.parent == None:
|
original_name = object.name
|
||||||
copy = duplicate_object_recursive(object, None, destination_collection)
|
object.name = original_name + "____bak"
|
||||||
|
copy = object.copy()
|
||||||
|
copy.name = original_name
|
||||||
|
|
||||||
|
|
||||||
|
destination_collection.objects.link(copy)
|
||||||
|
|
||||||
|
"""if object.parent == None:
|
||||||
if parent_empty is not None:
|
if parent_empty is not None:
|
||||||
copy.parent = parent_empty
|
copy.parent = parent_empty
|
||||||
|
"""
|
||||||
|
|
||||||
# for every sub-collection of the source, copy its content into a new sub-collection of the destination
|
# print(nester, "copy", copy)
|
||||||
|
# do this both for empty replacements & normal copies
|
||||||
|
if parent is not None:
|
||||||
|
copy.parent = parent
|
||||||
|
remove_unwanted_custom_properties(copy)
|
||||||
|
copy_animation_data(object, copy)
|
||||||
|
|
||||||
|
for child in object.children:
|
||||||
|
duplicate_object(child, copy, combine_mode, destination_collection, library_collections, legacy_mode, nester+" ")
|
||||||
|
|
||||||
|
# copies the contents of a collection into another one while replacing library instances with empties
|
||||||
|
def copy_hollowed_collection_into(source_collection, destination_collection, parent_empty=None, filter=None, library_collections=[], addon_prefs={}):
|
||||||
|
collection_instances_combine_mode = getattr(addon_prefs, "collection_instances_combine_mode")
|
||||||
|
legacy_mode = getattr(addon_prefs, "export_legacy_mode")
|
||||||
|
collection_instances_combine_mode= collection_instances_combine_mode
|
||||||
|
|
||||||
|
for object in source_collection.objects:
|
||||||
|
if object.name.endswith("____bak"): # some objects could already have been handled, ignore them
|
||||||
|
continue
|
||||||
|
if filter is not None and filter(object) is False:
|
||||||
|
continue
|
||||||
|
#check if a specific collection instance does not have an ovveride for combine_mode
|
||||||
|
combine_mode = object['_combine'] if '_combine' in object else collection_instances_combine_mode
|
||||||
|
parent = parent_empty
|
||||||
|
duplicate_object(object, parent, combine_mode, destination_collection, library_collections, legacy_mode)
|
||||||
|
|
||||||
|
# for every child-collection of the source, copy its content into a new sub-collection of the destination
|
||||||
for collection in source_collection.children:
|
for collection in source_collection.children:
|
||||||
original_name = collection.name
|
original_name = collection.name
|
||||||
collection.name = original_name + "____bak"
|
collection.name = original_name + "____bak"
|
||||||
@ -108,7 +170,6 @@ def copy_hollowed_collection_into(source_collection, destination_collection, par
|
|||||||
|
|
||||||
if parent_empty is not None:
|
if parent_empty is not None:
|
||||||
collection_placeholder.parent = parent_empty
|
collection_placeholder.parent = parent_empty
|
||||||
|
|
||||||
copy_hollowed_collection_into(
|
copy_hollowed_collection_into(
|
||||||
source_collection = collection,
|
source_collection = collection,
|
||||||
destination_collection = destination_collection,
|
destination_collection = destination_collection,
|
||||||
@ -118,6 +179,8 @@ def copy_hollowed_collection_into(source_collection, destination_collection, par
|
|||||||
addon_prefs=addon_prefs
|
addon_prefs=addon_prefs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# clear & remove "hollow scene"
|
# clear & remove "hollow scene"
|
||||||
@ -138,14 +201,14 @@ def clear_hollow_scene(temp_scene, original_root_collection):
|
|||||||
# reset original names
|
# reset original names
|
||||||
restore_original_names(original_root_collection)
|
restore_original_names(original_root_collection)
|
||||||
|
|
||||||
# remove empties (only needed when we go via ops ????)
|
# remove any data we created
|
||||||
temp_root_collection = temp_scene.collection
|
temp_root_collection = temp_scene.collection
|
||||||
temp_scene_objects = [o for o in temp_root_collection.objects]
|
temp_scene_objects = [o for o in temp_root_collection.all_objects]
|
||||||
for object in temp_scene_objects:
|
for object in temp_scene_objects:
|
||||||
|
print("removing", object.name)
|
||||||
bpy.data.objects.remove(object, do_unlink=True)
|
bpy.data.objects.remove(object, do_unlink=True)
|
||||||
# remove the temporary scene
|
# remove the temporary scene
|
||||||
bpy.data.scenes.remove(temp_scene)
|
bpy.data.scenes.remove(temp_scene, do_unlink=True)
|
||||||
|
|
||||||
|
|
||||||
# convenience utility to get lists of scenes
|
# convenience utility to get lists of scenes
|
||||||
def get_scenes(addon_prefs):
|
def get_scenes(addon_prefs):
|
||||||
@ -160,9 +223,6 @@ def get_scenes(addon_prefs):
|
|||||||
|
|
||||||
return [level_scene_names, level_scenes, library_scene_names, library_scenes]
|
return [level_scene_names, level_scenes, library_scene_names, library_scenes]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def inject_blueprints_list_into_main_scene(scene):
|
def inject_blueprints_list_into_main_scene(scene):
|
||||||
print("injecting assets/blueprints data into scene")
|
print("injecting assets/blueprints data into scene")
|
||||||
root_collection = scene.collection
|
root_collection = scene.collection
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
{"b_Tail02_013":["b_Tail03_014"],"Blueprint4_nested.001":["Blueprint3"],"Collection 2 1":["Empty_in_sub_collection"],"b_Root_00":["b_Hip_01"],"b_LeftForeArm_010":["b_LeftHand_011"],"b_Spine01_02":["b_Spine02_03"],"Blueprint7_hierarchy.001":["Blueprint4_nested.001","Cube.001"],"b_RightLeg01_019":["b_RightLeg02_020"],"b_LeftUpperArm_09":["b_LeftForeArm_010"],"no_name":["Parent_Object","lighting_components_World","assets_list_World_components","Collection","Collection 2"],"Blueprint3":["Blueprint3_mesh","Blueprint3_mesh"],"world":["no_name"],"Parent_Object":["Cube.003","Blueprint1","Cylinder.001"],"Light":["Light","DirectionalLight Gizmo"],"Blueprint1.001":["Blueprint1_mesh"],"Blueprint7_hierarchy":["Cube.001"],"Spot":["Spot"],"b_Hip_01":["b_Spine01_02","b_Tail01_012","b_LeftLeg01_015","b_RightLeg01_019"],"Cylinder":["Cylinder.001","Cylinder.001"],"Collection 2":["Collection 2 1","Empty_in_collection","Spot"],"b_RightForeArm_07":["b_RightHand_08"],"Blueprint3_mesh":["Cylinder","Cylinder"],"Blueprint4_nested":["Blueprint3"],"Fox_mesh":["fox1"],"b_LeftLeg01_015":["b_LeftLeg02_016"],"b_Neck_04":["b_Head_05"],"b_RightFoot01_021":["b_RightFoot02_022"],"Blueprint1_mesh":["Cube.001","Cube.001"],"b_Tail01_012":["b_Tail02_013"],"Fox":["Fox_mesh","_rootJoint"],"Collection":["Blueprint1.001","Blueprint4_nested","Blueprint6_animated","Blueprint7_hierarchy","Camera","Cube","Empty","Light","Plane"],"Cube":["Cube"],"_rootJoint":["b_Root_00"],"b_RightLeg02_020":["b_RightFoot01_021"],"b_RightUpperArm_06":["b_RightForeArm_07"],"Plane":["Plane"],"Camera":["Camera Gizmo"],"Blueprint6_animated":["Fox"],"b_Spine02_03":["b_Neck_04","b_RightUpperArm_06","b_LeftUpperArm_09"],"b_LeftLeg02_016":["b_LeftFoot01_017"],"b_LeftFoot01_017":["b_LeftFoot02_018"],"Cube.001":["Cube.002","Cylinder","Cube.002","Cylinder"],"Cylinder.001":["Cylinder.002","Blueprint7_hierarchy.001","Empty_as_child"],"Blueprint1":["Blueprint1_mesh"]}
|
@ -19,7 +19,6 @@ def setup_data(request):
|
|||||||
|
|
||||||
def finalizer():
|
def finalizer():
|
||||||
print("\nPerforming teardown...")
|
print("\nPerforming teardown...")
|
||||||
get_orphan_data()
|
|
||||||
|
|
||||||
if os.path.exists(models_path):
|
if os.path.exists(models_path):
|
||||||
shutil.rmtree(models_path)
|
shutil.rmtree(models_path)
|
||||||
@ -38,7 +37,10 @@ def setup_data(request):
|
|||||||
|
|
||||||
def get_orphan_data():
|
def get_orphan_data():
|
||||||
orphan_meshes = [m.name for m in bpy.data.meshes if m.users == 0]
|
orphan_meshes = [m.name for m in bpy.data.meshes if m.users == 0]
|
||||||
# print("orphan meshes before", orphan_meshes)
|
orphan_objects = [m.name for m in bpy.data.objects if m.users == 0]
|
||||||
|
|
||||||
|
#print("orphan meshes before", orphan_meshes)
|
||||||
|
return orphan_meshes + orphan_objects
|
||||||
|
|
||||||
def test_export_do_not_export_blueprints(setup_data):
|
def test_export_do_not_export_blueprints(setup_data):
|
||||||
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
||||||
@ -61,6 +63,9 @@ def test_export_do_not_export_blueprints(setup_data):
|
|||||||
)
|
)
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == False
|
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == False
|
||||||
|
orphan_data = get_orphan_data()
|
||||||
|
assert len(orphan_data) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_export_custom_blueprints_path(setup_data):
|
def test_export_custom_blueprints_path(setup_data):
|
||||||
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
||||||
@ -83,6 +88,7 @@ def test_export_custom_blueprints_path(setup_data):
|
|||||||
)
|
)
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "another_library_path", "Blueprint1.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["models_path"], "another_library_path", "Blueprint1.glb")) == True
|
||||||
|
assert len(get_orphan_data()) == 0
|
||||||
|
|
||||||
def test_export_materials_library(setup_data):
|
def test_export_materials_library(setup_data):
|
||||||
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
||||||
@ -107,7 +113,7 @@ def test_export_materials_library(setup_data):
|
|||||||
|
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["materials_path"], "testing_materials_library.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["materials_path"], "testing_materials_library.glb")) == True
|
||||||
|
assert len(get_orphan_data()) == 0
|
||||||
|
|
||||||
def test_export_materials_library_custom_path(setup_data):
|
def test_export_materials_library_custom_path(setup_data):
|
||||||
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
||||||
@ -134,6 +140,7 @@ def test_export_materials_library_custom_path(setup_data):
|
|||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["materials_path"], "testing_materials_library.glb")) == False
|
assert os.path.exists(os.path.join(setup_data["materials_path"], "testing_materials_library.glb")) == False
|
||||||
assert os.path.exists(os.path.join(setup_data["other_materials_path"], "testing_materials_library.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["other_materials_path"], "testing_materials_library.glb")) == True
|
||||||
|
assert len(get_orphan_data()) == 0
|
||||||
|
|
||||||
def test_export_collection_instances_combine_mode(setup_data): # TODO: change & check this
|
def test_export_collection_instances_combine_mode(setup_data): # TODO: change & check this
|
||||||
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
||||||
@ -160,6 +167,7 @@ def test_export_collection_instances_combine_mode(setup_data): # TODO: change &
|
|||||||
|
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "World_dynamic.glb")) == False
|
assert os.path.exists(os.path.join(setup_data["models_path"], "World_dynamic.glb")) == False
|
||||||
|
assert len(get_orphan_data()) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_export_do_not_export_marked_assets(setup_data):
|
def test_export_do_not_export_marked_assets(setup_data):
|
||||||
@ -188,6 +196,7 @@ def test_export_do_not_export_marked_assets(setup_data):
|
|||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint3.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint3.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint4_nested.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint4_nested.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint5.glb")) == False
|
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint5.glb")) == False
|
||||||
|
assert len(get_orphan_data()) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_export_separate_dynamic_and_static_objects(setup_data):
|
def test_export_separate_dynamic_and_static_objects(setup_data):
|
||||||
@ -216,6 +225,7 @@ def test_export_separate_dynamic_and_static_objects(setup_data):
|
|||||||
|
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "World_dynamic.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["models_path"], "World_dynamic.glb")) == True
|
||||||
|
assert len(get_orphan_data()) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_export_should_not_generate_orphan_data(setup_data):
|
def test_export_should_not_generate_orphan_data(setup_data):
|
||||||
@ -239,4 +249,5 @@ def test_export_should_not_generate_orphan_data(setup_data):
|
|||||||
)
|
)
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True
|
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == False
|
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == False
|
||||||
|
assert len(get_orphan_data()) == 0
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import json
|
|||||||
import pytest
|
import pytest
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
import filecmp
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from pixelmatch.contrib.PIL import pixelmatch
|
from pixelmatch.contrib.PIL import pixelmatch
|
||||||
|
|
||||||
@ -30,6 +31,10 @@ def setup_data(request):
|
|||||||
if os.path.exists(diagnostics_file_path):
|
if os.path.exists(diagnostics_file_path):
|
||||||
os.remove(diagnostics_file_path)
|
os.remove(diagnostics_file_path)
|
||||||
|
|
||||||
|
hierarchy_file_path = os.path.join(root_path, "bevy_hierarchy.json")
|
||||||
|
if os.path.exists(hierarchy_file_path):
|
||||||
|
os.remove(hierarchy_file_path)
|
||||||
|
|
||||||
screenshot_observed_path = os.path.join(root_path, "screenshot.png")
|
screenshot_observed_path = os.path.join(root_path, "screenshot.png")
|
||||||
if os.path.exists(screenshot_observed_path):
|
if os.path.exists(screenshot_observed_path):
|
||||||
os.remove(screenshot_observed_path)
|
os.remove(screenshot_observed_path)
|
||||||
@ -56,7 +61,8 @@ def test_export_complex(setup_data):
|
|||||||
# we use the global settings for that
|
# we use the global settings for that
|
||||||
export_props = {
|
export_props = {
|
||||||
"main_scene_names" : ['World'],
|
"main_scene_names" : ['World'],
|
||||||
"library_scene_names": ['Library']
|
"library_scene_names": ['Library'],
|
||||||
|
# "export_format":'GLTF_SEPARATE'
|
||||||
}
|
}
|
||||||
stored_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_settings")
|
stored_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_settings")
|
||||||
stored_settings.clear()
|
stored_settings.clear()
|
||||||
@ -107,9 +113,15 @@ def test_export_complex(setup_data):
|
|||||||
diagnostics = json.load(diagnostics_file)
|
diagnostics = json.load(diagnostics_file)
|
||||||
print("diagnostics", diagnostics)
|
print("diagnostics", diagnostics)
|
||||||
assert diagnostics["animations"] == True
|
assert diagnostics["animations"] == True
|
||||||
assert diagnostics["cylinder_found"] == True
|
|
||||||
assert diagnostics["empty_found"] == True
|
assert diagnostics["empty_found"] == True
|
||||||
assert diagnostics["blueprints_list_found"] == True
|
assert diagnostics["blueprints_list_found"] == True
|
||||||
|
assert diagnostics["exported_names_correct"] == True
|
||||||
|
|
||||||
|
with open(os.path.join(root_path, "bevy_hierarchy.json")) as hierarchy_file:
|
||||||
|
with open(os.path.join(os.path.dirname(__file__), "expected_bevy_hierarchy.json")) as expexted_hierarchy_file:
|
||||||
|
hierarchy = json.load(hierarchy_file)
|
||||||
|
expected = json.load(expexted_hierarchy_file)
|
||||||
|
assert sorted(hierarchy.items()) == sorted(expected.items())
|
||||||
|
|
||||||
# last but not least, do a visual compare
|
# last but not least, do a visual compare
|
||||||
screenshot_expected_path = os.path.join(root_path, "expected_screenshot.png")
|
screenshot_expected_path = os.path.join(root_path, "expected_screenshot.png")
|
||||||
|
Loading…
Reference in New Issue
Block a user