Fix disparity of FBX custom properties import and export #104702

Merged
Bastien Montagne merged 1 commits from Mysteryem/blender-addons:fbx_custom_prop_parity_pr into blender-v3.6-release 2023-06-21 15:52:58 +02:00
2 changed files with 40 additions and 14 deletions

View File

@ -1719,6 +1719,14 @@ def fbx_data_video_elements(root, vid, scene_data):
#~ else: #~ else:
#~ elem_data_single_bytes(fbx_vid, b"Content", b"") #~ 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): def fbx_data_armature_elements(root, arm_obj, scene_data):
""" """

View File

@ -1777,10 +1777,12 @@ def blen_read_texture_image(fbx_tmpl, fbx_obj, basedir, settings):
return image return image
def blen_read_camera(fbx_tmpl, fbx_obj, global_scale): def blen_read_camera(fbx_tmpl, fbx_obj, settings):
# meters to inches # meters to inches
M2I = 0.0393700787 M2I = 0.0393700787
global_scale = settings.global_scale
elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'NodeAttribute') elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'NodeAttribute')
fbx_props = (elem_find_first(fbx_obj, b'Properties70'), 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_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 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 return camera
def blen_read_light(fbx_tmpl, fbx_obj, global_scale): def blen_read_light(fbx_tmpl, fbx_obj, settings):
import math import math
elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'NodeAttribute') 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??? # TODO, cycles nodes???
lamp.color = elem_props_get_color_rgb(fbx_props, b'Color', (1.0, 1.0, 1.0)) 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.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) lamp.use_shadow = elem_props_get_bool(fbx_props, b'CastShadow', True)
if hasattr(lamp, "cycles"): if hasattr(lamp, "cycles"):
lamp.cycles.cast_shadow = lamp.use_shadow lamp.cycles.cast_shadow = lamp.use_shadow
# Keeping this for now, but this is not used nor exposed anymore afaik... # 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)) 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 return lamp
@ -1861,7 +1869,7 @@ class FbxImportHelperNode:
__slots__ = ( __slots__ = (
'_parent', 'anim_compensation_matrix', 'is_global_animation', 'armature_setup', 'armature', 'bind_matrix', '_parent', 'anim_compensation_matrix', 'is_global_animation', 'armature_setup', 'armature', 'bind_matrix',
'bl_bone', 'bl_data', 'bl_obj', 'bone_child_matrix', 'children', 'clusters', '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', 'is_armature', 'has_bone_children', 'is_bone', 'is_root', 'is_leaf',
'matrix', 'matrix_as_parent', 'matrix_geom', 'meshes', 'post_matrix', 'pre_matrix') '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_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_type = fbx_elem.props[2] if fbx_elem else None
self.fbx_elem = fbx_elem 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_obj = None
self.bl_data = bl_data 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!) 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: for child in self.children:
child.collect_armature_meshes() 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): def child_connect(par_bone, child_bone, child_head, connect_ctx):
# child_bone or child_head may be None. # child_bone or child_head may be None.
force_connect_children, connected = connect_ctx force_connect_children, connected = connect_ctx
@ -2246,6 +2255,9 @@ class FbxImportHelperNode:
self.bl_obj = arm.bl_obj self.bl_obj = arm.bl_obj
self.bl_data = arm.bl_data self.bl_data = arm.bl_data
self.bl_bone = bone.name # Could be different from the FBX name! 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 # get average distance to children
bone_size = 0.0 bone_size = 0.0
@ -2274,6 +2286,7 @@ class FbxImportHelperNode:
# while Blender attaches to the tail. # while Blender attaches to the tail.
self.bone_child_matrix = Matrix.Translation(-bone_tail) self.bone_child_matrix = Matrix.Translation(-bone_tail)
force_connect_children = settings.force_connect_children
connect_ctx = [force_connect_children, ...] connect_ctx = [force_connect_children, ...]
for child in self.children: for child in self.children:
if child.is_leaf and force_connect_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_head = (bone_matrix @ child.get_bind_matrix().normalized()).translation
child_connect(bone, None, child_head, connect_ctx) child_connect(bone, None, child_head, connect_ctx)
elif child.is_bone and not child.ignore: elif child.is_bone and not child.ignore:
child_bone = child.build_skeleton(arm, bone_matrix, bone_size, child_bone = child.build_skeleton(arm, bone_matrix, settings, bone_size)
force_connect_children=force_connect_children)
# Connection to parent. # Connection to parent.
child_connect(bone, child_bone, None, connect_ctx) child_connect(bone, child_bone, None, connect_ctx)
@ -2378,15 +2390,18 @@ class FbxImportHelperNode:
return obj 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 = arm.bl_obj.pose.bones[self.bl_bone]
pose_bone.matrix_basis = self.get_bind_matrix().inverted_safe() @ self.get_matrix() 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: for child in self.children:
if child.ignore: if child.ignore:
continue continue
if child.is_bone: 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): def merge_weights(self, combined_weights, fbx_cluster):
indices = elem_prop_first(elem_find_first(fbx_cluster, b'Indexes', default=None), default=()) indices = elem_prop_first(elem_find_first(fbx_cluster, b'Indexes', default=None), default=())
@ -2482,18 +2497,18 @@ class FbxImportHelperNode:
if child.ignore: if child.ignore:
continue continue
if child.is_bone: 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') bpy.ops.object.mode_set(mode='OBJECT')
arm.hide_viewport = is_hidden arm.hide_viewport = is_hidden
# Set pose matrix # Set pose matrix and PoseBone custom properties
for child in self.children: for child in self.children:
if child.ignore: if child.ignore:
continue continue
if child.is_bone: if child.is_bone:
child.set_pose_matrix(self) child.set_pose_matrix_and_custom_props(self, settings)
# Add bone children: # Add bone children:
for child in self.children: for child in self.children:
@ -2888,7 +2903,7 @@ def load(operator, context, filepath="",
continue continue
if fbx_obj.props[-1] == b'Camera': if fbx_obj.props[-1] == b'Camera':
assert(blen_data is None) 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 _ _(); del _
# ---- # ----
@ -2902,7 +2917,7 @@ def load(operator, context, filepath="",
continue continue
if fbx_obj.props[-1] == b'Light': if fbx_obj.props[-1] == b'Light':
assert(blen_data is None) 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 _ _(); del _
# ---- # ----
@ -2971,6 +2986,9 @@ def load(operator, context, filepath="",
if fbx_sdata.id not in {b'Geometry', b'NodeAttribute'}: if fbx_sdata.id not in {b'Geometry', b'NodeAttribute'}:
continue continue
parent.bl_data = bl_data 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: else:
# set parent # set parent
child.parent = parent child.parent = parent