feat(Blenvy): big overhaul, cleanup & fixed to the assets system

* now the blender side exports .meta.ron files in addition to the gltf files
 * these ron files contain the list of assets that are then preloaded on the Bevy side
 * removed the double loading of gltf files on the Bevy side, replaced with use of the new metadata/asset files
 * added bevy_common_assets/ ron as dependency for the file loading
 * big cleanup & partial restructure of the spawning steps
 * fixed premature removal of the BlueprintAssetsLoadState component that was leading to missing material gltf files in
setups withouth hot reload
 * added OriginalVisibility component & logic to correctly reset the visibility of entities to what they where before
the blueprint spawning
 * fixed a few not so visible issues with some components staying around after the blueprint instance has become "ready"
 * moved a number of component insertions to the new "get the assets list from the meta file"
 * also, loading speed feels faster ! (thanks to now loading the gltf files only once)
 * minor various tweaks & cleanups
This commit is contained in:
kaosat.dev 2024-07-29 22:32:23 +02:00
parent 0b038de584
commit ee873b06f1
13 changed files with 295 additions and 180 deletions

View File

@ -339,5 +339,6 @@ Bevy Side:
- [ ] solving problems with scene renames - [ ] solving problems with scene renames
- [ ] the ability to map external TEXT files to data in BLender (git-able, hand editable) - [ ] the ability to map external TEXT files to data in BLender (git-able, hand editable)
- [x] make aabbs calculation non configurable, getting rid of the last setting (for now) - [x] make aabbs calculation non configurable, getting rid of the last setting (for now)
- [ ] add information & screenshots about adding assets to the Blender add-on docs
clear && pytest -svv --blender-template ../../testing/bevy_example/art/testing_library.blend --blender-executable /home/ckaos/tools/blender/blender-4.1.0-linux-x64/blender tests/test_bevy_integration_prepare.py && pytest -svv --blender-executable /home/ckaos/tools/blender/blender-4.1.0-linux-x64/blender tests/test_bevy_integration.py clear && pytest -svv --blender-template ../../testing/bevy_example/art/testing_library.blend --blender-executable /home/ckaos/tools/blender/blender-4.1.0-linux-x64/blender tests/test_bevy_integration_prepare.py && pytest -svv --blender-executable /home/ckaos/tools/blender/blender-4.1.0-linux-x64/blender tests/test_bevy_integration.py

View File

@ -18,19 +18,7 @@ bevy = { version = "0.14", default-features = false, features = ["bevy_asset", "
serde = "1.0.188" serde = "1.0.188"
ron = "0.8.1" ron = "0.8.1"
serde_json = "1.0.108" serde_json = "1.0.108"
gltf = { version = "1.4.0", default-features = false, features = [ bevy_common_assets = {version = "0.11", features = ["ron"]}
"KHR_lights_punctual",
"KHR_materials_transmission",
"KHR_materials_ior",
"KHR_materials_volume",
"KHR_materials_unlit",
"KHR_materials_emissive_strength",
"KHR_texture_transform",
"extras",
"extensions",
"names",
"utils",
] }
[dev-dependencies] [dev-dependencies]

View File

@ -29,7 +29,7 @@ pub fn compute_scene_aabbs(
} else { } else {
let aabb = compute_descendant_aabb(root_entity, &children, &existing_aabbs); let aabb = compute_descendant_aabb(root_entity, &children, &existing_aabbs);
blenvy_config.aabb_cache.insert(name.to_string(), aabb); blenvy_config.aabb_cache.insert(name.to_string(), aabb);
info!("generating aabb for {:?}", name); info!("Step 7: generating aabb for {:?}", name);
commands commands
.entity(root_entity) .entity(root_entity)
.insert(aabb) .insert(aabb)

View File

@ -74,3 +74,26 @@ impl Default for BlueprintAssetsLoadState {
} }
} }
} }
// for preloading asset files
#[derive(serde::Deserialize, bevy::asset::Asset, bevy::reflect::TypePath, Debug)]
pub(crate) struct File{
pub(crate) path: String,
}
#[derive(serde::Deserialize, bevy::asset::Asset, bevy::reflect::TypePath, Debug)]
pub(crate) struct BlueprintPreloadAssets{
pub(crate) assets: Vec<(String, File)>
}
#[derive(Component)]
pub(crate) struct BlueprintMetaHandle(pub Handle<BlueprintPreloadAssets>);
/// flag component, usually added when a blueprint is loaded
#[derive(Component)]
pub(crate) struct BlueprintMetaLoaded;
#[derive(Component)]
pub(crate) struct BlueprintMetaLoading;

View File

@ -57,12 +57,17 @@ pub(crate) fn inject_materials(
material_found = Some(material); material_found = Some(material);
} else { } else {
let model_handle: Handle<Gltf> = asset_server.load(material_info.path.clone()); // FIXME: kinda weird now let model_handle: Handle<Gltf> = asset_server.load(material_info.path.clone()); // FIXME: kinda weird now
let mat_gltf = assets_gltf.get(model_handle.id()).unwrap_or_else(|| { let Some(mat_gltf) = assets_gltf.get(model_handle.id()) else {
warn!("materials file {} should have been preloaded skipping",material_info.path);
continue;
};
/*let mat_gltf = assets_gltf.get(model_handle.id()).unwrap_or_else(|| {
panic!( panic!(
"materials file {} should have been preloaded", "materials file {} should have been preloaded",
material_info.path material_info.path
) )
}); });*/
if mat_gltf if mat_gltf
.named_materials .named_materials
.contains_key(&material_info.name as &str) .contains_key(&material_info.name as &str)
@ -79,6 +84,7 @@ pub(crate) fn inject_materials(
} }
if let Some(material) = material_found { if let Some(material) = material_found {
info!("Step 6: injecting/replacing materials");
for (child_index, child) in children.iter().enumerate() { for (child_index, child) in children.iter().enumerate() {
if child_index == material_index { if child_index == material_index {
if with_materials_and_meshes.contains(*child) { if with_materials_and_meshes.contains(*child) {

View File

@ -1,4 +1,5 @@
pub mod spawn_from_blueprints; pub mod spawn_from_blueprints;
use bevy_common_assets::ron::RonAssetPlugin;
pub use spawn_from_blueprints::*; pub use spawn_from_blueprints::*;
pub mod animation; pub mod animation;
@ -110,8 +111,10 @@ impl Plugin for BlueprintsPlugin {
.register_type::<Vec<String>>() .register_type::<Vec<String>>()
.register_type::<BlueprintAssets>() .register_type::<BlueprintAssets>()
.register_type::<HashMap<String, Vec<String>>>() .register_type::<HashMap<String, Vec<String>>>()
.init_asset::<RawGltfAsset>() //.init_asset::<RawGltfAsset>()
.init_asset_loader::<RawGltfAssetLoader>() //.init_asset_loader::<RawGltfAssetLoader>()
.add_plugins(RonAssetPlugin::<BlueprintPreloadAssets>::new(&["meta.ron"]),)
.configure_sets( .configure_sets(
Update, Update,
(GltfBlueprintsSet::Spawn, GltfBlueprintsSet::AfterSpawn) (GltfBlueprintsSet::Spawn, GltfBlueprintsSet::AfterSpawn)
@ -121,7 +124,8 @@ impl Plugin for BlueprintsPlugin {
.add_systems( .add_systems(
Update, Update,
( (
load_raw_gltf, blueprints_prepare_metadata_file_for_spawn,
blueprints_check_assets_metadata_files_loading,
blueprints_prepare_spawn, blueprints_prepare_spawn,
blueprints_check_assets_loading, blueprints_check_assets_loading,
blueprints_assets_loaded, blueprints_assets_loaded,

View File

@ -1,21 +1,17 @@
use std::path::Path; use std::path::Path;
use bevy::{ use bevy::{
asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext},
gltf::Gltf, gltf::Gltf,
prelude::*, prelude::*,
scene::SceneInstance, scene::SceneInstance,
utils::hashbrown::HashMap, utils::{hashbrown::HashMap, warn},
}; };
use serde_json::Value;
use crate::{ use crate::{
AnimationInfos, AssetLoadTracker, AssetToBlueprintInstancesMapper, BlueprintAnimationInfosLink, AnimationInfos, AssetLoadTracker, AssetToBlueprintInstancesMapper, BlueprintAnimationInfosLink, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintAssets, BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintMetaLoading, BlueprintAssetsNotLoaded, BlueprintPreloadAssets, InstanceAnimationInfosLink, InstanceAnimationPlayerLink, InstanceAnimations, WatchingForChanges
BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintAssets, BlueprintAssetsLoadState,
BlueprintAssetsLoaded, BlueprintAssetsNotLoaded, InstanceAnimationInfosLink,
InstanceAnimationPlayerLink, InstanceAnimations, WatchingForChanges,
}; };
/// this is a flag component for our levels/game world /// this is a flag component for our levels/game world
#[derive(Component)] #[derive(Component)]
pub struct GameWorldTag; pub struct GameWorldTag;
@ -72,6 +68,13 @@ pub(crate) struct OriginalChildren(pub Vec<Entity>);
/// as it would first become invisible before re-appearing again /// as it would first become invisible before re-appearing again
pub struct HideUntilReady; pub struct HideUntilReady;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
/// Companion to the `HideUntilReady` component: this stores the visibility of the entity before the blueprint was inserted into it
pub(crate) struct OriginalVisibility(Visibility);
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
/// marker component, gets added to all children of a currently spawning blueprint instance, can be usefull to avoid manipulating still in progress entities /// marker component, gets added to all children of a currently spawning blueprint instance, can be usefull to avoid manipulating still in progress entities
@ -103,8 +106,6 @@ pub enum BlueprintEvent {
/// component gets added when a blueprint starts spawning, removed when spawning is completely done /// component gets added when a blueprint starts spawning, removed when spawning is completely done
pub struct BlueprintSpawning; pub struct BlueprintSpawning;
use gltf::Gltf as RawGltf;
/* /*
Overview of the Blueprint Spawning process Overview of the Blueprint Spawning process
- Blueprint Load Assets - Blueprint Load Assets
@ -121,62 +122,139 @@ Overview of the Blueprint Spawning process
=> distinguish between blueprint instances inside blueprint instances vs blueprint instances inside blueprints ?? => distinguish between blueprint instances inside blueprint instances vs blueprint instances inside blueprints ??
*/ */
#[derive(Asset, TypePath, Debug)] pub(super) fn blueprints_prepare_metadata_file_for_spawn(
pub struct RawGltfAsset(pub RawGltf); blueprint_instances_to_spawn: Query<(
Entity,
&BlueprintInfo,
Option<&Name>,
Option<&Parent>,
Option<&HideUntilReady>,
Option<&Visibility>,
Option<&AddToGameWorld>,
#[derive(Default)] ), (Without<BlueprintMetaLoading>, Without<BlueprintSpawning>, Without<BlueprintInstanceReady>)>,
pub(super) struct RawGltfAssetLoader; mut game_world: Query<Entity, With<GameWorldTag>>,
impl AssetLoader for RawGltfAssetLoader {
type Asset = RawGltfAsset;
type Settings = ();
type Error = gltf::Error;
async fn load<'a>(
&'a self,
reader: &'a mut Reader<'_>,
_settings: &'a (),
_load_context: &'a mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let gltf = RawGltf::from_slice_without_validation(&bytes)?;
Ok(RawGltfAsset(gltf))
}
}
#[derive(Debug, Component, Deref, DerefMut)]
#[component(storage = "SparseSet")]
pub(super) struct AssociatedRawGltfHandle(Handle<RawGltfAsset>);
pub(super) fn load_raw_gltf(
blueprint_instances_to_spawn: Query<(Entity, &BlueprintInfo), Added<SpawnBlueprint>>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, blueprint_info) in blueprint_instances_to_spawn.iter() { for (entity, blueprint_info, entity_name, original_parent, hide_until_ready, original_visibility, add_to_world) in blueprint_instances_to_spawn.iter() {
let gltf_handle: Handle<RawGltfAsset> = asset_server.load(&blueprint_info.path); // get path to assets / metadata file
commands info!("Step 1: spawn request detected: loading metadata file for {:?}", blueprint_info);
.entity(entity) let blueprint_path = blueprint_info.path.clone();
.insert(AssociatedRawGltfHandle(gltf_handle)); let metadata_path = blueprint_path.replace(".glb", ".meta.ron").replace(".gltf", ".meta.ron"); // FIXME: horrible
let mut asset_infos: Vec<AssetLoadTracker> = vec![];
//let foo_handle:Handle<BlueprintPreloadAssets> = asset_server.load(metadata_path);
let untyped_handle = asset_server.load_untyped(metadata_path.clone());
let asset_id = untyped_handle.id();
asset_infos.push(AssetLoadTracker {
name: metadata_path.clone(),
path: metadata_path.clone(),
id: asset_id,
loaded: false,
handle: untyped_handle.clone(),
});
// add the blueprint spawning marker & co
commands.entity(entity).insert((
BlueprintAssetsLoadState {
all_loaded: false,
asset_infos,
..Default::default()
},
BlueprintMetaLoading,
BlueprintSpawning
));
// if the entity has no name, add one based on the blueprint's
if entity_name.is_none(){
commands
.entity(entity)
.insert(bevy::prelude::Name::from(blueprint_info.name.clone()));
}
if original_parent.is_none() {
// only allow hiding until ready when the entity does not have a parent (?)
if hide_until_ready.is_some() {
// if there is already a set visibility, save it for later
if let Some(original_visibility) = original_visibility {
commands.entity(entity).insert(OriginalVisibility(*original_visibility));
}
// & now hide the instance until it is ready
commands.entity(entity)
.insert(Visibility::Hidden);
}
// only allow automatically adding a newly spawned blueprint instance to the "world", if the entity does not have a parent
if add_to_world.is_some() {
let world = game_world
.get_single_mut()
.expect("there should be a game world present");
commands.entity(world).add_child(entity);
}
}
} }
} }
// TODO: merge with other asset loading checker ?
pub(crate) fn blueprints_check_assets_metadata_files_loading(
mut blueprint_assets_to_load: Query<
(Entity, &BlueprintInfo, &mut BlueprintAssetsLoadState),
With<BlueprintMetaLoading>,
>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
for (entity, blueprint_info, 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);
let mut failed = false;
if let bevy::asset::LoadState::Failed(_) = asset_server.load_state(asset_id) {
failed = true
}
tracker.loaded = loaded || failed;
if loaded || failed {
loaded_amount += 1;
} else {
all_loaded = false;
}
if all_loaded {
commands.entity(entity).insert(BlueprintMetaHandle(asset_server.load(tracker.path.clone()))).remove::<BlueprintAssetsLoadState>();
break;
}
}
let progress: f32 = loaded_amount as f32 / total as f32;
assets_to_load.progress = progress;
// println!("LOADING: in progress for ALL assets of {:?} (instance of {}): {} ",entity_name, blueprint_info.path, progress * 100.0);
}
}
pub(super) fn blueprints_prepare_spawn( pub(super) fn blueprints_prepare_spawn(
blueprint_instances_to_spawn: Query<(Entity, &BlueprintInfo, &AssociatedRawGltfHandle)>, blueprint_instances_to_spawn: Query<(Entity, &BlueprintInfo, &BlueprintMetaHandle, Option<&Name>), Added<BlueprintMetaHandle>>,
mut commands: Commands, mut commands: Commands,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
// for hot reload // for hot reload
watching_for_changes: Res<WatchingForChanges>, watching_for_changes: Res<WatchingForChanges>,
mut assets_to_blueprint_instances: ResMut<AssetToBlueprintInstancesMapper>, mut assets_to_blueprint_instances: ResMut<AssetToBlueprintInstancesMapper>,
raw_gltf_assets: Res<Assets<RawGltfAsset>>,
// for debug // for debug
// all_names: Query<&Name> // all_names: Query<&Name>
blueprint_metas: Res<Assets<BlueprintPreloadAssets>>,
) { ) {
for (entity, blueprint_info, raw_gltf_handle) in blueprint_instances_to_spawn.iter() { for (entity, blueprint_info, blueprint_meta_handle, entity_name) in blueprint_instances_to_spawn.iter() {
info!( info!(
"BLUEPRINT: to spawn detected: {:?} path:{:?}", "Step 2: metadata loaded: loading assets for {:?}",
blueprint_info.name, blueprint_info.path blueprint_info,
); );
// we add the asset of the blueprint itself // we add the asset of the blueprint itself
// TODO: add detection of already loaded data // TODO: add detection of already loaded data
@ -197,66 +275,56 @@ pub(super) fn blueprints_prepare_spawn(
// and we also add all its assets // and we also add all its assets
/* prefetch attempt */ /* prefetch attempt */
let Some(RawGltfAsset(gltf)) = raw_gltf_assets.get(&raw_gltf_handle.0) else { if let Some(blenvy_metadata) = blueprint_metas.get(&blueprint_meta_handle.0) {
continue; for asset in blenvy_metadata.assets.iter() {
}; let asset_path = asset.1.path.clone();
for scene in gltf.scenes() { let asset_name = asset.0.clone();
if let Some(scene_extras) = scene.extras().clone() {
let lookup: HashMap<String, Value> =
serde_json::from_str(scene_extras.get()).unwrap();
if lookup.contains_key("BlueprintAssets") {
let assets_raw = &lookup["BlueprintAssets"];
//println!("ASSETS RAW {}", assets_raw);
let all_assets: BlueprintAssets =
ron::from_str(assets_raw.as_str().unwrap()).unwrap();
// println!("all_assets {:?}", all_assets);
for asset in all_assets.assets.iter() { let untyped_handle = asset_server.load_untyped(&asset_path);
let untyped_handle = asset_server.load_untyped(&asset.path); let asset_id = untyped_handle.id();
let asset_id = untyped_handle.id(); let loaded = asset_server.is_loaded_with_dependencies(asset_id);
let loaded = asset_server.is_loaded_with_dependencies(asset_id); if !loaded {
if !loaded { asset_infos.push(AssetLoadTracker {
asset_infos.push(AssetLoadTracker { name: asset_name.clone(),
name: asset.name.clone(), path: asset_path.clone(),
path: asset.path.clone(), id: asset_id,
id: asset_id, loaded: false,
loaded: false, handle: untyped_handle.clone(),
handle: untyped_handle.clone(), });
}); }
}
// FIXME: dang, too early, asset server has not yet started loading yet // FIXME: dang, too early, asset server has not yet started loading yet
// let path_id = asset_server.get_path_id(&asset.path).expect("we should have alread checked for this asset"); // let path_id = asset_server.get_path_id(&asset.path).expect("we should have alread checked for this asset");
let path_id = asset.path.clone(); let path_id = asset_path.clone();
// Only do this if hot reload is enabled // Only do this if hot reload is enabled
if watching_for_changes.0 { if watching_for_changes.0 {
if !assets_to_blueprint_instances if !assets_to_blueprint_instances
.untyped_id_to_blueprint_entity_ids .untyped_id_to_blueprint_entity_ids
.contains_key(&path_id) .contains_key(&path_id)
{ {
assets_to_blueprint_instances assets_to_blueprint_instances
.untyped_id_to_blueprint_entity_ids .untyped_id_to_blueprint_entity_ids
.insert(path_id.clone(), vec![]); .insert(path_id.clone(), vec![]);
} }
// only insert if not already present in mapping // only insert if not already present in mapping
if !assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids if !assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids
[&path_id] [&path_id]
.contains(&entity) .contains(&entity)
{ {
// println!("adding mapping between {} and entity {:?}", path_id, all_names.get(entity)); // println!("adding mapping between {} and entity {:?}", path_id, all_names.get(entity));
assets_to_blueprint_instances assets_to_blueprint_instances
.untyped_id_to_blueprint_entity_ids .untyped_id_to_blueprint_entity_ids
.get_mut(&path_id) .get_mut(&path_id)
.unwrap() .unwrap()
.push(entity); .push(entity);
}
}
}
} }
} }
} }
}else {
warn!("no asset metadata found for {}, please make sure to generate them using the Blender add-on, or preload your assets manually", blueprint_info.path);
}
// Only do this if hot reload is enabled // Only do this if hot reload is enabled
// TODO: should this be added to the list of "all assets" on the blender side instead // TODO: should this be added to the list of "all assets" on the blender side instead
@ -299,40 +367,35 @@ pub(super) fn blueprints_prepare_spawn(
commands.entity(entity).insert(BlueprintAssetsLoaded); commands.entity(entity).insert(BlueprintAssetsLoaded);
} }
// if the entity has no name, add one based on the blueprint's commands.entity(entity)
commands .remove::<BlueprintMetaLoading>()
.entity(entity) .remove::<BlueprintMetaHandle>();
.insert(bevy::prelude::Name::from(blueprint_info.name.clone()));
// add the blueprint spawning marker
commands
.entity(entity)
.insert(BlueprintSpawning)
.remove::<AssociatedRawGltfHandle>();
} }
} }
/// This system tracks & updates the loading state of all blueprints assets /// This system tracks & updates the loading state of all blueprints assets
pub(crate) fn blueprints_check_assets_loading( pub(crate) fn blueprints_check_assets_loading(
mut blueprint_assets_to_load: Query< mut blueprint_assets_to_load: Query<
(Entity, &BlueprintInfo, &mut BlueprintAssetsLoadState), (Entity, &BlueprintInfo, &mut BlueprintAssetsLoadState, Option<&Name>),
With<BlueprintAssetsNotLoaded>, With<BlueprintAssetsNotLoaded>,
>, >,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut commands: Commands, mut commands: Commands,
mut blueprint_events: EventWriter<BlueprintEvent>, mut blueprint_events: EventWriter<BlueprintEvent>,
// for hot reload
watching_for_changes: Res<WatchingForChanges>,
) { ) {
for (entity, blueprint_info, mut assets_to_load) in blueprint_assets_to_load.iter_mut() { for (entity, blueprint_info, mut assets_to_load, entity_name) in blueprint_assets_to_load.iter_mut() {
let mut all_loaded = true; let mut all_loaded = true;
let mut loaded_amount = 0; let mut loaded_amount = 0;
let total = assets_to_load.asset_infos.len(); let total = assets_to_load.asset_infos.len();
for tracker in assets_to_load.asset_infos.iter_mut() { for tracker in assets_to_load.asset_infos.iter_mut() {
let asset_id = tracker.id; let asset_id = tracker.id;
let loaded = asset_server.is_loaded_with_dependencies(asset_id); let loaded = asset_server.is_loaded_with_dependencies(asset_id);
if loaded {
debug!("LOADED {}", tracker.path.clone());
}
let mut failed = false; let mut failed = false;
if let bevy::asset::LoadState::Failed(_) = asset_server.load_state(asset_id) { if let bevy::asset::LoadState::Failed(_) = asset_server.load_state(asset_id) {
warn!("FAILED TO LOAD {}", tracker.path.clone());
failed = true failed = true
} }
tracker.loaded = loaded || failed; tracker.loaded = loaded || failed;
@ -344,7 +407,7 @@ pub(crate) fn blueprints_check_assets_loading(
} }
let progress: f32 = loaded_amount as f32 / total as f32; let progress: f32 = loaded_amount as f32 / total as f32;
assets_to_load.progress = progress; assets_to_load.progress = progress;
// println!("LOADING: in progress for ALL assets of {:?} (instance of {}): {} ",entity_name, blueprint_info.path, progress * 100.0); //println!("LOADING: in progress for ALL assets of {:?} (instance of {}): {} ",entity_name, blueprint_info.path, progress * 100.0);
if all_loaded { if all_loaded {
assets_to_load.all_loaded = true; assets_to_load.all_loaded = true;
@ -359,10 +422,6 @@ pub(crate) fn blueprints_check_assets_loading(
.entity(entity) .entity(entity)
.insert(BlueprintAssetsLoaded) .insert(BlueprintAssetsLoaded)
.remove::<BlueprintAssetsNotLoaded>(); .remove::<BlueprintAssetsNotLoaded>();
if !watching_for_changes.0 {
commands.entity(entity).remove::<BlueprintAssetsLoadState>(); //we REMOVE this component when in hot reload is OFF, as we
}
} }
} }
} }
@ -373,20 +432,15 @@ pub(crate) fn blueprints_assets_loaded(
Entity, Entity,
&BlueprintInfo, &BlueprintInfo,
Option<&Transform>, Option<&Transform>,
Option<&Parent>,
Option<&AddToGameWorld>,
Option<&Name>, Option<&Name>,
Option<&HideUntilReady>,
Option<&AnimationInfos>, Option<&AnimationInfos>,
), ),
( (
With<BlueprintAssetsLoaded>,
Added<BlueprintAssetsLoaded>, Added<BlueprintAssetsLoaded>,
Without<BlueprintAssetsNotLoaded>, Without<BlueprintAssetsNotLoaded>,
), ),
>, >,
all_children: Query<&Children>, all_children: Query<&Children>,
mut game_world: Query<Entity, With<GameWorldTag>>,
assets_gltf: Res<Assets<Gltf>>, assets_gltf: Res<Assets<Gltf>>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
@ -398,10 +452,7 @@ pub(crate) fn blueprints_assets_loaded(
entity, entity,
blueprint_info, blueprint_info,
transform, transform,
original_parent,
add_to_world,
name, name,
hide_until_ready,
animation_infos, animation_infos,
) in spawn_placeholders.iter() ) in spawn_placeholders.iter()
{ {
@ -411,8 +462,8 @@ pub(crate) fn blueprints_assets_loaded(
);*/ );*/
info!( info!(
"BLUEPRINT: all assets loaded, attempting to spawn blueprint SCENE {:?} for entity {:?}, id: {}", "Step 3: all assets loaded, attempting to spawn blueprint scene {:?} for entity {:?}, id: {}",
blueprint_info.name, name, entity blueprint_info, name, entity
); );
// info!("attempting to spawn {:?}", model_path); // info!("attempting to spawn {:?}", model_path);
@ -479,20 +530,7 @@ pub(crate) fn blueprints_assets_loaded(
}, },
)); ));
if original_parent.is_none() {
// only allow hiding until ready when the entity does not have a parent (?)
if hide_until_ready.is_some() {
commands.entity(entity).insert(Visibility::Hidden); // visibility:
}
// only allow automatically adding a newly spawned blueprint instance to the "world", if the entity does not have a parent
if add_to_world.is_some() {
let world = game_world
.get_single_mut()
.expect("there should be a game world present");
commands.entity(world).add_child(entity);
}
}
} }
} }
@ -536,7 +574,7 @@ pub(crate) fn blueprints_scenes_spawned(
) { ) {
for (entity, name, children, track_root) in spawned_blueprint_scene_instances.iter() { for (entity, name, children, track_root) in spawned_blueprint_scene_instances.iter() {
info!( info!(
"Done spawning blueprint scene for entity named {:?} (track root: {:?})", "Step 4: Done spawning blueprint scene for entity named {:?} (track root: {:?})",
name, track_root name, track_root
); );
let mut sub_blueprint_instances: Vec<Entity> = vec![]; let mut sub_blueprint_instances: Vec<Entity> = vec![];
@ -614,6 +652,8 @@ pub(crate) fn blueprints_scenes_spawned(
use crate::CopyComponents; use crate::CopyComponents;
use std::any::TypeId; use std::any::TypeId;
use super::{BlueprintMetaHandle, BlueprintMetaLoaded};
#[derive(Component, Reflect, Debug)] #[derive(Component, Reflect, Debug)]
#[reflect(Component)] #[reflect(Component)]
pub struct BlueprintReadyForPostProcess; pub struct BlueprintReadyForPostProcess;
@ -646,7 +686,7 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
all_names: Query<&Name>, all_names: Query<&Name>,
) { ) {
for (original, children, original_children, name, animations) in blueprint_scenes.iter() { for (original, children, original_children, name, animations) in blueprint_scenes.iter() {
info!("Cleaning up spawned scene {:?}", name); info!("Step 5: Cleaning up spawned scene {:?}", name);
if children.len() == 0 { if children.len() == 0 {
// TODO: investigate, Honestly not sure if this issue from Bevy 0.12 is still present at all anymore // TODO: investigate, Honestly not sure if this issue from Bevy 0.12 is still present at all anymore
@ -783,6 +823,7 @@ pub(crate) fn blueprints_finalize_instances(
&BlueprintInfo, &BlueprintInfo,
Option<&SubBlueprintSpawnRoot>, Option<&SubBlueprintSpawnRoot>,
Option<&HideUntilReady>, Option<&HideUntilReady>,
Option<&OriginalVisibility>,
), ),
(With<BlueprintSpawning>, With<BlueprintReadyForFinalizing>), (With<BlueprintSpawning>, With<BlueprintReadyForFinalizing>),
>, >,
@ -793,10 +834,10 @@ pub(crate) fn blueprints_finalize_instances(
mut commands: Commands, mut commands: Commands,
// all_names: Query<&Name> // all_names: Query<&Name>
) { ) {
for (entity, name, blueprint_info, parent_blueprint, hide_until_ready) in for (entity, name, blueprint_info, parent_blueprint, hide_until_ready, original_visibility) in
blueprint_instances.iter() blueprint_instances.iter()
{ {
info!("Finalizing blueprint instance {:?}", name); info!("Step 8: Finalizing blueprint instance {:?}", name);
commands commands
.entity(entity) .entity(entity)
.remove::<BlueprintReadyForFinalizing>() .remove::<BlueprintReadyForFinalizing>()
@ -804,8 +845,9 @@ pub(crate) fn blueprints_finalize_instances(
.remove::<BlueprintSpawning>() .remove::<BlueprintSpawning>()
.remove::<SpawnBlueprint>() .remove::<SpawnBlueprint>()
//.remove::<Handle<Scene>>(); // FIXME: if we delete the handle to the scene, things get despawned ! not what we want //.remove::<Handle<Scene>>(); // FIXME: if we delete the handle to the scene, things get despawned ! not what we want
//.remove::<BlueprintAssetsLoadState>(); // also clear the sub assets tracker to free up handles, perhaps just freeing up the handles and leave the rest would be better ? .remove::<BlueprintAssetsLoadState>() // also clear the sub assets tracker to free up handles, perhaps just freeing up the handles and leave the rest would be better ?
//.remove::<BlueprintAssetsLoaded>(); .remove::<BlueprintAssetsLoaded>()
.remove::<OriginalChildren>() // we do not need to keep the original children information
.insert(BlueprintInstanceReady); .insert(BlueprintInstanceReady);
// Deal with sub blueprints // Deal with sub blueprints
@ -839,12 +881,18 @@ pub(crate) fn blueprints_finalize_instances(
} }
} }
commands.entity(entity).remove::<BlueprintInstanceDisabled>();
for child in all_children.iter_descendants(entity) { for child in all_children.iter_descendants(entity) {
commands.entity(child).remove::<BlueprintInstanceDisabled>(); commands.entity(child).remove::<BlueprintInstanceDisabled>();
} }
if hide_until_ready.is_some() { if hide_until_ready.is_some() {
commands.entity(entity).insert(Visibility::Inherited); if let Some(original_visibility) = original_visibility {
commands.entity(entity).insert(original_visibility.0);
}else {
commands.entity(entity).insert(Visibility::Inherited);
}
} }
blueprint_events.send(BlueprintEvent::InstanceReady { blueprint_events.send(BlueprintEvent::InstanceReady {

View File

@ -4,8 +4,7 @@ use blenvy::*;
pub struct CorePlugin; pub struct CorePlugin;
impl Plugin for CorePlugin { impl Plugin for CorePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins((BlueprintsPlugin { app.add_plugins((BlenvyPlugin {
material_library: true,
..Default::default() ..Default::default()
},)); },));
} }

View File

@ -13,7 +13,6 @@ impl Plugin for CorePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins( app.add_plugins(
BlenvyPlugin { BlenvyPlugin {
aabbs: true,
registry_component_filter: SceneFilter::Denylist(HashSet::from([ registry_component_filter: SceneFilter::Denylist(HashSet::from([
// this is using Bevy's build in SceneFilter, you can compose what components you want to allow/deny // this is using Bevy's build in SceneFilter, you can compose what components you want to allow/deny
TypeId::of::<ComponentAToFilterOut>(), TypeId::of::<ComponentAToFilterOut>(),

View File

@ -1,12 +1,10 @@
import os import os
import bpy import bpy
from blenvy.assets.assets_scan import get_blueprint_asset_tree
from blenvy.assets.generate_asset_file import write_ron_assets_file
from ....materials.materials_helpers import add_material_info_to_objects, get_blueprint_materials from ....materials.materials_helpers import add_material_info_to_objects, get_blueprint_materials
from ..constants import TEMPSCENE_PREFIX from ..constants import TEMPSCENE_PREFIX
from ..common.generate_temporary_scene_and_export import generate_temporary_scene_and_export, copy_hollowed_collection_into, clear_hollow_scene from ..common.generate_temporary_scene_and_export import generate_temporary_scene_and_export, copy_hollowed_collection_into, clear_hollow_scene
from ..common.export_gltf import generate_gltf_export_settings from ..common.export_gltf import generate_gltf_export_settings
from ..utils import upsert_blueprint_assets from ..utils import upsert_blueprint_assets, write_blueprint_metadata_file
def export_blueprints(blueprints, settings, blueprints_data): def export_blueprints(blueprints, settings, blueprints_data):
blueprints_path_full = getattr(settings, "blueprints_path_full") blueprints_path_full = getattr(settings, "blueprints_path_full")
@ -33,6 +31,9 @@ def export_blueprints(blueprints, settings, blueprints_data):
(_, materials_per_object) = get_blueprint_materials(blueprint) (_, materials_per_object) = get_blueprint_materials(blueprint)
add_material_info_to_objects(materials_per_object, settings) add_material_info_to_objects(materials_per_object, settings)
write_blueprint_metadata_file(blueprint=blueprint, blueprints_data=blueprints_data, settings=settings)
# do the actual export # do the actual export
generate_temporary_scene_and_export( generate_temporary_scene_and_export(
settings, settings,

View File

@ -65,8 +65,9 @@ def generate_temporary_scene_and_export(settings, gltf_export_settings, gltf_out
except Exception as error: except Exception as error:
print("failed to export gltf !", error) print("failed to export gltf !", error)
raise error raise error
# restore everything finally:
tempScene_cleaner(temp_scene, scene_filler_data) # restore everything
tempScene_cleaner(temp_scene, scene_filler_data)
# reset active scene # reset active scene
bpy.context.window.scene = active_scene bpy.context.window.scene = active_scene

View File

@ -1,9 +1,10 @@
import os import os
from ..constants import TEMPSCENE_PREFIX from ..constants import TEMPSCENE_PREFIX
from ..common.generate_temporary_scene_and_export import generate_temporary_scene_and_export, copy_hollowed_collection_into, clear_hollow_scene from ..common.generate_temporary_scene_and_export import generate_temporary_scene_and_export, copy_hollowed_collection_into, clear_hollow_scene
from ..common.export_gltf import (generate_gltf_export_settings, export_gltf) from ..common.export_gltf import (generate_gltf_export_settings, export_gltf)
from .is_object_dynamic import is_object_dynamic, is_object_static from .is_object_dynamic import is_object_dynamic, is_object_static
from ..utils import upsert_scene_assets from ..utils import upsert_scene_assets, write_level_metadata_file
def export_level_scene(scene, settings, blueprints_data): def export_level_scene(scene, settings, blueprints_data):
@ -28,6 +29,7 @@ def export_level_scene(scene, settings, blueprints_data):
# we inject assets into the scene before it gets exported # we inject assets into the scene before it gets exported
# TODO: this should be done in the temporary scene ! # TODO: this should be done in the temporary scene !
upsert_scene_assets(scene, blueprints_data=blueprints_data, settings=settings) upsert_scene_assets(scene, blueprints_data=blueprints_data, settings=settings)
write_level_metadata_file(scene=scene, blueprints_data=blueprints_data, settings=settings)
if export_separate_dynamic_and_static_objects: if export_separate_dynamic_and_static_objects:
#print("SPLIT STATIC AND DYNAMIC") #print("SPLIT STATIC AND DYNAMIC")

View File

@ -4,6 +4,7 @@ from pathlib import Path
from blenvy.assets.assets_scan import get_blueprint_asset_tree, get_level_scene_assets_tree2 from blenvy.assets.assets_scan import get_blueprint_asset_tree, get_level_scene_assets_tree2
from blenvy.add_ons.bevy_components.utils import is_component_valid_and_enabled from blenvy.add_ons.bevy_components.utils import is_component_valid_and_enabled
from .constants import custom_properties_to_filter_out from .constants import custom_properties_to_filter_out
from ...assets.assets_scan import get_level_scene_assets_tree2
def remove_unwanted_custom_properties(object): def remove_unwanted_custom_properties(object):
to_remove = [] to_remove = []
@ -42,3 +43,45 @@ def upsert_blueprint_assets(blueprint, blueprints_data, settings):
print("all_assets_raw", all_assets_raw) print("all_assets_raw", all_assets_raw)
print("local assets", local_assets) print("local assets", local_assets)
blueprint.collection["BlueprintAssets"] = assets_to_fake_ron(local_assets) blueprint.collection["BlueprintAssets"] = assets_to_fake_ron(local_assets)
import os
def write_level_metadata_file(scene, blueprints_data, settings):
levels_path_full = getattr(settings,"levels_path_full")
all_assets_raw = get_level_scene_assets_tree2(level_scene=scene, blueprints_data=blueprints_data, settings=settings)
formated_assets = []
for asset in all_assets_raw:
#if asset["internal"] :
formated_asset = f'\n ("{asset["name"]}", File ( path: "{asset["path"]}" )),'
formated_assets.append(formated_asset)
metadata_file_path_full = os.path.join(levels_path_full, scene.name+".meta.ron")
os.makedirs(os.path.dirname(metadata_file_path_full), exist_ok=True)
with open(metadata_file_path_full, "w") as assets_file:
assets_file.write("(\n ")
assets_file.write(" assets:\n [ ")
assets_file.writelines(formated_assets)
assets_file.write("\n ]\n")
assets_file.write(")")
def write_blueprint_metadata_file(blueprint, blueprints_data, settings):
blueprints_path_full = getattr(settings,"blueprints_path_full")
all_assets_raw = get_blueprint_asset_tree(blueprint=blueprint, blueprints_data=blueprints_data, settings=settings)
formated_assets = []
for asset in all_assets_raw:
#if asset["internal"] :
formated_asset = f'\n ("{asset["name"]}", File ( path: "{asset["path"]}" )),'
formated_assets.append(formated_asset)
metadata_file_path_full = os.path.join(blueprints_path_full, blueprint.name+".meta.ron")
os.makedirs(os.path.dirname(metadata_file_path_full), exist_ok=True)
with open(metadata_file_path_full, "w") as assets_file:
assets_file.write("(\n ")
assets_file.write(" assets:\n [ ")
assets_file.writelines(formated_assets)
assets_file.write("\n ]\n")
assets_file.write(")")