mirror of
https://github.com/kaosat-dev/Blender_bevy_components_workflow.git
synced 2024-12-22 23:54:10 +00:00
Merge branch 'main' of github.com:kaosat-dev/Blender_bevy_components_worklflow into bevy_main
This commit is contained in:
commit
d00caaf414
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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` |
|
||||
|
@ -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"
|
||||
|
@ -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` |
|
||||
|
@ -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),
|
||||
|
@ -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();
|
||||
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();
|
||||
|
@ -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" }
|
||||
|
@ -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` |
|
||||
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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.1 ` | `0.12` | `0.1 - 0.2` |
|
||||
| branch `main` | `0.13` | `0.1.0` |
|
||||
| `0.3 ` | `0.13` | `0.3` |
|
||||
| `0.2 ` | `0.12` | `0.3` |
|
||||
| `0.1 ` | `0.12` | `0.1 -0.2` |
|
||||
| branch `main` | `0.12` | `0.1` |
|
||||
| branch `bevy_main` | `main` | `n/a` |
|
||||
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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" ]}
|
File diff suppressed because it is too large
Load Diff
BIN
testing/bevy_example/assets/testing.blend
Normal file
BIN
testing/bevy_example/assets/testing.blend
Normal file
Binary file not shown.
BIN
testing/bevy_example/expected_screenshot.png
Normal file
BIN
testing/bevy_example/expected_screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 645 KiB |
109
testing/bevy_example/src/game/mod.rs
Normal file
109
testing/bevy_example/src/game/mod.rs
Normal 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
|
||||
;
|
||||
}
|
||||
}
|
@ -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)]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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("'", "")
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)'}
|
||||
|
||||
|
31
tools/bevy_components/tests/setup_data.py
Normal file
31
tools/bevy_components/tests/setup_data.py
Normal 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
|
@ -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
|
||||
|
||||
|
@ -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']
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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():
|
||||
|
@ -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")))
|
||||
@ -93,7 +104,7 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences, ExportHelper):
|
||||
#print("saving settings", bpy.data.texts[".gltf_auto_export_settings"].as_string(), "raw", json.dumps(export_props))
|
||||
|
||||
def load_settings(self, context):
|
||||
#print("loading settings")
|
||||
# print("loading settings")
|
||||
settings = None
|
||||
try:
|
||||
settings = bpy.data.texts[".gltf_auto_export_settings"].as_string()
|
||||
@ -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"])
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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']
|
||||
else:
|
||||
|
||||
# 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)
|
||||
if parent_empty is not None:
|
||||
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
|
||||
|
||||
bpy.data.objects.remove(object, do_unlink=True)
|
||||
# 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))
|
||||
|
@ -219,4 +219,24 @@ def did_export_parameters_change(current_params, previous_params):
|
||||
print("copying ", key,"to", components_holder)
|
||||
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
|
9
tools/gltf_auto_export/pytest.ini
Normal file
9
tools/gltf_auto_export/pytest.ini
Normal file
@ -0,0 +1,9 @@
|
||||
[pytest]
|
||||
blender-template = ../../testing/bevy_example/assets/testing.blend
|
||||
addopts = -svv
|
||||
testpaths =
|
||||
tests
|
||||
|
||||
# dependencies:
|
||||
# pytest_blender
|
||||
# pixelmatch
|
0
tools/gltf_auto_export/tests/__init__.py
Normal file
0
tools/gltf_auto_export/tests/__init__.py
Normal file
212
tools/gltf_auto_export/tests/test_basic.py
Normal file
212
tools/gltf_auto_export/tests/test_basic.py
Normal 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
|
125
tools/gltf_auto_export/tests/test_bevy_integration.py
Normal file
125
tools/gltf_auto_export/tests/test_bevy_integration.py
Normal 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
|
||||
|
||||
|
||||
|
@ -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")
|
||||
@ -124,8 +124,6 @@ class GLTF_PT_auto_export_root(bpy.types.Panel):
|
||||
remove_operator.action = 'REMOVE'
|
||||
remove_operator.scene_type = 'library'
|
||||
col.separator()
|
||||
|
||||
|
||||
|
||||
class GLTF_PT_auto_export_blueprints(bpy.types.Panel):
|
||||
bl_space_type = 'FILE_BROWSER'
|
||||
|
Loading…
Reference in New Issue
Block a user