Automating Simplygon workflow in UE4 using Python

Introduction

With the Unreal Engine's Python Editor Script plugin Unreal Engine expanded its capabilities and allowed its user base to create automatic workflows based on Python scripts. Furthermore, it allowed plugin developers like us to tap into that pipeline without much effort and provide our users with the capability of using some of the functionality provided by our plugin through python to automate their workflows.

To get started with python in Unreal Engine you can check the official documentation Unreal Python. Once you have setup Simplygon and enabled the Python Editor Script plugin, you are all set to begin automating your Simplygon workflows using Python. The plugin ships with a helper script with a few examples. The following are three use cases where you can easily script with python. Before getting into Simplygon we need to setup a few helper methods.

Python script prerequisite

First, we begin the script by importing the Unreal module and the regular expression module which we will use later

import unreal
import re
#A helper method to get all actors based on a criteria.
def get_all_actor(use_selection = False, actor_class = None, actor_tag = None):
    if use_selection:
        selected_actors = unreal.EditorLevelLibrary.get_selected_level_actors()
        class_actors = selected_actors
        if actor_class:
            class_actors = [x for x in selected_actors if cast(x,actor_class)]
        tag_actors = class_actors
        if actor_tag:
            tag_actors = [x for x in selected_actors if x.actor_has_tag(actor_tag)]
        return [x for x in tag_actors]

    elif actor_class:
        actors = unreal.GameplayStatics.get_all_actors_of_class(unreal.EditorLevelLibrary.get_editor_world(),actor_class)
        tag_actors = actors
        if actor_tag:
            tag_actors = [x for x in actors if x.actor_has_tag(actor_tag)]
        return [x for x in tag_actors]

    elif actor_tag:
        tag_actors = unreal.GameplayStatics.get_all_actors_of_class(unreal.EditorLevelLibrary.get_editor_world(),actor_tag)
        return [x for x in tag_actors]

    else:
        actors = unreal.GameplayStatics.get_all_actors_of_class(unreal.EditorLevelLibrary.get_editor_world(),unreal.Actor)
        return [x for x in actors]
#A helper method to create a generic asset type using an asset factory. We use this for creating a LODRecipe asset in the next method.
def create_generic_asset(asset_path='', unique_name=True, asset_class=None, asset_factory=None):
    if unique_name:
        asset_path,asset_name = unreal.AssetToolsHelpers.get_asset_tools().create_unique_asset_name(base_package_name=asset_path,suffix='')
    if not unreal.EditorAssetLibrary.does_asset_exist(asset_path=asset_path):
        path = asset_path.rsplit('/',1)[0]
        name = asset_path.rsplit('/',1)[1]
        return unreal.AssetToolsHelpers.get_asset_tools().create_asset(asset_name=name,package_path=path,asset_class=asset_class,factory=asset_factory)
    
    return unreal.load_asset(asset_path)
#A helper method to create a LODRecipe.
def create_lod_recipe(base_path='', name = ''):

    if not base_path:
        base_path = '/Game/LODRecipes/'
    
    if not name:
        name = 'DefaultRecipe'

    return create_generic_asset(asset_path=base_path+name, asset_class=unreal.LODRecipe, asset_factory = unreal.LODRecipeFactory())
#A helper method to create a default remeshing pipeline and update the on screen size.
def create_remeshing_pipeline(screen_size = 300):
    remeshing_pipeline = unreal.RemeshingPipeline()
    remeshing_pipeline_settings = remeshing_pipeline.get_editor_property('settings')
    remeshing_settings = remeshing_pipeline_settings.get_editor_property('remeshing_settings')
    remeshing_settings.set_editor_property('on_screen_size',screen_size)
    return remeshing_pipeline
#A helper method to create a default far pipeline.
def create_far_pipeline(screen_size = 300):
    far_pipeline = unreal.StandinFarPipeline()
    pipeline_settings = far_pipeline.get_editor_property('settings')
    remeshing_settings = pipeline_settings.get_editor_property('remeshing_settings')
    remeshing_settings.set_editor_property('on_screen_size',screen_size)
    return far_pipeline
#A helper method to create a near pipline with geometry culling enabled.
def create_near_pipeline():
    near_pipeline = unreal.StandinNearPipeline()
    pipeline_settings = near_pipeline.get_editor_property('settings')
    aggregation_settings = pipeline_settings.get_editor_property('aggregation_settings')
    aggregation_settings.set_editor_property('enable_geometry_culling', True)
     
    #Enable volumentic culling
    geometry_cull_settings = pipeline_settings.get_editor_property('geometry_culling_settings')
    geometry_cull_settings.set_editor_property('use_clipping_geometry', True)
   
    #Fetch material caster properites
    material_prop_caster = pipeline_settings.get_editor_property('material_property_casters')
		
    #Create and add material property casters
    base_color_caster = unreal.BaseColorCaster()
    specular_caster = unreal.SpecualarCaster()
    normal_caster = unreal.NormalCaster()
    material_prop_caster.append(base_color_caster)
    material_prop_caster.append(specular_caster)
    material_prop_caster.append(normal_caster)
    return near_pipeline

#A helper method to find actor matching given name
def get_actors_by_name(selected_actors, name):
    r = re.compile(name+'*')
    result = [x for x in selected_actors if r.match(x.get_name())]
    return [x for x in result]

Now we have all the building blocks available to begin with our examples.

Example : How to create/apply a LODRecipe to an asset and generate LODs

We begin with creating a method stub

def create_lod_recipe_and_build_example():

First thing we do is to create a LODRecipe using one of the helper methods we setup earlier. By default when you create a LODRecipe it will populate it with Reduction Pipelines with triangle ratio set to 50%, 25% and 12.5%. And number of LODs would be set to four. The number of LODs is equal to number of LODs required plus one for the base mesh.

new_lod_chain = create_lod_recipe(name='ExampleRecipe')

Next, we increase the number of LODs to five. This is the number of required LODs (four, LOD1 - LOD4), plus one for the base mesh (LOD0). Note the naming of the UProperty is not entirely correct when converting to Python.

new_lod_chain.set_editor_property('num_lo_ds', 5)
pipelines = new_lod_chain.get_editor_property('per_lod_pipeline_settings')

Next, we update the triangle ratio for LOD1 to 88%

reduction_pipeline_settings = pipelines[1].get_editor_property('settings')
reduction_settings = reduction_pipeline_settings.get_editor_property('reduction_settings')
reduction_settings.set_editor_property('reduction_target_triangle_ratio', 0.88)

Next, for LOD4 we replace the pipeline with a remeshing pipeline with an on-screen size of 30 pixels. Using one of the helper methods created earlier

pipelines[4] = create_remeshing_pipeline(30)
asset = unreal.load_asset('/Game/Abandoned_City/Building_Environment_Pack/Meshes/Buildings/SM_Building_8.SM_Building_8')

Finally, we link the recipe with the asset, build LODs and save any dirty packages after processing

unreal.SimplygonBlueprintLibrary.assign_recipe_to_asset(new_lod_chain,asset)
unreal.SimplygonBlueprintLibrary.build_lod_recipe(asset)
unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True,True)

The following screen shot highlight key things that happen after script finishes. You can check the output window for any errors or details.

Before : No recipe is associated with the StaticMesh. After: Linked recipe.
After: Newly created recipe in the Content Browser

Example : How to setup and generate a Far Standin Mesh

Note: For this example we used actor tagging in our level to make life easier for us. So you can easily use editor selection or any other mechanics to get a group of actors.

We begin with creating a method stub.

def create_far_standin_from_selection_and_build_example():

Next, we get list of actors based on FarStandin tag and create standin actor from this list.

selected_actors = get_all_actor(actor_class=unreal.StaticMeshActor,actor_tag='FarStandin')
standin_actor =unreal.SimplygonBlueprintLibrary.create_standin_actor_from_actors(selected_actors)

Finally, we create a new far pipeline at 150 pixels, assign it to standin actor, build and save dirty packages

far_pipeline = create_far_pipeline(150)
standin_actor.set_editor_property("pipeline", unreal.SimplygonStandinPipeline.cast(far_pipeline))
unreal.SimplygonBlueprintLibrary.build_standin_meshes()
unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True,True)

The following screen shot highlight key things that happen after script finishes. You can check the output window for any errors or details.

After script is run Standin Outliner with newly created actor The resulting geometry

Example : How to setup and generate a Near Standin Mesh

Disclaimer: For this example we used actor tagging in our level to make life easier for us. So you can easily use editor selection or any other mechanics to get a group of actors.

We begin with creating a method stub

def create_near_standin_from_selection_and_build_example):

Next, we get list of actors based on the NearStandin tag to create standin actor from

selected_actors = get_all_actor(actor_class=unreal.StaticMeshActor,actor_tag='NearStandin')

Next, we get a sub list of actors that we want to add the clipping geometry user data metatag

clipping_meshes = get_actors_by_name(selected_actors,'Dino_Nest')
for actor in clipping_meshes:
	unreal.SimplygonBlueprintLibrary.add_simplygon_user_data(actor, unreal.SimplygonMetaTagType.CLIPPING_GEOMETRY)

Next, we create the standin actor

standin_actor =unreal.SimplygonBlueprintLibrary.create_standin_actor_from_actors(selected_actors)

Finally, we create a near pipeline, assign it to the standin actor, build and save packages.

near_pipeline = create_near_pipeline()
standin_actor.set_editor_property("pipeline", unreal.SimplygonStandinPipeline.cast(near_pipeline))
unreal.SimplygonBlueprintLibrary.build_standin_meshes()
unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True,True)

The following screen shot highlight key things that happen after script finishes. You can check the output window for any errors or details.

After script is run Standin Outliner with newly created actor with culling geometry The resulting geometry with culled parts
⇐ Back to blog post list