diff --git a/crates/bevy_gltf_blueprints/Cargo.toml b/crates/bevy_gltf_blueprints/Cargo.toml index 9c47f0d..fbd0749 100644 --- a/crates/bevy_gltf_blueprints/Cargo.toml +++ b/crates/bevy_gltf_blueprints/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "bevy_gltf_blueprints" -version = "0.9.0" +version = "0.10.0" 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" repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" keywords = ["gamedev", "bevy", "gltf", "blueprint", "prefab"] diff --git a/crates/bevy_gltf_blueprints/README.md b/crates/bevy_gltf_blueprints/README.md index 6cad9a1..4be5ee9 100644 --- a/crates/bevy_gltf_blueprints/README.md +++ b/crates/bevy_gltf_blueprints/README.md @@ -5,7 +5,7 @@ # 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 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 * 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 @@ -26,7 +28,7 @@ Here's a minimal usage example: # Cargo.toml [dependencies] 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`: ```toml -bevy_gltf_blueprints = "0.9" +bevy_gltf_blueprints = "0.10" ``` Or use `cargo add`: @@ -165,13 +167,10 @@ commands.spawn(( ### 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 ```SpawnHere``` component -[```BluePrintBundle```](./src/lib.rs#22) - - ## Additional information - 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, -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 @@ -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 -particularly from https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation/game/in_game.rs#86 -onward +particularly from https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/animation/game/in_game.rs ## 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: | `bevy_gltf_blueprints` | `bevy` | | :-- | :-- | -| `0.9` | `0.13` | +| `0.9 - 0.10` | `0.13` | | `0.3 - 0.8` | `0.12` | | `0.1 - 0.2` | `0.11` | | branch `main` | `0.13` | diff --git a/crates/bevy_gltf_blueprints/src/lib.rs b/crates/bevy_gltf_blueprints/src/lib.rs index 366f05f..78642cd 100644 --- a/crates/bevy_gltf_blueprints/src/lib.rs +++ b/crates/bevy_gltf_blueprints/src/lib.rs @@ -120,6 +120,9 @@ impl Plugin for BlueprintsPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() + .register_type::>() + .register_type::>>() .insert_resource(BluePrintsConfig { format: self.format, library_folder: self.library_folder.clone(), @@ -140,11 +143,24 @@ impl Plugin for BlueprintsPlugin { .add_systems( Update, ( - spawn_from_blueprints, - compute_scene_aabbs.run_if(aabbs_enabled), - apply_deferred.run_if(aabbs_enabled), + ( + prepare_blueprints, + check_for_loaded, + spawn_from_blueprints, + apply_deferred, + ) + .chain(), + (compute_scene_aabbs, apply_deferred) + .chain() + .run_if(aabbs_enabled), apply_deferred, - materials_inject.run_if(materials_library_enabled), + ( + materials_inject, + check_for_material_loaded, + materials_inject2, + ) + .chain() + .run_if(materials_library_enabled), ) .chain() .in_set(GltfBlueprintsSet::Spawn), diff --git a/crates/bevy_gltf_blueprints/src/materials.rs b/crates/bevy_gltf_blueprints/src/materials.rs index 5f0140a..fe440d4 100644 --- a/crates/bevy_gltf_blueprints/src/materials.rs +++ b/crates/bevy_gltf_blueprints/src/materials.rs @@ -4,6 +4,7 @@ use bevy::{ asset::{AssetServer, Assets, Handle}, ecs::{ component::Component, + entity::Entity, query::{Added, With}, reflect::ReflectComponent, system::{Commands, Query, Res, ResMut}, @@ -16,7 +17,7 @@ use bevy::{ render::mesh::Mesh, }; -use crate::BluePrintsConfig; +use crate::{AssetLoadTracker, AssetsToLoad, BluePrintsConfig}; #[derive(Component, Reflect, Default, Debug)] #[reflect(Component)] @@ -26,10 +27,111 @@ pub struct MaterialInfo { 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 pub(crate) fn materials_inject( + blueprints_config: ResMut, + material_infos: Query<(Entity, &MaterialInfo), Added>, + asset_server: Res, + 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 = asset_server.load(materials_path.clone()); + let material_file_id = material_file_handle.id(); + let asset_infos: Vec> = 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), + With, + >, + asset_server: Res, + 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::(); + } + } +} + +/// system that injects / replaces materials from material library +pub(crate) fn materials_inject2( mut blueprints_config: ResMut, - material_infos: Query<(&MaterialInfo, &Children), Added>, + material_infos: Query< + (&MaterialInfo, &Children), + ( + Added, + With, + ), + >, with_materials_and_meshes: Query< (), ( @@ -38,9 +140,9 @@ pub(crate) fn materials_inject( With>, ), >, - models: Res>, - + assets_gltf: Res>, asset_server: Res, + mut commands: Commands, ) { for (material_info, children) in material_infos.iter() { @@ -66,9 +168,9 @@ pub(crate) fn materials_inject( .expect("we should have the material available"); material_found = Some(material); } else { - let my_gltf: Handle = asset_server.load(materials_path.clone()); - let mat_gltf = models - .get(my_gltf.id()) + let model_handle: Handle = asset_server.load(materials_path.clone()); // FIXME: kinda weird now + let mat_gltf = assets_gltf + .get(model_handle.id()) .expect("material should have been preloaded"); if mat_gltf.named_materials.contains_key(material_name) { let material = mat_gltf diff --git a/crates/bevy_gltf_blueprints/src/spawn_from_blueprints.rs b/crates/bevy_gltf_blueprints/src/spawn_from_blueprints.rs index 3701fa5..f977c61 100644 --- a/crates/bevy_gltf_blueprints/src/spawn_from_blueprints.rs +++ b/crates/bevy_gltf_blueprints/src/spawn_from_blueprints.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use bevy::{gltf::Gltf, prelude::*}; +use bevy::{gltf::Gltf, prelude::*, utils::HashMap}; use crate::{Animations, BluePrintsConfig}; @@ -46,8 +46,152 @@ pub struct AddToGameWorld; /// helper component, just to transfer child data pub(crate) struct OriginalChildren(pub Vec); -/// 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>); + +/// helper component, for tracking loaded assets's loading state, id , handle etc +#[derive(Default, Debug)] +pub(crate) struct AssetLoadTracker { + #[allow(dead_code)] + pub name: String, + pub id: AssetId, + pub loaded: bool, + #[allow(dead_code)] + pub handle: Handle, +} + +/// helper component, for tracking loaded assets +#[derive(Component, Debug)] +pub(crate) struct AssetsToLoad { + pub all_loaded: bool, + pub asset_infos: Vec>, + pub progress: f32, +} +impl Default for AssetsToLoad { + 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 +pub(crate) fn prepare_blueprints( + spawn_placeholders: Query< + ( + Entity, + &BlueprintName, + Option<&Parent>, + Option<&Library>, + Option<&Name>, + Option<&BlueprintsList>, + ), + (Added, Added, Without), + >, + + mut commands: Commands, + asset_server: Res, + blueprints_config: Res, +) { + 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> = 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 = 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), + With, + >, + asset_server: Res, + 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::(); + } + } +} + pub(crate) fn spawn_from_blueprints( spawn_placeholders: Query< ( @@ -59,7 +203,11 @@ pub(crate) fn spawn_from_blueprints( Option<&AddToGameWorld>, Option<&Name>, ), - (Added, Added, Without), + ( + With, + Added, + Without, + ), >, mut commands: Commands, @@ -82,17 +230,10 @@ pub(crate) fn spawn_from_blueprints( ) in spawn_placeholders.iter() { debug!( - "need to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}", + "attempting to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}", blupeprint_name.0, name, entity, original_parent ); - let mut original_children: Vec = vec![]; - if let Ok(c) = children.get(entity) { - for child in c.iter() { - original_children.push(*child); - } - } - let what = &blupeprint_name.0; 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); let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str())); - debug!("attempting to spawn {:?}", model_path); - let model_handle: Handle = asset_server.load(model_path); + // info!("attempting to spawn {:?}", model_path); + let model_handle: Handle = asset_server.load(model_path.clone()); // FIXME: kinda weird now - let gltf = assets_gltf - .get(&model_handle) - .expect("this gltf should have been loaded"); + let gltf = assets_gltf.get(&model_handle).unwrap_or_else(|| { + panic!( + "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 let main_scene_name = gltf @@ -123,6 +267,12 @@ pub(crate) fn spawn_from_blueprints( transforms = *transform.unwrap(); } + let mut original_children: Vec = vec![]; + if let Ok(c) = children.get(entity) { + for child in c.iter() { + original_children.push(*child); + } + } commands.entity(entity).insert(( SceneBundle { scene: scene.clone(), diff --git a/crates/bevy_gltf_blueprints/src/spawn_post_process.rs b/crates/bevy_gltf_blueprints/src/spawn_post_process.rs index 2caa625..b53f4d7 100644 --- a/crates/bevy_gltf_blueprints/src/spawn_post_process.rs +++ b/crates/bevy_gltf_blueprints/src/spawn_post_process.rs @@ -1,12 +1,16 @@ use std::any::TypeId; +use bevy::gltf::Gltf; use bevy::prelude::*; use bevy::scene::SceneInstance; // use bevy::utils::hashbrown::HashSet; use super::{AnimationPlayerLink, Animations}; 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 /// - it removes one level of useless nesting @@ -89,6 +93,8 @@ pub(crate) fn spawned_blueprint_post_process( commands.entity(original).remove::(); commands.entity(original).remove::(); commands.entity(original).remove::>(); + commands.entity(original).remove::>(); // 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::(); commands.entity(root_entity).despawn_recursive(); } } diff --git a/crates/bevy_gltf_components/Cargo.toml b/crates/bevy_gltf_components/Cargo.toml index cf1b664..2004d97 100644 --- a/crates/bevy_gltf_components/Cargo.toml +++ b/crates/bevy_gltf_components/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "bevy_gltf_components" -version = "0.5.0" +version = "0.5.1" 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" repository = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" keywords = ["gamedev", "bevy", "assets", "gltf", "components"] diff --git a/crates/bevy_gltf_components/README.md b/crates/bevy_gltf_components/README.md index fa5289b..9ac3657 100644 --- a/crates/bevy_gltf_components/README.md +++ b/crates/bevy_gltf_components/README.md @@ -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) Please see the - * [example](https://github.com/kaosat-dev/Blender_bevy_components_workflow/examples/basic) - * or use [```bevy_asset_loader```](https://github.com/NiklasEi/bevy_asset_loader) for a reliable workflow. - * 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, + * [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 reliable preloading of files, as this crate does not deal with loading your assets. + * 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. Here's a minimal usage example: @@ -29,7 +29,7 @@ bevy_gltf_components = { version = "0.5"} ``` ```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() { App::new() .add_plugins(DefaultPlugins) @@ -84,7 +84,7 @@ Or disable the legacy mode: (enabled by default) 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 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, -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 @@ -116,7 +116,7 @@ Typically , the order of systems should be ## 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 diff --git a/crates/bevy_gltf_save_load/Cargo.toml b/crates/bevy_gltf_save_load/Cargo.toml index 2da1390..4e7eeea 100644 --- a/crates/bevy_gltf_save_load/Cargo.toml +++ b/crates/bevy_gltf_save_load/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_gltf_save_load" -version = "0.4.0" +version = "0.4.1" authors = ["Mark 'kaosat-dev' Moissette"] description = "Save & load your bevy games" homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow" @@ -15,8 +15,7 @@ workspace = true [dependencies] bevy = { version = "0.13", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] } -#bevy_gltf_blueprints = "0.9" -bevy_gltf_blueprints = { version = "0.9", path = "../bevy_gltf_blueprints" } +bevy_gltf_blueprints = { version = "0.10", path = "../bevy_gltf_blueprints" } [dev-dependencies] bevy = { version = "0.13", default-features = false, features = ["dynamic_linking"] } diff --git a/crates/bevy_gltf_save_load/README.md b/crates/bevy_gltf_save_load/README.md index 1141132..43aec04 100644 --- a/crates/bevy_gltf_save_load/README.md +++ b/crates/bevy_gltf_save_load/README.md @@ -36,7 +36,7 @@ Here's a minimal usage example: [dependencies] bevy="0.13" bevy_gltf_save_load = "0.4" -bevy_gltf_blueprints = "0.9" // also needed +bevy_gltf_blueprints = "0.10" // also needed ``` ```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 @@ -142,12 +142,7 @@ Add the following to your `[dependencies]` section in `Cargo.toml`: ```toml bevy_gltf_save_load = "0.3" -<<<<<<< HEAD -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 - +bevy_gltf_blueprints = "0.10" // also needed, as bevy_gltf_save_load does not re-export it at this time ``` Or use `cargo add`: @@ -268,7 +263,7 @@ pub fn request_load( - ```LoadingFinished``` for loading > 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 @@ -287,8 +282,8 @@ For convenience ```bevy_gltf_save_load``` provides two **SystemSets** Highly advised to get a better understanding of how things work ! 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') -- [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') +- [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) All examples are here: diff --git a/crates/bevy_registry_export/Cargo.toml b/crates/bevy_registry_export/Cargo.toml index 2471063..30e72cd 100644 --- a/crates/bevy_registry_export/Cargo.toml +++ b/crates/bevy_registry_export/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "bevy_registry_export" -version = "0.3.0" +version = "0.3.1" 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" 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"] edition = "2021" license = "MIT OR Apache-2.0" diff --git a/crates/bevy_registry_export/README.md b/crates/bevy_registry_export/README.md index 6fe601b..20a2a6e 100644 --- a/crates/bevy_registry_export/README.md +++ b/crates/bevy_registry_export/README.md @@ -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 diff --git a/examples/bevy_gltf_blueprints/animation/src/game/in_game.rs b/examples/bevy_gltf_blueprints/animation/src/game/in_game.rs index fcf711b..454ce27 100644 --- a/examples/bevy_gltf_blueprints/animation/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/animation/src/game/in_game.rs @@ -30,7 +30,7 @@ pub fn setup_game( SceneBundle { // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene scene: models - .get(game_assets.world.id()) + .get(game_assets.world.clone().unwrap().id()) .expect("main level should have been loaded") .scenes[0] .clone(), diff --git a/examples/bevy_gltf_blueprints/basic/src/game/in_game.rs b/examples/bevy_gltf_blueprints/basic/src/game/in_game.rs index 3af11d5..231137b 100644 --- a/examples/bevy_gltf_blueprints/basic/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/basic/src/game/in_game.rs @@ -20,7 +20,7 @@ pub fn setup_game( SceneBundle { // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene scene: models - .get(game_assets.world.id()) + .get(game_assets.world.clone().unwrap().id()) .expect("main level should have been loaded") .scenes[0] .clone(), diff --git a/examples/bevy_gltf_blueprints/basic_xpbd_physics/src/game/in_game.rs b/examples/bevy_gltf_blueprints/basic_xpbd_physics/src/game/in_game.rs index 3fa60da..6853655 100644 --- a/examples/bevy_gltf_blueprints/basic_xpbd_physics/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/basic_xpbd_physics/src/game/in_game.rs @@ -20,7 +20,7 @@ pub fn setup_game( SceneBundle { // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene scene: models - .get(game_assets.world.id()) + .get(game_assets.world.clone().unwrap().id()) .expect("main level should have been loaded") .scenes[0] .clone(), diff --git a/examples/bevy_gltf_blueprints/materials/src/game/in_game.rs b/examples/bevy_gltf_blueprints/materials/src/game/in_game.rs index e5fc8b8..6bdf1d9 100644 --- a/examples/bevy_gltf_blueprints/materials/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/materials/src/game/in_game.rs @@ -21,7 +21,7 @@ pub fn setup_game( SceneBundle { // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene scene: models - .get(game_assets.world.id()) + .get(game_assets.world.clone().unwrap().id()) .expect("main level should have been loaded") .scenes[0] .clone(), diff --git a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/in_game.rs b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/in_game.rs index 75be98a..11ee36d 100644 --- a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/in_game.rs +++ b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/in_game.rs @@ -21,7 +21,7 @@ pub fn setup_game( SceneBundle { // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene scene: models - .get(game_assets.world.id()) + .get(game_assets.world.clone().unwrap().id()) .expect("main level should have been loaded") .scenes[0] .clone(), diff --git a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/level_transitions.rs b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/level_transitions.rs index 41d5c90..4dcb69c 100644 --- a/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/level_transitions.rs +++ b/examples/bevy_gltf_blueprints/multiple_levels_multiple_blendfiles/src/game/level_transitions.rs @@ -76,7 +76,7 @@ pub fn trigger_level_transition( } else if target_level == "Level2" { level = game_assets.level2.clone().unwrap(); } else { - level = game_assets.world.clone(); + level = game_assets.world.clone().unwrap(); } info!("spawning new level"); commands.spawn(( diff --git a/examples/bevy_registry_export/basic/src/game/in_game.rs b/examples/bevy_registry_export/basic/src/game/in_game.rs index 75be98a..11ee36d 100644 --- a/examples/bevy_registry_export/basic/src/game/in_game.rs +++ b/examples/bevy_registry_export/basic/src/game/in_game.rs @@ -21,7 +21,7 @@ pub fn setup_game( SceneBundle { // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene scene: models - .get(game_assets.world.id()) + .get(game_assets.world.clone().unwrap().id()) .expect("main level should have been loaded") .scenes[0] .clone(), diff --git a/examples/common/src/assets/assets_game.rs b/examples/common/src/assets/assets_game.rs index 988e964..5bb5986 100644 --- a/examples/common/src/assets/assets_game.rs +++ b/examples/common/src/assets/assets_game.rs @@ -5,8 +5,8 @@ use bevy_asset_loader::prelude::*; #[derive(AssetCollection, Resource)] pub struct GameAssets { - #[asset(key = "world")] - pub world: Handle, + #[asset(key = "world", optional)] + pub world: Option>, #[asset(key = "world_dynamic", optional)] pub world_dynamic: Option>, @@ -16,8 +16,8 @@ pub struct GameAssets { #[asset(key = "level2", optional)] pub level2: Option>, - #[asset(key = "models", collection(typed, mapped))] - pub models: HashMap>, + #[asset(key = "models", collection(typed, mapped), optional)] + pub models: Option>>, #[asset(key = "materials", collection(typed, mapped), optional)] pub materials: Option>>, diff --git a/testing/bevy_example/assets/assets_game.assets.ron b/testing/bevy_example/assets/assets_game.assets.ron index 5b1e969..40825c0 100644 --- a/testing/bevy_example/assets/assets_game.assets.ron +++ b/testing/bevy_example/assets/assets_game.assets.ron @@ -1,6 +1,6 @@ ({ - "world":File (path: "models/World.glb"), + /*"world":File (path: "models/World.glb"), "models": Folder ( path: "models/library", - ), + ),*/ }) \ No newline at end of file diff --git a/testing/bevy_example/assets/registry.json b/testing/bevy_example/assets/registry.json index 65dea5f..33887c3 100644 --- a/testing/bevy_example/assets/registry.json +++ b/testing/bevy_example/assets/registry.json @@ -3576,6 +3576,22 @@ "type": "array", "typeInfo": "TupleStruct" }, + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintsList": { + "isComponent": true, + "isResource": false, + "items": false, + "prefixItems": [ + { + "type": { + "$ref": "#/$defs/bevy_utils::hashbrown::HashMap, 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": { "additionalProperties": false, "isComponent": true, @@ -10834,6 +10850,19 @@ "type": "object", "typeInfo": "Value" }, + "bevy_utils::hashbrown::HashMap, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>": { + "additionalProperties": { + "type": { + "$ref": "#/$defs/alloc::vec::Vec" + } + }, + "isComponent": false, + "isResource": false, + "short_name": "HashMap, DefaultHashBuilder>", + "title": "bevy_utils::hashbrown::HashMap, bevy_utils::hashbrown::hash_map::DefaultHashBuilder>", + "type": "object", + "typeInfo": "Map" + }, "bevy_utils::smallvec::SmallVec<[bevy_ecs::entity::Entity; 8]>": { "isComponent": false, "isResource": false, diff --git a/testing/bevy_example/src/core/mod.rs b/testing/bevy_example/src/core/mod.rs index 1ef7f3e..339ea40 100644 --- a/testing/bevy_example/src/core/mod.rs +++ b/testing/bevy_example/src/core/mod.rs @@ -11,6 +11,7 @@ impl Plugin for CorePlugin { legacy_mode: false, library_folder: "models/library".into(), format: GltfFormat::GLB, + material_library: true, aabbs: true, ..Default::default() }, diff --git a/testing/bevy_example/src/game/in_game.rs b/testing/bevy_example/src/game/in_game.rs index 75be98a..a5cda69 100644 --- a/testing/bevy_example/src/game/in_game.rs +++ b/testing/bevy_example/src/game/in_game.rs @@ -1,37 +1,25 @@ use bevy::prelude::*; 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 rand::Rng; pub fn setup_game( mut commands: Commands, - game_assets: Res, - models: Res>, + asset_server: Res, mut next_game_state: ResMut>, ) { - commands.insert_resource(AmbientLight { - color: Color::WHITE, - brightness: 0.2, - }); // here we actually spawn our game world/level - commands.spawn(( SceneBundle { - // note: because of this issue https://github.com/bevyengine/bevy/issues/10436, "world" is now a gltf file instead of a scene - scene: models - .get(game_assets.world.id()) - .expect("main level should have been loaded") - .scenes[0] - .clone(), + scene: asset_server.load("models/World.glb#Scene0"), ..default() }, bevy::prelude::Name::from("world"), GameWorldTag, InAppRunning, )); - next_game_state.set(GameState::InGame) } diff --git a/testing/bevy_example/src/game/mod.rs b/testing/bevy_example/src/game/mod.rs index 99b9b3f..973140b 100644 --- a/testing/bevy_example/src/game/mod.rs +++ b/testing/bevy_example/src/game/mod.rs @@ -4,7 +4,7 @@ use std::{ time::Duration, }; -use bevy_gltf_blueprints::{AnimationPlayerLink, BlueprintName}; +use bevy_gltf_blueprints::{AnimationPlayerLink, BlueprintName, BlueprintsList}; pub use in_game::*; use bevy::{ @@ -22,7 +22,8 @@ fn start_game(mut next_app_state: ResMut>) { // 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 a blueprints_list +#[allow(clippy::too_many_arguments)] fn validate_export( parents: Query<&Parent>, children: Query<&Children>, @@ -31,6 +32,8 @@ fn validate_export( animation_player_links: Query<(Entity, &AnimationPlayerLink)>, exported_cylinder: Query<(Entity, &Name, &UnitTest, &TupleTestF32)>, empties_candidates: Query<(Entity, &Name, &GlobalTransform)>, + + blueprints_list: Query<(Entity, &BlueprintsList)>, ) { 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( "bevy_diagnostics.json", format!( - "{{ \"animations\": {}, \"cylinder_found\": {} , \"nested_blueprint_found\": {}, \"empty_found\": {} }}", - animations_found, 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, blueprints_list_found ), ) .expect("Unable to write file"); @@ -97,9 +102,9 @@ impl Plugin for GamePlugin { fn build(&self, app: &mut App) { app.add_systems(Update, (spawn_test).run_if(in_state(GameState::InGame))) .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::AppRunning), setup_game) + .add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once .add_systems( Update, exit_game.run_if(on_timer(Duration::from_secs_f32(0.5))), diff --git a/tools/bevy_components/tests/expected_component_values.py b/tools/bevy_components/tests/expected_component_values.py index 7babda6..f7969ac 100644 --- a/tools/bevy_components/tests/expected_component_values.py +++ b/tools/bevy_components/tests/expected_component_values.py @@ -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))', 'BlueprintName': '(" ")', + 'BlueprintsList': '("")', 'BorderColor': '(Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0))', 'Button': '()', '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.6534725427627563))', 'BlueprintName': '("sbnpsago")', + 'BlueprintsList': '("")', 'BorderColor': '(Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, ' 'alpha:0.20609822869300842))', 'Button': '()', diff --git a/tools/bevy_components/tests/test_components.py b/tools/bevy_components/tests/test_components.py index e30a9a0..42e5656 100644 --- a/tools/bevy_components/tests/test_components.py +++ b/tools/bevy_components/tests/test_components.py @@ -48,12 +48,12 @@ def test_components_should_generate_correct_custom_properties(setup_data): except Exception as error: errors.append(error) - '''pp = pprint.PrettyPrinter(depth=14, width=120) + pp = pprint.PrettyPrinter(depth=14, width=120) print("CUSTOM PROPERTY VALUES") - pp.pprint(custom_property_values)''' + pp.pprint(custom_property_values) 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): @@ -105,7 +105,7 @@ def test_components_should_generate_correct_custom_properties_with_randomized_va print("error_components", error_components) 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): 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): print("ERROR", error, failing_components[index]) assert len(errors) == 0 - assert len(added_components) == 158 + assert len(added_components) == 159 def test_remove_components(setup_data): diff --git a/tools/gltf_auto_export/__init__.py b/tools/gltf_auto_export/__init__.py index 54c00bb..aa8f9ea 100644 --- a/tools/gltf_auto_export/__init__.py +++ b/tools/gltf_auto_export/__init__.py @@ -1,7 +1,7 @@ bl_info = { "name": "gltf_auto_export", "author": "kaosigh", - "version": (0, 15, 0), + "version": (0, 16, 0), "blender": (3, 4, 0), "location": "File > Import-Export", "description": "glTF/glb auto-export", diff --git a/tools/gltf_auto_export/auto_export/export_main_scenes.py b/tools/gltf_auto_export/auto_export/export_main_scenes.py index 8bdb018..7df4025 100644 --- a/tools/gltf_auto_export/auto_export/export_main_scenes.py +++ b/tools/gltf_auto_export/auto_export/export_main_scenes.py @@ -4,7 +4,7 @@ import bpy from ..helpers.generate_and_export import generate_and_export from .export_gltf import (generate_gltf_export_preferences, export_gltf) 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 @@ -16,6 +16,7 @@ def export_main_scene(scene, folder_path, addon_prefs, library_collections): gltf_export_preferences = generate_gltf_export_preferences(addon_prefs) export_output_folder = getattr(addon_prefs,"export_output_folder") 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") 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 not legacy_mode: + inject_blueprints_list_into_main_scene(scene) + if export_separate_dynamic_and_static_objects: #print("SPLIT STATIC AND DYNAMIC") # 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") export_gltf(gltf_output_path, export_settings) + if not legacy_mode: + remove_blueprints_list_from_main_scene(scene) + diff --git a/tools/gltf_auto_export/helpers/helpers_collections.py b/tools/gltf_auto_export/helpers/helpers_collections.py index edb3214..219add2 100644 --- a/tools/gltf_auto_export/helpers/helpers_collections.py +++ b/tools/gltf_auto_export/helpers/helpers_collections.py @@ -36,16 +36,23 @@ def get_marked_collections(scene, addon_prefs): return (collection_names, marked_collections) # 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() used_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) #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) + #print("sub", collection) node_name = collection.name children_per_collection[node_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) if object.instance_type == 'COLLECTION' : # and not 'Flatten' in object: 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) if len(list(sub_names)) > 0: children_per_collection[node_name] += (list(sub_names)) #print(" found sub collection in use", object.name, object.instance_collection) - if not collection_name in collection_names: collection_names.add(collection_name) 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])) -class Node : +class CollectionNode : def __init__(self, name="", parent=None): self.name = name self.children = [] @@ -93,7 +103,7 @@ def get_exportable_collections(main_scenes, library_scenes, addon_prefs): all_collections = [] all_collection_names = [] - root_node = Node() + root_node = CollectionNode() root_node.name = "root" children_per_collection = {} diff --git a/tools/gltf_auto_export/helpers/helpers_scenes.py b/tools/gltf_auto_export/helpers/helpers_scenes.py index e5b4dfb..55009c9 100644 --- a/tools/gltf_auto_export/helpers/helpers_scenes.py +++ b/tools/gltf_auto_export/helpers/helpers_scenes.py @@ -1,5 +1,6 @@ +import json 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) @@ -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['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 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 @@ -149,3 +161,45 @@ def get_scenes(addon_prefs): 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) diff --git a/tools/gltf_auto_export/tests/test_basic.py b/tools/gltf_auto_export/tests/test_basic.py index 0ebe949..7d01954 100644 --- a/tools/gltf_auto_export/tests/test_basic.py +++ b/tools/gltf_auto_export/tests/test_basic.py @@ -19,6 +19,8 @@ def setup_data(request): def finalizer(): print("\nPerforming teardown...") + get_orphan_data() + if os.path.exists(models_path): shutil.rmtree(models_path) @@ -33,9 +35,14 @@ def setup_data(request): 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): auto_export_operator = bpy.ops.export_scenes.auto_gltf - + # first, configure things # we use the global settings for that export_props = { @@ -57,7 +64,6 @@ def test_export_do_not_export_blueprints(setup_data): def test_export_custom_blueprints_path(setup_data): auto_export_operator = bpy.ops.export_scenes.auto_gltf - # first, configure things # we use the global settings for that 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_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 + diff --git a/tools/gltf_auto_export/tests/test_bevy_integration.py b/tools/gltf_auto_export/tests/test_bevy_integration.py index afd5255..e45114c 100644 --- a/tools/gltf_auto_export/tests/test_bevy_integration.py +++ b/tools/gltf_auto_export/tests/test_bevy_integration.py @@ -16,18 +16,16 @@ def setup_data(request): root_path = "../../testing/bevy_example" assets_root_path = os.path.join(root_path, "assets") 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") print("\nPerforming teardown...") if os.path.exists(models_path): shutil.rmtree(models_path) - """if os.path.exists(materials_path): + if os.path.exists(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") if os.path.exists(diagnostics_file_path): os.remove(diagnostics_file_path) @@ -75,7 +73,8 @@ def test_export_complex(setup_data): export_scene_settings=True, export_blueprints=True, export_legacy_mode=False, - export_animations=True + export_animations=True, + export_materials_library=True ) # blueprint1 => has an instance, got changed, 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 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", "Blueprint2.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", "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 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 - 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) print("RETURN CODE OF BEVY APP", return_code) assert return_code == 0 @@ -110,6 +109,7 @@ def test_export_complex(setup_data): assert diagnostics["animations"] == True assert diagnostics["cylinder_found"] == True assert diagnostics["empty_found"] == True + assert diagnostics["blueprints_list_found"] == True # last but not least, do a visual compare screenshot_expected_path = os.path.join(root_path, "expected_screenshot.png")