From acffdf2725278dd182c3f0cfedfdf55d281fba69 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 4 Sep 2023 16:32:40 +0100 Subject: [PATCH 1/5] Speed up _transformation_curves_gen Precalculate the matrices that do not change each frame. Convert animated rotation values to radians in advance with NumPy. --- io_scene_fbx/import_fbx.py | 90 +++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py index 7d18ef494..7ffaf5f3b 100644 --- a/io_scene_fbx/import_fbx.py +++ b/io_scene_fbx/import_fbx.py @@ -526,6 +526,56 @@ def blen_read_object_transform_preprocess(fbx_props, fbx_obj, rot_alt_mat, use_p # --------- # Animation +def _blen_read_object_transform_do_anim(transform_data, lcl_translation_mat, lcl_rot_euler, lcl_scale_mat): + """Specialized version of blen_read_object_transform_do for animation that pre-calculates the non-animated matrices + and returns a function that returns base_mat @ geom_mat. + + The lcl_translation_mat, lcl_rot_euler and lcl_scale_mat arguments should have their values updated each frame and + then the returned function will calculate the updated matrix.""" + # translation + geom_loc = Matrix.Translation(transform_data.geom_loc) + + # rotation + def to_rot(rot, rot_ord): + return Euler(convert_deg_to_rad_iter(rot), rot_ord).to_matrix().to_4x4() + + pre_rot = to_rot(transform_data.pre_rot, 'XYZ') + pst_rot = to_rot(transform_data.pst_rot, 'XYZ') + pst_rot_inv = pst_rot.inverted_safe() + geom_rot = to_rot(transform_data.geom_rot, 'XYZ') + + rot_ofs = Matrix.Translation(transform_data.rot_ofs) + rot_piv = Matrix.Translation(transform_data.rot_piv) + rot_piv_inv = rot_piv.inverted_safe() + sca_ofs = Matrix.Translation(transform_data.sca_ofs) + sca_piv = Matrix.Translation(transform_data.sca_piv) + sca_piv_inv = sca_piv.inverted_safe() + + # scale + geom_scale = Matrix() + geom_scale[0][0], geom_scale[1][1], geom_scale[2][2] = transform_data.geom_sca + + # Some matrices can be combined in advance, using the associative property of matrix multiplication, so that less + # matrix multiplication is required each frame. + geom_mat = geom_loc @ geom_rot @ geom_scale + + post_lcl_translation = rot_ofs @ rot_piv @ pre_rot + post_lcl_rotation = transform_data.rot_alt_mat @ pst_rot_inv @ rot_piv_inv @ sca_ofs @ sca_piv + post_lcl_scaling = sca_piv_inv @ geom_mat + + # Get the bound to_matrix method to avoid re-binding it on each call. + lcl_rot_to_matrix = lcl_rot_euler.to_matrix + # Get the unbound Matrix.to_4x4 method to avoid having to look it up again on each call. + matrix_to_4x4 = Matrix.to_4x4 + + return lambda: (lcl_translation_mat @ + post_lcl_translation @ + matrix_to_4x4(lcl_rot_to_matrix()) @ + post_lcl_rotation @ + lcl_scale_mat @ + post_lcl_scaling) + + def _transformation_curves_gen(item, values_arrays, channel_keys): """Yields flattened location/rotation/scaling values for imported PoseBone/Object Lcl Translation/Rotation/Scaling animation curve values. @@ -550,21 +600,39 @@ def _transformation_curves_gen(item, values_arrays, channel_keys): # Pre-compute inverted local rest matrix of the bone, if relevant. restmat_inv = item.get_bind_matrix().inverted_safe() if item.is_bone else None - transform_prop_to_attr = { - b'Lcl Translation': transform_data.loc, - b'Lcl Rotation': transform_data.rot, - b'Lcl Scaling': transform_data.sca, - } + # Create matrices/euler from the initial transformation values of this item. + # These variables will be updated in-place as we iterate through each frame. + translation_matrix = Matrix.Translation(transform_data.loc) + rotation_euler = Euler(transform_data.rot, transform_data.rot_ord) + scaling_matrix = Matrix() + scaling_matrix[0][0], scaling_matrix[1][1], scaling_matrix[2][2] = transform_data.sca + + # Create setters into translation_matrix, rotation_euler and scaling_matrix for each values_array and convert any + # rotation values into radians. + setters = [] + values_arrays_converted = [] + for values_array, (fbx_prop, channel) in zip(values_arrays, channel_keys): + if fbx_prop == b'Lcl Translation': + # translation_matrix.translation[channel] = value + setter = partial(setitem, translation_matrix.translation, channel) + elif fbx_prop == b'Lcl Rotation': + # FBX rotations are in degrees, but Blender uses radians, so convert all rotation values in advance. + values_array = np.deg2rad(values_array) + # rotation_euler[channel] = value + setter = partial(setitem, rotation_euler, channel) + else: + assert(fbx_prop == b'Lcl Scaling') + # scaling_matrix[channel][channel] = value + setter = partial(setitem, scaling_matrix[channel], channel) + setters.append(setter) + values_arrays_converted.append(values_array) - # Create a setter into transform_data for each values array. e.g. a values array for 'Lcl Scaling' with channel == 2 - # would set transform_data.sca[2]. - setters = [partial(setitem, transform_prop_to_attr[fbx_prop], channel) for fbx_prop, channel in channel_keys] # Create an iterator that gets one value from each array. Each iterated tuple will be all the imported # Lcl Translation/Lcl Rotation/Lcl Scaling values for a single frame, in that order. # Note that an FBX animation does not have to animate all the channels, so only the animated channels of each # property will be present. # .data, the memoryview of an np.ndarray, is faster to iterate than the ndarray itself. - frame_values_it = zip(*(arr.data for arr in values_arrays)) + frame_values_it = zip(*(arr.data for arr in values_arrays_converted)) # Pre-get/calculate these to slightly reduce the work done inside the loop. anim_compensation_matrix = item.anim_compensation_matrix @@ -582,6 +650,8 @@ def _transformation_curves_gen(item, values_arrays, channel_keys): to_axis_angle = Quaternion.to_axis_angle to_euler = Quaternion.to_euler + calc_mat = _blen_read_object_transform_do_anim(transform_data, translation_matrix, rotation_euler, scaling_matrix) + # Iterate through the values for each frame. for frame_values in frame_values_it: # Set each value into its corresponding attribute in transform_data. @@ -589,7 +659,7 @@ def _transformation_curves_gen(item, values_arrays, channel_keys): setter(value) # Calculate the updated matrix for this frame. - mat, _, _ = blen_read_object_transform_do(transform_data) + mat = calc_mat() # compensate for changes in the local matrix during processing if do_anim_compensation_matrix: -- 2.30.2 From 888ac37a7df34e7c88c99a31d1396a8d9d4cef9d Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 4 Sep 2023 20:50:50 +0100 Subject: [PATCH 2/5] Move pre- and post- matrices outside the hot loop --- io_scene_fbx/import_fbx.py | 77 +++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py index 7ffaf5f3b..c225e190b 100644 --- a/io_scene_fbx/import_fbx.py +++ b/io_scene_fbx/import_fbx.py @@ -526,7 +526,8 @@ def blen_read_object_transform_preprocess(fbx_props, fbx_obj, rot_alt_mat, use_p # --------- # Animation -def _blen_read_object_transform_do_anim(transform_data, lcl_translation_mat, lcl_rot_euler, lcl_scale_mat): +def _blen_read_object_transform_do_anim(transform_data, lcl_translation_mat, lcl_rot_euler, lcl_scale_mat, + extra_pre_matrix, extra_post_matrix): """Specialized version of blen_read_object_transform_do for animation that pre-calculates the non-animated matrices and returns a function that returns base_mat @ geom_mat. @@ -561,19 +562,30 @@ def _blen_read_object_transform_do_anim(transform_data, lcl_translation_mat, lcl post_lcl_translation = rot_ofs @ rot_piv @ pre_rot post_lcl_rotation = transform_data.rot_alt_mat @ pst_rot_inv @ rot_piv_inv @ sca_ofs @ sca_piv - post_lcl_scaling = sca_piv_inv @ geom_mat + post_lcl_scaling = sca_piv_inv @ geom_mat @ extra_post_matrix # Get the bound to_matrix method to avoid re-binding it on each call. lcl_rot_to_matrix = lcl_rot_euler.to_matrix # Get the unbound Matrix.to_4x4 method to avoid having to look it up again on each call. matrix_to_4x4 = Matrix.to_4x4 - return lambda: (lcl_translation_mat @ - post_lcl_translation @ - matrix_to_4x4(lcl_rot_to_matrix()) @ - post_lcl_rotation @ - lcl_scale_mat @ - post_lcl_scaling) + if extra_pre_matrix != Matrix(): + # There aren't any other matrices that must be multiplied before lcl_translation_mat that extra_pre_matrix can + # be combined with, so a separate lambda is required. + return lambda: (extra_pre_matrix @ + lcl_translation_mat @ + post_lcl_translation @ + matrix_to_4x4(lcl_rot_to_matrix()) @ + post_lcl_rotation @ + lcl_scale_mat @ + post_lcl_scaling) + else: + return lambda: (lcl_translation_mat @ + post_lcl_translation @ + matrix_to_4x4(lcl_rot_to_matrix()) @ + post_lcl_rotation @ + lcl_scale_mat @ + post_lcl_scaling) def _transformation_curves_gen(item, values_arrays, channel_keys): @@ -597,8 +609,22 @@ def _transformation_curves_gen(item, values_arrays, channel_keys): rot_eul_prev = bl_obj.rotation_euler.copy() rot_quat_prev = bl_obj.rotation_quaternion.copy() - # Pre-compute inverted local rest matrix of the bone, if relevant. - restmat_inv = item.get_bind_matrix().inverted_safe() if item.is_bone else None + # Pre-compute combined pre matrix + # Remove that rest pose matrix from current matrix (also in parent space) by computing the inverted local rest + # matrix of the bone, if relevant. + combined_pre_matrix = item.get_bind_matrix().inverted_safe() if item.is_bone else Matrix() + + # pre-matrix will contain any correction for a parent's correction matrix or the global matrix + if item.pre_matrix: + combined_pre_matrix @= item.pre_matrix + + # Pre-compute combined post matrix + # compensate for changes in the local matrix during processing + combined_post_matrix = item.anim_compensation_matrix if item.anim_compensation_matrix else Matrix() + + # post-matrix will contain any correction for lights, camera and bone orientation + if item.post_matrix: + combined_post_matrix @= item.post_matrix # Create matrices/euler from the initial transformation values of this item. # These variables will be updated in-place as we iterate through each frame. @@ -634,23 +660,12 @@ def _transformation_curves_gen(item, values_arrays, channel_keys): # .data, the memoryview of an np.ndarray, is faster to iterate than the ndarray itself. frame_values_it = zip(*(arr.data for arr in values_arrays_converted)) - # Pre-get/calculate these to slightly reduce the work done inside the loop. - anim_compensation_matrix = item.anim_compensation_matrix - do_anim_compensation_matrix = bool(anim_compensation_matrix) - - pre_matrix = item.pre_matrix - do_pre_matrix = bool(pre_matrix) - - post_matrix = item.post_matrix - do_post_matrix = bool(post_matrix) - - do_restmat_inv = bool(restmat_inv) - decompose = Matrix.decompose to_axis_angle = Quaternion.to_axis_angle to_euler = Quaternion.to_euler - calc_mat = _blen_read_object_transform_do_anim(transform_data, translation_matrix, rotation_euler, scaling_matrix) + calc_mat = _blen_read_object_transform_do_anim(transform_data, translation_matrix, rotation_euler, scaling_matrix, + combined_pre_matrix, combined_post_matrix) # Iterate through the values for each frame. for frame_values in frame_values_it: @@ -661,22 +676,6 @@ def _transformation_curves_gen(item, values_arrays, channel_keys): # Calculate the updated matrix for this frame. mat = calc_mat() - # compensate for changes in the local matrix during processing - if do_anim_compensation_matrix: - mat = mat @ anim_compensation_matrix - - # apply pre- and post matrix - # post-matrix will contain any correction for lights, camera and bone orientation - # pre-matrix will contain any correction for a parent's correction matrix or the global matrix - if do_pre_matrix: - mat = pre_matrix @ mat - if do_post_matrix: - mat = mat @ post_matrix - - # And now, remove that rest pose matrix from current mat (also in parent space). - if do_restmat_inv: - mat = restmat_inv @ mat - # Now we have a virtual matrix of transform from AnimCurves, we can yield keyframe values! loc, rot, sca = decompose(mat) if rot_mode == 'QUATERNION': -- 2.30.2 From 691c3d0c45be49efcf769d080f9eb4b6fb2cdc7f Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Tue, 5 Sep 2023 03:46:45 +0100 Subject: [PATCH 3/5] Tidy up --- io_scene_fbx/import_fbx.py | 109 +++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py index c225e190b..e2d39ce31 100644 --- a/io_scene_fbx/import_fbx.py +++ b/io_scene_fbx/import_fbx.py @@ -529,22 +529,25 @@ def blen_read_object_transform_preprocess(fbx_props, fbx_obj, rot_alt_mat, use_p def _blen_read_object_transform_do_anim(transform_data, lcl_translation_mat, lcl_rot_euler, lcl_scale_mat, extra_pre_matrix, extra_post_matrix): """Specialized version of blen_read_object_transform_do for animation that pre-calculates the non-animated matrices - and returns a function that returns base_mat @ geom_mat. + and returns a function that calculates (base_mat @ geom_mat). See the comments in blen_read_object_transform_do for + a full description of what this function is doing. The lcl_translation_mat, lcl_rot_euler and lcl_scale_mat arguments should have their values updated each frame and - then the returned function will calculate the updated matrix.""" - # translation + then calling the returned function will calculate the matrix for the current frame. + + extra_pre_matrix and extra_post_matrix are any extra matrices to multiply first/last.""" + # Translation geom_loc = Matrix.Translation(transform_data.geom_loc) - # rotation - def to_rot(rot, rot_ord): - return Euler(convert_deg_to_rad_iter(rot), rot_ord).to_matrix().to_4x4() - - pre_rot = to_rot(transform_data.pre_rot, 'XYZ') - pst_rot = to_rot(transform_data.pst_rot, 'XYZ') - pst_rot_inv = pst_rot.inverted_safe() - geom_rot = to_rot(transform_data.geom_rot, 'XYZ') + # Rotation + def to_rot_xyz(rot): + # All the rotations that can be precalculated have a fixed XYZ order. + return Euler(convert_deg_to_rad_iter(rot), 'XYZ').to_matrix().to_4x4() + pre_rot = to_rot_xyz(transform_data.pre_rot) + pst_rot_inv = to_rot_xyz(transform_data.pst_rot).inverted_safe() + geom_rot = to_rot_xyz(transform_data.geom_rot) + # Offsets and pivots rot_ofs = Matrix.Translation(transform_data.rot_ofs) rot_piv = Matrix.Translation(transform_data.rot_piv) rot_piv_inv = rot_piv.inverted_safe() @@ -552,37 +555,36 @@ def _blen_read_object_transform_do_anim(transform_data, lcl_translation_mat, lcl sca_piv = Matrix.Translation(transform_data.sca_piv) sca_piv_inv = sca_piv.inverted_safe() - # scale + # Scale geom_scale = Matrix() geom_scale[0][0], geom_scale[1][1], geom_scale[2][2] = transform_data.geom_sca # Some matrices can be combined in advance, using the associative property of matrix multiplication, so that less # matrix multiplication is required each frame. geom_mat = geom_loc @ geom_rot @ geom_scale - post_lcl_translation = rot_ofs @ rot_piv @ pre_rot post_lcl_rotation = transform_data.rot_alt_mat @ pst_rot_inv @ rot_piv_inv @ sca_ofs @ sca_piv post_lcl_scaling = sca_piv_inv @ geom_mat @ extra_post_matrix # Get the bound to_matrix method to avoid re-binding it on each call. - lcl_rot_to_matrix = lcl_rot_euler.to_matrix + lcl_rot_euler_to_matrix_3x3 = lcl_rot_euler.to_matrix # Get the unbound Matrix.to_4x4 method to avoid having to look it up again on each call. matrix_to_4x4 = Matrix.to_4x4 - if extra_pre_matrix != Matrix(): + if extra_pre_matrix == Matrix(): # There aren't any other matrices that must be multiplied before lcl_translation_mat that extra_pre_matrix can - # be combined with, so a separate lambda is required. - return lambda: (extra_pre_matrix @ - lcl_translation_mat @ + # be combined with, so skip extra_pre_matrix when it's the identity matrix. + return lambda: (lcl_translation_mat @ post_lcl_translation @ - matrix_to_4x4(lcl_rot_to_matrix()) @ + matrix_to_4x4(lcl_rot_euler_to_matrix_3x3()) @ post_lcl_rotation @ lcl_scale_mat @ post_lcl_scaling) else: - return lambda: (lcl_translation_mat @ + return lambda: (extra_pre_matrix @ + lcl_translation_mat @ post_lcl_translation @ - matrix_to_4x4(lcl_rot_to_matrix()) @ + matrix_to_4x4(lcl_rot_euler_to_matrix_3x3()) @ post_lcl_rotation @ lcl_scale_mat @ post_lcl_scaling) @@ -609,48 +611,46 @@ def _transformation_curves_gen(item, values_arrays, channel_keys): rot_eul_prev = bl_obj.rotation_euler.copy() rot_quat_prev = bl_obj.rotation_quaternion.copy() - # Pre-compute combined pre matrix + # Pre-compute combined pre-matrix # Remove that rest pose matrix from current matrix (also in parent space) by computing the inverted local rest # matrix of the bone, if relevant. combined_pre_matrix = item.get_bind_matrix().inverted_safe() if item.is_bone else Matrix() - - # pre-matrix will contain any correction for a parent's correction matrix or the global matrix + # item.pre_matrix will contain any correction for a parent's correction matrix or the global matrix if item.pre_matrix: combined_pre_matrix @= item.pre_matrix - # Pre-compute combined post matrix - # compensate for changes in the local matrix during processing + # Pre-compute combined post-matrix + # Compensate for changes in the local matrix during processing combined_post_matrix = item.anim_compensation_matrix if item.anim_compensation_matrix else Matrix() - - # post-matrix will contain any correction for lights, camera and bone orientation + # item.post_matrix will contain any correction for lights, camera and bone orientation if item.post_matrix: combined_post_matrix @= item.post_matrix # Create matrices/euler from the initial transformation values of this item. # These variables will be updated in-place as we iterate through each frame. - translation_matrix = Matrix.Translation(transform_data.loc) - rotation_euler = Euler(transform_data.rot, transform_data.rot_ord) - scaling_matrix = Matrix() - scaling_matrix[0][0], scaling_matrix[1][1], scaling_matrix[2][2] = transform_data.sca + lcl_translation_mat = Matrix.Translation(transform_data.loc) + lcl_rotation_eul = Euler(transform_data.rot, transform_data.rot_ord) + lcl_scaling_mat = Matrix() + lcl_scaling_mat[0][0], lcl_scaling_mat[1][1], lcl_scaling_mat[2][2] = transform_data.sca - # Create setters into translation_matrix, rotation_euler and scaling_matrix for each values_array and convert any - # rotation values into radians. - setters = [] + # Create setters into lcl_translation_mat, lcl_rotation_eul and lcl_scaling_mat for each values_array and convert + # any rotation values into radians. + lcl_setters = [] values_arrays_converted = [] for values_array, (fbx_prop, channel) in zip(values_arrays, channel_keys): if fbx_prop == b'Lcl Translation': - # translation_matrix.translation[channel] = value - setter = partial(setitem, translation_matrix.translation, channel) + # lcl_translation_mat.translation[channel] = value + setter = partial(setitem, lcl_translation_mat.translation, channel) elif fbx_prop == b'Lcl Rotation': # FBX rotations are in degrees, but Blender uses radians, so convert all rotation values in advance. values_array = np.deg2rad(values_array) - # rotation_euler[channel] = value - setter = partial(setitem, rotation_euler, channel) + # lcl_rotation_eul[channel] = value + setter = partial(setitem, lcl_rotation_eul, channel) else: assert(fbx_prop == b'Lcl Scaling') - # scaling_matrix[channel][channel] = value - setter = partial(setitem, scaling_matrix[channel], channel) - setters.append(setter) + # lcl_scaling_mat[channel][channel] = value + setter = partial(setitem, lcl_scaling_mat[channel], channel) + lcl_setters.append(setter) values_arrays_converted.append(values_array) # Create an iterator that gets one value from each array. Each iterated tuple will be all the imported @@ -660,33 +660,36 @@ def _transformation_curves_gen(item, values_arrays, channel_keys): # .data, the memoryview of an np.ndarray, is faster to iterate than the ndarray itself. frame_values_it = zip(*(arr.data for arr in values_arrays_converted)) - decompose = Matrix.decompose - to_axis_angle = Quaternion.to_axis_angle - to_euler = Quaternion.to_euler + # Getting the unbound methods in advance avoids having to look them up again on each call within the loop. + mat_decompose = Matrix.decompose + quat_to_axis_angle = Quaternion.to_axis_angle + quat_to_euler = Quaternion.to_euler + quat_dot = Quaternion.dot - calc_mat = _blen_read_object_transform_do_anim(transform_data, translation_matrix, rotation_euler, scaling_matrix, + calc_mat = _blen_read_object_transform_do_anim(transform_data, + lcl_translation_mat, lcl_rotation_eul, lcl_scaling_mat, combined_pre_matrix, combined_post_matrix) # Iterate through the values for each frame. for frame_values in frame_values_it: - # Set each value into its corresponding attribute in transform_data. - for setter, value in zip(setters, frame_values): - setter(value) + # Set each value into its corresponding lcl matrix/euler. + for lcl_setter, value in zip(lcl_setters, frame_values): + lcl_setter(value) # Calculate the updated matrix for this frame. mat = calc_mat() # Now we have a virtual matrix of transform from AnimCurves, we can yield keyframe values! - loc, rot, sca = decompose(mat) + loc, rot, sca = mat_decompose(mat) if rot_mode == 'QUATERNION': - if rot_quat_prev.dot(rot) < 0.0: + if quat_dot(rot_quat_prev, rot) < 0.0: rot = -rot rot_quat_prev = rot elif rot_mode == 'AXIS_ANGLE': - vec, ang = to_axis_angle(rot) + vec, ang = quat_to_axis_angle(rot) rot = ang, vec.x, vec.y, vec.z else: # Euler - rot = to_euler(rot, rot_mode, rot_eul_prev) + rot = quat_to_euler(rot, rot_mode, rot_eul_prev) rot_eul_prev = rot # Yield order matches the order that the location/rotation/scale FCurves are created in. -- 2.30.2 From 8bb30da036c855e4ebf761d1aae9ea47a5603b7e Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 11 Sep 2023 12:30:20 +0100 Subject: [PATCH 4/5] Fix in-place modification of item.anim_compensation_matrix --- io_scene_fbx/import_fbx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py index e2d39ce31..b5a29d5b8 100644 --- a/io_scene_fbx/import_fbx.py +++ b/io_scene_fbx/import_fbx.py @@ -621,7 +621,7 @@ def _transformation_curves_gen(item, values_arrays, channel_keys): # Pre-compute combined post-matrix # Compensate for changes in the local matrix during processing - combined_post_matrix = item.anim_compensation_matrix if item.anim_compensation_matrix else Matrix() + combined_post_matrix = item.anim_compensation_matrix.copy() if item.anim_compensation_matrix else Matrix() # item.post_matrix will contain any correction for lights, camera and bone orientation if item.post_matrix: combined_post_matrix @= item.post_matrix -- 2.30.2 From f7e372e475a3ddabebc475f63696bf4bd2289af7 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 11 Sep 2023 12:47:42 +0100 Subject: [PATCH 5/5] Increase FBX IO version --- io_scene_fbx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py index 1efffea8b..00243d5c1 100644 --- a/io_scene_fbx/__init__.py +++ b/io_scene_fbx/__init__.py @@ -5,7 +5,7 @@ bl_info = { "name": "FBX format", "author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem", - "version": (5, 7, 2), + "version": (5, 7, 3), "blender": (3, 6, 0), "location": "File > Import-Export", "description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions", -- 2.30.2