Skip to content

Commit e453c2f

Browse files
authored
Merge pull request #150 from james-baber/fix-lod-order
fixed lod order #30
2 parents 93be12c + 459e0ae commit e453c2f

File tree

4 files changed

+133
-47
lines changed

4 files changed

+133
-47
lines changed

send2ue/addon/__init__.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
bl_info = {
1616
"name": "Send to Unreal",
1717
"author": "Epic Games Inc.",
18-
"version": (1, 4, 9), # addon plugin version
18+
"version": (1, 4, 10), # addon plugin version
1919
"blender": (2, 83, 0), # minimum blender version
2020
"location": "Header > Pipeline > Export > Send to Unreal",
2121
"description": "Sends an asset to the first open Unreal Editor instance on your machine.",
2222
"warning": "",
23-
"wiki_url": "https://github.com/EpicGames/BlenderTools/wiki/Send-to-Unreal-Home",
23+
"wiki_url": "https://epicgames.github.io/BlenderTools/send2ue/quickstart.html",
2424
"category": "Pipeline",
2525
}
2626

@@ -39,6 +39,7 @@
3939
classes = [
4040
operators.Send2Ue,
4141
operators.ImportAsset,
42+
operators.NullOperator,
4243
addon_preferences.SendToUnrealPreferences,
4344
header_menu.TOPBAR_MT_Export,
4445
header_menu.TOPBAR_MT_Import

send2ue/addon/functions/export.py

+6-23
Original file line numberDiff line numberDiff line change
@@ -46,41 +46,20 @@ def get_fbx_paths(asset_name, asset_type):
4646
if properties.path_mode in ['export_to_disk', 'both']:
4747
if asset_type == 'MESH':
4848
# Check for relative paths and also sanitize the path
49-
export_dir = resolve_path(properties.disk_mesh_folder_path)
49+
export_dir = utilities.resolve_path(properties.disk_mesh_folder_path)
5050
fbx_paths['disk'] = os.path.join(
5151
export_dir,
5252
f'{get_unreal_asset_name(asset_name, properties)}.fbx'
5353
)
5454
if asset_type == 'ACTION':
55-
export_dir = resolve_path(properties.disk_animation_folder_path)
55+
export_dir = utilities.resolve_path(properties.disk_animation_folder_path)
5656
fbx_paths['disk'] = os.path.join(
5757
export_dir,
5858
f'{get_unreal_asset_name(asset_name, properties)}.fbx'
5959
)
6060
return fbx_paths
6161

6262

63-
def resolve_path(path):
64-
"""
65-
This function checks if a given path is relative and returns the full
66-
path else returns the original path
67-
68-
:param str path: The input path
69-
:return str: The expanded path
70-
"""
71-
72-
# Check for a relative path input. Relative paths are represented
73-
# by '//' eg. '//another/path/relative/to/blend_file'
74-
if path.startswith('//') or path.startswith('./'):
75-
# Build an absolute path resolving the relative path from the blend file
76-
path = bpy.path.abspath(path.replace("./", "//", 1))
77-
78-
# Make sure the path has the correct OS separators
79-
path = bpy.path.native_pathsep(path)
80-
81-
return path
82-
83-
8463
def get_from_collection(collection_name, object_type):
8564
"""
8665
This function fetches the objects inside each collection according to type and returns the
@@ -765,6 +744,10 @@ def create_mesh_data(mesh_objects, rig_objects, properties):
765744
# if importing lods
766745
if properties.import_lods:
767746
exported_asset_names = []
747+
748+
# recreate the lod meshes to ensure the correct order
749+
mesh_objects = utilities.recreate_lod_meshes(mesh_objects)
750+
768751
for mesh_object in mesh_objects:
769752
# get the name of the asset without the lod postfix
770753
asset_name = get_unreal_asset_name(mesh_object.name, properties)

send2ue/addon/functions/utilities.py

+115-22
Original file line numberDiff line numberDiff line change
@@ -73,29 +73,30 @@ def get_current_context():
7373
7474
:return dict: A dictionary of values that are the current context.
7575
"""
76-
active_object = bpy.context.active_object
77-
7876
selected_objects = []
7977
for selected_object in bpy.context.selected_objects:
80-
active_action = None
78+
active_action_name = ''
8179
# get the selected objects active animation
8280
if selected_object.animation_data:
83-
active_action = selected_object.animation_data.action
81+
if selected_object.animation_data.action:
82+
active_action_name = selected_object.animation_data.action.name
8483

8584
# save the selected object reference and its active animation
86-
selected_objects.append([selected_object, active_action])
85+
selected_objects.append([selected_object.name, active_action_name])
8786

8887
current_context = {
89-
'visible_objects': bpy.context.visible_objects,
88+
'visible_objects': [visible_object.name for visible_object in bpy.context.visible_objects],
9089
'selected_objects': selected_objects,
91-
'active_object': active_object,
9290
'mode': bpy.context.mode
9391
}
9492

9593
# save the current action if there is one
94+
active_object = bpy.context.active_object
9695
if active_object:
96+
current_context['active_object'] = active_object.name
9797
if active_object.animation_data:
98-
current_context['active_animation'] = active_object.animation_data.action
98+
if active_object.animation_data.action:
99+
current_context['active_animation'] = active_object.animation_data.action.name
99100

100101
return current_context
101102

@@ -106,21 +107,27 @@ def set_context(context):
106107
107108
:param dict context: A dictionary of values the the context should be set to.
108109
"""
109-
active_object = context['active_object']
110-
111110
# set the visible objects
112-
for scene_object in context['visible_objects']:
113-
scene_object.hide_set(False)
111+
for visible_object_name in context['visible_objects']:
112+
visible_object = bpy.data.objects.get(visible_object_name)
113+
if visible_object:
114+
visible_object.hide_set(False)
114115

115116
# set the selected objects
116-
for scene_object in context['selected_objects']:
117-
scene_object[0].select_set(True)
117+
for scene_object_name, active_action_name in context['selected_objects']:
118+
scene_object = bpy.data.objects.get(scene_object_name)
119+
if scene_object:
120+
scene_object.select_set(True)
121+
118122
# set the objects active animation
119-
if scene_object[1]:
120-
scene_object[0].animation_data.action = scene_object[1]
123+
active_action = bpy.data.objects.get(active_action_name)
124+
if active_action:
125+
scene_object.animation_data.action = active_action
121126

122127
# set the active object
123-
bpy.context.view_layer.objects.active = active_object
128+
active_object_name = context.get('active_object')
129+
if active_object_name:
130+
bpy.context.view_layer.objects.active = bpy.data.objects.get(active_object_name)
124131

125132
# set the mode
126133
if bpy.context.mode != context['mode']:
@@ -164,7 +171,7 @@ def remove_extra_data(data_blocks, original_data_blocks):
164171
data_blocks.remove(data_block_to_remove)
165172

166173

167-
def remove_object_scale_keyframes(scale, actions):
174+
def remove_object_scale_keyframes(actions):
168175
"""
169176
This function removes all scale keyframes the exist a object in the provided actions.
170177
@@ -619,8 +626,8 @@ def scale_object_actions(unordered_objects, actions, scale_factor):
619626
keyframe_point.handle_left[1] = keyframe_point.handle_left[1] * scale[fcurve.array_index]
620627
keyframe_point.handle_right[1] = keyframe_point.handle_right[1] * scale[fcurve.array_index]
621628

622-
# apply the scale on the object
623-
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
629+
# apply the scale on the object
630+
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
624631

625632

626633
def import_unreal_4_asset(file_path):
@@ -643,7 +650,7 @@ def import_unreal_4_asset(file_path):
643650
scale_object_actions(bpy.context.selected_objects, imported_actions, 1)
644651

645652
# remove the object scale keyframes
646-
remove_object_scale_keyframes(scale=1, actions=imported_actions)
653+
remove_object_scale_keyframes(actions=imported_actions)
647654

648655
# round keyframes
649656
round_keyframes(imported_actions)
@@ -657,4 +664,90 @@ def import_asset(file_path, properties):
657664
:param object properties: The property group that contains variables that maintain the addon's correct state.
658665
"""
659666
if properties.source_application == 'ue4':
660-
import_unreal_4_asset(file_path)
667+
import_unreal_4_asset(file_path)
668+
669+
clear_undo_history('Asset Import')
670+
671+
672+
def recreate_lod_meshes(mesh_objects):
673+
"""
674+
This function recreates the provided lod meshes by duplicating them and deleting there originals.
675+
676+
:param list mesh_objects: A list of lod mesh objects.
677+
:return object: The new object.
678+
"""
679+
new_mesh_objects = []
680+
# get the current selection and context
681+
context = get_current_context()
682+
683+
for mesh_object in mesh_objects:
684+
if 'LOD' in mesh_object.name:
685+
686+
previous_object_name = mesh_object.name
687+
previous_mesh_name = mesh_object.data.name
688+
689+
# deselect all objects
690+
deselect_all_objects()
691+
692+
# select and duplicate the mesh object
693+
mesh_object.select_set(True)
694+
bpy.ops.object.duplicate()
695+
696+
# remove the old object
697+
bpy.data.objects.remove(mesh_object)
698+
699+
# remove the old mesh
700+
previous_mesh = bpy.data.meshes.get(previous_mesh_name)
701+
if previous_mesh:
702+
bpy.data.meshes.remove(previous_mesh)
703+
704+
new_mesh_object = bpy.context.selected_objects[0]
705+
706+
# rename the duplicated object to the old name
707+
new_mesh_object.name = previous_object_name
708+
# rename the duplicated mesh to the old name
709+
new_mesh_object.data.name = previous_mesh_name
710+
711+
new_mesh_objects.append(new_mesh_object)
712+
713+
# restore selection and context
714+
set_context(context)
715+
716+
return new_mesh_objects
717+
718+
719+
def clear_undo_history(message):
720+
"""
721+
This function clears blenders undo history by doing a deselect all operation and repeatedly
722+
pushing that operation into the undo stack until all previous history is cleared from the undo
723+
history.
724+
725+
:param str message: The message to display in the undo history.
726+
"""
727+
# run this null operator
728+
bpy.ops.send2ue.null_operator()
729+
730+
# repeatedly push the last operator into the undo stack till there are no more undo steps
731+
for item in range(0, bpy.context.preferences.edit.undo_steps + 1):
732+
bpy.ops.ed.undo_push(message=message)
733+
734+
735+
def resolve_path(path):
736+
"""
737+
This function checks if a given path is relative and returns the full
738+
path else returns the original path
739+
740+
:param str path: The input path
741+
:return str: The expanded path
742+
"""
743+
744+
# Check for a relative path input. Relative paths are represented
745+
# by '//' eg. '//another/path/relative/to/blend_file'
746+
if path.startswith('//') or path.startswith('./'):
747+
# Build an absolute path resolving the relative path from the blend file
748+
path = bpy.path.abspath(path.replace("./", "//", 1))
749+
750+
# Make sure the path has the correct OS separators
751+
path = bpy.path.native_pathsep(path)
752+
753+
return path

send2ue/addon/operators.py

+9
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,13 @@ class ImportAsset(bpy.types.Operator, importer.ImportAsset):
2525
def execute(self, context):
2626
properties = bpy.context.preferences.addons[__package__].preferences
2727
utilities.import_asset(self.filepath, properties)
28+
return {'FINISHED'}
29+
30+
31+
class NullOperator(bpy.types.Operator):
32+
"""This is an operator that changes nothing, but it used to clear the undo stack"""
33+
bl_idname = "send2ue.null_operator"
34+
bl_label = "Null Operator"
35+
36+
def execute(self, context):
2837
return {'FINISHED'}

0 commit comments

Comments
 (0)