From 98017421b466c1ffd84d848935be469383a4682a Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Wed, 5 Apr 2023 01:10:55 +0100 Subject: [PATCH] Fix: FBX fails to export materials on geometry nodes objects For only the FBX exporter, fixes #104516. The exporter would always get the materials to export from the original, non-evaluated, object, but Geometry nodes, when applied, replace the materials of the object and mesh/curve/etc., so the wrong materials would be exported. Now, when modifiers are applied on export, the materials of the evaluated object are checked against the materials of the original object. When the materials differ, the materials of the evaluated object are set into the `override_materials` of the ObjectWrapper, causing its `materials` property (refactored from the `material_slots` property) to return them. --- io_scene_fbx/export_fbx_bin.py | 13 ++++++++++--- io_scene_fbx/fbx_utils.py | 12 ++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py index d1f31165c..1e589d318 100644 --- a/io_scene_fbx/export_fbx_bin.py +++ b/io_scene_fbx/export_fbx_bin.py @@ -1431,7 +1431,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes): me_fbxmaterials_idx = scene_data.mesh_material_indices.get(me) if me_fbxmaterials_idx is not None: # We cannot use me.materials here, as this array is filled with None in case materials are linked to object... - me_blmaterials = [mat_slot.material for mat_slot in me_obj.material_slots] + me_blmaterials = me_obj.materials if me_fbxmaterials_idx and me_blmaterials: lay_ma = elem_data_single_int32(geom, b"LayerElementMaterial", 0) elem_data_single_int32(lay_ma, b"Version", FBX_GEOMETRY_MATERIAL_VERSION) @@ -2598,6 +2598,14 @@ def fbx_data_from_scene(scene, depsgraph, settings): bmesh.ops.triangulate(bm, faces=bm.faces) bm.to_mesh(tmp_me) bm.free() + # Usually the materials of the evaluated object will be the same, but modifiers, such as Geometry Nodes, + # can change the materials. + orig_mats = tuple(slot.material for slot in ob.material_slots) + eval_mats = tuple(slot.material.original if slot.material else None + for slot in ob_to_convert.material_slots) + if orig_mats != eval_mats: + # Override the default behaviour of getting materials from ob_obj.bdata.material_slots. + ob_obj.override_materials = eval_mats data_meshes[ob_obj] = (get_blenderID_key(tmp_me), tmp_me, True) # Change armatures back. for armature, pose_position in backup_pose_positions: @@ -2713,8 +2721,7 @@ def fbx_data_from_scene(scene, depsgraph, settings): data_materials = {} for ob_obj in objects: # If obj is not a valid object for materials, wrapper will just return an empty tuple... - for ma_s in ob_obj.material_slots: - ma = ma_s.material + for ma in ob_obj.materials: if ma is None: continue # Empty slots! # Note theoretically, FBX supports any kind of materials, even GLSL shaders etc. diff --git a/io_scene_fbx/fbx_utils.py b/io_scene_fbx/fbx_utils.py index 4d8f381fc..97cce3942 100644 --- a/io_scene_fbx/fbx_utils.py +++ b/io_scene_fbx/fbx_utils.py @@ -1174,7 +1174,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): we need to use a key to identify each. """ __slots__ = ( - 'name', 'key', 'bdata', 'parented_to_armature', + 'name', 'key', 'bdata', 'parented_to_armature', 'override_materials', '_tag', '_ref', '_dupli_matrix' ) @@ -1229,6 +1229,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): self.bdata = bdata self._ref = armature self.parented_to_armature = False + self.override_materials = None def __eq__(self, other): return isinstance(other, self.__class__) and self.key == other.key @@ -1443,11 +1444,14 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): return () bones = property(get_bones) - def get_material_slots(self): + def get_materials(self): + override_materials = self.override_materials + if override_materials is not None: + return override_materials if self._tag in {'OB', 'DP'}: - return self.bdata.material_slots + return tuple(slot.material for slot in self.bdata.material_slots) return () - material_slots = property(get_material_slots) + materials = property(get_materials) def is_deformed_by_armature(self, arm_obj): if not (self.is_object and self.type == 'MESH'): -- 2.30.2