From 692f4691abc6a21f9d30f94a99882f2c46330903 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius <aras@nesnausk.org> Date: Wed, 14 Sep 2022 23:00:01 +0300 Subject: [PATCH] FBX: support new color attributes, and sRGB/Linear/None colors option (T98721) FBX exporter code was only supporting "Face Corner - Byte Color" attribute type, using the deprecated Mesh.vertex_colors API. Update the to use the new attributes API, and to handle all the attribute combinations. An option to pick between sRGB (default, matches current behavior), Linear (requested in T82847) or None (don't export/import any color attributes) has been added to the exporter and the importer. Reviewed By: Bastien Montagne Differential Revision: https://developer.blender.org/D15942 --- io_scene_fbx/__init__.py | 24 ++++++++++++++++++++++-- io_scene_fbx/export_fbx_bin.py | 29 ++++++++++++++++++++++------- io_scene_fbx/fbx_utils.py | 4 ++-- io_scene_fbx/import_fbx.py | 19 ++++++++++++------- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py index feea84369..185e8336d 100644 --- a/io_scene_fbx/__init__.py +++ b/io_scene_fbx/__init__.py @@ -3,8 +3,8 @@ bl_info = { "name": "FBX format", "author": "Campbell Barton, Bastien Montagne, Jens Restemeier", - "version": (4, 36, 3), - "blender": (3, 2, 0), + "version": (4, 37, 0), + "blender": (3, 4, 0), "location": "File > Import-Export", "description": "FBX IO meshes, UV's, vertex colors, materials, textures, cameras, lamps and actions", "warning": "", @@ -89,6 +89,15 @@ class ImportFBX(bpy.types.Operator, ImportHelper): description="Import custom normals, if available (otherwise Blender will recompute them)", default=True, ) + colors_type: EnumProperty( + name="Vertex Colors", + items=(('NONE', "None", "Do not import color attributes"), + ('SRGB', "sRGB", "Expect file colors in sRGB color space"), + ('LINEAR', "Linear", "Expect file colors in linear color space"), + ), + description="Import vertex color attributes", + default='SRGB', + ) use_image_search: BoolProperty( name="Image Search", @@ -230,6 +239,7 @@ class FBX_PT_import_include(bpy.types.Panel): sub.enabled = operator.use_custom_props sub.prop(operator, "use_custom_props_enum_as_string") layout.prop(operator, "use_image_search") + layout.prop(operator, "colors_type") class FBX_PT_import_transform(bpy.types.Panel): @@ -463,6 +473,15 @@ class ExportFBX(bpy.types.Operator, ExportHelper): "(prefer 'Normals Only' option if your target importer understand split normals)", default='OFF', ) + colors_type: EnumProperty( + name="Vertex Colors", + items=(('NONE', "None", "Do not export color attributes"), + ('SRGB', "sRGB", "Export colors in sRGB color space"), + ('LINEAR', "Linear", "Export colors in linear color space"), + ), + description="Export vertex color attributes", + default='SRGB', + ) use_subsurf: BoolProperty( name="Export Subdivision Surface", description="Export the last Catmull-Rom subdivision modifier as FBX subdivision " @@ -767,6 +786,7 @@ class FBX_PT_export_geometry(bpy.types.Panel): sub = layout.row() #~ sub.enabled = operator.mesh_smooth_type in {'OFF'} sub.prop(operator, "use_tspace") + layout.prop(operator, "colors_type") class FBX_PT_export_armature(bpy.types.Panel): diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py index f560b19c9..a4a96bf54 100644 --- a/io_scene_fbx/export_fbx_bin.py +++ b/io_scene_fbx/export_fbx_bin.py @@ -1111,14 +1111,20 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes): me.free_normals_split() # Write VertexColor Layers. - vcolnumber = len(me.vertex_colors) + colors_type = scene_data.settings.colors_type + vcolnumber = 0 if colors_type == 'NONE' else len(me.color_attributes) if vcolnumber: def _coltuples_gen(raw_cols): return zip(*(iter(raw_cols),) * 4) - t_lc = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 4 - for colindex, collayer in enumerate(me.vertex_colors): - collayer.data.foreach_get("color", t_lc) + color_prop_name = "color_srgb" if colors_type == 'SRGB' else "color" + + for colindex, collayer in enumerate(me.color_attributes): + is_point = collayer.domain == "POINT" + vcollen = len(me.vertices if is_point else me.loops) + t_lc = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * vcollen * 4 + collayer.data.foreach_get(color_prop_name, t_lc) + lay_vcol = elem_data_single_int32(geom, b"LayerElementColor", colindex) elem_data_single_int32(lay_vcol, b"Version", FBX_GEOMETRY_VCOLOR_VERSION) elem_data_single_string_unicode(lay_vcol, b"Name", collayer.name) @@ -1129,9 +1135,16 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes): elem_data_single_float64_array(lay_vcol, b"Colors", chain(*col2idx)) # Flatten again... col2idx = {col: idx for idx, col in enumerate(col2idx)} - elem_data_single_int32_array(lay_vcol, b"ColorIndex", (col2idx[c] for c in _coltuples_gen(t_lc))) + col_indices = list(col2idx[c] for c in _coltuples_gen(t_lc)) + if is_point: + # for "point" domain colors, we could directly emit them + # with a "ByVertex" mapping type, but some software does not + # properly understand that. So expand to full "ByPolygonVertex" + # index map. + col_indices = list((col_indices[c.vertex_index] for c in me.loops)) + elem_data_single_int32_array(lay_vcol, b"ColorIndex", col_indices) del col2idx - del t_lc + del t_lc del _coltuples_gen # Write UV layers. @@ -3021,6 +3034,7 @@ def save_single(operator, scene, depsgraph, filepath="", use_custom_props=False, bake_space_transform=False, armature_nodetype='NULL', + colors_type='SRGB', **kwargs ): @@ -3088,7 +3102,7 @@ def save_single(operator, scene, depsgraph, filepath="", add_leaf_bones, bone_correction_matrix, bone_correction_matrix_inv, bake_anim, bake_anim_use_all_bones, bake_anim_use_nla_strips, bake_anim_use_all_actions, bake_anim_step, bake_anim_simplify_factor, bake_anim_force_startend_keying, - False, media_settings, use_custom_props, + False, media_settings, use_custom_props, colors_type, ) import bpy_extras.io_utils @@ -3155,6 +3169,7 @@ def defaults_unity3d(): "use_mesh_modifiers_render": True, "use_mesh_edges": False, "mesh_smooth_type": 'FACE', + "colors_type": 'SRGB', "use_subsurf": False, "use_tspace": False, # XXX Why? Unity is expected to support tspace import... "use_triangles": False, diff --git a/io_scene_fbx/fbx_utils.py b/io_scene_fbx/fbx_utils.py index 41c63ef8e..e52cd9eb1 100644 --- a/io_scene_fbx/fbx_utils.py +++ b/io_scene_fbx/fbx_utils.py @@ -1220,7 +1220,7 @@ FBXExportSettings = namedtuple("FBXExportSettings", ( "bone_correction_matrix", "bone_correction_matrix_inv", "bake_anim", "bake_anim_use_all_bones", "bake_anim_use_nla_strips", "bake_anim_use_all_actions", "bake_anim_step", "bake_anim_simplify_factor", "bake_anim_force_startend_keying", - "use_metadata", "media_settings", "use_custom_props", + "use_metadata", "media_settings", "use_custom_props", "colors_type", )) # Helper container gathering some data we need multiple times: @@ -1249,5 +1249,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", + "use_prepost_rot", "colors_type", )) diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py index 72d666c1c..00376f38c 100644 --- a/io_scene_fbx/import_fbx.py +++ b/io_scene_fbx/import_fbx.py @@ -1061,7 +1061,12 @@ def blen_read_geom_layer_uv(fbx_obj, mesh): ) -def blen_read_geom_layer_color(fbx_obj, mesh): +def blen_read_geom_layer_color(fbx_obj, mesh, colors_type): + if colors_type == 'NONE': + return + use_srgb = colors_type == 'SRGB' + layer_type = 'BYTE_COLOR' if use_srgb else 'FLOAT_COLOR' + color_prop_name = "color_srgb" if use_srgb else "color" # almost same as UV's for layer_id in (b'LayerElementColor',): for fbx_layer in elem_find_iter(fbx_obj, layer_id): @@ -1074,8 +1079,7 @@ def blen_read_geom_layer_color(fbx_obj, mesh): fbx_layer_data = elem_prop_first(elem_find_first(fbx_layer, b'Colors')) fbx_layer_index = elem_prop_first(elem_find_first(fbx_layer, b'ColorIndex')) - # Always init our new layers with full white opaque color. - color_lay = mesh.vertex_colors.new(name=fbx_layer_name, do_init=False) + color_lay = mesh.color_attributes.new(name=fbx_layer_name, type=layer_type, domain='CORNER') if color_lay is None: print("Failed to add {%r %r} vertex color layer to %r (probably too many of them?)" @@ -1090,7 +1094,7 @@ def blen_read_geom_layer_color(fbx_obj, mesh): continue blen_read_geom_array_mapped_polyloop( - mesh, blen_data, "color", + mesh, blen_data, color_prop_name, fbx_layer_data, fbx_layer_index, fbx_layer_mapping, fbx_layer_ref, 4, 4, layer_id, @@ -1289,7 +1293,7 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings): blen_read_geom_layer_material(fbx_obj, mesh) blen_read_geom_layer_uv(fbx_obj, mesh) - blen_read_geom_layer_color(fbx_obj, mesh) + blen_read_geom_layer_color(fbx_obj, mesh, settings.colors_type) if fbx_edges: # edges in fact index the polygons (NOT the vertices) @@ -2365,7 +2369,8 @@ def load(operator, context, filepath="", automatic_bone_orientation=False, primary_bone_axis='Y', secondary_bone_axis='X', - use_prepost_rot=True): + use_prepost_rot=True, + colors_type='SRGB'): global fbx_elem_nil fbx_elem_nil = FBXElem('', (), (), ()) @@ -2504,7 +2509,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, + use_prepost_rot, colors_type, ) # #### And now, the "real" data. -- GitLab