test(auto_export):
* fleshed out testing of hierarchy * added export of serialized hierarchy from the bevy side & comparing it with a reference one on the python side * various tweaks
This commit is contained in:
parent
b51a0d38e2
commit
a001ee21f6
|
@ -14,3 +14,4 @@ bevy_rapier3d = { version = "0.25.0", features = ["serde-serialize", "debug-rend
|
||||||
bevy_asset_loader = { version = "0.20", features = ["standard_dynamic_assets"] }
|
bevy_asset_loader = { version = "0.20", features = ["standard_dynamic_assets"] }
|
||||||
bevy_editor_pls = { version = "0.8" }
|
bevy_editor_pls = { version = "0.8" }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
json-writer ="0.3"
|
|
@ -3516,6 +3516,17 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"typeInfo": "Struct"
|
"typeInfo": "Struct"
|
||||||
},
|
},
|
||||||
|
"bevy_gltf_blueprints::animation::Animated": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"isComponent": true,
|
||||||
|
"isResource": false,
|
||||||
|
"properties": {},
|
||||||
|
"required": [],
|
||||||
|
"short_name": "Animated",
|
||||||
|
"title": "bevy_gltf_blueprints::animation::Animated",
|
||||||
|
"type": "object",
|
||||||
|
"typeInfo": "Struct"
|
||||||
|
},
|
||||||
"bevy_gltf_blueprints::animation::Animations": {
|
"bevy_gltf_blueprints::animation::Animations": {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"isComponent": true,
|
"isComponent": true,
|
||||||
|
|
Binary file not shown.
|
@ -1,24 +1,30 @@
|
||||||
pub mod in_game;
|
pub mod in_game;
|
||||||
use std::{
|
use std::{
|
||||||
fs::{self},
|
collections::HashMap, fs, time::Duration
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy_gltf_blueprints::{AnimationPlayerLink, BlueprintName, BlueprintsList};
|
use bevy_gltf_blueprints::{AnimationPlayerLink, Animations, BlueprintName, BlueprintsList};
|
||||||
pub use in_game::*;
|
pub use in_game::*;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::*, render::view::screenshot::ScreenshotManager, time::common_conditions::on_timer,
|
ecs::query, prelude::*, render::view::screenshot::ScreenshotManager, time::common_conditions::on_timer, window::PrimaryWindow
|
||||||
window::PrimaryWindow,
|
|
||||||
};
|
};
|
||||||
use bevy_gltf_worlflow_examples_common_rapier::{AppState, GameState};
|
use bevy_gltf_worlflow_examples_common_rapier::{AppState, GameState};
|
||||||
|
|
||||||
use crate::{TupleTestF32, UnitTest};
|
use crate::{TupleTestF32, UnitTest};
|
||||||
|
use json_writer::to_json_string;
|
||||||
|
|
||||||
fn start_game(mut next_app_state: ResMut<NextState<AppState>>) {
|
fn start_game(mut next_app_state: ResMut<NextState<AppState>>) {
|
||||||
next_app_state.set(AppState::AppLoading);
|
next_app_state.set(AppState::AppLoading);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Node {
|
||||||
|
value: String,
|
||||||
|
children: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
// 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 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 "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
|
// 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
|
||||||
|
@ -34,19 +40,10 @@ fn validate_export(
|
||||||
empties_candidates: Query<(Entity, &Name, &GlobalTransform)>,
|
empties_candidates: Query<(Entity, &Name, &GlobalTransform)>,
|
||||||
|
|
||||||
blueprints_list: Query<(Entity, &BlueprintsList)>,
|
blueprints_list: Query<(Entity, &BlueprintsList)>,
|
||||||
|
root: Query<(Entity, &Name, &Children), (Without<Parent>, With<Children>)>
|
||||||
) {
|
) {
|
||||||
let animations_found = !animation_player_links.is_empty();
|
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;
|
let mut nested_blueprint_found = false;
|
||||||
for (entity, name, blueprint_name) in blueprints.iter() {
|
for (entity, name, blueprint_name) in blueprints.iter() {
|
||||||
if name.to_string() == *"Blueprint4_nested" && blueprint_name.0 == *"Blueprint4_nested" {
|
if name.to_string() == *"Blueprint4_nested" && blueprint_name.0 == *"Blueprint4_nested" {
|
||||||
|
@ -71,14 +68,46 @@ fn validate_export(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// check if there are blueprints_list components
|
||||||
let blueprints_list_found = !blueprints_list.is_empty();
|
let blueprints_list_found = !blueprints_list.is_empty();
|
||||||
|
|
||||||
|
// there should be no entity named xxx____bak as it means an error in the Blender side export process
|
||||||
|
let mut exported_names_correct = true;
|
||||||
|
for name in names.iter() {
|
||||||
|
if name.to_string().ends_with("___bak") {
|
||||||
|
exported_names_correct = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate parent/child tree
|
||||||
|
if !root.is_empty() {
|
||||||
|
let root = root.single();
|
||||||
|
let mut tree: HashMap<String, Vec<String>> = HashMap::new();
|
||||||
|
// println!("root {}", root.1);
|
||||||
|
|
||||||
|
for child in children.iter_descendants(root.0) {
|
||||||
|
let child_name:String = names.get(child).map_or(String::from("no_name"), |e| e.to_string() ); //|e| e.to_string(), || "no_name".to_string());
|
||||||
|
//println!(" child {}", child_name);
|
||||||
|
let parent = parents.get(child).unwrap();
|
||||||
|
let parent_name:String = names.get(parent.get()).map_or(String::from("no_name"), |e| e.to_string() ); //|e| e.to_string(), || "no_name".to_string());
|
||||||
|
tree.entry(parent_name).or_default().push(child_name.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let hierarchy = to_json_string(&tree);
|
||||||
|
fs::write(
|
||||||
|
"bevy_hierarchy.json",
|
||||||
|
hierarchy
|
||||||
|
)
|
||||||
|
.expect("unable to write hierarchy file")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fs::write(
|
fs::write(
|
||||||
"bevy_diagnostics.json",
|
"bevy_diagnostics.json",
|
||||||
format!(
|
format!(
|
||||||
"{{ \"animations\": {}, \"cylinder_found\": {} , \"nested_blueprint_found\": {}, \"empty_found\": {}, \"blueprints_list_found\": {} }}",
|
"{{ \"animations\": {}, \"nested_blueprint_found\": {}, \"empty_found\": {}, \"blueprints_list_found\": {}, \"exported_names_correct\": {} }}",
|
||||||
animations_found, cylinder_found, nested_blueprint_found, empty_found, blueprints_list_found
|
animations_found, nested_blueprint_found, empty_found, blueprints_list_found, exported_names_correct
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.expect("Unable to write file");
|
.expect("Unable to write file");
|
||||||
|
@ -97,6 +126,35 @@ fn exit_game(mut app_exit_events: ResMut<Events<bevy::app::AppExit>>) {
|
||||||
app_exit_events.send(bevy::app::AppExit);
|
app_exit_events.send(bevy::app::AppExit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct Animations(Vec<Handle<AnimationClip>>);
|
||||||
|
*/
|
||||||
|
|
||||||
|
fn animations(
|
||||||
|
foo:Query<(Entity, &Name, &AnimationPlayer),(Added<AnimationPlayer>)>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for bla in foo.iter() {
|
||||||
|
let mut counter = 0;
|
||||||
|
counter +=1;
|
||||||
|
println!("found some animations {} {}", counter, bla.1);
|
||||||
|
|
||||||
|
if bla.1.to_string() == "Collection".to_string(){
|
||||||
|
/*commands.insert_resource(Animations(vec![
|
||||||
|
asset_server.load("models/World.glb#Animation0"),
|
||||||
|
asset_server.load("models/World.glb#Animation1"),
|
||||||
|
]));*/
|
||||||
|
/*commands.entity(bla.0).insert(Animations {
|
||||||
|
named_animations:
|
||||||
|
})*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct GamePlugin;
|
pub struct GamePlugin;
|
||||||
impl Plugin for GamePlugin {
|
impl Plugin for GamePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
|
@ -104,6 +162,7 @@ impl Plugin for GamePlugin {
|
||||||
.add_systems(Update, validate_export)
|
.add_systems(Update, validate_export)
|
||||||
.add_systems(OnEnter(AppState::MenuRunning), start_game)
|
.add_systems(OnEnter(AppState::MenuRunning), start_game)
|
||||||
.add_systems(OnEnter(AppState::AppRunning), setup_game)
|
.add_systems(OnEnter(AppState::AppRunning), setup_game)
|
||||||
|
.add_systems(Update, animations)
|
||||||
.add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once
|
.add_systems(Update, generate_screenshot.run_if(on_timer(Duration::from_secs_f32(0.2)))) // TODO: run once
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
def get_standard_exporter_settings():
|
||||||
|
for scene in bpy.data.scenes:
|
||||||
|
if 'glTF2ExportSettings' in scene:
|
||||||
|
print("standard exporter settings", scene['glTF2ExportSettings'])
|
|
@ -0,0 +1 @@
|
||||||
|
{"b_Tail02_013":["b_Tail03_014"],"Blueprint4_nested.001":["Blueprint3"],"Collection 2 1":["Empty_in_sub_collection"],"b_Root_00":["b_Hip_01"],"b_LeftForeArm_010":["b_LeftHand_011"],"b_Spine01_02":["b_Spine02_03"],"Blueprint7_hierarchy.001":["Blueprint4_nested.001","Cube.001"],"b_RightLeg01_019":["b_RightLeg02_020"],"b_LeftUpperArm_09":["b_LeftForeArm_010"],"no_name":["Parent_Object","lighting_components_World","assets_list_World_components","Collection","Collection 2"],"Blueprint3":["Blueprint3_mesh","Blueprint3_mesh"],"world":["no_name"],"Parent_Object":["Cube.003","Blueprint1","Cylinder.001"],"Light":["Light","DirectionalLight Gizmo"],"Blueprint1.001":["Blueprint1_mesh"],"Blueprint7_hierarchy":["Cube.001"],"Spot":["Spot"],"b_Hip_01":["b_Spine01_02","b_Tail01_012","b_LeftLeg01_015","b_RightLeg01_019"],"Cylinder":["Cylinder.001","Cylinder.001"],"Collection 2":["Collection 2 1","Empty_in_collection","Spot"],"b_RightForeArm_07":["b_RightHand_08"],"Blueprint3_mesh":["Cylinder","Cylinder"],"Blueprint4_nested":["Blueprint3"],"Fox_mesh":["fox1"],"b_LeftLeg01_015":["b_LeftLeg02_016"],"b_Neck_04":["b_Head_05"],"b_RightFoot01_021":["b_RightFoot02_022"],"Blueprint1_mesh":["Cube.001","Cube.001"],"b_Tail01_012":["b_Tail02_013"],"Fox":["Fox_mesh","_rootJoint"],"Collection":["Blueprint1.001","Blueprint4_nested","Blueprint6_animated","Blueprint7_hierarchy","Camera","Cube","Empty","Light","Plane"],"Cube":["Cube"],"_rootJoint":["b_Root_00"],"b_RightLeg02_020":["b_RightFoot01_021"],"b_RightUpperArm_06":["b_RightForeArm_07"],"Plane":["Plane"],"Camera":["Camera Gizmo"],"Blueprint6_animated":["Fox"],"b_Spine02_03":["b_Neck_04","b_RightUpperArm_06","b_LeftUpperArm_09"],"b_LeftLeg02_016":["b_LeftFoot01_017"],"b_LeftFoot01_017":["b_LeftFoot02_018"],"Cube.001":["Cube.002","Cylinder","Cube.002","Cylinder"],"Cylinder.001":["Cylinder.002","Blueprint7_hierarchy.001","Empty_as_child"],"Blueprint1":["Blueprint1_mesh"]}
|
|
@ -5,6 +5,7 @@ import json
|
||||||
import pytest
|
import pytest
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
import filecmp
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from pixelmatch.contrib.PIL import pixelmatch
|
from pixelmatch.contrib.PIL import pixelmatch
|
||||||
|
|
||||||
|
@ -108,9 +109,15 @@ def test_export_complex(setup_data):
|
||||||
diagnostics = json.load(diagnostics_file)
|
diagnostics = json.load(diagnostics_file)
|
||||||
print("diagnostics", diagnostics)
|
print("diagnostics", diagnostics)
|
||||||
assert diagnostics["animations"] == True
|
assert diagnostics["animations"] == True
|
||||||
assert diagnostics["cylinder_found"] == True
|
|
||||||
assert diagnostics["empty_found"] == True
|
assert diagnostics["empty_found"] == True
|
||||||
assert diagnostics["blueprints_list_found"] == True
|
assert diagnostics["blueprints_list_found"] == True
|
||||||
|
assert diagnostics["exported_names_correct"] == True
|
||||||
|
|
||||||
|
with open(os.path.join(root_path, "bevy_hierarchy.json")) as hierarchy_file:
|
||||||
|
with open(os.path.join(os.path.dirname(__file__), "expected_bevy_hierarchy.json")) as expexted_hierarchy_file:
|
||||||
|
hierarchy = json.load(hierarchy_file)
|
||||||
|
expected = json.load(expexted_hierarchy_file)
|
||||||
|
assert sorted(hierarchy.items()) == sorted(expected.items())
|
||||||
|
|
||||||
# last but not least, do a visual compare
|
# last but not least, do a visual compare
|
||||||
screenshot_expected_path = os.path.join(root_path, "expected_screenshot.png")
|
screenshot_expected_path = os.path.join(root_path, "expected_screenshot.png")
|
||||||
|
|
Loading…
Reference in New Issue