feat(): Asset preloading basics (#169)

* closes #80 
* preliminary work for further asset management from the Blender side without needing additional dependencies & boilerplate on the Bevy side 
* feat(auto_export): 
     * now injecting list/tree of sub blueprints to main scenes/levels & blueprints
     * made blueprints/asset list conditional on NOT legacy mode
* feat(gltf_blueprints): 
    * basics of dynamic loading of sub_blueprints
    * added same kind of logic to the loading of material files
    * also made a more generic variant of the BlueprintAssetLoadTracker 
    * added gltf file path for more informative error in case a gltf is missing
* refactor(): 
   * made the various gltf assets loaded by bevy_asset_loader optional (to handle the case where they are not present)
   * updated all examples accordingly
* chore(crates): fixed links, slight tweaks, bumped versions
This commit is contained in:
Mark Moissette 2024-03-18 18:00:19 +01:00 committed by GitHub
parent 1353e14802
commit 9f21df035b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 519 additions and 127 deletions

View File

@ -1,8 +1,8 @@
[package] [package]
name = "bevy_gltf_blueprints" name = "bevy_gltf_blueprints"
version = "0.9.0" version = "0.10.0"
authors = ["Mark 'kaosat-dev' Moissette"] authors = ["Mark 'kaosat-dev' Moissette"]
description = "Adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy." description = "Adds the ability to define Blueprints/Prefabs for Bevy inside gltf files and spawn them in Bevy."
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
keywords = ["gamedev", "bevy", "gltf", "blueprint", "prefab"] keywords = ["gamedev", "bevy", "gltf", "blueprint", "prefab"]

View File

@ -5,7 +5,7 @@
# bevy_gltf_blueprints # bevy_gltf_blueprints
Built upon [bevy_gltf_components](https://crates.io/crates/bevy_gltf_components) this crate adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy. Built on [bevy_gltf_components](https://crates.io/crates/bevy_gltf_components) this crate adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy.
* Allows you to create lightweight levels, where all assets are different gltf files and loaded after the main level is loaded * Allows you to create lightweight levels, where all assets are different gltf files and loaded after the main level is loaded
* Allows you to spawn different entities from gtlf files at runtime in a clean manner, including simplified animation support ! * Allows you to spawn different entities from gtlf files at runtime in a clean manner, including simplified animation support !
@ -15,7 +15,9 @@ A blueprint is a set of **overrideable** components + a hierarchy: ie
* just a Gltf file with Gltf_extras specifying components * just a Gltf file with Gltf_extras specifying components
* a component called BlueprintName * a component called BlueprintName
Particularly useful when using [Blender](https://www.blender.org/) as an editor for the [Bevy](https://bevyengine.org/) game engine, combined with the [Blender plugin](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/gltf_auto_export) that does a lot of the work for you Particularly useful when using [Blender](https://www.blender.org/) as an editor for the [Bevy](https://bevyengine.org/) game engine, combined with the Blender add-ons that do a lot of the work for you
- [gltf_auto_export](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/gltf_auto_export)
- [bevy_components](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/bevy_components)
## Usage ## Usage
@ -26,7 +28,7 @@ Here's a minimal usage example:
# Cargo.toml # Cargo.toml
[dependencies] [dependencies]
bevy="0.13" bevy="0.13"
bevy_gltf_blueprints = { version = "0.9"} bevy_gltf_blueprints = { version = "0.10"}
``` ```
@ -64,7 +66,7 @@ fn spawn_blueprint(
Add the following to your `[dependencies]` section in `Cargo.toml`: Add the following to your `[dependencies]` section in `Cargo.toml`:
```toml ```toml
bevy_gltf_blueprints = "0.9" bevy_gltf_blueprints = "0.10"
``` ```
Or use `cargo add`: Or use `cargo add`:
@ -165,13 +167,10 @@ commands.spawn((
### BluePrintBundle ### BluePrintBundle
There is also a bundle for convenience , which just has There is also a ```BluePrintBundle``` for convenience , which just has
* a ```BlueprintName``` component * a ```BlueprintName``` component
* a ```SpawnHere``` component * a ```SpawnHere``` component
[```BluePrintBundle```](./src/lib.rs#22)
## Additional information ## Additional information
- When a blueprint is spawned, all its children entities (and nested children etc) also have an ```InBlueprint``` component that gets insert - When a blueprint is spawned, all its children entities (and nested children etc) also have an ```InBlueprint``` component that gets insert
@ -219,7 +218,7 @@ the ordering of systems is very important !
For example to replace your proxy components (stand-in components when you cannot/ do not want to use real components in the gltf file) with actual ones, which should happen **AFTER** the Blueprint based spawning, For example to replace your proxy components (stand-in components when you cannot/ do not want to use real components in the gltf file) with actual ones, which should happen **AFTER** the Blueprint based spawning,
so ```bevy_gltf_blueprints``` provides a **SystemSet** for that purpose:[```GltfBlueprintsSet```](./src/lib.rs#16) so ```bevy_gltf_blueprints``` provides a **SystemSet** for that purpose: ```GltfBlueprintsSet```
Typically , the order of systems should be Typically , the order of systems should be
@ -281,8 +280,7 @@ pub fn animation_change_on_proximity_foxes(
see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation for how to set it up correctly see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation for how to set it up correctly
particularly from https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation/game/in_game.rs#86 particularly from https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation/game/in_game.rs
onward
## Materials ## Materials
@ -346,7 +344,7 @@ The main branch is compatible with the latest Bevy release, while the branch `be
Compatibility of `bevy_gltf_blueprints` versions: Compatibility of `bevy_gltf_blueprints` versions:
| `bevy_gltf_blueprints` | `bevy` | | `bevy_gltf_blueprints` | `bevy` |
| :-- | :-- | | :-- | :-- |
| `0.9` | `0.13` | | `0.9 - 0.10` | `0.13` |
| `0.3 - 0.8` | `0.12` | | `0.3 - 0.8` | `0.12` |
| `0.1 - 0.2` | `0.11` | | `0.1 - 0.2` | `0.11` |
| branch `main` | `0.13` | | branch `main` | `0.13` |

View File

@ -120,6 +120,9 @@ impl Plugin for BlueprintsPlugin {
.register_type::<MaterialInfo>() .register_type::<MaterialInfo>()
.register_type::<SpawnHere>() .register_type::<SpawnHere>()
.register_type::<Animations>() .register_type::<Animations>()
.register_type::<BlueprintsList>()
.register_type::<Vec<String>>()
.register_type::<HashMap<String, Vec<String>>>()
.insert_resource(BluePrintsConfig { .insert_resource(BluePrintsConfig {
format: self.format, format: self.format,
library_folder: self.library_folder.clone(), library_folder: self.library_folder.clone(),
@ -140,11 +143,24 @@ impl Plugin for BlueprintsPlugin {
.add_systems( .add_systems(
Update, Update,
( (
(
prepare_blueprints,
check_for_loaded,
spawn_from_blueprints, spawn_from_blueprints,
compute_scene_aabbs.run_if(aabbs_enabled),
apply_deferred.run_if(aabbs_enabled),
apply_deferred, apply_deferred,
materials_inject.run_if(materials_library_enabled), )
.chain(),
(compute_scene_aabbs, apply_deferred)
.chain()
.run_if(aabbs_enabled),
apply_deferred,
(
materials_inject,
check_for_material_loaded,
materials_inject2,
)
.chain()
.run_if(materials_library_enabled),
) )
.chain() .chain()
.in_set(GltfBlueprintsSet::Spawn), .in_set(GltfBlueprintsSet::Spawn),

View File

@ -4,6 +4,7 @@ use bevy::{
asset::{AssetServer, Assets, Handle}, asset::{AssetServer, Assets, Handle},
ecs::{ ecs::{
component::Component, component::Component,
entity::Entity,
query::{Added, With}, query::{Added, With},
reflect::ReflectComponent, reflect::ReflectComponent,
system::{Commands, Query, Res, ResMut}, system::{Commands, Query, Res, ResMut},
@ -16,7 +17,7 @@ use bevy::{
render::mesh::Mesh, render::mesh::Mesh,
}; };
use crate::BluePrintsConfig; use crate::{AssetLoadTracker, AssetsToLoad, BluePrintsConfig};
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
@ -26,10 +27,111 @@ pub struct MaterialInfo {
pub source: String, pub source: String,
} }
/// flag component
#[derive(Component)]
pub(crate) struct BlueprintMaterialAssetsLoaded;
/// flag component
#[derive(Component)]
pub(crate) struct BlueprintMaterialAssetsNotLoaded;
/// system that injects / replaces materials from material library /// system that injects / replaces materials from material library
pub(crate) fn materials_inject( pub(crate) fn materials_inject(
blueprints_config: ResMut<BluePrintsConfig>,
material_infos: Query<(Entity, &MaterialInfo), Added<MaterialInfo>>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
for (entity, material_info) in material_infos.iter() {
let model_file_name = format!(
"{}_materials_library.{}",
&material_info.source, &blueprints_config.format
);
let materials_path = Path::new(&blueprints_config.material_library_folder)
.join(Path::new(model_file_name.as_str()));
let material_name = &material_info.name;
let material_full_path = materials_path.to_str().unwrap().to_string() + "#" + material_name; // TODO: yikes, cleanup
if blueprints_config
.material_library_cache
.contains_key(&material_full_path)
{
debug!("material is cached, retrieving");
blueprints_config
.material_library_cache
.get(&material_full_path)
.expect("we should have the material available");
commands
.entity(entity)
.insert(BlueprintMaterialAssetsLoaded);
} else {
let material_file_handle: Handle<Gltf> = asset_server.load(materials_path.clone());
let material_file_id = material_file_handle.id();
let asset_infos: Vec<AssetLoadTracker<Gltf>> = vec![AssetLoadTracker {
name: material_full_path,
id: material_file_id,
loaded: false,
handle: material_file_handle.clone(),
}];
commands
.entity(entity)
.insert(AssetsToLoad {
all_loaded: false,
asset_infos,
..Default::default()
})
.insert(BlueprintMaterialAssetsNotLoaded);
/**/
}
}
}
// TODO, merge with check_for_loaded, make generic ?
pub(crate) fn check_for_material_loaded(
mut blueprint_assets_to_load: Query<
(Entity, &mut AssetsToLoad<Gltf>),
With<BlueprintMaterialAssetsNotLoaded>,
>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
for (entity, mut assets_to_load) in blueprint_assets_to_load.iter_mut() {
let mut all_loaded = true;
let mut loaded_amount = 0;
let total = assets_to_load.asset_infos.len();
for tracker in assets_to_load.asset_infos.iter_mut() {
let asset_id = tracker.id;
let loaded = asset_server.is_loaded_with_dependencies(asset_id);
tracker.loaded = loaded;
if loaded {
loaded_amount += 1;
} else {
all_loaded = false;
}
}
let progress: f32 = loaded_amount as f32 / total as f32;
assets_to_load.progress = progress;
if all_loaded {
assets_to_load.all_loaded = true;
commands
.entity(entity)
.insert(BlueprintMaterialAssetsLoaded)
.remove::<BlueprintMaterialAssetsNotLoaded>();
}
}
}
/// system that injects / replaces materials from material library
pub(crate) fn materials_inject2(
mut blueprints_config: ResMut<BluePrintsConfig>, mut blueprints_config: ResMut<BluePrintsConfig>,
material_infos: Query<(&MaterialInfo, &Children), Added<MaterialInfo>>, material_infos: Query<
(&MaterialInfo, &Children),
(
Added<BlueprintMaterialAssetsLoaded>,
With<BlueprintMaterialAssetsLoaded>,
),
>,
with_materials_and_meshes: Query< with_materials_and_meshes: Query<
(), (),
( (
@ -38,9 +140,9 @@ pub(crate) fn materials_inject(
With<Handle<Mesh>>, With<Handle<Mesh>>,
), ),
>, >,
models: Res<Assets<bevy::gltf::Gltf>>, assets_gltf: Res<Assets<Gltf>>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (material_info, children) in material_infos.iter() { for (material_info, children) in material_infos.iter() {
@ -66,9 +168,9 @@ pub(crate) fn materials_inject(
.expect("we should have the material available"); .expect("we should have the material available");
material_found = Some(material); material_found = Some(material);
} else { } else {
let my_gltf: Handle<Gltf> = asset_server.load(materials_path.clone()); let model_handle: Handle<Gltf> = asset_server.load(materials_path.clone()); // FIXME: kinda weird now
let mat_gltf = models let mat_gltf = assets_gltf
.get(my_gltf.id()) .get(model_handle.id())
.expect("material should have been preloaded"); .expect("material should have been preloaded");
if mat_gltf.named_materials.contains_key(material_name) { if mat_gltf.named_materials.contains_key(material_name) {
let material = mat_gltf let material = mat_gltf

View File

@ -1,6 +1,6 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use bevy::{gltf::Gltf, prelude::*}; use bevy::{gltf::Gltf, prelude::*, utils::HashMap};
use crate::{Animations, BluePrintsConfig}; use crate::{Animations, BluePrintsConfig};
@ -46,8 +46,152 @@ pub struct AddToGameWorld;
/// helper component, just to transfer child data /// helper component, just to transfer child data
pub(crate) struct OriginalChildren(pub Vec<Entity>); pub(crate) struct OriginalChildren(pub Vec<Entity>);
/// main spawning functions, /// helper component, is used to store the list of sub blueprints to enable automatic loading of dependend blueprints
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct BlueprintsList(pub HashMap<String, Vec<String>>);
/// helper component, for tracking loaded assets's loading state, id , handle etc
#[derive(Default, Debug)]
pub(crate) struct AssetLoadTracker<T: bevy::prelude::Asset> {
#[allow(dead_code)]
pub name: String,
pub id: AssetId<T>,
pub loaded: bool,
#[allow(dead_code)]
pub handle: Handle<T>,
}
/// helper component, for tracking loaded assets
#[derive(Component, Debug)]
pub(crate) struct AssetsToLoad<T: bevy::prelude::Asset> {
pub all_loaded: bool,
pub asset_infos: Vec<AssetLoadTracker<T>>,
pub progress: f32,
}
impl<T: bevy::prelude::Asset> Default for AssetsToLoad<T> {
fn default() -> Self {
Self {
all_loaded: Default::default(),
asset_infos: Default::default(),
progress: Default::default(),
}
}
}
/// flag component, usually added when a blueprint is loaded
#[derive(Component)]
pub(crate) struct BlueprintAssetsLoaded;
/// flag component
#[derive(Component)]
pub(crate) struct BlueprintAssetsNotLoaded;
/// spawning prepare function,
/// * also takes into account the already exisiting "override" components, ie "override components" > components from blueprint /// * also takes into account the already exisiting "override" components, ie "override components" > components from blueprint
pub(crate) fn prepare_blueprints(
spawn_placeholders: Query<
(
Entity,
&BlueprintName,
Option<&Parent>,
Option<&Library>,
Option<&Name>,
Option<&BlueprintsList>,
),
(Added<BlueprintName>, Added<SpawnHere>, Without<Spawned>),
>,
mut commands: Commands,
asset_server: Res<AssetServer>,
blueprints_config: Res<BluePrintsConfig>,
) {
for (entity, blupeprint_name, original_parent, library_override, name, blueprints_list) in
spawn_placeholders.iter()
{
debug!(
"requesting to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}",
blupeprint_name.0, name, entity, original_parent
);
// println!("main model path {:?}", model_path);
if blueprints_list.is_some() {
let blueprints_list = blueprints_list.unwrap();
// println!("blueprints list {:?}", blueprints_list.0.keys());
let mut asset_infos: Vec<AssetLoadTracker<Gltf>> = vec![];
let library_path =
library_override.map_or_else(|| &blueprints_config.library_folder, |l| &l.0);
for (blueprint_name, _) in blueprints_list.0.iter() {
let model_file_name = format!("{}.{}", &blueprint_name, &blueprints_config.format);
let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str()));
let model_handle: Handle<Gltf> = asset_server.load(model_path.clone());
let model_id = model_handle.id();
let loaded = asset_server.is_loaded_with_dependencies(model_id);
if !loaded {
asset_infos.push(AssetLoadTracker {
name: model_path.to_string_lossy().into(),
id: model_id,
loaded: false,
handle: model_handle.clone(),
});
}
}
// if not all assets are already loaded, inject a component to signal that we need them to be loaded
if !asset_infos.is_empty() {
commands
.entity(entity)
.insert(AssetsToLoad {
all_loaded: false,
asset_infos,
..Default::default()
})
.insert(BlueprintAssetsNotLoaded);
} else {
commands.entity(entity).insert(BlueprintAssetsLoaded);
}
} else {
// in case there are no blueprintsList, we revert back to the old behaviour
commands.entity(entity).insert(BlueprintAssetsLoaded);
}
}
}
pub(crate) fn check_for_loaded(
mut blueprint_assets_to_load: Query<
(Entity, &mut AssetsToLoad<Gltf>),
With<BlueprintAssetsNotLoaded>,
>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
for (entity, mut assets_to_load) in blueprint_assets_to_load.iter_mut() {
let mut all_loaded = true;
let mut loaded_amount = 0;
let total = assets_to_load.asset_infos.len();
for tracker in assets_to_load.asset_infos.iter_mut() {
let asset_id = tracker.id;
let loaded = asset_server.is_loaded_with_dependencies(asset_id);
tracker.loaded = loaded;
if loaded {
loaded_amount += 1;
} else {
all_loaded = false;
}
}
let progress: f32 = loaded_amount as f32 / total as f32;
// println!("progress: {}",progress);
assets_to_load.progress = progress;
if all_loaded {
assets_to_load.all_loaded = true;
commands
.entity(entity)
.insert(BlueprintAssetsLoaded)
.remove::<BlueprintAssetsNotLoaded>();
}
}
}
pub(crate) fn spawn_from_blueprints( pub(crate) fn spawn_from_blueprints(
spawn_placeholders: Query< spawn_placeholders: Query<
( (
@ -59,7 +203,11 @@ pub(crate) fn spawn_from_blueprints(
Option<&AddToGameWorld>, Option<&AddToGameWorld>,
Option<&Name>, Option<&Name>,
), ),
(Added<BlueprintName>, Added<SpawnHere>, Without<Spawned>), (
With<BlueprintAssetsLoaded>,
Added<BlueprintAssetsLoaded>,
Without<BlueprintAssetsNotLoaded>,
),
>, >,
mut commands: Commands, mut commands: Commands,
@ -82,17 +230,10 @@ pub(crate) fn spawn_from_blueprints(
) in spawn_placeholders.iter() ) in spawn_placeholders.iter()
{ {
debug!( debug!(
"need to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}", "attempting to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}",
blupeprint_name.0, name, entity, original_parent blupeprint_name.0, name, entity, original_parent
); );
let mut original_children: Vec<Entity> = vec![];
if let Ok(c) = children.get(entity) {
for child in c.iter() {
original_children.push(*child);
}
}
let what = &blupeprint_name.0; let what = &blupeprint_name.0;
let model_file_name = format!("{}.{}", &what, &blueprints_config.format); let model_file_name = format!("{}.{}", &what, &blueprints_config.format);
@ -101,12 +242,15 @@ pub(crate) fn spawn_from_blueprints(
library_override.map_or_else(|| &blueprints_config.library_folder, |l| &l.0); library_override.map_or_else(|| &blueprints_config.library_folder, |l| &l.0);
let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str())); let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str()));
debug!("attempting to spawn {:?}", model_path); // info!("attempting to spawn {:?}", model_path);
let model_handle: Handle<Gltf> = asset_server.load(model_path); let model_handle: Handle<Gltf> = asset_server.load(model_path.clone()); // FIXME: kinda weird now
let gltf = assets_gltf let gltf = assets_gltf.get(&model_handle).unwrap_or_else(|| {
.get(&model_handle) panic!(
.expect("this gltf should have been loaded"); "gltf file {:?} should have been loaded",
model_path.to_str()
)
});
// WARNING we work under the assumtion that there is ONLY ONE named scene, and that the first one is the right one // WARNING we work under the assumtion that there is ONLY ONE named scene, and that the first one is the right one
let main_scene_name = gltf let main_scene_name = gltf
@ -123,6 +267,12 @@ pub(crate) fn spawn_from_blueprints(
transforms = *transform.unwrap(); transforms = *transform.unwrap();
} }
let mut original_children: Vec<Entity> = vec![];
if let Ok(c) = children.get(entity) {
for child in c.iter() {
original_children.push(*child);
}
}
commands.entity(entity).insert(( commands.entity(entity).insert((
SceneBundle { SceneBundle {
scene: scene.clone(), scene: scene.clone(),

View File

@ -1,12 +1,16 @@
use std::any::TypeId; use std::any::TypeId;
use bevy::gltf::Gltf;
use bevy::prelude::*; 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::{AnimationPlayerLink, Animations};
use super::{SpawnHere, Spawned}; use super::{SpawnHere, Spawned};
use crate::{CopyComponents, InBlueprint, NoInBlueprint, OriginalChildren}; use crate::{
AssetsToLoad, BlueprintAssetsLoaded, CopyComponents, InBlueprint, NoInBlueprint,
OriginalChildren,
};
/// this system is in charge of doing any necessary post processing after a blueprint scene has been spawned /// this system is in charge of doing any necessary post processing after a blueprint scene has been spawned
/// - it removes one level of useless nesting /// - it removes one level of useless nesting
@ -89,6 +93,8 @@ pub(crate) fn spawned_blueprint_post_process(
commands.entity(original).remove::<SpawnHere>(); commands.entity(original).remove::<SpawnHere>();
commands.entity(original).remove::<Spawned>(); commands.entity(original).remove::<Spawned>();
commands.entity(original).remove::<Handle<Scene>>(); commands.entity(original).remove::<Handle<Scene>>();
commands.entity(original).remove::<AssetsToLoad<Gltf>>(); // also clear the sub assets tracker to free up handles, perhaps just freeing up the handles and leave the rest would be better ?
commands.entity(original).remove::<BlueprintAssetsLoaded>();
commands.entity(root_entity).despawn_recursive(); commands.entity(root_entity).despawn_recursive();
} }
} }

View File

@ -1,8 +1,8 @@
[package] [package]
name = "bevy_gltf_components" name = "bevy_gltf_components"
version = "0.5.0" version = "0.5.1"
authors = ["Mark 'kaosat-dev' Moissette"] authors = ["Mark 'kaosat-dev' Moissette"]
description = "Allows you to define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side." description = "Allows you to define Bevy components direclty inside gltf files and instanciate the components on the Bevy side."
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
keywords = ["gamedev", "bevy", "assets", "gltf", "components"] keywords = ["gamedev", "bevy", "assets", "gltf", "components"]

View File

@ -13,9 +13,9 @@ This crate allows you to define [Bevy](https://bevyengine.org/) components direc
***important*** : the plugin for processing gltf files runs in ***update*** , so you cannot use the components directly if you spawn your scene from gltf in ***setup*** (the additional components will not show up) ***important*** : the plugin for processing gltf files runs in ***update*** , so you cannot use the components directly if you spawn your scene from gltf in ***setup*** (the additional components will not show up)
Please see the Please see the
* [example](https://github.com/kaosat-dev/Blender_bevy_components_workflow/examples/basic) * [example](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_components/basic)
* or use [```bevy_asset_loader```](https://github.com/NiklasEi/bevy_asset_loader) for a reliable workflow. * or use [```bevy_asset_loader```](https://github.com/NiklasEi/bevy_asset_loader) for reliable preloading of files, as this crate does not deal with loading your assets.
* alternatively, use the [```bevy_gltf_blueprints```](https://github.com/kaosat-dev/Blender_bevy_components_workflow/blob/main/crates/bevy_gltf_blueprints) crate, build on this crate's features, * alternatively, use the [```bevy_gltf_blueprints```](https://crates.io/crates/bevy_gltf_blueprints) crate, built on this crate's features,
that allows you to directly spawn entities from gltf based blueprints. that allows you to directly spawn entities from gltf based blueprints.
Here's a minimal usage example: Here's a minimal usage example:
@ -29,7 +29,7 @@ bevy_gltf_components = { version = "0.5"}
``` ```
```rust no_run ```rust no_run
//too barebones of an example to be meaningfull, please see https://github.com/kaosat-dev/Blender_bevy_components_workflow/examples/basic for a real example //too barebones of an example to be meaningfull, please see https://github.com/kaosat-dev/Blender_bevy_components_workflow/bevy_gltf_components/examples/basic for a real example
fn main() { fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
@ -84,7 +84,7 @@ Or disable the legacy mode: (enabled by default)
ComponentsFromGltfPlugin{legacy_mode: false} ComponentsFromGltfPlugin{legacy_mode: false}
``` ```
You **need** to disable legacy mode if you want to use the [```bevy_components```](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/tools_bevy_blueprints/tools/bevy_components) Blender addon + the [```bevy_registry_export crate```](https://crates.io/crates/bevy_registry_export) ! You **need** to disable legacy mode if you want to use the [```bevy_components```](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/bevy_components) Blender addon + the [```bevy_registry_export crate```](https://crates.io/crates/bevy_registry_export) !
As it create custom properties that are writen in real **ron** file format As it create custom properties that are writen in real **ron** file format
instead of a simplified version (the one in the legacy mode) instead of a simplified version (the one in the legacy mode)
@ -98,7 +98,7 @@ For example to replace your proxy components (stand-in components when you canno
which should happen **AFTER** the components from the gltf files have been injected, which should happen **AFTER** the components from the gltf files have been injected,
so ```bevy_gltf_components``` provides a **SystemSet** for that purpose:[```GltfComponentsSet```](./src/lib.rs#46) so ```bevy_gltf_components``` provides a **SystemSet** for that purpose:```GltfComponentsSet```
Typically , the order of systems should be Typically , the order of systems should be
@ -116,7 +116,7 @@ Typically , the order of systems should be
## Examples ## Examples
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/basic https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_components/basic

View File

@ -1,6 +1,6 @@
[package] [package]
name = "bevy_gltf_save_load" name = "bevy_gltf_save_load"
version = "0.4.0" version = "0.4.1"
authors = ["Mark 'kaosat-dev' Moissette"] authors = ["Mark 'kaosat-dev' Moissette"]
description = "Save & load your bevy games" description = "Save & load your bevy games"
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
@ -15,8 +15,7 @@ workspace = true
[dependencies] [dependencies]
bevy = { version = "0.13", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] } bevy = { version = "0.13", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] }
#bevy_gltf_blueprints = "0.9" bevy_gltf_blueprints = { version = "0.10", path = "../bevy_gltf_blueprints" }
bevy_gltf_blueprints = { version = "0.9", path = "../bevy_gltf_blueprints" }
[dev-dependencies] [dev-dependencies]
bevy = { version = "0.13", default-features = false, features = ["dynamic_linking"] } bevy = { version = "0.13", default-features = false, features = ["dynamic_linking"] }

View File

@ -36,7 +36,7 @@ Here's a minimal usage example:
[dependencies] [dependencies]
bevy="0.13" bevy="0.13"
bevy_gltf_save_load = "0.4" bevy_gltf_save_load = "0.4"
bevy_gltf_blueprints = "0.9" // also needed bevy_gltf_blueprints = "0.10" // also needed
``` ```
```rust no_run ```rust no_run
@ -133,7 +133,7 @@ pub fn setup_game(
``` ```
take a look at the [example]('https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_save_load/basic/src/game/mod.rs) for more clarity take a look at the [example](https://github.com/kaosat-dev/Blender_bevy_components_workflow/blob/main/examples/bevy_gltf_save_load/basic/src/game/mod.rs) for more clarity
## Installation ## Installation
@ -142,12 +142,7 @@ Add the following to your `[dependencies]` section in `Cargo.toml`:
```toml ```toml
bevy_gltf_save_load = "0.3" bevy_gltf_save_load = "0.3"
<<<<<<< HEAD bevy_gltf_blueprints = "0.10" // also needed, as bevy_gltf_save_load does not re-export it at this time
bevy_gltf_blueprints = "0.8" // also needed, as bevy_gltf_save_load does not re-export it at this time
=======
bevy_gltf_blueprints = "0.6" // also needed, as bevy_gltf_save_load does not re-export it at this time
>>>>>>> 9cb9dda5d35c635d367fa81ca1a6c752cda9bc02
``` ```
Or use `cargo add`: Or use `cargo add`:
@ -268,7 +263,7 @@ pub fn request_load(
- ```LoadingFinished``` for loading - ```LoadingFinished``` for loading
> Note: I **highly** recomend you change states when you start/finish saving & loading, otherwise things **will** get unpredictable > Note: I **highly** recomend you change states when you start/finish saving & loading, otherwise things **will** get unpredictable
Please see [the example]('https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_save_load/basic/src/game/mod.rs#77') for this. Please see [the example](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_save_load/basic/src/game/mod.rs) for this.
## Additional notes ## Additional notes
@ -287,8 +282,8 @@ For convenience ```bevy_gltf_save_load``` provides two **SystemSets**
Highly advised to get a better understanding of how things work ! Highly advised to get a better understanding of how things work !
To get started I recomend looking at To get started I recomend looking at
- [world setup]('https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_save_load/basic/src/game/in_game.rs#13') - [world setup](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_save_load/basic/src/game/in_game.rs)
- [various events & co]('https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_save_load/basic/src/game/mod.rs#77') - [various events & co](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_save_load/basic/src/game/mod.rs)
All examples are here: All examples are here:

View File

@ -1,11 +1,11 @@
[package] [package]
name = "bevy_registry_export" name = "bevy_registry_export"
version = "0.3.0" version = "0.3.1"
authors = ["Mark 'kaosat-dev' Moissette", "Pascal 'Killercup' Hertleif"] authors = ["Mark 'kaosat-dev' Moissette", "Pascal 'Killercup' Hertleif"]
description = "Allows you to define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side." description = "Allows you to create a Json export of all your components/ registered types of your Bevy app/game"
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
keywords = ["gamedev", "bevy", "assets", "gltf", "components"] keywords = ["gamedev", "bevy", "assets", "registry", "components"]
categories = ["game-development"] categories = ["game-development"]
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"

View File

@ -36,7 +36,7 @@ fn main() {
``` ```
take a look at the [example]('https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_registry_export/basic/src/core/mod.rs) for more clarity take a look at the [example](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_registry_export/basic/src/core/mod.rs) for more clarity
## Installation ## Installation

View File

@ -30,7 +30,7 @@ pub fn setup_game(
SceneBundle { SceneBundle {
// note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene
scene: models scene: models
.get(game_assets.world.id()) .get(game_assets.world.clone().unwrap().id())
.expect("main level should have been loaded") .expect("main level should have been loaded")
.scenes[0] .scenes[0]
.clone(), .clone(),

View File

@ -20,7 +20,7 @@ pub fn setup_game(
SceneBundle { SceneBundle {
// note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene
scene: models scene: models
.get(game_assets.world.id()) .get(game_assets.world.clone().unwrap().id())
.expect("main level should have been loaded") .expect("main level should have been loaded")
.scenes[0] .scenes[0]
.clone(), .clone(),

View File

@ -20,7 +20,7 @@ pub fn setup_game(
SceneBundle { SceneBundle {
// note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene
scene: models scene: models
.get(game_assets.world.id()) .get(game_assets.world.clone().unwrap().id())
.expect("main level should have been loaded") .expect("main level should have been loaded")
.scenes[0] .scenes[0]
.clone(), .clone(),

View File

@ -21,7 +21,7 @@ pub fn setup_game(
SceneBundle { SceneBundle {
// note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene
scene: models scene: models
.get(game_assets.world.id()) .get(game_assets.world.clone().unwrap().id())
.expect("main level should have been loaded") .expect("main level should have been loaded")
.scenes[0] .scenes[0]
.clone(), .clone(),

View File

@ -21,7 +21,7 @@ pub fn setup_game(
SceneBundle { SceneBundle {
// note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene
scene: models scene: models
.get(game_assets.world.id()) .get(game_assets.world.clone().unwrap().id())
.expect("main level should have been loaded") .expect("main level should have been loaded")
.scenes[0] .scenes[0]
.clone(), .clone(),

View File

@ -76,7 +76,7 @@ pub fn trigger_level_transition(
} else if target_level == "Level2" { } else if target_level == "Level2" {
level = game_assets.level2.clone().unwrap(); level = game_assets.level2.clone().unwrap();
} else { } else {
level = game_assets.world.clone(); level = game_assets.world.clone().unwrap();
} }
info!("spawning new level"); info!("spawning new level");
commands.spawn(( commands.spawn((

View File

@ -21,7 +21,7 @@ pub fn setup_game(
SceneBundle { SceneBundle {
// note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene
scene: models scene: models
.get(game_assets.world.id()) .get(game_assets.world.clone().unwrap().id())
.expect("main level should have been loaded") .expect("main level should have been loaded")
.scenes[0] .scenes[0]
.clone(), .clone(),

View File

@ -5,8 +5,8 @@ use bevy_asset_loader::prelude::*;
#[derive(AssetCollection, Resource)] #[derive(AssetCollection, Resource)]
pub struct GameAssets { pub struct GameAssets {
#[asset(key = "world")] #[asset(key = "world", optional)]
pub world: Handle<Gltf>, pub world: Option<Handle<Gltf>>,
#[asset(key = "world_dynamic", optional)] #[asset(key = "world_dynamic", optional)]
pub world_dynamic: Option<Handle<Gltf>>, pub world_dynamic: Option<Handle<Gltf>>,
@ -16,8 +16,8 @@ pub struct GameAssets {
#[asset(key = "level2", optional)] #[asset(key = "level2", optional)]
pub level2: Option<Handle<Gltf>>, pub level2: Option<Handle<Gltf>>,
#[asset(key = "models", collection(typed, mapped))] #[asset(key = "models", collection(typed, mapped), optional)]
pub models: HashMap<String, Handle<Gltf>>, pub models: Option<HashMap<String, Handle<Gltf>>>,
#[asset(key = "materials", collection(typed, mapped), optional)] #[asset(key = "materials", collection(typed, mapped), optional)]
pub materials: Option<HashMap<String, Handle<Gltf>>>, pub materials: Option<HashMap<String, Handle<Gltf>>>,

View File

@ -1,6 +1,6 @@
({ ({
"world":File (path: "models/World.glb"), /*"world":File (path: "models/World.glb"),
"models": Folder ( "models": Folder (
path: "models/library", path: "models/library",
), ),*/
}) })

View File

@ -3576,6 +3576,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,
@ -10834,6 +10850,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,

View File

@ -11,6 +11,7 @@ impl Plugin for CorePlugin {
legacy_mode: false, legacy_mode: false,
library_folder: "models/library".into(), library_folder: "models/library".into(),
format: GltfFormat::GLB, format: GltfFormat::GLB,
material_library: true,
aabbs: true, aabbs: true,
..Default::default() ..Default::default()
}, },

View File

@ -1,37 +1,25 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy_gltf_blueprints::{BluePrintBundle, BlueprintName, GameWorldTag}; use bevy_gltf_blueprints::{BluePrintBundle, BlueprintName, GameWorldTag};
use bevy_gltf_worlflow_examples_common_rapier::{assets::GameAssets, GameState, InAppRunning}; use bevy_gltf_worlflow_examples_common_rapier::{GameState, InAppRunning};
use bevy_rapier3d::prelude::Velocity; use bevy_rapier3d::prelude::Velocity;
use rand::Rng; use rand::Rng;
pub fn setup_game( pub fn setup_game(
mut commands: Commands, mut commands: Commands,
game_assets: Res<GameAssets>, asset_server: Res<AssetServer>,
models: Res<Assets<bevy::gltf::Gltf>>,
mut next_game_state: ResMut<NextState<GameState>>, mut next_game_state: ResMut<NextState<GameState>>,
) { ) {
commands.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 0.2,
});
// here we actually spawn our game world/level // here we actually spawn our game world/level
commands.spawn(( commands.spawn((
SceneBundle { SceneBundle {
// note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene scene: asset_server.load("models/World.glb#Scene0"),
scene: models
.get(game_assets.world.id())
.expect("main level should have been loaded")
.scenes[0]
.clone(),
..default() ..default()
}, },
bevy::prelude::Name::from("world"), bevy::prelude::Name::from("world"),
GameWorldTag, GameWorldTag,
InAppRunning, InAppRunning,
)); ));
next_game_state.set(GameState::InGame) next_game_state.set(GameState::InGame)
} }

View File

@ -4,7 +4,7 @@ use std::{
time::Duration, time::Duration,
}; };
use bevy_gltf_blueprints::{AnimationPlayerLink, BlueprintName}; use bevy_gltf_blueprints::{AnimationPlayerLink, BlueprintName, BlueprintsList};
pub use in_game::*; pub use in_game::*;
use bevy::{ use bevy::{
@ -22,7 +22,8 @@ fn start_game(mut next_app_state: ResMut<NextState<AppState>>) {
// 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 "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
#[allow(clippy::too_many_arguments)]
fn validate_export( fn validate_export(
parents: Query<&Parent>, parents: Query<&Parent>,
children: Query<&Children>, children: Query<&Children>,
@ -31,6 +32,8 @@ fn validate_export(
animation_player_links: Query<(Entity, &AnimationPlayerLink)>, animation_player_links: Query<(Entity, &AnimationPlayerLink)>,
exported_cylinder: Query<(Entity, &Name, &UnitTest, &TupleTestF32)>, exported_cylinder: Query<(Entity, &Name, &UnitTest, &TupleTestF32)>,
empties_candidates: Query<(Entity, &Name, &GlobalTransform)>, empties_candidates: Query<(Entity, &Name, &GlobalTransform)>,
blueprints_list: Query<(Entity, &BlueprintsList)>,
) { ) {
let animations_found = !animation_player_links.is_empty(); let animations_found = !animation_player_links.is_empty();
@ -69,11 +72,13 @@ fn validate_export(
} }
} }
let blueprints_list_found = !blueprints_list.is_empty();
fs::write( fs::write(
"bevy_diagnostics.json", "bevy_diagnostics.json",
format!( format!(
"{{ \"animations\": {}, \"cylinder_found\": {} , \"nested_blueprint_found\": {}, \"empty_found\": {} }}", "{{ \"animations\": {}, \"cylinder_found\": {} , \"nested_blueprint_found\": {}, \"empty_found\": {}, \"blueprints_list_found\": {} }}",
animations_found, cylinder_found, nested_blueprint_found, empty_found animations_found, cylinder_found, nested_blueprint_found, empty_found, blueprints_list_found
), ),
) )
.expect("Unable to write file"); .expect("Unable to write file");
@ -97,9 +102,9 @@ 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.add_systems(Update, (spawn_test).run_if(in_state(GameState::InGame)))
.add_systems(Update, validate_export) .add_systems(Update, validate_export)
.add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once
.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( .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))),

View File

@ -15,6 +15,7 @@ expected_custom_property_values = {'AComponentWithAnExtremlyExageratedOrMaybeNot
'0.0, low_frequency_boost_curvature: 0.0, prefilter_settings: (threshold: 0.0, threshold_softness: ' '0.0, low_frequency_boost_curvature: 0.0, prefilter_settings: (threshold: 0.0, threshold_softness: '
'0.0))', '0.0))',
'BlueprintName': '(" ")', 'BlueprintName': '(" ")',
'BlueprintsList': '("")',
'BorderColor': '(Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0))', 'BorderColor': '(Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0))',
'Button': '()', 'Button': '()',
'CalculatedClip': '(clip: (max: Vec2(x:0.0, y:0.0), min: Vec2(x:0.0, y:0.0)))', 'CalculatedClip': '(clip: (max: Vec2(x:0.0, y:0.0), min: Vec2(x:0.0, y:0.0)))',
@ -214,6 +215,7 @@ expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExagerate
'0.8133212327957153, prefilter_settings: (threshold: 0.8235888481140137, threshold_softness: ' '0.8133212327957153, prefilter_settings: (threshold: 0.8235888481140137, threshold_softness: '
'0.6534725427627563))', '0.6534725427627563))',
'BlueprintName': '("sbnpsago")', 'BlueprintName': '("sbnpsago")',
'BlueprintsList': '("")',
'BorderColor': '(Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, ' 'BorderColor': '(Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
'alpha:0.20609822869300842))', 'alpha:0.20609822869300842))',
'Button': '()', 'Button': '()',

View File

@ -48,12 +48,12 @@ def test_components_should_generate_correct_custom_properties(setup_data):
except Exception as error: except Exception as error:
errors.append(error) errors.append(error)
'''pp = pprint.PrettyPrinter(depth=14, width=120) pp = pprint.PrettyPrinter(depth=14, width=120)
print("CUSTOM PROPERTY VALUES") print("CUSTOM PROPERTY VALUES")
pp.pprint(custom_property_values)''' pp.pprint(custom_property_values)
assert len(errors) == 0 assert len(errors) == 0
assert len(added_components) == 158 assert len(added_components) == 159
def test_components_should_generate_correct_custom_properties_with_randomized_values(setup_data): def test_components_should_generate_correct_custom_properties_with_randomized_values(setup_data):
@ -105,7 +105,7 @@ def test_components_should_generate_correct_custom_properties_with_randomized_va
print("error_components", error_components) print("error_components", error_components)
assert len(errors) == 0 assert len(errors) == 0
assert len(added_components) == 158 assert len(added_components) == 159
def test_components_should_generate_correct_propertyGroup_values_from_custom_properties(setup_data): def test_components_should_generate_correct_propertyGroup_values_from_custom_properties(setup_data):
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
@ -163,7 +163,7 @@ def test_components_should_generate_correct_propertyGroup_values_from_custom_pro
for index, error in enumerate(errors): for index, error in enumerate(errors):
print("ERROR", error, failing_components[index]) print("ERROR", error, failing_components[index])
assert len(errors) == 0 assert len(errors) == 0
assert len(added_components) == 158 assert len(added_components) == 159
def test_remove_components(setup_data): def test_remove_components(setup_data):

View File

@ -1,7 +1,7 @@
bl_info = { bl_info = {
"name": "gltf_auto_export", "name": "gltf_auto_export",
"author": "kaosigh", "author": "kaosigh",
"version": (0, 15, 0), "version": (0, 16, 0),
"blender": (3, 4, 0), "blender": (3, 4, 0),
"location": "File > Import-Export", "location": "File > Import-Export",
"description": "glTF/glb auto-export", "description": "glTF/glb auto-export",

View File

@ -4,7 +4,7 @@ import bpy
from ..helpers.generate_and_export import generate_and_export from ..helpers.generate_and_export import generate_and_export
from .export_gltf import (generate_gltf_export_preferences, export_gltf) from .export_gltf import (generate_gltf_export_preferences, export_gltf)
from ..modules.bevy_dynamic import is_object_dynamic, is_object_static from ..modules.bevy_dynamic import is_object_dynamic, is_object_static
from ..helpers.helpers_scenes import clear_hollow_scene, copy_hollowed_collection_into from ..helpers.helpers_scenes import clear_hollow_scene, copy_hollowed_collection_into, inject_blueprints_list_into_main_scene, remove_blueprints_list_from_main_scene
# export all main scenes # export all main scenes
@ -16,6 +16,7 @@ def export_main_scene(scene, folder_path, addon_prefs, library_collections):
gltf_export_preferences = generate_gltf_export_preferences(addon_prefs) gltf_export_preferences = generate_gltf_export_preferences(addon_prefs)
export_output_folder = getattr(addon_prefs,"export_output_folder") export_output_folder = getattr(addon_prefs,"export_output_folder")
export_blueprints = getattr(addon_prefs,"export_blueprints") export_blueprints = getattr(addon_prefs,"export_blueprints")
legacy_mode = getattr(addon_prefs, "export_legacy_mode")
export_separate_dynamic_and_static_objects = getattr(addon_prefs, "export_separate_dynamic_and_static_objects") export_separate_dynamic_and_static_objects = getattr(addon_prefs, "export_separate_dynamic_and_static_objects")
gltf_output_path = os.path.join(folder_path, export_output_folder, scene.name) gltf_output_path = os.path.join(folder_path, export_output_folder, scene.name)
@ -29,6 +30,9 @@ def export_main_scene(scene, folder_path, addon_prefs, library_collections):
} }
if export_blueprints : if export_blueprints :
if not legacy_mode:
inject_blueprints_list_into_main_scene(scene)
if export_separate_dynamic_and_static_objects: if export_separate_dynamic_and_static_objects:
#print("SPLIT STATIC AND DYNAMIC") #print("SPLIT STATIC AND DYNAMIC")
# first export static objects # first export static objects
@ -67,5 +71,8 @@ def export_main_scene(scene, folder_path, addon_prefs, library_collections):
print(" exporting gltf to", gltf_output_path, ".gltf/glb") print(" exporting gltf to", gltf_output_path, ".gltf/glb")
export_gltf(gltf_output_path, export_settings) export_gltf(gltf_output_path, export_settings)
if not legacy_mode:
remove_blueprints_list_from_main_scene(scene)

View File

@ -36,16 +36,23 @@ def get_marked_collections(scene, addon_prefs):
return (collection_names, marked_collections) return (collection_names, marked_collections)
# gets all collections within collections that might also be relevant # gets all collections within collections that might also be relevant
def get_sub_collections(collections, parent, children_per_collection): def get_sub_collections(collections, parent=None, children_per_collection=None):
if parent == None:
parent = CollectionNode()
if children_per_collection == None:
children_per_collection = {}
collection_names = set() collection_names = set()
used_collections = [] used_collections = []
for root_collection in collections: for root_collection in collections:
node = Node(name=root_collection.name, parent=parent) #print("collections", collections)
node = CollectionNode(name=root_collection.name, parent=parent)
parent.children.append(node) parent.children.append(node)
#print("root collection", root_collection.name) #print("root collection", root_collection.name)
for collection in traverse_tree(root_collection): # TODO: filter out COLLECTIONS that have the flatten flag (unlike the flatten flag on colleciton instances themselves) for collection in traverse_tree(root_collection): # TODO: filter out COLLECTIONS that have the flatten flag (unlike the flatten flag on colleciton instances themselves)
#print("sub", collection)
node_name = collection.name node_name = collection.name
children_per_collection[node_name] = [] children_per_collection[node_name] = []
#print(" scanning", collection.name) #print(" scanning", collection.name)
@ -53,12 +60,15 @@ def get_sub_collections(collections, parent, children_per_collection):
#print("FLATTEN", object.name, 'Flatten' in object) #print("FLATTEN", object.name, 'Flatten' in object)
if object.instance_type == 'COLLECTION' : # and not 'Flatten' in object: if object.instance_type == 'COLLECTION' : # and not 'Flatten' in object:
collection_name = object.instance_collection.name collection_name = object.instance_collection.name
#print("sub obj", collection_name)
# FIXME: not sure:
children_per_collection[node_name].append(collection_name)
(sub_names, sub_collections) = get_sub_collections([object.instance_collection], node, children_per_collection) (sub_names, sub_collections) = get_sub_collections([object.instance_collection], node, children_per_collection)
if len(list(sub_names)) > 0: if len(list(sub_names)) > 0:
children_per_collection[node_name] += (list(sub_names)) children_per_collection[node_name] += (list(sub_names))
#print(" found sub collection in use", object.name, object.instance_collection) #print(" found sub collection in use", object.name, object.instance_collection)
if not collection_name in collection_names: if not collection_name in collection_names:
collection_names.add(collection_name) collection_names.add(collection_name)
used_collections.append(object.instance_collection) used_collections.append(object.instance_collection)
@ -77,7 +87,7 @@ def flatten_collection_tree(node, children_per_collection):
children_per_collection[node.name] = list(set( children_per_collection[node.name])) children_per_collection[node.name] = list(set( children_per_collection[node.name]))
class Node : class CollectionNode :
def __init__(self, name="", parent=None): def __init__(self, name="", parent=None):
self.name = name self.name = name
self.children = [] self.children = []
@ -93,7 +103,7 @@ def get_exportable_collections(main_scenes, library_scenes, addon_prefs):
all_collections = [] all_collections = []
all_collection_names = [] all_collection_names = []
root_node = Node() root_node = CollectionNode()
root_node.name = "root" root_node.name = "root"
children_per_collection = {} children_per_collection = {}

View File

@ -1,5 +1,6 @@
import json
import bpy import bpy
from .helpers_collections import (set_active_collection) from .helpers_collections import (CollectionNode, get_sub_collections, get_used_collections, set_active_collection)
from .object_makers import (make_empty) from .object_makers import (make_empty)
@ -73,6 +74,17 @@ def copy_hollowed_collection_into(source_collection, destination_collection, par
empty_obj['BlueprintName'] = '"'+collection_name+'"' if legacy_mode else '("'+collection_name+'")' empty_obj['BlueprintName'] = '"'+collection_name+'"' if legacy_mode else '("'+collection_name+'")'
empty_obj['SpawnHere'] = '()' empty_obj['SpawnHere'] = '()'
# we also inject a list of all sub blueprints, so that the bevy side can preload them
if not legacy_mode:
root_node = CollectionNode()
root_node.name = "root"
children_per_collection = {}
print("collection stuff", original_name)
get_sub_collections([object.instance_collection], root_node, children_per_collection)
empty_obj["BlueprintsList"] = f"({json.dumps(dict(children_per_collection))})"
#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
@ -149,3 +161,45 @@ 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):
print("injecting assets/blueprints data into scene")
root_collection = scene.collection
assets_list = None
assets_list_name = f"assets_list_{scene.name}_components"
for object in scene.objects:
if object.name == assets_list_name:
assets_list = object
break
if assets_list is None:
assets_list = make_empty(assets_list_name, [0,0,0], [0,0,0], [0,0,0], root_collection)
# find all blueprints used in a scene
# TODO: export a tree rather than a flat list ? because you could have potential clashing items in flat lists (amongst other issues)
(collection_names, collections) = get_used_collections(scene)
root_node = CollectionNode()
root_node.name = "root"
children_per_collection = {}
#print("collection_names", collection_names, "collections", collections)
get_sub_collections(collections, root_node, children_per_collection)
# what about marked assets ?
# what about audio assets ?
# what about materials ?
# object['MaterialInfo'] = '(name: "'+material.name+'", source: "'+current_project_name + '")'
#assets_list["blueprints_direct"] = list(collection_names)
assets_list["BlueprintsList"] = f"({json.dumps(dict(children_per_collection))})"
#assets_list["Materials"]= '()'
def remove_blueprints_list_from_main_scene(scene):
assets_list = None
assets_list_name = f"assets_list_{scene.name}_components"
for object in scene.objects:
if object.name == assets_list_name:
assets_list = object
if assets_list is not None:
bpy.data.objects.remove(assets_list, do_unlink=True)

View File

@ -19,6 +19,8 @@ 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)
@ -33,6 +35,11 @@ def setup_data(request):
return None return None
def get_orphan_data():
orphan_meshes = [m.name for m in bpy.data.meshes if m.users == 0]
# print("orphan meshes before", orphan_meshes)
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
@ -57,7 +64,6 @@ def test_export_do_not_export_blueprints(setup_data):
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
# first, configure things # first, configure things
# we use the global settings for that # we use the global settings for that
export_props = { export_props = {
@ -210,3 +216,27 @@ 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
def test_export_should_not_generate_orphan_data(setup_data):
auto_export_operator = bpy.ops.export_scenes.auto_gltf
# first, configure things
# we use the global settings for that
export_props = {
"main_scene_names" : ['World'],
"library_scene_names": ['Library']
}
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.write(json.dumps(export_props))
auto_export_operator(
direct_mode=True,
export_output_folder="./models",
export_scene_settings=True,
export_blueprints=False,
)
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

View File

@ -16,18 +16,16 @@ def setup_data(request):
root_path = "../../testing/bevy_example" root_path = "../../testing/bevy_example"
assets_root_path = os.path.join(root_path, "assets") assets_root_path = os.path.join(root_path, "assets")
models_path = os.path.join(assets_root_path, "models") models_path = os.path.join(assets_root_path, "models")
#materials_path = os.path.join("../../testing", "materials") materials_path = os.path.join(assets_root_path, "materials")
#other_materials_path = os.path.join("../../testing", "other_materials") #other_materials_path = os.path.join("../../testing", "other_materials")
print("\nPerforming teardown...") print("\nPerforming teardown...")
if os.path.exists(models_path): if os.path.exists(models_path):
shutil.rmtree(models_path) shutil.rmtree(models_path)
"""if os.path.exists(materials_path): if os.path.exists(materials_path):
shutil.rmtree(materials_path) shutil.rmtree(materials_path)
if os.path.exists(other_materials_path):
shutil.rmtree(other_materials_path)"""
diagnostics_file_path = os.path.join(root_path, "bevy_diagnostics.json") diagnostics_file_path = os.path.join(root_path, "bevy_diagnostics.json")
if os.path.exists(diagnostics_file_path): if os.path.exists(diagnostics_file_path):
os.remove(diagnostics_file_path) os.remove(diagnostics_file_path)
@ -75,7 +73,8 @@ def test_export_complex(setup_data):
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,
export_legacy_mode=False, export_legacy_mode=False,
export_animations=True export_animations=True,
export_materials_library=True
) )
# blueprint1 => has an instance, got changed, should export # blueprint1 => has an instance, got changed, should export
# blueprint2 => has NO instance, but marked as asset, should export # blueprint2 => has NO instance, but marked as asset, should export
@ -84,7 +83,6 @@ def test_export_complex(setup_data):
# blueprint5 => has NO instance, not marked as asset, should NOT export # blueprint5 => has NO instance, not marked as asset, should NOT export
assert os.path.exists(os.path.join(models_path, "World.glb")) == True assert os.path.exists(os.path.join(models_path, "World.glb")) == True
assert os.path.exists(os.path.join(models_path, "library", "Blueprint1.glb")) == True assert os.path.exists(os.path.join(models_path, "library", "Blueprint1.glb")) == True
assert os.path.exists(os.path.join(models_path, "library", "Blueprint2.glb")) == True assert os.path.exists(os.path.join(models_path, "library", "Blueprint2.glb")) == True
assert os.path.exists(os.path.join(models_path, "library", "Blueprint3.glb")) == True assert os.path.exists(os.path.join(models_path, "library", "Blueprint3.glb")) == True
@ -93,13 +91,14 @@ def test_export_complex(setup_data):
assert os.path.exists(os.path.join(models_path, "library", "Blueprint6_animated.glb")) == True assert os.path.exists(os.path.join(models_path, "library", "Blueprint6_animated.glb")) == True
assert os.path.exists(os.path.join(models_path, "library", "Blueprint7_hierarchy.glb")) == True assert os.path.exists(os.path.join(models_path, "library", "Blueprint7_hierarchy.glb")) == True
# 'assets_list_'+scene.name+"_components" should have been removed after the export
assets_list_object_name = "assets_list_"+"World"+"_components"
assets_list_object_present = assets_list_object_name in bpy.data.objects
assert assets_list_object_present == False
# now run bevy # now run bevy
command = "cargo run --features bevy/dynamic_linking" command = "cargo run --features bevy/dynamic_linking"
# assert getattr(propertyGroup, 'a') == 0.5714026093482971
FNULL = open(os.devnull, 'w') #use this if you want to suppress output to stdout from the subprocess FNULL = open(os.devnull, 'w') #use this if you want to suppress output to stdout from the subprocess
filename = "my_file.dat"
args = command
#subprocess.call(args, stdout=FNULL, stderr=FNULL, shell=False, cwd=bevy_run_exec_path)
return_code = subprocess.call(["cargo", "run", "--features", "bevy/dynamic_linking"], cwd=root_path) return_code = subprocess.call(["cargo", "run", "--features", "bevy/dynamic_linking"], cwd=root_path)
print("RETURN CODE OF BEVY APP", return_code) print("RETURN CODE OF BEVY APP", return_code)
assert return_code == 0 assert return_code == 0
@ -110,6 +109,7 @@ def test_export_complex(setup_data):
assert diagnostics["animations"] == True assert diagnostics["animations"] == True
assert diagnostics["cylinder_found"] == True assert diagnostics["cylinder_found"] == True
assert diagnostics["empty_found"] == True assert diagnostics["empty_found"] == True
assert diagnostics["blueprints_list_found"] == True
# 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")