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,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
pub struct MaterialInfos(Vec<MaterialInfo>);
#[derive(Component, Default, Debug)]
pub struct MaterialProcessed;
/// system that injects / replaces materials from material library
pub(crate) fn inject_materials(
mut blenvy_config: ResMut<BlenvyConfig>,
material_infos: Query<
(Entity, &MaterialInfo, &Children),
material_infos_query: Query<
(Entity, &MaterialInfos, &Children),
Without<MaterialProcessed>, // (With<BlueprintReadyForPostProcess>)
/*(
Added<BlueprintMaterialAssetsLoaded>,
@ -37,57 +41,65 @@ pub(crate) fn inject_materials(
mut commands: Commands,
) {
for (entity, material_info, children) in material_infos.iter() {
let material_full_path = format!("{}#{}", material_info.path, material_info.name);
let mut material_found: Option<&Handle<StandardMaterial>> = None;
if blenvy_config
.materials_cache
.contains_key(&material_full_path)
{
debug!("material is cached, retrieving");
let material = blenvy_config
for (entity, material_infos, children) in material_infos_query.iter() {
for (material_index, material_info) in material_infos.0.iter().enumerate() {
let material_full_path = format!("{}#{}", material_info.path, material_info.name);
let mut material_found: Option<&Handle<StandardMaterial>> = None;
if blenvy_config
.materials_cache
.get(&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)
.contains_key(&material_full_path)
{
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
debug!("material is cached, retrieving");
let material = blenvy_config
.materials_cache
.insert(material_full_path, material.clone());
.get(&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
.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 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());
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()
);
commands.entity(*child).insert(material.clone());
}
}
}
}
}
commands.entity(entity).insert(MaterialProcessed);
}
}

View File

@ -91,6 +91,8 @@ impl Plugin for BlueprintsPlugin {
.add_event::<BlueprintEvent>()
.register_type::<BlueprintInfo>()
.register_type::<MaterialInfo>()
.register_type::<MaterialInfos>()
.register_type::<SpawnBlueprint>()
.register_type::<BlueprintInstanceDisabled>()
.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
# deal with materials
if export_materials_library:
if export_materials_library and len(materials_to_export) > 0:
print("export MATERIALS")
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:
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

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)
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):
root_collection = temp_scene.collection
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
}
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
gltf_output_path = os.path.join(materials_path_full, current_project_name + "_materials")
print(" exporting Materials to", gltf_output_path, ".gltf/glb")
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", "")
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")
all_materials = bpy.data.materials
local_materials = [material for material in all_materials if material.library is None]
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
# in your current Blender session for example)
materials_not_on_disk = find_materials_not_on_disk(local_materials, materials_path_full, export_gltf_extension)
if export_materials_library and change_detection:
if changed_export_parameters:
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"
#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))
# first check if all materials have already been exported before (if this is the first time the exporter is run
# in your current Blender session for example)
materials_not_on_disk = find_materials_not_on_disk(local_materials, materials_path_full, export_gltf_extension)
# 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

View File

@ -40,7 +40,9 @@ def get_materials(object, materials_per_object):
# print(" slot", m, "material", material)
used_materials_names.append(material.name)
# 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
@ -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}")
#print("ADDING MAERIAL INFOS")
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
# for a few components we could hardcode this
component_value = 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)
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)
materials_exported_path = posixpath.join(materials_path, f"{materials_library_name}{export_gltf_extension}")
#object['MaterialInfo'] = component_value
material_infos.append(material_info)
object['MaterialInfos'] = f"({material_infos})".replace("'","")
print("adding materialInfos to object", object, "material infos", material_infos)
# get all the materials of all objects in a given scene