Compare commits

..

5 Commits

Author SHA1 Message Date
kaosat.dev 185c25f7b2 feat(bevy_components): started migration of data storage to enable
support for components with identical short names
2024-04-30 23:50:08 +02:00
kaosat.dev dc053562bc chore(auto_export): cleanups 2024-04-30 11:59:04 +02:00
kaosat.dev 49917e3b17 feat():
* cleaned up crate code
 * added duplicate named component to testing project to resolve issues with clashing short_names for bevy components
2024-04-30 11:58:03 +02:00
kaosat.dev 9138c81c60 refactor(): removed remains of legacy mode 2024-04-30 11:33:05 +02:00
kaosat.dev eda18b7d25 test(auto_export): overhauled & cleaned up tests 2024-04-30 11:05:24 +02:00
30 changed files with 436 additions and 414 deletions

View File

@ -306,24 +306,6 @@ see https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/exa
Generating optimised blueprints and material libraries can be automated using the latests version of the [Blender plugin](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/gltf_auto_export) Generating optimised blueprints and material libraries can be automated using the latests version of the [Blender plugin](https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/tools/gltf_auto_export)
## Legacy mode
Starting in version 0.7 there is a new parameter ```legacy_mode``` for backwards compatibility
To disable the legacy mode: (enabled by default)
```rust no_run
BlueprintsPlugin{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) !
As it create custom properties that are writen in real **ron** file format instead of a simplified version (the one in the legacy mode)
> Note: the legacy mode support will be dropped in future versions, and the default behaviour will be NO legacy mode
## Examples ## Examples
https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/basic https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_gltf_blueprints/basic

View File

@ -78,8 +78,6 @@ impl fmt::Display for GltfFormat {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// Plugin for gltf blueprints /// Plugin for gltf blueprints
pub struct BlueprintsPlugin { pub struct BlueprintsPlugin {
pub legacy_mode: bool, // flag that gets passed on to bevy_gltf_components
pub format: GltfFormat, pub format: GltfFormat,
/// The base folder where library/blueprints assets are loaded from, relative to the executable. /// The base folder where library/blueprints assets are loaded from, relative to the executable.
pub library_folder: PathBuf, pub library_folder: PathBuf,
@ -93,7 +91,6 @@ pub struct BlueprintsPlugin {
impl Default for BlueprintsPlugin { impl Default for BlueprintsPlugin {
fn default() -> Self { fn default() -> Self {
Self { Self {
legacy_mode: true,
format: GltfFormat::GLB, format: GltfFormat::GLB,
library_folder: PathBuf::from("models/library"), library_folder: PathBuf::from("models/library"),
aabbs: false, aabbs: false,
@ -113,9 +110,7 @@ fn materials_library_enabled(blueprints_config: Res<BluePrintsConfig>) -> bool {
impl Plugin for BlueprintsPlugin { impl Plugin for BlueprintsPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins(ComponentsFromGltfPlugin { app.add_plugins(ComponentsFromGltfPlugin {})
legacy_mode: self.legacy_mode,
})
.register_type::<BlueprintName>() .register_type::<BlueprintName>()
.register_type::<MaterialInfo>() .register_type::<MaterialInfo>()
.register_type::<SpawnHere>() .register_type::<SpawnHere>()

View File

@ -78,17 +78,78 @@ Use the default configuration:
ComponentsFromGltfPlugin::default() ComponentsFromGltfPlugin::default()
``` ```
Or disable the legacy mode: (enabled by default) As of v0.6 Legacy mode has been removed , you can emulate it using a system that should run BEFORE bevy_gltf_components
```rust no run
if simplified_types {
if let TypeInfo::TupleStruct(info) = type_registration.type_info() {
// we handle tupple strucs with only one field differently, as Blender's custom properties with custom ui (float, int, bool, etc) always give us a tupple struct
if info.field_len() == 1 {
let field = info
.field_at(0)
.expect("we should always have at least one field here");
let field_name = field.type_path();
let mut formated = parsed_value.clone();
match field_name {
"f32" => {
formated = parsed_value.parse::<f32>().unwrap().to_string();
}
"f64" => {
formated = parsed_value.parse::<f64>().unwrap().to_string();
}
"u8" => {
formated = parsed_value.parse::<u8>().unwrap().to_string();
}
"u16" => {
formated = parsed_value.parse::<u16>().unwrap().to_string();
}
"u32" => {
formated = parsed_value.parse::<u32>().unwrap().to_string();
}
"u64" => {
formated = parsed_value.parse::<u64>().unwrap().to_string();
}
"u128" => {
formated = parsed_value.parse::<u128>().unwrap().to_string();
}
"glam::Vec2" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
formated = format!("(x:{},y:{})", parsed[0], parsed[1]);
}
"glam::Vec3" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
formated =
format!("(x:{},y:{},z:{})", parsed[0], parsed[1], parsed[2]);
}
"bevy_render::color::Color" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
if parsed.len() == 3 {
formated = format!(
"Rgba(red:{},green:{},blue:{}, alpha: 1.0)",
parsed[0], parsed[1], parsed[2]
);
}
if parsed.len() == 4 {
formated = format!(
"Rgba(red:{},green:{},blue:{}, alpha:{})",
parsed[0], parsed[1], parsed[2], parsed[3]
);
}
}
_ => {}
}
parsed_value = format!("({formated})");
}
}
if parsed_value.is_empty() {
parsed_value = "()".to_string();
}
}
```rust no_run
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/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)
> Note: the legacy mode support will be dropped in future versions, and the default behaviour will be NO legacy mode
## SystemSet ## SystemSet

View File

@ -10,13 +10,7 @@ pub use process_gltfs::*;
pub mod blender_settings; pub mod blender_settings;
use bevy::{ use bevy::{
app::Startup, ecs::{component::Component, reflect::ReflectComponent, system::Resource},
ecs::{
component::Component,
reflect::ReflectComponent,
system::{Res, Resource},
},
log::warn,
prelude::{App, IntoSystemConfigs, Plugin, SystemSet, Update}, prelude::{App, IntoSystemConfigs, Plugin, SystemSet, Update},
reflect::Reflect, reflect::Reflect,
}; };
@ -66,23 +60,13 @@ pub enum GltfComponentsSet {
} }
#[derive(Clone, Resource)] #[derive(Clone, Resource)]
pub struct GltfComponentsConfig { pub struct GltfComponentsConfig {}
pub(crate) legacy_mode: bool,
}
pub struct ComponentsFromGltfPlugin { pub struct ComponentsFromGltfPlugin {}
pub legacy_mode: bool,
}
impl Default for ComponentsFromGltfPlugin { impl Default for ComponentsFromGltfPlugin {
fn default() -> Self { fn default() -> Self {
Self { legacy_mode: true } Self {}
}
}
fn check_for_legacy_mode(gltf_components_config: Res<GltfComponentsConfig>) {
if gltf_components_config.legacy_mode {
warn!("using simplified component definitions is deprecated since 0.3, prefer defining components with real ron values (use the bevy_components tool for Blender for simplicity) ");
} }
} }
@ -90,10 +74,7 @@ impl Plugin for ComponentsFromGltfPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins(blender_settings::plugin) app.add_plugins(blender_settings::plugin)
.register_type::<GltfProcessed>() .register_type::<GltfProcessed>()
.insert_resource(GltfComponentsConfig { .insert_resource(GltfComponentsConfig {})
legacy_mode: self.legacy_mode,
})
.add_systems(Startup, check_for_legacy_mode)
.add_systems( .add_systems(
Update, Update,
(add_components_from_gltf_extras).in_set(GltfComponentsSet::Injection), (add_components_from_gltf_extras).in_set(GltfComponentsSet::Injection),

View File

@ -13,7 +13,7 @@ use bevy::{
utils::HashMap, utils::HashMap,
}; };
use crate::{ronstring_to_reflect_component, GltfComponentsConfig, GltfProcessed}; use crate::{ronstring_to_reflect_component, GltfProcessed};
/// main function: injects components into each entity in gltf files that have `gltf_extras`, using reflection /// main function: injects components into each entity in gltf files that have `gltf_extras`, using reflection
pub fn add_components_from_gltf_extras(world: &mut World) { pub fn add_components_from_gltf_extras(world: &mut World) {
@ -22,7 +22,7 @@ pub fn add_components_from_gltf_extras(world: &mut World) {
let mut entity_components: HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>> = let mut entity_components: HashMap<Entity, Vec<(Box<dyn Reflect>, TypeRegistration)>> =
HashMap::new(); HashMap::new();
let gltf_components_config = world.resource::<GltfComponentsConfig>(); // let gltf_components_config = world.resource::<GltfComponentsConfig>();
for (entity, name, extra, parent) in extras.iter(world) { for (entity, name, extra, parent) in extras.iter(world) {
debug!( debug!(
@ -33,11 +33,7 @@ pub fn add_components_from_gltf_extras(world: &mut World) {
let type_registry: &AppTypeRegistry = world.resource(); let type_registry: &AppTypeRegistry = world.resource();
let type_registry = type_registry.read(); let type_registry = type_registry.read();
let reflect_components = ronstring_to_reflect_component( let reflect_components = ronstring_to_reflect_component(&extra.value, &type_registry);
&extra.value,
&type_registry,
gltf_components_config.legacy_mode,
);
// we assign the components specified /xxx_components objects to their parent node // we assign the components specified /xxx_components objects to their parent node
let mut target_entity = entity; let mut target_entity = entity;

View File

@ -1,6 +1,6 @@
use bevy::log::{debug, warn}; use bevy::log::{debug, warn};
use bevy::reflect::serde::UntypedReflectDeserializer; use bevy::reflect::serde::UntypedReflectDeserializer;
use bevy::reflect::{Reflect, TypeInfo, TypeRegistration, TypeRegistry}; use bevy::reflect::{Reflect, TypeRegistration, TypeRegistry};
use bevy::utils::HashMap; use bevy::utils::HashMap;
use ron::Value; use ron::Value;
use serde::de::DeserializeSeed; use serde::de::DeserializeSeed;
@ -10,7 +10,6 @@ use super::capitalize_first_letter;
pub fn ronstring_to_reflect_component( pub fn ronstring_to_reflect_component(
ron_string: &str, ron_string: &str,
type_registry: &TypeRegistry, type_registry: &TypeRegistry,
simplified_types: bool,
) -> Vec<(Box<dyn Reflect>, TypeRegistration)> { ) -> Vec<(Box<dyn Reflect>, TypeRegistration)> {
let lookup: HashMap<String, Value> = ron::from_str(ron_string).unwrap(); let lookup: HashMap<String, Value> = ron::from_str(ron_string).unwrap();
let mut components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new(); let mut components: Vec<(Box<dyn Reflect>, TypeRegistration)> = Vec::new();
@ -18,7 +17,7 @@ pub fn ronstring_to_reflect_component(
let type_string = key.replace("component: ", "").trim().to_string(); let type_string = key.replace("component: ", "").trim().to_string();
let capitalized_type_name = capitalize_first_letter(type_string.as_str()); let capitalized_type_name = capitalize_first_letter(type_string.as_str());
let mut parsed_value: String; let parsed_value: String;
match value.clone() { match value.clone() {
Value::String(str) => { Value::String(str) => {
parsed_value = str; parsed_value = str;
@ -30,72 +29,7 @@ pub fn ronstring_to_reflect_component(
type_registry.get_with_short_type_path(capitalized_type_name.as_str()) type_registry.get_with_short_type_path(capitalized_type_name.as_str())
{ {
debug!("TYPE INFO {:?}", type_registration.type_info()); debug!("TYPE INFO {:?}", type_registration.type_info());
if simplified_types {
if let TypeInfo::TupleStruct(info) = type_registration.type_info() {
// we handle tupple strucs with only one field differently, as Blender's custom properties with custom ui (float, int, bool, etc) always give us a tupple struct
if info.field_len() == 1 {
let field = info
.field_at(0)
.expect("we should always have at least one field here");
let field_name = field.type_path();
let mut formated = parsed_value.clone();
match field_name {
"f32" => {
formated = parsed_value.parse::<f32>().unwrap().to_string();
}
"f64" => {
formated = parsed_value.parse::<f64>().unwrap().to_string();
}
"u8" => {
formated = parsed_value.parse::<u8>().unwrap().to_string();
}
"u16" => {
formated = parsed_value.parse::<u16>().unwrap().to_string();
}
"u32" => {
formated = parsed_value.parse::<u32>().unwrap().to_string();
}
"u64" => {
formated = parsed_value.parse::<u64>().unwrap().to_string();
}
"u128" => {
formated = parsed_value.parse::<u128>().unwrap().to_string();
}
"glam::Vec2" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
formated = format!("(x:{},y:{})", parsed[0], parsed[1]);
}
"glam::Vec3" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
formated =
format!("(x:{},y:{},z:{})", parsed[0], parsed[1], parsed[2]);
}
"bevy_render::color::Color" => {
let parsed: Vec<f32> = ron::from_str(&parsed_value).unwrap();
if parsed.len() == 3 {
formated = format!(
"Rgba(red:{},green:{},blue:{}, alpha: 1.0)",
parsed[0], parsed[1], parsed[2]
);
}
if parsed.len() == 4 {
formated = format!(
"Rgba(red:{},green:{},blue:{}, alpha:{})",
parsed[0], parsed[1], parsed[2], parsed[3]
);
}
}
_ => {}
}
parsed_value = format!("({formated})");
}
}
if parsed_value.is_empty() {
parsed_value = "()".to_string();
}
}
let ron_string = format!( let ron_string = format!(
"{{ \"{}\":{} }}", "{{ \"{}\":{} }}",
type_registration.type_info().type_path(), type_registration.type_info().type_path(),

View File

@ -100,8 +100,6 @@ fn main() {
All examples are here: All examples are here:
> the examples use ```bevy_gltf_blueprints``` with the **legacy_mode** set to **FALSE** as the new custom properties generated by the Blender add-on require newer/ non legacy logic.
- https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_registry_export/basic - https://github.com/kaosat-dev/Blender_bevy_components_workflow/tree/main/examples/bevy_registry_export/basic

View File

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

View File

@ -2960,6 +2960,22 @@
"type": "object", "type": "object",
"typeInfo": "Struct" "typeInfo": "Struct"
}, },
"bevy_example::dupe_components::EnumTest": {
"isComponent": true,
"isResource": false,
"oneOf": [
"Metal",
"Wood",
"Rock",
"Cloth",
"Squishy",
"None"
],
"short_name": "EnumTest",
"title": "bevy_example::dupe_components::EnumTest",
"type": "string",
"typeInfo": "Enum"
},
"bevy_example::game::animation::Marker1": { "bevy_example::game::animation::Marker1": {
"additionalProperties": false, "additionalProperties": false,
"isComponent": true, "isComponent": true,

View File

@ -8,7 +8,6 @@ impl Plugin for CorePlugin {
app.add_plugins(( app.add_plugins((
ExportRegistryPlugin::default(), ExportRegistryPlugin::default(),
BlueprintsPlugin { BlueprintsPlugin {
legacy_mode: false,
library_folder: "blueprints".into(), library_folder: "blueprints".into(),
format: GltfFormat::GLB, format: GltfFormat::GLB,
material_library: true, material_library: true,

View File

@ -0,0 +1,13 @@
use bevy::prelude::*;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub enum EnumTest {
Metal,
Wood,
Rock,
Cloth,
Squishy,
#[default]
None,
}

View File

@ -7,6 +7,7 @@ use crate::core::*;
mod game; mod game;
use game::*; use game::*;
mod dupe_components;
mod test_components; mod test_components;
use test_components::*; use test_components::*;

View File

@ -1,3 +1,4 @@
use crate::dupe_components;
use bevy::{ use bevy::{
pbr::{ExtendedMaterial, MaterialExtension}, pbr::{ExtendedMaterial, MaterialExtension},
prelude::*, prelude::*,
@ -164,6 +165,7 @@ impl Plugin for ComponentsTestPlugin {
.register_type::<TupleVec2>() .register_type::<TupleVec2>()
.register_type::<TupleVec3>() .register_type::<TupleVec3>()
.register_type::<EnumTest>() .register_type::<EnumTest>()
.register_type::<dupe_components::EnumTest>()
.register_type::<TupleTestColor>() .register_type::<TupleTestColor>()
.register_type::<TupleVec>() .register_type::<TupleVec>()
.register_type::<Vec<String>>() .register_type::<Vec<String>>()

Binary file not shown.

View File

@ -276,14 +276,8 @@ given object is located)
> IMPORTANT !! use this if you have previously used v0.1 , as v0.2 had a breaking change, that makes it **necessary** to use this **once** to upgrade the UI data > IMPORTANT !! use this if you have previously used v0.1 , as v0.2 had a breaking change, that makes it **necessary** to use this **once** to upgrade the UI data
## Additional important information
> Note: the legacy mode support has been removed since version
- for the components to work correctly with [```bevy_gltf_components```](https://crates.io/crates/bevy_gltf_components) or [```bevy_gltf_blueprints```](https://crates.io/crates/bevy_gltf_blueprints) you will need to set the ```legacy_mode``` for those plugins to **FALSE**
as the component data generated by this add on is a complete, clean **ron** data that is incompatible with the previous (legacy versions).
Please see the documentation of those crates for more information.
> Note: the legacy mode support will be dropped in future versions, and the default behaviour will be NO legacy mode
## Examples ## Examples

View File

@ -115,11 +115,11 @@ UI:
- [x] check if output "string" in custom properties are correct - [x] check if output "string" in custom properties are correct
- gltf_auto_export - gltf_auto_export
- [ ] add support for "enabled" flag - [x] add support for "enabled" flag
- [ ] add special components - [ ] add special components
- "AutoExport" => Needed - "AutoExport" => Needed
- "Dynamic" ? naah wait that should be exported by the Bevy side - "Dynamic" ? naah wait that should be exported by the Bevy side
- [ ] filter out Components_meta ?? - [x] filter out Components_meta ??
- [x] add legacy mode to the persisted parameters - [x] add legacy mode to the persisted parameters
- bevy_gltf_components: - bevy_gltf_components:

View File

@ -12,14 +12,26 @@ class ComponentDefinitionsList(bpy.types.PropertyGroup):
items = [] items = []
type_infos = context.window_manager.components_registry.type_infos type_infos = context.window_manager.components_registry.type_infos
short_names = context.window_manager.components_registry.short_names_to_long_names short_names = context.window_manager.components_registry.short_names_to_long_names
for short_name in sorted(short_names.keys()): """for short_name in sorted(short_names.keys()):
long_name = short_names[short_name] long_name = short_names[short_name]
definition = type_infos[long_name] definition = type_infos[long_name]
is_component = definition['isComponent'] if "isComponent" in definition else False is_component = definition['isComponent'] if "isComponent" in definition else False
if self.filter in short_name and is_component:
if not 'Handle' in short_name and not "Cow" in short_name and not "AssetId" in short_name and short_name not in self.exclude: # FIXME: hard coded, seems wrong
items.append((long_name, short_name, long_name))"""
for long_name in type_infos.keys():
definition = type_infos[long_name]
short_name = definition["short_name"]
is_component = definition['isComponent'] if "isComponent" in definition else False
if self.filter in short_name and is_component: if self.filter in short_name and is_component:
if not 'Handle' in short_name and not "Cow" in short_name and not "AssetId" in short_name and short_name not in self.exclude: # FIXME: hard coded, seems wrong if not 'Handle' in short_name and not "Cow" in short_name and not "AssetId" in short_name and short_name not in self.exclude: # FIXME: hard coded, seems wrong
items.append((long_name, short_name, long_name)) items.append((long_name, short_name, long_name))
items.sort(key=lambda a: a[1])
return items return items
@classmethod @classmethod

View File

@ -72,11 +72,13 @@ def get_component_metadata_by_short_name(object, short_name):
# remove no longer valid metadata from object # remove no longer valid metadata from object
def cleanup_invalid_metadata(object): def cleanup_invalid_metadata(object):
bevy_components = json.loads(object['bevy_components']) if 'bevy_components' in object else {}
components_metadata = object.components_meta.components components_metadata = object.components_meta.components
to_remove = [] to_remove = []
for index, component_meta in enumerate(components_metadata): for index, component_meta in enumerate(components_metadata):
short_name = component_meta.name short_name = component_meta.name
if short_name not in object.keys(): long_name = component_meta.long_name
if long_name not in bevy_components.keys():
print("component:", short_name, "present in metadata, but not in object") print("component:", short_name, "present in metadata, but not in object")
to_remove.append(index) to_remove.append(index)
for index in to_remove: for index in to_remove:
@ -131,6 +133,20 @@ def add_metadata_to_components_without_metadata(object):
upsert_component_in_object(object, component_name, registry) upsert_component_in_object(object, component_name, registry)
import json
def inject_component(object, long_name, value):
if not 'bevy_components' in object:
object['bevy_components'] = '{}'
previous = json.loads(object['bevy_components'])
previous[long_name] = value
object['bevy_components'] = json.dumps(previous)
#object['bevy_components'][long_name] = value # Sigh, this does not work, hits Blender's 63 char length limit
def bla_component(object, long_name):
if 'bevy_components' in object:
current = json.loads(object['bevy_components'])
del current[long_name]
object['bevy_components'] = json.dumps(current)
# adds a component to an object (including metadata) using the provided component definition & optional value # adds a component to an object (including metadata) using the provided component definition & optional value
def add_component_to_object(object, component_definition, value=None): def add_component_to_object(object, component_definition, value=None):
@ -138,13 +154,13 @@ def add_component_to_object(object, component_definition, value=None):
if object is not None: if object is not None:
# print("add_component_to_object", component_definition) # print("add_component_to_object", component_definition)
long_name = component_definition["title"] long_name = component_definition["title"]
short_name = component_definition["short_name"]
registry = bpy.context.window_manager.components_registry registry = bpy.context.window_manager.components_registry
if not registry.has_type_infos(): if not registry.has_type_infos():
raise Exception('registry type infos have not been loaded yet or are missing !') raise Exception('registry type infos have not been loaded yet or are missing !')
definition = registry.type_infos[long_name] definition = registry.type_infos[long_name]
print("HEAAAY", value)
# now we use our pre_generated property groups to set the initial value of our custom property # now we use our pre_generated property groups to set the initial value of our custom property
(_, propertyGroup) = upsert_component_in_object(object, component_name=short_name, registry=registry) (_, propertyGroup) = upsert_component_in_object(object, long_name=long_name, registry=registry)
if value == None: if value == None:
value = property_group_value_to_custom_property_value(propertyGroup, definition, registry, None) value = property_group_value_to_custom_property_value(propertyGroup, definition, registry, None)
else: # we have provided a value, that is a raw , custom property value, to set the value of the propertyGroup else: # we have provided a value, that is a raw , custom property value, to set the value of the propertyGroup
@ -152,22 +168,25 @@ def add_component_to_object(object, component_definition, value=None):
property_group_value_from_custom_property_value(propertyGroup, definition, registry, value) property_group_value_from_custom_property_value(propertyGroup, definition, registry, value)
del object["__disable__update"] del object["__disable__update"]
object[short_name] = value # object[short_name] = value
ping_depsgraph_update(object) print("ADDING VAALUEEE", value)
inject_component(object, long_name, value)
#ping_depsgraph_update(object)
def upsert_component_in_object(object, component_name, registry): def upsert_component_in_object(object, long_name, registry):
# print("upsert_component_in_object", object, "component name", component_name) # print("upsert_component_in_object", object, "component name", component_name)
# TODO: upsert this part too ? # TODO: upsert this part too ?
target_components_metadata = object.components_meta.components target_components_metadata = object.components_meta.components
component_definition = find_component_definition_from_short_name(component_name) print("target_components_metadata", target_components_metadata)
component_definition = registry.type_infos.get(long_name, None)
if component_definition != None: if component_definition != None:
short_name = component_definition["short_name"] short_name = component_definition["short_name"]
long_name = component_definition["title"] long_name = component_definition["title"]
property_group_name = registry.get_propertyGroupName_from_shortName(short_name) property_group_name = registry.get_propertyGroupName_from_longName(long_name)
propertyGroup = None propertyGroup = None
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None) component_meta = next(filter(lambda component: component["long_name"] == long_name, target_components_metadata), None)
if not component_meta: if not component_meta:
component_meta = target_components_metadata.add() component_meta = target_components_metadata.add()
component_meta.name = short_name component_meta.name = short_name
@ -285,7 +304,7 @@ def apply_customProperty_values_to_object_propertyGroups(object):
# removes the given component from the object: removes both the custom property and the matching metadata from the object # removes the given component from the object: removes both the custom property and the matching metadata from the object
def remove_component_from_object(object, component_name): def remove_component_from_object(object, component_name):
del object[component_name] bla_component(object, component_name)
components_metadata = getattr(object, "components_meta", None) components_metadata = getattr(object, "components_meta", None)
if components_metadata == None: if components_metadata == None:
@ -294,8 +313,8 @@ def remove_component_from_object(object, component_name):
components_metadata = components_metadata.components components_metadata = components_metadata.components
to_remove = [] to_remove = []
for index, component_meta in enumerate(components_metadata): for index, component_meta in enumerate(components_metadata):
short_name = component_meta.name long_name = component_meta.long_name
if short_name == component_name: if long_name == component_name:
to_remove.append(index) to_remove.append(index)
break break
for index in to_remove: for index in to_remove:

View File

@ -114,7 +114,7 @@ class RemoveComponentOperator(Operator):
else: else:
object = bpy.data.objects[self.object_name] object = bpy.data.objects[self.object_name]
print("removing component ", self.component_name, "from object '"+object.name+"'") print("removing component ", self.component_name, "from object '"+object.name+"'")
if object is not None and self.component_name in object: if object is not None and 'bevy_components' in object and self.component_name in object['bevy_components']:
remove_component_from_object(object, self.component_name) remove_component_from_object(object, self.component_name)
else: else:
self.report({"ERROR"}, "The object/ component to remove ("+ self.component_name +") does not exist") self.report({"ERROR"}, "The object/ component to remove ("+ self.component_name +") does not exist")
@ -128,7 +128,7 @@ class RemoveComponentFromAllObjectsOperator(Operator):
bl_options = {"UNDO"} bl_options = {"UNDO"}
component_name: StringProperty( component_name: StringProperty(
name="component name", name="component name (long name)",
description="component to delete", description="component to delete",
) # type: ignore ) # type: ignore

View File

@ -151,11 +151,15 @@ class BEVY_COMPONENTS_PT_ComponentsPanel(bpy.types.Panel):
components_in_object = object.components_meta.components components_in_object = object.components_meta.components
for component_name in sorted(dict(object)) : # sorted by component name, practical components_bla = json.loads(object["bevy_components"]) if "bevy_components" in object else '{}'
#print("components_names", dict(components_bla).keys())
for component_name in sorted(dict(components_bla)) : # sorted by component name, practical
#print("component_name", component_name)
if component_name == "components_meta": if component_name == "components_meta":
continue continue
# anything withouth metadata gets skipped, we only want to see real components, not all custom props # anything withouth metadata gets skipped, we only want to see real components, not all custom props
component_meta = next(filter(lambda component: component["name"] == component_name, components_in_object), None) component_meta = next(filter(lambda component: component["long_name"] == component_name, components_in_object), None)
if component_meta == None: if component_meta == None:
continue continue
@ -173,9 +177,13 @@ class BEVY_COMPONENTS_PT_ComponentsPanel(bpy.types.Panel):
row.label(text=component_name) row.label(text=component_name)
# we fetch the matching ui property group # we fetch the matching ui property group
root_propertyGroup_name = registry.get_propertyGroupName_from_shortName(component_name) root_propertyGroup_name = registry.get_propertyGroupName_from_longName(component_name)
print("root_propertyGroup_name", root_propertyGroup_name)
print("component_meta", component_meta, component_invalid)
if root_propertyGroup_name: if root_propertyGroup_name:
propertyGroup = getattr(component_meta, root_propertyGroup_name, None) propertyGroup = getattr(component_meta, root_propertyGroup_name, None)
print("propertyGroup", propertyGroup)
if propertyGroup: if propertyGroup:
# if the component has only 0 or 1 field names, display inline, otherwise change layout # if the component has only 0 or 1 field names, display inline, otherwise change layout
single_field = len(propertyGroup.field_names) < 2 single_field = len(propertyGroup.field_names) < 2

View File

@ -74,7 +74,7 @@ def process_component(registry, definition, update, extras=None, nesting = []):
-BasicTest => the registration & update callback of this one overwrites the first "basicTest" -BasicTest => the registration & update callback of this one overwrites the first "basicTest"
have not found a cleaner workaround so far have not found a cleaner workaround so far
""" """
property_group_name = registry.generate_propGroup_name(nesting, short_name) property_group_name = registry.generate_propGroup_name(nesting, short_name, component_name)
(property_group_pointer, property_group_class) = property_group_from_infos(property_group_name, property_group_params) (property_group_pointer, property_group_class) = property_group_from_infos(property_group_name, property_group_params)
# add our component propertyGroup to the registry # add our component propertyGroup to the registry
registry.register_component_propertyGroup(property_group_name, property_group_pointer) registry.register_component_propertyGroup(property_group_name, property_group_pointer)

View File

@ -244,6 +244,7 @@ class ComponentsRegistry(PropertyGroup):
# cleanup previous data if any # cleanup previous data if any
self.propGroupIdCounter = 0 self.propGroupIdCounter = 0
self.short_names_to_propgroup_names.clear() self.short_names_to_propgroup_names.clear()
self.long_names_to_propgroup_names.clear()
self.missing_types_list.clear() self.missing_types_list.clear()
self.type_infos.clear() self.type_infos.clear()
self.type_infos_missing.clear() self.type_infos_missing.clear()
@ -330,10 +331,11 @@ class ComponentsRegistry(PropertyGroup):
default=0 default=0
) # type: ignore ) # type: ignore
short_names_to_propgroup_names = {} short_names_to_propgroup_names = {} # TODO; double check if needed, remove otherwise
long_names_to_propgroup_names = {}
# generate propGroup name from nesting level & shortName: each shortName + nesting is unique # generate propGroup name from nesting level & shortName: each shortName + nesting is unique
def generate_propGroup_name(self, nesting, shortName): def generate_propGroup_name(self, nesting, shortName, longName):
#print("gen propGroup name for", shortName, nesting) #print("gen propGroup name for", shortName, nesting)
#if shortName in self.short_names_to_propgroup_names and len(nesting) == 0: #if shortName in self.short_names_to_propgroup_names and len(nesting) == 0:
# return self.get_propertyGroupName_from_shortName(shortName) # return self.get_propertyGroupName_from_shortName(shortName)
@ -342,13 +344,22 @@ class ComponentsRegistry(PropertyGroup):
propGroupIndex = str(self.propGroupIdCounter) propGroupIndex = str(self.propGroupIdCounter)
propGroupName = propGroupIndex + "_ui" propGroupName = propGroupIndex + "_ui"
key = str(nesting) + shortName if len(nesting) > 0 else shortName
self.short_names_to_propgroup_names[key] = propGroupName #
"""key = str(nesting) + shortName if len(nesting) > 0 else shortName
self.short_names_to_propgroup_names[key] = propGroupName"""
# FIXME:
key = str(nesting) + longName if len(nesting) > 0 else longName
self.long_names_to_propgroup_names[longName] = propGroupName
return propGroupName return propGroupName
def get_propertyGroupName_from_shortName(self, shortName): def get_propertyGroupName_from_shortName(self, shortName):
return self.short_names_to_propgroup_names.get(shortName, None) return self.short_names_to_propgroup_names.get(shortName, None)
def get_propertyGroupName_from_longName(self, longName):
return self.long_names_to_propgroup_names.get(longName, None)
########### ###########
""" """

View File

@ -120,12 +120,6 @@ This issue has been resolved in v0.9.
- materials path: where to export materials to - materials path: where to export materials to
- Legacy mode for bevy: the export of custom properties is slightly different
when using bevy_gltf_components or bevy_gltf_blueprints with ```legacy_mode``` turned on (the default currently), toggle this on to keep using the older variant
> tldr: legacy mode in this add on should match your use of legacy mode on the Bevy side
> if you use the ```bevy_components``` add-on **legacy mode** should be turned **OFF**
* and your standard gltf export parameters in the **gltf** panel * and your standard gltf export parameters in the **gltf** panel
![blender addon use2](./docs/blender_addon_use2.png) ![blender addon use2](./docs/blender_addon_use2.png)

View File

@ -1,12 +1,9 @@
import json import json
import os
from types import SimpleNamespace
import bpy import bpy
from bpy.types import (PropertyGroup) from bpy.types import (PropertyGroup)
from bpy.props import (PointerProperty, IntProperty, StringProperty) from bpy.props import (PointerProperty, IntProperty, StringProperty)
from .did_export_settings_change import did_export_settings_change
from .get_blueprints_to_export import get_blueprints_to_export from .get_blueprints_to_export import get_blueprints_to_export
from ..constants import TEMPSCENE_PREFIX from ..constants import TEMPSCENE_PREFIX
@ -158,47 +155,6 @@ class AutoExportTracker(PropertyGroup):
# keep it simple, just use Simplenamespace for compatibility with the rest of our code # keep it simple, just use Simplenamespace for compatibility with the rest of our code
# TODO: debounce # TODO: debounce
"""export_settings_changed = did_export_settings_change()
tmp = {}
for k in AutoExportGltfAddonPreferences.__annotations__:
item = AutoExportGltfAddonPreferences.__annotations__[k]
default = item.keywords.get('default', None)
tmp[k] = default
auto_settings = get_auto_exporter_settings()
for k in auto_settings:
tmp[k] = auto_settings[k]
tmp['__annotations__'] = tmp
# path to the current blend file
file_path = bpy.data.filepath
# Get the folder
folder_path = os.path.dirname(file_path)
export_output_folder =tmp["export_output_folder"]
export_models_path = os.path.join(folder_path, export_output_folder)
export_blueprints_path = os.path.join(folder_path, export_output_folder, tmp["export_blueprints_path"]) if tmp["export_blueprints_path"] != '' else folder_path
tmp["export_blueprints_path"] = export_blueprints_path
tmp["export_models_path"] = export_models_path
addon_prefs = SimpleNamespace(**tmp)
#print("cls.changed_objects_per_scene", cls.changed_objects_per_scene)
(collections, collections_to_export, internal_collections, collections_per_scene) = get_blueprints_to_export(cls.changed_objects_per_scene, export_settings_changed, addon_prefs)
#print("collections to export", collections_to_export)
try:
# we save this list of collections in the context
bpy.context.window_manager.exportedCollections.clear()
#TODO: add error handling for this
for collection_name in collections_to_export:
ui_info = bpy.context.window_manager.exportedCollections.add()
ui_info.name = collection_name
except Exception as error:
pass
#self.report({"ERROR"}, "Failed to populate list of exported collections/blueprints")
"""
"""depsgraph = bpy.context.evaluated_depsgraph_get()
for update in depsgraph.updates:
print("update", update)"""
def disable_change_detection(self): def disable_change_detection(self):
#print("disable change detection") #print("disable change detection")
self.change_detection_enabled = False self.change_detection_enabled = False

View File

@ -6,6 +6,8 @@ import shutil
import pathlib import pathlib
import mathutils import mathutils
from .test_helpers import prepare_auto_export, run_auto_export_and_compare
@pytest.fixture @pytest.fixture
def setup_data(request): def setup_data(request):
print("\nSetting up resources...") print("\nSetting up resources...")
@ -56,85 +58,6 @@ def setup_data(request):
return None return None
def prepare_auto_export(auto_export_overrides={}):
# with change detection
# first, configure things
# we use the global settings for that
export_props = {
"main_scene_names" : ['World'],
"library_scene_names": ['Library'],
**auto_export_overrides
}
# store settings for the auto_export part
stored_auto_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_auto_settings.clear()
stored_auto_settings.write(json.dumps(export_props))
gltf_settings = {
"export_animations": False,
"export_optimize_animation_size": False
}
# and store settings for the gltf part
stored_gltf_settings = bpy.data.texts[".gltf_auto_export_gltf_settings"] if ".gltf_auto_export_gltf_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_gltf_settings")
stored_gltf_settings.clear()
stored_gltf_settings.write(json.dumps(gltf_settings))
def run_auto_export(setup_data):
auto_export_operator = bpy.ops.export_scenes.auto_gltf
auto_export_operator(
auto_export=True,
direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models",
export_scene_settings=True,
export_blueprints=True,
export_materials_library=False
)
levels_path = setup_data["levels_path"]
level_file_paths = list(map(lambda file_name: os.path.join(levels_path, file_name), sorted(os.listdir(levels_path)))) if os.path.exists(levels_path) else []
blueprints_path = setup_data["blueprints_path"]
blueprints_file_paths = list(map(lambda file_name: os.path.join(blueprints_path, file_name), sorted(os.listdir(blueprints_path)))) if os.path.exists(blueprints_path) else []
modification_times = list(map(lambda file_path: os.path.getmtime(file_path), blueprints_file_paths + level_file_paths))
# assert os.path.exists(world_file_path) == True
mapped_files_to_timestamps_and_index = {}
for (index, file_path) in enumerate(blueprints_file_paths + level_file_paths):
file_path = pathlib.Path(file_path).stem
mapped_files_to_timestamps_and_index[file_path] = (modification_times[index], index)
return (modification_times, mapped_files_to_timestamps_and_index)
def run_auto_export_and_compare(setup_data, changes, expected_changed_files = []):
(modification_times_first, mapped ) = run_auto_export(setup_data)
for index, change in enumerate(changes):
change()
(modification_times, mapped ) = run_auto_export(setup_data)
changed_files = expected_changed_files[index]
changed_file_indices = [mapped[changed_file][1] for changed_file in changed_files]
#print("changed files", changed_files, changed_file_indices, "mapped", mapped)
other_files_modification_times = [value for index, value in enumerate(modification_times) if index not in changed_file_indices]
other_files_modification_times_first = [value for index, value in enumerate(modification_times_first) if index not in changed_file_indices]
print("other_files_modification_times_new ", other_files_modification_times)
print("other_files_modification_times_first", other_files_modification_times_first)
for changed_file_index in changed_file_indices:
#print("modification_times_new [changed_file_index]", modification_times[changed_file_index])
#print("modification_times_first[changed_file_index]", modification_times_first[changed_file_index])
if changed_file_index in modification_times_first and changed_file_index in modification_times:
assert modification_times[changed_file_index] != modification_times_first[changed_file_index], f"failure in change: {index}, at file {changed_file_index}"
# TODO: we should throw an error in the "else" case ?
assert other_files_modification_times == other_files_modification_times_first , f"failure in change: {index}"
# reset the comparing
modification_times_first = modification_times
def test_export_change_tracking_custom_properties(setup_data): def test_export_change_tracking_custom_properties(setup_data):
# set things up # set things up
prepare_auto_export() prepare_auto_export()
@ -185,7 +108,7 @@ def test_export_change_tracking_custom_properties_collection_instances_combine_m
run_auto_export_and_compare( run_auto_export_and_compare(
setup_data=setup_data, setup_data=setup_data,
changes=[first_change, second_change, third_change, fourth_change], changes=[first_change, second_change, third_change, fourth_change],
expected_changed_files = [[], ["World"], ["World"], ["World"]] # only the "world" file should have changed expected_changed_files = [[], ["World"], ["World","Blueprint1"], ["World"]] # only the "world" file should have changed
) )

View File

@ -4,18 +4,38 @@ import json
import pytest import pytest
import shutil import shutil
from .test_helpers import prepare_auto_export
@pytest.fixture @pytest.fixture
def setup_data(request): def setup_data(request):
print("\nSetting up resources...") print("\nSetting up resources...")
def finalizer():
root_path = "../../testing/bevy_example" root_path = "../../testing/bevy_example"
assets_root_path = os.path.join(root_path, "assets") assets_root_path = os.path.join(root_path, "assets")
blueprints_path = os.path.join(assets_root_path, "blueprints")
levels_path = os.path.join(assets_root_path, "levels")
models_path = os.path.join(assets_root_path, "models") models_path = os.path.join(assets_root_path, "models")
materials_path = os.path.join(assets_root_path, "materials") materials_path = os.path.join(assets_root_path, "materials")
#other_materials_path = os.path.join("../../testing", "other_materials") #other_materials_path = os.path.join("../../testing", "other_materials")
yield {
"root_path": root_path,
"models_path": models_path,
"blueprints_path": blueprints_path,
"levels_path": levels_path,
"materials_path":materials_path
}
def finalizer():
print("\nPerforming teardown...") print("\nPerforming teardown...")
if os.path.exists(blueprints_path):
shutil.rmtree(blueprints_path)
if os.path.exists(levels_path):
shutil.rmtree(levels_path)
if os.path.exists(models_path): if os.path.exists(models_path):
shutil.rmtree(models_path) shutil.rmtree(models_path)
@ -49,11 +69,7 @@ def setup_data(request):
- removes generated files - removes generated files
""" """
def test_export_no_parameters(setup_data): def test_export_no_parameters(setup_data):
root_path = "../../testing/bevy_example"
assets_root_path = os.path.join(root_path, "assets")
models_path = os.path.join(assets_root_path, "models")
auto_export_operator = bpy.ops.export_scenes.auto_gltf auto_export_operator = bpy.ops.export_scenes.auto_gltf
# make sure to clear any parameters first # make sure to clear any parameters first
@ -61,23 +77,20 @@ def test_export_no_parameters(setup_data):
stored_auto_settings.clear() stored_auto_settings.clear()
stored_auto_settings.write(json.dumps({})) stored_auto_settings.write(json.dumps({}))
# first test exporting withouth any parameters set, this should not export anything # first test exporting without any parameters set, this should not export anything
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_materials_library=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_materials_library=True
) )
world_file_path = os.path.join(models_path, "World.glb") world_file_path = os.path.join(setup_data["levels_path"], "World.glb")
assert os.path.exists(world_file_path) != True assert os.path.exists(world_file_path) != True
def test_export_auto_export_parameters_only(setup_data): def test_export_auto_export_parameters_only(setup_data):
root_path = "../../testing/bevy_example"
assets_root_path = os.path.join(root_path, "assets")
models_path = os.path.join(assets_root_path, "models")
auto_export_operator = bpy.ops.export_scenes.auto_gltf auto_export_operator = bpy.ops.export_scenes.auto_gltf
export_props = { export_props = {
"main_scene_names" : ['World'], "main_scene_names" : ['World'],
"library_scene_names": ['Library'], "library_scene_names": ['Library'],
@ -91,17 +104,15 @@ def test_export_auto_export_parameters_only(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_materials_library=True export_materials_library=True
) )
world_file_path = os.path.join(models_path, "World.glb") world_file_path = os.path.join(setup_data["levels_path"], "World.glb")
assert os.path.exists(world_file_path) == True assert os.path.exists(world_file_path) == True
def test_export_changed_parameters(setup_data): def test_export_changed_parameters(setup_data):
root_path = "../../testing/bevy_example"
assets_root_path = os.path.join(root_path, "assets")
models_path = os.path.join(assets_root_path, "models")
auto_export_operator = bpy.ops.export_scenes.auto_gltf auto_export_operator = bpy.ops.export_scenes.auto_gltf
# with change detection # with change detection
@ -129,27 +140,26 @@ def test_export_changed_parameters(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,
export_materials_library=True export_materials_library=True
) )
world_file_path = os.path.join(models_path, "World.glb") world_file_path = os.path.join(setup_data["levels_path"], "World.glb")
assert os.path.exists(world_file_path) == True assert os.path.exists(world_file_path) == True
models_library_path = os.path.join(models_path, "library") blueprints_path = setup_data["blueprints_path"]
model_library_file_paths = list(map(lambda file_name: os.path.join(models_library_path, file_name), sorted(os.listdir(models_library_path)))) model_library_file_paths = list(map(lambda file_name: os.path.join(blueprints_path, file_name), sorted(os.listdir(blueprints_path))))
modification_times_first = list(map(lambda file_path: os.path.getmtime(file_path), model_library_file_paths)) modification_times_first = list(map(lambda file_path: os.path.getmtime(file_path), model_library_file_paths))
print("files", model_library_file_paths)
print("mod times", modification_times_first)
# export again, with no param changes: this should NOT export anything again, ie, modification times should be the same # export again, with no param changes: this should NOT export anything again, ie, modification times should be the same
print("second export") print("second export")
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,
@ -174,6 +184,7 @@ def test_export_changed_parameters(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,
@ -189,6 +200,7 @@ def test_export_changed_parameters(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,
@ -217,6 +229,7 @@ def test_export_changed_parameters(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,
@ -232,6 +245,7 @@ def test_export_changed_parameters(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,

View File

@ -11,15 +11,34 @@ def setup_data(request):
print("\nSetting up resources...") print("\nSetting up resources...")
root_path = "../../testing/bevy_example" root_path = "../../testing/bevy_example"
assets_root_path = os.path.join(root_path, "assets") assets_root_path = os.path.join(root_path, "assets")
blueprints_path = os.path.join(assets_root_path, "blueprints")
levels_path = os.path.join(assets_root_path, "levels")
models_path = os.path.join(assets_root_path, "models") models_path = os.path.join(assets_root_path, "models")
materials_path = os.path.join(assets_root_path, "materials") materials_path = os.path.join(assets_root_path, "materials")
other_materials_path = os.path.join(assets_root_path, "other_materials") other_materials_path = os.path.join(assets_root_path, "other_materials")
yield {"root_path": root_path, "assets_root_path": assets_root_path, "models_path": models_path, "materials_path": materials_path, "other_materials_path": other_materials_path} other_blueprints_path = os.path.join(assets_root_path, "other_blueprints")
yield {
"root_path": root_path,
"models_path": models_path,
"blueprints_path": blueprints_path,
"levels_path": levels_path,
"materials_path":materials_path,
"other_materials_path":other_materials_path,
"other_blueprints_path":other_blueprints_path
}
def finalizer(): def finalizer():
print("\nPerforming teardown...") print("\nPerforming teardown...")
if os.path.exists(blueprints_path):
shutil.rmtree(blueprints_path)
if os.path.exists(levels_path):
shutil.rmtree(levels_path)
if os.path.exists(models_path): if os.path.exists(models_path):
shutil.rmtree(models_path) shutil.rmtree(models_path)
@ -29,6 +48,9 @@ def setup_data(request):
if os.path.exists(other_materials_path): if os.path.exists(other_materials_path):
shutil.rmtree(other_materials_path) shutil.rmtree(other_materials_path)
if os.path.exists(other_blueprints_path):
shutil.rmtree(other_blueprints_path)
request.addfinalizer(finalizer) request.addfinalizer(finalizer)
@ -58,12 +80,13 @@ def test_export_do_not_export_blueprints(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_output_folder="./models", export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="assets/models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=False, 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"], "World.glb")) == True
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == False assert os.path.exists(os.path.join(setup_data["blueprints_path"],"Blueprint1.glb")) == False
orphan_data = get_orphan_data() orphan_data = get_orphan_data()
assert len(orphan_data) == 0 assert len(orphan_data) == 0
@ -84,13 +107,14 @@ def test_export_custom_blueprints_path(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,
export_blueprints_path = "another_library_path" export_blueprints_path = "assets/other_blueprints"
) )
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True assert os.path.exists(os.path.join(setup_data["levels_path"], "World.glb")) == True
assert os.path.exists(os.path.join(setup_data["models_path"], "another_library_path", "Blueprint1.glb")) == True assert os.path.exists(os.path.join(setup_data["root_path"],"assets", "other_blueprints", "Blueprint1.glb")) == True
assert len(get_orphan_data()) == 0 assert len(get_orphan_data()) == 0
def test_export_materials_library(setup_data): def test_export_materials_library(setup_data):
@ -109,13 +133,14 @@ def test_export_materials_library(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,
export_materials_library = True export_materials_library = True
) )
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == True assert os.path.exists(os.path.join(setup_data["blueprints_path"], "Blueprint1.glb")) == True
assert os.path.exists(os.path.join(setup_data["materials_path"], "testing_materials_library.glb")) == True assert os.path.exists(os.path.join(setup_data["materials_path"], "testing_materials_library.glb")) == True
assert len(get_orphan_data()) == 0 assert len(get_orphan_data()) == 0
@ -135,19 +160,20 @@ def test_export_materials_library_custom_path(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,
export_materials_library = True, export_materials_library = True,
export_materials_path="other_materials" export_materials_path="assets/other_materials"
) )
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == True assert os.path.exists(os.path.join(setup_data["blueprints_path"], "Blueprint1.glb")) == True
assert os.path.exists(os.path.join(setup_data["materials_path"], "testing_materials_library.glb")) == False assert os.path.exists(os.path.join(setup_data["materials_path"], "testing_materials_library.glb")) == False
assert os.path.exists(os.path.join(setup_data["other_materials_path"], "testing_materials_library.glb")) == True assert os.path.exists(os.path.join(setup_data["other_materials_path"], "testing_materials_library.glb")) == True
assert len(get_orphan_data()) == 0 assert len(get_orphan_data()) == 0
def test_export_collection_instances_combine_mode(setup_data): # TODO: change & check this def test_export_collection_instances_combine_mode(setup_data): # There is more in depth testing of this in the "change_tracking" tests
auto_export_operator = bpy.ops.export_scenes.auto_gltf auto_export_operator = bpy.ops.export_scenes.auto_gltf
# first, configure things # first, configure things
@ -166,13 +192,14 @@ def test_export_collection_instances_combine_mode(setup_data): # TODO: change &
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_blueprints=True, export_blueprints=True,
collection_instances_combine_mode = 'Embed' collection_instances_combine_mode = 'Embed'
) )
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True assert os.path.exists(os.path.join(setup_data["levels_path"], "World.glb")) == True
assert os.path.exists(os.path.join(setup_data["models_path"], "World_dynamic.glb")) == False assert os.path.exists(os.path.join(setup_data["levels_path"], "World_dynamic.glb")) == False
assert len(get_orphan_data()) == 0 assert len(get_orphan_data()) == 0
@ -192,17 +219,18 @@ def test_export_do_not_export_marked_assets(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,
export_marked_assets = False export_marked_assets = False
) )
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True assert os.path.exists(os.path.join(setup_data["levels_path"], "World.glb")) == True
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == True assert os.path.exists(os.path.join(setup_data["blueprints_path"], "Blueprint1.glb")) == True
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint2.glb")) == False assert os.path.exists(os.path.join(setup_data["blueprints_path"],"Blueprint2.glb")) == False
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint3.glb")) == True assert os.path.exists(os.path.join(setup_data["blueprints_path"],"Blueprint3.glb")) == True
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint4_nested.glb")) == True assert os.path.exists(os.path.join(setup_data["blueprints_path"],"Blueprint4_nested.glb")) == True
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint5.glb")) == False assert os.path.exists(os.path.join(setup_data["blueprints_path"],"Blueprint5.glb")) == False
assert len(get_orphan_data()) == 0 assert len(get_orphan_data()) == 0
@ -225,14 +253,15 @@ def test_export_separate_dynamic_and_static_objects(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,
export_separate_dynamic_and_static_objects = True export_separate_dynamic_and_static_objects = True
) )
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True assert os.path.exists(os.path.join(setup_data["levels_path"], "World.glb")) == True
assert os.path.exists(os.path.join(setup_data["models_path"], "World_dynamic.glb")) == True assert os.path.exists(os.path.join(setup_data["levels_path"], "World_dynamic.glb")) == True
assert len(get_orphan_data()) == 0 assert len(get_orphan_data()) == 0
@ -252,11 +281,12 @@ def test_export_should_not_generate_orphan_data(setup_data):
auto_export_operator( auto_export_operator(
auto_export=True, auto_export=True,
direct_mode=True, direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=False, export_blueprints=True,
) )
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True assert os.path.exists(os.path.join(setup_data["levels_path"], "World.glb")) == True
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == False assert os.path.exists(os.path.join(setup_data["blueprints_path"],"Blueprint1.glb")) == True
assert len(get_orphan_data()) == 0 assert len(get_orphan_data()) == 0

View File

@ -0,0 +1,78 @@
import bpy
import os
import json
import pathlib
def prepare_auto_export(auto_export_overrides={}, gltf_export_settings = {"export_animations": False, "export_optimize_animation_size": False}):
# with change detection
# first, configure things
# we use the global settings for that
export_props = {
"main_scene_names" : ['World'],
"library_scene_names": ['Library'],
**auto_export_overrides
}
# store settings for the auto_export part
stored_auto_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_auto_settings.clear()
stored_auto_settings.write(json.dumps(export_props))
gltf_settings = gltf_export_settings
# and store settings for the gltf part
stored_gltf_settings = bpy.data.texts[".gltf_auto_export_gltf_settings"] if ".gltf_auto_export_gltf_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_gltf_settings")
stored_gltf_settings.clear()
stored_gltf_settings.write(json.dumps(gltf_settings))
def run_auto_export(setup_data):
auto_export_operator = bpy.ops.export_scenes.auto_gltf
auto_export_operator(
auto_export=True,
direct_mode=True,
export_root_folder = os.path.abspath(setup_data["root_path"]),
export_output_folder="./models",
export_scene_settings=True,
export_blueprints=True,
export_materials_library=False
)
levels_path = setup_data["levels_path"]
level_file_paths = list(map(lambda file_name: os.path.join(levels_path, file_name), sorted(os.listdir(levels_path)))) if os.path.exists(levels_path) else []
blueprints_path = setup_data["blueprints_path"]
blueprints_file_paths = list(map(lambda file_name: os.path.join(blueprints_path, file_name), sorted(os.listdir(blueprints_path)))) if os.path.exists(blueprints_path) else []
modification_times = list(map(lambda file_path: os.path.getmtime(file_path), blueprints_file_paths + level_file_paths))
# assert os.path.exists(world_file_path) == True
mapped_files_to_timestamps_and_index = {}
for (index, file_path) in enumerate(blueprints_file_paths + level_file_paths):
file_path = pathlib.Path(file_path).stem
mapped_files_to_timestamps_and_index[file_path] = (modification_times[index], index)
return (modification_times, mapped_files_to_timestamps_and_index)
def run_auto_export_and_compare(setup_data, changes, expected_changed_files = []):
(modification_times_first, mapped ) = run_auto_export(setup_data)
for index, change in enumerate(changes):
change()
(modification_times, mapped ) = run_auto_export(setup_data)
changed_files = expected_changed_files[index]
changed_file_indices = [mapped[changed_file][1] for changed_file in changed_files]
print("changed files", changed_files, changed_file_indices, "mapped", mapped)
other_files_modification_times = [value for index, value in enumerate(modification_times) if index not in changed_file_indices]
other_files_modification_times_first = [value for index, value in enumerate(modification_times_first) if index not in changed_file_indices]
print("other_files_modification_times_new ", other_files_modification_times)
print("other_files_modification_times_first", other_files_modification_times_first)
for changed_file_index in changed_file_indices:
#print("modification_times_new [changed_file_index]", modification_times[changed_file_index])
#print("modification_times_first[changed_file_index]", modification_times_first[changed_file_index])
if changed_file_index in modification_times_first and changed_file_index in modification_times:
assert modification_times[changed_file_index] != modification_times_first[changed_file_index], f"failure in change: {index}, at file {changed_file_index}"
# TODO: we should throw an error in the "else" case ?
assert other_files_modification_times == other_files_modification_times_first , f"failure in change: {index}"
# reset the comparing
modification_times_first = modification_times

View File

@ -47,6 +47,12 @@
- [ ] update cleanup_materials - [ ] update cleanup_materials
- [x] remove legacy mode
- [x] from auto_export
- [x] from rust code
- [x] from examples
- [x] added notes & workaround information in docs
- [ ] remove bulk of tracker related code - [ ] remove bulk of tracker related code
- [ ] clean up - [ ] clean up
- [x] split up change detection in settings to its own panel - [x] split up change detection in settings to its own panel

View File

@ -58,7 +58,7 @@ class GLTF_PT_auto_export_changes_list(bpy.types.Panel):
class GLTF_PT_auto_export_blueprints_list(bpy.types.Panel): class GLTF_PT_auto_export_blueprints_list(bpy.types.Panel):
bl_space_type = 'VIEW_3D' bl_space_type = 'VIEW_3D'
bl_region_type = 'UI' bl_region_type = 'UI'
bl_label = "Blueprints to export" bl_label = "Blueprints"
bl_parent_id = "GLTF_PT_auto_export_SidePanel" bl_parent_id = "GLTF_PT_auto_export_SidePanel"
bl_options = {'DEFAULT_CLOSED'} bl_options = {'DEFAULT_CLOSED'}