feat(Blenvy): more work done on materials handling overhaul (wip)

* split out materials scan from injection of materialInfo into objects
 * added material Asset injection into list of assets at scene level
 * related tweaks & cleanups
 * continued overhaul on the bevy side
This commit is contained in:
kaosat.dev 2024-06-10 00:31:23 +02:00
parent 1fdb45bab6
commit ed0c85b66e
13 changed files with 76 additions and 54 deletions

View File

@ -17,14 +17,14 @@ use bevy::{
render::mesh::Mesh, render::mesh::Mesh,
}; };
use crate::{AssetLoadTracker, AssetsToLoad, BluePrintsConfig}; use crate::{AssetLoadTracker, AssetsToLoad, BluePrintsConfig, BlueprintInstanceReady};
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
/// struct containing the name & source of the material to apply /// struct containing the name & path of the material to apply
pub struct MaterialInfo { pub struct MaterialInfo {
pub name: String, pub name: String,
pub source: String, pub path: String,
} }
/// flag component /// flag component
@ -37,21 +37,21 @@ 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>, blueprints_config: ResMut<BluePrintsConfig>,
material_infos: Query<(Entity, &MaterialInfo), Added<MaterialInfo>>,
ready_blueprints: Query<(Entity, &Children), (With<BlueprintInstanceReady>)>,
material_infos: Query<(Entity, &MaterialInfo, &Parent), Added<MaterialInfo>>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut commands: Commands, 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 /*for(entity, children) in ready_blueprints.iter() {
println!("Blueprint ready !");
} */
for (entity, material_info, parent) in material_infos.iter() {
println!("Entity with material info {:?} {:?}", entity, material_info);
let parent_blueprint = ready_blueprints.get(parent.get());
println!("Parent blueprint {:?}", parent_blueprint)
/*if blueprints_config
.material_library_cache .material_library_cache
.contains_key(&material_full_path) .contains_key(&material_full_path)
{ {
@ -68,7 +68,7 @@ pub(crate) fn materials_inject(
let material_file_id = material_file_handle.id(); let material_file_id = material_file_handle.id();
// FIXME: fix this stuff // FIXME: fix this stuff
/*let asset_infos: Vec<AssetLoadTracker> = vec![AssetLoadTracker { let asset_infos: Vec<AssetLoadTracker> = vec![AssetLoadTracker {
name: material_full_path, name: material_full_path,
id: material_file_id, id: material_file_id,
loaded: false, loaded: false,
@ -83,17 +83,17 @@ pub(crate) fn materials_inject(
..Default::default() ..Default::default()
}) })
.insert(BlueprintMaterialAssetsNotLoaded); .insert(BlueprintMaterialAssetsNotLoaded);
*/
} } */
} }
} }
// TODO, merge with check_for_loaded, make generic ? // TODO, merge with check_for_loaded, make generic ?
// FIXME: fix this: // FIXME: fix this:
/*
pub(crate) fn check_for_material_loaded( pub(crate) fn check_for_material_loaded(
mut blueprint_assets_to_load: Query< mut blueprint_assets_to_load: Query<
(Entity, &mut AssetsToLoad<Gltf>), (Entity, &mut AssetsToLoad),
With<BlueprintMaterialAssetsNotLoaded>, With<BlueprintMaterialAssetsNotLoaded>,
>, >,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
@ -125,7 +125,7 @@ pub(crate) fn check_for_material_loaded(
} }
} }
} }
*/
/// system that injects / replaces materials from material library /// system that injects / replaces materials from material library
pub(crate) fn materials_inject2( pub(crate) fn materials_inject2(
mut blueprints_config: ResMut<BluePrintsConfig>, mut blueprints_config: ResMut<BluePrintsConfig>,
@ -152,7 +152,7 @@ pub(crate) fn materials_inject2(
for (material_info, children) in material_infos.iter() { for (material_info, children) in material_infos.iter() {
let model_file_name = format!( let model_file_name = format!(
"{}_materials_library.{}", "{}_materials_library.{}",
&material_info.source, &blueprints_config.format &material_info.path, &blueprints_config.format
); );
let materials_path = Path::new(&blueprints_config.material_library_folder) let materials_path = Path::new(&blueprints_config.material_library_folder)
.join(Path::new(model_file_name.as_str())); .join(Path::new(model_file_name.as_str()));

View File

@ -27,6 +27,13 @@ pub struct SpawnHere;
/// flag component for dynamically spawned scenes /// flag component for dynamically spawned scenes
pub struct Spawned; pub struct Spawned;
#[derive(Component)]
/// flag component added when a Blueprint instance ist Ready : ie :
/// - its assets have loaded
/// - it has finished spawning
pub struct BlueprintInstanceReady;
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
/// flag component marking any spwaned child of blueprints ..unless the original entity was marked with the `NoInBlueprint` marker component /// flag component marking any spwaned child of blueprints ..unless the original entity was marked with the `NoInBlueprint` marker component
@ -298,6 +305,7 @@ pub(crate) fn spawn_from_blueprints2(
..Default::default() ..Default::default()
}, },
Spawned, Spawned,
BlueprintInstanceReady, // FIXME: not sure if this is should be added here or in the post process
OriginalChildren(original_children), OriginalChildren(original_children),
BlueprintAnimations { BlueprintAnimations {
// these are animations specific to the inside of the blueprint // these are animations specific to the inside of the blueprint

View File

@ -113,10 +113,14 @@ General issues:
- [ ] add option to 'split out' meshes from blueprints ? - [ ] add option to 'split out' meshes from blueprints ?
- [ ] ie considering meshletts etc , it would make sense to keep blueprints seperate from purely mesh gltfs - [ ] ie considering meshletts etc , it would make sense to keep blueprints seperate from purely mesh gltfs
- [ ] remove 'export_marked_assets' it should be a default setting - [x] remove 'export_marked_assets' it should be a default setting
- [x] disable/ hide asset editing ui for external assets - [x] disable/ hide asset editing ui for external assets
- [ ] inject_export_path_into_internal_blueprints should be called on every asset/blueprint scan !! Not just on export - [ ] inject_export_path_into_internal_blueprints should be called on every asset/blueprint scan !! Not just on export
- [x] fix level asets UI - [x] fix level asets UI
- [ ] persist exported materials path in blueprints so that it can be read from library file users
- [ ] just like "export_path" write it into each blueprint's collection
- [ ] scan for used materials per blueprint !
- [ ] for scenes, scan for used materials of all non instance objects (TODO: what about overrides ?)
- [x] remove BlueprintsList & replace is with assets list - [x] remove BlueprintsList & replace is with assets list

View File

@ -4,7 +4,7 @@ from pathlib import Path
from blenvy.core.helpers_collections import (traverse_tree) from blenvy.core.helpers_collections import (traverse_tree)
from blenvy.core.object_makers import make_cube from blenvy.core.object_makers import make_cube
from blenvy.materials.materials_helpers import get_all_materials from blenvy.materials.materials_helpers import add_material_info_to_objects, get_all_materials
from .generate_temporary_scene_and_export import generate_temporary_scene_and_export from .generate_temporary_scene_and_export import generate_temporary_scene_and_export
from .export_gltf import (generate_gltf_export_settings) from .export_gltf import (generate_gltf_export_settings)
@ -69,8 +69,8 @@ def export_materials(collections, library_scenes, settings):
gltf_export_settings = generate_gltf_export_settings(settings) gltf_export_settings = generate_gltf_export_settings(settings)
materials_path_full = getattr(settings,"materials_path_full") materials_path_full = getattr(settings,"materials_path_full")
used_material_names = get_all_materials(collections, library_scenes) (used_material_names, materials_per_object) = get_all_materials(collections, library_scenes)
current_project_name = Path(bpy.context.blend_data.filepath).stem add_material_info_to_objects(materials_per_object, settings)
gltf_export_settings = { **gltf_export_settings, gltf_export_settings = { **gltf_export_settings,
'use_active_scene': True, 'use_active_scene': True,
@ -81,7 +81,8 @@ def export_materials(collections, library_scenes, settings):
'export_apply':True 'export_apply':True
} }
gltf_output_path = os.path.join(materials_path_full, current_project_name + "_materials_library") current_project_name = Path(bpy.context.blend_data.filepath).stem
gltf_output_path = os.path.join(materials_path_full, current_project_name + "_materials")
print(" exporting Materials to", gltf_output_path, ".gltf/glb") print(" exporting Materials to", gltf_output_path, ".gltf/glb")

View File

@ -21,7 +21,6 @@ parameter_names_whitelist_auto_export = [
'export_separate_dynamic_and_static_objects', 'export_separate_dynamic_and_static_objects',
'export_materials_library', 'export_materials_library',
'collection_instances_combine_mode', 'collection_instances_combine_mode',
'export_marked_assets'
] ]
def get_setting_changes(): def get_setting_changes():

View File

@ -1,6 +1,9 @@
import json import json
import os import os
from pathlib import Path
from types import SimpleNamespace from types import SimpleNamespace
import bpy
from blenvy.blueprints.blueprint_helpers import inject_blueprints_list_into_main_scene, remove_blueprints_list_from_main_scene from blenvy.blueprints.blueprint_helpers import inject_blueprints_list_into_main_scene, remove_blueprints_list_from_main_scene
from ..constants import TEMPSCENE_PREFIX from ..constants import TEMPSCENE_PREFIX
from ..common.generate_temporary_scene_and_export import generate_temporary_scene_and_export, copy_hollowed_collection_into, clear_hollow_scene from ..common.generate_temporary_scene_and_export import generate_temporary_scene_and_export, copy_hollowed_collection_into, clear_hollow_scene
@ -49,9 +52,9 @@ def export_main_scene(scene, settings, blueprints_data):
auto_assets = [] auto_assets = []
all_assets = [] all_assets = []
export_gltf_extension = getattr(settings.auto_export, "export_gltf_extension", ".glb")
blueprints_path = getattr(settings, "blueprints_path") blueprints_path = getattr(settings, "blueprints_path")
export_gltf_extension = getattr(settings.auto_export, "export_gltf_extension", ".glb")
for blueprint in blueprints_in_scene: for blueprint in blueprints_in_scene:
if blueprint.local: if blueprint.local:
blueprint_exported_path = os.path.join(blueprints_path, f"{blueprint.name}{export_gltf_extension}") blueprint_exported_path = os.path.join(blueprints_path, f"{blueprint.name}{export_gltf_extension}")
@ -69,8 +72,15 @@ def export_main_scene(scene, settings, blueprints_data):
"""for asset in auto_assets: """for asset in auto_assets:
print(" generated asset", asset.name, asset.path)""" print(" generated asset", asset.name, asset.path)"""
scene["local_assets"] = assets_to_fake_ron([{"name": asset.name, "path": asset.path} for asset in scene.user_assets] + auto_assets) materials_path = getattr(settings, "materials_path")
scene["AllAssets"] = assets_to_fake_ron(all_assets + [{"name": asset.name, "path": asset.path} for asset in scene.user_assets] + auto_assets) current_project_name = Path(bpy.context.blend_data.filepath).stem
materials_library_name = f"{current_project_name}_materials"
materials_exported_path = os.path.join(materials_path, f"{materials_library_name}{export_gltf_extension}")
material_assets = [{"name": materials_library_name, "path": materials_exported_path}] # we also add the material library as an asset
scene["local_assets"] = assets_to_fake_ron([{"name": asset.name, "path": asset.path} for asset in scene.user_assets] + auto_assets + material_assets)
scene["AllAssets"] = assets_to_fake_ron(all_assets + [{"name": asset.name, "path": asset.path} for asset in scene.user_assets] + auto_assets + material_assets)
if export_separate_dynamic_and_static_objects: if export_separate_dynamic_and_static_objects:

View File

@ -101,13 +101,6 @@ class AutoExportSettings(PropertyGroup):
update=save_settings update=save_settings
) # type: ignore ) # type: ignore
export_marked_assets: BoolProperty(
name='Auto export marked assets',
description='Collections that have been marked as assets will be systematically exported, even if not in use in another scene',
default=True,
update=save_settings
) # type: ignore
dry_run: EnumProperty( dry_run: EnumProperty(
name="dry run", name="dry run",
description="debug/ develop helper to enable everything but the actual exporting of files", description="debug/ develop helper to enable everything but the actual exporting of files",

View File

@ -51,7 +51,6 @@ def draw_settings_ui(layout, auto_export_settings):
# collections/blueprints # collections/blueprints
section.prop(auto_export_settings, "collection_instances_combine_mode") section.prop(auto_export_settings, "collection_instances_combine_mode")
section.prop(auto_export_settings, "export_marked_assets")
section.separator() section.separator()
section.prop(auto_export_settings, "export_separate_dynamic_and_static_objects") section.prop(auto_export_settings, "export_separate_dynamic_and_static_objects")

View File

@ -8,8 +8,6 @@ from .blueprint import Blueprint
# - with the "auto_export" flag # - with the "auto_export" flag
# https://blender.stackexchange.com/questions/167878/how-to-get-all-collections-of-the-current-scene # https://blender.stackexchange.com/questions/167878/how-to-get-all-collections-of-the-current-scene
def blueprints_scan(main_scenes, library_scenes, settings): def blueprints_scan(main_scenes, library_scenes, settings):
export_marked_assets = getattr(settings.auto_export, "export_marked_assets")
blueprints = {} blueprints = {}
blueprints_from_objects = {} blueprints_from_objects = {}
blueprint_name_from_instances = {} blueprint_name_from_instances = {}
@ -84,12 +82,12 @@ def blueprints_scan(main_scenes, library_scenes, settings):
if ( if (
'AutoExport' in collection and collection['AutoExport'] == True # get marked collections 'AutoExport' in collection and collection['AutoExport'] == True # get marked collections
or export_marked_assets and collection.asset_data is not None # or if you have marked collections as assets you can auto export them too or collection.asset_data is not None # or if you have marked collections as assets you can auto export them too
or collection.name in list(internal_collection_instances.keys()) # or if the collection has an instance in one of the main scenes or collection.name in list(internal_collection_instances.keys()) # or if the collection has an instance in one of the main scenes
): ):
blueprint = Blueprint(collection.name) blueprint = Blueprint(collection.name)
blueprint.local = True blueprint.local = True
blueprint.marked = 'AutoExport' in collection and collection['AutoExport'] == True or export_marked_assets and collection.asset_data is not None blueprint.marked = 'AutoExport' in collection and collection['AutoExport'] == True or collection.asset_data is not None
blueprint.objects = [object.name for object in collection.all_objects if not object.instance_type == 'COLLECTION'] # inneficient, double loop blueprint.objects = [object.name for object in collection.all_objects if not object.instance_type == 'COLLECTION'] # inneficient, double loop
blueprint.nested_blueprints = [object.instance_collection.name for object in collection.all_objects if object.instance_type == 'COLLECTION'] # FIXME: not precise enough, aka "what is a blueprint" blueprint.nested_blueprints = [object.instance_collection.name for object in collection.all_objects if object.instance_type == 'COLLECTION'] # FIXME: not precise enough, aka "what is a blueprint"
blueprint.collection = collection blueprint.collection = collection

View File

@ -4,32 +4,43 @@ from pathlib import Path
from ..core.helpers_collections import (traverse_tree) from ..core.helpers_collections import (traverse_tree)
# get materials per object, and injects the materialInfo component # get materials per object, and injects the materialInfo component
def get_materials(object): def get_materials(object, materials_per_object):
material_slots = object.material_slots material_slots = object.material_slots
used_materials_names = [] used_materials_names = []
#materials_per_object = {}
current_project_name = Path(bpy.context.blend_data.filepath).stem
for m in material_slots: for m in material_slots:
material = m.material material = m.material
# print(" slot", m, "material", material) # print(" slot", m, "material", material)
used_materials_names.append(material.name) used_materials_names.append(material.name)
# TODO:, also respect slots & export multiple materials if applicable ! # TODO:, also respect slots & export multiple materials if applicable !
# TODO: do NOT modify objects like this !! do it in a different function materials_per_object[object] = material
object['MaterialInfo'] = '(name: "'+material.name+'", source: "'+current_project_name + '")'
return used_materials_names return used_materials_names
def get_all_materials(collection_names, library_scenes): def get_all_materials(collection_names, library_scenes):
used_material_names = [] used_material_names = []
materials_per_object = {}
for scene in library_scenes: for scene in library_scenes:
root_collection = scene.collection root_collection = scene.collection
for cur_collection in traverse_tree(root_collection): for cur_collection in traverse_tree(root_collection):
if cur_collection.name in collection_names: if cur_collection.name in collection_names:
for object in cur_collection.all_objects: for object in cur_collection.all_objects:
used_material_names = used_material_names + get_materials(object) used_material_names = used_material_names + get_materials(object, materials_per_object)
# we only want unique names # we only want unique names
used_material_names = list(set(used_material_names)) used_material_names = list(set(used_material_names))
return used_material_names return (used_material_names, materials_per_object)
def add_material_info_to_objects(materials_per_object, settings):
materials_path = getattr(settings, "materials_path")
export_gltf_extension = getattr(settings.auto_export, "export_gltf_extension", ".glb")
current_project_name = Path(bpy.context.blend_data.filepath).stem
materials_library_name = f"{current_project_name}_materials"
materials_exported_path = os.path.join(materials_path, f"{materials_library_name}{export_gltf_extension}")
for object in materials_per_object.keys():
material = materials_per_object[object]
# TODO: switch to using actual components ?
materials_exported_path = os.path.join(materials_path, f"{materials_library_name}{export_gltf_extension}")
object['MaterialInfo'] = f'(name: "{material.name}", path: "{materials_exported_path}")'

View File

@ -105,7 +105,7 @@ def test_export_complex(setup_data):
blenvy.auto_export.auto_export = True blenvy.auto_export.auto_export = True
blenvy.auto_export.export_scene_settings = True blenvy.auto_export.export_scene_settings = True
blenvy.auto_export.export_blueprints = True blenvy.auto_export.export_blueprints = True
blenvy.auto_export.export_materials_library = False # TODO: switch back blenvy.auto_export.export_materials_library = True
bpy.data.scenes['World'].blenvy_scene_type = 'Level' # set scene as main/level scene bpy.data.scenes['World'].blenvy_scene_type = 'Level' # set scene as main/level scene
bpy.data.scenes['Library'].blenvy_scene_type = 'Library' # set scene as Library scene bpy.data.scenes['Library'].blenvy_scene_type = 'Library' # set scene as Library scene

View File

@ -53,7 +53,7 @@ def test_export_external_blueprints(setup_data):
blenvy.auto_export.auto_export = True blenvy.auto_export.auto_export = True
blenvy.auto_export.export_scene_settings = True blenvy.auto_export.export_scene_settings = True
blenvy.auto_export.export_blueprints = True blenvy.auto_export.export_blueprints = True
blenvy.auto_export.export_materials_library = False # TODO switch back blenvy.auto_export.export_materials_library = True
print("SCENES", bpy.data.scenes) print("SCENES", bpy.data.scenes)
for scene in bpy.data.scenes: for scene in bpy.data.scenes:

View File

@ -223,7 +223,6 @@ def test_export_do_not_export_marked_assets(setup_data):
export_output_folder="./models", export_output_folder="./models",
export_scene_settings=True, export_scene_settings=True,
export_blueprints=True, export_blueprints=True,
export_marked_assets = False
) )
assert os.path.exists(os.path.join(setup_data["levels_path"], "World.glb")) == True assert os.path.exists(os.path.join(setup_data["levels_path"], "World.glb")) == True
assert os.path.exists(os.path.join(setup_data["blueprints_path"], "Blueprint1.glb")) == True assert os.path.exists(os.path.join(setup_data["blueprints_path"], "Blueprint1.glb")) == True