feat(Blenvy): added basics of correct multi-material meshes support

* relying on the fact that the mesh-per-material generated by the gltf exporter is deterministic:
ie always uses the ordering of materials in an object
 * added new component MaterialInfos (plural) with a vec of MaterialInfo's
 * modified how materials per object are gathered on the Blender side
 * and modified the processing on the Bevy side to also use the ordered approach
 * seems to work well so far !
This commit is contained in:
kaosat.dev 2024-07-27 12:48:47 +02:00
parent e534917dca
commit 4865d432d9
8 changed files with 111 additions and 67 deletions

View File

@ -10,14 +10,18 @@ pub struct MaterialInfo {
pub path: String, pub path: String,
} }
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct MaterialInfos(Vec<MaterialInfo>);
#[derive(Component, Default, Debug)] #[derive(Component, Default, Debug)]
pub struct MaterialProcessed; pub struct MaterialProcessed;
/// system that injects / replaces materials from material library /// system that injects / replaces materials from material library
pub(crate) fn inject_materials( pub(crate) fn inject_materials(
mut blenvy_config: ResMut<BlenvyConfig>, mut blenvy_config: ResMut<BlenvyConfig>,
material_infos: Query< material_infos_query: Query<
(Entity, &MaterialInfo, &Children), (Entity, &MaterialInfos, &Children),
Without<MaterialProcessed>, // (With<BlueprintReadyForPostProcess>) Without<MaterialProcessed>, // (With<BlueprintReadyForPostProcess>)
/*( /*(
Added<BlueprintMaterialAssetsLoaded>, Added<BlueprintMaterialAssetsLoaded>,
@ -37,57 +41,65 @@ pub(crate) fn inject_materials(
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, material_info, children) in material_infos.iter() { for (entity, material_infos, children) in material_infos_query.iter() {
let material_full_path = format!("{}#{}", material_info.path, material_info.name); for (material_index, material_info) in material_infos.0.iter().enumerate() {
let mut material_found: Option<&Handle<StandardMaterial>> = None; let material_full_path = format!("{}#{}", material_info.path, material_info.name);
let mut material_found: Option<&Handle<StandardMaterial>> = None;
if blenvy_config if blenvy_config
.materials_cache
.contains_key(&material_full_path)
{
debug!("material is cached, retrieving");
let material = blenvy_config
.materials_cache .materials_cache
.get(&material_full_path) .contains_key(&material_full_path)
.expect("we should have the material available");
material_found = Some(material);
} else {
let model_handle: Handle<Gltf> = asset_server.load(material_info.path.clone()); // FIXME: kinda weird now
let mat_gltf = assets_gltf.get(model_handle.id()).unwrap_or_else(|| {
panic!(
"materials file {} should have been preloaded",
material_info.path
)
});
if mat_gltf
.named_materials
.contains_key(&material_info.name as &str)
{ {
let material = mat_gltf debug!("material is cached, retrieving");
.named_materials let material = blenvy_config
.get(&material_info.name as &str)
.expect("this material should have been loaded at this stage, please make sure you are correctly preloading them");
blenvy_config
.materials_cache .materials_cache
.insert(material_full_path, material.clone()); .get(&material_full_path)
.expect("we should have the material available");
material_found = Some(material); material_found = Some(material);
} else {
let model_handle: Handle<Gltf> = asset_server.load(material_info.path.clone()); // FIXME: kinda weird now
let mat_gltf = assets_gltf.get(model_handle.id()).unwrap_or_else(|| {
panic!(
"materials file {} should have been preloaded",
material_info.path
)
});
if mat_gltf
.named_materials
.contains_key(&material_info.name as &str)
{
let material = mat_gltf
.named_materials
.get(&material_info.name as &str)
.expect("this material should have been loaded at this stage, please make sure you are correctly preloading them");
blenvy_config
.materials_cache
.insert(material_full_path, material.clone());
material_found = Some(material);
}
} }
}
commands.entity(entity).insert(MaterialProcessed); if let Some(material) = material_found {
for (child_index, child) in children.iter().enumerate() {
if child_index == material_index {
if with_materials_and_meshes.contains(*child) {
info!(
"injecting material {}, path: {:?}",
material_info.name,
material_info.path.clone()
);
if let Some(material) = material_found { commands.entity(*child).insert(material.clone());
for child in children.iter() { }
if with_materials_and_meshes.contains(*child) { }
info!(
"injecting material {}, path: {:?}",
material_info.name,
material_info.path.clone()
);
commands.entity(*child).insert(material.clone());
} }
} }
} }
commands.entity(entity).insert(MaterialProcessed);
} }
} }

View File

@ -91,6 +91,8 @@ impl Plugin for BlueprintsPlugin {
.add_event::<BlueprintEvent>() .add_event::<BlueprintEvent>()
.register_type::<BlueprintInfo>() .register_type::<BlueprintInfo>()
.register_type::<MaterialInfo>() .register_type::<MaterialInfo>()
.register_type::<MaterialInfos>()
.register_type::<SpawnBlueprint>() .register_type::<SpawnBlueprint>()
.register_type::<BlueprintInstanceDisabled>() .register_type::<BlueprintInstanceDisabled>()
.register_type::<HideUntilReady>() .register_type::<HideUntilReady>()

BIN
tools/blenvy.zip Normal file

Binary file not shown.

View File

@ -102,7 +102,7 @@ def auto_export(changes_per_scene, changes_per_collection, changes_per_material,
old_selections = bpy.context.selected_objects old_selections = bpy.context.selected_objects
# deal with materials # deal with materials
if export_materials_library: if export_materials_library and len(materials_to_export) > 0:
print("export MATERIALS") print("export MATERIALS")
export_materials(materials_to_export, settings, blueprints_data) export_materials(materials_to_export, settings, blueprints_data)

View File

@ -73,7 +73,7 @@ def generate_gltf_export_settings(settings):
if str(key) not in constant_keys: if str(key) not in constant_keys:
gltf_export_settings[key] = standard_gltf_exporter_settings.get(key) gltf_export_settings[key] = standard_gltf_exporter_settings.get(key)
print("GLTF EXPORT SETTINGS", gltf_export_settings) #print("GLTF EXPORT SETTINGS", gltf_export_settings)
return gltf_export_settings return gltf_export_settings

View File

@ -46,6 +46,13 @@ def generate_materials_scene_content(root_collection, used_material_names):
make_material_object("Material_"+material_name, [index * 0.2,0,0], material=material, collection=root_collection) make_material_object("Material_"+material_name, [index * 0.2,0,0], material=material, collection=root_collection)
return {} return {}
# generates a scene for a given material
def generate_material_scene_content(root_collection, material_name):
material = bpy.data.materials[material_name]
make_material_object(f"Material_{material_name}", [0,0,0], material=material, collection=root_collection)
return {}
def clear_materials_scene(temp_scene): def clear_materials_scene(temp_scene):
root_collection = temp_scene.collection root_collection = temp_scene.collection
scene_objects = [o for o in root_collection.objects] scene_objects = [o for o in root_collection.objects]
@ -83,9 +90,23 @@ def export_materials(materials_to_export, settings, blueprints_data):
'export_apply':True 'export_apply':True
} }
for material in materials_to_export:
print("exporting material", material.name)
gltf_output_path = os.path.join(materials_path_full, material.name)
generate_temporary_scene_and_export(
settings=settings,
gltf_export_settings=gltf_export_settings,
temp_scene_name="__materials_scene",
gltf_output_path=gltf_output_path,
tempScene_filler= lambda temp_collection: generate_material_scene_content(temp_collection, material.name),
tempScene_cleaner= lambda temp_scene, params: clear_materials_scene(temp_scene=temp_scene)
)
current_project_name = Path(bpy.context.blend_data.filepath).stem current_project_name = Path(bpy.context.blend_data.filepath).stem
gltf_output_path = os.path.join(materials_path_full, current_project_name + "_materials") 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")
generate_temporary_scene_and_export( generate_temporary_scene_and_export(

View File

@ -8,21 +8,26 @@ def get_materials_to_export(changes_per_material, changed_export_parameters, blu
materials_path_full = getattr(settings,"materials_path_full", "") materials_path_full = getattr(settings,"materials_path_full", "")
change_detection = getattr(settings.auto_export, "change_detection") change_detection = getattr(settings.auto_export, "change_detection")
export_materials_library = getattr(settings.auto_export, "export_materials_library")
collection_instances_combine_mode = getattr(settings.auto_export, "collection_instances_combine_mode") collection_instances_combine_mode = getattr(settings.auto_export, "collection_instances_combine_mode")
all_materials = bpy.data.materials all_materials = bpy.data.materials
local_materials = [material for material in all_materials if material.library is None] local_materials = [material for material in all_materials if material.library is None]
materials_to_export = [] materials_to_export = []
if change_detection and not changed_export_parameters:
changed_materials = [bpy.data.materials[material_name] for material_name in list(changes_per_material.keys())]
# first check if all materials have already been exported before (if this is the first time the exporter is run if export_materials_library and change_detection:
# in your current Blender session for example) if changed_export_parameters:
materials_not_on_disk = find_materials_not_on_disk(local_materials, materials_path_full, export_gltf_extension) materials_to_export = [bpy.data.materials[material_name] for material_name in list(changes_per_material.keys())] # TODO: should be based on the list of materials in use
else :
changed_materials = [bpy.data.materials[material_name] for material_name in list(changes_per_material.keys())]
# also deal with blueprints that are always marked as "always_export" # first check if all materials have already been exported before (if this is the first time the exporter is run
#materials_always_export = [material for material in internal_materials if is_material_always_export(material)] # in your current Blender session for example)
materials_always_export = [] materials_not_on_disk = find_materials_not_on_disk(local_materials, materials_path_full, export_gltf_extension)
materials_to_export = list(set(changed_materials + materials_not_on_disk + materials_always_export))
# also deal with blueprints that are always marked as "always_export"
#materials_always_export = [material for material in internal_materials if is_material_always_export(material)]
materials_always_export = []
materials_to_export = list(set(changed_materials + materials_not_on_disk + materials_always_export))
print("materials_to_export", materials_to_export, local_materials)
return materials_to_export return materials_to_export

View File

@ -40,7 +40,9 @@ def get_materials(object, materials_per_object):
# 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 !
materials_per_object[object] = material if not object in materials_per_object:
materials_per_object[object] = []
materials_per_object[object].append(material)
return used_materials_names return used_materials_names
@ -68,16 +70,18 @@ def add_material_info_to_objects(materials_per_object, settings):
materials_exported_path = posixpath.join(materials_path, f"{materials_library_name}{export_gltf_extension}") materials_exported_path = posixpath.join(materials_path, f"{materials_library_name}{export_gltf_extension}")
#print("ADDING MAERIAL INFOS") #print("ADDING MAERIAL INFOS")
for object in materials_per_object.keys(): for object in materials_per_object.keys():
material = materials_per_object[object] material_infos = []
for material in materials_per_object[object]:
# problem with using actual components: you NEED the type registry/component infos, so if there is none , or it is not loaded yet, it does not work
# for a few components we could hardcode this
material_info = f'(name: "{material.name}", path: "{materials_exported_path}")'
#bpy.ops.blenvy.component_add(target_item_name=object.name, target_item_type="OBJECT", component_type="blenvy::blueprints::materials::MaterialInfo", component_value=component_value)
# problem with using actual components: you NEED the type registry/component infos, so if there is none , or it is not loaded yet, it does not work materials_exported_path = posixpath.join(materials_path, f"{materials_library_name}{export_gltf_extension}")
# for a few components we could hardcode this #object['MaterialInfo'] = component_value
component_value = f'(name: "{material.name}", path: "{materials_exported_path}")' material_infos.append(material_info)
#bpy.ops.blenvy.component_add(target_item_name=object.name, target_item_type="OBJECT", component_type="blenvy::blueprints::materials::MaterialInfo", component_value=component_value) object['MaterialInfos'] = f"({material_infos})".replace("'","")
print("adding materialInfos to object", object, "material infos", material_infos)
materials_exported_path = posixpath.join(materials_path, f"{materials_library_name}{export_gltf_extension}")
object['MaterialInfo'] = component_value
print("adding materialInfo to object", object, "material info", component_value)
# get all the materials of all objects in a given scene # get all the materials of all objects in a given scene