Merge branch 'main' of github.com:kaosat-dev/Blender_bevy_components_worklflow into bevy_main

This commit is contained in:
kaosat.dev 2024-03-04 09:09:13 +01:00
commit d00caaf414
64 changed files with 1246 additions and 841 deletions

View File

@ -16,7 +16,7 @@ members = [
"examples/bevy_gltf_save_load/basic/",
"examples/bevy_registry_export/basic",
"testing/bevy_registry_export/basic"
"testing/bevy_example"
]
resolver = "2"
@ -34,9 +34,6 @@ semicolon_if_nothing_returned = "warn"
[profile.dev.package."*"]
opt-level = 3
[profile.dev.package.bevy]
features = ["dynamic"]
#### --------------------Production/ release-------------------------------
[profile.release]
strip = "debuginfo"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gltf_blueprints"
version = "0.8.0"
version = "0.9.0"
authors = ["Mark 'kaosat-dev' Moissette"]
description = "Adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy."
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"

View File

@ -25,8 +25,8 @@ Here's a minimal usage example:
```toml
# Cargo.toml
[dependencies]
bevy="0.12"
bevy_gltf_blueprints = { version = "0.8"}
bevy="0.13"
bevy_gltf_blueprints = { version = "0.9"}
```
@ -64,7 +64,7 @@ fn spawn_blueprint(
Add the following to your `[dependencies]` section in `Cargo.toml`:
```toml
bevy_gltf_blueprints = "0.8"
bevy_gltf_blueprints = "0.9"
```
Or use `cargo add`:
@ -346,8 +346,8 @@ The main branch is compatible with the latest Bevy release, while the branch `be
Compatibility of `bevy_gltf_blueprints` versions:
| `bevy_gltf_blueprints` | `bevy` |
| :-- | :-- |
| `0.8` | `0.13` |
| `0.3 - 0.7` | `0.12` |
| `0.9` | `0.13` |
| `0.3 - 0.8` | `0.12` |
| `0.1 - 0.2` | `0.11` |
| branch `main` | `0.13` |
| branch `bevy_main` | `main` |

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gltf_components"
version = "0.4.0"
version = "0.5.0"
authors = ["Mark 'kaosat-dev' Moissette"]
description = "Allows you to define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side."
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"

View File

@ -24,7 +24,7 @@ Here's a minimal usage example:
# Cargo.toml
[dependencies]
bevy="0.13"
bevy_gltf_components = { version = "0.4"}
bevy_gltf_components = { version = "0.5"}
```
@ -60,7 +60,7 @@ bevy_gltf_components = { version = "0.4"}
Add the following to your `[dependencies]` section in `Cargo.toml`:
```toml
bevy_gltf_components = "0.4"
bevy_gltf_components = "0.5"
```
Or use `cargo add`:
@ -71,7 +71,7 @@ cargo add bevy_gltf_components
## Configuration
starting with version 0.4, this plugin is configurable
starting with version 0.3, this plugin is configurable
Use the default configuration:
```rust no_run
@ -117,8 +117,8 @@ The main branch is compatible with the latest Bevy release, while the branch `be
Compatibility of `bevy_gltf_components` versions:
| `bevy_gltf_components` | `bevy` |
| :-- | :-- |
| `0.4` | `0.13` |
| `0.2 - 0.3` | `0.12` |
| `0.5` | `0.13` |
| `0.2 - 0.4` | `0.12` |
| `0.1` | `0.11` |
| branch `main` | `0.13` |
| branch `bevy_main` | `main` |

View File

@ -8,7 +8,13 @@ pub mod process_gltfs;
pub use process_gltfs::*;
use bevy::{
<<<<<<< HEAD
ecs::{component::Component, reflect::ReflectComponent, system::Resource},
=======
app::Startup,
ecs::system::{Res, Resource},
log::warn,
>>>>>>> 9cb9dda5d35c635d367fa81ca1a6c752cda9bc02
prelude::{App, IntoSystemConfigs, Plugin, SystemSet, Update},
reflect::Reflect,
};
@ -72,12 +78,19 @@ impl Default for ComponentsFromGltfPlugin {
}
}
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) ");
}
}
impl Plugin for ComponentsFromGltfPlugin {
fn build(&self, app: &mut App) {
app.register_type::<GltfProcessed>()
.insert_resource(GltfComponentsConfig {
legacy_mode: self.legacy_mode,
})
.add_systems(Startup, check_for_legacy_mode)
.add_systems(
Update,
(add_components_from_gltf_extras).in_set(GltfComponentsSet::Injection),

View File

@ -91,14 +91,11 @@ pub fn ronstring_to_reflect_component(
parsed_value = format!("({formated})");
}
}
}
// FIXME: waaait this should be part of the legacy mode as it modifies the ron data ???
// so it means the values generated by the Blende add-on are incomplete (trivial to fix, but still)
if parsed_value.is_empty() {
parsed_value = "()".to_string();
}
}
let ron_string = format!(
"{{ \"{}\":{} }}",
type_registration.type_info().type_path(),
@ -106,7 +103,7 @@ pub fn ronstring_to_reflect_component(
);
// usefull to determine what an entity looks like Serialized
/*let test_struct = VecOfColors(vec![Color::Rgba { red: 0., green: 0.0, blue: 0.0, alpha: 0.0 }]);//TuppleTestColor(Color::Rgba { red: 0., green: 0.0, blue: 0.0, alpha: 0.0 });
/*let test_struct = CameraRenderGraph::new("name");
let serializer = ReflectSerializer::new(&test_struct, &type_registry);
let serialized =
ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gltf_save_load"
version = "0.3.0"
version = "0.4.0"
authors = ["Mark 'kaosat-dev' Moissette"]
description = "Save & load your bevy games"
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"
@ -18,5 +18,5 @@ bevy = { version = "0.13", default-features = false, features = ["bevy_asset", "
[dependencies]
bevy = { version = "0.13", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] }
#bevy_gltf_blueprints = "0.7"
#bevy_gltf_blueprints = "0.9"
bevy_gltf_blueprints = { path = "../bevy_gltf_blueprints" }

View File

@ -35,8 +35,8 @@ Here's a minimal usage example:
# Cargo.toml
[dependencies]
bevy="0.13"
bevy_gltf_save_load = "0.3"
bevy_gltf_blueprints = "0.6" // also needed
bevy_gltf_save_load = "0.4"
bevy_gltf_blueprints = "0.9" // also needed
```
```rust no_run
@ -142,7 +142,11 @@ Add the following to your `[dependencies]` section in `Cargo.toml`:
```toml
bevy_gltf_save_load = "0.3"
<<<<<<< HEAD
bevy_gltf_blueprints = "0.8" // also needed, as bevy_gltf_save_load does not re-export it at this time
=======
bevy_gltf_blueprints = "0.6" // also needed, as bevy_gltf_save_load does not re-export it at this time
>>>>>>> 9cb9dda5d35c635d367fa81ca1a6c752cda9bc02
```
@ -299,9 +303,9 @@ The main branch is compatible with the latest Bevy release, while the branch `be
Compatibility of `bevy_gltf_save_load` versions:
| `bevy_gltf_save_load` | `bevy` |
| :-- | :-- |
| `0.3 ` | `0.13` |
| `0.1 - 0.2` | `0.12` |
| branch `main` | `0.13` |
| `0.4 ` | `0.13` |
| `0.1 -0.3` | `0.12` |
| branch `main` | `0.12` |
| branch `bevy_main` | `main` |

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_registry_export"
version = "0.2.0"
version = "0.3.0"
authors = ["Mark 'kaosat-dev' Moissette", "Pascal 'Killercup' Hertleif"]
description = "Allows you to define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side."
homepage = "https://github.com/kaosat-dev/Blender_bevy_components_workflow"

View File

@ -18,7 +18,7 @@ Here's a minimal usage example:
# Cargo.toml
[dependencies]
bevy="0.13"
bevy_registry_export = "0.2"
bevy_registry_export = "0.3"
```
```rust no_run
@ -44,7 +44,7 @@ take a look at the [example]('https://github.com/kaosat-dev/Blender_bevy_compone
Add the following to your `[dependencies]` section in `Cargo.toml`:
```toml
bevy_registry_export = "0.2"
bevy_registry_export = "0.3"
```
@ -112,9 +112,10 @@ The main branch is compatible with the latest Bevy release, while the branch `be
Compatibility of `bevy_registry_export` versions:
| `bevy_registry_export` | `bevy` | `bevy_components (Blender add-on)` |
| :-- | :-- |:-- |
| `0.1 ` | `0.13` | `0.1 - 0.2` |
| `0.3 ` | `0.13` | `0.3` |
| `0.2 ` | `0.12` | `0.3` |
| `0.1 ` | `0.12` | `0.1 -0.2` |
| branch `main` | `0.13` | `0.1.0` |
| branch `main` | `0.12` | `0.1` |
| branch `bevy_main` | `main` | `n/a` |

View File

@ -1,14 +1,14 @@
[package]
name = "bevy_bevy_registry_export_basic_testing"
name = "bevy_example"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
bevy="0.12"
bevy_gltf_blueprints = { path = "../../../crates/bevy_gltf_blueprints" }
bevy_registry_export = { path = "../../../crates/bevy_registry_export" }
bevy_gltf_worlflow_examples_common = { path = "../../../examples/common" }
bevy_gltf_blueprints = { path = "../../crates/bevy_gltf_blueprints" }
bevy_registry_export = { path = "../../crates/bevy_registry_export" }
bevy_gltf_worlflow_examples_common = { path = "../../examples/common" }
bevy_rapier3d = { version = "0.23.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] }
bevy_asset_loader = { version = "0.18", features = ["standard_dynamic_assets" ]}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 KiB

View File

@ -0,0 +1,109 @@
pub mod in_game;
use std::{
fs::{self},
time::Duration,
};
use bevy_gltf_blueprints::{AnimationPlayerLink, BlueprintName};
pub use in_game::*;
use bevy::{
prelude::*, render::view::screenshot::ScreenshotManager, time::common_conditions::on_timer,
window::PrimaryWindow,
};
use bevy_gltf_worlflow_examples_common::{AppState, GameState};
use crate::{TupleTestF32, UnitTest};
fn start_game(mut next_app_state: ResMut<NextState<AppState>>) {
next_app_state.set(AppState::AppLoading);
}
// if the export from Blender worked correctly, we should have animations (simplified here by using AnimationPlayerLink)
// if the export from Blender worked correctly, we should have an Entity called "Cylinder" that has two components: UnitTest, TupleTestF32
// if the export from Blender worked correctly, we should have an Entity called "Blueprint4_nested" that has a child called "Blueprint3" that has a "BlueprintName" component with value Blueprint3
fn validate_export(
parents: Query<&Parent>,
children: Query<&Children>,
names: Query<&Name>,
blueprints: Query<(Entity, &Name, &BlueprintName)>,
animation_player_links: Query<(Entity, &AnimationPlayerLink)>,
exported_cylinder: Query<(Entity, &Name, &UnitTest, &TupleTestF32)>,
empties_candidates: Query<(Entity, &Name, &GlobalTransform)>,
) {
let animations_found = !animation_player_links.is_empty();
let mut cylinder_found = false;
if let Ok(nested_cylinder) = exported_cylinder.get_single() {
let parent_name = names
.get(parents.get(nested_cylinder.0).unwrap().get())
.unwrap();
cylinder_found = parent_name.to_string() == *"Cube.001"
&& nested_cylinder.1.to_string() == *"Cylinder"
&& nested_cylinder.3 .0 == 75.1;
}
let mut nested_blueprint_found = false;
for (entity, name, blueprint_name) in blueprints.iter() {
if name.to_string() == *"Blueprint4_nested" && blueprint_name.0 == *"Blueprint4_nested" {
if let Ok(cur_children) = children.get(entity) {
for child in cur_children.iter() {
if let Ok((_, child_name, child_blueprint_name)) = blueprints.get(*child) {
if child_name.to_string() == *"Blueprint3"
&& child_blueprint_name.0 == *"Blueprint3"
{
nested_blueprint_found = true;
}
}
}
}
}
}
let mut empty_found = false;
for (_, name, _) in empties_candidates.iter() {
if name.to_string() == *"Empty" {
empty_found = true;
break;
}
}
fs::write(
"bevy_diagnostics.json",
format!(
"{{ \"animations\": {}, \"cylinder_found\": {} , \"nested_blueprint_found\": {}, \"empty_found\": {} }}",
animations_found, cylinder_found, nested_blueprint_found, empty_found
),
)
.expect("Unable to write file");
}
fn generate_screenshot(
main_window: Query<Entity, With<PrimaryWindow>>,
mut screenshot_manager: ResMut<ScreenshotManager>,
) {
screenshot_manager
.save_screenshot_to_disk(main_window.single(), "screenshot.png")
.unwrap();
}
fn exit_game(mut app_exit_events: ResMut<Events<bevy::app::AppExit>>) {
app_exit_events.send(bevy::app::AppExit);
}
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, (spawn_test).run_if(in_state(GameState::InGame)))
.add_systems(Update, validate_export)
.add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once
.add_systems(OnEnter(AppState::MenuRunning), start_game)
.add_systems(OnEnter(AppState::AppRunning), setup_game)
.add_systems(
Update,
exit_game.run_if(on_timer(Duration::from_secs_f32(0.5))),
) // shut down the app after this time
;
}
}

View File

@ -7,11 +7,11 @@ use std::ops::Range;
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
struct UnitTest;
pub struct UnitTest;
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
#[reflect(Component)]
struct TupleTestF32(f32);
pub struct TupleTestF32(pub f32);
#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)]
#[reflect(Component)]

View File

@ -1,98 +0,0 @@
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common::{AppState, InMainMenu};
pub fn setup_main_menu(mut commands: Commands) {
commands.spawn((Camera2dBundle::default(), InMainMenu));
commands.spawn((
TextBundle::from_section(
"SOME GAME TITLE !!",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 18.0,
color: Color::WHITE,
..Default::default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(100.0),
left: Val::Px(200.0),
..default()
}),
InMainMenu,
));
commands.spawn((
TextBundle::from_section(
"New Game (press Enter to start, press T once the game is started for demo spawning)",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 18.0,
color: Color::WHITE,
..Default::default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(200.0),
left: Val::Px(200.0),
..default()
}),
InMainMenu,
));
/*
commands.spawn((
TextBundle::from_section(
"Load Game",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 18.0,
color: Color::WHITE,
..Default::default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(250.0),
left: Val::Px(200.0),
..default()
}),
InMainMenu
));
commands.spawn((
TextBundle::from_section(
"Exit Game",
TextStyle {
//font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 18.0,
color: Color::WHITE,
..Default::default()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(300.0),
left: Val::Px(200.0),
..default()
}),
InMainMenu
));*/
}
pub fn teardown_main_menu(bla: Query<Entity, With<InMainMenu>>, mut commands: Commands) {
for bli in bla.iter() {
commands.entity(bli).despawn_recursive();
}
}
pub fn main_menu(
keycode: Res<ButtonInput<KeyCode>>,
mut next_app_state: ResMut<NextState<AppState>>,
) {
if keycode.just_pressed(KeyCode::Enter) {
next_app_state.set(AppState::AppLoading);
}
}

View File

@ -1,22 +0,0 @@
pub mod in_game;
pub use in_game::*;
pub mod in_main_menu;
pub use in_main_menu::*;
use bevy::prelude::*;
use bevy_gltf_worlflow_examples_common::{AppState, GameState};
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(spawn_test, spawn_test_unregisted_components).run_if(in_state(GameState::InGame)),
)
.add_systems(OnEnter(AppState::MenuRunning), setup_main_menu)
.add_systems(OnExit(AppState::MenuRunning), teardown_main_menu)
.add_systems(Update, main_menu.run_if(in_state(AppState::MenuRunning)))
.add_systems(OnEnter(AppState::AppRunning), setup_game);
}
}

View File

@ -18,6 +18,8 @@ of your Bevy components you get a nicely packed custom_property to use with ...
> IMPORTANT !! if you have previously used v0.1 , v0.2 had a breaking change, please see [this](#regenerate-ui-values) section on how to upgrade your data to v0.2.\
This problem should not be present going forward
> IMPORTANT !! if you have previously used v0.2 , v0.3 had a breaking change, please see [this](#regenerate-custom-property-values) section on how to upgrade your data to v0.3.
## Installation:
* grab the latest release zip file from the releases tab (choose the bevy_components releases !)
@ -206,6 +208,9 @@ It will add the component to the select object
![update custom properties for all](./docs/other_options2.png)
> IMPORTANT !! use this if you have previously used v0.1 or v0.2 , as v0.3 had a breaking change, that makes it **necessary** to use this **once** to upgrade components data
You should also re-export your gltf files , otherwise you might run into issues
### regenerate UI values

View File

@ -192,7 +192,7 @@ UI:
- [x] fix enums (see Clusterconfig)
- [x] need an example with one tupple one struct
- [x] projection
- [ ] additionalmassproperties
- [x] additionalmassproperties
- [x] fix tupleStructs (see TupleVecF32F32) => always the same problem of having us pre-parse data without knowing what we have inside
- find a way to only split by level 0 (highest level) nesting "," seperators, ignoring any level of nesting until we dig one level deeper
- solve nesting level use issues

View File

@ -1,7 +1,7 @@
bl_info = {
"name": "bevy_components",
"author": "kaosigh",
"version": (0, 2, 0),
"version": (0, 3, 0),
"blender": (3, 4, 0),
"location": "VIEW_3D",
"description": "UI to help create Bevy blueprints and components",

View File

@ -6,6 +6,7 @@ conversion_tables = {
"char": lambda value: '"'+value+'"',
"str": lambda value: '"'+value+'"',
"alloc::string::String": lambda value: '"'+value+'"',
"alloc::borrow::Cow<str>": lambda value: '"'+value+'"',
"glam::Vec2": lambda value: "Vec2(x:"+str(value[0])+ ", y:"+str(value[1])+")",
"glam::DVec2": lambda value: "DVec2(x:"+str(value[0])+ ", y:"+str(value[1])+")",
@ -39,7 +40,7 @@ def property_group_value_to_custom_property_value(property_group, definition, re
elif type_info == "Struct":
values = {}
if len(property_group.field_names) ==0:
value = ''
value = '()'
else:
for index, field_name in enumerate(property_group.field_names):
item_type_name = definition["properties"][field_name]["type"]["$ref"].replace("#/$defs/", "")
@ -137,7 +138,6 @@ def property_group_value_to_custom_property_value(property_group, definition, re
value = conversion_tables[type_name](value) if is_value_type else value
value = '""' if isinstance(value, PropertyGroup) else value
#print("generating custom property value", value, type(value))
if isinstance(value, str):
value = value.replace("'", "")

View File

@ -161,6 +161,8 @@ type_mappings = {
"glam::Quat": lambda value: parse_vec4(value, float, "Quat"),
'alloc::string::String': lambda value: str(value.replace('"', "")),
'alloc::borrow::Cow<str>': lambda value: str(value.replace('"', "")),
'bevy_render::color::Color': lambda value: parse_color(value, float, "Rgba"),
'bevy_ecs::Entity': lambda value: int(value),
}

View File

@ -31,7 +31,7 @@ def toggle_watcher(self, context):
def watch_schema():
self = bpy.context.window_manager.components_registry
print("watching schema file for changes")
# print("watching schema file for changes")
try:
stamp = os.stat(self.schemaFullPath).st_mtime
stamp = str(stamp)

View File

@ -1,6 +1,6 @@
expected_custom_property_values = {'AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut': '',
expected_custom_property_values = {'AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut': '()',
'Aabb': '(center: Vec3A(x:0.0, y:0.0, z:0.0), half_extents: Vec3A(x:0.0, y:0.0, z:0.0))',
'AdditionalMassProperties': 'Mass(0.0)',
'AmbientLightSettings': '(brightness: 0.0, color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0))',
@ -14,20 +14,20 @@ expected_custom_property_values = {'AComponentWithAnExtremlyExageratedOrMaybeNot
'0.0))',
'BlueprintName': '(" ")',
'BorderColor': '(Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0))',
'Button': '',
'Button': '()',
'CalculatedClip': '(clip: (max: Vec2(x:0.0, y:0.0), min: Vec2(x:0.0, y:0.0)))',
'Camera': '(hdr: true, is_active: true, msaa_writeback: true, order: 0, viewport: None)',
'Camera2d': '(clear_color: Default)',
'Camera3d': '(clear_color: Default, depth_load_op: Clear(0.0), depth_texture_usages: "", '
'screen_space_specular_transmission_quality: "", screen_space_specular_transmission_steps: 0)',
'CameraRenderGraph': '( )',
'CameraTrackable': '',
'CameraRenderGraph': '(" ")',
'CameraTrackable': '()',
'CameraTracking': '(offset: Vec3(x:0.0, y:0.0, z:0.0))',
'CameraTrackingOffset': '(Vec3(x:0.0, y:0.0, z:0.0))',
'CascadeShadowConfig': '(bounds: [], minimum_distance: 0.0, overlap_proportion: 0.0)',
'Cascades': '(cascades: "")',
'CascadesFrusta': '',
'CascadesVisibleEntities': '',
'CascadesFrusta': '()',
'CascadesVisibleEntities': '()',
'Ccd': '(enabled: true)',
'Children': '([])',
'ClusterConfig': 'None',
@ -36,10 +36,10 @@ expected_custom_property_values = {'AComponentWithAnExtremlyExageratedOrMaybeNot
'CollisionGroups': '(filters: (0), memberships: (0))',
'ColorGrading': '(exposure: 0.0, gamma: 0.0, post_saturation: 0.0, pre_saturation: 0.0)',
'ContactForceEventThreshold': '(0.0)',
'ContentSize': '',
'ContentSize': '()',
'ContrastAdaptiveSharpeningSettings': '(denoise: true, enabled: true, sharpening_strength: 0.0)',
'CubemapFrusta': '',
'CubemapVisibleEntities': '',
'CubemapFrusta': '()',
'CubemapVisibleEntities': '()',
'Damping': '(angular_damping: 0.0, linear_damping: 0.0)',
'DebandDither': 'Disabled',
'DirectionalLight': '(color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), illuminance: 0.0, shadow_depth_bias: 0.0, '
@ -53,7 +53,7 @@ expected_custom_property_values = {'AComponentWithAnExtremlyExageratedOrMaybeNot
'FogSettings': '(color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), directional_light_color: Rgba(red:1.0, '
'green:1.0, blue:0.0, alpha:1.0), directional_light_exponent: 0.0, falloff: "")',
'Friction': '(coefficient: 0.0, combine_rule: "")',
'Frustum': '',
'Frustum': '()',
'Fxaa': '(edge_threshold: "", edge_threshold_min: "", enabled: true)',
'GlobalTransform': '((matrix3: (x_axis: Vec3A(x:0.0, y:0.0, z:0.0), y_axis: Vec3A(x:0.0, y:0.0, z:0.0), z_axis: '
'Vec3A(x:0.0, y:0.0, z:0.0)), translation: Vec3A(x:0.0, y:0.0, z:0.0)))',
@ -86,13 +86,13 @@ expected_custom_property_values = {'AComponentWithAnExtremlyExageratedOrMaybeNot
'Handle<WireframeMaterial>': 'Strong("")',
'InheritedVisibility': '(true)',
'Interaction': 'Pressed',
'Label': '',
'Label': '()',
'LockedAxes': '(0)',
'MaterialInfo': '(name: " ", source: " ")',
'Mesh2dHandle': '(Strong(""))',
'MeshMorphWeights': '(weights: [])',
'MorphWeights': '(first_mesh: "", weights: [])',
'Name': '(hash: 0, name: )',
'Name': '(hash: 0, name: " ")',
'NestedTupleStuff': '(0.0, 0, (basic: (a: 0.0, b: 0, c: " "), color: (Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0)), '
'colors_list: ([]), enable: true, enum_inner: Metal, nested: (vec: (Vec3(x:0.0, y:0.0, z:0.0))), '
'text: " ", toggle: (true)))',
@ -100,37 +100,37 @@ expected_custom_property_values = {'AComponentWithAnExtremlyExageratedOrMaybeNot
'colors_list: ([]), enable: true, enum_inner: Metal, nested: (vec: (Vec3(x:0.0, y:0.0, z:0.0))), '
'text: " ", toggle: (true))',
'NestingTestLevel3': '(vec: (Vec3(x:0.0, y:0.0, z:0.0)))',
'NoFrustumCulling': '',
'NoWireframe': '',
'NoFrustumCulling': '()',
'NoWireframe': '()',
'Node': '(calculated_size: Vec2(x:0.0, y:0.0), outline_offset: 0.0, outline_width: 0.0, stack_index: 0, '
'unrounded_size: Vec2(x:0.0, y:0.0))',
'NotShadowCaster': '',
'NotShadowReceiver': '',
'NotShadowCaster': '()',
'NotShadowReceiver': '()',
'OrthographicProjection': '(area: (max: Vec2(x:0.0, y:0.0), min: Vec2(x:0.0, y:0.0)), far: 0.0, near: 0.0, scale: '
'0.0, scaling_mode: Fixed(height: 0.0, width: 0.0), viewport_origin: Vec2(x:0.0, y:0.0))',
'Outline': '(color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), offset: Auto, width: Auto)',
'Parent': '(0)',
'PerspectiveProjection': '(aspect_ratio: 0.0, far: 0.0, fov: 0.0, near: 0.0)',
'Pickable': '',
'Player': '',
'Pickable': '()',
'Player': '()',
'PointLight': '(color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), intensity: 0.0, radius: 0.0, range: 0.0, '
'shadow_depth_bias: 0.0, shadow_normal_bias: 0.0, shadows_enabled: true)',
'PrimaryWindow': '',
'PrimaryWindow': '()',
'Projection': 'Perspective((aspect_ratio: 0.0, far: 0.0, fov: 0.0, near: 0.0))',
'RelativeCursorPosition': '(normalized: "", normalized_visible_node_rect: (max: Vec2(x:0.0, y:0.0), min: Vec2(x:0.0, '
'y:0.0)))',
'RenderLayers': '(0)',
'Restitution': '(coefficient: 0.0, combine_rule: "")',
'RigidBody': 'Dynamic',
'SSAOSettings': '',
'SSAOSettings': '()',
'ScreenSpaceAmbientOcclusionSettings': '(quality_level: "")',
'Sensor': '',
'Sensor': '()',
'ShadowFilteringMethod': 'Hardware2x2',
'ShadowmapSettings': '(size: 0)',
'SkinnedMesh': '(inverse_bindposes: Strong(""), joints: [])',
'Sleeping': '(angular_threshold: 0.0, linear_threshold: 0.0, sleeping: true)',
'SolverGroups': '(filters: (0), memberships: (0))',
'SpawnHere': '',
'SpawnHere': '()',
'SpotLight': '(color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), inner_angle: 0.0, intensity: 0.0, outer_angle: '
'0.0, radius: 0.0, range: 0.0, shadow_depth_bias: 0.0, shadow_normal_bias: 0.0, shadows_enabled: true)',
'Sprite': '(anchor: Center, color: Rgba(red:1.0, green:1.0, blue:0.0, alpha:1.0), custom_size: "", flip_x: true, '
@ -167,14 +167,14 @@ expected_custom_property_values = {'AComponentWithAnExtremlyExageratedOrMaybeNot
'UiImage': '(flip_x: true, flip_y: true, texture: Strong(""))',
'UiImageSize': '(size: Vec2(x:0.0, y:0.0))',
'UiTextureAtlasImage': '(flip_x: true, flip_y: true, index: 0)',
'UnitTest': '',
'UnitTest': '()',
'VecOfColors': '([])',
'VecOfF32s': '([])',
'VecOfVec3s2': '([])',
'Velocity': '(angvel: Vec3(x:0.0, y:0.0, z:0.0), linvel: Vec3(x:0.0, y:0.0, z:0.0))',
'ViewVisibility': '(true)',
'Visibility': 'Inherited',
'VisibleEntities': '',
'VisibleEntities': '()',
'Window': '(canvas: None, composite_alpha_mode: Auto, cursor: (grab_mode: None, hit_test: true, icon: Default, '
'visible: true), decorations: true, enabled_buttons: (close: true, maximize: true, minimize: true), '
'fit_canvas_to_parent: true, focused: true, ime_enabled: true, ime_position: Vec2(x:0.0, y:0.0), internal: '
@ -183,12 +183,13 @@ expected_custom_property_values = {'AComponentWithAnExtremlyExageratedOrMaybeNot
'resize_constraints: (max_height: 0.0, max_width: 0.0, min_height: 0.0, min_width: 0.0), resolution: '
'(physical_height: 0, physical_width: 0, scale_factor: 0.0, scale_factor_override: None), title: " ", '
'transparent: true, visible: true, window_level: AlwaysOnBottom, window_theme: "")',
'Wireframe': '',
'Wireframe': '()',
'ZIndex': 'Local(0)'}
expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut': '',
expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExageratedOrMaybeNotButCouldBeNameOrWut': '()',
'Aabb': '(center: Vec3A(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019), half_extents: '
'Vec3A(x:0.20609822869300842, y:0.8133212327957153, z:0.8235888481140137))',
'AdditionalMassProperties': 'Mass(0.42888906598091125)',
@ -207,22 +208,22 @@ expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExagerate
'BlueprintName': '("sbnpsago")',
'BorderColor': '(Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
'alpha:0.20609822869300842))',
'Button': '',
'Button': '()',
'CalculatedClip': '(clip: (max: Vec2(x:0.5714026093482971, y:0.42888906598091125), min: Vec2(x:0.5780913233757019, '
'y:0.20609822869300842)))',
'Camera': '(hdr: true, is_active: false, msaa_writeback: false, order: 61, viewport: None)',
'Camera2d': '(clear_color: None)',
'Camera3d': '(clear_color: None, depth_load_op: Clear(0.42888906598091125), depth_texture_usages: "", '
'screen_space_specular_transmission_quality: "", screen_space_specular_transmission_steps: 73)',
'CameraRenderGraph': '(sbnpsago)',
'CameraTrackable': '',
'CameraRenderGraph': '("sbnpsago")',
'CameraTrackable': '()',
'CameraTracking': '(offset: Vec3(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019))',
'CameraTrackingOffset': '(Vec3(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019))',
'CascadeShadowConfig': '(bounds: [0.42888906598091125], minimum_distance: 0.5780913233757019, overlap_proportion: '
'0.20609822869300842)',
'Cascades': '(cascades: "")',
'CascadesFrusta': '',
'CascadesVisibleEntities': '',
'CascadesFrusta': '()',
'CascadesVisibleEntities': '()',
'Ccd': '(enabled: true)',
'Children': '([0])',
'ClusterConfig': 'None',
@ -232,10 +233,10 @@ expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExagerate
'ColorGrading': '(exposure: 0.5714026093482971, gamma: 0.42888906598091125, post_saturation: 0.5780913233757019, '
'pre_saturation: 0.20609822869300842)',
'ContactForceEventThreshold': '(0.5714026093482971)',
'ContentSize': '',
'ContentSize': '()',
'ContrastAdaptiveSharpeningSettings': '(denoise: true, enabled: false, sharpening_strength: 0.42888906598091125)',
'CubemapFrusta': '',
'CubemapVisibleEntities': '',
'CubemapFrusta': '()',
'CubemapVisibleEntities': '()',
'Damping': '(angular_damping: 0.5714026093482971, linear_damping: 0.42888906598091125)',
'DebandDither': 'Disabled',
'DirectionalLight': '(color: Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
@ -254,7 +255,7 @@ expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExagerate
'green:0.8235888481140137, blue:0.6534725427627563, alpha:0.16022956371307373), '
'directional_light_exponent: 0.5206693410873413, falloff: "")',
'Friction': '(coefficient: 0.5714026093482971, combine_rule: "")',
'Frustum': '',
'Frustum': '()',
'Fxaa': '(edge_threshold: "", edge_threshold_min: "", enabled: true)',
'GlobalTransform': '((matrix3: (x_axis: Vec3A(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019), '
'y_axis: Vec3A(x:0.20609822869300842, y:0.8133212327957153, z:0.8235888481140137), z_axis: '
@ -289,13 +290,13 @@ expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExagerate
'Handle<WireframeMaterial>': 'Strong("")',
'InheritedVisibility': '(true)',
'Interaction': 'None',
'Label': '',
'Label': '()',
'LockedAxes': '(73)',
'MaterialInfo': '(name: "sbnpsago", source: "piuzfbqp")',
'Mesh2dHandle': '(Strong(""))',
'MeshMorphWeights': '(weights: [0.42888906598091125])',
'MorphWeights': '(first_mesh: "", weights: [0.42888906598091125])',
'Name': '(hash: 73, name: bnpsagop)',
'Name': '(hash: 73, name: "bnpsagop")',
'NestedTupleStuff': '(0.5714026093482971, 54, (basic: (a: 0.4825616776943207, b: 1, c: "gopiuzfb"), color: '
'(Rgba(red:0.5206693410873413, green:0.3277728259563446, blue:0.24999667704105377, '
'alpha:0.952816903591156)), colors_list: ([Rgba(red:0.0445563830435276, green:0.8601610660552979, '
@ -311,13 +312,13 @@ expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExagerate
'(vec: (Vec3(x:0.1329781413078308, y:0.7678378224372864, z:0.9824132323265076))), text: '
'"otmbsahe", toggle: (false))',
'NestingTestLevel3': '(vec: (Vec3(x:0.5714026093482971, y:0.42888906598091125, z:0.5780913233757019)))',
'NoFrustumCulling': '',
'NoWireframe': '',
'NoFrustumCulling': '()',
'NoWireframe': '()',
'Node': '(calculated_size: Vec2(x:0.5714026093482971, y:0.42888906598091125), outline_offset: 0.5780913233757019, '
'outline_width: 0.20609822869300842, stack_index: 62, unrounded_size: Vec2(x:0.8235888481140137, '
'y:0.6534725427627563))',
'NotShadowCaster': '',
'NotShadowReceiver': '',
'NotShadowCaster': '()',
'NotShadowReceiver': '()',
'OrthographicProjection': '(area: (max: Vec2(x:0.5714026093482971, y:0.42888906598091125), min: '
'Vec2(x:0.5780913233757019, y:0.20609822869300842)), far: 0.8133212327957153, near: '
'0.8235888481140137, scale: 0.6534725427627563, scaling_mode: '
@ -328,13 +329,13 @@ expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExagerate
'Parent': '(0)',
'PerspectiveProjection': '(aspect_ratio: 0.5714026093482971, far: 0.42888906598091125, fov: 0.5780913233757019, near: '
'0.20609822869300842)',
'Pickable': '',
'Player': '',
'Pickable': '()',
'Player': '()',
'PointLight': '(color: Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
'alpha:0.20609822869300842), intensity: 0.8133212327957153, radius: 0.8235888481140137, range: '
'0.6534725427627563, shadow_depth_bias: 0.16022956371307373, shadow_normal_bias: 0.5206693410873413, '
'shadows_enabled: false)',
'PrimaryWindow': '',
'PrimaryWindow': '()',
'Projection': 'Perspective((aspect_ratio: 0.42888906598091125, far: 0.5780913233757019, fov: 0.20609822869300842, '
'near: 0.8133212327957153))',
'RelativeCursorPosition': '(normalized: "", normalized_visible_node_rect: (max: Vec2(x:0.5714026093482971, '
@ -342,15 +343,15 @@ expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExagerate
'RenderLayers': '(73)',
'Restitution': '(coefficient: 0.5714026093482971, combine_rule: "")',
'RigidBody': 'Dynamic',
'SSAOSettings': '',
'SSAOSettings': '()',
'ScreenSpaceAmbientOcclusionSettings': '(quality_level: "")',
'Sensor': '',
'Sensor': '()',
'ShadowFilteringMethod': 'Jimenez14',
'ShadowmapSettings': '(size: 73)',
'SkinnedMesh': '(inverse_bindposes: Strong(""), joints: [0, 0])',
'Sleeping': '(angular_threshold: 0.5714026093482971, linear_threshold: 0.42888906598091125, sleeping: true)',
'SolverGroups': '(filters: (73), memberships: (4))',
'SpawnHere': '',
'SpawnHere': '()',
'SpotLight': '(color: Rgba(red:0.5714026093482971, green:0.42888906598091125, blue:0.5780913233757019, '
'alpha:0.20609822869300842), inner_angle: 0.8133212327957153, intensity: 0.8235888481140137, '
'outer_angle: 0.6534725427627563, radius: 0.16022956371307373, range: 0.5206693410873413, '
@ -402,7 +403,7 @@ expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExagerate
'UiImage': '(flip_x: true, flip_y: false, texture: Weak(Uuid(uuid: "73b3b118-7d01-4778-8bcc-4e79055f5d22")))',
'UiImageSize': '(size: Vec2(x:0.5714026093482971, y:0.42888906598091125))',
'UiTextureAtlasImage': '(flip_x: true, flip_y: false, index: 54)',
'UnitTest': '',
'UnitTest': '()',
'VecOfColors': '([Rgba(red:0.42888906598091125, green:0.5780913233757019, blue:0.20609822869300842, '
'alpha:0.8133212327957153)])',
'VecOfF32s': '([0.42888906598091125])',
@ -411,7 +412,7 @@ expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExagerate
'Vec3(x:0.20609822869300842, y:0.8133212327957153, z:0.8235888481140137))',
'ViewVisibility': '(true)',
'Visibility': 'Visible',
'VisibleEntities': '',
'VisibleEntities': '()',
'Window': '(canvas: None, composite_alpha_mode: PostMultiplied, cursor: (grab_mode: Confined, hit_test: true, icon: '
'Default, visible: false), decorations: false, enabled_buttons: (close: true, maximize: false, minimize: '
'true), fit_canvas_to_parent: false, focused: true, ime_enabled: true, ime_position: '
@ -423,5 +424,6 @@ expected_custom_property_values_randomized = {'AComponentWithAnExtremlyExagerate
'0.36258742213249207), resolution: (physical_height: 58, physical_width: 98, scale_factor: '
'0.8600491285324097, scale_factor_override: None), title: "otmbsahe", transparent: false, visible: true, '
'window_level: Normal, window_theme: "")',
'Wireframe': '',
'Wireframe': '()',
'ZIndex': 'Local(54)'}

View File

@ -0,0 +1,31 @@
import bpy
import pytest
@pytest.fixture
def setup_data(request):
print("\nSetting up resources...")
schemaPath = "../../testing/bevy_example/assets/registry.json"
yield {"schema_path": schemaPath}
def finalizer():
print("\nPerforming teardown...")
registry = bpy.context.window_manager.components_registry
type_infos = registry.type_infos
object = bpy.context.object
remove_component_operator = bpy.ops.object.remove_bevy_component
for type_name in type_infos:
definition = type_infos[type_name]
component_name = definition["short_name"]
if component_name in object:
try:
remove_component_operator(component_name=component_name)
except Exception as error:
pass
request.addfinalizer(finalizer)
return None

View File

@ -1,5 +1,4 @@
import bpy
import pytest
import pprint
from ..propGroups.conversions_to_prop_group import property_group_value_from_custom_property_value
@ -7,37 +6,11 @@ from ..propGroups.conversions_from_prop_group import property_group_value_to_cus
from .component_values_shuffler import component_values_shuffler
from .expected_component_values import (expected_custom_property_values, expected_custom_property_values_randomized)
@pytest.fixture
def setup_data(request):
print("\nSetting up resources...")
def finalizer():
print("\nPerforming teardown...")
registry = bpy.context.window_manager.components_registry
#registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
#bpy.ops.object.reload_registry()
type_infos = registry.type_infos
object = bpy.context.object
remove_component_operator = bpy.ops.object.remove_bevy_component
for type_name in type_infos:
definition = type_infos[type_name]
component_name = definition["short_name"]
if component_name in object:
try:
remove_component_operator(component_name=component_name)
except Exception as error:
pass
request.addfinalizer(finalizer)
return None
from .setup_data import setup_data
def test_components_should_generate_correct_custom_properties(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
registry.schemaPath = setup_data["schema_path"]
bpy.ops.object.reload_registry()
type_infos = registry.type_infos
@ -83,9 +56,9 @@ def test_components_should_generate_correct_custom_properties(setup_data):
assert len(added_components) == 152
def test_components_should_generate_correct_custom_properties_with_randomozied_values(setup_data):
def test_components_should_generate_correct_custom_properties_with_randomized_values(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
registry.schemaPath = setup_data["schema_path"]
bpy.ops.object.reload_registry()
type_infos = registry.type_infos
@ -136,7 +109,7 @@ def test_components_should_generate_correct_custom_properties_with_randomozied_v
def test_components_should_generate_correct_propertyGroup_values_from_custom_properties(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
registry.schemaPath = setup_data["schema_path"]
bpy.ops.object.reload_registry()
type_infos = registry.type_infos
@ -195,7 +168,7 @@ def test_components_should_generate_correct_propertyGroup_values_from_custom_pro
def test_remove_components(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
registry.schemaPath = setup_data["schema_path"]
bpy.ops.object.reload_registry()
type_infos = registry.type_infos
@ -244,10 +217,9 @@ def test_remove_components(setup_data):
def test_copy_paste_components(setup_data):
context = bpy.context
registry = context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
registry.schemaPath = setup_data["schema_path"]
bpy.ops.object.reload_registry()
#component_type = "bevy_bevy_registry_export_basic_example::test_components::BasicTest"
short_name = "BasicTest"
component_type = registry.short_names_to_long_names[short_name]
@ -294,4 +266,3 @@ def test_copy_paste_components(setup_data):
a_fieldValue = getattr(propertyGroup, propertyGroup.field_names[0])
assert a_fieldValue == 25.0

View File

@ -1,11 +1,10 @@
import bpy
from .setup_data import setup_data
def test_blend():
def test_blend(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
registry.schemaPath = setup_data["schema_path"]
bpy.ops.object.reload_registry()
#print("registry type infos", registry.type_infos)
short_name = "BasicTest"
component_type = registry.short_names_to_long_names[short_name]
@ -19,11 +18,6 @@ def test_blend():
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == short_name, target_components_metadata), None)
propertyGroup = getattr(component_meta, property_group_name, None)
print("propertyGroup", propertyGroup, propertyGroup.field_names)
"""copy_component_operator = bpy.ops.object.copy_bevy_component
copy_component_operator()"""
assert propertyGroup.field_names == ['a', 'b', 'c']

View File

@ -1,9 +1,10 @@
import bpy
from .component_values_shuffler import component_values_shuffler
from .setup_data import setup_data
def test_shuffler():
def test_shuffler(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = "../../testing/bevy_registry_export/basic/assets/registry.json"
registry.schemaPath = setup_data["schema_path"]
bpy.ops.object.reload_registry()
type_infos = registry.type_infos

View File

@ -1,7 +1,7 @@
bl_info = {
"name": "gltf_auto_export",
"author": "kaosigh",
"version": (0, 12, 1),
"version": (0, 14, 0),
"blender": (3, 4, 0),
"location": "File > Import-Export",
"description": "glTF/glb auto-export",

View File

@ -21,6 +21,9 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
folder_path = os.path.dirname(file_path)
# get the preferences for our addon
#should we use change detection or not
export_change_detection = getattr(addon_prefs, "export_change_detection")
export_blueprints = getattr(addon_prefs,"export_blueprints")
export_output_folder = getattr(addon_prefs,"export_output_folder")
@ -75,7 +78,7 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
if matching_collection is not None:
changed_collections.append(matching_collection)
collections_to_export = list(set(changed_collections + collections_not_on_disk))
collections_to_export = list(set(changed_collections + collections_not_on_disk)) if export_change_detection else collections
# we need to re_export everything if the export parameters have been changed
collections_to_export = collections if changed_export_parameters else collections_to_export
@ -110,14 +113,14 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
print("export MAIN scenes")
for scene_name in main_scene_names:
# we have more relaxed rules to determine if the main scenes have changed : any change is ok, (allows easier handling of changes, render settings etc)
do_export_main_scene = changed_export_parameters or scene_name in changes_per_scene.keys() or not check_if_blueprint_on_disk(scene_name, export_levels_path, gltf_extension)
do_export_main_scene = not export_change_detection or changed_export_parameters or scene_name in changes_per_scene.keys() or not check_if_blueprint_on_disk(scene_name, export_levels_path, gltf_extension)
if do_export_main_scene:
print(" exporting scene:", scene_name)
export_main_scene(bpy.data.scenes[scene_name], folder_path, addon_prefs, library_collections)
# now deal with blueprints/collections
do_export_library_scene = changed_export_parameters or len(collections_to_export) > 0 # export_library_scene_name in changes_per_scene.keys()
do_export_library_scene = not export_change_detection or changed_export_parameters or len(collections_to_export) > 0 # export_library_scene_name in changes_per_scene.keys()
print("export LIBRARY")
if do_export_library_scene:
# we only want to go through the library scenes where our collections to export are present

View File

@ -33,7 +33,8 @@ def generate_gltf_export_preferences(addon_prefs):
export_skins=True,
export_morph=False,
export_apply=False,
export_animations=False
export_animations=False,
export_optimize_animation_size=False
)
for key in addon_prefs.__annotations__.keys():

View File

@ -9,7 +9,8 @@ from ..helpers.helpers_collections import (get_exportable_collections)
from .auto_export import auto_export
class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
"""test"""
"""auto export gltf"""
#bl_idname = "object.xxx"
bl_idname = "export_scenes.auto_gltf"
bl_label = "Apply settings"
bl_options = {'PRESET', 'UNDO'}
@ -21,7 +22,7 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
'export_main_scene_name',
'export_output_folder',
'export_library_scene_name',
'export_change_detection',
'export_blueprints',
'export_blueprints_path',
@ -79,7 +80,17 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
# we inject all that we need, the above is not sufficient
for (k, v) in self.properties.items():
if k in self.white_list or k not in AutoExportGltfPreferenceNames:
export_props[k] = v
value = v
# FIXME: really weird having to do this
if k == "collection_instances_combine_mode":
value = self.collection_instances_combine_mode
if k == "export_format":
value = self.export_format
if k == "export_image_format":
value = self.export_image_format
if k == "export_materials":
value = self.export_materials
export_props[k] = value
# we add main & library scene names to our preferences
export_props['main_scene_names'] = list(map(lambda scene_data: scene_data.name, getattr(self,"main_scenes")))
@ -102,10 +113,10 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
self.will_save_settings = False
if settings:
#print("loading settings in invoke AutoExportGLTF", settings)
print("loading settings in invoke AutoExportGLTF", settings)
try:
for (k, v) in settings.items():
#print("loading setting", k, v)
print("loading setting", k, v)
setattr(self, k, v)
self.will_save_settings = True
@ -128,7 +139,8 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
item = library_scenes.add()
item.name = item_name
except (AttributeError, TypeError):
except Exception as error:
print("error", error)
self.report({"ERROR"}, "Loading export settings failed. Removed corrupted settings")
bpy.data.texts.remove(bpy.data.texts[".gltf_auto_export_settings"])

View File

@ -15,6 +15,7 @@ AutoExportGltfPreferenceNames = [
'export_main_scene_name',
'export_output_folder',
'export_library_scene_name',
'export_change_detection',
'export_blueprints',
'export_blueprints_path',
@ -39,7 +40,7 @@ AutoExportGltfPreferenceNames = [
'library_scene_names',
'previous_export_settings',
'filter_glob',
'will_save_settings'
'will_save_settings',
]
class AutoExportGltfAddonPreferences(AddonPreferences):
@ -86,6 +87,11 @@ class AutoExportGltfAddonPreferences(AddonPreferences):
description='The name of the library scene to auto export',
default='Library'
)
export_change_detection: BoolProperty(
name='Change detection',
description='Use change detection to determine what/if should be exported',
default=True
)
# scene components
export_scene_settings: BoolProperty(
name='Export scene settings',

View File

@ -2,18 +2,60 @@ import bpy
from .helpers_collections import (set_active_collection)
from .object_makers import (make_empty)
# these are mostly for when using this add-on together with the bevy_components add-on
custom_properties_to_filter_out = ['_combine', 'template', 'components_meta']
def is_component_valid(object, component_name):
if "components_meta" in object:
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == component_name, target_components_metadata), None)
if component_meta != None:
return component_meta.enabled and not component_meta.invalid
return True
def remove_unwanted_custom_properties(object):
to_remove = []
for component_name in object.keys():
if not is_component_valid(object, component_name):
to_remove.append(component_name)
for cp in custom_properties_to_filter_out + to_remove:
if cp in object:
del object[cp]
def duplicate_object(object):
obj_copy = object.copy()
if object.data:
data = object.data.copy()
obj_copy.data = data
if object.animation_data and object.animation_data.action:
obj_copy.animation_data.action = object.animation_data.action.copy()
return obj_copy
#also removes unwanted custom_properties for all objects in hiearchy
def duplicate_object_recursive(object, parent, collection):
original_name = object.name
object.name = original_name + "____bak"
copy = duplicate_object(object)
copy.name = original_name
collection.objects.link(copy)
remove_unwanted_custom_properties(copy)
if parent:
copy.parent = parent
for child in object.children:
duplicate_object_recursive(child, copy, collection)
return copy
# copies the contents of a collection into another one while replacing library instances with empties
def copy_hollowed_collection_into(source_collection, destination_collection, parent_empty=None, filter=None, library_collections=[], addon_prefs={}):
collection_instances_combine_mode = getattr(addon_prefs, "collection_instances_combine_mode")
legacy_mode = getattr(addon_prefs, "export_legacy_mode")
root_objects = []
special_properties= { # to be able to reset any special property afterwards
"combine": [],
}
collection_instances_combine_mode= collection_instances_combine_mode
for object in source_collection.objects:
if filter is not None and filter(object) is False:
continue
@ -29,25 +71,22 @@ def copy_hollowed_collection_into(source_collection, destination_collection, par
empty_obj = make_empty(original_name, object.location, object.rotation_euler, object.scale, destination_collection)
"""we inject the collection/blueprint name, as a component called 'BlueprintName', but we only do this in the empty, not the original object"""
empty_obj['BlueprintName'] = '"'+collection_name+'"' if legacy_mode else '("'+collection_name+'")'
empty_obj['SpawnHere'] = ''
empty_obj['SpawnHere'] = '()'
for k, v in object.items():
if k != 'template' or k != '_combine': # do not copy these properties
empty_obj[k] = v
# we copy custom properties over from our original object to our empty
for component_name, component_value in object.items():
if component_name not in custom_properties_to_filter_out and is_component_valid(object, component_name): #copy only valid properties
empty_obj[component_name] = component_value
if parent_empty is not None:
empty_obj.parent = parent_empty
else:
# we backup special properties that we do not want to export, and remove them
if '_combine' in object:
special_properties["combine"].append((object, object['_combine']))
del object['_combine']
# we create a copy of our object and its children, to leave the original one as it is
if object.parent == None:
copy = duplicate_object_recursive(object, None, destination_collection)
if parent_empty is not None:
object.parent = parent_empty
destination_collection.objects.link(object)
else:
root_objects.append(object)
destination_collection.objects.link(object)
copy.parent = parent_empty
# for every sub-collection of the source, copy its content into a new sub-collection of the destination
for collection in source_collection.children:
@ -58,7 +97,7 @@ def copy_hollowed_collection_into(source_collection, destination_collection, par
if parent_empty is not None:
collection_placeholder.parent = parent_empty
nested_results = copy_hollowed_collection_into(
copy_hollowed_collection_into(
source_collection = collection,
destination_collection = destination_collection,
parent_empty = collection_placeholder,
@ -66,19 +105,11 @@ def copy_hollowed_collection_into(source_collection, destination_collection, par
library_collections = library_collections,
addon_prefs=addon_prefs
)
sub_root_objects = nested_results["root_objects"]
sub_special_properties = nested_results["special_properties"]
root_objects.extend(sub_root_objects)
for s in sub_special_properties.keys():
if not s in special_properties.keys():
special_properties[s] = []
special_properties[s].extend(sub_special_properties[s])
return {"root_objects": root_objects, "special_properties": special_properties}
return {}
# clear & remove "hollow scene"
def clear_hollow_scene(temp_scene, original_root_collection, root_objects, special_properties):
def clear_hollow_scene(temp_scene, original_root_collection):
def restore_original_names(collection):
if collection.name.endswith("____bak"):
collection.name = collection.name.replace("____bak", "")
@ -86,6 +117,9 @@ def clear_hollow_scene(temp_scene, original_root_collection, root_objects, speci
if object.instance_type == 'COLLECTION':
if object.name.endswith("____bak"):
object.name = object.name.replace("____bak", "")
else:
if object.name.endswith("____bak"):
object.name = object.name.replace("____bak", "")
for child_collection in collection.children:
restore_original_names(child_collection)
@ -96,37 +130,15 @@ def clear_hollow_scene(temp_scene, original_root_collection, root_objects, speci
temp_root_collection = temp_scene.collection
temp_scene_objects = [o for o in temp_root_collection.objects]
for object in temp_scene_objects:
if object.type == 'EMPTY':
if hasattr(object, "SpawnHere"):
bpy.data.objects.remove(object, do_unlink=True)
else:
try:
temp_root_collection.objects.unlink(object)
except:
print("failed to unlink", object)
if object in root_objects:
pass
else:
bpy.data.objects.remove(object, do_unlink=True)
else:
temp_root_collection.objects.unlink(object)
# remove temporary collections
"""for collection in temporary_collections:
bpy.data.collections.remove(collection)"""
# put back special properties
for (object, value) in special_properties["combine"]:
object['_combine'] = value
# remove the temporary scene
bpy.data.scenes.remove(temp_scene)
# convenience utility to get lists of scenes
def get_scenes(addon_prefs):
level_scene_names= list(map(lambda scene: scene.name, getattr(addon_prefs,"main_scenes")))
library_scene_names = list(map(lambda scene: scene.name, getattr(addon_prefs,"library_scenes")))
level_scene_names= list(map(lambda scene: scene.name, getattr(addon_prefs,"main_scenes"))) # getattr(addon_prefs, "main_scene_names_compact").split(',')#
library_scene_names = list(map(lambda scene: scene.name, getattr(addon_prefs,"library_scenes"))) #getattr(addon_prefs, "main_scene_names_compact").split(',')#
level_scene_names = list(filter(lambda name: name in bpy.data.scenes, level_scene_names))
library_scene_names = list(filter(lambda name: name in bpy.data.scenes, library_scene_names))

View File

@ -220,3 +220,23 @@ def did_export_parameters_change(current_params, previous_params):
if not key in components_holder:
components_holder[key] = components[key]
"""
# potentially useful alternative
def duplicate_object2(object, original_name):
print("copy object", object)
with bpy.context.temp_override(object=object, active_object = object):
bpy.ops.object.duplicate(linked=False)
new_obj = bpy.context.active_object
print("new obj", new_obj, "bpy.context.view_layer", bpy.context.view_layer.objects)
for obj in bpy.context.view_layer.objects:
print("obj", obj)
bpy.context.view_layer.update()
new_obj.name = original_name
if object.animation_data:
print("OJECT ANIMATION")
new_obj.animation_data.action = object.animation_data.action.copy()
return new_obj

View File

@ -0,0 +1,9 @@
[pytest]
blender-template = ../../testing/bevy_example/assets/testing.blend
addopts = -svv
testpaths =
tests
# dependencies:
# pytest_blender
# pixelmatch

View File

View File

@ -0,0 +1,212 @@
import bpy
import os
import subprocess
import json
import pytest
import shutil
@pytest.fixture
def setup_data(request):
print("\nSetting up resources...")
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(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}
def finalizer():
print("\nPerforming teardown...")
if os.path.exists(models_path):
shutil.rmtree(models_path)
if os.path.exists(materials_path):
shutil.rmtree(materials_path)
if os.path.exists(other_materials_path):
shutil.rmtree(other_materials_path)
request.addfinalizer(finalizer)
return None
def test_export_do_not_export_blueprints(setup_data):
auto_export_operator = bpy.ops.export_scenes.auto_gltf
# first, configure things
# we use the global settings for that
export_props = {
"main_scene_names" : ['World'],
"library_scene_names": ['Library']
}
stored_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_settings")
stored_settings.clear()
stored_settings.write(json.dumps(export_props))
auto_export_operator(
direct_mode=True,
export_output_folder="./models",
export_scene_settings=True,
export_blueprints=False,
)
assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == False
def test_export_custom_blueprints_path(setup_data):
auto_export_operator = bpy.ops.export_scenes.auto_gltf
# first, configure things
# we use the global settings for that
export_props = {
"main_scene_names" : ['World'],
"library_scene_names": ['Library']
}
stored_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_settings")
stored_settings.clear()
stored_settings.write(json.dumps(export_props))
auto_export_operator(
direct_mode=True,
export_output_folder="./models",
export_scene_settings=True,
export_blueprints=True,
export_blueprints_path = "another_library_path"
)
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"], "another_library_path", "Blueprint1.glb")) == True
def test_export_materials_library(setup_data):
auto_export_operator = bpy.ops.export_scenes.auto_gltf
# first, configure things
# we use the global settings for that
export_props = {
"main_scene_names" : ['World'],
"library_scene_names": ['Library']
}
stored_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_settings")
stored_settings.clear()
stored_settings.write(json.dumps(export_props))
auto_export_operator(
direct_mode=True,
export_output_folder="./models",
export_scene_settings=True,
export_blueprints=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["materials_path"], "testing_materials_library.glb")) == True
def test_export_materials_library_custom_path(setup_data):
auto_export_operator = bpy.ops.export_scenes.auto_gltf
# first, configure things
# we use the global settings for that
export_props = {
"main_scene_names" : ['World'],
"library_scene_names": ['Library']
}
stored_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_settings")
stored_settings.clear()
stored_settings.write(json.dumps(export_props))
auto_export_operator(
direct_mode=True,
export_output_folder="./models",
export_scene_settings=True,
export_blueprints=True,
export_materials_library = True,
export_materials_path="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["materials_path"], "testing_materials_library.glb")) == False
assert os.path.exists(os.path.join(setup_data["other_materials_path"], "testing_materials_library.glb")) == True
def test_export_collection_instances_combine_mode(setup_data): # TODO: change & check this
auto_export_operator = bpy.ops.export_scenes.auto_gltf
# first, configure things
# we use the global settings for that
export_props = {
"main_scene_names" : ['World'],
"library_scene_names": ['Library']
}
stored_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_settings")
stored_settings.clear()
stored_settings.write(json.dumps(export_props))
bpy.data.objects["Cube"]["dynamic"] = True
auto_export_operator(
direct_mode=True,
export_output_folder="./models",
export_blueprints=True,
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["models_path"], "World_dynamic.glb")) == False
def test_export_do_not_export_marked_assets(setup_data):
auto_export_operator = bpy.ops.export_scenes.auto_gltf
# first, configure things
# we use the global settings for that
export_props = {
"main_scene_names" : ['World'],
"library_scene_names": ['Library']
}
stored_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_settings")
stored_settings.clear()
stored_settings.write(json.dumps(export_props))
auto_export_operator(
direct_mode=True,
export_output_folder="./models",
export_scene_settings=True,
export_blueprints=True,
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["models_path"], "library", "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["models_path"], "library", "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["models_path"], "library", "Blueprint5.glb")) == False
def test_export_separate_dynamic_and_static_objects(setup_data):
auto_export_operator = bpy.ops.export_scenes.auto_gltf
# first, configure things
# we use the global settings for that
export_props = {
"main_scene_names" : ['World'],
"library_scene_names": ['Library']
}
stored_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_settings")
stored_settings.clear()
stored_settings.write(json.dumps(export_props))
bpy.data.objects["Cube"]["dynamic"] = True
auto_export_operator(
direct_mode=True,
export_output_folder="./models",
export_scene_settings=True,
export_blueprints=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["models_path"], "World_dynamic.glb")) == True

View File

@ -0,0 +1,125 @@
import bpy
import os
import subprocess
import json
import pytest
import shutil
from PIL import Image
from pixelmatch.contrib.PIL import pixelmatch
@pytest.fixture
def setup_data(request):
print("\nSetting up resources...")
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("../../testing", "materials")
#other_materials_path = os.path.join("../../testing", "other_materials")
print("\nPerforming teardown...")
if os.path.exists(models_path):
shutil.rmtree(models_path)
"""if os.path.exists(materials_path):
shutil.rmtree(materials_path)
if os.path.exists(other_materials_path):
shutil.rmtree(other_materials_path)"""
diagnostics_file_path = os.path.join(root_path, "bevy_diagnostics.json")
if os.path.exists(diagnostics_file_path):
os.remove(diagnostics_file_path)
screenshot_observed_path = os.path.join(root_path, "screenshot.png")
if os.path.exists(screenshot_observed_path):
os.remove(screenshot_observed_path)
request.addfinalizer(finalizer)
return None
"""
- removes existing gltf files if needed
- calls exporter on the testing scene
- launches bevy app & checks for output
- if all worked => test is a-ok
"""
def test_export_complex(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
# with change detection
# first, configure things
# we use the global settings for that
export_props = {
"main_scene_names" : ['World'],
"library_scene_names": ['Library']
}
stored_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_settings")
stored_settings.clear()
stored_settings.write(json.dumps(export_props))
# move the main cube
bpy.data.objects["Cube"].location = [1, 0, 0]
# move the cube in the library
bpy.data.objects["Blueprint1_mesh"].location = [1, 2, 1]
auto_export_operator(
direct_mode=True,
export_output_folder="./models",
export_scene_settings=True,
export_blueprints=True,
export_legacy_mode=False,
export_animations=True
)
# blueprint1 => has an instance, got changed, should export
# blueprint2 => has NO instance, but marked as asset, should export
# blueprint3 => has NO instance, not marked as asset, used inside blueprint 4: should export
# blueprint4 => has an instance, with nested blueprint3, should export
# blueprint5 => has NO instance, not marked as asset, should NOT export
assert os.path.exists(os.path.join(models_path, "World.glb")) == True
assert os.path.exists(os.path.join(models_path, "library", "Blueprint1.glb")) == True
assert os.path.exists(os.path.join(models_path, "library", "Blueprint2.glb")) == True
assert os.path.exists(os.path.join(models_path, "library", "Blueprint3.glb")) == True
assert os.path.exists(os.path.join(models_path, "library", "Blueprint4_nested.glb")) == True
assert os.path.exists(os.path.join(models_path, "library", "Blueprint5.glb")) == False
assert os.path.exists(os.path.join(models_path, "library", "Blueprint6_animated.glb")) == True
assert os.path.exists(os.path.join(models_path, "library", "Blueprint7_hierarchy.glb")) == True
# now run bevy
bla = "cargo run --features bevy/dynamic_linking"
# assert getattr(propertyGroup, 'a') == 0.5714026093482971
FNULL = open(os.devnull, 'w') #use this if you want to suppress output to stdout from the subprocess
filename = "my_file.dat"
args = bla
#subprocess.call(args, stdout=FNULL, stderr=FNULL, shell=False, cwd=bevy_run_exec_path)
return_code = subprocess.call(["cargo", "run", "--features", "bevy/dynamic_linking"], cwd=root_path)
print("RETURN CODE OF BEVY APP", return_code)
assert return_code == 0
with open(os.path.join(root_path, "bevy_diagnostics.json")) as diagnostics_file:
diagnostics = json.load(diagnostics_file)
print("diagnostics", diagnostics)
assert diagnostics["animations"] == True
assert diagnostics["cylinder_found"] == True
assert diagnostics["empty_found"] == True
# last but not least, do a visual compare
screenshot_expected_path = os.path.join(root_path, "expected_screenshot.png")
screenshot_observed_path = os.path.join(root_path, "screenshot.png")
img_a = Image.open(screenshot_expected_path)
img_b = Image.open(screenshot_observed_path)
img_diff = Image.new("RGBA", img_a.size)
mismatch = pixelmatch(img_a, img_b, img_diff, includeAA=True)
print("image mismatch", mismatch)
assert mismatch < 50

View File

@ -37,7 +37,6 @@ class GLTF_PT_auto_export_main(bpy.types.Panel):
sfile = context.space_data
class GLTF_PT_auto_export_root(bpy.types.Panel):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS'
@ -66,6 +65,7 @@ class GLTF_PT_auto_export_root(bpy.types.Panel):
layout.active = operator.auto_export
layout.prop(operator, 'will_save_settings')
layout.prop(operator, "export_change_detection")
layout.prop(operator, "export_output_folder")
layout.prop(operator, "export_scene_settings")
layout.prop(operator, "export_legacy_mode")
@ -125,8 +125,6 @@ class GLTF_PT_auto_export_root(bpy.types.Panel):
remove_operator.scene_type = 'library'
col.separator()
class GLTF_PT_auto_export_blueprints(bpy.types.Panel):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS'