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  

Thursday, August 30, 2018

UE4 Python API Documentation

The team has worked really hard to get this building and automagically updated as we expose more useful stuff:

https://api.unrealengine.com/INT/PythonAPI/

posted by Chris at 4:56 PM  

Thursday, April 12, 2018

Creating a Volume Slider in UE4 with Python

Working in cinematics and Sequencer, I often would like a global audio slider to lower audio levels while I am working on a sequence. I thought this might be a good simple tool example. This is completely bare bones, just a single slider in a dialog, for something more complex, you can check out scriptWrangler.

import unreal
from PySide import QtCore, QtGui, QtUiTools
 
class VolumeSlider(QtGui.QDialog):
 
    def __init__(self, parent=None):
        super(VolumeSlider, self).__init__(parent)
        self.setWindowTitle("Volume Slider")
        self.slider = QtGui.QSlider()
        self.slider.sliderMoved.connect(self.slider_fn)
        self.slider.setRange(0,100)
        self.slider.setValue(100)
 
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.slider)
        self.setLayout(layout)
 
        #load the master SoundClass
        self.master_sound_class = unreal.load_asset(None, name='/Engine/EngineSounds/Master')
 
    def slider_fn(self):
        volume_float = self.slider.value()/100.00
        #set the volume to the desired value
        self.master_sound_class.properties.set_editor_property(name='volume', value=volume_float)
 
APP = None
if not QtGui.QApplication.instance():
    APP = QtGui.QApplication(sys.argv)
 
main_window = VolumeSlider()
main_window.show()
posted by Chris at 1:05 AM  

Friday, April 6, 2018

Vector Math Examples in UE4 Python

One of the most visited/indexed posts on this site was the brief Maya vector math post, now that we are releasing Python in UE4, I thought I would port those examples to UE4 Python.

Creating Vectors

Let’s query the bounding box extents of a skeletal mesh actor. This will return two points in world space as vectors:

bbox = mesh.get_actor_bounds(False)
v1, v2 = bbox[0], bbox[1]

If we print the first vector, we see it’s a struct of type Vector

print v1
#<Struct 'Vector' (0x000001EACECEFE78) {x: 540.073303, y: 32.021194, z: 124.710869}>;

If you want the vector as a tuple or something to export to elsewhere, you just make a tuple of its components:

print (v1.x, v1.y, v1.z)
#(540.0733032226562, 32.02119445800781, 124.71086883544922)

If you want to create your own vector, you simply do:

my_vec3 = unreal.Vector(1,2,3)
Print my_vec3
#<Struct 'Vector' (0x000001EACECECA40) {x: 1.000000, y: 2.000000, z: 3.000000}>

Length / Distance / Magnitude

The Vector struct supports many mathematical operations, let’s say we want to get the distance from v1 to v2, we would subtract them, which returns a new vector, then we would get the length (‘size’ in this case) of the vector:

new_vec = v2-v1
Print new_vec.size()
#478.868011475

The vector struct has a lot of convenience functions built in (check out the docs here) , for instance, let’s get the distance from v1, to v2 (the diagonal across our bounding box), without doing the above by calling the dist() function:

print v1.dist(v2)
#478.868011475

There is also a distSquared function it you just want to quickly compare which are greater and not calc real distance.

USE CASE: FIND SMALL ACTORS

Using what we got, let’s play in the default scene:

We can iterate through the scene and find actors under a certain size/volume:

for actor in static_meshes:
    mesh_component = actor.get_component_by_class(unreal.StaticMeshComponent)
    v1, v2 = mesh_component.get_local_bounds()
    if v1.dist(v2) < 150:
        print actor.get_name()
#Statue

I am querying the bounds of the static mesh components because the get_bounds() on the actor class returns the unscaled bounds, you may notice that many things in the scene, including the statue and floor are scaled. This is reflected in the get_local_bounds() of the mesh component.

DOT PRODUCT / ANGLE BETWEEN TWO VECTORS

If we wanted to know the angle between two vectors, we would use the dot product of those, let’s create two new vectors v1, and v2, because our previous were not really vectors per se, but point locations Just like in UE4, you use the ‘|’ pipe to do a vector dot product.

v1 = unreal.Vector(0,0,1)
v2 = unreal.Vector(0,1,1)
v1.normalize()
v2.normalize()
print v1 | v2
print v1 * v2

Notice that asterisk will multiply the vectors, pipe will return the sum of the components, or the dot product / scalar product as a float.

For the angle between, let’s import python’s math library:

import math
dot = v1|v2
print math.acos(dot) #returns 0.785398180512
print math.acos(dot) * 180 / math.pi
#returns 45.0000009806

Above I used the Python math library, but there’s also an Unreal math library available unreal.MathLibrary, you should check that out, it has vector math functions that don’t exist in the vector class:

dot = v1|v2
print unreal.MathLibrary.acos(dot) #returns 0.785398180512
print unreal.MathLibrary.acos(dot) * 180 / math.pi #returns 45.0000009806
print unreal.MathLibrary.radians_to_degrees(unreal.MathLibrary.acos(dot)) #returns: 45.0

USE CASE: CHECK ACTOR COLINEARITY

Let’s use the dot product to check if chairs are colinear, or facing the same direction. Let’s dupe one chair and call it ‘Chair_dupe’:

fwd_vectors = {}
for actor in static_meshes:
    if 'Chair' in  actor.get_name():
        actor_vec = actor.get_actor_forward_vector()
        for stored_actor in fwd_vectors:
            if actor_vec | fwd_vectors[stored_actor] == 1.0:
                print actor.get_name(), 'is colinear to', stored_actor
        fwd_vectors[actor.get_name()] = actor_vec
#returns: Chair_dupe is colinear to Chair

I hope this was helpful, I can post some other examples later.

posted by Chris at 1:23 AM  

Tuesday, April 3, 2018

Stumbling Into UE4 Python

Out of the gate, it’s important to understand that the Python exposure wraps BluePrint, not the C++ SDK, if something isn’t exposed to blueprint, you cannot access it with Python.

A Note on Naming

There are notable differences that should be pointed out regarding the python exposure and the UE4 programming documentation. UE4 class names have dropped the prefix, an FRotator would be unreal.Rotator, FVector, unreal.Vector, etc. Functions on those classes have been converted to PEP8 style names that differ from their C++ counterparts, for example Actor.GetActorBounds is Actor.get_actor_bounds, just FYI.

Detective Work

If you have ever used an initial alpha or beta embedded Python implementation in an application, like Maya or MotionBuilder, (if you’re old like me!) –you know the drill. There’s no documentation yet, but you can use existing blueprint/C++ documentation and native python built-in functions like dir, help, and type to try and navigate.

DIR()
Python’s dir command returns a ‘directory’ of all attributes of an object. This is extremely useful. Let’s query the attributes of a static mesh actor:

print dir(actor)

This returns a massive list, if you print each item, you’ll see something like this:

...
get_remote_role
get_squared_distance_to
get_tickable_when_paused
get_typed_outer
get_velocity
get_vertical_distance_to
get_world
has_authority
hidden
initial_life_span
instigator
is_actor_being_destroyed
...

HELP()
Jamie Dale has put a lot of work into making help work as expected, through dir we saw the actor has a get_velocity property, let’s ask help abut it:

help(actor.get_velocity)

Help returns not only info about get_velocity, but tells us what a function takes and returns:

Help on built-in function get_velocity:
get_velocity(...)
    x.get_velocity() -&gt; Vector -- Returns velocity (in cm/s (Unreal Units/second) of the rootcomponent if it is either using physics or has an associated MovementComponent
    param: return_value (Vector)

TYPE()
Type just returns the type of object, but it’s useful when you’re not sure exactly what something is. Let’s ask what type our actor is:

print type(actor)
#<type 'StaticMeshActor'>

Now let’s ask it about the get_velocity above, we know it’s a function because I just used it, but as an example:

print type(actor.get_velocity)
#<type 'builtin_function_or_method_with_closure'>

With these core concepts, you can really begin stumbling around and making useful scripts and tools!

In Practice

Let’s query all StaticMeshes in a level (the default UE4 map). First thing is to load the level:

import unreal
 
level_path = '/Game/StarterContent/Maps/Minimal_Default'
level = unreal.find_asset(None, name=level_path)

Next we use get_all_actors_of_class to query all the static meshes:

static_meshes = unreal.GameplayStatics.get_all_actors_of_class(level, unreal.StaticMeshActor)
print static_meshes
#returns: [StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Table"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Statue"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Floor_14"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Floor"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Chair_15"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Chair"']

That’s returned a long list of class objects, let’s make that more readable by calling the get_name function of each class with a simple list comprehensions:

print [mesh.get_name() for mesh in static_meshes]
#returns: ['Table', 'Statue', 'Floor_14', 'Floor', 'Chair_15', 'Chair']

How did I know there was a get_name functions? I saw it when I ran directory on the static mesh actor: dir(actor).

Let’s move the table:

if mesh.get_name() == 'Table':
    xform = actor.get_actor_transform()
    location = actor.get_actor_location()
    print xform
    print location
#returns:
#<Struct 'Transform' (0x0000000095E5D3C0) {rotation: {x: 0.000000, y: 0.000000, z: 0.000000, w: 1.000000}, translation: {x: -180.000000, y: 0.000000, z: 32.000000}, scale3d: {x: 1.000000, y: 1.000000, z: 1.000000}}>
#<Struct 'Vector' (0x00000000B8ACE5C0) {x: -180.000000, y: 0.000000, z: 32.000000}>

Let’s change the location and use set_actor_location to set a new location:

location.z += 28 #location = 60
actor.set_actor_location(location)
#returns:
#Traceback (most recent call last):
#TypeError: Required argument 'sweep' (pos 2) not found

Wow, so that fails, we need to find out why, let’s ask help:

print help(actor.set_actor_location)
Help on built-in function set_actor_location:
 
set_actor_location(...)
    x.set_actor_location(new_location, sweep, teleport) -> HitResult or None -- Move the Actor to the specified location.
    param: new_location (Vector) -- The new location to move the Actor to.
    param: sweep (bool) -- Whether we sweep to the destination location, triggering overlaps along the way and stopping short of the target if blocked by something. Only the root component is swept and checked for blocking collision, child components move without sweeping. If collision is off, this has no effect.
    param: teleport (bool) -- Whether we teleport the physics state (if physics collision is enabled for this object). If true, physics velocity for this object is unchanged (so ragdoll parts are not affected by change in location). If false, physics velocity is updated based on the change in position (affecting ragdoll parts). If CCD is on and not teleporting, this will affect objects along the entire swept volume.
    param: sweep_hit_result (HitResult) -- The hit result from the move if swept.
    param: return_value (bool)
    return: Whether the location was successfully set (if not swept), or whether movement occurred at all (if swept).

Ok, awesome, so I’ll tell it I don’t want it to sweep, and I do want it to teleport:

location.z += 28 #location = 60
actor.set_actor_location(location. False, True)

You should see the table move!

NOTE: Careful, in order for your Python changes to be added to the undo stack, you need to use unreal.ScopedEditorTransaction.

I hope this was helpful, I can post some other examples later.

posted by Chris at 1:28 AM  

Monday, April 2, 2018

ScriptWrangler: A light-weight script editor for embedded Python environments

As a UE4 Python tool example, I wrote a pretty bare bones script editor for UE4 and tossed it up on github. While my goal was making a tool to allow me to play around with UE4 Python, this is actually a lightweight script editor for any python environment that is lacking one.

Epic will make some kind of script editor in the future, but until then, feel free to use (and improve!) this one:
https://github.com/chrisevans3d/scriptWrangler

This requires PySide, you can pip install that, I gave an example in my previous post, here.

To run the script, you simply enter the path to it, example:

posted by Chris at 3:48 PM  

Tuesday, March 20, 2018

Python Ships in Unreal Engine 4.19

ADDING THE PLUGIN

For anyone with version 4.19 of the engine or later, you now have access to Python. It’s marked ‘experimental’, enable it under plugins:

You then can enter python through the command line:

As the Python implementation wraps blueprint and blutilities, you only have access to things exposed through blueprint.

INSTALLING THIRD PARTY LIBRARIES

The team has made it a lot easier to install 3rd party libraries by shipping with pip.exe, it’s in this folder: <build>\Engine\Source\ThirdParty\Python\Win64\Scripts

Here’s an example of installing PySide into the build:

&gt;&gt;pip install --target=Y:\Build\UE_4.19\Engine\Source\ThirdParty\Python\Win64\Lib\site-packages Pyside
Collecting Pyside
  Using cached PySide-1.2.4-cp27-none-win_amd64.whl
Installing collected packages: Pyside
Successfully installed Pyside-1.2.4
posted by Chris at 10:15 AM  

Monday, March 5, 2018

uExport: A Simple Maya Tool for Exporting to UE4


I posted the skeleton of this a while ago in the post “Why does everyone write their own FBX exporter?“, many people asked me for the exporter over the years and I sent it to them. Since that post, uExport has become the way we get all characters into UE4 at Epic. It’s been crazy to see this little fledgling tool, developed largely in spare time to allow us to export non-A.R.T.v1 rigs (like the kite and deer in the first demo I worked on at Epic) become something we rely on so much.

It’s here on GitHub, there’s a lot more info in the readme.

posted by Chris at 9:05 PM  

Saturday, May 7, 2016

Simple Runtime Rigging in UE4

This is something that I found when cleaning my old computer out at work this week after an upgrade. This is an example of using a look at controller and a blendspace to drive a fleshy eye deformation in UE4 live at runtime.

This is the technique Jeremy Ernst used on Smaug’s eye in the demo we did with Weta over a year ago. Create a 2d blendspace for the eye poses. Export out left, right, up, down poses and connect them:

eye2

Grab the local transform from the eye, and set two vars with it:

image13

Create a Look At Controller and integrate the blendspace:

image06

posted by Chris at 7:40 AM  

Powered by WordPress