Stumbling Toward 'Awesomeness'

A Technical Art Blog

Wednesday, December 5, 2018

Importing A Character into UE4 with Python

One aspect that most people immediately want to do, when they hear that UE4 has python exposure, is import assets. This isn’t the most straight forward, so I will walk you through a character import. Yes, all the code below imports a single skelmesh/skeleton.

#first we need to know where we're importing the character into, let's make a new path
asset_path = '/Game/Characters/MyTest'
unreal.EditorAssetLibrary.make_directory(asset_path)
 
#now we make an import task and access it's options, which are similar to the import UI
task = unreal.AssetImportTask()
 
#now let's set some import options on the task class
task.filename = PATH_TO_FBX_FILE
task.destination_path = asset_path
task.destination_name = 'my_asset'
task.replace_existing = True
task.automated = True
#save the file when it is imported, that's right!
task.save = True

Now let’s instance an ‘options’ class on the task class and set some skeletal mesh specific options

task.options = unreal.FbxImportUI()
 
task.options.import_as_skeletal = True
task.options.override_full_name = True
task.options.mesh_type_to_import = unreal.FBXImportType.FBXIT_SKELETAL_MESH
 
task.options.skeletal_mesh_import_data.set_editor_property('update_skeleton_reference_pose', False)
task.options.skeletal_mesh_import_data.set_editor_property('use_t0_as_ref_pose', True)
task.options.skeletal_mesh_import_data.set_editor_property('preserve_smoothing_groups', 1)
task.options.skeletal_mesh_import_data.set_editor_property('import_meshes_in_bone_hierarchy', False)
task.options.skeletal_mesh_import_data.set_editor_property('import_morph_targets', True)
task.options.skeletal_mesh_import_data.set_editor_property('threshold_position',  0.00002)
task.options.skeletal_mesh_import_data.set_editor_property('threshold_tangent_normal', 0.00002)
task.options.skeletal_mesh_import_data.set_editor_property('threshold_uv', 0.000977)
task.options.create_physics_asset = False
task.options.import_animations = False
task.options.skeletal_mesh_import_data.set_editor_property('convert_scene', True)
task.options.skeletal_mesh_import_data.set_editor_property('force_front_x_axis', False)
task.options.skeletal_mesh_import_data.set_editor_property('convert_scene_unit', False)

Now let’s set the normal/tangent import method, and the normal generation method. This a bit odd, you need to set it as an existing python object, let’s set it to normals and tangents, but there are also (unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS, unreal.FBXNormalImportMethod.FBXNIM_COMPUTE_NORMALS)

normal_import_method = unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS_AND_TANGENTS
normal_generation_method = unreal.FBXNormalGenerationMethod.MIKK_T_SPACE
 
task.options.skeletal_mesh_import_data.set_editor_property('normal_generation_method', normal_generation_method)
task.options.skeletal_mesh_import_data.set_editor_property('normal_import_method', normal_import_method)

The task class and its options look to be prepared, let’s now do the deed!

imported_asset = unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
#let's check what files were imported/created:
imported_skelmesh = task.imported_object_paths
posted by Chris at 1:43 PM  

Saturday, December 1, 2018

The Asset Registry: Finding and Iterating Through Assets with UE4 Python

One thing that you will want to do right away is iterate through a bank of existing assets or find assets in a build. In UE4, your main window into the ‘content browser’ is the ‘asset registry’. You can use it to find all kinds of assets, iterate through assets in a folder, etc.

Let’s go ahead and instance it to take a look, now would be a good time to open the unreal.AssetRegistryHelpers UE4 Python API docs in another tab! Also, I am running this in UE4 4.21 release, with the free Paragon asset Marketplace assets.

Walking Assets In A Directory

Let’s ask it for all the assets in a certain path.

asset_reg = unreal.AssetRegistryHelpers.get_asset_registry()
assets = asset_reg.get_assets_by_path('/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes')

The method to get the asset registry has returned an unreal.AssetRegistry class. If you look at this class, you can see some really useful calls, like get_assets_by_path, that I used on the next line.

Let’s take a look at the assets:

for asset in assets: print asset

This yields:

LogPython: <Struct 'AssetData' (0x000001ADF8564560) {object_path: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes/Morigesh_Skeleton.Morigesh_Skeleton", package_name: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes/Morigesh_Skeleton", package_path: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes", asset_name: "Morigesh_Skeleton", as
set_class: "Skeleton"}>
LogPython: <Struct 'AssetData' (0x000001ADF8566A90) {object_path: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes/Orion_Proto_Retarget.Orion_Proto_Retarget", package_name: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes/Orion_Proto_Retarget", package_path: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes", asset_name: "Orion_Proto_R
etarget", asset_class: "Rig"}>
LogPython: <Struct 'AssetData' (0x000001ADF8564560) {object_path: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes/Morigesh_Cyl_Shadows.Morigesh_Cyl_Shadows", package_name: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes/Morigesh_Cyl_Shadows", package_path: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes", asset_name: "Morigesh_Cyl_
Shadows", asset_class: "PhysicsAsset"}>
LogPython: <Struct 'AssetData' (0x000001ADF8566A90) {object_path: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes/Morigesh_Physics.Morigesh_Physics", package_name: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes/Morigesh_Physics", package_path: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes", asset_name: "Morigesh_Physics", asset_
class: "PhysicsAsset"}>
LogPython: <Struct 'AssetData' (0x000001ADF85654B0) {object_path: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes/Morigesh.Morigesh", package_name: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes/Morigesh", package_path: "/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes", asset_name: "Morigesh", asset_class: "SkeletalMesh"}></code>

It has returned Python Objects of ‘unreal.AssetData‘ type, this class has a lot of things we can query, like class type, name, full path, etc. Let’s print the class name for each:

for asset in assets:
    print asset.class

Let’s only look at skeletal meshes and then let’s do something to them. In order to manipulate them, we need to load them, look at what the get_full_name function returns:

for asset in assets:
    #you could use isinstance unreal.SkeletalMesh, but let's build on what we learned
    if asset.asset_class == 'SkeletalMesh':
        print asset.get_full_name()
#>SkeletalMesh'/Game/ParagonMorigesh/Characters/Heroes/Morigesh/Meshes/Morigesh.Morigesh'

We need to split that output, then load the asset:

for asset in assets:
    if asset.asset_class == 'SkeletalMesh':
        full_name = asset.get_full_name()
        path = full_name.split(' ')[-1]
        skelmesh = unreal.load_asset(path)

Now this returned an unreal.SkeletalMesh class and we can ask it for it’s skeleton:

skeleton = skelmesh.skeleton

Finding Assets

Let’s say someone gives you a list of problematic assets, but they’re not long paths, just asset names! You want to be able to find the long path for all assets in the list so that you can do something with them. The AssetRegistry can help!

Let’s build a dictionary of all asets in the build, the keys will be the short names, and the values will be the long paths:

def get_asset_dict(asset_type=None):
    asset_list = None
    if asset_type:
        asset_list = unreal.AssetRegistryHelpers.get_asset_registry().get_assets_by_class(asset_type)
    else:
        asset_list = unreal.AssetRegistryHelpers.get_asset_registry().get_all_assets()
    asset_dict = {}
    for asset in asset_list:
        asset_name = str(asset.asset_name)
        obj_path = asset.object_path
        if asset_name not in asset_dict:
            asset_dict[asset_name] = [str(obj_path)]
        else:
            asset_dict[asset_name].append(str(obj_path))
 
    return asset_dict

This takes a second or two to build, but you now have an index of all assets by package name, that you can query their full path. It’s a bit faster if you query all assets of a certain type you know you’re looking for. You also will know when there is more than one asset with a name, because it’s list will have multiple entries. (That’s why we store what we find in a list, there could be multiple assets with the same name)

posted by Chris at 10:12 PM  

Powered by WordPress