Compare commits

..

2 Commits

Author SHA1 Message Date
kaosat.dev
bef709a0ed feat(Blenvy:Bevy):
* bumped up Bevy version to v0.14 !
 * fixed (albeit in a clunky way) issues with sub blueprint detection
 * improved error message for missing material library
 * added HideUntilReady component & logic, to hide 'in-spawning' blueprint instances until they are ready
 * "add-to-world" is now only trigerred for blueprint instances that have no parents (avoid footgun)
 * minor cleanups & tweaks
 * added test component with Vec3 to double check for issues
 * updated test blend file to include the component above + added a light to the test spawnable blueprint
to check for "light flashes" (aka indirectly testing "HideUntilReady")
2024-07-07 22:21:12 +02:00
kaosat.dev
478be88a55 feat(Blenvy:Blender):
* fixed issue with parenting due to Blender's very weird matrix_parent_inverse ... solves all issues with children of empties
blueprint instances within blueprint instances etc having the wrong transforms in some cases
 * fixed bad gltf format propagation: semi ok solution, but a cleaner one would be better
 * added additional custom properties to the blacklist , level/blueprint exports are now using the one in the constants
instead of a local copy
 * minor tweaks & cleanups
2024-07-07 22:08:59 +02:00
22 changed files with 193 additions and 84 deletions

View File

@ -14,7 +14,7 @@ license = "MIT OR Apache-2.0"
workspace = true workspace = true
[dependencies] [dependencies]
bevy = { version = "0.14.0-rc.3", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "file_watcher"] } bevy = { version = "0.14", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "file_watcher"] }
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"
@ -34,4 +34,4 @@ gltf = { version = "1.4.0", default-features = false, features = [
[dev-dependencies] [dev-dependencies]
bevy = { version = "0.14.0-rc.3", default-features = false, features = ["dynamic_linking"] } bevy = { version = "0.14", default-features = false, features = ["dynamic_linking"] }

View File

@ -57,7 +57,7 @@ pub(crate) fn inject_materials(
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 let mat_gltf = assets_gltf
.get(model_handle.id()) .get(model_handle.id())
.expect("material should have been preloaded"); .expect(&format!("materials file {} should have been preloaded", material_info.path));
if mat_gltf.named_materials.contains_key(&material_info.name as &str) { if mat_gltf.named_materials.contains_key(&material_info.name as &str) {
let material = mat_gltf let material = mat_gltf
.named_materials .named_materials

View File

@ -60,6 +60,12 @@ pub struct AddToGameWorld;
pub(crate) struct OriginalChildren(pub Vec<Entity>); pub(crate) struct OriginalChildren(pub Vec<Entity>);
#[derive(Component)]
/// You can add this component to a blueprint instance, and the instance will be hidden until it is ready
/// You usually want to use this for worlds/level spawning , or dynamic spawning at runtime, but not when you are adding blueprint instances to an existing entity
/// as it would first become invisible before re-appearing again
pub struct HideUntilReady;
#[derive(Event, Debug)] #[derive(Event, Debug)]
pub enum BlueprintEvent { pub enum BlueprintEvent {
@ -261,6 +267,7 @@ pub(crate) fn blueprints_assets_ready(spawn_placeholders: Query<
Option<&Parent>, Option<&Parent>,
Option<&AddToGameWorld>, Option<&AddToGameWorld>,
Option<&Name>, Option<&Name>,
Option<&HideUntilReady>
), ),
( (
With<BlueprintAssetsLoaded>, With<BlueprintAssetsLoaded>,
@ -283,6 +290,7 @@ pub(crate) fn blueprints_assets_ready(spawn_placeholders: Query<
original_parent, original_parent,
add_to_world, add_to_world,
name, name,
hide_until_ready,
) in spawn_placeholders.iter() ) in spawn_placeholders.iter()
{ {
/*info!( /*info!(
@ -336,8 +344,6 @@ pub(crate) fn blueprints_assets_ready(spawn_placeholders: Query<
SceneBundle { SceneBundle {
scene: scene.clone(), scene: scene.clone(),
transform: transforms, transform: transforms,
visibility: Visibility::Hidden,
..Default::default() ..Default::default()
}, },
OriginalChildren(original_children), OriginalChildren(original_children),
@ -345,15 +351,19 @@ pub(crate) fn blueprints_assets_ready(spawn_placeholders: Query<
// these are animations specific to the inside of the blueprint // these are animations specific to the inside of the blueprint
named_animations: named_animations//gltf.named_animations.clone(), named_animations: named_animations//gltf.named_animations.clone(),
}, },
)); ));
/* if add_to_world.is_some() { 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() && original_parent.is_some() {
let world = game_world let world = game_world
.get_single_mut() .get_single_mut()
.expect("there should be a game world present"); .expect("there should be a game world present");
commands.entity(world).add_child(entity); commands.entity(world).add_child(entity);
} */ }
} }
} }
@ -382,8 +392,6 @@ pub struct BlueprintChildrenReady;
#[reflect(Component)] #[reflect(Component)]
pub struct BlueprintReadyForPostProcess; pub struct BlueprintReadyForPostProcess;
// TODO: disregard blueprints that have been spawned WAIT , we already have BlueprintSpawning
pub(crate) fn blueprints_scenes_spawned( pub(crate) fn blueprints_scenes_spawned(
spawned_blueprint_scene_instances: Query<(Entity, Option<&Name>, Option<&Children>, Option<&SpawnTrackRoot>), (With<BlueprintSpawning>, Added<SceneInstance>)>, spawned_blueprint_scene_instances: Query<(Entity, Option<&Name>, Option<&Children>, Option<&SpawnTrackRoot>), (With<BlueprintSpawning>, Added<SceneInstance>)>,
with_blueprint_infos : Query<(Entity, Option<&Name>), With<BlueprintInfo>>, with_blueprint_infos : Query<(Entity, Option<&Name>), With<BlueprintInfo>>,
@ -396,7 +404,6 @@ pub(crate) fn blueprints_scenes_spawned(
mut commands: Commands, mut commands: Commands,
all_names: Query<&Name> all_names: Query<&Name>
) { ) {
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!("Done spawning blueprint scene for entity named {:?} (track root: {:?})", name, track_root); info!("Done spawning blueprint scene for entity named {:?} (track root: {:?})", name, track_root);
@ -405,34 +412,59 @@ pub(crate) fn blueprints_scenes_spawned(
let mut tracker_data: HashMap<Entity, bool> = HashMap::new(); let mut tracker_data: HashMap<Entity, bool> = HashMap::new();
for parent in all_parents.iter_ancestors(entity) { if track_root.is_none() {
if with_blueprint_infos.get(parent).is_ok() { for parent in all_parents.iter_ancestors(entity) {
if with_blueprint_infos.get(parent).is_ok() {
println!("found a parent with blueprint_info {:?} for {:?}", all_names.get(parent), all_names.get(entity)); println!("found a parent with blueprint_info {:?} for {:?}", all_names.get(parent), all_names.get(entity));
break; commands.entity(entity).insert(SpawnTrackRoot(parent));// Injecting to know which entity is the root
break;
}
} }
} }
if children.is_some() { if children.is_some() {
for child in all_children.iter_descendants(entity) { for child in all_children.iter_descendants(entity) {
if with_blueprint_infos.get(child).is_ok() { if with_blueprint_infos.get(child).is_ok() {
sub_blueprint_instances.push(child); // println!("Parent blueprint instance of {:?} is {:?}", all_names.get(child), all_names.get(entity));
if let Ok(nname) = all_names.get(child) {
sub_blueprint_instance_names.push(nname.clone());
}
tracker_data.insert(child, false);
if track_root.is_some() {
let prev_root = track_root.unwrap().0;
// if we already had a track root, and it is different from the current entity , change the previous track root's list of children for parent in all_parents.iter_ancestors(child) {
if prev_root != entity { if with_blueprint_infos.get(parent).is_ok() {
let mut tracker = sub_blueprint_trackers.get_mut(prev_root).expect("should have a tracker");
tracker.1.sub_blueprint_instances.remove(&child); if parent == entity {
//println!("yohoho");
println!("Parent blueprint instance of {:?} is {:?}", all_names.get(child), all_names.get(parent));
commands.entity(child).insert(SpawnTrackRoot(entity));// Injecting to know which entity is the root
tracker_data.insert(child, false);
sub_blueprint_instances.push(child);
if let Ok(nname) = all_names.get(child) {
sub_blueprint_instance_names.push(nname.clone());
}
/*if track_root.is_some() {
let prev_root = track_root.unwrap().0;
// if we already had a track root, and it is different from the current entity , change the previous track root's list of children
if prev_root != entity {
let mut tracker = sub_blueprint_trackers.get_mut(prev_root).expect("should have a tracker");
tracker.1.sub_blueprint_instances.remove(&child);
}
}*/
}
break;
} }
} }
commands.entity(child).insert(SpawnTrackRoot(entity));// Injecting to know which entity is the root
} }
} }
} }
@ -481,7 +513,7 @@ pub(crate) fn blueprints_transfer_components(
) { ) {
for (original, children, original_children, name, track_root) in foo.iter() { for (original, children, original_children, name, track_root) in foo.iter() {
println!("YOOO ready !! {:?}", name); info!("YOOO ready !! removing empty nodes {:?}", name);
if children.len() == 0 { if children.len() == 0 {
warn!("timing issue ! no children found, please restart your bevy app (bug being investigated)"); warn!("timing issue ! no children found, please restart your bevy app (bug being investigated)");
@ -515,7 +547,7 @@ pub(crate) fn blueprints_transfer_components(
} }
commands.entity(original) commands.entity(original)
.insert(BlueprintReadyForPostProcess); // Tag the entity so any systems dealing with post processing can now it is now their "turn" .insert(BlueprintReadyForPostProcess); // Tag the entity so any systems dealing with post processing can know it is now their "turn"
// commands.entity(original).remove::<Handle<Scene>>(); // FIXME: if we delete the handle to the scene, things get despawned ! not what we want // commands.entity(original).remove::<Handle<Scene>>(); // FIXME: if we delete the handle to the scene, things get despawned ! not what we want
//commands.entity(original).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 ? //commands.entity(original).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 ?
//commands.entity(original).remove::<BlueprintAssetsLoaded>(); //commands.entity(original).remove::<BlueprintAssetsLoaded>();
@ -524,6 +556,7 @@ pub(crate) fn blueprints_transfer_components(
// now check if the current entity is a child blueprint instance of another entity // now check if the current entity is a child blueprint instance of another entity
// this should always be done last, as children should be finished before the parent can be processed correctly // this should always be done last, as children should be finished before the parent can be processed correctly
// TODO: perhaps use observers for these
if let Some(track_root) = track_root { if let Some(track_root) = track_root {
let root_name = all_names.get(track_root.0); let root_name = all_names.get(track_root.0);
println!("got some root {:?}", root_name); println!("got some root {:?}", root_name);
@ -555,19 +588,23 @@ pub(crate) fn blueprints_transfer_components(
pub struct BlueprintReadyForFinalizing; pub struct BlueprintReadyForFinalizing;
pub(crate) fn blueprints_finalize_instances( pub(crate) fn blueprints_finalize_instances(
blueprint_instances: Query<(Entity, Option<&Name>, &BlueprintInfo), (With<BlueprintSpawning>, With<BlueprintReadyForFinalizing>)>, blueprint_instances: Query<(Entity, Option<&Name>, &BlueprintInfo, Option<&HideUntilReady>), (With<BlueprintSpawning>, With<BlueprintReadyForFinalizing>)>,
mut blueprint_events: EventWriter<BlueprintEvent>, mut blueprint_events: EventWriter<BlueprintEvent>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, name, blueprint_info) in blueprint_instances.iter() { for (entity, name, blueprint_info, hide_until_ready) in blueprint_instances.iter() {
info!("Finalizing blueprint instance {:?}", name); info!("Finalizing blueprint instance {:?}", name);
commands.entity(entity) commands.entity(entity)
.remove::<SpawnHere>() .remove::<SpawnHere>()
.remove::<BlueprintSpawning>() .remove::<BlueprintSpawning>()
.remove::<BlueprintReadyForPostProcess>() .remove::<BlueprintReadyForPostProcess>()
.insert(BlueprintInstanceReady) .insert(BlueprintInstanceReady)
.insert(Visibility::Visible)
; ;
if hide_until_ready.is_some() {
println!("REVEAAAL");
commands.entity(entity).insert(Visibility::Visible);
}
blueprint_events.send(BlueprintEvent::InstanceReady {entity: entity, blueprint_name: blueprint_info.name.clone(), blueprint_path: blueprint_info.path.clone()}); blueprint_events.send(BlueprintEvent::InstanceReady {entity: entity, blueprint_name: blueprint_info.name.clone(), blueprint_path: blueprint_info.path.clone()});
} }

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
bevy = { version = "0.14.0-rc.3", features = ["dynamic_linking"] } bevy = { version = "0.14", features = ["dynamic_linking"] }
blenvy = { path = "../../crates/blenvy" } blenvy = { path = "../../crates/blenvy" }
# bevy_gltf_blueprints = { path = "../../crates/bevy_gltf_blueprints" } # bevy_gltf_blueprints = { path = "../../crates/bevy_gltf_blueprints" }
# bevy_registry_export = { path = "../../crates/bevy_registry_export" } # bevy_registry_export = { path = "../../crates/bevy_registry_export" }

View File

@ -4182,6 +4182,30 @@
"type": "object", "type": "object",
"typeInfo": "Struct" "typeInfo": "Struct"
}, },
"bevy_example::test_components::RedirectPropHitImpulse": {
"isComponent": true,
"isResource": false,
"long_name": "bevy_example::test_components::RedirectPropHitImpulse",
"oneOf": [
{
"items": false,
"long_name": "Local",
"prefixItems": [
{
"type": {
"$ref": "#/$defs/glam::Vec3"
}
}
],
"short_name": "Local",
"type": "array",
"typeInfo": "Tuple"
}
],
"short_name": "RedirectPropHitImpulse",
"type": "object",
"typeInfo": "Enum"
},
"bevy_example::test_components::TupleTest2": { "bevy_example::test_components::TupleTest2": {
"isComponent": true, "isComponent": true,
"isResource": false, "isResource": false,
@ -8145,11 +8169,6 @@
"$ref": "#/$defs/bevy_render::alpha::AlphaMode" "$ref": "#/$defs/bevy_render::alpha::AlphaMode"
} }
}, },
"anisotropy_channel": {
"type": {
"$ref": "#/$defs/bevy_pbr::pbr_material::UvChannel"
}
},
"anisotropy_rotation": { "anisotropy_rotation": {
"type": { "type": {
"$ref": "#/$defs/f32" "$ref": "#/$defs/f32"
@ -8160,11 +8179,6 @@
"$ref": "#/$defs/f32" "$ref": "#/$defs/f32"
} }
}, },
"anisotropy_texture": {
"type": {
"$ref": "#/$defs/core::option::Option<bevy_asset::handle::Handle<bevy_render::texture::image::Image>>"
}
},
"attenuation_color": { "attenuation_color": {
"type": { "type": {
"$ref": "#/$defs/bevy_color::color::Color" "$ref": "#/$defs/bevy_color::color::Color"
@ -8374,7 +8388,6 @@
"clearcoat_perceptual_roughness", "clearcoat_perceptual_roughness",
"anisotropy_strength", "anisotropy_strength",
"anisotropy_rotation", "anisotropy_rotation",
"anisotropy_channel",
"double_sided", "double_sided",
"unlit", "unlit",
"fog_enabled", "fog_enabled",

View File

@ -1,5 +1,5 @@
use bevy::prelude::*; use bevy::prelude::*;
use blenvy::{BluePrintBundle, BlueprintInfo, DynamicBlueprintInstance, GameWorldTag, SpawnHere}; use blenvy::{BluePrintBundle, BlueprintInfo, DynamicBlueprintInstance, GameWorldTag, HideUntilReady, SpawnHere};
use crate::{GameState, InAppRunning}; use crate::{GameState, InAppRunning};
//use bevy_rapier3d::prelude::Velocity; //use bevy_rapier3d::prelude::Velocity;
@ -23,6 +23,7 @@ pub fn setup_game(
commands.spawn(( commands.spawn((
BlueprintInfo{name: "World".into(), path: "levels/World.glb".into()}, BlueprintInfo{name: "World".into(), path: "levels/World.glb".into()},
HideUntilReady,
bevy::prelude::Name::from("world"), //FIXME: not really needed ? could be infered from blueprint's name/ path bevy::prelude::Name::from("world"), //FIXME: not really needed ? could be infered from blueprint's name/ path
SpawnHere, SpawnHere,
GameWorldTag, GameWorldTag,
@ -67,6 +68,7 @@ pub fn spawn_test(
}, },
DynamicBlueprintInstance, DynamicBlueprintInstance,
bevy::prelude::Name::from(format!("test{}", name_index)), bevy::prelude::Name::from(format!("test{}", name_index)),
HideUntilReady,
// SpawnHere, // SpawnHere,
TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
/*Velocity { /*Velocity {

View File

@ -129,10 +129,19 @@ fn exit_game(mut app_exit_events: ResMut<Events<bevy::app::AppExit>>) {
fn check_for_gltf_events( fn check_for_gltf_events(
mut blueprint_events: EventReader<BlueprintEvent>, mut blueprint_events: EventReader<BlueprintEvent>,
all_names: Query<&Name>,
) )
{ {
for event in blueprint_events.read() { for event in blueprint_events.read() {
info!("BLUEPRINT EVENT: {:?}", event); match event {
BlueprintEvent::InstanceReady{entity, blueprint_name, blueprint_path} => {
info!("BLUEPRINT EVENT: {:?} for {:?}", event, all_names.get(*entity));
}
_=> {
info!("BLUEPRINT EVENT: {:?}", event);
}
}
} }
} }

View File

@ -1,7 +1,7 @@
use bevy::{gltf::{GltfMaterialExtras, GltfMeshExtras, GltfSceneExtras}, prelude::*}; use bevy::{gltf::{GltfMaterialExtras, GltfMeshExtras, GltfSceneExtras}, prelude::*};
use blenvy::{BlueprintAssets, BlueprintInstanceReady}; use blenvy::{BlueprintAssets, BlueprintInstanceReady};
use crate::{BasicTest, EnumComplex}; use crate::{BasicTest, EnumComplex, RedirectPropHitImpulse};
#[derive(Component)] #[derive(Component)]
pub struct HiearchyDebugTag; pub struct HiearchyDebugTag;
@ -12,7 +12,7 @@ pub fn setup_hierarchy_debug(mut commands: Commands, asset_server: Res<AssetServ
TextBundle::from_section( TextBundle::from_section(
"", "",
TextStyle { TextStyle {
color: LinearRgba { red: 1.0, green:0.0, blue: 0.0, alpha: 1.0}.into(), color: LinearRgba { red: 1.0, green:1.0, blue: 1.0, alpha: 1.0}.into(),
font_size: 15., font_size: 15.,
..default() ..default()
}, },
@ -30,7 +30,10 @@ pub fn setup_hierarchy_debug(mut commands: Commands, asset_server: Res<AssetServ
pub fn get_descendants( pub fn get_descendants(
all_children: &Query<&Children>, all_children: &Query<&Children>,
all_names:&Query<&Name>, root: &Entity, all_names:&Query<&Name>,
root: &Entity,
all_transforms: &Query<&Transform>,
all_global_transforms: &Query<&GlobalTransform>,
nesting: usize, nesting: usize,
to_check: &Query<&BasicTest>//&Query<(&BlueprintInstanceReady, &BlueprintAssets)>, to_check: &Query<&BasicTest>//&Query<(&BlueprintInstanceReady, &BlueprintAssets)>,
) )
@ -48,14 +51,14 @@ pub fn get_descendants(
let components_to_check = to_check.get(*root); let components_to_check = to_check.get(*root);
hierarchy_display.push( format!("{}{} ({:?})", " ".repeat(nesting), name, components_to_check) ); // hierarchy_display.push( format!("{}{} ({:?}) ({:?})", " ".repeat(nesting), name, all_transforms.get(*root), all_global_transforms.get(*root)) ); //components_to_check ({:?})
if let Ok(children) = all_children.get(*root) { if let Ok(children) = all_children.get(*root) {
for child in children.iter() { for child in children.iter() {
let child_descendants_display = get_descendants(&all_children, &all_names, &child, nesting + 4, &to_check); let child_descendants_display = get_descendants(&all_children, &all_names, &child, &all_transforms, &all_global_transforms, nesting + 4, &to_check);
hierarchy_display.push(child_descendants_display); hierarchy_display.push(child_descendants_display);
} }
} }
@ -66,6 +69,8 @@ pub fn draw_hierarchy_debug(
root: Query<(Entity, Option<&Name>, &Children), (Without<Parent>)>, root: Query<(Entity, Option<&Name>, &Children), (Without<Parent>)>,
all_children: Query<&Children>, all_children: Query<&Children>,
all_names:Query<&Name>, all_names:Query<&Name>,
all_transforms: Query<&Transform>,
all_global_transforms: Query<&GlobalTransform>,
to_check: Query<&BasicTest>,//Query<(&BlueprintInstanceReady, &BlueprintAssets)>, to_check: Query<&BasicTest>,//Query<(&BlueprintInstanceReady, &BlueprintAssets)>,
mut display: Query<&mut Text, With<HiearchyDebugTag>>, mut display: Query<&mut Text, With<HiearchyDebugTag>>,
@ -75,7 +80,7 @@ pub fn draw_hierarchy_debug(
for (root_entity, name, children) in root.iter() { for (root_entity, name, children) in root.iter() {
// hierarchy_display.push( format!("Hierarchy root{:?}", name) ); // hierarchy_display.push( format!("Hierarchy root{:?}", name) );
hierarchy_display.push(get_descendants(&all_children, &all_names, &root_entity, 0, &to_check)); hierarchy_display.push(get_descendants(&all_children, &all_names, &root_entity, &all_transforms, &all_global_transforms, 0, &to_check));
// let mut children = all_children.get(root_entity); // let mut children = all_children.get(root_entity);
/*for child in children.iter() { /*for child in children.iter() {
// hierarchy_display // hierarchy_display
@ -134,13 +139,13 @@ for (id, name, scene_extras, extras, mesh_extras, material_extras) in
} }
fn check_for_component( fn check_for_component(
foo: Query<(Entity, Option<&Name>, &EnumComplex)>, foo: Query<(Entity, Option<&Name>, &RedirectPropHitImpulse)>,
mut display: Query<&mut Text, With<HiearchyDebugTag>>, mut display: Query<&mut Text, With<HiearchyDebugTag>>,
){ ){
let mut info_lines: Vec<String> = vec![]; let mut info_lines: Vec<String> = vec![];
for (entiity, name , enum_complex) in foo.iter(){ for (entiity, name , enum_complex) in foo.iter(){
let data = format!(" We have a 'EnumComplex' component: {:?} (on {:?})", enum_complex, name); let data = format!(" We have a matching component: {:?} (on {:?})", enum_complex, name);
info_lines.push(data); info_lines.push(data);
println!("yoho component"); println!("yoho component");
@ -156,8 +161,8 @@ impl Plugin for HiearchyDebugPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app app
.add_systems(Startup, setup_hierarchy_debug) .add_systems(Startup, setup_hierarchy_debug)
// .add_systems(Update, check_for_component) //.add_systems(Update, check_for_component)
.add_systems(Update, draw_hierarchy_debug) //.add_systems(Update, draw_hierarchy_debug)
//.add_systems(Update, check_for_gltf_extras) //.add_systems(Update, check_for_gltf_extras)
; ;

View File

@ -224,6 +224,12 @@ pub struct ComponentWithFieldsOfIdenticalType{
#[reflect(Component)] #[reflect(Component)]
pub struct ComponentWithFieldsOfIdenticalType2(f32, f32, f32); pub struct ComponentWithFieldsOfIdenticalType2(f32, f32, f32);
#[derive(Debug, Clone, Copy, PartialEq, Reflect, Component)]
#[reflect(Component, )]
pub enum RedirectPropHitImpulse {
Local(Vec3),
}
pub struct ComponentsTestPlugin; pub struct ComponentsTestPlugin;
impl Plugin for ComponentsTestPlugin { impl Plugin for ComponentsTestPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
@ -279,6 +285,8 @@ impl Plugin for ComponentsTestPlugin {
.register_type::<ComponentWithFieldsOfIdenticalType>() .register_type::<ComponentWithFieldsOfIdenticalType>()
.register_type::<ComponentWithFieldsOfIdenticalType2>() .register_type::<ComponentWithFieldsOfIdenticalType2>()
.register_type::<RedirectPropHitImpulse>()
.add_plugins(MaterialPlugin::< .add_plugins(MaterialPlugin::<
ExtendedMaterial<StandardMaterial, MyExtension>, ExtendedMaterial<StandardMaterial, MyExtension>,
>::default()); >::default());

View File

@ -199,7 +199,8 @@ Blender side:
- [ ] disabled components - [ ] disabled components
- [ ] blueprint instances as children of blueprint instances - [ ] blueprint instances as children of blueprint instances
- [ ] blueprint instances as children of empties - [ ] blueprint instances as children of empties
- [x] update testing files - [x] update testing blend files
- [ ] disable 'export_hierarchy_full_collections' for all cases: not reliable and redudant
- [ ] add option to 'split out' meshes from blueprints ? - [ ] add option to 'split out' meshes from blueprints ?

View File

@ -21,16 +21,11 @@ def auto_export(changes_per_scene, changed_export_parameters, settings):
# have the export parameters (not auto export, just gltf export) have changed: if yes (for example switch from glb to gltf, compression or not, animations or not etc), we need to re-export everything # have the export parameters (not auto export, just gltf export) have changed: if yes (for example switch from glb to gltf, compression or not, animations or not etc), we need to re-export everything
print ("changed_export_parameters", changed_export_parameters) print ("changed_export_parameters", changed_export_parameters)
try: try:
# path to the current blend file
file_path = bpy.data.filepath
# Get the folder
blend_file_path = os.path.dirname(file_path)
#should we use change detection or not #should we use change detection or not
change_detection = getattr(settings.auto_export, "change_detection") change_detection = getattr(settings.auto_export, "change_detection")
export_scene_settings = getattr(settings.auto_export,"export_scene_settings") export_scene_settings = getattr(settings.auto_export, "export_scene_settings")
do_export_blueprints = getattr(settings.auto_export,"export_blueprints") do_export_blueprints = getattr(settings.auto_export, "export_blueprints")
export_materials_library = getattr(settings.auto_export,"export_materials_library") export_materials_library = getattr(settings.auto_export, "export_materials_library")
# standard gltf export settings are stored differently # standard gltf export settings are stored differently
standard_gltf_exporter_settings = get_standard_exporter_settings() standard_gltf_exporter_settings = get_standard_exporter_settings()
@ -47,7 +42,10 @@ def auto_export(changes_per_scene, changed_export_parameters, settings):
# we inject the blueprints export path # we inject the blueprints export path
blueprints_path = getattr(settings,"blueprints_path") blueprints_path = getattr(settings,"blueprints_path")
inject_export_path_into_internal_blueprints(internal_blueprints=blueprints_data.internal_blueprints, blueprints_path=blueprints_path, gltf_extension=gltf_extension) # inject the "export_path" and "material_path" properties into the internal blueprints
inject_export_path_into_internal_blueprints(internal_blueprints=blueprints_data.internal_blueprints, blueprints_path=blueprints_path, gltf_extension=gltf_extension, settings=settings)
for blueprint in blueprints_data.blueprints: for blueprint in blueprints_data.blueprints:
bpy.context.window_manager.blueprints_registry.add_blueprint(blueprint) bpy.context.window_manager.blueprints_registry.add_blueprint(blueprint)
#bpy.context.window_manager.blueprints_registry.refresh_blueprints() #bpy.context.window_manager.blueprints_registry.refresh_blueprints()

View File

@ -119,6 +119,10 @@ def duplicate_object(object, parent, combine_mode, destination_collection, bluep
# do this both for empty replacements & normal copies # do this both for empty replacements & normal copies
if parent is not None: if parent is not None:
copy.parent = parent copy.parent = parent
# without this, the copy"s offset from parent (if any ) will not be set correctly !
# see here for example https://blender.stackexchange.com/questions/3763/parenting-messes-up-transforms-where-is-the-offset-stored
copy.matrix_parent_inverse = object.matrix_parent_inverse
remove_unwanted_custom_properties(copy) remove_unwanted_custom_properties(copy)
copy_animation_data(object, copy) copy_animation_data(object, copy)

View File

@ -24,6 +24,7 @@ def generate_gltf_export_settings(settings):
export_cameras=True, export_cameras=True,
export_extras=True, # For custom exported properties. export_extras=True, # For custom exported properties.
export_lights=True, export_lights=True,
export_hierarchy_full_collections=False
#export_texcoords=True, #export_texcoords=True,
#export_normals=True, #export_normals=True,
@ -54,6 +55,7 @@ def generate_gltf_export_settings(settings):
standard_gltf_exporter_settings = get_standard_exporter_settings() standard_gltf_exporter_settings = get_standard_exporter_settings()
# these essential params should NEVER be overwritten , no matter the settings of the standard exporter
constant_keys = [ constant_keys = [
'use_selection', 'use_selection',
'use_visible', 'use_visible',
@ -63,9 +65,10 @@ def generate_gltf_export_settings(settings):
'export_cameras', 'export_cameras',
'export_extras', # For custom exported properties. 'export_extras', # For custom exported properties.
'export_lights', 'export_lights',
'export_hierarchy_full_collections'
] ]
# a certain number of essential params should NEVER be overwritten , no matter the settings of the standard exporter #
for key in standard_gltf_exporter_settings.keys(): for key in standard_gltf_exporter_settings.keys():
if str(key) not in constant_keys: if str(key) not in constant_keys:
gltf_export_settings[key] = standard_gltf_exporter_settings.get(key) gltf_export_settings[key] = standard_gltf_exporter_settings.get(key)

View File

@ -5,7 +5,7 @@ from blenvy.core.object_makers import (make_empty)
from .duplicate_object import duplicate_object from .duplicate_object import duplicate_object
from .export_gltf import export_gltf from .export_gltf import export_gltf
from blenvy.core.scene_helpers import add_scene_property from blenvy.core.scene_helpers import add_scene_property
from ..constants import custom_properties_to_filter_out
""" """
generates a temporary scene, fills it with data, cleans up after itself generates a temporary scene, fills it with data, cleans up after itself
* named using temp_scene_name * named using temp_scene_name
@ -19,7 +19,7 @@ def generate_temporary_scene_and_export(settings, gltf_export_settings, gltf_out
temp_scene = bpy.data.scenes.new(name=temp_scene_name) temp_scene = bpy.data.scenes.new(name=temp_scene_name)
temp_root_collection = temp_scene.collection temp_root_collection = temp_scene.collection
properties_black_list = ['glTF2ExportSettings', 'assets', 'user_assets', 'components_meta', 'Components_meta', 'Generated_assets', 'generated_assets'] properties_black_list = custom_properties_to_filter_out
if additional_data is not None: # FIXME not a fan of having this here if additional_data is not None: # FIXME not a fan of having this here
for entry in dict(additional_data): for entry in dict(additional_data):
# we copy everything over except those on the black list # we copy everything over except those on the black list
@ -75,7 +75,6 @@ def generate_temporary_scene_and_export(settings, gltf_export_settings, gltf_out
# copies the contents of a collection into another one while replacing library instances with empties # copies the contents of a collection into another one while replacing library instances with empties
def copy_hollowed_collection_into(source_collection, destination_collection, parent_empty=None, filter=None, blueprints_data=None, settings={}): def copy_hollowed_collection_into(source_collection, destination_collection, parent_empty=None, filter=None, blueprints_data=None, settings={}):
collection_instances_combine_mode = getattr(settings.auto_export, "collection_instances_combine_mode") collection_instances_combine_mode = getattr(settings.auto_export, "collection_instances_combine_mode")
for object in source_collection.objects: for object in source_collection.objects:
if object.name.endswith("____bak"): # some objects could already have been handled, ignore them if object.name.endswith("____bak"): # some objects could already have been handled, ignore them
continue continue

View File

@ -1,4 +1,11 @@
TEMPSCENE_PREFIX = "__temp_scene" TEMPSCENE_PREFIX = "__temp_scene"
#hard coded custom properties to ignore #hard coded custom properties to ignore on export
custom_properties_to_filter_out = ['_combine', 'template', 'components_meta'] custom_properties_to_filter_out = [
'glTF2ExportSettings',
'assets', 'user_assets', 'Generated_assets', 'generated_assets',
'components_meta', 'Components_meta',
'_combine', 'template',
'Blenvy_scene_type', 'blenvy_scene_type'
]
#['_combine', 'template', 'components_meta', 'Components_meta', 'Blenvy_scene_type']

View File

@ -55,7 +55,7 @@ def export_main_scene(scene, settings, blueprints_data):
auto_assets = [] auto_assets = []
all_assets = [] all_assets = []
export_gltf_extension = getattr(settings.auto_export, "export_gltf_extension", ".glb") export_gltf_extension = getattr(settings, "export_gltf_extension", ".glb")
blueprints_path = getattr(settings, "blueprints_path") blueprints_path = getattr(settings, "blueprints_path")
for blueprint in blueprints_in_scene: for blueprint in blueprints_in_scene:
@ -64,6 +64,11 @@ def export_main_scene(scene, settings, blueprints_data):
else: else:
# get the injected path of the external blueprints # get the injected path of the external blueprints
blueprint_exported_path = blueprint.collection['export_path'] if 'export_path' in blueprint.collection else None blueprint_exported_path = blueprint.collection['export_path'] if 'export_path' in blueprint.collection else None
# add their material path
materials_exported_path = blueprint.collection['materials_path'] if 'materials_path' in blueprint.collection else None
auto_assets.append({"name": blueprint.name+"_material", "path": materials_exported_path})#, "generated": True, "internal":blueprint.local, "parent": None})
if blueprint_exported_path is not None: # and not does_asset_exist(assets_list, blueprint_exported_path): if blueprint_exported_path is not None: # and not does_asset_exist(assets_list, blueprint_exported_path):
auto_assets.append({"name": blueprint.name, "path": blueprint_exported_path})#, "generated": True, "internal":blueprint.local, "parent": None}) auto_assets.append({"name": blueprint.name, "path": blueprint_exported_path})#, "generated": True, "internal":blueprint.local, "parent": None})
@ -80,7 +85,7 @@ def export_main_scene(scene, settings, blueprints_data):
materials_library_name = f"{current_project_name}_materials" materials_library_name = f"{current_project_name}_materials"
materials_exported_path = os.path.join(materials_path, f"{materials_library_name}{export_gltf_extension}") materials_exported_path = os.path.join(materials_path, f"{materials_library_name}{export_gltf_extension}")
material_assets = [{"name": materials_library_name, "path": materials_exported_path}] # we also add the material library as an asset material_assets = [{"name": materials_library_name, "path": materials_exported_path}] # we also add the material library as an asset
print("material_assets", material_assets, "extension", export_gltf_extension)
scene["BlueprintAssets"] = assets_to_fake_ron(all_assets + [{"name": asset.name, "path": asset.path} for asset in scene.user_assets] + auto_assets + material_assets) scene["BlueprintAssets"] = assets_to_fake_ron(all_assets + [{"name": asset.name, "path": asset.path} for asset in scene.user_assets] + auto_assets + material_assets)
#scene["BlueprintAssets"] = assets_to_fake_ron([{'name':'foo', 'path':'bar'}]) #scene["BlueprintAssets"] = assets_to_fake_ron([{'name':'foo', 'path':'bar'}])

View File

@ -62,7 +62,7 @@ def get_userTextures():
def get_blueprint_assets_tree(blueprint, blueprints_data, parent, settings): def get_blueprint_assets_tree(blueprint, blueprints_data, parent, settings):
blueprints_path = getattr(settings, "blueprints_path") blueprints_path = getattr(settings, "blueprints_path")
export_gltf_extension = getattr(settings.auto_export, "export_gltf_extension", ".glb") export_gltf_extension = getattr(settings, "export_gltf_extension", ".glb")
assets_list = [] assets_list = []
@ -91,7 +91,7 @@ def get_blueprint_assets_tree(blueprint, blueprints_data, parent, settings):
def get_main_scene_assets_tree(main_scene, blueprints_data, settings): def get_main_scene_assets_tree(main_scene, blueprints_data, settings):
blueprints_path = getattr(settings, "blueprints_path") blueprints_path = getattr(settings, "blueprints_path")
export_gltf_extension = getattr(settings.auto_export, "export_gltf_extension", ".glb") export_gltf_extension = getattr(settings, "export_gltf_extension", ".glb")
blueprint_instance_names_for_scene = blueprints_data.blueprint_instances_per_main_scene.get(main_scene.name, None) blueprint_instance_names_for_scene = blueprints_data.blueprint_instances_per_main_scene.get(main_scene.name, None)
assets_list = get_user_assets_as_list(main_scene) assets_list = get_user_assets_as_list(main_scene)
@ -123,7 +123,7 @@ def get_main_scene_assets_tree(main_scene, blueprints_data, settings):
def get_blueprint_asset_tree(blueprint, blueprints_data, settings): def get_blueprint_asset_tree(blueprint, blueprints_data, settings):
blueprints_path = getattr(settings, "blueprints_path") blueprints_path = getattr(settings, "blueprints_path")
export_gltf_extension = getattr(settings.auto_export, "export_gltf_extension", ".glb") export_gltf_extension = getattr(settings, "export_gltf_extension", ".glb")
assets_list = get_user_assets_as_list(blueprint.collection) assets_list = get_user_assets_as_list(blueprint.collection)

View File

@ -2,6 +2,8 @@
import os import os
import json import json
import bpy import bpy
from pathlib import Path
from ..core.scene_helpers import add_scene_property from ..core.scene_helpers import add_scene_property
def find_blueprints_not_on_disk(blueprints, folder_path, extension): def find_blueprints_not_on_disk(blueprints, folder_path, extension):
@ -19,12 +21,23 @@ def check_if_blueprint_on_disk(scene_name, folder_path, extension):
found = os.path.exists(gltf_output_path) and os.path.isfile(gltf_output_path) found = os.path.exists(gltf_output_path) and os.path.isfile(gltf_output_path)
return found return found
def inject_export_path_into_internal_blueprints(internal_blueprints, blueprints_path, gltf_extension): def inject_export_path_into_internal_blueprints(internal_blueprints, blueprints_path, gltf_extension, settings):
export_materials_library = getattr(settings.auto_export, "export_materials_library")
# FIXME: duplicate of materials stuff
export_gltf_extension = getattr(settings, "export_gltf_extension", ".glb")
materials_path = getattr(settings, "materials_path")
current_project_name = Path(bpy.context.blend_data.filepath).stem
materials_library_name = f"{current_project_name}_materials"
materials_exported_path = os.path.join(materials_path, f"{materials_library_name}{export_gltf_extension}")
for blueprint in internal_blueprints: for blueprint in internal_blueprints:
blueprint_exported_path = os.path.join(blueprints_path, f"{blueprint.name}{gltf_extension}") blueprint_exported_path = os.path.join(blueprints_path, f"{blueprint.name}{gltf_extension}")
# print("injecting blueprint path", blueprint_exported_path, "for", blueprint.name) # print("injecting blueprint path", blueprint_exported_path, "for", blueprint.name)
print("blueprint_exported_path", blueprint_exported_path)
blueprint.collection["export_path"] = blueprint_exported_path blueprint.collection["export_path"] = blueprint_exported_path
if export_materials_library:
blueprint.collection["materials_path"] = materials_exported_path
def inject_blueprints_list_into_main_scene(scene, blueprints_data, settings): def inject_blueprints_list_into_main_scene(scene, blueprints_data, settings):
project_root_path = getattr(settings, "project_root_path") project_root_path = getattr(settings, "project_root_path")

View File

@ -34,7 +34,7 @@ def get_all_materials(collection_names, library_scenes):
def add_material_info_to_objects(materials_per_object, settings): def add_material_info_to_objects(materials_per_object, settings):
materials_path = getattr(settings, "materials_path") materials_path = getattr(settings, "materials_path")
export_gltf_extension = getattr(settings.auto_export, "export_gltf_extension", ".glb") export_gltf_extension = getattr(settings, "export_gltf_extension", ".glb")
current_project_name = Path(bpy.context.blend_data.filepath).stem current_project_name = Path(bpy.context.blend_data.filepath).stem
materials_library_name = f"{current_project_name}_materials" materials_library_name = f"{current_project_name}_materials"

View File

@ -122,9 +122,13 @@ def test_export_complex(setup_data):
user_asset.path = "audio/fake.mp3" user_asset.path = "audio/fake.mp3"
# we have to cheat, since we cannot rely on the data injected when saving the library file (since we are not saving it as part of the tests) # we have to cheat, since we cannot rely on the data injected when saving the library file (since we are not saving it as part of the tests)
#bpy.data.collections["External_blueprint"]["export_path"] = "blueprints/External_blueprint.glb" bpy.data.collections["External_blueprint"]["export_path"] = "blueprints/External_blueprint.glb"
#bpy.data.collections["External_blueprint2"]["export_path"] = "blueprints/External_blueprint2.glb" bpy.data.collections["External_blueprint2"]["export_path"] = "blueprints/External_blueprint2.glb"
#bpy.data.collections["External_blueprint3"]["export_path"] = "blueprints/External_blueprint3.glb" bpy.data.collections["External_blueprint3"]["export_path"] = "blueprints/External_blueprint3.glb"
bpy.data.collections["External_blueprint"]["materials_path"] = "materials/testing_library_materials.glb"
bpy.data.collections["External_blueprint2"]["materials_path"] = "materials/testing_library_materials.glb"
bpy.data.collections["External_blueprint3"]["materials_path"] = "materials/testing_library_materials.glb"
# do the actual exporting # do the actual exporting
prepare_and_export() prepare_and_export()

View File

@ -80,6 +80,7 @@ def test_export_complex(setup_data):
export_props = { export_props = {
} }
gltf_settings = { gltf_settings = {
"export_format":"GLTF_SEPARATE",
"export_animations": True, "export_animations": True,
"export_optimize_animation_size": False, "export_optimize_animation_size": False,
"export_apply":True "export_apply":True