Skip to content

Commit

Permalink
Merge pull request #150 from james-baber/fix-lod-order
Browse files Browse the repository at this point in the history
fixed lod order
#30
  • Loading branch information
james-baber authored Nov 12, 2020
2 parents 93be12c + 459e0ae commit e453c2f
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 47 deletions.
5 changes: 3 additions & 2 deletions send2ue/addon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
bl_info = {
"name": "Send to Unreal",
"author": "Epic Games Inc.",
"version": (1, 4, 9), # addon plugin version
"version": (1, 4, 10), # addon plugin version
"blender": (2, 83, 0), # minimum blender version
"location": "Header > Pipeline > Export > Send to Unreal",
"description": "Sends an asset to the first open Unreal Editor instance on your machine.",
"warning": "",
"wiki_url": "https://github.com/EpicGames/BlenderTools/wiki/Send-to-Unreal-Home",
"wiki_url": "https://epicgames.github.io/BlenderTools/send2ue/quickstart.html",
"category": "Pipeline",
}

Expand All @@ -39,6 +39,7 @@
classes = [
operators.Send2Ue,
operators.ImportAsset,
operators.NullOperator,
addon_preferences.SendToUnrealPreferences,
header_menu.TOPBAR_MT_Export,
header_menu.TOPBAR_MT_Import
Expand Down
29 changes: 6 additions & 23 deletions send2ue/addon/functions/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,41 +46,20 @@ def get_fbx_paths(asset_name, asset_type):
if properties.path_mode in ['export_to_disk', 'both']:
if asset_type == 'MESH':
# Check for relative paths and also sanitize the path
export_dir = resolve_path(properties.disk_mesh_folder_path)
export_dir = utilities.resolve_path(properties.disk_mesh_folder_path)
fbx_paths['disk'] = os.path.join(
export_dir,
f'{get_unreal_asset_name(asset_name, properties)}.fbx'
)
if asset_type == 'ACTION':
export_dir = resolve_path(properties.disk_animation_folder_path)
export_dir = utilities.resolve_path(properties.disk_animation_folder_path)
fbx_paths['disk'] = os.path.join(
export_dir,
f'{get_unreal_asset_name(asset_name, properties)}.fbx'
)
return fbx_paths


def resolve_path(path):
"""
This function checks if a given path is relative and returns the full
path else returns the original path
:param str path: The input path
:return str: The expanded path
"""

# Check for a relative path input. Relative paths are represented
# by '//' eg. '//another/path/relative/to/blend_file'
if path.startswith('//') or path.startswith('./'):
# Build an absolute path resolving the relative path from the blend file
path = bpy.path.abspath(path.replace("./", "//", 1))

# Make sure the path has the correct OS separators
path = bpy.path.native_pathsep(path)

return path


def get_from_collection(collection_name, object_type):
"""
This function fetches the objects inside each collection according to type and returns the
Expand Down Expand Up @@ -765,6 +744,10 @@ def create_mesh_data(mesh_objects, rig_objects, properties):
# if importing lods
if properties.import_lods:
exported_asset_names = []

# recreate the lod meshes to ensure the correct order
mesh_objects = utilities.recreate_lod_meshes(mesh_objects)

for mesh_object in mesh_objects:
# get the name of the asset without the lod postfix
asset_name = get_unreal_asset_name(mesh_object.name, properties)
Expand Down
137 changes: 115 additions & 22 deletions send2ue/addon/functions/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,29 +73,30 @@ def get_current_context():
:return dict: A dictionary of values that are the current context.
"""
active_object = bpy.context.active_object

selected_objects = []
for selected_object in bpy.context.selected_objects:
active_action = None
active_action_name = ''
# get the selected objects active animation
if selected_object.animation_data:
active_action = selected_object.animation_data.action
if selected_object.animation_data.action:
active_action_name = selected_object.animation_data.action.name

# save the selected object reference and its active animation
selected_objects.append([selected_object, active_action])
selected_objects.append([selected_object.name, active_action_name])

current_context = {
'visible_objects': bpy.context.visible_objects,
'visible_objects': [visible_object.name for visible_object in bpy.context.visible_objects],
'selected_objects': selected_objects,
'active_object': active_object,
'mode': bpy.context.mode
}

# save the current action if there is one
active_object = bpy.context.active_object
if active_object:
current_context['active_object'] = active_object.name
if active_object.animation_data:
current_context['active_animation'] = active_object.animation_data.action
if active_object.animation_data.action:
current_context['active_animation'] = active_object.animation_data.action.name

return current_context

Expand All @@ -106,21 +107,27 @@ def set_context(context):
:param dict context: A dictionary of values the the context should be set to.
"""
active_object = context['active_object']

# set the visible objects
for scene_object in context['visible_objects']:
scene_object.hide_set(False)
for visible_object_name in context['visible_objects']:
visible_object = bpy.data.objects.get(visible_object_name)
if visible_object:
visible_object.hide_set(False)

# set the selected objects
for scene_object in context['selected_objects']:
scene_object[0].select_set(True)
for scene_object_name, active_action_name in context['selected_objects']:
scene_object = bpy.data.objects.get(scene_object_name)
if scene_object:
scene_object.select_set(True)

# set the objects active animation
if scene_object[1]:
scene_object[0].animation_data.action = scene_object[1]
active_action = bpy.data.objects.get(active_action_name)
if active_action:
scene_object.animation_data.action = active_action

# set the active object
bpy.context.view_layer.objects.active = active_object
active_object_name = context.get('active_object')
if active_object_name:
bpy.context.view_layer.objects.active = bpy.data.objects.get(active_object_name)

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


def remove_object_scale_keyframes(scale, actions):
def remove_object_scale_keyframes(actions):
"""
This function removes all scale keyframes the exist a object in the provided actions.
Expand Down Expand Up @@ -619,8 +626,8 @@ def scale_object_actions(unordered_objects, actions, scale_factor):
keyframe_point.handle_left[1] = keyframe_point.handle_left[1] * scale[fcurve.array_index]
keyframe_point.handle_right[1] = keyframe_point.handle_right[1] * scale[fcurve.array_index]

# apply the scale on the object
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
# apply the scale on the object
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)


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

# remove the object scale keyframes
remove_object_scale_keyframes(scale=1, actions=imported_actions)
remove_object_scale_keyframes(actions=imported_actions)

# round keyframes
round_keyframes(imported_actions)
Expand All @@ -657,4 +664,90 @@ def import_asset(file_path, properties):
:param object properties: The property group that contains variables that maintain the addon's correct state.
"""
if properties.source_application == 'ue4':
import_unreal_4_asset(file_path)
import_unreal_4_asset(file_path)

clear_undo_history('Asset Import')


def recreate_lod_meshes(mesh_objects):
"""
This function recreates the provided lod meshes by duplicating them and deleting there originals.
:param list mesh_objects: A list of lod mesh objects.
:return object: The new object.
"""
new_mesh_objects = []
# get the current selection and context
context = get_current_context()

for mesh_object in mesh_objects:
if 'LOD' in mesh_object.name:

previous_object_name = mesh_object.name
previous_mesh_name = mesh_object.data.name

# deselect all objects
deselect_all_objects()

# select and duplicate the mesh object
mesh_object.select_set(True)
bpy.ops.object.duplicate()

# remove the old object
bpy.data.objects.remove(mesh_object)

# remove the old mesh
previous_mesh = bpy.data.meshes.get(previous_mesh_name)
if previous_mesh:
bpy.data.meshes.remove(previous_mesh)

new_mesh_object = bpy.context.selected_objects[0]

# rename the duplicated object to the old name
new_mesh_object.name = previous_object_name
# rename the duplicated mesh to the old name
new_mesh_object.data.name = previous_mesh_name

new_mesh_objects.append(new_mesh_object)

# restore selection and context
set_context(context)

return new_mesh_objects


def clear_undo_history(message):
"""
This function clears blenders undo history by doing a deselect all operation and repeatedly
pushing that operation into the undo stack until all previous history is cleared from the undo
history.
:param str message: The message to display in the undo history.
"""
# run this null operator
bpy.ops.send2ue.null_operator()

# repeatedly push the last operator into the undo stack till there are no more undo steps
for item in range(0, bpy.context.preferences.edit.undo_steps + 1):
bpy.ops.ed.undo_push(message=message)


def resolve_path(path):
"""
This function checks if a given path is relative and returns the full
path else returns the original path
:param str path: The input path
:return str: The expanded path
"""

# Check for a relative path input. Relative paths are represented
# by '//' eg. '//another/path/relative/to/blend_file'
if path.startswith('//') or path.startswith('./'):
# Build an absolute path resolving the relative path from the blend file
path = bpy.path.abspath(path.replace("./", "//", 1))

# Make sure the path has the correct OS separators
path = bpy.path.native_pathsep(path)

return path
9 changes: 9 additions & 0 deletions send2ue/addon/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,13 @@ class ImportAsset(bpy.types.Operator, importer.ImportAsset):
def execute(self, context):
properties = bpy.context.preferences.addons[__package__].preferences
utilities.import_asset(self.filepath, properties)
return {'FINISHED'}


class NullOperator(bpy.types.Operator):
"""This is an operator that changes nothing, but it used to clear the undo stack"""
bl_idname = "send2ue.null_operator"
bl_label = "Null Operator"

def execute(self, context):
return {'FINISHED'}

0 comments on commit e453c2f

Please sign in to comment.