mirror of
https://github.com/kaosat-dev/Blender_bevy_components_workflow.git
synced 2024-11-26 21:37:01 +00:00
Compare commits
5 Commits
f387fbec48
...
185c25f7b2
Author | SHA1 | Date | |
---|---|---|---|
|
185c25f7b2 | ||
|
dc053562bc | ||
|
49917e3b17 | ||
|
9138c81c60 | ||
|
eda18b7d25 |
@ -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
|
||||||
|
@ -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,77 +110,75 @@ 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::<MaterialInfo>()
|
||||||
.register_type::<BlueprintName>()
|
.register_type::<SpawnHere>()
|
||||||
.register_type::<MaterialInfo>()
|
.register_type::<BlueprintAnimations>()
|
||||||
.register_type::<SpawnHere>()
|
.register_type::<SceneAnimations>()
|
||||||
.register_type::<BlueprintAnimations>()
|
.register_type::<AnimationInfo>()
|
||||||
.register_type::<SceneAnimations>()
|
.register_type::<AnimationInfos>()
|
||||||
.register_type::<AnimationInfo>()
|
.register_type::<Vec<AnimationInfo>>()
|
||||||
.register_type::<AnimationInfos>()
|
.register_type::<AnimationMarkers>()
|
||||||
.register_type::<Vec<AnimationInfo>>()
|
.register_type::<HashMap<u32, Vec<String>>>()
|
||||||
.register_type::<AnimationMarkers>()
|
.register_type::<HashMap<String, HashMap<u32, Vec<String>>>>()
|
||||||
.register_type::<HashMap<u32, Vec<String>>>()
|
.add_event::<AnimationMarkerReached>()
|
||||||
.register_type::<HashMap<String, HashMap<u32, Vec<String>>>>()
|
.register_type::<BlueprintsList>()
|
||||||
.add_event::<AnimationMarkerReached>()
|
.register_type::<HashMap<String, Vec<String>>>()
|
||||||
.register_type::<BlueprintsList>()
|
.insert_resource(BluePrintsConfig {
|
||||||
.register_type::<HashMap<String, Vec<String>>>()
|
format: self.format,
|
||||||
.insert_resource(BluePrintsConfig {
|
library_folder: self.library_folder.clone(),
|
||||||
format: self.format,
|
|
||||||
library_folder: self.library_folder.clone(),
|
|
||||||
|
|
||||||
aabbs: self.aabbs,
|
aabbs: self.aabbs,
|
||||||
aabb_cache: HashMap::new(),
|
aabb_cache: HashMap::new(),
|
||||||
|
|
||||||
material_library: self.material_library,
|
material_library: self.material_library,
|
||||||
material_library_folder: self.material_library_folder.clone(),
|
material_library_folder: self.material_library_folder.clone(),
|
||||||
material_library_cache: HashMap::new(),
|
material_library_cache: HashMap::new(),
|
||||||
})
|
})
|
||||||
.configure_sets(
|
.configure_sets(
|
||||||
Update,
|
Update,
|
||||||
(GltfBlueprintsSet::Spawn, GltfBlueprintsSet::AfterSpawn)
|
(GltfBlueprintsSet::Spawn, GltfBlueprintsSet::AfterSpawn)
|
||||||
.chain()
|
|
||||||
.after(GltfComponentsSet::Injection),
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
(
|
|
||||||
(
|
|
||||||
prepare_blueprints,
|
|
||||||
check_for_loaded,
|
|
||||||
spawn_from_blueprints,
|
|
||||||
apply_deferred,
|
|
||||||
)
|
|
||||||
.chain(),
|
|
||||||
(compute_scene_aabbs, apply_deferred)
|
|
||||||
.chain()
|
.chain()
|
||||||
.run_if(aabbs_enabled),
|
.after(GltfComponentsSet::Injection),
|
||||||
apply_deferred,
|
|
||||||
(
|
|
||||||
materials_inject,
|
|
||||||
check_for_material_loaded,
|
|
||||||
materials_inject2,
|
|
||||||
)
|
|
||||||
.chain()
|
|
||||||
.run_if(materials_library_enabled),
|
|
||||||
)
|
)
|
||||||
.chain()
|
.add_systems(
|
||||||
.in_set(GltfBlueprintsSet::Spawn),
|
Update,
|
||||||
)
|
(
|
||||||
.add_systems(
|
(
|
||||||
Update,
|
prepare_blueprints,
|
||||||
(spawned_blueprint_post_process, apply_deferred)
|
check_for_loaded,
|
||||||
.chain()
|
spawn_from_blueprints,
|
||||||
.in_set(GltfBlueprintsSet::AfterSpawn),
|
apply_deferred,
|
||||||
)
|
)
|
||||||
.add_systems(
|
.chain(),
|
||||||
Update,
|
(compute_scene_aabbs, apply_deferred)
|
||||||
(
|
.chain()
|
||||||
trigger_instance_animation_markers_events,
|
.run_if(aabbs_enabled),
|
||||||
trigger_blueprint_animation_markers_events,
|
apply_deferred,
|
||||||
),
|
(
|
||||||
);
|
materials_inject,
|
||||||
|
check_for_material_loaded,
|
||||||
|
materials_inject2,
|
||||||
|
)
|
||||||
|
.chain()
|
||||||
|
.run_if(materials_library_enabled),
|
||||||
|
)
|
||||||
|
.chain()
|
||||||
|
.in_set(GltfBlueprintsSet::Spawn),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(spawned_blueprint_post_process, apply_deferred)
|
||||||
|
.chain()
|
||||||
|
.in_set(GltfBlueprintsSet::AfterSpawn),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
trigger_instance_animation_markers_events,
|
||||||
|
trigger_blueprint_animation_markers_events,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
@ -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;
|
||||||
|
@ -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(),
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
13
testing/bevy_example/src/dupe_components/mod.rs
Normal file
13
testing/bevy_example/src/dupe_components/mod.rs
Normal 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,
|
||||||
|
}
|
@ -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::*;
|
||||||
|
|
||||||
|
@ -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>>()
|
||||||
|
BIN
tools/assets/materials/testing_materials_library.glb
Normal file
BIN
tools/assets/materials/testing_materials_library.glb
Normal file
Binary file not shown.
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
###########
|
###########
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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...")
|
||||||
|
root_path = "../../testing/bevy_example"
|
||||||
|
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")
|
||||||
|
materials_path = os.path.join(assets_root_path, "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():
|
def finalizer():
|
||||||
root_path = "../../testing/bevy_example"
|
|
||||||
assets_root_path = os.path.join(root_path, "assets")
|
|
||||||
models_path = os.path.join(assets_root_path, "models")
|
|
||||||
materials_path = os.path.join(assets_root_path, "materials")
|
|
||||||
#other_materials_path = os.path.join("../../testing", "other_materials")
|
|
||||||
|
|
||||||
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,
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
78
tools/gltf_auto_export/tests/test_helpers.py
Normal file
78
tools/gltf_auto_export/tests/test_helpers.py
Normal 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
|
@ -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
|
@ -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'}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user