This commit is contained in:
Mark Moissette 2024-03-11 22:47:29 +00:00 committed by GitHub
commit 1b8c3de6a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 362 additions and 43 deletions

View File

@ -120,6 +120,10 @@ impl Plugin for BlueprintsPlugin {
.register_type::<MaterialInfo>() .register_type::<MaterialInfo>()
.register_type::<SpawnHere>() .register_type::<SpawnHere>()
.register_type::<Animations>() .register_type::<Animations>()
.register_type::<BlueprintsList>()
.register_type::<Vec<String>>()
.register_type::<HashMap<String,Vec<String>>>()
.insert_resource(BluePrintsConfig { .insert_resource(BluePrintsConfig {
format: self.format, format: self.format,
library_folder: self.library_folder.clone(), library_folder: self.library_folder.clone(),
@ -140,11 +144,14 @@ impl Plugin for BlueprintsPlugin {
.add_systems( .add_systems(
Update, Update,
( (
spawn_from_blueprints, (spawn_from_blueprints,
check_for_loaded,
actually_spawn_stuff, apply_deferred).chain(),
compute_scene_aabbs.run_if(aabbs_enabled), compute_scene_aabbs.run_if(aabbs_enabled),
apply_deferred.run_if(aabbs_enabled), apply_deferred.run_if(aabbs_enabled),
apply_deferred, apply_deferred,
materials_inject.run_if(materials_library_enabled), (materials_inject, check_for_material_loaded, materials_inject2).chain().run_if(materials_library_enabled),
) )
.chain() .chain()
.in_set(GltfBlueprintsSet::Spawn), .in_set(GltfBlueprintsSet::Spawn),

View File

@ -1,22 +1,19 @@
use std::path::Path; use std::path::Path;
use bevy::{ use bevy::{
asset::{AssetServer, Assets, Handle}, asset::{AssetId, AssetServer, Assets, Handle},
ecs::{ ecs::{
component::Component, component::Component, entity::Entity, query::{Added, With}, reflect::ReflectComponent, system::{Commands, Query, Res, ResMut}
query::{Added, With},
reflect::ReflectComponent,
system::{Commands, Query, Res, ResMut},
}, },
gltf::Gltf, gltf::Gltf,
hierarchy::{Children, Parent}, hierarchy::{Children, Parent},
log::debug, log::{debug, info},
pbr::StandardMaterial, pbr::StandardMaterial,
reflect::Reflect, reflect::Reflect,
render::mesh::Mesh, render::mesh::Mesh,
}; };
use crate::BluePrintsConfig; use crate::{AssetLoadTracker, AssetsToLoad, BluePrintsConfig};
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
@ -26,10 +23,110 @@ pub struct MaterialInfo {
pub source: String, pub source: String,
} }
/// flag component
#[derive(Component)]
pub(crate) struct BlueprintMaterialAssetsLoaded;
/// flag component
#[derive(Component)]
pub(crate) struct BlueprintMaterialAssetsNotLoaded;
/// system that injects / replaces materials from material library /// system that injects / replaces materials from material library
pub(crate) fn materials_inject( pub(crate) fn materials_inject(
blueprints_config: ResMut<BluePrintsConfig>,
material_infos: Query<(Entity, &MaterialInfo), Added<MaterialInfo>>,
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
for (entity, material_info) in material_infos.iter() {
let model_file_name = format!(
"{}_materials_library.{}",
&material_info.source, &blueprints_config.format
);
let materials_path = Path::new(&blueprints_config.material_library_folder)
.join(Path::new(model_file_name.as_str()));
let material_name = &material_info.name;
let material_full_path = materials_path.to_str().unwrap().to_string() + "#" + material_name; // TODO: yikes, cleanup
if blueprints_config
.material_library_cache
.contains_key(&material_full_path)
{
debug!("material is cached, retrieving");
blueprints_config
.material_library_cache
.get(&material_full_path)
.expect("we should have the material available");
commands
.entity(entity)
.insert(BlueprintMaterialAssetsLoaded);
} else {
let material_file_handle: Handle<Gltf> = asset_server.load(materials_path.clone());
let material_file_id = material_file_handle.id();
println!("loading material {} {}", material_full_path, material_file_id);
let mut asset_infos:Vec<AssetLoadTracker<Gltf>> = vec![];
asset_infos.push(AssetLoadTracker {
name: material_full_path,
id: material_file_id,
loaded: false,
handle: material_file_handle.clone()
});
commands
.entity(entity)
.insert(AssetsToLoad{
all_loaded: false,
asset_infos: asset_infos,
progress: 0.0
//..Default::default()
})
.insert(BlueprintMaterialAssetsNotLoaded);
/**/
}
}
}
// TODO, merge with check_for_loaded, make generic ?
pub(crate) fn check_for_material_loaded(
mut blueprint_assets_to_load: Query<(Entity, &mut AssetsToLoad<Gltf>),With<BlueprintMaterialAssetsNotLoaded>>,
asset_server: Res<AssetServer>,
mut commands: Commands,
){
for (entity, mut assets_to_load) in blueprint_assets_to_load.iter_mut(){
let mut all_loaded = true;
let mut loaded_amount = 0;
let total = assets_to_load.asset_infos.len();
for tracker in assets_to_load.asset_infos.iter_mut(){
let asset_id = tracker.id;
let loaded = asset_server.is_loaded_with_dependencies(asset_id);
tracker.loaded = loaded;
if loaded {
println!("loaded {} {}", tracker.name, tracker.id);
loaded_amount += 1;
}else{
all_loaded = false;
}
}
let progress:f32 = loaded_amount as f32 / total as f32;
println!("progress (materials): {}",progress);
assets_to_load.progress = progress;
if all_loaded {
assets_to_load.all_loaded = true;
commands.entity(entity)
.insert(BlueprintMaterialAssetsLoaded)
.remove::<BlueprintMaterialAssetsNotLoaded>();
}
}
}
/// system that injects / replaces materials from material library
pub(crate) fn materials_inject2(
mut blueprints_config: ResMut<BluePrintsConfig>, mut blueprints_config: ResMut<BluePrintsConfig>,
material_infos: Query<(&MaterialInfo, &Children), Added<MaterialInfo>>, material_infos: Query<(&MaterialInfo, &Children, ), (Added<BlueprintMaterialAssetsLoaded>, With<BlueprintMaterialAssetsLoaded>)>,
with_materials_and_meshes: Query< with_materials_and_meshes: Query<
(), (),
( (
@ -38,12 +135,13 @@ pub(crate) fn materials_inject(
With<Handle<Mesh>>, With<Handle<Mesh>>,
), ),
>, >,
models: Res<Assets<bevy::gltf::Gltf>>, assets_gltf: Res<Assets<Gltf>>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (material_info, children) in material_infos.iter() { for (material_info, children) in material_infos.iter() {
println!("here");
let model_file_name = format!( let model_file_name = format!(
"{}_materials_library.{}", "{}_materials_library.{}",
&material_info.source, &blueprints_config.format &material_info.source, &blueprints_config.format
@ -59,16 +157,18 @@ pub(crate) fn materials_inject(
.material_library_cache .material_library_cache
.contains_key(&material_full_path) .contains_key(&material_full_path)
{ {
debug!("material is cached, retrieving"); info!("material is cached, retrieving");
let material = blueprints_config let material = blueprints_config
.material_library_cache .material_library_cache
.get(&material_full_path) .get(&material_full_path)
.expect("we should have the material available"); .expect("we should have the material available");
material_found = Some(material); material_found = Some(material);
}else { }else {
let my_gltf: Handle<Gltf> = asset_server.load(materials_path.clone()); let model_handle: Handle<Gltf> = asset_server.load(materials_path.clone());// FIXME: kinda weird now
let mat_gltf = models println!("loading material {:?} {}", materials_path, model_handle.id());
.get(my_gltf.id())
let mat_gltf = assets_gltf
.get(model_handle.id())
.expect("material should have been preloaded"); .expect("material should have been preloaded");
if mat_gltf.named_materials.contains_key(material_name) { if mat_gltf.named_materials.contains_key(material_name) {
let material = mat_gltf let material = mat_gltf

View File

@ -1,6 +1,6 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use bevy::{gltf::Gltf, prelude::*}; use bevy::{gltf::Gltf, prelude::*, utils::HashMap};
use crate::{Animations, BluePrintsConfig}; use crate::{Animations, BluePrintsConfig};
@ -46,9 +46,167 @@ pub struct AddToGameWorld;
/// helper component, just to transfer child data /// helper component, just to transfer child data
pub(crate) struct OriginalChildren(pub Vec<Entity>); pub(crate) struct OriginalChildren(pub Vec<Entity>);
/// main spawning functions,
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct BlueprintsList(pub HashMap<String,Vec<String>>);
#[derive(Reflect, Default, Debug)]
pub(crate) struct BlueprintLoadTracker{
pub name: String,
pub id: AssetId<Gltf>,
pub loaded: bool,
pub handle: Handle<Gltf>
}
#[derive(Component, Default, Debug)]
pub(crate) struct BlueprintAssetsToLoad{
pub all_loaded: bool,
pub asset_infos: Vec<BlueprintLoadTracker>,
pub progress: f32
}
#[derive(Default, Debug)]
pub(crate) struct AssetLoadTracker<T:bevy::prelude::Asset>{
pub name: String,
pub id: AssetId<T>,
pub loaded: bool,
pub handle: Handle<T>
}
#[derive(Component, Default, Debug)]
pub(crate) struct AssetsToLoad<T:bevy::prelude::Asset>{
pub all_loaded: bool,
pub asset_infos: Vec<AssetLoadTracker<T>>,
pub progress: f32
}
/// flag component
#[derive(Component)]
pub(crate) struct BlueprintAssetsLoaded;
/// flag component
#[derive(Component)]
pub(crate) struct BlueprintAssetsNotLoaded;
/// spawning prepare function,
/// * also takes into account the already exisiting "override" components, ie "override components" > components from blueprint /// * also takes into account the already exisiting "override" components, ie "override components" > components from blueprint
pub(crate) fn spawn_from_blueprints( pub(crate) fn spawn_from_blueprints(
spawn_placeholders: Query<
(
Entity,
&BlueprintName,
Option<&Parent>,
Option<&Library>,
Option<&Name>,
Option<&BlueprintsList>,
),
(Added<BlueprintName>, Added<SpawnHere>, Without<Spawned>),
>,
mut commands: Commands,
asset_server: Res<AssetServer>,
blueprints_config: Res<BluePrintsConfig>,
) {
for (
entity,
blupeprint_name,
original_parent,
library_override,
name,
blueprints_list,
) in spawn_placeholders.iter()
{
debug!(
"preparing to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}",
blupeprint_name.0, name, entity, original_parent
);
// println!("main model path {:?}", model_path);
if blueprints_list.is_some() {
let blueprints_list = blueprints_list.unwrap();
// println!("blueprints list {:?}", blueprints_list.0.keys());
let mut asset_infos:Vec<AssetLoadTracker<Gltf>> = vec![];
for (blueprint_name, _) in blueprints_list.0.iter() {
/*if blueprint_name == what {
println!("WHOLY MOLLY !")
}*/
// println!("library path {:?}", library_path);
let mut library_path = &blueprints_config.library_folder; // TODO: we cannot use the overriden library path
// FIXME: hack
if blueprint_name == "World" {
library_path= &library_override.unwrap().0;
}
let model_file_name = format!("{}.{}", &blueprint_name, &blueprints_config.format);
let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str()));
let model_handle: Handle<Gltf> = asset_server.load(model_path.clone());
let model_id = model_handle.id();
let loaded = asset_server.is_loaded_with_dependencies(model_id);
if !loaded {
asset_infos.push(AssetLoadTracker {
name: model_path.to_string_lossy().into(),
id: model_id,
loaded: false,
handle: model_handle.clone()
})
}
}
// if not all assets are already loaded, inject a component to signal that we need them to be loaded
if asset_infos.len() > 0 {
commands
.entity(entity)
.insert(AssetsToLoad{
all_loaded: false,
asset_infos: asset_infos,
progress: 0.0
//..Default::default()
})
.insert(BlueprintAssetsNotLoaded);
}else {
commands
.entity(entity)
.insert(BlueprintAssetsLoaded);
}
}
}
}
pub(crate) fn check_for_loaded(
mut blueprint_assets_to_load: Query<(Entity, &mut AssetsToLoad<Gltf>), With<BlueprintAssetsNotLoaded>>,
asset_server: Res<AssetServer>,
mut commands: Commands,
){
for (entity, mut assets_to_load) in blueprint_assets_to_load.iter_mut(){
let mut all_loaded = true;
let mut loaded_amount = 0;
let total = assets_to_load.asset_infos.len();
for tracker in assets_to_load.asset_infos.iter_mut(){
let asset_id = tracker.id;
let loaded = asset_server.is_loaded_with_dependencies(asset_id);
tracker.loaded = loaded;
if loaded {
loaded_amount += 1;
}else{
all_loaded = false;
}
}
let progress:f32 = loaded_amount as f32 / total as f32;
println!("progress: {}",progress);
assets_to_load.progress = progress;
if all_loaded {
assets_to_load.all_loaded = true;
commands.entity(entity)
.insert(BlueprintAssetsLoaded)
.remove::<BlueprintAssetsNotLoaded>();
}
}
}
pub(crate) fn actually_spawn_stuff(
spawn_placeholders: Query< spawn_placeholders: Query<
( (
Entity, Entity,
@ -59,7 +217,7 @@ pub(crate) fn spawn_from_blueprints(
Option<&AddToGameWorld>, Option<&AddToGameWorld>,
Option<&Name>, Option<&Name>,
), ),
(Added<BlueprintName>, Added<SpawnHere>, Without<Spawned>), (With<BlueprintAssetsLoaded>, Added<BlueprintAssetsLoaded>, Without<BlueprintAssetsNotLoaded>),
>, >,
mut commands: Commands, mut commands: Commands,
@ -71,6 +229,7 @@ pub(crate) fn spawn_from_blueprints(
children: Query<&Children>, children: Query<&Children>,
){ ){
for ( for (
entity, entity,
blupeprint_name, blupeprint_name,
@ -79,20 +238,14 @@ pub(crate) fn spawn_from_blueprints(
library_override, library_override,
add_to_world, add_to_world,
name, name,
) in spawn_placeholders.iter() ) in spawn_placeholders.iter()
{ {
debug!( info!(
"need to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}", "need to spawn {:?} for entity {:?}, id: {:?}, parent:{:?}",
blupeprint_name.0, name, entity, original_parent blupeprint_name.0, name, entity, original_parent
); );
let mut original_children: Vec<Entity> = vec![];
if let Ok(c) = children.get(entity) {
for child in c.iter() {
original_children.push(*child);
}
}
let what = &blupeprint_name.0; let what = &blupeprint_name.0;
let model_file_name = format!("{}.{}", &what, &blueprints_config.format); let model_file_name = format!("{}.{}", &what, &blueprints_config.format);
@ -101,8 +254,8 @@ pub(crate) fn spawn_from_blueprints(
library_override.map_or_else(|| &blueprints_config.library_folder, |l| &l.0); library_override.map_or_else(|| &blueprints_config.library_folder, |l| &l.0);
let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str())); let model_path = Path::new(&library_path).join(Path::new(model_file_name.as_str()));
debug!("attempting to spawn {:?}", model_path); info!("attempting to spawn {:?}", model_path);
let model_handle: Handle<Gltf> = asset_server.load(model_path); let model_handle: Handle<Gltf> = asset_server.load(model_path);// FIXME: kinda weird now
let gltf = assets_gltf let gltf = assets_gltf
.get(&model_handle) .get(&model_handle)
@ -123,6 +276,12 @@ pub(crate) fn spawn_from_blueprints(
transforms = *transform.unwrap(); transforms = *transform.unwrap();
} }
let mut original_children: Vec<Entity> = vec![];
if let Ok(c) = children.get(entity) {
for child in c.iter() {
original_children.push(*child);
}
}
commands.entity(entity).insert(( commands.entity(entity).insert((
SceneBundle { SceneBundle {
scene: scene.clone(), scene: scene.clone(),

View File

@ -6,7 +6,7 @@ use bevy::scene::SceneInstance;
use super::{AnimationPlayerLink, Animations}; use super::{AnimationPlayerLink, Animations};
use super::{SpawnHere, Spawned}; use super::{SpawnHere, Spawned};
use crate::{CopyComponents, InBlueprint, NoInBlueprint, OriginalChildren}; use crate::{BlueprintAssetsToLoad, CopyComponents, InBlueprint, NoInBlueprint, OriginalChildren};
/// this system is in charge of doing any necessary post processing after a blueprint scene has been spawned /// this system is in charge of doing any necessary post processing after a blueprint scene has been spawned
/// - it removes one level of useless nesting /// - it removes one level of useless nesting
@ -89,6 +89,7 @@ pub(crate) fn spawned_blueprint_post_process(
commands.entity(original).remove::<SpawnHere>(); commands.entity(original).remove::<SpawnHere>();
commands.entity(original).remove::<Spawned>(); commands.entity(original).remove::<Spawned>();
commands.entity(original).remove::<Handle<Scene>>(); commands.entity(original).remove::<Handle<Scene>>();
commands.entity(original).remove::<BlueprintAssetsToLoad>();// also clear the sub assets tracker to free up handles, perhaps just freeing up the handles and leave the rest would be better ?
commands.entity(root_entity).despawn_recursive(); commands.entity(root_entity).despawn_recursive();
} }
} }

View File

@ -4,7 +4,7 @@ import bpy
from ..helpers.generate_and_export import generate_and_export from ..helpers.generate_and_export import generate_and_export
from .export_gltf import (generate_gltf_export_preferences, export_gltf) from .export_gltf import (generate_gltf_export_preferences, export_gltf)
from ..modules.bevy_dynamic import is_object_dynamic, is_object_static from ..modules.bevy_dynamic import is_object_dynamic, is_object_static
from ..helpers.helpers_scenes import clear_hollow_scene, copy_hollowed_collection_into from ..helpers.helpers_scenes import clear_hollow_scene, copy_hollowed_collection_into, inject_blueprints_list_into_main_scene
# export all main scenes # export all main scenes
@ -29,6 +29,8 @@ def export_main_scene(scene, folder_path, addon_prefs, library_collections):
} }
if export_blueprints : if export_blueprints :
inject_blueprints_list_into_main_scene(scene)
if export_separate_dynamic_and_static_objects: if export_separate_dynamic_and_static_objects:
#print("SPLIT STATIC AND DYNAMIC") #print("SPLIT STATIC AND DYNAMIC")
# first export static objects # first export static objects

View File

@ -36,16 +36,23 @@ def get_marked_collections(scene, addon_prefs):
return (collection_names, marked_collections) return (collection_names, marked_collections)
# gets all collections within collections that might also be relevant # gets all collections within collections that might also be relevant
def get_sub_collections(collections, parent, children_per_collection): def get_sub_collections(collections, parent=None, children_per_collection=None):
if parent == None:
parent = CollectionNode()
if children_per_collection == None:
children_per_collection = {}
collection_names = set() collection_names = set()
used_collections = [] used_collections = []
for root_collection in collections: for root_collection in collections:
node = Node(name=root_collection.name, parent=parent) print("collections", collections)
node = CollectionNode(name=root_collection.name, parent=parent)
parent.children.append(node) parent.children.append(node)
#print("root collection", root_collection.name) #print("root collection", root_collection.name)
for collection in traverse_tree(root_collection): # TODO: filter out COLLECTIONS that have the flatten flag (unlike the flatten flag on colleciton instances themselves) for collection in traverse_tree(root_collection): # TODO: filter out COLLECTIONS that have the flatten flag (unlike the flatten flag on colleciton instances themselves)
print("sub", collection)
node_name = collection.name node_name = collection.name
children_per_collection[node_name] = [] children_per_collection[node_name] = []
#print(" scanning", collection.name) #print(" scanning", collection.name)
@ -53,12 +60,17 @@ def get_sub_collections(collections, parent, children_per_collection):
#print("FLATTEN", object.name, 'Flatten' in object) #print("FLATTEN", object.name, 'Flatten' in object)
if object.instance_type == 'COLLECTION' : # and not 'Flatten' in object: if object.instance_type == 'COLLECTION' : # and not 'Flatten' in object:
collection_name = object.instance_collection.name collection_name = object.instance_collection.name
print("sub obj", collection_name)
# FIXME: not sure:
children_per_collection[node_name].append(collection_name)
(sub_names, sub_collections) = get_sub_collections([object.instance_collection], node, children_per_collection) (sub_names, sub_collections) = get_sub_collections([object.instance_collection], node, children_per_collection)
print("gna", sub_names, sub_collections)
if len(list(sub_names)) > 0: if len(list(sub_names)) > 0:
print("toto")
children_per_collection[node_name] += (list(sub_names)) children_per_collection[node_name] += (list(sub_names))
#print(" found sub collection in use", object.name, object.instance_collection) #print(" found sub collection in use", object.name, object.instance_collection)
if not collection_name in collection_names: if not collection_name in collection_names:
collection_names.add(collection_name) collection_names.add(collection_name)
used_collections.append(object.instance_collection) used_collections.append(object.instance_collection)
@ -77,7 +89,7 @@ def flatten_collection_tree(node, children_per_collection):
children_per_collection[node.name] = list(set( children_per_collection[node.name])) children_per_collection[node.name] = list(set( children_per_collection[node.name]))
class Node : class CollectionNode :
def __init__(self, name="", parent=None): def __init__(self, name="", parent=None):
self.name = name self.name = name
self.children = [] self.children = []
@ -93,7 +105,7 @@ def get_exportable_collections(main_scenes, library_scenes, addon_prefs):
all_collections = [] all_collections = []
all_collection_names = [] all_collection_names = []
root_node = Node() root_node = CollectionNode()
root_node.name = "root" root_node.name = "root"
children_per_collection = {} children_per_collection = {}

View File

@ -1,5 +1,6 @@
import json
import bpy import bpy
from .helpers_collections import (set_active_collection) from .helpers_collections import (CollectionNode, get_sub_collections, get_used_collections, set_active_collection)
from .object_makers import (make_empty) from .object_makers import (make_empty)
@ -149,3 +150,40 @@ def get_scenes(addon_prefs):
return [level_scene_names, level_scenes, library_scene_names, library_scenes] return [level_scene_names, level_scenes, library_scene_names, library_scenes]
def inject_blueprints_list_into_main_scene(scene):
print("injecting assets data")
root_collection = scene.collection
assets_list = None
for object in scene.objects:
if object.name == "assets_list"+scene.name:
assets_list = object
break
if assets_list is None:
assets_list = make_empty('assets_list_'+scene.name, [0,0,0], [0,0,0], [0,0,0], root_collection)
# find all blueprints used in a scene
# TODO: export a tree rather than a flat list ? because you could have potential clashing items in flat lists (amongst other issues)
(collection_names, collections) = get_used_collections(scene)
root_node = CollectionNode()
root_node.name = "root"
children_per_collection = {}
#print("collection_names", collection_names, "collections", collections)
(bla, bli ) = get_sub_collections(collections, root_node, children_per_collection)
#print("sfdsfsdf", bla, bli, "root", root_node, "children_per_collection", children_per_collection)
# with sub collections
# (collection_names, collections) = get_sub_collections(all_collections, root_node, children_per_collection)
#
# what about marked assets ?
#assets_list["blueprints_direct"] = list(collection_names)
assets_list["BlueprintsList"] = f"({json.dumps(dict(children_per_collection))})"
#'({"a":[]})'
#'([])'
#
#
print("assets list", assets_list["BlueprintsList"], children_per_collection)

View File

@ -20,7 +20,7 @@ def setup_data(request):
#other_materials_path = os.path.join("../../testing", "other_materials") #other_materials_path = os.path.join("../../testing", "other_materials")
print("\nPerforming teardown...") print("\nPerforming teardown...")
if os.path.exists(models_path): '''if os.path.exists(models_path):
shutil.rmtree(models_path) shutil.rmtree(models_path)
"""if os.path.exists(materials_path): """if os.path.exists(materials_path):
@ -34,7 +34,7 @@ def setup_data(request):
screenshot_observed_path = os.path.join(root_path, "screenshot.png") screenshot_observed_path = os.path.join(root_path, "screenshot.png")
if os.path.exists(screenshot_observed_path): if os.path.exists(screenshot_observed_path):
os.remove(screenshot_observed_path) os.remove(screenshot_observed_path)'''
request.addfinalizer(finalizer) request.addfinalizer(finalizer)