From acc28f6d8760a8b7196460042ee8ecc02da50512 Mon Sep 17 00:00:00 2001 From: Kyle Youngblom Date: Mon, 30 Mar 2026 17:16:19 -0500 Subject: [PATCH] Send2UE - Fix combined mesh export transforms and child selection - Fix collision primitive transforms (position, rotation, scale) for combined mesh exports with EMPTY display transforms - Fix visual mesh distortion when parent EMPTY has non-uniform scale/rotation set for display purposes - Add EMPTY type check to prevent non-EMPTY parents from entering combined mesh path - Skip child objects not in current View Layer to prevent selection error - Filter non-export-collection children from combined mesh exports --- src/addons/send2ue/__init__.py | 2 +- src/addons/send2ue/core/io/fbx_b4.py | 74 ++++++++++++++++++++++++---- src/addons/send2ue/core/utilities.py | 10 +++- src/addons/send2ue/release_notes.md | 11 ++++- 4 files changed, 83 insertions(+), 14 deletions(-) diff --git a/src/addons/send2ue/__init__.py b/src/addons/send2ue/__init__.py index 3850900f..46c97286 100644 --- a/src/addons/send2ue/__init__.py +++ b/src/addons/send2ue/__init__.py @@ -11,7 +11,7 @@ bl_info = { "name": "Send to Unreal", "author": "Epic Games Inc (now a community fork)", - "version": (2, 6, 8), + "version": (2, 6, 9), "blender": (3, 6, 0), "location": "Header > Pipeline > Send to Unreal", "description": "Sends an asset to the first open Unreal Editor instance on your machine.", diff --git a/src/addons/send2ue/core/io/fbx_b4.py b/src/addons/send2ue/core/io/fbx_b4.py index ecca5a86..de50a72b 100644 --- a/src/addons/send2ue/core/io/fbx_b4.py +++ b/src/addons/send2ue/core/io/fbx_b4.py @@ -466,8 +466,9 @@ def fbx_data_object_elements(root, ob_obj, scene_data): if bpy.context.scene.send2ue.use_object_origin: loc = Vector((0, 0, 0)) - elif ob_obj.type == 'Ellipsis': - loc = Vector((loc[0] * SCALE_FACTOR, loc[1] * SCALE_FACTOR, loc[2] * SCALE_FACTOR)) + elif ob_obj.type == 'EMPTY': + if bpy.context.scene.send2ue.use_object_origin: + loc = Vector((0, 0, 0)) elif ob_obj.type == 'MESH': # centers mesh object by their object origin if bpy.context.scene.send2ue.use_object_origin: @@ -486,13 +487,66 @@ def fbx_data_object_elements(root, ob_obj, scene_data): # https://github.com/EpicGamesExt/BlenderTools/issues/627 empty_object_name = asset_data.get('empty_object_name') if empty_object_name: - empty_object = bpy.data.objects.get(empty_object_name) - empty_world_location = empty_object.matrix_world.to_translation() - loc = Vector(( - (object_world_location[0] - empty_world_location[0]) * SCALE_FACTOR, - (object_world_location[1] - empty_world_location[1]) * SCALE_FACTOR, - (object_world_location[2] - empty_world_location[2]) * SCALE_FACTOR - )) + # Check if this is a collision mesh (UBX_, UCP_, USP_, UCX_) + _is_collision = any( + current_object.name.startswith(token + '_') + for token in ('UBX', 'UCP', 'USP', 'UCX') + ) + if _is_collision: + import math + _gm = scene_data.settings.global_matrix + empty_object = bpy.data.objects.get(empty_object_name) + _parent_is_empty = ( + ob_obj.parent and ob_obj.parent.name == empty_object_name + ) + if ob_obj.parent and not _parent_is_empty: + # Child collision (parented to visual mesh, grandchild + # of EMPTY): fbx_object_tx is contaminated because + # fbx_object_matrix's parent correction (lines 1790-1796 + # in fbx_utils.py) uses matrix_local which contains + # EMPTY_world^-1 via matrix_parent_inverse. The EMPTY's + # non-uniform display scale leaks into loc/rot/scale. + # Compute clean local transform using matrix_world + # (where EMPTY's influence is fully cancelled) via + # global_space=True which takes the clean is_global path. + _desired_fbx = ob_obj.fbx_object_matrix(scene_data, global_space=True) + _par_world = ob_obj.parent.fbx_object_matrix( + scene_data, global_space=True + ) + _local = _par_world.inverted_safe() @ _desired_fbx + _l, _r, _s = _local.decompose() + loc = Vector(_l) + rot = tuple(math.degrees(a) for a in _r.to_euler('XYZ')) + scale = Vector(_s) + else: + # Root collision (parented to EMPTY or no parent): + # fbx_object_tx IS clean here because the EMPTY is not + # in the export set, so fbx_object_matrix takes the + # is_global path which uses matrix_world (clean). + # Just adjust loc to be EMPTY-relative. + _empty_fbx_pos = _gm @ empty_object.matrix_world.to_translation() + loc = Vector(( + loc[0] - _empty_fbx_pos[0], + loc[1] - _empty_fbx_pos[1], + loc[2] - _empty_fbx_pos[2] + )) + # scale from fbx_object_tx already includes + # the unit conversion (100x from global_matrix) + # for root nodes. No additional factor needed. + else: + # Non-collision visual meshes: root FBX nodes with + # correct fbx_object_tx world transforms. Adjust loc + # to center the combined mesh around the EMPTY's + # position (ignoring EMPTY rot/scale which are for + # display only). + empty_object = bpy.data.objects.get(empty_object_name) + _gm = scene_data.settings.global_matrix + _empty_fbx_pos = _gm @ empty_object.matrix_world.to_translation() + loc = Vector(( + loc[0] - _empty_fbx_pos[0], + loc[1] - _empty_fbx_pos[1], + loc[2] - _empty_fbx_pos[2] + )) else: asset_world_location = asset_object.matrix_world.to_translation() loc = Vector(( @@ -646,4 +700,4 @@ def fbx_data_bindpose_element(root, me_obj, me, scene_data, arm_obj=None, mat_wo export_fbx_bin.fbx_animations_do = original_fbx_animations_do export_fbx_bin.fbx_data_armature_elements = original_fbx_data_armature_elements export_fbx_bin.fbx_data_object_elements = original_fbx_data_object_elements - export_fbx_bin.fbx_data_bindpose_element = original_fbx_data_bindpose_element \ No newline at end of file + export_fbx_bin.fbx_data_bindpose_element = original_fbx_data_bindpose_element diff --git a/src/addons/send2ue/core/utilities.py b/src/addons/send2ue/core/utilities.py index 624023c1..1b324841 100644 --- a/src/addons/send2ue/core/utilities.py +++ b/src/addons/send2ue/core/utilities.py @@ -1202,12 +1202,14 @@ def report_path_error_message(layout, send2ue_property, report_text): def select_all_children(scene_object, object_type, exclude_postfix_tokens=False): """ - Selects all of an objects children. + Selects all of an objects children that are in the Export collection hierarchy. :param object scene_object: A object. :param str object_type: The type of object to select. :param bool exclude_postfix_tokens: Whether or not to exclude objects that have a postfix token. """ + export_collection = bpy.data.collections.get(ToolInfo.EXPORT_COLLECTION.value) + export_objects = set(export_collection.all_objects) if export_collection else set() children = scene_object.children or get_meshes_using_armature_modifier(scene_object) for child_object in children: if child_object.type == object_type: @@ -1215,7 +1217,11 @@ def select_all_children(scene_object, object_type, exclude_postfix_tokens=False) if any(child_object.name.startswith(f'{token.value}_') for token in PreFixToken): continue - child_object.select_set(True) + if child_object not in export_objects: + continue + + if child_object.name in bpy.context.view_layer.objects: + child_object.select_set(True) if child_object.children: select_all_children(child_object, object_type, exclude_postfix_tokens) diff --git a/src/addons/send2ue/release_notes.md b/src/addons/send2ue/release_notes.md index 63de7f4f..548dda6e 100644 --- a/src/addons/send2ue/release_notes.md +++ b/src/addons/send2ue/release_notes.md @@ -1,3 +1,12 @@ +## Bug Fixes +* Fixed combined mesh collision transforms not matching visual mesh positions + * [188](https://github.com/poly-hammer/BlenderTools/pull/188) +* Fixed combined mesh visual mesh transforms when using object origin centering +* Fixed distortion of rotated instanced meshes when parent EMPTY has display transforms +* Added EMPTY type check to prevent non-EMPTY parents from entering combined mesh path +* Fixed crash when child objects exist outside the current View Layer +* Fixed non-export-collection children being included in combined mesh exports + ## Patch Changes * Lowered aggressiveness of socket names * [173](https://github.com/poly-hammer/BlenderTools/issues/173) @@ -6,5 +15,5 @@ @Daerst ## Tests Passing On -* Blender `3.6`, `4.2` (installed from blender.org) +* Blender `3.6`, `4.2`, `5.0` (installed from blender.org) * Unreal `5.3`, `5.4`