From b1d9a916477882fe1bd12d6fa189426245762938 Mon Sep 17 00:00:00 2001 From: Oxicid Date: Fri, 15 Aug 2025 15:04:36 +0200 Subject: [PATCH] FBX: Add material name collision mode Introduces the Material Name Collision option, similar to how USD / OBJ importers have it. Pull Request: https://projects.blender.org/blender/blender/pulls/144375 --- scripts/addons_core/io_scene_fbx/__init__.py | 16 +++++++++- scripts/addons_core/io_scene_fbx/fbx_utils.py | 2 +- .../addons_core/io_scene_fbx/import_fbx.py | 9 ++++-- source/blender/editors/io/io_fbx_ops.cc | 29 +++++++++++++++++++ source/blender/io/fbx/IO_fbx.hh | 10 +++++++ source/blender/io/fbx/importer/fbx_import.cc | 15 ++++++++-- 6 files changed, 74 insertions(+), 7 deletions(-) diff --git a/scripts/addons_core/io_scene_fbx/__init__.py b/scripts/addons_core/io_scene_fbx/__init__.py index 0e18704bef3..9ef02d8d7f2 100644 --- a/scripts/addons_core/io_scene_fbx/__init__.py +++ b/scripts/addons_core/io_scene_fbx/__init__.py @@ -198,6 +198,15 @@ class ImportFBX(bpy.types.Operator, ImportHelper): description="Use pre/post rotation from FBX transform (you may have to disable that in some cases)", default=True, ) + mtl_name_collision_mode: EnumProperty( + name="Material Name Collision", + items=(("MAKE_UNIQUE", "Make Unique", "Import each FBX material as a unique Blender material"), + ("REFERENCE_EXISTING", "Reference Existing", + "If a material with the same name already exists, reference that instead of importing"), + ), + default='MAKE_UNIQUE', + description="Behavior when the name of an imported material conflicts with an existing material", + ) def draw(self, context): layout = self.layout @@ -206,6 +215,7 @@ class ImportFBX(bpy.types.Operator, ImportHelper): import_panel_include(layout, self) import_panel_transform(layout, self) + import_panel_materials(layout, self) import_panel_animation(layout, self) import_panel_armature(layout, self) @@ -267,6 +277,11 @@ def import_panel_transform_orientation(layout, operator): body.prop(operator, "axis_forward") body.prop(operator, "axis_up") +def import_panel_materials(layout, operator): + header, body = layout.panel("FBX_import_material", default_closed=True) + header.label(text="Materials") + if body: + body.prop(operator, "mtl_name_collision_mode") def import_panel_animation(layout, operator): header, body = layout.panel("FBX_import_animation", default_closed=True) @@ -290,7 +305,6 @@ def import_panel_armature(layout, operator): sub.prop(operator, "primary_bone_axis") sub.prop(operator, "secondary_bone_axis") - @orientation_helper(axis_forward='-Z', axis_up='Y') class ExportFBX(bpy.types.Operator, ExportHelper): """Write a FBX file""" diff --git a/scripts/addons_core/io_scene_fbx/fbx_utils.py b/scripts/addons_core/io_scene_fbx/fbx_utils.py index 51c16562a85..6a5efecf21a 100644 --- a/scripts/addons_core/io_scene_fbx/fbx_utils.py +++ b/scripts/addons_core/io_scene_fbx/fbx_utils.py @@ -1927,5 +1927,5 @@ FBXImportSettings = namedtuple("FBXImportSettings", ( "use_custom_props", "use_custom_props_enum_as_string", "nodal_material_wrap_map", "image_cache", "ignore_leaf_bones", "force_connect_children", "automatic_bone_orientation", "bone_correction_matrix", - "use_prepost_rot", "colors_type", + "use_prepost_rot", "colors_type", "mtl_name_collision_mode", )) diff --git a/scripts/addons_core/io_scene_fbx/import_fbx.py b/scripts/addons_core/io_scene_fbx/import_fbx.py index 9f3503c10f4..4525d3790f9 100644 --- a/scripts/addons_core/io_scene_fbx/import_fbx.py +++ b/scripts/addons_core/io_scene_fbx/import_fbx.py @@ -2050,6 +2050,10 @@ def blen_read_material(fbx_tmpl, fbx_obj, settings): elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'Material') + if settings.mtl_name_collision_mode == "REFERENCE_EXISTING": + if (ma := bpy.data.materials.get(elem_name_utf8)): + return ma + nodal_material_wrap_map = settings.nodal_material_wrap_map ma = bpy.data.materials.new(name=elem_name_utf8) @@ -3043,7 +3047,8 @@ def load(operator, context, filepath="", primary_bone_axis='Y', secondary_bone_axis='X', use_prepost_rot=True, - colors_type='SRGB'): + colors_type='SRGB', + mtl_name_collision_mode="MAKE_UNIQUE"): global fbx_elem_nil fbx_elem_nil = FBXElem('', (), (), ()) @@ -3182,7 +3187,7 @@ def load(operator, context, filepath="", use_custom_props, use_custom_props_enum_as_string, nodal_material_wrap_map, image_cache, ignore_leaf_bones, force_connect_children, automatic_bone_orientation, bone_correction_matrix, - use_prepost_rot, colors_type, + use_prepost_rot, colors_type, mtl_name_collision_mode, ) # #### And now, the "real" data. diff --git a/source/blender/editors/io/io_fbx_ops.cc b/source/blender/editors/io/io_fbx_ops.cc index 3c042682d5b..765f435fbf1 100644 --- a/source/blender/editors/io/io_fbx_ops.cc +++ b/source/blender/editors/io/io_fbx_ops.cc @@ -33,6 +33,20 @@ # include "io_fbx_ops.hh" # include "io_utils.hh" +const EnumPropertyItem rna_enum_fbx_mtl_name_collision_mode_items[] = { + {int(eFBXMtlNameCollisionMode::MakeUnique), + "MAKE_UNIQUE", + 0, + "Make Unique", + "Import each FBX material as a unique Blender material"}, + {int(eFBXMtlNameCollisionMode::ReferenceExisting), + "REFERENCE_EXISTING", + 0, + "Reference Existing", + "If a material with the same name already exists, reference that instead of importing"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + static const EnumPropertyItem fbx_vertex_colors_mode[] = { {int(eFBXVertexColorMode::None), "NONE", 0, "None", "Do not import color attributes"}, {int(eFBXVertexColorMode::sRGB), @@ -60,6 +74,8 @@ static wmOperatorStatus wm_fbx_import_exec(bContext *C, wmOperator *op) params.use_anim = RNA_boolean_get(op->ptr, "use_anim"); params.anim_offset = RNA_float_get(op->ptr, "anim_offset"); params.vertex_colors = eFBXVertexColorMode(RNA_enum_get(op->ptr, "import_colors")); + params.mtl_name_collision_mode = eFBXMtlNameCollisionMode( + RNA_enum_get(op->ptr, "mtl_name_collision_mode")); params.reports = op->reports; @@ -110,6 +126,11 @@ static void ui_fbx_import_settings(const bContext *C, uiLayout *layout, PointerR col->prop(ptr, "validate_meshes", UI_ITEM_NONE, std::nullopt, ICON_NONE); } + if (uiLayout *panel = layout->panel(C, "FBX_import_material", true, IFACE_("Materials"))) { + uiLayout *col = &panel->column(false); + col->prop(ptr, "mtl_name_collision_mode", UI_ITEM_NONE, std::nullopt, ICON_NONE); + } + { PanelLayout panel = layout->panel(C, "FBX_import_anim", true); panel.header->use_property_split_set(false); @@ -157,6 +178,14 @@ void WM_OT_fbx_import(wmOperatorType *ot) FILE_SORT_DEFAULT); RNA_def_float(ot->srna, "global_scale", 1.0f, 1e-6f, 1e6f, "Scale", "", 0.001f, 1000.0f); + + RNA_def_enum( + ot->srna, + "mtl_name_collision_mode", + rna_enum_fbx_mtl_name_collision_mode_items, + int(eFBXMtlNameCollisionMode::MakeUnique), + "Material Name Collision", + "Behavior when the name of an imported material conflicts with an existing material"); RNA_def_enum(ot->srna, "import_colors", fbx_vertex_colors_mode, diff --git a/source/blender/io/fbx/IO_fbx.hh b/source/blender/io/fbx/IO_fbx.hh index 323c4cc7604..e5afc6bef2f 100644 --- a/source/blender/io/fbx/IO_fbx.hh +++ b/source/blender/io/fbx/IO_fbx.hh @@ -18,6 +18,15 @@ struct Mesh; struct bContext; struct ReportList; +/** + * Behavior when the name of an imported material + * conflicts with an existing material. + */ +enum class eFBXMtlNameCollisionMode { + MakeUnique = 0, + ReferenceExisting = 1, +}; + enum class eFBXVertexColorMode { None = 0, sRGB = 1, @@ -27,6 +36,7 @@ enum class eFBXVertexColorMode { struct FBXImportParams { char filepath[FILE_MAX] = ""; float global_scale = 1.0f; + eFBXMtlNameCollisionMode mtl_name_collision_mode = eFBXMtlNameCollisionMode::MakeUnique; eFBXVertexColorMode vertex_colors = eFBXVertexColorMode::sRGB; bool validate_meshes = true; bool use_custom_normals = true; diff --git a/source/blender/io/fbx/importer/fbx_import.cc b/source/blender/io/fbx/importer/fbx_import.cc index f88bf300168..a909506014a 100644 --- a/source/blender/io/fbx/importer/fbx_import.cc +++ b/source/blender/io/fbx/importer/fbx_import.cc @@ -8,6 +8,7 @@ #include "BKE_camera.h" #include "BKE_layer.hh" +#include "BKE_lib_id.hh" #include "BKE_light.h" #include "BKE_object.hh" #include "BKE_report.hh" @@ -98,9 +99,17 @@ void FbxImportContext::import_globals(Scene *scene) const void FbxImportContext::import_materials() { for (const ufbx_material *fmat : this->fbx.materials) { - Material *mat = io::fbx::import_material(this->bmain, this->base_dir, *fmat); - if (this->params.use_custom_props) { - read_custom_properties(fmat->props, mat->id, this->params.props_enum_as_string); + Material *mat = nullptr; + /* Check if a material with this name already exists in the main database */ + if (this->params.mtl_name_collision_mode == eFBXMtlNameCollisionMode::ReferenceExisting) { + mat = (Material *)BKE_libblock_find_name(this->bmain, ID_MA, fmat->name.data); + } + + if (mat == nullptr) { + mat = io::fbx::import_material(this->bmain, this->base_dir, *fmat); + if (this->params.use_custom_props) { + read_custom_properties(fmat->props, mat->id, this->params.props_enum_as_string); + } } this->mapping.mat_to_material.add(fmat, mat); }