From ced1d46fb070dee903ba57ff7f42f191bc869e2a Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Sat, 17 Jun 2023 15:15:38 +0100 Subject: [PATCH] Fix disparity of FBX custom properties import and export Light, camera, bone and pose bone properties are now imported to match the exporter behaviour. Image custom properties are now exported to match the importer behaviour, except for Image custom properties that are rarely imported from a Texture Node instead of a Video Node, which are always exported to a Video Node. This resolves one of the issues in #104677. --- io_scene_fbx/export_fbx_bin.py | 8 ++++++ io_scene_fbx/import_fbx.py | 46 +++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py index a58e7a5c1..66bb8e3a6 100644 --- a/io_scene_fbx/export_fbx_bin.py +++ b/io_scene_fbx/export_fbx_bin.py @@ -1719,6 +1719,14 @@ def fbx_data_video_elements(root, vid, scene_data): #~ else: #~ elem_data_single_bytes(fbx_vid, b"Content", b"") + # Blender currently has no UI for editing custom properties on Images, but the importer will import Image custom + # properties from either a Video Node or a Texture Node, preferring a Video node if one exists. We'll propagate + # these custom properties only to Video Nodes because that is most likely where they were imported from, and Texture + # Nodes are more like Blender's Shader Nodes than Images, which is what we're exporting here. + if scene_data.settings.use_custom_props: + fbx_data_element_custom_properties(props, vid) + + def fbx_data_armature_elements(root, arm_obj, scene_data): """ diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py index 50d23db78..961594417 100644 --- a/io_scene_fbx/import_fbx.py +++ b/io_scene_fbx/import_fbx.py @@ -1777,10 +1777,12 @@ def blen_read_texture_image(fbx_tmpl, fbx_obj, basedir, settings): return image -def blen_read_camera(fbx_tmpl, fbx_obj, global_scale): +def blen_read_camera(fbx_tmpl, fbx_obj, settings): # meters to inches M2I = 0.0393700787 + global_scale = settings.global_scale + elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'NodeAttribute') fbx_props = (elem_find_first(fbx_obj, b'Properties70'), @@ -1808,10 +1810,13 @@ def blen_read_camera(fbx_tmpl, fbx_obj, global_scale): camera.clip_start = elem_props_get_number(fbx_props, b'NearPlane', 0.01) * global_scale camera.clip_end = elem_props_get_number(fbx_props, b'FarPlane', 100.0) * global_scale + if settings.use_custom_props: + blen_read_custom_properties(fbx_obj, camera, settings) + return camera -def blen_read_light(fbx_tmpl, fbx_obj, global_scale): +def blen_read_light(fbx_tmpl, fbx_obj, settings): import math elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'NodeAttribute') @@ -1841,13 +1846,16 @@ def blen_read_light(fbx_tmpl, fbx_obj, global_scale): # TODO, cycles nodes??? lamp.color = elem_props_get_color_rgb(fbx_props, b'Color', (1.0, 1.0, 1.0)) lamp.energy = elem_props_get_number(fbx_props, b'Intensity', 100.0) / 100.0 - lamp.distance = elem_props_get_number(fbx_props, b'DecayStart', 25.0) * global_scale + lamp.distance = elem_props_get_number(fbx_props, b'DecayStart', 25.0) * settings.global_scale lamp.use_shadow = elem_props_get_bool(fbx_props, b'CastShadow', True) if hasattr(lamp, "cycles"): lamp.cycles.cast_shadow = lamp.use_shadow # Keeping this for now, but this is not used nor exposed anymore afaik... lamp.shadow_color = elem_props_get_color_rgb(fbx_props, b'ShadowColor', (0.0, 0.0, 0.0)) + if settings.use_custom_props: + blen_read_custom_properties(fbx_obj, lamp, settings) + return lamp @@ -1861,7 +1869,7 @@ class FbxImportHelperNode: __slots__ = ( '_parent', 'anim_compensation_matrix', 'is_global_animation', 'armature_setup', 'armature', 'bind_matrix', 'bl_bone', 'bl_data', 'bl_obj', 'bone_child_matrix', 'children', 'clusters', - 'fbx_elem', 'fbx_name', 'fbx_transform_data', 'fbx_type', + 'fbx_elem', 'fbx_data_elem', 'fbx_name', 'fbx_transform_data', 'fbx_type', 'is_armature', 'has_bone_children', 'is_bone', 'is_root', 'is_leaf', 'matrix', 'matrix_as_parent', 'matrix_geom', 'meshes', 'post_matrix', 'pre_matrix') @@ -1869,6 +1877,7 @@ class FbxImportHelperNode: self.fbx_name = elem_name_ensure_class(fbx_elem, b'Model') if fbx_elem else 'Unknown' self.fbx_type = fbx_elem.props[2] if fbx_elem else None self.fbx_elem = fbx_elem + self.fbx_data_elem = None # FBX elem of a connected NodeAttribute/Geometry for helpers whose bl_data does not exist or is yet to be created. self.bl_obj = None self.bl_data = bl_data self.bl_bone = None # Name of bone if this is a bone (this may be different to fbx_name if there was a name conflict in Blender!) @@ -2199,7 +2208,7 @@ class FbxImportHelperNode: for child in self.children: child.collect_armature_meshes() - def build_skeleton(self, arm, parent_matrix, parent_bone_size=1, force_connect_children=False): + def build_skeleton(self, arm, parent_matrix, settings, parent_bone_size=1): def child_connect(par_bone, child_bone, child_head, connect_ctx): # child_bone or child_head may be None. force_connect_children, connected = connect_ctx @@ -2246,6 +2255,9 @@ class FbxImportHelperNode: self.bl_obj = arm.bl_obj self.bl_data = arm.bl_data self.bl_bone = bone.name # Could be different from the FBX name! + # Read EditBone custom props the NodeAttribute + if settings.use_custom_props and self.fbx_data_elem: + blen_read_custom_properties(self.fbx_data_elem, bone, settings) # get average distance to children bone_size = 0.0 @@ -2274,6 +2286,7 @@ class FbxImportHelperNode: # while Blender attaches to the tail. self.bone_child_matrix = Matrix.Translation(-bone_tail) + force_connect_children = settings.force_connect_children connect_ctx = [force_connect_children, ...] for child in self.children: if child.is_leaf and force_connect_children: @@ -2282,8 +2295,7 @@ class FbxImportHelperNode: child_head = (bone_matrix @ child.get_bind_matrix().normalized()).translation child_connect(bone, None, child_head, connect_ctx) elif child.is_bone and not child.ignore: - child_bone = child.build_skeleton(arm, bone_matrix, bone_size, - force_connect_children=force_connect_children) + child_bone = child.build_skeleton(arm, bone_matrix, settings, bone_size) # Connection to parent. child_connect(bone, child_bone, None, connect_ctx) @@ -2378,15 +2390,18 @@ class FbxImportHelperNode: return obj - def set_pose_matrix(self, arm): + def set_pose_matrix_and_custom_props(self, arm, settings): pose_bone = arm.bl_obj.pose.bones[self.bl_bone] pose_bone.matrix_basis = self.get_bind_matrix().inverted_safe() @ self.get_matrix() + if settings.use_custom_props: + blen_read_custom_properties(self.fbx_elem, pose_bone, settings) + for child in self.children: if child.ignore: continue if child.is_bone: - child.set_pose_matrix(arm) + child.set_pose_matrix_and_custom_props(arm, settings) def merge_weights(self, combined_weights, fbx_cluster): indices = elem_prop_first(elem_find_first(fbx_cluster, b'Indexes', default=None), default=()) @@ -2482,18 +2497,18 @@ class FbxImportHelperNode: if child.ignore: continue if child.is_bone: - child.build_skeleton(self, Matrix(), force_connect_children=settings.force_connect_children) + child.build_skeleton(self, Matrix(), settings) bpy.ops.object.mode_set(mode='OBJECT') arm.hide_viewport = is_hidden - # Set pose matrix + # Set pose matrix and PoseBone custom properties for child in self.children: if child.ignore: continue if child.is_bone: - child.set_pose_matrix(self) + child.set_pose_matrix_and_custom_props(self, settings) # Add bone children: for child in self.children: @@ -2888,7 +2903,7 @@ def load(operator, context, filepath="", continue if fbx_obj.props[-1] == b'Camera': assert(blen_data is None) - fbx_item[1] = blen_read_camera(fbx_tmpl, fbx_obj, global_scale) + fbx_item[1] = blen_read_camera(fbx_tmpl, fbx_obj, settings) _(); del _ # ---- @@ -2902,7 +2917,7 @@ def load(operator, context, filepath="", continue if fbx_obj.props[-1] == b'Light': assert(blen_data is None) - fbx_item[1] = blen_read_light(fbx_tmpl, fbx_obj, global_scale) + fbx_item[1] = blen_read_light(fbx_tmpl, fbx_obj, settings) _(); del _ # ---- @@ -2971,6 +2986,9 @@ def load(operator, context, filepath="", if fbx_sdata.id not in {b'Geometry', b'NodeAttribute'}: continue parent.bl_data = bl_data + if bl_data is None: + # If there's no bl_data, add the fbx_sdata so that it can be read when creating the bl_data/bone + parent.fbx_data_elem = fbx_sdata else: # set parent child.parent = parent -- 2.30.2