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:
kaosat.dev 2024-03-20 17:57:06 +01:00
parent b51a0d38e2
commit a001ee21f6
7 changed files with 104 additions and 19 deletions

View File

@ -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_editor_pls = { version = "0.8" }
rand = "0.8.5"
json-writer ="0.3"

View File

@ -3516,6 +3516,17 @@
"type": "object",
"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": {
"additionalProperties": false,
"isComponent": true,

View File

@ -1,24 +1,30 @@
pub mod in_game;
use std::{
fs::{self},
time::Duration,
collections::HashMap, fs, time::Duration
};
use bevy_gltf_blueprints::{AnimationPlayerLink, BlueprintName, BlueprintsList};
use bevy_gltf_blueprints::{AnimationPlayerLink, Animations, BlueprintName, BlueprintsList};
pub use in_game::*;
use bevy::{
prelude::*, render::view::screenshot::ScreenshotManager, time::common_conditions::on_timer,
window::PrimaryWindow,
ecs::query, prelude::*, render::view::screenshot::ScreenshotManager, time::common_conditions::on_timer, window::PrimaryWindow
};
use bevy_gltf_worlflow_examples_common_rapier::{AppState, GameState};
use crate::{TupleTestF32, UnitTest};
use json_writer::to_json_string;
fn start_game(mut next_app_state: ResMut<NextState<AppState>>) {
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 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
@ -34,19 +40,10 @@ fn validate_export(
empties_candidates: Query<(Entity, &Name, &GlobalTransform)>,
blueprints_list: Query<(Entity, &BlueprintsList)>,
root: Query<(Entity, &Name, &Children), (Without<Parent>, With<Children>)>
) {
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" {
@ -71,14 +68,46 @@ fn validate_export(
break;
}
}
// check if there are blueprints_list components
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(
"bevy_diagnostics.json",
format!(
"{{ \"animations\": {}, \"cylinder_found\": {} , \"nested_blueprint_found\": {}, \"empty_found\": {}, \"blueprints_list_found\": {} }}",
animations_found, cylinder_found, nested_blueprint_found, empty_found, blueprints_list_found
"{{ \"animations\": {}, \"nested_blueprint_found\": {}, \"empty_found\": {}, \"blueprints_list_found\": {}, \"exported_names_correct\": {} }}",
animations_found, nested_blueprint_found, empty_found, blueprints_list_found, exported_names_correct
),
)
.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);
}
/*
#[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;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
@ -104,6 +162,7 @@ impl Plugin for GamePlugin {
.add_systems(Update, validate_export)
.add_systems(OnEnter(AppState::MenuRunning), start_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,

View File

@ -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'])

View File

@ -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"]}

View File

@ -5,6 +5,7 @@ import json
import pytest
import shutil
import filecmp
from PIL import Image
from pixelmatch.contrib.PIL import pixelmatch
@ -108,9 +109,15 @@ def test_export_complex(setup_data):
diagnostics = json.load(diagnostics_file)
print("diagnostics", diagnostics)
assert diagnostics["animations"] == True
assert diagnostics["cylinder_found"] == True
assert diagnostics["empty_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
screenshot_expected_path = os.path.join(root_path, "expected_screenshot.png")