├── .gitignore ├── GetStubAndSetupBSCodeIDE.txt ├── README.md └── scripts ├── Asset ├── Change_Lightmass_Resolution.py ├── Commit_on_Dataprep.py ├── Consolidate_material_instances.py ├── Edit_Blueprint_asset_properties.py ├── Edit_a_light_component.py └── get_property.py ├── Create ├── Create_Import_update_material_parameters.py ├── Create_a_basic_material.py ├── Create_and_connect_material_parameter_expression.py └── Create_new_blueprint.py ├── ImportExport ├── Asset_Import_task.py ├── Batch_JT_Import.py ├── CAD_importer.py ├── DataprepOperation.py ├── Deltagen_Import_look_variants.py ├── Deltagen_Import_package_variants.py ├── Export_selected_actors_to_FBX.py ├── Import_CAD_and_re-export_as_FBX_or_OBJ.py ├── Import_Datasmith_CAD_and_set_LODs.py ├── Import_Datatable.py ├── Interchange_Pipeline.py ├── import_ABC.py └── import_FBX.py ├── Level ├── Generate_Box_UV_on_selected_actors.py ├── Line_Trace_Place_Actor.py ├── Merge_and_proxy.py ├── Merge_hierarchy_and_replace_them_by_instance_based_on_metadata.py ├── Rename_components_on_a_selected_BP_actor_in_the_level.py ├── Substitute_actor_with_a_given_tag.py └── Substitute_assets_with_consolidate_assets.py └── Misc ├── ExamplePythonMenus.py ├── Register_your_python_code_to_run_at_a_later_time.py ├── TakeHighResScreenshot_from_all_cam.py ├── Validate_pointLight.py └── tick_in_python.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | PythonScriptsProject/ 3 | -------------------------------------------------------------------------------- /GetStubAndSetupBSCodeIDE.txt: -------------------------------------------------------------------------------- 1 | To setup quickly a Python Env for unreal: 2 | inside the project: 3 | - Activate plugins: 4 | Edit > Plugins 5 | PythonEditorScriptPlugin 6 | EditorScriptingUtilities 7 | Restart the engine if needed 8 | - Configure the plugins: 9 | Edit > Project Settings 10 | Plugins > Python : Check "Developer Mode" (this mode publish Stub file in the intermediary folder of the project so we can use auto completion in our Python IDE) 11 | Restart the engine 12 | - Setup the IDE: 13 | Depending of your IDE, for Visual Code we do thing like this: 14 | Open VSCode 15 | Download/install/enable/configure Python and Pylance 16 | configure your settings.json like this 17 | { 18 | "python.pythonPath": "D:\\UE\\UE_4.25\\Engine\\Binaries\\ThirdParty\\Python\\Win64\\python.exe", 19 | "python.languageServer": "Pylance", 20 | "python.analysis.stubPath": "D:\\projects\\PythonStub\\4.24", 21 | } 22 | (note: path can be relative if the setting are workspace based) 23 | more documentation on how to launch a python script: 24 | https://docs.unrealengine.com/en-US/Engine/Editor/ScriptingAndAutomation/Python/index.html 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PythonSamples 2 | 3 | This folder contains some python samples to script the editor in Unreal Engine. 4 | 5 | The samples cover diverse functions of the editor: asset manipulation, data import, scene manipulation, etc. 6 | The python API evolves over versions and is still prone to changes. Files initially commited were working with UE 4.26. 7 | 8 | The examples are provided to illustrate usage of the API. No particular care was given to meet python coding standards or homogenize the coding styles from the different authors. 9 | 10 | ## Animation 11 | Look into your installation folder *\Engine\Plugins\MovieScene\SequencerScripting\Content\Python* for samples. 12 | 13 | ## Asset 14 | * *Change_Lightmass_Resolution.py* : Change the lightmass resolution on static meshes of selected actors. 15 | * *Commit_on_Dataprep.py* : load and execute a dataprep asset. 16 | * *Consolidate_material_instances.py* : Consolidate (remove duplicate) materials that have identical names. 17 | * *Edit_a_light_component.py* : change the IES texture of a light component of filtered actors. 18 | * *Edit_Blueprint_asset_properties.py* : edit default value of a blueprint (Class editing and not instance editing). 19 | * *get_property.py* : show how to filter actors by label and read a property on the actor. 20 | 21 | ## Creation 22 | * *Create_a_basic_material.py* : Create a material and add a base color property in the graph. 23 | * *Create_and_connect_material_parameter_expression.py* : Add and link material parameter expression inside a material graph. 24 | * *Create_Import_update_material_parameters.py* : Show how to import texture, create material and material instance, reparent material and set parameters on a material instance. 25 | * *Create_new_blueprint.py* : Create a new Blueprint. 26 | 27 | ## Import / Export 28 | * *Asset_Import_task.py* : import all ies files contained in a directory. 29 | * *Batch_JT_Import.py* : batch import all JT files from a folder using a slow task. 30 | * *CAD_importer.py* : simple CAD import. 31 | * *Deltagen_Import_look_variants.py* : for variant import in UE from Deltagen exports, read and create look variants. 32 | * *Deltagen_Import_package_variants.py* : for variant import in UE from Deltagen exports, read and create dependencies in package variants. 33 | * *Export_selected_actors_to_FBX.py* : Export all selected actors into a FBX file. 34 | * *import_ABC.py* : import an alembic file. 35 | * *Import_CAD_and_re-export_as_FBX_or_OBJ.py* : import CAD file, merge static meshes and export as FBX or OBJ format. 36 | * *Import_Datasmith_CAD_and_set_LODs.py* : multiple import of CAD file with different tessellation settings to use them as LOD on the final static mesh. 37 | * *Import_Datatable.py* : import JSON file following structure define in project as datatable. 38 | * *import_FBX.py* : import FBX. 39 | * *DataprepOperation.py* : example of a custom Dataprep Operation made in python, add tags to static mesh actor with specific string in actor label. 40 | * *Interchange_Pipeline.py* : example of a custom Interchange Pipeline made in python, override compression settings based on texture name suffix. 41 | 42 | ## Level 43 | * *Generate_Box_UV_on_selected_actors.py* : generate box style UV on all selected actors in the level. 44 | * *Line_Trace_Place_Actor.py* : perform line trace on convex geometry to spawn actor on surface. 45 | * *Merge_and_proxy.py* : generate a merged and proxy mesh from selection, then use proxy mesh as LOD1 of the merged mesh. Replace selection with merged mesh and its proxy LOD. 46 | * *Merge_hierarchy_and_replace_them_by_instance_based_on_metadata.py* : Detect repetitive sub hierarchies based on SU.GUID, merge first instance as static mesh, and replace all instances with new static meshes actors. Simplify hierarchy. 47 | * *Rename_components_on_a_selected_BP_actor_in_the_level.py* : Rename static mesh components of selected actors in the level. 48 | * *Substitute_actor_with_a_given_tag.py* : spawn actors in place of filtered components with given tag. 49 | * *Substitute_assets_with_consolidate_assets.py* : replace material and consolidate. 50 | 51 | ## Miscellaneous 52 | * *ExamplePythonMenus.py* : Adds custom menus in main toolbar, on right-click actions on assets, in custom editor window such as texture editor window menu. 53 | * *Register_your_python_code_to_run_at_a_later_time.py* : execute python callback on pre tick. 54 | * *TakeHighResScreenshot_from_all_cam.py* : demonstrate how to break down screenshot capture and perform them on tick. 55 | * *tick_in_python.py* : demonstrate how to break down some work and perform it on tick. 56 | * *Validate_pointLight.py* : show how to use asset or log while testing for light properties. 57 | 58 | ## External resources 59 | 60 | * [Unreal Engine Python API Documentation](https://docs.unrealengine.com/en-US/PythonAPI/index.html) 61 | * [Alex Quevillon's Python in UE How To and Samples](https://www.youtube.com/playlist?list=PLBLmKCAjA25Br8cOVzUroqi_Nwipg-IdP) 62 | * [Remote Debuging with PyCharm](http://guillaumepastor.com/programming/debug-unreal-engine-python-using-pycharm/) 63 | -------------------------------------------------------------------------------- /scripts/Asset/Change_Lightmass_Resolution.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | ######### fill list of actors ######### 4 | def get_selected(): 5 | actor_subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 6 | return actor_subsys.get_selected_level_actors() 7 | 8 | selected_actors = get_selected() 9 | #filter to retain only static mesh actors 10 | selected_static_mesh_actors = unreal.EditorFilterLibrary.by_class(selected_actors, unreal.StaticMeshActor.static_class()) 11 | #iterate over static mesh actors 12 | for sma in selected_static_mesh_actors: 13 | #get static mesh component 14 | smc = sma.static_mesh_component 15 | if smc is None: 16 | continue 17 | #get static mesh 18 | sm = smc.static_mesh 19 | if sm is None: 20 | continue 21 | #edit the property 22 | sm.set_editor_property('light_map_resolution',1024) 23 | #save the modification of the related asset in the content folder 24 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 25 | assetSubsystem.save_loaded_asset(sm) -------------------------------------------------------------------------------- /scripts/Asset/Commit_on_Dataprep.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | # Path to the Dataprep Asset 4 | EditorLib_Path = '/Game/Manual/Dataprep/' 5 | 6 | # List dataprep assets in path 7 | asset_subsys = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 8 | loaded_dataprep = asset_subsys.list_assets(EditorLib_Path) 9 | 10 | # Select the dataprep asset first in list 11 | dataprepAsset = asset_subsys.load_asset(loaded_dataprep[0]) 12 | 13 | # Execute (commit) the dataprep 14 | unreal.EditorDataprepAssetLibrary.execute_dataprep(dataprepAsset,unreal.DataprepReportMethod.STANDARD_LOG,unreal.DataprepReportMethod.STANDARD_LOG) -------------------------------------------------------------------------------- /scripts/Asset/Consolidate_material_instances.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | from unreal import MaterialInstanceConstant 3 | 4 | # consolidate together Material Instances assets having the same name 5 | # WARNING: will erase consolidated assets 6 | 7 | # retrieves all assets from the directory and its sub directories 8 | asset_subsys = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 9 | all_asset_names = asset_subsys.list_assets("/Game/Test/", True, False) 10 | 11 | # loads all assets of the MaterialInstanceConstant class 12 | material_assets = [] 13 | for asset_name in all_asset_names: 14 | loaded_asset = asset_subsys.load_asset(asset_name) 15 | if loaded_asset.__class__ == MaterialInstanceConstant: 16 | material_assets.append(loaded_asset) 17 | 18 | # regroup assets having identical names 19 | asset_consolidation = {} 20 | for i in range(0, len(material_assets)): 21 | name = material_assets[i].get_name() 22 | if not name in asset_consolidation: 23 | asset_consolidation[name] = [] 24 | asset_consolidation[name].append(i) 25 | 26 | # consolidate references of identical assets 27 | for asset_name, assets_ids in asset_consolidation.items(): 28 | if len(assets_ids) < 2: 29 | continue 30 | asset_subsys.consolidate_assets(material_assets[assets_ids[0]], [material_assets[i] for i in assets_ids[1:]]) 31 | 32 | # Need to fixup redirectors after that, though it's not accessible from Python 33 | # UAssetTools::FixupReferencers not exposed as of 4.26 34 | # 35 | # It has to be done from the Editor or a Commandlet 36 | # https://docs.unrealengine.com/en-us/Engine/Basics/Redirectors -------------------------------------------------------------------------------- /scripts/Asset/Edit_Blueprint_asset_properties.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | bp_gc = unreal.load_object(None, "/Game/Blueprints/BP_Property.BP_Property_C") 4 | bp_cdo = unreal.get_default_object(bp_gc) 5 | #here we assume there is a BP_Property blueprint with the following float and boolean variables 6 | bp_cdo.set_editor_property("FloatProp", 1.0) 7 | bp_cdo.set_editor_property("bBoolProp", True) -------------------------------------------------------------------------------- /scripts/Asset/Edit_a_light_component.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | def getActor(actorName): 4 | """ 5 | seach for an actor label 6 | 7 | arg: 8 | actorName = str|label beaing searched for 9 | 10 | returns: 11 | list of actor(s) 12 | """ 13 | actor_subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 14 | level_actors = actor_subsys.get_all_level_actors() 15 | 16 | filtered_list = unreal.EditorFilterLibrary.by_actor_label( 17 | level_actors, 18 | actorName, 19 | unreal.EditorScriptingStringMatchType.EXACT_MATCH 20 | ) 21 | 22 | if len(filtered_list) == 0: 23 | unreal.log_warning('Did not find any actor with label: "{}"'.format(actorName)) 24 | if len(filtered_list) > 1: 25 | unreal.log_warning('More then one actor with label: "{}"'.format(actorName)) 26 | return filtered_list 27 | 28 | SEARCH_LABEL = "BP_Property" 29 | DIRECTORY_NAME = "/game/Textures/IES/" 30 | 31 | # Retrieve the actor that we are going to change the light profile. 32 | myActor = getActor(SEARCH_LABEL)[0] 33 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 34 | 35 | # Get its light component 36 | pointLightsComponents = myActor.get_components_by_class(unreal.PointLightComponent) 37 | if len(pointLightsComponents)>0 : 38 | # List ie textures 39 | my_textures = assetSubsystem.list_assets(DIRECTORY_NAME) 40 | 41 | # load the texture 42 | my_load_asset = assetSubsystem.load_asset(my_textures[0]) 43 | print(my_load_asset) 44 | 45 | # Change the texture file 46 | pointLightsComponents[0].set_editor_property('ies_texture', my_load_asset) 47 | -------------------------------------------------------------------------------- /scripts/Asset/get_property.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | actor_subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 4 | level_actors = actor_subsys.get_all_level_actors() 5 | filtered_list = unreal.EditorFilterLibrary.by_actor_label( 6 | level_actors, 7 | "my_actor", 8 | unreal.EditorScriptingStringMatchType.EXACT_MATCH 9 | ) 10 | actor = filtered_list[0] 11 | 12 | value = actor.get_editor_property("my_property") 13 | 14 | print (value) 15 | -------------------------------------------------------------------------------- /scripts/Create/Create_Import_update_material_parameters.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | import os 3 | 4 | def get_asset_from_path(path): 5 | return unreal.load_asset(path) 6 | 7 | def get_path_from_asset(asset): 8 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 9 | fullpath = assetSubsystem.get_path_name_for_loaded_asset(asset) 10 | index = fullpath.find(".") 11 | return fullpath[:index] 12 | 13 | def get_fullpath(package_path, asset_name): 14 | fullpath = os.path.join(package_path, asset_name) 15 | fullpath = fullpath.replace("\\", "/") 16 | return fullpath 17 | 18 | def split_path(fullpath): 19 | return os.path.split(fullpath) 20 | 21 | def create_blueprint(asset_fullpath): 22 | asset = get_asset_from_path(asset_fullpath) 23 | if asset is not None: 24 | return asset 25 | else: 26 | package_path, asset_name = split_path(asset_fullpath) 27 | factory = unreal.BlueprintFactory() 28 | factory.set_editor_property("ParentClass", unreal.Actor) 29 | asset_tools = unreal.AssetToolsHelpers.get_asset_tools() 30 | my_new_asset = asset_tools.create_asset(asset_name, package_path, None, factory) 31 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 32 | assetSubsystem.save_loaded_asset(my_new_asset) 33 | return my_new_asset 34 | 35 | def create_material(asset_fullpath): 36 | asset = get_asset_from_path(asset_fullpath) 37 | if asset is not None: 38 | return asset 39 | else: 40 | package_path, asset_name = split_path(asset_fullpath) 41 | asset_tools = unreal.AssetToolsHelpers.get_asset_tools() 42 | factory = unreal.MaterialFactoryNew() 43 | my_new_asset = asset_tools.create_asset(asset_name, package_path, None, factory) 44 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 45 | assetSubsystem.save_loaded_asset(my_new_asset) 46 | return my_new_asset 47 | 48 | def create_material_instance(asset_fullpath): 49 | asset = get_asset_from_path(asset_fullpath) 50 | if asset is not None: 51 | return asset 52 | else: 53 | package_path, asset_name = split_path(asset_fullpath) 54 | factory = unreal.MaterialInstanceConstantFactoryNew() 55 | asset_tools = unreal.AssetToolsHelpers.get_asset_tools() 56 | my_new_asset = asset_tools.create_asset(asset_name, package_path, None, factory) 57 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 58 | assetSubsystem.save_loaded_asset(my_new_asset) 59 | return my_new_asset 60 | 61 | def set_material_instance_parent(mic_asset, parent_material_asset): 62 | if mic_asset is not None and parent_material_asset is not None: 63 | mic_asset.set_editor_property("Parent", parent_material_asset) 64 | 65 | def set_material_instance_param(mic_asset, param, value): 66 | if mic_asset is not None: 67 | if type(value) is float: 68 | unreal.MaterialEditingLibrary.set_material_instance_scalar_parameter_value(mic_asset, param, value) 69 | if type(value) is unreal.Texture2D: 70 | unreal.MaterialEditingLibrary.set_material_instance_texture_parameter_value(mic_asset, param, value) 71 | if type(value) is unreal.LinearColor: 72 | unreal.MaterialEditingLibrary.set_material_instance_vector_parameter_value(mic_asset, param, value) 73 | 74 | def import_asset(asset_filepath, asset_destination_path): 75 | package_path, asset_name = split_path(asset_destination_path) 76 | asset_tools = unreal.AssetToolsHelpers.get_asset_tools() 77 | import_data = unreal.AssetImportTask() 78 | import_data.filename = asset_filepath 79 | import_data.destination_name = asset_name 80 | import_data.destination_path = package_path 81 | import_data.replace_existing = True 82 | asset_tools.import_asset_tasks([import_data]) 83 | 84 | def move_rename_asset(source_fullpath, destination_fullpath): 85 | asset = get_asset_from_path(source_fullpath) 86 | if asset is not None: 87 | asset_tools = unreal.AssetToolsHelpers.get_asset_tools() 88 | package_path, asset_name = split_path(destination_fullpath) 89 | rename_data = unreal.AssetRenameData() 90 | rename_data.asset = asset 91 | rename_data.new_name = asset_name 92 | rename_data.new_package_path = package_path 93 | asset_tools.rename_assets([rename_data]) 94 | 95 | #------------------------------------------------------------------------------------------------------------ 96 | #------------------------------------------------------------------------------------------------------------ 97 | # Runtime 98 | 99 | # import a texture 100 | asset_filepath = "C:/Temp/Texture_Normal.png" 101 | asset_destination_fullpath = "/Game/Create/normal_ref" 102 | import_asset(asset_filepath, asset_destination_fullpath) 103 | 104 | # move and rename an asset 105 | source_fullpath = "/Game/Create/normal_ref" 106 | destination_fullpath = "/Game/Create2/normal_ref123" 107 | move_rename_asset(source_fullpath, destination_fullpath) 108 | 109 | #-------------------------------------------------------- 110 | # Create a Material 111 | mat_fullpath = "/Game/Create/M_Test_00" 112 | new_mat_asset = create_material(mat_fullpath) 113 | print(new_mat_asset) 114 | 115 | # Create a Material Instance 116 | mic_fullpath = "/Game/Create/MIC_Test_00" 117 | new_mic_asset = create_material_instance(mic_fullpath) 118 | print(new_mic_asset) 119 | 120 | #-------------------------------------------------------- 121 | # Set Material Instance parent material 122 | mic_asset = get_asset_from_path("/Game/Create/MIC_Test_00") 123 | parent_material_asset = get_asset_from_path("/Game/Materials/M_MyMaterialTest_01") 124 | set_material_instance_parent(mic_asset, parent_material_asset) 125 | 126 | # Set Material Instance scalar parameter 127 | mic_asset = get_asset_from_path("/Game/Create/MIC_Test_00") 128 | set_material_instance_param(mic_asset, "myscale", 34.0) 129 | 130 | # Set Material Instance texture parameter 131 | mic_asset = get_asset_from_path("/Game/Create/MIC_Test_00") 132 | texture_asset = get_asset_from_path('/Engine/EngineResources/AICON-Green') 133 | set_material_instance_param(mic_asset, "mytexture", texture_asset) 134 | 135 | # Set Material Instance vector parameter 136 | mic_asset = get_asset_from_path("/Game/Create/MIC_Test_00") 137 | vector = unreal.LinearColor(1,2,3,1) 138 | set_material_instance_param(mic_asset, "mycolor", vector) -------------------------------------------------------------------------------- /scripts/Create/Create_a_basic_material.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | test_dir = '/Game/Create/Awsome_Tests' 4 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 5 | 6 | # Create a test directory in Game 7 | if assetSubsystem.does_directory_exist(test_dir): 8 | assetSubsystem.delete_directory(test_dir) 9 | assetSubsystem.make_directory(test_dir) 10 | 11 | 12 | def basic_material(name='test_mat', path='/Game', color = {'r': 1.000000,'g': 1.000000,'b': 1.000000,'a': 0} ): 13 | ''' 14 | Create a material with a Constant 3 Vector to the base color input 15 | 16 | arg: 17 | name = str, material name 18 | path = str, asset library path where the material will be located 19 | color = dict, color vector 20 | ''' 21 | assetTools = unreal.AssetToolsHelpers.get_asset_tools() 22 | material = assetTools.create_asset(asset_name=name, package_path=path, asset_class=unreal.Material, factory=unreal.MaterialFactoryNew()) 23 | loaded_yellow = assetSubsystem.load_asset('{}/{}'.format(path, name)) 24 | constant_3vector = unreal.MaterialEditingLibrary.create_material_expression(material, unreal.MaterialExpressionConstant3Vector) 25 | constant_3vector.set_editor_property('constant', color) 26 | unreal.MaterialEditingLibrary.connect_material_property(constant_3vector, "", unreal.MaterialProperty.MP_BASE_COLOR) 27 | unreal.MaterialEditingLibrary.layout_material_expressions(material) 28 | unreal.MaterialEditingLibrary.recompile_material(material) 29 | 30 | basic_material ( 31 | name = 'yellow', 32 | path = test_dir, 33 | color = {'r': 1.000000,'g': 1.000000,'b': 0.000000,'a': 0} 34 | ) -------------------------------------------------------------------------------- /scripts/Create/Create_and_connect_material_parameter_expression.py: -------------------------------------------------------------------------------- 1 | 2 | import unreal 3 | 4 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 5 | 6 | #load the material asset and the material parameter collection asset from content folder 7 | materialTest = assetSubsystem.load_asset('/game/Create/MAT_1') 8 | materialParameterCollectionTest = assetSubsystem.load_asset('/game/Create/MPC_1') 9 | 10 | 11 | #create a material expression of type MaterialExpressionCollectionParameter 12 | mecpc = unreal.MaterialEditingLibrary.create_material_expression(materialTest, unreal.MaterialExpressionCollectionParameter, -1000, 200) 13 | #specify which collection parameter we want to use 14 | mecpc.set_editor_property('collection',materialParameterCollectionTest) 15 | #specify which parameter we want to use. MPC_1 has a parameter_name called 'Scalar1' 16 | mecpc.set_editor_property('parameter_name','Scalar1') 17 | #connect the expression to the output. The second parameter is supposed to be a string describing which output from the expression we want to use. leaving it empty the first one is selected 18 | unreal.MaterialEditingLibrary.connect_material_property(mecpc, '', unreal.MaterialProperty.MP_METALLIC) 19 | 20 | #recompile material 21 | unreal.MaterialEditingLibrary.recompile_material(materialTest) 22 | #save the material 23 | assetSubsystem.save_loaded_asset(materialTest, True) 24 | 25 | -------------------------------------------------------------------------------- /scripts/Create/Create_new_blueprint.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | asset_name = "MyAwesomeBPActorClass" 4 | package_path = "/Game/Create" 5 | 6 | factory = unreal.BlueprintFactory() 7 | factory.set_editor_property("ParentClass", unreal.Actor) 8 | 9 | asset_tools = unreal.AssetToolsHelpers.get_asset_tools() 10 | my_new_asset = asset_tools.create_asset(asset_name, package_path, None, factory) 11 | 12 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 13 | assetSubsystem.save_loaded_asset(my_new_asset) -------------------------------------------------------------------------------- /scripts/ImportExport/Asset_Import_task.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | import os 3 | import glob 4 | 5 | 6 | # Import additional ies files assets to tests other file types. 7 | asset_path_to_change = os.path.join('C:/temp/','IES_Types') 8 | 9 | # Listing all .ies files in folder 10 | # Folder where files are located 11 | files = glob.glob(asset_path_to_change+os.sep+'*.ies') 12 | 13 | # Create folder in UE 14 | light_directory_name = '/Game/Create/add_ies_types' 15 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 16 | destination_path = assetSubsystem.make_directory(light_directory_name) 17 | 18 | # Import all files 19 | for f in files: 20 | uetask = unreal.AssetImportTask() 21 | uetask.filename = f 22 | uetask.destination_path = light_directory_name 23 | uetask.replace_existing = True 24 | uetask.automated = False 25 | uetask.save = False 26 | 27 | task = [uetask] 28 | unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks(task) -------------------------------------------------------------------------------- /scripts/ImportExport/Batch_JT_Import.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | import os 3 | 4 | # content directory where assets will be stored 5 | content_folder = "/Game/Create/JT/" 6 | 7 | input_directory = "C:/temp/JT" 8 | 9 | def count_jt_files_in_directory(directory): 10 | count = 0 11 | item_list = os.listdir(directory) 12 | for item in item_list: 13 | item_full_path = os.path.join(directory, item) 14 | if os.path.isdir(item_full_path): 15 | count = count + count_jt_files_in_directory(item_full_path) 16 | else: 17 | ext = os.path.splitext(item)[1] 18 | if ext == '.jt': 19 | count = count + 1 20 | return count 21 | 22 | def process_directory(directory, parent_actor, slow_task): 23 | global file_index 24 | global assetSubsystem 25 | global levelSubsystem 26 | actorSubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 27 | 28 | jt_files = [] 29 | item_list = os.listdir(directory) 30 | for item in item_list: 31 | if slow_task.should_cancel(): 32 | return 33 | 34 | item_full_path = os.path.join(directory, item) 35 | if os.path.isdir(item_full_path): 36 | # create a dummy actor for sub directories 37 | # no python access to the Empty Actor Factory, so we use Static Mesh Actor with no mesh 38 | new_actor = actorSubsystem.spawn_actor_from_class(unreal.StaticMeshActor, unreal.Vector(0, 0, 0), unreal.Rotator(0, 0, 0)) 39 | new_actor.root_component.set_editor_property("mobility", unreal.ComponentMobility.MOVABLE) 40 | new_actor.set_actor_label(item) 41 | new_actor.attach_to_actor(parent_actor, unreal.Name(), unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, False) 42 | process_directory(item_full_path, new_actor, slow_task) 43 | else: 44 | ext = os.path.splitext(item)[1] 45 | if ext == '.jt': 46 | slow_task.enter_progress_frame(1) 47 | print("loading file " + item_full_path) 48 | 49 | asset_content_path = content_folder + '/' + str(file_index).zfill(3) 50 | file_index = file_index + 1 51 | 52 | # if the directory already exists, it means we already processed it in a previous execution so we skip this file 53 | if assetSubsystem.does_directory_exist(asset_content_path): 54 | continue 55 | 56 | # init datasmith CAD scene import from a CAD file and a target content directory 57 | datasmith_scene = unreal.DatasmithSceneElement.construct_datasmith_scene_from_file(item_full_path) 58 | 59 | # check scene initialization 60 | if datasmith_scene is None: 61 | print("Error: Failed creating Datasmith CAD scene") 62 | continue 63 | 64 | # set CAD import options 65 | base_options = datasmith_scene.get_options().base_options 66 | base_options.scene_handling = unreal.DatasmithImportScene.CURRENT_LEVEL 67 | base_options.static_mesh_options.generate_lightmap_u_vs = False 68 | 69 | tessellation_options = datasmith_scene.get_options(unreal.DatasmithCommonTessellationOptions) 70 | if tessellation_options: 71 | tessellation_options.options.chord_tolerance = 0.3 72 | tessellation_options.options.max_edge_length = 200.0 73 | tessellation_options.options.normal_tolerance = 25.0 74 | tessellation_options.options.stitching_technique = unreal.DatasmithCADStitchingTechnique.STITCHING_NONE 75 | 76 | # import the scene into the current level 77 | result = datasmith_scene.import_scene(asset_content_path) 78 | if not result.import_succeed: 79 | print("Error: Datasmith scene import failed") 80 | continue 81 | 82 | # no geometry imported, nothing to do 83 | if len(result.imported_actors) == 0: 84 | print("Warning: Non actor imported") 85 | continue 86 | 87 | # set mobility and parent on actors 88 | for imported_actor in result.imported_actors: 89 | imported_actor.root_component.set_editor_property("mobility", unreal.ComponentMobility.MOVABLE) 90 | if imported_actor.get_attach_parent_actor() is None: 91 | imported_actor.attach_to_actor(parent_actor, unreal.Name(), unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, False) 92 | 93 | # save static meshes and materials assets 94 | for static_mesh in result.imported_meshes: 95 | assetSubsystem.save_loaded_asset(static_mesh) 96 | for i in range(static_mesh.get_num_sections(0)): 97 | material_interface = static_mesh.get_material(i) 98 | assetSubsystem.save_loaded_asset(material_interface) 99 | 100 | # save level 101 | saved_level = levelSubsystem.save_all_dirty_levels() 102 | if not saved_level: 103 | print("Error: Cannot save level") 104 | return 105 | 106 | # get the number of JT files in the directory 107 | nb_jt_files = count_jt_files_in_directory(input_directory) 108 | 109 | # global variable 110 | file_index = 0 111 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 112 | levelSubsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem) 113 | 114 | # progress bar 115 | with unreal.ScopedSlowTask(nb_jt_files, "Data Preparation") as slow_task: 116 | slow_task.make_dialog(True) 117 | 118 | level_path = content_folder + "/ImportedLevel" 119 | if assetSubsystem.does_asset_exist(level_path): 120 | # if the level already exists, we just load it 121 | levelSubsystem.load_level(level_path) 122 | else: 123 | # create a new level to hold the imported scene 124 | created_new_level = levelSubsystem.new_level_from_template(level_path, "/Engine/Maps/Templates/Template_Default") 125 | if not created_new_level: 126 | print("Error: Cannot create new level") 127 | quit() 128 | 129 | process_directory(input_directory, None, slow_task) -------------------------------------------------------------------------------- /scripts/ImportExport/CAD_importer.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | cad_file_path = "c:/temp/motor.3dxml" 4 | 5 | directory_name = "/Game/Create/motor" 6 | 7 | datasmith_scene = unreal.DatasmithSceneElement.construct_datasmith_scene_from_file(cad_file_path) 8 | 9 | # set CAD import options 10 | import_options = datasmith_scene.get_options() 11 | import_options.base_options.scene_handling = unreal.DatasmithImportScene.CURRENT_LEVEL 12 | 13 | tessellation_options = datasmith_scene.get_options(unreal.DatasmithCommonTessellationOptions) 14 | if tessellation_options: 15 | tessellation_options.options.chord_tolerance = 0.1 16 | tessellation_options.options.max_edge_length = 0 17 | tessellation_options.options.normal_tolerance = 15.0 18 | 19 | result = datasmith_scene.import_scene(directory_name) 20 | 21 | print (result.import_succeed) -------------------------------------------------------------------------------- /scripts/ImportExport/DataprepOperation.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | #This python script should be put into a folder that is accessible to UE and add it to the "startup scripts" in the project settings 4 | #https://docs.unrealengine.com/en-US/scripting-the-unreal-editor-using-python/ 5 | 6 | #overriding Dataprep Operation class 7 | #this operation add a tag to static mesh actor with a specific string in their label 8 | @unreal.uclass() 9 | class DEO_PythonOperationTest(unreal.DataprepOperation): 10 | #exposing variable to dataprep operation 11 | mesh_name = unreal.uproperty(str) 12 | @unreal.ufunction(override=True) 13 | def on_execution(self, context: unreal.DataprepContext): 14 | if (len(self.mesh_name)>0): 15 | for sma in unreal.EditorFilterLibrary.by_class(context.objects,unreal.StaticMeshActor): 16 | if sma.get_actor_label().find(self.mesh_name) >= 0: 17 | unreal.DataprepOperationsLibrary.add_tags([sma],["PythonAddedTag"]) -------------------------------------------------------------------------------- /scripts/ImportExport/Deltagen_Import_look_variants.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | import xml.etree.ElementTree as ET 3 | from xml.etree.ElementTree import ParseError 4 | import sys 5 | import os 6 | 7 | # set var file from the arguments 8 | var_file = sys.argv[-1] 9 | 10 | # file exists 11 | if os.path.exists(var_file): 12 | print("Var file exist:", os.path.basename(var_file)) 13 | 14 | # If file not exist, the script stops 15 | else: 16 | print("file not exist") 17 | sys.exit() 18 | 19 | 20 | # This need to be dynamic directory, '/' should be at the end of content_output 21 | content_output = "/Game/Create/MaterialVariants/" 22 | LevelVariantSets_output = "/Game/Create/Variants/" 23 | 24 | xml_data = "" 25 | with open(var_file, "r", encoding='utf-8-sig') as file: 26 | # xml parser does not like the "DAF::" string 27 | xml_data = file.read().replace('DAF::', '') 28 | 29 | try: 30 | rttDocument = ET.fromstring(xml_data) 31 | except ParseError as e: 32 | print("parsing failed") 33 | print(e.msg) 34 | 35 | if rttDocument.tag != 'rttDocument': 36 | print('parse error: first node is not ') 37 | 38 | # for each material get the static mesh actors that are using it 39 | actorSubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 40 | static_mesh_actors = [a for a in actorSubsystem.get_all_level_actors() if a.__class__ == unreal.StaticMeshActor] 41 | material_actors = {} 42 | for a in static_mesh_actors: 43 | for m in a.static_mesh_component.get_materials(): 44 | if not m.get_name() in material_actors: 45 | material_actors[m.get_name()] = [] 46 | material_actors[m.get_name()].append(a) 47 | 48 | asset_tools = unreal.AssetToolsHelpers.get_asset_tools() 49 | 50 | # create a new level variant set 51 | # lvs = unreal.VariantManagerLibrary.create_level_variant_sets_asset('LookVariants', LevelVariantSets_output) 52 | 53 | # use existing level variant set 54 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 55 | lvs = assetSubsystem.load_asset(LevelVariantSets_output+'LevelVariantSets') 56 | 57 | 58 | # loop through all variants in the var file 59 | for variant_switch in rttDocument.findall('./ProductAspects/AspectContainer/Aspect/VariantSwitch'): 60 | proto_id = variant_switch.find('PrototypeID') 61 | 62 | # process only look variants 63 | if proto_id.text != 'LOOK_SHADER_VARIANT_ID': 64 | continue 65 | 66 | variant_set_name = variant_switch.find('Name').text 67 | print("processing look variant : " + variant_set_name) 68 | 69 | # create a new Variant Set to manage look variants 70 | variant_set = unreal.VariantSet() 71 | variant_set.set_display_text(variant_set_name) 72 | # add the empty Variant Set to the Level Variant Sets Asset. 73 | unreal.VariantManagerLibrary.add_variant_set(lvs, variant_set) 74 | 75 | target_lists = variant_switch.find('TargetLists') 76 | if target_lists is None: 77 | print("target list is empty for variant set name:"+variant_set_name) 78 | continue 79 | target_description = target_lists.find('TargetDescription') 80 | if target_description is None: 81 | print("target description is empty for variant set name:"+variant_set_name) 82 | continue 83 | if target_description.find('name') is None: 84 | print("target description Name is empty for variant set name:"+variant_set_name) 85 | continue 86 | material_name = target_description.find('name').text.replace(' ', '_') 87 | 88 | actors = [] 89 | if not material_name in material_actors: 90 | print("did not find actors with material " + material_name) 91 | else: 92 | actors = material_actors[material_name] 93 | 94 | variant_list = variant_switch.find('VariantList') 95 | variants = variant_list.find('Variants') 96 | for variant in variants: 97 | # create new mariant instance 98 | variant_name = variant.find('Name').text 99 | variant = unreal.Variant() 100 | variant.set_display_text(variant_name) 101 | unreal.VariantManagerLibrary.add_variant(variant_set, variant) 102 | # create new dummy material instance 103 | variant_sanitized_name = unreal.PackageTools.sanitize_package_name(variant_name).replace(')', '_').replace('(', '_') 104 | variant_path = content_output + variant_sanitized_name 105 | if assetSubsystem.does_asset_exist(variant_path): 106 | material = assetSubsystem.load_asset(variant_path) 107 | else: 108 | material = asset_tools.create_asset(variant_sanitized_name, content_output, unreal.MaterialInstanceConstant, unreal.MaterialInstanceConstantFactoryNew()) 109 | assetSubsystem.save_loaded_asset(material) 110 | # capture the actor material in the variant 111 | for actor in actors: 112 | unreal.VariantManagerLibrary.add_actor_binding(variant, actor) 113 | property_value = unreal.VariantManagerLibrary.capture_property(variant, actor, 'Static Mesh Component / Material[0]') 114 | unreal.VariantManagerLibrary.set_value_object(property_value, material) 115 | -------------------------------------------------------------------------------- /scripts/ImportExport/Deltagen_Import_package_variants.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | import xml.etree.ElementTree as ET 3 | from xml.etree.ElementTree import ParseError 4 | import sys 5 | import os 6 | 7 | # need to run the Deltagen_Import_look_variants 8 | #get all variant set's names, variant's names and their ID 9 | def getVariantSetsAndVariantFromFile(): 10 | print("Lising all variants") 11 | variant_sets_dict = dict() 12 | for variant_switch in rttDocument.findall('./ProductAspects/AspectContainer/Aspect/VariantSwitch'): 13 | variant_set_name = variant_switch.find('Name').text 14 | print("processing variant : " + variant_set_name) 15 | 16 | variant_list = variant_switch.find('VariantList') 17 | variants = variant_list.find('Variants') 18 | variant_dict = dict() 19 | for variant in variants: 20 | # find variant 21 | variant_name = variant.find('Name').text 22 | variant_id = int(variant.find('VariantID').text) 23 | variant_dict[variant_id] = variant_name 24 | if variant_set_name in variant_sets_dict: 25 | print("Error - Variant set already in dictionary") 26 | else: 27 | variant_sets_dict[variant_set_name] = variant_dict 28 | return variant_sets_dict 29 | 30 | 31 | # set var file from the arguments 32 | var_file = sys.argv[-1] 33 | 34 | # file exists 35 | if os.path.exists(var_file): 36 | print("Var file exist:", os.path.basename(var_file)) 37 | 38 | # If file not exist, the script stops 39 | else: 40 | print("file not exist") 41 | sys.exit() 42 | 43 | #clean package variant 44 | # remove any actor in the package variants 45 | # should we test that prototypeId of TargetDescription are VARIANTSWITCH_NODE_ID instead? 46 | # should we have that clean in the C++ import instead? 47 | bCleanPackageVariant = True 48 | 49 | # This need to be dynamic directory, '/' should be at the end of content_output 50 | content_output = "/Game/Create/MaterialsVariants/" 51 | LevelVariantSets_output = "/Game/Create/Variants/" 52 | 53 | xml_data = "" 54 | with open(var_file, "r", encoding='utf-8-sig') as file: 55 | # xml parser does not like the "DAF::" string 56 | xml_data = file.read().replace('DAF::', '') 57 | 58 | try: 59 | rttDocument = ET.fromstring(xml_data) 60 | except ParseError as e: 61 | print("parsing failed") 62 | print(e.msg) 63 | 64 | if rttDocument.tag != 'rttDocument': 65 | print('parse error: first node is not ') 66 | 67 | var_file_variant_sets_dict = getVariantSetsAndVariantFromFile() 68 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 69 | 70 | if not assetSubsystem.does_asset_exist(LevelVariantSets_output+'LevelVariantSets'): 71 | # create a new level variant set 72 | #lvs = unreal.VariantManagerLibrary.create_level_variant_sets_asset('LookVariants', LevelVariantSets_output) 73 | lvs = unreal.VariantManagerLibrary.create_level_variant_sets_asset('LevelVariantSets', LevelVariantSets_output) 74 | else: 75 | # use existing level variant set 76 | lvs = assetSubsystem.load_asset(LevelVariantSets_output+'LevelVariantSets') 77 | 78 | lvs_num_variant_set = lvs.get_num_variant_sets() 79 | lvs_variant_set_names = dict() 80 | for i in range(lvs_num_variant_set): 81 | lvs_variant_set = lvs.get_variant_set(i) 82 | lvs_variant_set_names[str(lvs_variant_set.get_display_text())] = lvs_variant_set 83 | print(lvs_variant_set_names) 84 | 85 | # loop through all variants in the var file 86 | for variant_switch in rttDocument.findall('./ProductAspects/AspectContainer/Aspect/VariantSwitch'): 87 | proto_id = variant_switch.find('PrototypeID') 88 | dependencies = dict() 89 | 90 | # process only package variants 91 | if proto_id.text != 'PACKAGE_VARIANT': 92 | continue 93 | 94 | variant_set_name = variant_switch.find('Name').text 95 | print("processing package variant : " + variant_set_name) 96 | 97 | #retrieve current variant set from the LVS asset 98 | lvs_current_variant_set = lvs.get_variant_set_by_name(variant_set_name) 99 | if lvs_current_variant_set == None: 100 | continue 101 | print("Current Variant Set: " + str(lvs_current_variant_set.get_display_text())) 102 | 103 | target_lists = variant_switch.find('TargetLists') 104 | if target_lists is None: 105 | print("target list is empty for variant set name:"+variant_set_name) 106 | continue 107 | 108 | target_description_list = target_lists.findall('TargetDescription') 109 | if len(target_description_list) == 0: 110 | print("target description is empty for variant set name:"+variant_set_name) 111 | continue 112 | 113 | for target_description in target_description_list: 114 | if target_description.find('name') is None: 115 | print("target description Name is empty for variant set name:"+variant_set_name) 116 | continue 117 | target_name = target_description.find('name').text.replace(' ', '_') 118 | target_id = int(target_description.find('TargetID').text) 119 | print(target_name) 120 | if target_name in lvs_variant_set_names: 121 | dependencies[target_id] = target_name 122 | 123 | if len(dependencies) > 0: 124 | print("process dependencies") 125 | 126 | variant_list = variant_switch.find('VariantList') 127 | variants = variant_list.find('Variants') 128 | for variant in variants: 129 | # find variant 130 | variant_name = variant.find('Name').text 131 | lvs_current_variant = lvs_current_variant_set.get_variant_by_name(variant_name) 132 | 133 | if(lvs_current_variant == None): 134 | continue 135 | 136 | values = variant.find("Values") 137 | value_list = values.findall("Value") 138 | print("########################") 139 | print("Current Variant: " + variant_name) 140 | if len(value_list) == len(dependencies): 141 | ind = 0 142 | for value in value_list: 143 | # process each dependency line 144 | dependency_variant_set = lvs_variant_set_names[dependencies[ind]] 145 | data_value = int(value.find('Data').text) 146 | # check if dependency variant set exists in existing lvs 147 | if str(dependency_variant_set.get_display_text()) in var_file_variant_sets_dict: 148 | var_file_variant_dict = var_file_variant_sets_dict[str(dependency_variant_set.get_display_text())] 149 | if data_value in var_file_variant_dict: 150 | variant_name = var_file_variant_dict[data_value] 151 | print(dependency_variant_set.get_display_text()) 152 | print(dependency_variant_set.get_variant_by_name(variant_name).get_display_text()) 153 | dependency = unreal.VariantDependency(dependency_variant_set,dependency_variant_set.get_variant_by_name(variant_name)) 154 | # Current import create actor connection instead of dependencies, cleaning before replacing by dependencies 155 | if bCleanPackageVariant: 156 | actors_to_remove = [] 157 | for index_actor in range(lvs_current_variant.get_num_actors()): 158 | actors_to_remove.append(lvs_current_variant.get_actor(index_actor).get_name()) 159 | for a in actors_to_remove: 160 | lvs_current_variant.remove_actor_binding_by_name(a) 161 | lvs_current_variant.add_dependency(dependency) 162 | ind = ind + 1 163 | assetSubsystem.save_loaded_asset(lvs) 164 | 165 | # clean references to lvs 166 | lvs_variant_set_names.clear() 167 | lvs_variant_set = None 168 | lvs_current_variant = None 169 | lvs_current_variant_set = None 170 | dependency_variant_set = None -------------------------------------------------------------------------------- /scripts/ImportExport/Export_selected_actors_to_FBX.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | # WARNING - UE4 has a bug in FBX export 4 | # the export of hierarchy should be controled by the editor preference 'Keep Attach Hierarchy' 5 | # but in the code the value of this setting is not checked and the actual variable controling this is uninitialized 6 | # which leads to different behaviors on different sessions... you may or may not get your hierarchy in the FBX... 7 | 8 | output_file = 'C:\\Temp\\ue4_output.fbx' 9 | 10 | actorSubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 11 | selected_actors = actorSubsystem.get_selected_level_actors() 12 | if len(selected_actors) == 0: 13 | print("No actor selected, nothing to export") 14 | quit() 15 | 16 | task = unreal.AssetExportTask() 17 | task.object = selected_actors[0].get_world() 18 | task.filename = output_file 19 | task.selected = True 20 | task.replace_identical = False 21 | task.prompt = False 22 | task.automated = True 23 | task.options = unreal.FbxExportOption() 24 | task.options.vertex_color = False 25 | task.options.collision = False 26 | task.options.level_of_detail = False 27 | unreal.Exporter.run_asset_export_task(task) -------------------------------------------------------------------------------- /scripts/ImportExport/Import_CAD_and_re-export_as_FBX_or_OBJ.py: -------------------------------------------------------------------------------- 1 | ## Code for FBX export 2 | import unreal 3 | 4 | file_to_import = "C:\\temp\\CAD\\Clutch assembly.SLDASM" 5 | final_fbx_file = "C:\\temp\\my_filename.fbx" 6 | asset_folder = '/Game/Create/MyCADScene' 7 | merge_actor_name = 'NEW_MESH_actor' 8 | fbx_destination = '/Game/Create/NEW_MESH' 9 | 10 | # clear anything existing in the level. 11 | actorSubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 12 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 13 | staticmeshsubsystem = unreal.get_editor_subsystem(unreal.StaticMeshEditorSubsystem) 14 | all_actors = actorSubsystem.get_all_level_actors() 15 | 16 | actorSubsystem.destroy_actors(all_actors) 17 | 18 | # Construct the Datasmith Scene from a file on disk. 19 | ds_scene_in_memory = unreal.DatasmithSceneElement.construct_datasmith_scene_from_file(file_to_import) 20 | 21 | print('constructed the scene') 22 | 23 | if ds_scene_in_memory is None: 24 | print('Scene loading failed.') 25 | quit() 26 | 27 | # Set import options. 28 | import_options = ds_scene_in_memory.get_options() 29 | tessellation_options = ds_scene_in_memory.get_options(unreal.DatasmithCommonTessellationOptions) 30 | if tessellation_options: 31 | tessellation_options.options.chord_tolerance = 1 32 | tessellation_options.options.max_edge_length = 40 33 | tessellation_options.options.normal_tolerance = 45 34 | import_options.base_options.scene_handling = unreal.DatasmithImportScene.CURRENT_LEVEL 35 | 36 | # Finalize the process by creating assets and actors. 37 | ds_scene_in_memory.import_scene(asset_folder) 38 | 39 | print('Import complete!') 40 | 41 | # merge the actors into one object 42 | all_actors = actorSubsystem.get_all_level_actors() 43 | merge_options = unreal.MergeStaticMeshActorsOptions() 44 | merge_options.new_actor_label = merge_actor_name 45 | # look for the unreal.MeshMergingSettings class to see what options you can set in here 46 | merge_options.base_package_name = fbx_destination 47 | 48 | new_mesh_actor = staticmeshsubsystem.merge_static_mesh_actors(all_actors, merge_options) 49 | 50 | # load the merged asset 51 | #SM_ prefix added by meger static mesh actors will be removed in a future version of UE 52 | assetname = fbx_destination.split('/')[-1] 53 | fbx_destination2 = fbx_destination.rstrip(assetname) + '/SM_'+assetname 54 | loaded_asset = assetSubsystem.load_asset(fbx_destination2) 55 | 56 | # set up the FBX export options 57 | task = unreal.AssetExportTask() 58 | task.object = loaded_asset # the asset to export 59 | task.filename = final_fbx_file # the filename to export as 60 | task.automated = True # don't display the export options dialog 61 | task.replace_identical = True # always overwrite the output 62 | task.options = unreal.FbxExportOption() 63 | 64 | # export! 65 | result = unreal.Exporter.run_asset_export_task(task) 66 | 67 | print('Export complete!') 68 | for error_msg in task.errors: 69 | unreal.log_error('{}'.format(error_msg)) 70 | 71 | 72 | 73 | ## Code for OBJ export 74 | import unreal 75 | 76 | file_to_import = "C:\\temp\\CAD\\Clutch assembly.SLDASM" 77 | final_obj_file = "C:\\temp\\my_filename.obj" 78 | asset_folder = '/Game/Create/MyCADScene' 79 | obj_destination = '/Game/Create/NEW_MESH' 80 | 81 | # clear anything existing in the level. 82 | actorSubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 83 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 84 | staticmeshsubsystem = unreal.get_editor_subsystem(unreal.StaticMeshEditorSubsystem) 85 | all_actors = actorSubsystem.get_all_level_actors() 86 | 87 | for a in all_actors: 88 | actorSubsystem.destroy_actor(a) 89 | 90 | # Construct the Datasmith Scene from a file on disk. 91 | ds_scene_in_memory = unreal.DatasmithSceneElement.construct_datasmith_scene_from_file(file_to_import) 92 | 93 | print('constructed the scene') 94 | 95 | if ds_scene_in_memory is None: 96 | print('Scene loading failed.') 97 | quit() 98 | 99 | # Set import options. 100 | import_options = ds_scene_in_memory.get_options() 101 | tessellation_options = ds_scene_in_memory.get_options(unreal.DatasmithCommonTessellationOptions) 102 | if tessellation_options: 103 | tessellation_options.options.chord_tolerance = 1 104 | tessellation_options.options.max_edge_length = 40 105 | tessellation_options.options.normal_tolerance = 45 106 | import_options.base_options.scene_handling = unreal.DatasmithImportScene.CURRENT_LEVEL 107 | 108 | # Finalize the process by creating assets and actors. 109 | ds_scene_in_memory.import_scene(asset_folder) 110 | 111 | print('Import complete!') 112 | 113 | # merge the actors into one object 114 | all_actors = actorSubsystem.get_all_level_actors() 115 | merge_options = unreal.EditorScriptingMergeStaticMeshActorsOptions() 116 | # look for the unreal.MeshMergingSettings class to see what options you can set in here 117 | merge_options.base_package_name = obj_destination 118 | new_mesh_actor = staticmeshsubsystem.merge_static_mesh_actors(all_actors, merge_options) 119 | 120 | # load the merged asset 121 | #SM_ prefix added by meger static mesh actors will be removed in a future version of UE 122 | assetname = obj_destination.split('/')[-1] 123 | obj_destination2 = obj_destination.rstrip(assetname) + '/SM_'+assetname 124 | loaded_asset = assetSubsystem.load_asset(obj_destination2) 125 | 126 | # set up the OBJ export options 127 | task = unreal.AssetExportTask() 128 | task.object = loaded_asset # the asset to export 129 | task.filename = final_obj_file # the filename to export as 130 | task.automated = True # don't display the export options dialog 131 | task.replace_identical = True # always overwrite the output 132 | task.exporter = unreal.StaticMeshExporterOBJ() 133 | 134 | # export! 135 | result = unreal.Exporter.run_asset_export_task(task) 136 | 137 | print('Export complete!') 138 | for error_msg in task.errors: 139 | unreal.log_error('{}'.format(error_msg)) 140 | -------------------------------------------------------------------------------- /scripts/ImportExport/Import_Datasmith_CAD_and_set_LODs.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | # load a CAD files multiple times with different tessellation settings 4 | # to generate the LODs of the static meshes. 5 | # THIS IS NOT OPTIMIZED (many file IO) 6 | 7 | actorSubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 8 | staticmeshsubsystem = unreal.get_editor_subsystem(unreal.StaticMeshEditorSubsystem) 9 | 10 | input_file = 'C:/Temp/CAD/Clutch assembly.SLDASM' 11 | 12 | content_folder = '/Game/' 13 | 14 | # number of LODs 15 | num_lods = 3 16 | 17 | # tessellation settings for LOD0 18 | base_settings = [0.01, 10.0, 15.0] 19 | 20 | # will hold all meshes LODs, by mesh name 21 | meshes_lods = {} 22 | 23 | # will hold material instances of LOD0, that will be used for all LODs 24 | materials = {} 25 | 26 | for lod_index in range(num_lods): 27 | asset_content_path = content_folder + '/LOD_' + str(lod_index) 28 | 29 | # init datasmith CAD scene import from a CAD file and a target content directory 30 | datasmith_scene = unreal.DatasmithSceneElement.construct_datasmith_scene_from_file(input_file) 31 | 32 | # check scene initialization 33 | if datasmith_scene is None: 34 | print('Error: Failed creating Datasmith CAD scene') 35 | continue 36 | 37 | # set CAD import options 38 | import_options = datasmith_scene.get_options() 39 | import_options.base_options.scene_handling = unreal.DatasmithImportScene.CURRENT_LEVEL 40 | import_options.base_options.static_mesh_options.generate_lightmap_u_vs = False 41 | 42 | tessellation_options = datasmith_scene.get_options(unreal.DatasmithCommonTessellationOptions) 43 | if tessellation_options: 44 | tessellation_options.options.chord_tolerance = base_settings[0]*(2**lod_index) 45 | tessellation_options.options.max_edge_length = base_settings[1]*(2**lod_index) 46 | tessellation_options.options.normal_tolerance = base_settings[2]*(2**lod_index) 47 | tessellation_options.options.stitching_technique = unreal.DatasmithCADStitchingTechnique.STITCHING_NONE 48 | 49 | # import the scene into the current level 50 | result = datasmith_scene.import_scene(asset_content_path) 51 | if not result.import_succeed: 52 | print('Error: Datasmith scene import failed') 53 | continue 54 | 55 | # no geometry imported, nothing to do 56 | if len(result.imported_actors) == 0: 57 | print('Warning: Non actor imported') 58 | continue 59 | 60 | if lod_index == 0: 61 | # base LOD, init mesh LODs dict and store materials 62 | for static_mesh in result.imported_meshes: 63 | meshes_lods[static_mesh.get_name()] = [static_mesh] 64 | for i in range(static_mesh.get_num_sections(0)): 65 | material_interface = static_mesh.get_material(i) 66 | materials[material_interface.get_name()] = material_interface 67 | else: 68 | # add a mesh LOD to the dict and replace material 69 | for static_mesh in result.imported_meshes: 70 | meshes_lods[static_mesh.get_name()].append(static_mesh) 71 | for i in range(static_mesh.get_num_sections(0)): 72 | material_interface = static_mesh.get_material(i) 73 | static_mesh.set_material(i, materials[material_interface.get_name()]) 74 | # delete actors in level 75 | for actor in result.imported_actors: 76 | actorSubsystem.destroy_actor(actor) 77 | # could also delete material instances... 78 | 79 | # set LODs for meshes 80 | for name, meshes in meshes_lods.items(): 81 | base_mesh = meshes[0] 82 | for lod_index in range(1,num_lods): 83 | lod_mesh = meshes[lod_index] 84 | staticmeshsubsystem.set_lod_from_static_mesh(base_mesh, lod_index, lod_mesh, 0, True) -------------------------------------------------------------------------------- /scripts/ImportExport/Import_Datatable.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | 4 | def import_data_table_as_json(filename): 5 | task = unreal.AssetImportTask() 6 | task.filename = filename 7 | task.destination_path = "/Game/DataTables" 8 | task.replace_existing = True 9 | task.automated = True 10 | task.save = False 11 | 12 | task.factory = unreal.ReimportDataTableFactory() 13 | task.factory.automated_import_settings.import_row_struct = unreal.load_object(None, '/Game/DataTables/S_TestStruct') 14 | task.factory.automated_import_settings.import_type = unreal.CSVImportType.ECSV_DATA_TABLE 15 | 16 | unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) 17 | 18 | import_data_table_as_json("C:/temp/import_data_table.json") -------------------------------------------------------------------------------- /scripts/ImportExport/Interchange_Pipeline.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | #This python script should be put into a folder that is accessible to UE and add it to the "startup scripts" in the project settings 4 | #https://docs.unrealengine.com/en-US/scripting-the-unreal-editor-using-python/ 5 | 6 | 7 | #this pipeline will change the compression settings on textures based on their name suffixes 8 | 9 | @unreal.uclass() 10 | class MyProjectPythonPipeline(unreal.InterchangePythonPipelineBase): 11 | configure_texture_from_name_suffix = unreal.uproperty(bool,meta=dict(Category="Textures")) 12 | def cast(self, object_to_cast, object_class): 13 | try: 14 | return object_class.cast(object_to_cast) 15 | except: 16 | return None 17 | 18 | def recursive_set_node_properties(self, base_node_container, node_unique_id): 19 | node = base_node_container.get_node(node_unique_id) 20 | texture_node = self.cast(node, unreal.InterchangeTexture2DFactoryNode) 21 | if texture_node: 22 | texture_name = texture_node.get_display_label() 23 | if texture_name.endswith("_D"): 24 | #unreal.TextureCompressionSettings.TC_BC7 is 11 25 | texture_node.set_custom_compression_settings(11) 26 | elif texture_name.ends_with("_N"): 27 | #unreal.TextureCompressionSettings.TC_NORMALMAP is 1 28 | texture_node.set_custom_compression_settings(1) 29 | else: 30 | #unreal.TextureCompressionSettings.TC_DEFAULT is 0 31 | texture_node.set_custom_compression_settings(0) 32 | childrens = base_node_container.get_node_children_uids(node.get_unique_id()) 33 | for child_uid in childrens: 34 | self.recursive_set_node_properties(base_node_container, child_uid) 35 | 36 | @unreal.ufunction(override=True) 37 | def scripted_execute_pipeline(self, base_node_container, in_source_datas, content_base_path): 38 | if not self.configure_texture_from_name_suffix: 39 | return 40 | root_nodes = base_node_container.get_roots() 41 | for node_unique_id in root_nodes: 42 | self.recursive_set_node_properties(base_node_container, node_unique_id) 43 | return True -------------------------------------------------------------------------------- /scripts/ImportExport/import_ABC.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | # Makes a generic Import task that will be used for our alembic file. 4 | def make_alembic_import_task(filepath, destination_path, save=True): 5 | task = unreal.AssetImportTask() 6 | task.set_editor_property("filename", filepath) 7 | task.set_editor_property("destination_path", destination_path) 8 | task.set_editor_property("replace_existing", True) 9 | task.set_editor_property("automated", True) 10 | task.set_editor_property("save", True) 11 | return task 12 | 13 | # Sets specific settings for our alembic file to import 14 | def set_alembic_import_settings(): 15 | options = unreal.AbcImportSettings() 16 | options.set_editor_property("import_type", unreal.AlembicImportType.SKELETAL) 17 | options.material_settings.set_editor_property("create_materials", False) 18 | options.material_settings.set_editor_property("find_materials", True) 19 | return options 20 | 21 | # Hard-coded paths 22 | abc_file = "C:/temp/my_alembic.abc" 23 | import_path = "/Game/Create/ABC_Testing/" 24 | 25 | # Calling functions to make the import task and set it's settings for our Alembic File 26 | abc_import_task = make_alembic_import_task(abc_file, import_path, True) 27 | abc_import_task.options = set_alembic_import_settings() 28 | 29 | # Imports Alembic file 30 | unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([abc_import_task]) -------------------------------------------------------------------------------- /scripts/ImportExport/import_FBX.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | data = unreal.AutomatedAssetImportData() 4 | data.destination_path = "/Game/Create/my_fbx" 5 | data.filenames = ["C:/temp/my_filename.fbx"] 6 | factory = unreal.FbxSceneImportFactory() 7 | data.factory = factory 8 | unreal.AssetToolsHelpers.get_asset_tools().import_assets_automated(data) -------------------------------------------------------------------------------- /scripts/Level/Generate_Box_UV_on_selected_actors.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | # retrieves selected actors in the world outliner 4 | actorsubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 5 | staticmeshsubsystem = unreal.get_editor_subsystem(unreal.StaticMeshEditorSubsystem) 6 | actors = actorsubsystem.get_selected_level_actors() 7 | 8 | for actor in actors: 9 | static_mesh = actor.static_mesh_component.static_mesh 10 | box = static_mesh.get_bounding_box() 11 | 12 | tiling = 1.0 13 | pos = (box.min + box.max) * 0.5 14 | rot = unreal.Rotator() 15 | bounding_box_size = box.max - box.min 16 | max_edge = max([bounding_box_size.x, bounding_box_size.y, bounding_box_size.z]) 17 | box_uv_size = unreal.Vector(max_edge/tiling, max_edge/tiling, max_edge/tiling) 18 | 19 | staticmeshsubsystem.generate_box_uv_channel(static_mesh, 0, 0, pos, rot, box_uv_size) 20 | -------------------------------------------------------------------------------- /scripts/Level/Line_Trace_Place_Actor.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | actorsubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 4 | assetsubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 5 | layersubsystem = unreal.get_editor_subsystem(unreal.LayersSubsystem) 6 | sm = assetsubsystem.load_asset("/game/gltf/test/SM_Drop") 7 | 8 | world = layersubsystem.get_world() 9 | posToTest = unreal.Vector(0,0,0) 10 | 11 | listOfObjectTypeQuery = unreal.Array(unreal.ObjectTypeQuery) 12 | listOfObjectTypeQuery.append(unreal.ObjectTypeQuery.OBJECT_TYPE_QUERY1) 13 | listOfObjectTypeQuery.append(unreal.ObjectTypeQuery.OBJECT_TYPE_QUERY2) 14 | hit = unreal.SystemLibrary.line_trace_single_for_objects(world,unreal.Vector(posToTest.x,posToTest.y,500),unreal.Vector(posToTest.x,posToTest.y,0),listOfObjectTypeQuery,True,unreal.Array(unreal.Actor),unreal.DrawDebugTrace.NONE,True) 15 | if hit != None: 16 | print(hit) 17 | print(hit.to_tuple()) 18 | print(hit.to_tuple()[4]) 19 | print(hit.to_tuple()[5])#impact_point 20 | print(hit.to_tuple()[6]) 21 | actorsubsystem.spawn_actor_from_object(sm,hit.to_tuple()[5]) -------------------------------------------------------------------------------- /scripts/Level/Merge_and_proxy.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | from unreal import (MeshLODSelectionType, ScopedSlowTask, StaticMeshActor) 3 | 4 | # progress bar 5 | with ScopedSlowTask(4, "Create proxy LOD") as slow_task: 6 | slow_task.make_dialog(True) 7 | slow_task.enter_progress_frame(1) 8 | 9 | actorSubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 10 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 11 | staticMeshSubsystem = unreal.get_editor_subsystem(unreal.StaticMeshEditorSubsystem) 12 | 13 | level_actors = actorSubsystem.get_selected_level_actors() 14 | actors_to_merge = [a for a in level_actors if a.__class__ == StaticMeshActor] 15 | 16 | slow_task.enter_progress_frame(1) 17 | # set the merge options 18 | merge_options = unreal.MergeStaticMeshActorsOptions() 19 | merge_options.base_package_name = "/Game/Create/SM_Merged" 20 | merge_options.destroy_source_actors = False 21 | merge_options.new_actor_label = "Merged Actor" 22 | merge_options.spawn_merged_actor = True 23 | merge_options.mesh_merging_settings.bake_vertex_data_to_mesh = False 24 | merge_options.mesh_merging_settings.computed_light_map_resolution = False 25 | merge_options.mesh_merging_settings.generate_light_map_uv = False 26 | merge_options.mesh_merging_settings.lod_selection_type = MeshLODSelectionType.ALL_LO_DS 27 | merge_options.mesh_merging_settings.merge_physics_data = True 28 | merge_options.mesh_merging_settings.pivot_point_at_zero = True 29 | # merge meshes actors and retrieve spawned actor 30 | merged_actor = staticMeshSubsystem.merge_static_mesh_actors(actors_to_merge, merge_options) 31 | merged_mesh = merged_actor.static_mesh_component.static_mesh 32 | 33 | slow_task.enter_progress_frame(1) 34 | # set the proxy options 35 | proxy_option = unreal.CreateProxyMeshActorOptions() 36 | proxy_option.base_package_name = "/Game/Create/SM_Proxy" 37 | proxy_option.destroy_source_actors = False 38 | proxy_option.new_actor_label = "Proxy Actor" 39 | proxy_option.spawn_merged_actor = True 40 | proxy_option.mesh_proxy_settings.allow_distance_field = False 41 | proxy_option.mesh_proxy_settings.allow_vertex_colors = False 42 | proxy_option.mesh_proxy_settings.calculate_correct_lod_model = False 43 | proxy_option.mesh_proxy_settings.compute_light_map_resolution = False 44 | proxy_option.mesh_proxy_settings.create_collision = False 45 | proxy_option.mesh_proxy_settings.generate_lightmap_u_vs = False 46 | proxy_option.mesh_proxy_settings.hard_angle_threshold = 89 47 | # increased normal texture size to capture more details 48 | proxy_option.mesh_proxy_settings.material_settings.diffuse_texture_size = (2048, 2048) 49 | proxy_option.mesh_proxy_settings.max_ray_cast_dist = 10.0 50 | proxy_option.mesh_proxy_settings.merge_distance = 1.0 51 | proxy_option.mesh_proxy_settings.override_transfer_distance = True 52 | proxy_option.mesh_proxy_settings.override_voxel_size = True 53 | proxy_option.mesh_proxy_settings.recalculate_normals = False 54 | proxy_option.mesh_proxy_settings.reuse_mesh_lightmap_u_vs = False 55 | # affects decimation quality 56 | proxy_option.mesh_proxy_settings.screen_size = 800 57 | proxy_option.mesh_proxy_settings.use_hard_angle_threshold = True 58 | proxy_option.mesh_proxy_settings.voxel_size = 0.5 59 | # create proxy and retrieve spawned actor 60 | proxy_actor = staticMeshSubsystem.create_proxy_mesh_actor(actors_to_merge, proxy_option) 61 | proxy_mesh = proxy_actor.static_mesh_component.static_mesh 62 | actorSubsystem.destroy_actor(proxy_actor) 63 | 64 | slow_task.enter_progress_frame(1) 65 | for actor in level_actors: 66 | actorSubsystem.destroy_actor(actor) 67 | 68 | staticMeshSubsystem.set_lod_from_static_mesh(merged_mesh, 1, proxy_mesh, 0, True) 69 | assetSubsystem.delete_loaded_asset(proxy_mesh) -------------------------------------------------------------------------------- /scripts/Level/Merge_hierarchy_and_replace_them_by_instance_based_on_metadata.py: -------------------------------------------------------------------------------- 1 | import re 2 | import unreal 3 | 4 | # content folder where to put merged geometry 5 | content_folder = "/Game/Create/Test_MergeComponents/" 6 | 7 | # utility function to get the full hierarchy of an actor 8 | def get_actor_hierarchy(actor): 9 | result = [] 10 | list = actor.get_attached_actors() 11 | while list: 12 | a = list.pop() 13 | result.append(a) 14 | for child in a.get_attached_actors(): 15 | list.append(child) 16 | return result 17 | 18 | 19 | # retrieves all Actor in levels having the SU.GUID.XXXX tag 20 | actorSubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 21 | staticMeshSubsystem = unreal.get_editor_subsystem(unreal.StaticMeshEditorSubsystem) 22 | level_actors = actorSubsystem.get_all_level_actors() 23 | guid_actors = {} 24 | for actor in level_actors: 25 | tags = [str(tag) for tag in actor.tags if str(tag).startswith('SU.GUID.')] 26 | if len(tags) > 0: 27 | guid = tags[0][8:] 28 | if not guid in guid_actors: 29 | guid_actors[guid] = [] 30 | guid_actors[guid].append(actor) 31 | 32 | # handle nested components by using only the highest level actors for a guid 33 | for guid, actors in guid_actors.items(): 34 | actors_to_remove = [] 35 | for actor in actors: 36 | parent = actor.get_attach_parent_actor() 37 | has_ancestor_in_roots = False 38 | while not parent is None: 39 | if len([tag for tag in parent.tags if str(tag).startswith('SU.GUID.')]) > 0: 40 | has_ancestor_in_roots = True 41 | break 42 | parent = parent.get_attach_parent_actor() 43 | if has_ancestor_in_roots: 44 | actors_to_remove.append(actor) 45 | for actor in actors_to_remove: 46 | actors.remove(actor) 47 | 48 | 49 | # merge and instanciate 50 | for guid, actors in guid_actors.items(): 51 | print("GUID: " + guid) 52 | if len(actors) == 0: 53 | continue 54 | 55 | actor = actors[0] 56 | # retrieves the list of static meshes to merge 57 | actors_to_merge = [a for a in [actor] + get_actor_hierarchy(actor) if a.__class__ == unreal.StaticMeshActor] 58 | 59 | # special case where 0 or 1 actor to merge 60 | if len(actors_to_merge) < 2: 61 | for old_actor in actors: 62 | if len(actors_to_merge) == 1: 63 | # keep static mesh actors and reparent them 64 | static_mesh_actor = [a for a in [old_actor] + get_actor_hierarchy(old_actor) if a.__class__ == unreal.StaticMeshActor][0] 65 | static_mesh_actor.attach_to_actor(old_actor.get_attach_parent_actor(), unreal.Name(), unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, False) 66 | # delete unnecessary nodes 67 | actors_to_delete = [a for a in [old_actor] + get_actor_hierarchy(old_actor) if a.__class__ != unreal.StaticMeshActor] 68 | for delete_actor in actors_to_delete: 69 | print("deleting actor " + delete_actor.get_actor_label()) 70 | actorSubsystem.destroy_actor(delete_actor) 71 | continue 72 | 73 | print("merging all static meshes under " + actor.get_actor_label()) 74 | backup_transform = actor.get_actor_transform() 75 | actor.set_actor_transform(unreal.Transform(), False, False) 76 | 77 | cleaned_actor_name = re.sub('[^-a-zA-Z0-9_]+', '_', actor.get_actor_label()) 78 | new_path_name = content_folder + cleaned_actor_name 79 | print("new path name " + new_path_name) 80 | 81 | merge_options = unreal.MergeStaticMeshActorsOptions() 82 | merge_options.base_package_name = new_path_name 83 | merge_options.destroy_source_actors = False 84 | merge_options.mesh_merging_settings.bake_vertex_data_to_mesh = False 85 | merge_options.mesh_merging_settings.computed_light_map_resolution = False 86 | merge_options.mesh_merging_settings.generate_light_map_uv = False 87 | merge_options.mesh_merging_settings.lod_selection_type = unreal.MeshLODSelectionType.ALL_LO_DS 88 | # only if single LOD level is merged 89 | # merge_options.mesh_merging_settings.lod_selection_type = unreal.MeshLODSelectionType.SPECIFIC_LOD 90 | # merge_options.mesh_merging_settings.specific_lod = 0 91 | # merge_options.mesh_merging_settings.merge_materials = True 92 | merge_options.mesh_merging_settings.merge_physics_data = True 93 | merge_options.mesh_merging_settings.pivot_point_at_zero = True 94 | merge_options.mesh_merging_settings.specific_lod = 0 95 | merge_options.mesh_merging_settings.use_landscape_culling = False 96 | merge_options.mesh_merging_settings.use_texture_binning = False 97 | merge_options.mesh_merging_settings.use_vertex_data_for_baking_material = False 98 | merge_options.new_actor_label = cleaned_actor_name 99 | merge_options.spawn_merged_actor = True 100 | 101 | # merge and retrieve first instance 102 | merged_actor = staticMeshSubsystem.merge_static_mesh_actors(actors_to_merge, merge_options) 103 | merged_actor.root_component.set_editor_property("mobility", unreal.ComponentMobility.MOVABLE) 104 | merged_actor.set_actor_transform(backup_transform, False, False) 105 | merged_mesh = merged_actor.static_mesh_component.static_mesh 106 | 107 | # instanciate all other components instances 108 | for old_actor in actors: 109 | if old_actor == actor: 110 | new_actor = merged_actor 111 | else: 112 | new_actor = actorSubsystem.spawn_actor_from_class(unreal.StaticMeshActor, unreal.Vector(0, 0, 0), unreal.Rotator(0, 0, 0)) 113 | new_actor.set_actor_transform(old_actor.get_actor_transform(), False, False) 114 | new_actor.static_mesh_component.set_static_mesh(merged_mesh) 115 | new_actor.root_component.set_editor_property("mobility", unreal.ComponentMobility.MOVABLE) 116 | new_actor.set_actor_label(old_actor.get_actor_label()) 117 | new_actor.attach_to_actor(old_actor.get_attach_parent_actor(), unreal.Name(), unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, False) 118 | # delete unnecessary nodes 119 | actors_to_delete = [a for a in [old_actor] + get_actor_hierarchy(old_actor)] 120 | for delete_actor in actors_to_delete: 121 | print("deleting actor " + delete_actor.get_actor_label()) 122 | actorSubsystem.destroy_actor(delete_actor) 123 | -------------------------------------------------------------------------------- /scripts/Level/Rename_components_on_a_selected_BP_actor_in_the_level.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | actorSubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 4 | actors = actorSubsystem.get_selected_level_actors() 5 | 6 | for actor in actors: 7 | sm_comps = actor.get_components_by_class(unreal.StaticMeshComponent) 8 | for sm_comp in sm_comps: 9 | comp_name = sm_comp.get_name() 10 | sm_comp.rename("Generated " + comp_name) -------------------------------------------------------------------------------- /scripts/Level/Substitute_actor_with_a_given_tag.py: -------------------------------------------------------------------------------- 1 | 2 | import unreal 3 | 4 | ##### Replace RPC assets 5 | 6 | rpc_replacement_asset = '/Game/MultiUserViewer/Meshes/Tree/HillTree_02' 7 | rpcs_to_replace = [ 8 | 'Hawthorn', 9 | 'Honey_Locust', 10 | 'Largetooth_Aspen', 11 | 'Lombardy_Poplar', 12 | 'Red_Ash', 13 | 'Red_Maple', 14 | 'Scarlet_Oak' 15 | ] 16 | 17 | actorSubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 18 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 19 | all_actor_components = actorSubsystem.get_all_level_actors_components() 20 | loaded_asset = assetSubsystem.load_asset(rpc_replacement_asset) 21 | for component in all_actor_components: 22 | if component.component_has_tag("Revit.RPC"): 23 | actor_name = component.get_owner().get_actor_label() 24 | print("Found an RPC component: " + actor_name) 25 | for replacement_key in rpcs_to_replace: 26 | if (actor_name.startswith(replacement_key)): 27 | print("Replacing...") 28 | spawn_location = component.get_owner().get_actor_location() 29 | spawn_rotation = component.get_owner().get_actor_rotation() 30 | # randomize the rotation 31 | spawn_rotation.yaw = unreal.MathLibrary.random_float_in_range(0,360) 32 | # spawn the actor 33 | new_actor = actorSubsystem.spawn_actor_from_object(loaded_asset, spawn_location, spawn_rotation) 34 | new_actor.root_component.set_editor_property("mobility", unreal.ComponentMobility.MOVABLE) 35 | # randomize its scale factor 36 | scale_factor = unreal.MathLibrary.random_float_in_range(0.75,1.25) 37 | world_scale = new_actor.get_actor_scale3d() 38 | world_scale.z = world_scale.z * scale_factor 39 | new_actor.set_actor_scale3d( world_scale ) 40 | # make the new actor a child of the RPC actor 41 | new_actor.attach_to_actor( component.get_owner(), "", unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, False ) 42 | break -------------------------------------------------------------------------------- /scripts/Level/Substitute_assets_with_consolidate_assets.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | def replace_material(original, replacement): 4 | assetSubsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) 5 | original_asset = assetSubsystem.load_asset(original) 6 | replacement_asset = assetSubsystem.load_asset(replacement) 7 | assetSubsystem.consolidate_assets(replacement_asset, [original_asset]) 8 | #still need to run fixup redirectors 9 | 10 | replace_material("/Game/Materials/M_MetalShiny_3", "/Game/Materials/M_MetalShiny_4") -------------------------------------------------------------------------------- /scripts/Misc/ExamplePythonMenus.py: -------------------------------------------------------------------------------- 1 | # Copyright Epic Games, Inc. All Rights Reserved. 2 | # 3 | 4 | import unreal 5 | 6 | menu_owner = "TestPy" 7 | tool_menus = unreal.ToolMenus.get() 8 | 9 | def GetMainPythonMenu(): 10 | main_menu = tool_menus.extend_menu("LevelEditor.MainMenu") 11 | main_menu.add_sub_menu(menu_owner, "", "Custom", "Custom", "") 12 | 13 | custom_menu = tool_menus.register_menu("LevelEditor.MainMenu.Custom", "", unreal.MultiBoxType.MENU, False) 14 | custom_menu.add_section("PythonSection", "Python Examples") 15 | custom_menu.add_sub_menu(menu_owner, "PythonSection", "Python", "Python", "") 16 | 17 | return tool_menus.register_menu("LevelEditor.MainMenu.Custom.Python", "", unreal.MultiBoxType.MENU, False) 18 | 19 | @unreal.uclass() 20 | class MenuEntryScript01(unreal.ToolMenuEntryScript): 21 | 22 | clicked_count = unreal.uproperty(int) 23 | 24 | def init_as_toolbar_button(self): 25 | self.data.menu = "LevelEditor.LevelEditorToolBar" 26 | self.data.advanced.entry_type = unreal.MultiBlockType.TOOL_BAR_BUTTON 27 | 28 | @unreal.ufunction(override=True) 29 | def execute(self, context): 30 | self.clicked_count += 1 31 | print("MenuEntryScript01 command has been called: " + str(self.clicked_count)) 32 | 33 | @unreal.ufunction(override=True) 34 | def get_check_state(self, context): 35 | if self.clicked_count % 2 == 0: 36 | return unreal.CheckBoxState.UNCHECKED 37 | else: 38 | return unreal.CheckBoxState.CHECKED 39 | 40 | @unreal.ufunction(override=True) 41 | def can_execute(self, context): 42 | return True 43 | 44 | @unreal.ufunction(override=True) 45 | def is_visible(self, context): 46 | return True 47 | 48 | @unreal.ufunction(override=True) 49 | def get_label(self, context): 50 | return str(self.data.label) + ": " + str(self.clicked_count) 51 | 52 | @unreal.ufunction(override=True) 53 | def get_tool_tip(self, context): 54 | return "Python class MenuEntryScript01 ToolTip" 55 | 56 | @unreal.ufunction(override=True) 57 | def get_icon(self, context): 58 | if self.clicked_count % 2 == 0: 59 | return unreal.ScriptSlateIcon("EditorStyle", "LevelEditor.Build") 60 | else: 61 | return unreal.ScriptSlateIcon("EditorStyle", "LevelEditor.OpenLevelBlueprint") 62 | 63 | @unreal.uclass() 64 | class ContentBrowserAssetContextMenuExample(unreal.ToolMenuEntryScript): 65 | 66 | @unreal.ufunction(override=True) 67 | def execute(self, context): 68 | print("ContentBrowserAssetContextMenuExample command has been called") 69 | content_browser_context = context.find_by_class(unreal.ContentBrowserAssetContextMenuContext) 70 | if content_browser_context: 71 | selected_objects = content_browser_context.get_selected_objects() 72 | for x in selected_objects: 73 | print(" SelectedObject: ", x) 74 | 75 | @unreal.uclass() 76 | class ContentBrowserFolderContextMenuExample(unreal.ToolMenuEntryScript): 77 | 78 | @unreal.ufunction(override=True) 79 | def execute(self, context): 80 | print("ContentBrowserFolderContextMenuExample command has been called") 81 | 82 | content_browser_folder_context = context.find_by_class(unreal.ContentBrowserDataMenuContext_FolderMenu) 83 | if content_browser_folder_context: 84 | for x in content_browser_folder_context.selected_items: 85 | if x.is_folder(): 86 | print(" Folder: ", x) 87 | if x.is_file(): 88 | print(" File: ", x) 89 | print(" VirtualPath: ", x.get_virtual_path()) 90 | 91 | @unreal.uclass() 92 | class AssetEditorMenuExample(unreal.ToolMenuEntryScript): 93 | @unreal.ufunction(override=True) 94 | def execute(self, context): 95 | print("AssetEditorMenuExample command has been called") 96 | asset_editor_toolkit_context = context.find_by_class(unreal.AssetEditorToolkitMenuContext) 97 | if asset_editor_toolkit_context: 98 | editing_objects = asset_editor_toolkit_context.get_editing_objects() 99 | for x in editing_objects: 100 | print(" editing_object: ", x) 101 | 102 | @unreal.uclass() 103 | class MenuEntryScriptDynamic01(unreal.ToolMenuEntryScript): 104 | @unreal.ufunction(override=True) 105 | def construct_menu_entry(self, menu, section, context): 106 | entry = unreal.ToolMenuEntryExtensions.init_menu_entry( 107 | menu_owner, 108 | "MenuEntryScriptDymamic01", 109 | "Dynamic Entry", 110 | "Dynamic Entry ToolTip", 111 | unreal.ToolMenuStringCommandType.PYTHON, 112 | "", 113 | "print(\"Dynamic Python Menu Entry called\")") 114 | menu.add_menu_entry(section, entry) 115 | 116 | @unreal.uclass() 117 | class SubMenuEntryScript01(unreal.ToolMenuEntryScript): 118 | def init(self): 119 | self.data.advanced.is_sub_menu = True 120 | 121 | @unreal.uclass() 122 | class PyTestDynamicSection01(unreal.ToolMenuSectionDynamic): 123 | @unreal.ufunction(override=True) 124 | def construct_sections(self, menu, context): 125 | menu.add_section("DynamicSection01", "Dynamic Section 01") 126 | 127 | entry = unreal.ToolMenuEntryExtensions.init_menu_entry( 128 | menu_owner, 129 | "DynamicSection01Entry01", 130 | "Dynamic Section01 Entry01", 131 | "ToolTip", 132 | unreal.ToolMenuStringCommandType.PYTHON, 133 | "", 134 | "print(\"DynamicSection01Entry01 called\")") 135 | menu.add_menu_entry("DynamicSection01", entry) 136 | 137 | entry = unreal.ToolMenuEntryExtensions.init_menu_entry( 138 | menu_owner, 139 | "DynamicSection01Entry02", 140 | "Dynamic Section01 Entry02", 141 | "ToolTip", 142 | unreal.ToolMenuStringCommandType.PYTHON, 143 | "", 144 | "print(\"DynamicSection01Entry02 called\")") 145 | menu.add_menu_entry("DynamicSection01", entry) 146 | 147 | def AddMenuStringCommands(menu): 148 | 149 | menu.add_section("StringCommands", "String Commands", "", unreal.ToolMenuInsertType.FIRST) 150 | 151 | entry = unreal.ToolMenuEntryExtensions.init_menu_entry( 152 | menu_owner, 153 | "StringCommandsCommand", 154 | "Command", 155 | "Execute Command", 156 | unreal.ToolMenuStringCommandType.COMMAND, 157 | "", 158 | "echo test string menu command") 159 | menu.add_menu_entry("StringCommands", entry) 160 | 161 | entry = unreal.ToolMenuEntryExtensions.init_menu_entry( 162 | menu_owner, 163 | "StringCommandsPython", 164 | "Python", 165 | "Execute Python", 166 | unreal.ToolMenuStringCommandType.PYTHON, 167 | "", 168 | "print(\"python command executed\")") 169 | menu.add_menu_entry("StringCommands", entry) 170 | 171 | entry = unreal.ToolMenuEntryExtensions.init_menu_entry( 172 | menu_owner, 173 | "StringCommandsCustom", 174 | "Custom", 175 | "Test Custom", 176 | unreal.ToolMenuStringCommandType.CUSTOM, 177 | "TestCustom", 178 | "Test handling of custom script language that does not exist") 179 | menu.add_menu_entry("StringCommands", entry) 180 | 181 | def AddToolbarStringCommands(menu): 182 | 183 | menu.add_section("StringCommands", "String Commands") 184 | 185 | # Button 186 | entry = unreal.ToolMenuEntryExtensions.init_menu_entry( 187 | menu_owner, 188 | "TestStringPy", 189 | "TestStringPy", 190 | "Test Python toolbar button", 191 | unreal.ToolMenuStringCommandType.PYTHON, 192 | "", 193 | "print(\"test python toolbar button pressed\")") 194 | entry.type = unreal.MultiBlockType.TOOL_BAR_BUTTON 195 | menu.add_menu_entry("StringCommands", entry) 196 | 197 | # Combo button 198 | entry = unreal.ToolMenuEntryExtensions.init_menu_entry( 199 | menu_owner, 200 | "TestComboPy", 201 | "TestComboPy", 202 | "", 203 | unreal.ToolMenuStringCommandType.COMMAND, 204 | "", 205 | "") 206 | entry.type = unreal.MultiBlockType.TOOL_BAR_COMBO_BUTTON 207 | menu.add_menu_entry("StringCommands", entry) 208 | 209 | # Create menu that goes with the combo button above 210 | sub_menu = tool_menus.register_menu("LevelEditor.LevelEditorToolBar.TestComboPy", "", unreal.MultiBoxType.MENU, False) 211 | entry = unreal.ToolMenuEntryExtensions.init_menu_entry( 212 | menu_owner, 213 | "TestComboItem", 214 | "Test Item", 215 | "Tooltip", 216 | unreal.ToolMenuStringCommandType.PYTHON, 217 | "", 218 | "print(\"Toolbar combo button's Item01 has been clicked\")") 219 | sub_menu.add_menu_entry("Section01", entry) 220 | 221 | def AddClassBasedMenuItems(menu): 222 | menu.add_section("MenuEntryScript", "Menu Entry Script") 223 | 224 | # Basic 225 | entry = MenuEntryScript01() 226 | entry.init_entry(menu_owner, "LevelEditor.MainMenu.Custom.Python", "MenuEntryScript", "MenuEntryScriptA", "Basic") 227 | menu.add_menu_entry_object(entry) 228 | 229 | # Checkbox 230 | entry = MenuEntryScript01() 231 | entry.init_entry(menu_owner, "LevelEditor.MainMenu.Custom.Python", "MenuEntryScript", "MenuEntryScriptToggleButton", "Toggle Button") 232 | entry.data.advanced.user_interface_action_type = unreal.UserInterfaceActionType.TOGGLE_BUTTON 233 | menu.add_menu_entry_object(entry) 234 | 235 | # Dynamic Entry 236 | entry = MenuEntryScriptDynamic01() 237 | entry.init_entry(menu_owner, "LevelEditor.MainMenu.Custom.Python", "MenuEntryScript", "MenuEntryScriptDymamic", "Dymamic") 238 | menu.add_menu_entry_object(entry) 239 | 240 | # SubMenu 241 | entry = SubMenuEntryScript01() 242 | entry.init_entry(menu_owner, "LevelEditor.MainMenu.Custom.Python", "MenuEntryScript", "MenuEntryScriptSubMenu", "Sub menu") 243 | entry.init() 244 | menu.add_menu_entry_object(entry) 245 | 246 | # SubMenu Menu 247 | sub_menu = tool_menus.register_menu("LevelEditor.MainMenu.Custom.Python.MenuEntryScriptSubMenu", "", unreal.MultiBoxType.MENU, False) 248 | entry = unreal.ToolMenuEntryExtensions.init_menu_entry( 249 | menu_owner, 250 | "ItemA", 251 | "SubMenu Item", 252 | "SubMenu Item ToolTip", 253 | unreal.ToolMenuStringCommandType.PYTHON, 254 | "", 255 | "print(\"Python submenu item has been called\")") 256 | sub_menu.add_menu_entry("Section01", entry) 257 | 258 | # Dynamic Section 259 | menu.add_dynamic_section("DynamicSection", PyTestDynamicSection01()) 260 | 261 | # Toolbar Button 262 | entry = MenuEntryScript01() 263 | entry.init_as_toolbar_button() 264 | entry.data.label = "PyClass" 265 | toolbar = tool_menus.extend_menu("LevelEditor.LevelEditorToolBar") 266 | toolbar.add_menu_entry_object(entry) 267 | 268 | def Run(): 269 | 270 | # Allow iterating on this menu python file without restarting editor 271 | tool_menus.unregister_owner_by_name(menu_owner) 272 | 273 | menu = GetMainPythonMenu() 274 | AddMenuStringCommands(menu) 275 | AddToolbarStringCommands(tool_menus.extend_menu("LevelEditor.LevelEditorToolBar")) 276 | AddClassBasedMenuItems(menu) 277 | 278 | # Content Browser Asset 279 | menu = tool_menus.extend_menu("ContentBrowser.AssetContextMenu") 280 | entry = ContentBrowserAssetContextMenuExample() 281 | entry.init_entry(menu_owner, "ContentBrowser.AssetContextMenu", "CommonAssetActions", "PythonExample", "Python Example") 282 | menu.add_menu_entry_object(entry) 283 | 284 | # Content Browser Folder 285 | menu = tool_menus.extend_menu("ContentBrowser.FolderContextMenu") 286 | entry = ContentBrowserFolderContextMenuExample() 287 | entry.init_entry(menu_owner, "ContentBrowser.FolderContextMenu", "PathViewFolderOptions", "PythonExample", "Python Example") 288 | menu.add_menu_entry_object(entry) 289 | 290 | # Asset Editor 291 | menu = tool_menus.extend_menu("AssetEditor.TextureEditor.MainMenu.File") 292 | entry = AssetEditorMenuExample() 293 | entry.init_entry(menu_owner, "AssetEditor.TextureEditor.MainMenu.File", "Example", "PythonExample", "Python Example") 294 | menu.add_menu_entry_object(entry) 295 | 296 | tool_menus.refresh_all_widgets() 297 | 298 | Run() 299 | -------------------------------------------------------------------------------- /scripts/Misc/Register_your_python_code_to_run_at_a_later_time.py: -------------------------------------------------------------------------------- 1 | 2 | import unreal 3 | 4 | tickhandle = None 5 | 6 | def testRegistry(deltaTime): 7 | unreal.log_warning("ticking.") 8 | asset_registry = unreal.AssetRegistryHelpers.get_asset_registry() 9 | if asset_registry.is_loading_assets(): 10 | unreal.log_warning("still loading...") 11 | else: 12 | unreal.log_warning("ready!") 13 | unreal.unregister_slate_pre_tick_callback(tickhandle) 14 | 15 | tickhandle = unreal.register_slate_pre_tick_callback(testRegistry) -------------------------------------------------------------------------------- /scripts/Misc/TakeHighResScreenshot_from_all_cam.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | from collections import deque 3 | from collections.abc import Iterable 4 | 5 | class PyTick(): 6 | 7 | _delegate_handle = None 8 | _current = None 9 | schedule = None 10 | 11 | def __init__(self): 12 | self.schedule = deque() 13 | self._delegate_handle = unreal.register_slate_post_tick_callback(self._callback) 14 | 15 | def _callback(self, _): 16 | if self._current is None: 17 | if self.schedule: 18 | self._current = self.schedule.popleft() 19 | 20 | else: 21 | print ('Done jobs') 22 | unreal.unregister_slate_post_tick_callback(self._delegate_handle) 23 | return 24 | 25 | try: 26 | task = next(self._current) 27 | 28 | if task is not None and isinstance(task, Iterable): 29 | # reschedule current task, and do the new one 30 | self.schedule.appendleft(self._current) 31 | self._current = task 32 | 33 | except StopIteration: 34 | self._current = None 35 | 36 | except: 37 | self._current = None 38 | raise 39 | 40 | def take_all_cam_screenshots(): 41 | 42 | actorSubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 43 | level_actors = actorSubsystem.get_all_level_actors() 44 | all_cameras = unreal.EditorFilterLibrary.by_class( 45 | level_actors, 46 | unreal.CameraActor 47 | ) 48 | 49 | for cam in all_cameras: 50 | camera_name = cam.get_actor_label() 51 | unreal.AutomationLibrary.take_high_res_screenshot(1280, 720, camera_name, cam) 52 | print ('Requested screenshot for '+ camera_name ) 53 | yield 54 | 55 | py_tick = PyTick() 56 | py_tick.schedule.append(take_all_cam_screenshots()) 57 | -------------------------------------------------------------------------------- /scripts/Misc/Validate_pointLight.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | actorSubsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) 4 | gl_level_actors = actorSubsystem.get_all_level_actors() 5 | 6 | 7 | def getActor(actorName): 8 | filtered_list = unreal.EditorFilterLibrary.by_actor_label( 9 | gl_level_actors, 10 | actorName, 11 | unreal.EditorScriptingStringMatchType.EXACT_MATCH 12 | ) 13 | 14 | if len(filtered_list) == 0: 15 | unreal.log_warning('Did not find any actor with label: "{}"'.format(actorName)) 16 | if len(filtered_list) > 1: 17 | unreal.log_warning('More then one actor with label: "{}"'.format(actorName)) 18 | return filtered_list 19 | 20 | 21 | myActor = getActor('TPhotometricLight_UniforDiffuse')[0] 22 | my_light_component = myActor.light_component 23 | # Usual Python assert 24 | assert my_light_component.intensity_units.name == 'LUMENS', 'Wrong light intensity units' 25 | 26 | # or use unreal log error msgs 27 | if not my_light_component.intensity_units.name == 'LUMENS': 28 | unreal.log_error('Wrong light intensity units') -------------------------------------------------------------------------------- /scripts/Misc/tick_in_python.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | from collections import deque 3 | from collections.abc import Iterable 4 | 5 | class PyTick(): 6 | 7 | _delegate_handle = None 8 | _current = None 9 | schedule = None 10 | 11 | def __init__(self): 12 | self.schedule = deque() 13 | self._delegate_handle = unreal.register_slate_post_tick_callback(self._callback) 14 | 15 | def _callback(self, _): 16 | if self._current is None: 17 | if self.schedule: 18 | self._current = self.schedule.popleft() 19 | 20 | else: 21 | print ('Done jobs') 22 | unreal.unregister_slate_post_tick_callback(self._delegate_handle) 23 | return 24 | 25 | try: 26 | task = next(self._current) 27 | 28 | if task is not None and isinstance(task, Iterable): 29 | # reschedule current task, and do the new one 30 | self.schedule.appendleft(self._current) 31 | self._current = task 32 | 33 | except StopIteration: 34 | self._current = None 35 | 36 | except: 37 | self._current = None 38 | raise 39 | 40 | def my_loop(): 41 | for i in range(10): 42 | print ('Tick A %s'% i) 43 | yield my_inner_loop() # chain another task inside 44 | 45 | def my_inner_loop(): 46 | for i in range(2): 47 | print ('Tick B %s'% i) 48 | yield 49 | 50 | py_tick = PyTick() 51 | py_tick.schedule.append(my_loop()) --------------------------------------------------------------------------------