├── .gitignore ├── .gitmodules ├── Integration ├── PrismInit.py └── UE4PrismPlugins │ ├── .gitignore │ ├── Config │ └── UserEngine.ini │ ├── Content │ └── Python │ │ ├── BlueprintLibrary │ │ ├── SampleActorAction.py │ │ ├── SampleAssetAction.py │ │ ├── SampleBlueprintFunction.py │ │ └── __init__.py │ │ ├── UserInterfaces │ │ ├── SampleEditorExtension.py │ │ └── __init__.py │ │ ├── init_unreal.py │ │ ├── unreal_global.py │ │ ├── unreal_icon.py │ │ ├── unreal_startup.py │ │ ├── unreal_uiutils.py │ │ └── unreal_utils.py │ ├── LICENSE │ ├── PythonScripting.uplugin │ ├── README.md │ └── Resources │ └── Icon128.png ├── Scripts ├── Prism_UnrealEngine_Functions.py ├── Prism_UnrealEngine_Integration.py ├── Prism_UnrealEngine_Variables.py ├── Prism_UnrealEngine_externalAccess_Functions.py ├── Prism_UnrealEngine_init.py └── Prism_UnrealEngine_init_unloaded.py └── external_modules ├── remote_execution.py └── ue4.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | .venv -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external_modules/UnrealAPI"] 2 | path = external_modules/UnrealAPI 3 | url = https://github.com/josephkirk/UnrealApi 4 | branch = py2 5 | -------------------------------------------------------------------------------- /Integration/PrismInit.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | def prismInit(): 6 | prismRoot = os.getenv("PRISM_ROOT") 7 | if not prismRoot: 8 | prismRoot = PRISMROOT 9 | 10 | scriptDir = os.path.join(prismRoot, "Scripts") 11 | 12 | if scriptDir not in sys.path: 13 | sys.path.append(scriptDir) 14 | 15 | import PrismCore 16 | 17 | pcore = PrismCore.PrismCore(app="UE4") 18 | return pcore 19 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Config/UserEngine.ini: -------------------------------------------------------------------------------- 1 | [/Script/PythonScriptPlugin.PythonScriptPluginSettings] 2 | +AdditionalPaths=(Path="D:\unreal\PythonProject\Python") 3 | bDeveloperMode=True 4 | bRemoteExecution=True 5 | RemoteExecutionMulticastGroupEndpoint=239.0.0.1:6766 6 | RemoteExecutionMulticastBindAddress=0.0.0.0 7 | RemoteExecutionSendBufferSizeBytes=2097152 8 | RemoteExecutionReceiveBufferSizeBytes=2097152 9 | RemoteExecutionMulticastTtl=0 10 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Content/Python/BlueprintLibrary/SampleActorAction.py: -------------------------------------------------------------------------------- 1 | from unreal_global import * 2 | import unreal_utils 3 | import unreal 4 | 5 | 6 | @unreal.uclass() 7 | class SampleActorAction(unreal.ActorActionUtility): 8 | # @unreal.ufunction(override=True) 9 | # def get_supported_class(self): 10 | # return unreal.Actor 11 | 12 | @unreal.ufunction( 13 | static=True, 14 | meta=dict(CallInEditor=True, Category="Sample Actor Action Library"), 15 | ) 16 | def python_test_actor_action(): 17 | unreal.log("Execute Sample Actor Action") 18 | 19 | @unreal.ufunction( 20 | params=[str], 21 | static=True, 22 | meta=dict(CallInEditor=True, Category="Sample Actor Action Library"), 23 | ) 24 | def python_test_actor_action_with_parameters(input_string): 25 | unreal.log("Execute Sample Actor Action with {}".format(input_string)) 26 | 27 | 28 | # Unreal need a actual blueprint asset to be created and inherit from the new class for it to work 29 | # unreal_utils.register_editor_utility_blueprint("SampleActorUtility", SampleActorAction) 30 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Content/Python/BlueprintLibrary/SampleAssetAction.py: -------------------------------------------------------------------------------- 1 | from unreal_global import * 2 | import unreal_utils 3 | import unreal 4 | 5 | 6 | @unreal.uclass() 7 | class SamplePythonAssetAction(unreal.AssetActionUtility): 8 | # @unreal.ufunction(override=True) 9 | # def get_supported_class(self): 10 | # return unreal.StaticMesh 11 | 12 | @unreal.ufunction( 13 | static=True, 14 | meta=dict(CallInEditor=True, Category="Sample Asset Action Library"), 15 | ) 16 | def python_dump_asset_info(): 17 | selected_assets = UtilLibrary.get_selected_assets() 18 | for asset in selected_assets: 19 | unreal.log( 20 | "{} {} {}".format( 21 | asset.get_name(), asset.get_outermost(), asset.get_path_name() 22 | ) 23 | ) 24 | 25 | # @unreal.ufunction(params=[int], static=True, meta=dict(CallInEditor=True, Category="Sample Asset Action Library")) 26 | # def test_asset_action(input_num): 27 | # unreal.log("Execute Sample Asset Action with {}".format(input_num)) 28 | 29 | 30 | # Unreal need a actual blueprint asset to be created and inherit from the new class for it to work 31 | # unreal_utils.register_editor_utility_blueprint( 32 | # "SampleAssetUtility", SamplePythonAssetAction 33 | # ) 34 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Content/Python/BlueprintLibrary/SampleBlueprintFunction.py: -------------------------------------------------------------------------------- 1 | from unreal_global import * 2 | 3 | 4 | @unreal.uclass() 5 | class SamplePythonBlueprintLibrary(unreal.BlueprintFunctionLibrary): 6 | @unreal.ufunction( 7 | static=True, meta=dict(Category="Sample Blueprint Function Library") 8 | ) 9 | def python_test_bp_action_noinput(): 10 | unreal.log("Execute Bluerprint Action No Input") 11 | 12 | @unreal.ufunction( 13 | params=[int, str], 14 | static=True, 15 | meta=dict(Category="Sample Blueprint Function Library"), 16 | ) 17 | def python_test_bp_action_input(input_num, input_str): 18 | unreal.log( 19 | "Execute Bluerprint Action With Inputs {} {}".format( 20 | input_num, input_string 21 | ) 22 | ) 23 | 24 | @unreal.ufunction( 25 | ret=str, static=True, meta=dict(Category="Sample Blueprint Function Library") 26 | ) 27 | def python_test_bp_action_return(): 28 | result = "Success" 29 | unreal.log("Execute Bluerprint Action Return") 30 | return result 31 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Content/Python/BlueprintLibrary/__init__.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | from os.path import dirname, basename, isfile, join 3 | import glob 4 | import importlib 5 | import traceback 6 | import sys 7 | 8 | dir_name = dirname(__file__) 9 | dir_basename = basename(dir_name) 10 | modules = glob.glob(join(dir_name, "*.py")) 11 | __all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] 12 | 13 | unreal.log("""@ 14 | 15 | #################### 16 | 17 | Load Blueprint Library 18 | 19 | #################### 20 | """) 21 | 22 | for m in __all__: 23 | # __import__(m, locals(), globals()) 24 | try: 25 | mod = importlib.import_module("{}.{}".format(dir_basename, m)) 26 | if m in globals(): 27 | unreal.log("""@ 28 | #################### 29 | 30 | ReLoad Blueprint Library 31 | 32 | #################### 33 | """) 34 | try: 35 | reload(mod) 36 | except: 37 | importlib.reload(mod) 38 | # try: 39 | # unreal.reload(mod) 40 | # except: 41 | # unreal.log("{} is not Unreal Generated Class".format(m)) 42 | 43 | unreal.log("Successfully import {}".format(m)) 44 | except Exception as why: 45 | unreal.log_error("Fail to import {}.\n {}".format(m, why)) 46 | traceback.print_exc(file=sys.stdout) 47 | 48 | unreal.log("""@ 49 | 50 | #################### 51 | """) 52 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Content/Python/UserInterfaces/SampleEditorExtension.py: -------------------------------------------------------------------------------- 1 | # Create Virtuos Menu 2 | # Nguyen PHi Hung 3 | 4 | import unreal_uiutils 5 | from unreal_utils import AssetRegistryPostLoad 6 | import unreal 7 | 8 | 9 | def extend_editor(): 10 | # Create standard menu entry 11 | me_reloadbutton = unreal_uiutils.create_menu_button( 12 | name="ReloadBtn", 13 | label="Reload", 14 | command_string="import importlib; import unreal_startup; importlib.reload(unreal_startup); unreal_startup.reload()", 15 | ) 16 | me_quitbutton = unreal_uiutils.create_menu_button( 17 | name="QuitUnrealBtn", 18 | label="Quit Unreal", 19 | command_string="SystemLib.quit_editor()", 20 | ) 21 | 22 | # This create a button on toolboard 23 | tb_reloadbutton = unreal_uiutils.create_toolbar_button( 24 | name="ReloadBtn", 25 | label="PyReload", 26 | command_string="import importlib; import unreal_startup; importlib.reload(unreal_startup); unreal_startup.reload()", 27 | ) 28 | 29 | # This create a drop down button on toolboard 30 | tb_combobutton = unreal_uiutils.create_toolbar_combo_button( 31 | "comboBtn", "python.tools" 32 | ) 33 | #TODO: Find out where it is possible to register a new context menu for combo button 34 | 35 | # create submenu in 'File' Menu and register menu entry created above 36 | pythonsubmenu = unreal_uiutils.extend_mainmenu_item( 37 | "File", "PythonTools", "PythonTools", "Python Tools" 38 | ) 39 | pythonsubmenu.add_section("python.file.menu", "Python Tools") 40 | pythonsubmenu.add_menu_entry("python.file.menu", me_reloadbutton) 41 | pythonsubmenu.add_menu_entry("python.file.menu", me_quitbutton) 42 | 43 | # Create Standalone Menu and register menu entry created above 44 | new_mainmenu = unreal_uiutils.extend_mainmenu("Python", "Python") 45 | section = new_mainmenu.add_section("python.menu", "Python Tools") 46 | new_mainmenu.add_menu_entry("python.menu", me_reloadbutton) 47 | new_mainmenu.add_menu_entry("python.menu", me_quitbutton) 48 | new_entry = unreal.ToolMenuEntryExtensions.init_menu_entry( 49 | new_mainmenu.menu_name, 50 | "submenu", 51 | "Test Sub Menu", 52 | "nothing here", 53 | unreal.ToolMenuStringCommandType.COMMAND, 54 | "command", 55 | "", 56 | ) 57 | print(f"Script Object: {new_entry.script_object}") 58 | 59 | # Extend Asset Context Menu 60 | sub_asset_context_menu = unreal_uiutils.extend_toolmenu(unreal_uiutils.get_asset_context_menu(), "PythonAssetContextMenu", "Python Asset Actions") 61 | sub_asset_context_menu.add_section("python.assetmenu", "Tools") 62 | action_sampleassetprint = unreal_uiutils.create_menu_button( 63 | name="SampleAssetTool", 64 | label="Print Selected Assets", 65 | command_string='print(UtilLibrary.get_selected_assets())', 66 | ) 67 | sub_asset_context_menu.add_menu_entry("python.assetmenu", action_sampleassetprint) 68 | 69 | # Extend Asset Context Menu - Only show for Level Sequence Asset 70 | sequence_contextmenu = unreal_uiutils.get_sequence_asset_context_menu() 71 | sub_sequence_context_menu = unreal_uiutils.extend_toolmenu(sequence_contextmenu, "PythonSequenceContextMenu", "Python Sequence Actions") 72 | sub_sequence_context_menu.add_section("python.sequencemenu", "Tools") 73 | action_samplesequenceprint = unreal_uiutils.create_menu_button( 74 | name="SampleSequenceTool", 75 | label="Print Selected Assets", 76 | command_string='print(UtilLibrary.get_selected_assets())', 77 | ) 78 | sub_sequence_context_menu.add_menu_entry("python.sequencemenu", action_samplesequenceprint) 79 | 80 | # Extend Actor Context Menu 81 | actor_context_menu = unreal_uiutils.get_actor_context_menu() 82 | actor_context_menu.add_section("python.actoractions", "Python Tools") 83 | #!NOTE: The submenu can only be seen when right click in viewport and not the outliner 84 | sub_actor_context_menu = actor_context_menu.add_sub_menu(actor_context_menu.menu_name, "python.actoractions", "PythonActorContextMenu", "Python Actor Actions") 85 | # is_sub_actor_context_menu_registered = unreal.ToolMenus.get().is_menu_registered(sub_actor_context_menu.menu_name) 86 | # if not is_sub_actor_context_menu_registered: 87 | # unreal.ToolMenus.get().register_menu(sub_actor_context_menu.menu_name, actor_context_menu.menu_name) 88 | # print("{} - is registered {}".format(sub_actor_context_menu.menu_name, is_sub_actor_context_menu_registered)) 89 | sub_actor_context_menu.add_section("python.actormenu", "Tools") 90 | action_sampleactorprint = unreal_uiutils.create_menu_button( 91 | name="SampleActorTool", 92 | label="Print Selected Actor", 93 | command_string='print(LevelLibrary.get_selected_level_actors())', 94 | ) 95 | sub_actor_context_menu.add_menu_entry("python.actormenu", action_sampleactorprint) 96 | # actor_context_menu.add_menu_entry("python.actoractions", action_sampleactorprint) 97 | 98 | # AssetRegistryPostLoad.register_callback(extend_editor) 99 | extend_editor() -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Content/Python/UserInterfaces/__init__.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | import sys 3 | from os.path import dirname, basename, isfile, join 4 | import glob 5 | import importlib 6 | import traceback 7 | 8 | dir_name = dirname(__file__) 9 | dir_basename = basename(dir_name) 10 | modules = glob.glob(join(dir_name, "*.py")) 11 | __all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] 12 | 13 | unreal.log("""@ 14 | 15 | #################### 16 | 17 | Load UI Library 18 | 19 | #################### 20 | """) 21 | for m in __all__: 22 | # __import__(m, locals(), globals()) 23 | try: 24 | mod = importlib.import_module("{}.{}".format(dir_basename, m)) 25 | if m in globals(): 26 | unreal.log("""@ 27 | #################### 28 | 29 | ReLoad UI Library 30 | 31 | #################### 32 | """) 33 | try: 34 | reload(mod) 35 | except: 36 | importlib.reload(mod) 37 | unreal.log("Successfully import {}".format(m)) 38 | except Exception as why: 39 | unreal.log_error("Fail to import {}.\n {}".format(m, why)) 40 | traceback.print_exc(file=sys.stdout) 41 | unreal.log("""@ 42 | 43 | #################### 44 | """) 45 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Content/Python/init_unreal.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | from unreal_global import * 3 | 4 | unreal.log("""@ 5 | 6 | #################### 7 | 8 | Unreal Init Script 9 | 10 | #################### 11 | 12 | """) 13 | 14 | import unreal_startup -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Content/Python/unreal_global.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | 3 | AssetRegistry = unreal.AssetRegistryHelpers.get_asset_registry() 4 | 5 | UtilLibrary = unreal.EditorUtilityLibrary 6 | """ 7 | rename_asset(asset, new_name) -> None 8 | get_selection_set() -> Array(Actor) 9 | get_selection_bounds() -> (origin=Vector, box_extent=Vector, sphere_radius=float) 10 | get_selected_blueprint_classes() -> Array(type(Class)) 11 | get_selected_assets() -> Array(Object) 12 | get_selected_asset_data() -> Array(AssetData) 13 | get_actor_reference(path_to_actor) -> Actor 14 | """ 15 | 16 | AutomationScheduler = unreal.AutomationScheduler 17 | 18 | AssetTools = unreal.AssetToolsHelpers.get_asset_tools() 19 | """ 20 | rename_referencing_soft_object_paths(packages_to_check, asset_redirector_map) 21 | 22 | rename_assets_with_dialog(assets_and_names, auto_checkout=False) 23 | 24 | rename_assets(assets_and_names) 25 | 26 | open_editor_for_assets(assets) 27 | 28 | import_asset_tasks(import_tasks) 29 | 30 | import_assets_with_dialog(destination_path) 31 | 32 | import_assets_automated(import_data) 33 | 34 | find_soft_references_to_object(target_object) 35 | 36 | export_assets_with_dialog(assets_to_export, prompt_for_individual_filenames) 37 | 38 | export_assets(assets_to_export, export_path) 39 | 40 | duplicate_asset_with_dialog_and_title(asset_name, package_path, original_object, dialog_title) 41 | 42 | duplicate_asset_with_dialog(asset_name, package_path, original_object) 43 | 44 | duplicate_asset(asset_name, package_path, original_object) 45 | 46 | create_unique_asset_name(base_package_name, suffix) 47 | 48 | create_asset_with_dialog(asset_name, package_path, asset_class, factory, calling_context="None") 49 | 50 | create_asset(asset_name, package_path, asset_class, factory, calling_context="None") 51 | """ 52 | 53 | AssetLibrary = unreal.EditorAssetLibrary 54 | """ 55 | sync_browser_to_objects(cls, asset_paths) 56 | 57 | set_metadata_tag(cls, object, tag, value) 58 | 59 | save_loaded_assets(cls, assets_to_save, only_if_is_dirty=True) 60 | 61 | save_loaded_asset(cls, asset_to_save, only_if_is_dirty=True) 62 | 63 | save_directory(cls, directory_path, only_if_is_dirty=True, recursive=True) 64 | 65 | save_asset(cls, asset_to_save, only_if_is_dirty=True) 66 | 67 | rename_loaded_asset(cls, source_asset, destination_asset_path) 68 | 69 | rename_directory(cls, source_directory_path, destination_directory_path) 70 | 71 | rename_asset(cls, source_asset_path, destination_asset_path) 72 | 73 | remove_metadata_tag(cls, object, tag) 74 | 75 | make_directory(cls, directory_path) 76 | 77 | load_blueprint_class(cls, asset_path) 78 | 79 | load_asset(cls, asset_path) 80 | 81 | list_assets(cls, directory_path, recursive=True, include_folder=False) 82 | 83 | list_asset_by_tag_value(cls, tag_name, tag_value) 84 | 85 | get_tag_values(cls, asset_path) 86 | 87 | get_path_name_for_loaded_asset(cls, loaded_asset) 88 | 89 | get_metadata_tag_values(cls, object) 90 | 91 | get_metadata_tag(cls, object, tag) 92 | 93 | find_package_referencers_for_asset(cls, asset_path, load_assets_to_confirm=False) 94 | 95 | find_asset_data(cls, asset_path) 96 | 97 | duplicate_loaded_asset(cls, source_asset, destination_asset_path) 98 | 99 | duplicate_directory(cls, source_directory_path, destination_directory_path) 100 | 101 | duplicate_asset(cls, source_asset_path, destination_asset_path) 102 | 103 | does_directory_have_assets(cls, directory_path, recursive=True) 104 | 105 | does_directory_exist(cls, directory_path) 106 | 107 | does_asset_exist(cls, asset_path) 108 | 109 | do_assets_exist(cls, asset_paths) 110 | 111 | delete_loaded_assets(cls, assets_to_delete) 112 | 113 | delete_loaded_asset(cls, asset_to_delete) 114 | 115 | delete_directory(cls, directory_path) 116 | 117 | delete_asset(cls, asset_path_to_delete) 118 | 119 | consolidate_assets(cls, asset_to_consolidate_to, assets_to_consolidate) 120 | 121 | checkout_loaded_assets(cls, assets_to_checkout) 122 | 123 | checkout_loaded_asset(cls, asset_to_checkout) 124 | 125 | checkout_directory(cls, directory_path, recursive=True) 126 | 127 | checkout_asset(cls, asset_to_checkout) 128 | """ 129 | 130 | FilterLibrary = unreal.EditorFilterLibrary 131 | """ 132 | Utility class to filter a list of objects. Object should be in the World Editor. 133 | 134 | by_selection(cls, target_array, filter_type=EditorScriptingFilterType.INCLUDE) 135 | 136 | by_level_name(cls, target_array, level_name, filter_type=EditorScriptingFilterType.INCLUDE) 137 | 138 | by_layer(cls, target_array, layer_name, filter_type=EditorScriptingFilterType.INCLUDE) 139 | 140 | by_id_name(cls, target_array, name_sub_string, string_match=EditorScriptingStringMatchType.CONTAINS, filter_type=EditorScriptingFilterType.INCLUDE) 141 | 142 | by_class(cls, target_array, object_class, filter_type=EditorScriptingFilterType.INCLUDE) 143 | 144 | by_actor_tag(cls, target_array, tag, filter_type=EditorScriptingFilterType.INCLUDE) 145 | 146 | by_actor_label(cls, target_array, name_sub_string, string_match=EditorScriptingStringMatchType.CONTAINS, filter_type=EditorScriptingFilterType.INCLUDE, ignore_case=True) 147 | 148 | """ 149 | 150 | AutomationLibrary = unreal.AutomationLibrary 151 | """ 152 | take_high_res_screenshot(cls, res_x, res_y, filename, camera=None, mask_enabled=False, capture_hdr=False, comparison_tolerance=ComparisonTolerance.LOW, comparison_notes="") 153 | 154 | take_automation_screenshot_of_ui(cls, world_context_object, latent_info, name, options) 155 | 156 | take_automation_screenshot_at_camera(cls, world_context_object, latent_info, camera, name_override, notes, options) 157 | 158 | take_automation_screenshot(cls, world_context_object, latent_info, name, notes, options) 159 | 160 | set_scalability_quality_to_low(cls, world_context_object) 161 | 162 | set_scalability_quality_to_epic(cls, world_context_object) 163 | 164 | set_scalability_quality_level_relative_to_max(cls, world_context_object, value=1) 165 | 166 | get_stat_inc_max(cls, stat_name) 167 | 168 | get_stat_inc_average(cls, stat_name) 169 | 170 | get_stat_exc_max(cls, stat_name) 171 | 172 | get_stat_exc_average(cls, stat_name) 173 | 174 | get_stat_call_count(cls, stat_name) 175 | 176 | get_default_screenshot_options_for_rendering(cls, tolerance=ComparisonTolerance.LOW, delay=0.200000) 177 | 178 | get_default_screenshot_options_for_gameplay(cls, tolerance=ComparisonTolerance.LOW, delay=0.200000) 179 | 180 | enable_stat_group(cls, world_context_object, group_name) 181 | 182 | disable_stat_group(cls, world_context_object, group_name) 183 | 184 | automation_wait_for_loading(cls, world_context_object, latent_info) 185 | 186 | are_automated_tests_running(cls) 187 | 188 | add_expected_log_error(cls, expected_pattern_string, occurrences=1, exact_match=False) 189 | """ 190 | 191 | LevelLibrary = unreal.EditorLevelLibrary 192 | """ 193 | spawn_actor_from_object(object_to_use, location, rotation=[0.000000, 0.000000, 0.000000]) -> Actor 194 | spawn_actor_from_class(actor_class, location, rotation=[0.000000, 0.000000, 0.000000]) -> Actor 195 | set_selected_level_actors(actors_to_select) -> None 196 | set_level_viewport_camera_info(camera_location, camera_rotation) -> None 197 | set_current_level_by_name(level_name) -> bool 198 | set_actor_selection_state(actor, should_be_selected) -> None 199 | select_nothing() -> None 200 | save_current_level() -> bool 201 | save_all_dirty_levels() -> bool 202 | replace_mesh_components_meshes_on_actors(actors, mesh_to_be_replaced, new_mesh) -> None 203 | replace_mesh_components_meshes(mesh_components, mesh_to_be_replaced, new_mesh) -> None 204 | replace_mesh_components_materials_on_actors(actors, material_to_be_replaced, new_material) -> None 205 | replace_mesh_components_materials(mesh_components, material_to_be_replaced, new_material) -> None 206 | pilot_level_actor(actor_to_pilot) -> None 207 | new_level_from_template(asset_path, template_asset_path) -> bool 208 | new_level(asset_path) -> bool 209 | merge_static_mesh_actors(actors_to_merge, merge_options) -> StaticMeshActor or None 210 | load_level(asset_path) -> bool 211 | join_static_mesh_actors(actors_to_join, join_options) -> Actor 212 | get_selected_level_actors() -> Array(Actor) 213 | get_level_viewport_camera_info() -> (camera_location=Vector, camera_rotation=Rotator) or None 214 | get_game_world() -> World 215 | get_editor_world() -> World 216 | get_all_level_actors_components() -> Array(ActorComponent) 217 | get_all_level_actors() -> Array(Actor) 218 | get_actor_reference(path_to_actor) -> Actor 219 | eject_pilot_level_actor() -> None 220 | editor_set_game_view(game_view) -> None 221 | editor_play_simulate() -> None 222 | editor_invalidate_viewports() -> None 223 | destroy_actor(actor_to_destroy) -> bool 224 | create_proxy_mesh_actor(actors_to_merge, merge_options) -> StaticMeshActor or None 225 | convert_actors(actors, actor_class, static_mesh_package_path) -> Array(Actor) 226 | clear_actor_selection_set() -> None 227 | """ 228 | 229 | SequenceBindingLibrary = unreal.MovieSceneBindingExtensions 230 | """ 231 | set_parent(binding, parent_binding) -> None 232 | remove_track(binding, track_to_remove) -> None 233 | remove(binding) -> None 234 | is_valid(binding) -> bool 235 | get_tracks(binding) -> Array(MovieSceneTrack) 236 | get_possessed_object_class(binding) -> type(Class) 237 | get_parent(binding) -> SequencerBindingProxy 238 | get_object_template(binding) -> Object 239 | get_name(binding) -> str 240 | get_id(binding) -> Guid 241 | get_display_name(binding) -> Text 242 | get_child_possessables(binding) -> Array(SequencerBindingProxy) 243 | find_tracks_by_type(binding, track_type) -> Array(MovieSceneTrack) 244 | find_tracks_by_exact_type(binding, track_type) -> Array(MovieSceneTrack) 245 | add_track(binding, track_type) -> MovieSceneTrack 246 | """ 247 | 248 | SequenceFolderLibrary = unreal.MovieSceneFolderExtensions 249 | """ 250 | set_folder_name(folder, folder_name) -> bool 251 | set_folder_color(folder, folder_color) -> bool 252 | remove_child_object_binding(folder, object_binding) -> bool 253 | remove_child_master_track(folder, master_track) -> bool 254 | remove_child_folder(target_folder, folder_to_remove) -> bool 255 | get_folder_name(folder) -> Name 256 | get_folder_color(folder) -> Color 257 | get_child_object_bindings(folder) -> Array(SequencerBindingProxy) 258 | get_child_master_tracks(folder) -> Array(MovieSceneTrack) 259 | get_child_folders(folder) -> Array(MovieSceneFolder) 260 | add_child_object_binding(folder, object_binding) -> bool 261 | add_child_master_track(folder, master_track) -> bool 262 | add_child_folder(target_folder, folder_to_add) -> bool 263 | """ 264 | 265 | SequencePropertyLibrary = unreal.MovieScenePropertyTrackExtensions 266 | """ 267 | X.set_property_name_and_path(track, property_name, property_path) -> None 268 | X.set_object_property_class(track, property_class) -> None 269 | X.get_unique_track_name(track) -> Name 270 | X.get_property_path(track) -> str 271 | X.get_property_name(track) -> Name 272 | X.get_object_property_class(track) -> type(Class) 273 | """ 274 | 275 | SequenceSectionLibrary = unreal.MovieSceneSectionExtensions 276 | """ 277 | set_start_frame_seconds(section, start_time) -> None 278 | set_start_frame_bounded(section, is_bounded) -> None 279 | set_start_frame(section, start_frame) -> None 280 | set_range_seconds(section, start_time, end_time) -> None 281 | set_range(section, start_frame, end_frame) -> None 282 | set_end_frame_seconds(section, end_time) -> None 283 | set_end_frame_bounded(section, is_bounded) -> None 284 | set_end_frame(section, end_frame) -> None 285 | get_start_frame_seconds(section) -> float 286 | get_start_frame(section) -> int32 287 | get_parent_sequence_frame(section, frame, parent_sequence) -> int32 288 | get_end_frame_seconds(section) -> float 289 | get_end_frame(section) -> int32 290 | get_channels(section) -> Array(MovieSceneScriptingChannel) 291 | find_channels_by_type(section, channel_type) -> Array(MovieSceneScriptingChannel) 292 | """ 293 | SequenceLibrary = unreal.MovieSceneSectionExtensions 294 | """ 295 | set_work_range_start(sequence, start_time_in_seconds) -> None 296 | set_work_range_end(sequence, end_time_in_seconds) -> None 297 | set_view_range_start(sequence, start_time_in_seconds) -> None 298 | set_view_range_end(sequence, end_time_in_seconds) -> None 299 | set_tick_resolution(sequence, tick_resolution) -> None 300 | set_read_only(sequence, read_only) -> None 301 | set_playback_start_seconds(sequence, start_time) -> None 302 | set_playback_start(sequence, start_frame) -> None 303 | set_playback_end_seconds(sequence, end_time) -> None 304 | set_playback_end(sequence, end_frame) -> None 305 | set_display_rate(sequence, display_rate) -> None 306 | make_range_seconds(sequence, start_time, duration) -> SequencerScriptingRange 307 | make_range(sequence, start_frame, duration) -> SequencerScriptingRange 308 | make_binding_id(master_sequence, binding, space=MovieSceneObjectBindingSpace.ROOT) -> MovieSceneObjectBindingID 309 | locate_bound_objects(sequence, binding, context) -> Array(Object) 310 | is_read_only(sequence) -> bool 311 | get_work_range_start(sequence) -> float 312 | get_work_range_end(sequence) -> float 313 | get_view_range_start(sequence) -> float 314 | get_view_range_end(sequence) -> float 315 | get_timecode_source(sequence) -> Timecode 316 | get_tick_resolution(sequence) -> FrameRate 317 | get_spawnables(sequence) -> Array(SequencerBindingProxy) 318 | get_root_folders_in_sequence(sequence) -> Array(MovieSceneFolder) 319 | get_possessables(sequence) -> Array(SequencerBindingProxy) 320 | get_playback_start_seconds(sequence) -> float 321 | get_playback_start(sequence) -> int32 322 | get_playback_range(sequence) -> SequencerScriptingRange 323 | get_playback_end_seconds(sequence) -> float 324 | get_playback_end(sequence) -> int32 325 | get_movie_scene(sequence) -> MovieScene 326 | get_master_tracks(sequence) -> Array(MovieSceneTrack) 327 | get_marked_frames(sequence) -> Array(MovieSceneMarkedFrame) 328 | get_display_rate(sequence) -> FrameRate 329 | get_bindings(sequence) -> Array(SequencerBindingProxy) 330 | find_next_marked_frame(sequence, frame_number, forward) -> int32 331 | find_master_tracks_by_type(sequence, track_type) -> Array(MovieSceneTrack) 332 | find_master_tracks_by_exact_type(sequence, track_type) -> Array(MovieSceneTrack) 333 | find_marked_frame_by_label(sequence, label) -> int32 334 | find_marked_frame_by_frame_number(sequence, frame_number) -> int32 335 | find_binding_by_name(sequence, name) -> SequencerBindingProxy 336 | delete_marked_frames(sequence) -> None 337 | delete_marked_frame(sequence, delete_index) -> None 338 | add_spawnable_from_instance(sequence, object_to_spawn) -> SequencerBindingProxy 339 | add_spawnable_from_class(sequence, class_to_spawn) -> SequencerBindingProxy 340 | add_root_folder_to_sequence(sequence, new_folder_name) -> MovieSceneFolder 341 | add_possessable(sequence, object_to_possess) -> SequencerBindingProxy 342 | add_master_track(sequence, track_type) -> MovieSceneTrack 343 | add_marked_frame(sequence, marked_frame) -> int32 344 | """ 345 | 346 | SequenceTrackLibrary = unreal.MovieSceneTrackExtensions 347 | """ 348 | remove_section(track, section) -> None 349 | get_sections(track) -> Array(MovieSceneSection) 350 | get_display_name(track) -> Text 351 | add_section(track) -> MovieSceneSection 352 | """ 353 | 354 | SequenceTools = unreal.SequencerTools 355 | """ 356 | render_movie(capture_settings, on_finished_callback) -> bool 357 | is_rendering_movie() -> bool 358 | import_fbx(world, sequence, bindings, import_fbx_settings, import_filename) -> bool 359 | get_object_bindings(world, sequence, object, range) -> Array(SequencerBoundObjects) 360 | get_bound_objects(world, sequence, bindings, range) -> Array(SequencerBoundObjects) 361 | export_fbx(world, sequence, bindings, override_options, fbx_file_name) -> bool 362 | export_anim_sequence(world, sequence, anim_sequence, binding) -> bool 363 | cancel_movie_render() -> None 364 | """ 365 | 366 | LevelSequenceLibrary = unreal.LevelSequenceEditorBlueprintLibrary 367 | """ 368 | set_lock_level_sequence(lock) -> None 369 | set_current_time(new_frame) -> None 370 | play() -> None 371 | pause() -> None 372 | open_level_sequence(level_sequence) -> bool 373 | is_playing() -> bool 374 | is_level_sequence_locked() -> bool 375 | get_current_time() -> int32 376 | get_current_level_sequence() -> LevelSequence 377 | close_level_sequence() -> None 378 | """ 379 | 380 | PathLib = unreal.Paths 381 | """ 382 | video_capture_dir() -> str 383 | validate_path(path) -> (did_succeed=bool, out_reason=Text) 384 | split(path) -> (path_part=str, filename_part=str, extension_part=str) 385 | source_config_dir() -> str 386 | should_save_to_user_dir() -> bool 387 | shader_working_dir() -> str 388 | set_project_file_path(new_game_project_file_path) -> None 389 | set_extension(path, new_extension) -> str 390 | screen_shot_dir() -> str 391 | sandboxes_dir() -> str 392 | root_dir() -> str 393 | remove_duplicate_slashes(path) -> str 394 | project_user_dir() -> str 395 | project_saved_dir() -> str 396 | project_plugins_dir() -> str 397 | project_persistent_download_dir() -> str 398 | project_mods_dir() -> str 399 | project_log_dir() -> str 400 | project_intermediate_dir() -> str 401 | project_dir() -> str 402 | project_content_dir() -> str 403 | project_config_dir() -> str 404 | profiling_dir() -> str 405 | normalize_filename(path) -> str 406 | normalize_directory_name(path) -> str 407 | make_valid_file_name(string, replacement_char="") -> str 408 | make_standard_filename(path) -> str 409 | make_platform_filename(path) -> str 410 | make_path_relative_to(path, relative_to) -> str or None 411 | launch_dir() -> str 412 | is_same_path(path_a, path_b) -> bool 413 | is_restricted_path(path) -> bool 414 | is_relative(path) -> bool 415 | is_project_file_path_set() -> bool 416 | is_drive(path) -> bool 417 | has_project_persistent_download_dir() -> bool 418 | get_tool_tip_localization_paths() -> Array(str) 419 | get_restricted_folder_names() -> Array(str) 420 | get_relative_path_to_root() -> str 421 | get_property_name_localization_paths() -> Array(str) 422 | get_project_file_path() -> str 423 | get_path(path) -> str 424 | get_invalid_file_system_chars() -> str 425 | get_game_localization_paths() -> Array(str) 426 | get_extension(path, include_dot=False) -> str 427 | get_engine_localization_paths() -> Array(str) 428 | get_editor_localization_paths() -> Array(str) 429 | get_clean_filename(path) -> str 430 | get_base_filename(path, remove_path=True) -> str 431 | generated_config_dir() -> str 432 | game_user_developer_dir() -> str 433 | game_source_dir() -> str 434 | game_developers_dir() -> str 435 | game_agnostic_saved_dir() -> str 436 | file_exists(path) -> bool 437 | feature_pack_dir() -> str 438 | enterprise_plugins_dir() -> str 439 | enterprise_feature_pack_dir() -> str 440 | enterprise_dir() -> str 441 | engine_version_agnostic_user_dir() -> str 442 | engine_user_dir() -> str 443 | engine_source_dir() -> str 444 | engine_saved_dir() -> str 445 | engine_plugins_dir() -> str 446 | engine_intermediate_dir() -> str 447 | engine_dir() -> str 448 | engine_content_dir() -> str 449 | engine_config_dir() -> str 450 | directory_exists(path) -> bool 451 | diff_dir() -> str 452 | create_temp_filename(path, prefix="", extension=".tmp") -> str 453 | convert_to_sandbox_path(path, sandbox_name) -> str 454 | convert_relative_path_to_full(path, base_path="") -> str 455 | convert_from_sandbox_path(path, sandbox_name) -> str 456 | combine(paths) -> str 457 | collapse_relative_directories(path) -> str or None 458 | cloud_dir() -> str 459 | change_extension(path, new_extension) -> str 460 | bug_it_dir() -> str 461 | automation_transient_dir() -> str 462 | automation_log_dir() -> str 463 | automation_dir() -> str 464 | """ 465 | 466 | SystemLib = unreal.SystemLibrary 467 | """ 468 | unregister_for_remote_notifications() -> None 469 | unload_primary_asset_list(primary_asset_id_list) -> None 470 | unload_primary_asset(primary_asset_id) -> None 471 | transact_object(object) -> None 472 | sphere_trace_single_for_objects(world_context_object, start, end, radius, object_types, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> HitResult or None 473 | sphere_trace_single_by_profile(world_context_object, start, end, radius, profile_name, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> HitResult or None 474 | sphere_trace_single(world_context_object, start, end, radius, trace_channel, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> HitResult or None 475 | sphere_trace_multi_for_objects(world_context_object, start, end, radius, object_types, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> Array(HitResult) or None 476 | sphere_trace_multi_by_profile(world_context_object, start, end, radius, profile_name, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> Array(HitResult) or None 477 | sphere_trace_multi(world_context_object, start, end, radius, trace_channel, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> Array(HitResult) or None 478 | sphere_overlap_components(world_context_object, sphere_pos, sphere_radius, object_types, component_class_filter, actors_to_ignore) -> Array(PrimitiveComponent) or None 479 | sphere_overlap_actors(world_context_object, sphere_pos, sphere_radius, object_types, actor_class_filter, actors_to_ignore) -> Array(Actor) or None 480 | snapshot_object(object) -> None 481 | show_platform_specific_leaderboard_screen(category_name) -> None 482 | show_platform_specific_achievements_screen(specific_player) -> None 483 | show_interstitial_ad() -> None 484 | show_ad_banner(ad_id_index, show_on_bottom_of_screen) -> None 485 | set_window_title(title) -> None 486 | set_volume_buttons_handled_by_system(enabled) -> None 487 | set_user_activity(user_activity) -> None 488 | set_suppress_viewport_transition_message(world_context_object, state) -> None 489 | set_gamepads_block_device_feedback(block) -> None 490 | retriggerable_delay(world_context_object, duration, latent_info) -> None 491 | reset_gamepad_assignment_to_controller(controller_id) -> None 492 | reset_gamepad_assignments() -> None 493 | register_for_remote_notifications() -> None 494 | quit_game(world_context_object, specific_player, quit_preference, ignore_platform_restrictions) -> None 495 | quit_editor() -> None 496 | print_text(world_context_object, text="Hello", print_to_screen=True, print_to_log=True, text_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=2.000000) -> None 497 | print_string(world_context_object, string="Hello", print_to_screen=True, print_to_log=True, text_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=2.000000) -> None 498 | not_equal_soft_object_reference(a, b) -> bool 499 | not_equal_soft_class_reference(a, b) -> bool 500 | not_equal_primary_asset_type(a, b) -> bool 501 | not_equal_primary_asset_id(a, b) -> bool 502 | normalize_filename(filename) -> str 503 | move_component_to(component, target_relative_location, target_relative_rotation, ease_out, ease_in, over_time, force_shortest_rotation_path, move_action, latent_info) -> None 504 | make_literal_text(value) -> Text 505 | make_literal_string(value) -> str 506 | make_literal_name(value) -> Name 507 | make_literal_int(value) -> int32 508 | make_literal_float(value) -> float 509 | make_literal_byte(value) -> uint8 510 | make_literal_bool(value) -> bool 511 | load_interstitial_ad(ad_id_index) -> None 512 | load_class_asset_blocking(asset_class) -> type(Class) 513 | load_asset_blocking(asset) -> Object 514 | line_trace_single_for_objects(world_context_object, start, end, object_types, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> HitResult or None 515 | line_trace_single_by_profile(world_context_object, start, end, profile_name, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> HitResult or None 516 | line_trace_single(world_context_object, start, end, trace_channel, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> HitResult or None 517 | line_trace_multi_for_objects(world_context_object, start, end, object_types, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> Array(HitResult) or None 518 | line_trace_multi_by_profile(world_context_object, start, end, profile_name, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> Array(HitResult) or None 519 | line_trace_multi(world_context_object, start, end, trace_channel, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> Array(HitResult) or None 520 | launch_url(url) -> None 521 | un_pause_timer_handle(world_context_object, handle) -> None 522 | un_pause_timer_delegate(delegate) -> None 523 | un_pause_timer(object, function_name) -> None 524 | timer_exists_handle(world_context_object, handle) -> bool 525 | timer_exists_delegate(delegate) -> bool 526 | timer_exists(object, function_name) -> bool 527 | set_timer_delegate(delegate, time, looping, initial_start_delay=0.000000, initial_start_delay_variance=0.000000) -> TimerHandle 528 | set_timer(object, function_name, time, looping, initial_start_delay=0.000000, initial_start_delay_variance=0.000000) -> TimerHandle 529 | pause_timer_handle(world_context_object, handle) -> None 530 | pause_timer_delegate(delegate) -> None 531 | pause_timer(object, function_name) -> None 532 | is_valid_timer_handle(handle) -> bool 533 | is_timer_paused_handle(world_context_object, handle) -> bool 534 | is_timer_paused_delegate(delegate) -> bool 535 | is_timer_paused(object, function_name) -> bool 536 | is_timer_active_handle(world_context_object, handle) -> bool 537 | is_timer_active_delegate(delegate) -> bool 538 | is_timer_active(object, function_name) -> bool 539 | invalidate_timer_handle(handle) -> (TimerHandle, handle=TimerHandle) 540 | get_timer_remaining_time_handle(world_context_object, handle) -> float 541 | get_timer_remaining_time_delegate(delegate) -> float 542 | get_timer_remaining_time(object, function_name) -> float 543 | get_timer_elapsed_time_handle(world_context_object, handle) -> float 544 | get_timer_elapsed_time_delegate(delegate) -> float 545 | get_timer_elapsed_time(object, function_name) -> float 546 | clear_timer_handle(world_context_object, handle) -> None 547 | clear_timer_delegate(delegate) -> None 548 | clear_timer(object, function_name) -> None 549 | clear_and_invalidate_timer_handle(world_context_object, handle) -> TimerHandle 550 | is_valid_soft_object_reference(soft_object_reference) -> bool 551 | is_valid_soft_class_reference(soft_class_reference) -> bool 552 | is_valid_primary_asset_type(primary_asset_type) -> bool 553 | is_valid_primary_asset_id(primary_asset_id) -> bool 554 | is_valid_class(class_) -> bool 555 | is_valid(object) -> bool 556 | is_unattended() -> bool 557 | is_standalone(world_context_object) -> bool 558 | is_split_screen(world_context_object) -> bool 559 | is_server(world_context_object) -> bool 560 | is_screensaver_enabled() -> bool 561 | is_packaged_for_distribution() -> bool 562 | is_logged_in(specific_player) -> bool 563 | is_interstitial_ad_requested() -> bool 564 | is_interstitial_ad_available() -> bool 565 | is_dedicated_server(world_context_object) -> bool 566 | is_controller_assigned_to_gamepad(controller_id) -> bool 567 | hide_ad_banner() -> None 568 | get_volume_buttons_handled_by_system() -> bool 569 | get_unique_device_id() -> str 570 | get_supported_fullscreen_resolutions() -> Array(IntPoint) or None 571 | get_soft_object_reference_from_primary_asset_id(primary_asset_id) -> Object 572 | get_soft_class_reference_from_primary_asset_id(primary_asset_id) -> Class 573 | get_rendering_material_quality_level() -> int32 574 | get_rendering_detail_mode() -> int32 575 | get_project_saved_directory() -> str 576 | get_project_directory() -> str 577 | get_project_content_directory() -> str 578 | get_primary_assets_with_bundle_state(required_bundles, excluded_bundles, valid_types, force_current_state) -> Array(PrimaryAssetId) 579 | get_primary_asset_id_list(primary_asset_type) -> Array(PrimaryAssetId) 580 | get_primary_asset_id_from_soft_object_reference(soft_object_reference) -> PrimaryAssetId 581 | get_primary_asset_id_from_soft_class_reference(soft_class_reference) -> PrimaryAssetId 582 | get_primary_asset_id_from_object(object) -> PrimaryAssetId 583 | get_primary_asset_id_from_class(class_) -> PrimaryAssetId 584 | get_preferred_languages() -> Array(str) 585 | get_platform_user_name() -> str 586 | get_platform_user_dir() -> str 587 | get_path_name(object) -> str 588 | get_outer_object(object) -> Object 589 | get_object_name(object) -> str 590 | get_object_from_primary_asset_id(primary_asset_id) -> Object 591 | get_min_y_resolution_for_ui() -> int32 592 | get_min_y_resolution_for3d_view() -> int32 593 | get_local_currency_symbol() -> str 594 | get_local_currency_code() -> str 595 | get_game_time_in_seconds(world_context_object) -> float 596 | get_gamepad_controller_name(controller_id) -> str 597 | get_game_name() -> str 598 | get_game_bundle_id() -> str 599 | get_frame_count() -> int64 600 | get_engine_version() -> str 601 | get_display_name(object) -> str 602 | get_device_id() -> str 603 | get_default_locale() -> str 604 | get_default_language() -> str 605 | get_current_bundle_state(primary_asset_id, force_current_state) -> Array(Name) or None 606 | get_convenient_windowed_resolutions() -> Array(IntPoint) or None 607 | get_console_variable_int_value(variable_name) -> int32 608 | get_console_variable_float_value(variable_name) -> float 609 | get_console_variable_bool_value(variable_name) -> bool 610 | get_component_bounds(component) -> (origin=Vector, box_extent=Vector, sphere_radius=float) 611 | get_command_line() -> str 612 | get_class_from_primary_asset_id(primary_asset_id) -> type(Class) 613 | get_class_display_name(class_) -> str 614 | get_ad_id_count() -> int32 615 | get_actor_list_from_component_list(component_list, actor_class_filter) -> Array(Actor) 616 | get_actor_bounds(actor) -> (origin=Vector, box_extent=Vector) 617 | force_close_ad_banner() -> None 618 | flush_persistent_debug_lines(world_context_object) -> None 619 | flush_debug_strings(world_context_object) -> None 620 | execute_console_command(world_context_object, command, specific_player=None) -> None 621 | equal_equal_soft_object_reference(a, b) -> bool 622 | equal_equal_soft_class_reference(a, b) -> bool 623 | equal_equal_primary_asset_type(a, b) -> bool 624 | equal_equal_primary_asset_id(a, b) -> bool 625 | end_transaction() -> int32 626 | draw_debug_string(world_context_object, text_location, text, test_base_actor=None, text_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=0.000000) -> None 627 | draw_debug_sphere(world_context_object, center, radius=100.000000, segments=12, line_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=0.000000, thickness=0.000000) -> None 628 | draw_debug_point(world_context_object, position, size, point_color, duration=0.000000) -> None 629 | draw_debug_plane(world_context_object, plane_coordinates, location, size, plane_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=0.000000) -> None 630 | draw_debug_line(world_context_object, line_start, line_end, line_color, duration=0.000000, thickness=0.000000) -> None 631 | draw_debug_frustum(world_context_object, frustum_transform, frustum_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=0.000000, thickness=0.000000) -> None 632 | draw_debug_float_history_transform(world_context_object, float_history, draw_transform, draw_size, draw_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=0.000000) -> None 633 | draw_debug_float_history_location(world_context_object, float_history, draw_location, draw_size, draw_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=0.000000) -> None 634 | draw_debug_cylinder(world_context_object, start, end, radius=100.000000, segments=12, line_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=0.000000, thickness=0.000000) -> None 635 | draw_debug_coordinate_system(world_context_object, axis_loc, axis_rot, scale=1.000000, duration=0.000000, thickness=0.000000) -> None 636 | draw_debug_cone_in_degrees(world_context_object, origin, direction, length=100.000000, angle_width=45.000000, angle_height=45.000000, num_sides=12, line_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=0.000000, thickness=0.000000) -> None 637 | draw_debug_cone(world_context_object, origin, direction, length, angle_width, angle_height, num_sides, line_color, duration=0.000000, thickness=0.000000) -> None 638 | draw_debug_circle(world_context_object, center, radius, num_segments=12, line_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=0.000000, thickness=0.000000, y_axis=[0.000000, 1.000000, 0.000000], z_axis=[0.000000, 0.000000, 1.000000], draw_axis=False) -> None 639 | draw_debug_capsule(world_context_object, center, half_height, radius, rotation, line_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=0.000000, thickness=0.000000) -> None 640 | draw_debug_camera(camera_actor, camera_color=[0.000000, 0.000000, 0.000000, 0.000000], duration=0.000000) -> None 641 | draw_debug_box(world_context_object, center, extent, line_color, rotation=[0.000000, 0.000000, 0.000000], duration=0.000000, thickness=0.000000) -> None 642 | draw_debug_arrow(world_context_object, line_start, line_end, arrow_size, line_color, duration=0.000000, thickness=0.000000) -> None 643 | does_implement_interface(test_object, interface) -> bool 644 | delay(world_context_object, duration, latent_info) -> None 645 | create_copy_for_undo_buffer(object_to_modify) -> None 646 | convert_to_relative_path(filename) -> str 647 | convert_to_absolute_path(filename) -> str 648 | conv_soft_obj_path_to_soft_obj_ref(soft_object_path) -> Object 649 | conv_soft_object_reference_to_string(soft_object_reference) -> str 650 | conv_soft_class_reference_to_string(soft_class_reference) -> str 651 | conv_soft_class_path_to_soft_class_ref(soft_class_path) -> Class 652 | conv_primary_asset_type_to_string(primary_asset_type) -> str 653 | conv_primary_asset_id_to_string(primary_asset_id) -> str 654 | conv_interface_to_object(interface) -> Object 655 | control_screensaver(allow_screen_saver) -> None 656 | component_overlap_components(component, component_transform, object_types, component_class_filter, actors_to_ignore) -> Array(PrimitiveComponent) or None 657 | component_overlap_actors(component, component_transform, object_types, actor_class_filter, actors_to_ignore) -> Array(Actor) or None 658 | collect_garbage() -> None 659 | capsule_trace_single_for_objects(world_context_object, start, end, radius, half_height, object_types, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> HitResult or None 660 | capsule_trace_single_by_profile(world_context_object, start, end, radius, half_height, profile_name, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> HitResult or None 661 | capsule_trace_single(world_context_object, start, end, radius, half_height, trace_channel, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> HitResult or None 662 | capsule_trace_multi_for_objects(world_context_object, start, end, radius, half_height, object_types, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> Array(HitResult) or None 663 | capsule_trace_multi_by_profile(world_context_object, start, end, radius, half_height, profile_name, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> Array(HitResult) or None 664 | capsule_trace_multi(world_context_object, start, end, radius, half_height, trace_channel, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> Array(HitResult) or None 665 | capsule_overlap_components(world_context_object, capsule_pos, radius, half_height, object_types, component_class_filter, actors_to_ignore) -> Array(PrimitiveComponent) or None 666 | capsule_overlap_actors(world_context_object, capsule_pos, radius, half_height, object_types, actor_class_filter, actors_to_ignore) -> Array(Actor) or None 667 | can_launch_url(url) -> bool 668 | cancel_transaction(index) -> None 669 | box_trace_single_for_objects(world_context_object, start, end, half_size, orientation, object_types, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> HitResult or None 670 | box_trace_single_by_profile(world_context_object, start, end, half_size, orientation, profile_name, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> HitResult or None 671 | box_trace_single(world_context_object, start, end, half_size, orientation, trace_channel, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> HitResult or None 672 | box_trace_multi_for_objects(world_context_object, start, end, half_size, orientation, object_types, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> Array(HitResult) or None 673 | box_trace_multi_by_profile(world_context_object, start, end, half_size, orientation, profile_name, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> Array(HitResult) or None 674 | box_trace_multi(world_context_object, start, end, half_size, orientation, trace_channel, trace_complex, actors_to_ignore, draw_debug_type, ignore_self, trace_color=[0.000000, 0.000000, 0.000000, 0.000000], trace_hit_color=[0.000000, 0.000000, 0.000000, 0.000000], draw_time=5.000000) -> Array(HitResult) or None 675 | box_overlap_components(world_context_object, box_pos, extent, object_types, component_class_filter, actors_to_ignore) -> Array(PrimitiveComponent) or None 676 | box_overlap_actors(world_context_object, box_pos, box_extent, object_types, actor_class_filter, actors_to_ignore) -> Array(Actor) or None 677 | begin_transaction(context, description, primary_object) -> int32 678 | add_float_history_sample(value, float_history) -> DebugFloatHistory 679 | """ 680 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Content/Python/unreal_icon.py: -------------------------------------------------------------------------------- 1 | # Know default icon in EditorStyle set 2 | EditorStyle = [ 3 | "Kismet.Status.Unknown" 4 | "Kismet.Status.Error" 5 | "Kismet.Status.Good" 6 | "Kismet.Status.Warning" 7 | 8 | "AssetEditor.SaveAsset" 9 | "SystemWideCommands.FindInContentBrowser" 10 | "BlueprintEditor.FindInBlueprint" 11 | "MaterialEditor.FindInMaterial" 12 | "TranslationEditor.Search" 13 | 14 | "FullBlueprintEditor.EditGlobalOptions" 15 | "FullBlueprintEditor.EditClassDefaults" 16 | "FullBlueprintEditor.SwitchToBlueprintDefaultsMode" 17 | "BlueprintEditor.EnableSimulation" 18 | "PlayWorld.PlayInViewport" 19 | "GraphEditor.ToggleHideUnrelatedNodes" 20 | "LevelEditor.Build" 21 | "LevelEditor.Recompile" 22 | "LevelEditor.OpenContentBrowser" 23 | "LevelEditor.EditorModes" 24 | "LevelEditor.ToggleVR" 25 | "LevelEditor.GameSettings" 26 | "LevelEditor.OpenLevelBlueprint" 27 | "LevelEditor.OpenMarketplace" 28 | "LevelEditor.EditMatinee" 29 | 30 | "LevelEditor.SourceControl" 31 | "LevelEditor.SourceControl.On" 32 | "LevelEditor.SourceControl.Off" 33 | "LevelEditor.SourceControl.Unknown" 34 | "LevelEditor.SourceControl.Problem" 35 | "MaterialEditor.Apply" 36 | "MaterialEditor.CameraHome" 37 | "MaterialEditor.ToggleRealtimeExpressions" 38 | "MaterialEditor.AlwaysRefreshAllPreviews" 39 | "MaterialEditor.ToggleLivePreview" 40 | "MaterialEditor.ToggleMaterialStats" 41 | "MaterialEditor.TogglePlatformStats" 42 | "MaterialEditor.CleanUnusedExpressions" 43 | "MaterialEditor.ShowHideConnectors" 44 | 45 | "PlayWorld.PausePlaySession" 46 | "PlayWorld.StopPlaySession" 47 | "PlayWorld.PossessPlayer" 48 | "PlayWorld.EjectFromPlayer" 49 | "PlayWorld.RepeatLastLaunch" 50 | "PlayWorld.PlayInNewProcess" 51 | "PlayWorld.PlayInEditorFloating" 52 | "PlayWorld.PlayInMobilePreview" 53 | "PlayWorld.PlayInVR" 54 | "PlayWorld.LateJoinSession" 55 | "PlayWorld.ResumePlaySession" 56 | "PlayWorld.Simulate" 57 | "PlayWorld.RepeatLastPlay" 58 | 59 | "PlayWorld.SingleFrameAdvance" 60 | "PlayWorld.ShowCurrentStatement" 61 | "PlayWorld.StepOut" 62 | "PlayWorld.StepInto" 63 | "PlayWorld.StepOver" 64 | 65 | "BTEditor.SwitchToBehaviorTreeMode" 66 | "UMGEditor.SwitchToDesigner" 67 | "FullBlueprintEditor.SwitchToScriptingMode" 68 | 69 | "EditorViewport.ToggleRealTime" 70 | 71 | "StaticMeshEditor.SetShowWireframe" 72 | "StaticMeshEditor.SetShowVertexColor" 73 | "StaticMeshEditor.SetRealtimePreview" 74 | "StaticMeshEditor.ReimportMesh" 75 | "StaticMeshEditor.SetShowBounds" 76 | "StaticMeshEditor.SetShowCollision" 77 | "StaticMeshEditor.SetShowGrid" 78 | "StaticMeshEditor.SetDrawUVs" 79 | "StaticMeshEditor.ResetCamera" 80 | "StaticMeshEditor.SetShowPivot" 81 | "StaticMeshEditor.SetShowSockets" 82 | "StaticMeshEditor.SetShowNormals" 83 | "StaticMeshEditor.SetShowTangents" 84 | "StaticMeshEditor.SetShowBinormals" 85 | "StaticMeshEditor.SetDrawAdditionalData" 86 | "StaticMeshEditor.SetShowVertices" 87 | "StaticMeshEditor.ToggleShowPivots" 88 | "StaticMeshEditor.ToggleShowSockets" 89 | "StaticMeshEditor.ToggleShowNormals" 90 | "StaticMeshEditor.ToggleShowTangents" 91 | "StaticMeshEditor.ToggleShowBinormals" 92 | "StaticMeshEditor.ToggleShowBounds" 93 | "StaticMeshEditor.ToggleShowGrids" 94 | "StaticMeshEditor.ToggleShowVertices" 95 | "StaticMeshEditor.ToggleShowWireframes" 96 | "StaticMeshEditor.ToggleShowVertexColors" 97 | "Persona.BakeMaterials" 98 | 99 | "AnimationEditor.ApplyCompression" 100 | "AnimationEditor.ExportToFBX" 101 | "AnimationEditor.ReimportAnimation" 102 | "AnimationEditor.CreateAsset" 103 | "AnimationEditor.SetKey" 104 | "AnimationEditor.ApplyAnimation" 105 | 106 | "Persona.TogglePreviewAsset" 107 | "Persona.CreateAsset" 108 | "Persona.ExportToFBX" 109 | "Persona.ConvertToStaticMesh" 110 | 111 | "EditorViewport.LocationGridSnap" 112 | "EditorViewport.RotationGridSnap" 113 | "EditorViewport.Layer2DSnap" 114 | "EditorViewport.ScaleGridSnap" 115 | "EditorViewport.ToggleSurfaceSnapping" 116 | "EditorViewport.RelativeCoordinateSystem_Local" 117 | "EditorViewport.RelativeCoordinateSystem_Local.Small" 118 | "EditorViewport.RelativeCoordinateSystem_World" 119 | "EditorViewport.RelativeCoordinateSystem_World.Small" 120 | "EditorViewport.CamSpeedSetting" 121 | 122 | "DetailsView.EditRawProperties" 123 | 124 | "CurveEd.Visible" 125 | "CurveEd.VisibleHighlight" 126 | "CurveEd.Invisible" 127 | "CurveEd.InvisibleHighlight" 128 | "Level.VisibleIcon16x" 129 | "Level.VisibleHighlightIcon16x" 130 | "Level.NotVisibleIcon16x" 131 | "Level.NotVisibleHighlightIcon16x" 132 | "GenericViewButton" 133 | 134 | "PropertyWindow.Button_CreateNewBlueprint" 135 | "PropertyWindow.Button_Browse" 136 | "PropertyWindow.Button_Use" 137 | 138 | "GenericLock" 139 | "GenericLock.Small" 140 | "GenericUnlock" 141 | "GenericUnlock.Small" 142 | "PropertyWindow.Locked" 143 | "PropertyWindow.Unlocked" 144 | "FindResults.LockButton_Locked" 145 | "FindResults.LockButton_Unlocked" 146 | "ContentBrowser.LockButton_Locked" 147 | "ContentBrowser.LockButton_Unlocked" 148 | 149 | "DetailsView.PulldownArrow.Down" 150 | "DetailsView.PulldownArrow.Down.Hovered" 151 | "DetailsView.PulldownArrow.Up" 152 | "DetailsView.PulldownArrow.Up.Hovered" 153 | 154 | "TreeArrow_Collapsed" 155 | "TreeArrow_Collapsed_Hovered" 156 | "TreeArrow_Expanded" 157 | "TreeArrow_Expanded_Hovered" 158 | 159 | "EditorViewport.TranslateMode" 160 | "EditorViewport.RotateMode" 161 | "EditorViewport.ScaleMode" 162 | 163 | "TimelineEditor.AddFloatTrack" 164 | "TimelineEditor.AddVectorTrack" 165 | "TimelineEditor.AddEventTrack" 166 | "TimelineEditor.AddColorTrack" 167 | "TimelineEditor.AddCurveAssetTrack" 168 | "TimelineEditor.DeleteTrack" 169 | ] -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Content/Python/unreal_startup.py: -------------------------------------------------------------------------------- 1 | import unreal 2 | import unreal_uiutils 3 | from unreal_global import * 4 | from unreal_utils import AssetRegistryPostLoad 5 | unreal.log("""@ 6 | 7 | #################### 8 | 9 | Init Start up Script 10 | 11 | #################### 12 | 13 | """) 14 | 15 | assetregistry_pretickhandle = None 16 | 17 | def assetregistry_postload_handle(deltaTime): 18 | """ 19 | Run callback method after registry run to prevent crashed when create new asset at startupS 20 | """ 21 | unreal.log_warning("..Checking Asset Registy Status...") 22 | if AssetRegistry.is_loading_assets(): 23 | unreal.log_warning("..Asset registy still loading...") 24 | else: 25 | unreal.log_warning("Asset registy ready!") 26 | unreal.unregister_slate_post_tick_callback(assetregistry_pretickhandle) 27 | AssetRegistryPostLoad.run_callbacks() 28 | 29 | assetregistry_pretickhandle = unreal.register_slate_post_tick_callback(assetregistry_postload_handle) 30 | 31 | import BlueprintLibrary 32 | import UserInterfaces 33 | 34 | def reload(): 35 | import importlib 36 | importlib.reload(BlueprintLibrary) 37 | importlib.reload(UserInterfaces) 38 | 39 | unreal_uiutils.refresh() 40 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Content/Python/unreal_uiutils.py: -------------------------------------------------------------------------------- 1 | # 4.25 + 2 | 3 | import unreal 4 | 5 | # @unreal.uclass() 6 | class EditorToolbarMenuEntry(unreal.ToolMenuEntryScript): 7 | def __init__( 8 | self, 9 | menu="None", 10 | section="None", 11 | name="None", 12 | label="", 13 | tool_tip="", 14 | icon=["None", "None", "None"], 15 | owner_name="None", 16 | insert_position=["None", unreal.ToolMenuInsertType.DEFAULT], 17 | advanced=[ 18 | "None", 19 | unreal.MultiBlockType.MENU_ENTRY, 20 | unreal.UserInterfaceActionType.BUTTON, 21 | False, 22 | False, 23 | True, 24 | False, 25 | ], 26 | ): 27 | unreal.Object.__init__(self) 28 | self._data = unreal.ToolMenuEntryScriptData( 29 | menu, 30 | section, 31 | name, 32 | label, 33 | tool_tip, 34 | icon, 35 | owner_name, 36 | insert_position, 37 | advanced, 38 | ) 39 | 40 | @property 41 | def data(self): 42 | return self._data 43 | 44 | 45 | def refresh(): 46 | unreal.ToolMenus.get().refresh_all_widgets() 47 | 48 | # Get Toolbar 49 | 50 | def get_toolbar(): 51 | return unreal.ToolMenus.get().find_menu("LevelEditor.LevelEditorToolBar") 52 | 53 | def get_staticmesh_toolbar(): 54 | return unreal.ToolMenus.get().find_menu("AssetEditor.StaticMeshEditor.ToolBar") 55 | 56 | def get_skeletalmesh_toolbar(): 57 | return unreal.ToolMenus.get().find_menu("AssetEditor.SkeletalMeshEditor.ToolBar") 58 | 59 | # Get Toolbar SubMenu 60 | 61 | def get_buildcombo_sub_menu(): 62 | return unreal.ToolMenus.get().find_menu("LevelEditor.LevelEditorToolBar.BuildComboButton") 63 | 64 | def get_buildcombo_ligtingquality_sub_menu(): 65 | return unreal.ToolMenus.get().find_menu("LevelEditor.LevelEditorToolBar.BuildComboButton.LightingQuality") 66 | 67 | def get_buildcombo_ligtinginfo_sub_menu(): 68 | return unreal.ToolMenus.get().find_menu("LevelEditor.LevelEditorToolBar.BuildComboButton.LightingInfo") 69 | 70 | def get_buildcombo_ligtingdensity_sub_menu(): 71 | return unreal.ToolMenus.get().find_menu("LevelEditor.LevelEditorToolBar.BuildComboButton.LightingInfo.LightingDensity") 72 | 73 | def get_buildcombo_ligtingresolution_sub_menu(): 74 | return unreal.ToolMenus.get().find_menu("LevelEditor.LevelEditorToolBar.BuildComboButton.LightingInfo.LightingResolution") 75 | 76 | def get_leveltoolbar_setttings_sub_menu(): 77 | return unreal.ToolMenus.get().find_menu("LevelEditor.LevelEditorToolBar.LevelToolbarQuickSettings") 78 | 79 | def get_sourcecontrol_sub_menu(): 80 | return unreal.ToolMenus.get().find_menu("LevelEditor.LevelEditorToolBar.SourceControl") 81 | 82 | def get_editormodes_sub_menu(): 83 | return unreal.ToolMenus.get().find_menu("LevelEditor.LevelEditorToolBar.EditorModes") 84 | 85 | def get_openblueprint_sub_menu(): 86 | return unreal.ToolMenus.get().find_menu("LevelEditor.LevelEditorToolBar.OpenBlueprint") 87 | 88 | def get_cinematics_sub_menu(): 89 | return unreal.ToolMenus.get().find_menu("LevelEditor.LevelEditorToolBar.Cinematics") 90 | 91 | def get_compilecombo_sub_menu(): 92 | return unreal.ToolMenus.get().find_menu("LevelEditor.LevelEditorToolBar.CompileComboButton") 93 | 94 | # Get Context Menu 95 | 96 | def get_asset_context_menu(): 97 | return unreal.ToolMenus.get().find_menu("ContentBrowser.AssetContextMenu") 98 | 99 | def get_folder_context_menu(): 100 | return unreal.ToolMenus.get().find_menu("ContentBrowser.FolderContextMenu") 101 | 102 | def get_actor_context_menu(): 103 | return unreal.ToolMenus.get().find_menu("LevelEditor.ActorContextMenu") 104 | 105 | def get_dragdrop_context_menu(): 106 | return unreal.ToolMenus.get().find_menu("ContentBrowser.DragDropContextMenu") 107 | 108 | def get_sequence_asset_context_menu(): 109 | return unreal.ToolMenus.get().find_menu("ContentBrowser.AssetContextMenu.LevelSequence") 110 | 111 | def get_cameraanim_asset_context_menu(): 112 | return unreal.ToolMenus.get().find_menu("ContentBrowser.AssetContextMenu.CameraAnim") 113 | 114 | def get_mediaplayer_assetpicker_context_menu(): 115 | return unreal.ToolMenus.get().find_menu("MediaPlayer.AssetPickerAssetContextMenu") 116 | 117 | def get_soundwave_asset_context_menu(): 118 | return unreal.ToolMenus.get().find_menu("ContentBrowser.AssetContextMenu.SoundWave") 119 | 120 | def get_addnew_context_menu(): 121 | return unreal.ToolMenus.get().find_menu("ContentBrowser.AddNewContextMenu") 122 | 123 | def get_toolbar_item(name): 124 | return unreal.ToolMenus.get().find_menu( 125 | "LevelEditor.LevelEditorToolBar.{}".format(name) 126 | ) 127 | 128 | # Get Main Menu 129 | 130 | def get_mainmenu(): 131 | return unreal.ToolMenus.get().find_menu("MainFrame.MainMenu") 132 | 133 | def get_file_menu(): 134 | return unreal.ToolMenus.get().find_menu("MainFrame.MainMenu.File") 135 | 136 | def get_edit_menu(): 137 | return unreal.ToolMenus.get().find_menu("MainFrame.MainMenu.Edit") 138 | 139 | def get_asset_menu(): 140 | return unreal.ToolMenus.get().find_menu("MainFrame.MainMenu.Window") 141 | 142 | def get_window_menu(): 143 | return unreal.ToolMenus.get().find_menu("MainFrame.MainMenu.Help") 144 | 145 | def get_mainmenu_item(name): 146 | return unreal.ToolMenus.get().find_menu("MainFrame.MainMenu.{}".format(name)) 147 | 148 | 149 | def create_python_tool_menu_entry( 150 | name, label, command_string="", entry_type=unreal.MultiBlockType.MENU_ENTRY 151 | ): 152 | menu_entry = unreal.ToolMenuEntry(name, type=entry_type) 153 | menu_entry.set_label(label) 154 | if command_string: 155 | menu_entry.set_string_command( 156 | unreal.ToolMenuStringCommandType.PYTHON, "python", command_string 157 | ) 158 | return menu_entry 159 | 160 | 161 | def create_menu_button(name, label, command_string=""): 162 | return create_python_tool_menu_entry(name, label, command_string) 163 | 164 | 165 | def create_toolbar_button( 166 | name, label, section_name="", icons =["EditorStyle", "LevelEditor.EditorModes", "LevelEditor.EditorModes"], command_string="", register_button=True 167 | ): 168 | button = create_python_tool_menu_entry( 169 | name, label, command_string, entry_type=unreal.MultiBlockType.TOOL_BAR_BUTTON 170 | ) 171 | button.set_icon(*icons) 172 | if register_button: 173 | get_toolbar().add_menu_entry(section_name, button) 174 | return button 175 | 176 | 177 | def create_toolbar_combo_button(name, section_name, tool_tip="", register_button=True): 178 | # menu_name = ".".join([str(get_toolbar().menu_name),menu]) 179 | # section_name = ".".join([str(get_toolbar().menu_name), menu, section]) 180 | # get_toolbar().add_section(section_name) 181 | # menu_entry_script = EditorToolbarMenuEntry(get_toolbar().menu_name, section_name, name, "", tool_tip, ["EditorStyle", "DetailsView.PulldownArrow.Down", "DetailsView.PulldownArrow.Down"], get_toolbar().menu_name, ["None", unreal.ToolMenuInsertType.DEFAULT], ["None", unreal.MultiBlockType.TOOL_BAR_COMBO_BUTTON, unreal.UserInterfaceActionType.BUTTON, True, True, True, False]) 182 | # menu_entry_script.register_menu_entry() 183 | menu_entry = unreal.ToolMenuEntry( 184 | name, type=unreal.MultiBlockType.TOOL_BAR_COMBO_BUTTON 185 | ) 186 | if register_button: 187 | get_toolbar().add_menu_entry(section_name, menu_entry) 188 | return menu_entry 189 | 190 | 191 | def create_editable_text(name, label): 192 | menu_entry = unreal.ToolMenuEntry(name, type=unreal.MultiBlockType.EDITABLE_TEXT) 193 | menu_entry.set_label(label) 194 | return menu_entry 195 | 196 | 197 | def create_widget(name): 198 | return unreal.ToolMenuEntry(name, type=unreal.MultiBlockType.WIDGET) 199 | 200 | 201 | def create_heading(name): 202 | return unreal.ToolMenuEntry(name, type=unreal.MultiBlockType.HEADING) 203 | 204 | 205 | def create_separator(name="Separator"): 206 | return unreal.ToolMenuEntry(name, type=unreal.MultiBlockType.SEPARATOR) 207 | 208 | 209 | def extend_mainmenu_item(mainmenu_name, section_name, name, label, tooltips=""): 210 | parent_menu = get_mainmenu_item(mainmenu_name) 211 | return parent_menu.add_sub_menu( 212 | parent_menu.menu_name, section_name, name, label, tooltips 213 | ) 214 | 215 | def extend_file_menu(section_name, name, label, tooltips=""): 216 | extend_mainmenu_item("File", section_name, name, label, tooltips="") 217 | 218 | def extend_edit_menu(section_name, name, label, tooltips=""): 219 | extend_mainmenu_item("Edit", section_name, name, label, tooltips="") 220 | 221 | def extend_asset_menu(section_name, name, label, tooltips=""): 222 | extend_mainmenu_item("Asset", section_name, name, label, tooltips="") 223 | 224 | def extend_mesh_menu(section_name, name, label, tooltips=""): 225 | extend_mainmenu_item("Mesh", section_name, name, label, tooltips="") 226 | 227 | def extend_help_menu(section_name, name, label, tooltips=""): 228 | extend_mainmenu_item("Help", section_name, name, label, tooltips="") 229 | 230 | def extend_mainmenu(name, label, tooltip=""): 231 | main_menu = get_mainmenu() 232 | return main_menu.add_sub_menu( 233 | main_menu.menu_name, unreal.Name(), name, label, tooltip 234 | ) 235 | 236 | def extend_toolmenu(owner, name, label, section_name=unreal.Name(), tooltip=""): 237 | return owner.add_sub_menu( 238 | owner.menu_name, section_name, name, label, tooltip 239 | ) 240 | 241 | def extend_toolbar(name, label, tooltip=""): 242 | toolbar = get_toolbar() 243 | return toolbar.add_sub_menu(toolbar.menu_name, unreal.Name(), name, label, tooltip) 244 | 245 | def add_sequencer_toolbarbutton( 246 | name, label, section_name="Asset", icons = None, command_string="", register_button=True 247 | ): 248 | button = create_python_tool_menu_entry( 249 | name, label, command_string, entry_type=unreal.MultiBlockType.TOOL_BAR_BUTTON 250 | ) 251 | if icons: 252 | button.set_icon(*icons) 253 | if register_button: 254 | get_sequencer_toolbar().add_menu_entry(section_name, button) 255 | return button 256 | 257 | def parent_qt_window(qt_widget): 258 | unreal.parent_external_window_to_slate(qt_widget.winId()) 259 | 260 | def show_message(title, message, message_type, default_value=unreal.AppReturnType.NO): 261 | return unreal.EditorDialog.show_message(title, message, message_type, default_value) 262 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Content/Python/unreal_utils.py: -------------------------------------------------------------------------------- 1 | # Nguyen Phi Hung @ 2020 2 | # hung.nguyen@onikuma.games 3 | 4 | from unreal_global import * 5 | import hashlib 6 | import unreal 7 | 8 | 9 | class AssetRegistryPostLoad: 10 | _callbacks = {} 11 | 12 | @classmethod 13 | def register_callback(cls, func, *args, **kws): 14 | cls._callbacks[hashlib.md5(str((func, args, kws)).encode()).hexdigest()] = ( 15 | func, 16 | args, 17 | kws, 18 | ) 19 | 20 | @classmethod 21 | def unregister_callback(cls, func, *args, **kws): 22 | try: 23 | del cls._callbacks[hashlib.md5(str((func, args, kws)).encode()).hexdigest()] 24 | except KeyError: 25 | unreal.log_error( 26 | "No callback name {} with arguments: {} and keywords {} to unregister".format( 27 | func.__name__, args, kws 28 | ) 29 | ) 30 | 31 | @classmethod 32 | def run_callbacks(cls): 33 | for func_name, func_param in cls._callbacks.items(): 34 | func, args, kws = func_param 35 | unreal.log_warning( 36 | "execute {} with param {} keywords: {}".format(func.__name__, args, kws) 37 | ) 38 | try: 39 | func(*args, **kws) 40 | except Exception as why: 41 | unreal.log_error("failed to run with error:\n{}".format(why)) 42 | 43 | def get_outer_package(): 44 | return unreal.find_object(None, "/Engine/Transient") 45 | 46 | def create_unreal_asset( 47 | asset_name, package_path, factory, asset_class, force=False, save=True 48 | ): 49 | """ 50 | 51 | INPUT: 52 | asset_name: str 53 | Exp: "MyAwesomeBPActorClass" 54 | package_path: str 55 | Exp: "/Game/MyContentFolder" 56 | package_path: unreal.Factory: 57 | Exp: unreal.BlueprintFactory() 58 | asset_class: unreal.Object 59 | Exp: unreal.Actor 60 | force: bool 61 | Force remove old and create new one 62 | save: bool 63 | Save asset after creation 64 | 65 | OUPUT: 66 | unreal.Object 67 | 68 | """ 69 | 70 | asset_path = "{}/{}".format(package_path, asset_name) 71 | if AssetLibrary.does_asset_exist(asset_path): 72 | if force: 73 | unreal.log_warning("{} exists. Skip creating".format(asset_name)) 74 | return 75 | else: 76 | unreal.log_warning("{} exists. Remove existing asset.".format(asset_name)) 77 | AssetLibrary.delete_asset(asset_path) 78 | 79 | factory.set_editor_property("ParentClass", asset_class) 80 | 81 | new_asset = AssetTools.create_asset(asset_name, package_path, None, factory) 82 | 83 | if save: 84 | AssetLibrary.save_loaded_asset(new_asset) 85 | 86 | return new_asset 87 | 88 | 89 | def create_levelsequence_asset(asset_name, package_path, force=False, save=True): 90 | create_unreal_asset( 91 | asset_name, 92 | package_path, 93 | unreal.LevelSequenceFactoryNew(), 94 | unreal.LevelSequence, 95 | force, 96 | save, 97 | ) 98 | 99 | 100 | def create_blueprint_asset( 101 | asset_name, package_path, asset_parent_class, force=False, save=True 102 | ): 103 | create_unreal_asset( 104 | asset_name, 105 | package_path, 106 | unreal.BlueprintFactory(), 107 | asset_parent_class, 108 | force, 109 | save, 110 | ) 111 | 112 | 113 | def create_editor_utility_blueprint( 114 | asset_name, asset_parent_class, force=False, save=False 115 | ): 116 | create_unreal_asset( 117 | f"{asset_name}", 118 | "/Engine/", 119 | unreal.EditorUtilityBlueprintFactory(), 120 | asset_parent_class, 121 | force, 122 | save, 123 | ) 124 | 125 | 126 | def register_editor_utility_blueprint(asset_name, asset_parent_class): 127 | AssetRegistryPostLoad.register_callback( 128 | create_editor_utility_blueprint, asset_name, asset_parent_class 129 | ) 130 | 131 | 132 | # def new_object(object_class): 133 | # unreal.load_object(unreal.new_object(object_class)) 134 | # def register_editor_utility_blueprint(asset_name, asset_parent_class): 135 | # AssetRegistryPostLoad.register_callback(new_object, asset_parent_class) 136 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Phi Hung Nguyen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/PythonScripting.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "PythonScripting", 6 | "Description": "Python Scripting Template", 7 | "Category": "Other", 8 | "CreatedBy": "Nguyen Phi Hung", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Plugins": [ 18 | { 19 | "Name": "PythonScriptPlugin", 20 | "Enabled": true 21 | }, 22 | { 23 | "Name": "SequencerScripting", 24 | "Enabled": true 25 | }, 26 | { 27 | "Name": "PythonAutomationTest", 28 | "Enabled": true 29 | }, 30 | { 31 | "Name": "EditorScriptingUtilities", 32 | "Enabled": true 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/README.md: -------------------------------------------------------------------------------- 1 | # Template for extend unreal editor with python 2 | 3 | ## USAGE 4 | Clone this project to your Unreal Project Plugin folder 5 | 6 | In **BlueprintLibrary** Module duplicate relevant blueprint action utility sample and extend functionality, any module will be auto generated with blueprint asset when unreal load. 7 | 8 | In **User Interfaces** store examples of how to create menu and toolbar button as well as add python function to asset/actor context menu. 9 | 10 | !!! Note 11 | Test in Unreal 4.26 12 | -------------------------------------------------------------------------------- /Integration/UE4PrismPlugins/Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephkirk/UnrealEnginePrismPlugin/afbe3817bc2bb657ffbfd46ed441e6a0ada6591a/Integration/UE4PrismPlugins/Resources/Icon128.png -------------------------------------------------------------------------------- /Scripts/Prism_UnrealEngine_Functions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | #################################################### 4 | # 5 | # PRISM - Pipeline for animation and VFX projects 6 | # 7 | # www.prism-pipeline.com 8 | # 9 | # contact: contact@prism-pipeline.com 10 | # 11 | #################################################### 12 | # 13 | # 14 | # Copyright (C) 2016-2020 Richard Frangenberg 15 | # 16 | # Licensed under GNU GPL-3.0-or-later 17 | # 18 | # This file is part of Prism. 19 | # 20 | # Prism is free software: you can redistribute it and/or modify 21 | # it under the terms of the GNU General Public License as published by 22 | # the Free Software Foundation, either version 3 of the License, or 23 | # (at your option) any later version. 24 | # 25 | # Prism is distributed in the hope that it will be useful, 26 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | # GNU General Public License for more details. 29 | # 30 | # You should have received a copy of the GNU General Public License 31 | # along with Prism. If not, see . 32 | 33 | 34 | import os 35 | import sys 36 | import platform 37 | import subprocess 38 | 39 | try: 40 | from PySide2.QtCore import * 41 | from PySide2.QtGui import * 42 | from PySide2.QtWidgets import * 43 | except: 44 | from PySide.QtCore import * 45 | from PySide.QtGui import * 46 | 47 | 48 | from PrismUtils.Decorators import err_catcher as err_catcher 49 | 50 | 51 | class Prism_UnrealEngine_Functions(object): 52 | def __init__(self, core, plugin): 53 | self.core = core 54 | self.plugin = plugin 55 | self.win = platform.system() == "Windows" 56 | 57 | @err_catcher(name=__name__) 58 | def startup(self, origin): 59 | # for obj in QApplication.topLevelWidgets(): 60 | # if obj.objectName() == 'UnrealEngineWindow': 61 | # QtParent = obj 62 | # break 63 | # else: 64 | # return False 65 | 66 | origin.timer.stop() 67 | 68 | origin.messageParent = QWidget() 69 | # origin.messageParent.setParent(QtParent, Qt.Window) 70 | if self.core.useOnTop: 71 | origin.messageParent.setWindowFlags( 72 | origin.messageParent.windowFlags() ^ Qt.WindowStaysOnTopHint 73 | ) 74 | 75 | # origin.startasThread() 76 | 77 | return False 78 | 79 | # @err_catcher(name=__name__) 80 | # def prismSettings_loadUI(self, settings, tab): 81 | # # get executable path 82 | # tab.setLayout(QVBoxLayout()) 83 | # return "" 84 | 85 | @err_catcher(name=__name__) 86 | def autosaveEnabled(self, origin): 87 | # get autosave enabled 88 | return False 89 | 90 | @err_catcher(name=__name__) 91 | def onProjectChanged(self, origin): 92 | pass 93 | 94 | @err_catcher(name=__name__) 95 | def sceneOpen(self, origin): 96 | if hasattr(origin, "asThread") and origin.asThread.isRunning(): 97 | origin.startasThread() 98 | 99 | @err_catcher(name=__name__) 100 | def executeScript(self, origin, code, execute=False, logErr=True): 101 | if logErr: 102 | try: 103 | if not execute: 104 | return eval(code) 105 | else: 106 | exec(code) 107 | except Exception as e: 108 | msg = "\npython code:\n%s" % code 109 | exec("raise type(e), type(e)(e.message + msg), sys.exc_info()[2]") 110 | else: 111 | try: 112 | if not execute: 113 | return eval(code) 114 | else: 115 | exec(code) 116 | except: 117 | pass 118 | 119 | @err_catcher(name=__name__) 120 | def getCurrentFileName(self, origin, path=True): 121 | return "" 122 | 123 | @err_catcher(name=__name__) 124 | def getSceneExtension(self, origin): 125 | return self.sceneFormats[0] 126 | 127 | @err_catcher(name=__name__) 128 | def saveScene(self, origin, filepath, details={}): 129 | # save scenefile 130 | return True 131 | 132 | @err_catcher(name=__name__) 133 | def getImportPaths(self, origin): 134 | return [] 135 | 136 | @err_catcher(name=__name__) 137 | def getFrameRange(self, origin): 138 | startframe = 0 139 | endframe = 100 140 | 141 | return [startframe, endframe] 142 | 143 | @err_catcher(name=__name__) 144 | def setFrameRange(self, origin, startFrame, endFrame): 145 | pass 146 | 147 | @err_catcher(name=__name__) 148 | def getFPS(self, origin): 149 | return 60 150 | 151 | @err_catcher(name=__name__) 152 | def setFPS(self, origin, fps): 153 | pass 154 | 155 | @err_catcher(name=__name__) 156 | def getAppVersion(self, origin): 157 | return "1.0" 158 | 159 | @err_catcher(name=__name__) 160 | def onProjectBrowserStartup(self, origin): 161 | # origin.sl_preview.mousePressEvent = origin.sliderDrag 162 | origin.sl_preview.mousePressEvent = origin.sl_preview.origMousePressEvent 163 | 164 | @err_catcher(name=__name__) 165 | def openScene(self, origin, filepath, force=False): 166 | # load scenefile 167 | return True 168 | 169 | @err_catcher(name=__name__) 170 | def correctExt(self, origin, lfilepath): 171 | return lfilepath 172 | 173 | @err_catcher(name=__name__) 174 | def setSaveColor(self, origin, btn): 175 | btn.setPalette(origin.savedPalette) 176 | 177 | @err_catcher(name=__name__) 178 | def clearSaveColor(self, origin, btn): 179 | btn.setPalette(origin.oldPalette) 180 | 181 | @err_catcher(name=__name__) 182 | def setProject_loading(self, origin): 183 | pass 184 | 185 | @err_catcher(name=__name__) 186 | def onPrismSettingsOpen(self, origin): 187 | pass 188 | 189 | @err_catcher(name=__name__) 190 | def createProject_startup(self, origin): 191 | pass 192 | 193 | @err_catcher(name=__name__) 194 | def editShot_startup(self, origin): 195 | pass 196 | 197 | @err_catcher(name=__name__) 198 | def shotgunPublish_startup(self, origin): 199 | pass 200 | 201 | @err_catcher(name=__name__) 202 | def sm_export_addObjects(self, origin, objects=None): 203 | if not objects: 204 | objects = [] # get selected objects from scene 205 | 206 | for i in objects: 207 | if not i in origin.nodes: 208 | origin.nodes.append(i) 209 | 210 | origin.updateUi() 211 | origin.stateManager.saveStatesToScene() 212 | 213 | @err_catcher(name=__name__) 214 | def getNodeName(self, origin, node): 215 | if self.isNodeValid(origin, node): 216 | try: 217 | return node.name 218 | except: 219 | QMessageBox.warning( 220 | self.core.messageParent, "Warning", "Cannot get name from %s" % node 221 | ) 222 | return node 223 | else: 224 | return "invalid" 225 | 226 | @err_catcher(name=__name__) 227 | def selectNodes(self, origin): 228 | if origin.lw_objects.selectedItems() != []: 229 | nodes = [] 230 | for i in origin.lw_objects.selectedItems(): 231 | node = origin.nodes[origin.lw_objects.row(i)] 232 | if self.isNodeValid(origin, node): 233 | nodes.append(node) 234 | # select(nodes) 235 | 236 | @err_catcher(name=__name__) 237 | def isNodeValid(self, origin, handle): 238 | return True 239 | 240 | @err_catcher(name=__name__) 241 | def getCamNodes(self, origin, cur=False): 242 | sceneCams = [] # get cams from scene 243 | if cur: 244 | sceneCams = ["Current View"] + sceneCams 245 | 246 | return sceneCams 247 | 248 | @err_catcher(name=__name__) 249 | def getCamName(self, origin, handle): 250 | if handle == "Current View": 251 | return handle 252 | 253 | return str(nodes[0]) 254 | 255 | @err_catcher(name=__name__) 256 | def selectCam(self, origin): 257 | if self.isNodeValid(origin, origin.curCam): 258 | select(origin.curCam) 259 | 260 | @err_catcher(name=__name__) 261 | def sm_export_startup(self, origin): 262 | pass 263 | 264 | # @err_catcher(name=__name__) 265 | # def sm_export_setTaskText(self, origin, prevTaskName, newTaskName): 266 | # origin.l_taskName.setText(newTaskName) 267 | 268 | @err_catcher(name=__name__) 269 | def sm_export_removeSetItem(self, origin, node): 270 | pass 271 | 272 | @err_catcher(name=__name__) 273 | def sm_export_clearSet(self, origin): 274 | pass 275 | 276 | @err_catcher(name=__name__) 277 | def sm_export_updateObjects(self, origin): 278 | pass 279 | 280 | @err_catcher(name=__name__) 281 | def sm_export_exportShotcam(self, origin, startFrame, endFrame, outputName): 282 | result = self.sm_export_exportAppObjects( 283 | origin, 284 | startFrame, 285 | endFrame, 286 | (outputName + ".abc"), 287 | nodes=[origin.curCam], 288 | expType=".abc", 289 | ) 290 | result = self.sm_export_exportAppObjects( 291 | origin, 292 | startFrame, 293 | endFrame, 294 | (outputName + ".fbx"), 295 | nodes=[origin.curCam], 296 | expType=".fbx", 297 | ) 298 | return result 299 | 300 | @err_catcher(name=__name__) 301 | def sm_export_exportAppObjects( 302 | self, 303 | origin, 304 | startFrame, 305 | endFrame, 306 | outputName, 307 | scaledExport=False, 308 | nodes=None, 309 | expType=None, 310 | ): 311 | pass 312 | 313 | @err_catcher(name=__name__) 314 | def sm_export_preDelete(self, origin): 315 | pass 316 | 317 | @err_catcher(name=__name__) 318 | def sm_export_unColorObjList(self, origin): 319 | origin.lw_objects.setStyleSheet( 320 | "QListWidget { border: 3px solid rgb(50,50,50); }" 321 | ) 322 | 323 | @err_catcher(name=__name__) 324 | def sm_export_typeChanged(self, origin, idx): 325 | pass 326 | 327 | @err_catcher(name=__name__) 328 | def sm_export_preExecute(self, origin, startFrame, endFrame): 329 | warnings = [] 330 | 331 | return warnings 332 | 333 | @err_catcher(name=__name__) 334 | def sm_export_loadData(self, origin, data): 335 | pass 336 | 337 | @err_catcher(name=__name__) 338 | def sm_export_getStateProps(self, origin, stateProps): 339 | stateProps.update() 340 | 341 | return stateProps 342 | 343 | @err_catcher(name=__name__) 344 | def sm_render_isVray(self, origin): 345 | return False 346 | 347 | @err_catcher(name=__name__) 348 | def sm_render_setVraySettings(self, origin): 349 | pass 350 | 351 | @err_catcher(name=__name__) 352 | def sm_render_startup(self, origin): 353 | pass 354 | 355 | @err_catcher(name=__name__) 356 | def sm_render_getRenderLayer(self, origin): 357 | rlayerNames = [] 358 | 359 | return rlayerNames 360 | 361 | @err_catcher(name=__name__) 362 | def sm_render_refreshPasses(self, origin): 363 | pass 364 | 365 | @err_catcher(name=__name__) 366 | def sm_render_openPasses(self, origin, item=None): 367 | pass 368 | 369 | @err_catcher(name=__name__) 370 | def removeAOV(self, aovName): 371 | pass 372 | 373 | @err_catcher(name=__name__) 374 | def sm_render_preSubmit(self, origin, rSettings): 375 | pass 376 | 377 | @err_catcher(name=__name__) 378 | def sm_render_startLocalRender(self, origin, outputName, rSettings): 379 | pass 380 | 381 | @err_catcher(name=__name__) 382 | def sm_render_undoRenderSettings(self, origin, rSettings): 383 | pass 384 | 385 | @err_catcher(name=__name__) 386 | def sm_render_getDeadlineParams(self, origin, dlParams, homeDir): 387 | pass 388 | 389 | @err_catcher(name=__name__) 390 | def getCurrentRenderer(self, origin): 391 | return "Renderer" 392 | 393 | @err_catcher(name=__name__) 394 | def getCurrentSceneFiles(self, origin): 395 | curFileName = self.core.getCurrentFileName() 396 | scenefiles = [curFileName] 397 | return scenefiles 398 | 399 | @err_catcher(name=__name__) 400 | def sm_render_getRenderPasses(self, origin): 401 | return [] 402 | 403 | @err_catcher(name=__name__) 404 | def sm_render_addRenderPass(self, origin, passName, steps): 405 | pass 406 | 407 | @err_catcher(name=__name__) 408 | def sm_render_preExecute(self, origin): 409 | warnings = [] 410 | 411 | return warnings 412 | 413 | @err_catcher(name=__name__) 414 | def sm_render_fixOutputPath(self, origin, outputName): 415 | return outputName 416 | 417 | @err_catcher(name=__name__) 418 | def getProgramVersion(self, origin): 419 | return "1.0" 420 | 421 | @err_catcher(name=__name__) 422 | def sm_render_getDeadlineSubmissionParams(self, origin, dlParams, jobOutputFile): 423 | dlParams["Build"] = dlParams["build"] 424 | dlParams["OutputFilePath"] = os.path.split(jobOutputFile)[0] 425 | dlParams["OutputFilePrefix"] = os.path.splitext( 426 | os.path.basename(jobOutputFile) 427 | )[0] 428 | dlParams["Renderer"] = self.getCurrentRenderer(origin) 429 | 430 | if origin.chb_resOverride.isChecked() and "resolution" in dlParams: 431 | resString = "Image" 432 | dlParams[resString + "Width"] = str(origin.sp_resWidth.value()) 433 | dlParams[resString + "Height"] = str(origin.sp_resHeight.value()) 434 | 435 | return dlParams 436 | 437 | @err_catcher(name=__name__) 438 | def deleteNodes(self, origin, handles, num=0): 439 | pass 440 | 441 | @err_catcher(name=__name__) 442 | def sm_import_startup(self, origin): 443 | pass 444 | 445 | @err_catcher(name=__name__) 446 | def sm_import_disableObjectTracking(self, origin): 447 | self.deleteNodes(origin, [origin.setName]) 448 | 449 | @err_catcher(name=__name__) 450 | def sm_import_importToApp(self, origin, doImport, update, impFileName): 451 | return {"result": result, "doImport": doImport} 452 | 453 | @err_catcher(name=__name__) 454 | def sm_import_updateObjects(self, origin): 455 | pass 456 | 457 | @err_catcher(name=__name__) 458 | def sm_import_removeNameSpaces(self, origin): 459 | pass 460 | 461 | @err_catcher(name=__name__) 462 | def sm_import_unitConvert(self, origin): 463 | pass 464 | 465 | @err_catcher(name=__name__) 466 | def sm_playblast_startup(self, origin): 467 | frange = self.getFrameRange(origin) 468 | origin.sp_rangeStart.setValue(frange[0]) 469 | origin.sp_rangeEnd.setValue(frange[1]) 470 | 471 | @err_catcher(name=__name__) 472 | def sm_playblast_createPlayblast(self, origin, jobFrames, outputName): 473 | pass 474 | 475 | @err_catcher(name=__name__) 476 | def sm_playblast_preExecute(self, origin): 477 | warnings = [] 478 | 479 | return warnings 480 | 481 | @err_catcher(name=__name__) 482 | def sm_playblast_execute(self, origin): 483 | pass 484 | 485 | @err_catcher(name=__name__) 486 | def sm_playblast_postExecute(self, origin): 487 | pass 488 | 489 | @err_catcher(name=__name__) 490 | def onStateManagerOpen(self, origin): 491 | pass 492 | 493 | @err_catcher(name=__name__) 494 | def sm_saveStates(self, origin, buf): 495 | pass 496 | 497 | @err_catcher(name=__name__) 498 | def sm_saveImports(self, origin, importPaths): 499 | pass 500 | 501 | @err_catcher(name=__name__) 502 | def sm_readStates(self, origin): 503 | return [] 504 | 505 | @err_catcher(name=__name__) 506 | def sm_deleteStates(self, origin): 507 | pass 508 | 509 | @err_catcher(name=__name__) 510 | def sm_getExternalFiles(self, origin): 511 | extFiles = [] 512 | return [extFiles, []] 513 | 514 | @err_catcher(name=__name__) 515 | def sm_createRenderPressed(self, origin): 516 | origin.createPressed("Render") 517 | -------------------------------------------------------------------------------- /Scripts/Prism_UnrealEngine_Integration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | #################################################### 4 | # 5 | # PRISM - Pipeline for animation and VFX projects 6 | # 7 | # www.prism-pipeline.com 8 | # 9 | # contact: contact@prism-pipeline.com 10 | # 11 | #################################################### 12 | # 13 | # 14 | # Copyright (C) 2016-2020 Richard Frangenberg 15 | # 16 | # Licensed under GNU GPL-3.0-or-later 17 | # 18 | # This file is part of Prism. 19 | # 20 | # Prism is free software: you can redistribute it and/or modify 21 | # it under the terms of the GNU General Public License as published by 22 | # the Free Software Foundation, either version 3 of the License, or 23 | # (at your option) any later version. 24 | # 25 | # Prism is distributed in the hope that it will be useful, 26 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | # GNU General Public License for more details. 29 | # 30 | # You should have received a copy of the GNU General Public License 31 | # along with Prism. If not, see . 32 | 33 | 34 | import os 35 | import sys 36 | import platform 37 | import shutil 38 | 39 | try: 40 | from PySide2.QtCore import * 41 | from PySide2.QtGui import * 42 | from PySide2.QtWidgets import * 43 | except: 44 | from PySide.QtCore import * 45 | from PySide.QtGui import * 46 | 47 | from PrismUtils.Decorators import err_catcher_plugin as err_catcher 48 | 49 | 50 | class Prism_UnrealEngine_Integration(object): 51 | def __init__(self, core, plugin): 52 | self.core = core 53 | self.plugin = plugin 54 | 55 | if platform.system() == "Windows": 56 | self.examplePath = ( 57 | r"C:\Program Files\Epic Games\UE_4.26" 58 | ) 59 | elif platform.system() == "Linux": 60 | userName = ( 61 | os.environ["SUDO_USER"] 62 | if "SUDO_USER" in os.environ 63 | else os.environ["USER"] 64 | ) 65 | self.examplePath = os.path.join("/home", userName, "Epic Games", "UE_4.26") 66 | elif platform.system() == "Darwin": 67 | userName = ( 68 | os.environ["SUDO_USER"] 69 | if "SUDO_USER" in os.environ 70 | else os.environ["USER"] 71 | ) 72 | self.examplePath = ( 73 | "/Users/%s/Library/Preferences/Epic Games/UE_4.26" % userName 74 | ) 75 | 76 | @err_catcher(name=__name__) 77 | def getExecutable(self): 78 | execPath = "" 79 | if platform.system() == "Windows": 80 | defaultpath = os.path.join(self.getUnrealEnginePath(), "UE4_Editor.exe") 81 | if os.path.exists(defaultpath): 82 | execPath = defaultpath 83 | 84 | return execPath 85 | 86 | 87 | @err_catcher(name=__name__) 88 | def getUnrealEnginePath(self): 89 | # get executable path 90 | return "" 91 | 92 | def addIntegration(self, installPath): 93 | try: 94 | integrationBase = os.path.join( 95 | os.path.dirname(os.path.dirname(__file__)), "Integration" 96 | ) 97 | addedFiles = [] 98 | 99 | initpath = os.path.join(installPath, "scripts", "PrismInit.py") 100 | 101 | if os.path.exists(initpath): 102 | os.remove(initpath) 103 | 104 | if os.path.exists(initpath + "c"): 105 | os.remove(initpath + "c") 106 | 107 | origInitFile = os.path.join(integrationBase, "PrismInit.py") 108 | shutil.copy2(origInitFile, initpath) 109 | addedFiles.append(initpath) 110 | 111 | with open(initpath, "r") as init: 112 | initStr = init.read() 113 | 114 | with open(initpath, "w") as init: 115 | initStr = initStr.replace( 116 | "PRISMROOT", '"%s"' % self.core.prismRoot.replace("\\", "/") 117 | ) 118 | init.write(initStr) 119 | 120 | if platform.system() in ["Linux", "Darwin"]: 121 | for i in addedFiles: 122 | os.chmod(i, 0o777) 123 | 124 | return True 125 | 126 | except Exception as e: 127 | exc_type, exc_obj, exc_tb = sys.exc_info() 128 | 129 | msgStr = ( 130 | "Errors occurred during the installation of the UnrealEngine integration.\nThe installation is possibly incomplete.\n\n%s\n%s\n%s" 131 | % (str(e), exc_type, exc_tb.tb_lineno) 132 | ) 133 | msgStr += "\n\nRunning this application as administrator could solve this problem eventually." 134 | 135 | QMessageBox.warning(self.core.messageParent, "Prism Integration", msgStr) 136 | return False 137 | 138 | def removeIntegration(self, installPath): 139 | try: 140 | initPy = os.path.join(installPath, "scripts", "PrismInit.py") 141 | initPyc = os.path.join(installPath, "scripts", "PrismInit.pyc") 142 | shelfpath = os.path.join(installPath, "prefs", "shelves", "shelf_Prism.mel") 143 | 144 | for i in [initPy, initPyc, shelfpath]: 145 | if os.path.exists(i): 146 | os.remove(i) 147 | 148 | userSetup = os.path.join(installPath, "scripts", "userSetup.py") 149 | self.core.integration.removeIntegrationData(filepath=userSetup) 150 | 151 | return True 152 | 153 | except Exception as e: 154 | exc_type, exc_obj, exc_tb = sys.exc_info() 155 | 156 | msgStr = ( 157 | "Errors occurred during the removal of the UnrealEngine integration.\n\n%s\n%s\n%s" 158 | % (str(e), exc_type, exc_tb.tb_lineno) 159 | ) 160 | msgStr += "\n\nRunning this application as administrator could solve this problem eventually." 161 | 162 | QMessageBox.warning(self.core.messageParent, "Prism Integration", msgStr) 163 | return False 164 | 165 | def updateInstallerUI(self, userFolders, pItem): 166 | try: 167 | pluginItem = QTreeWidgetItem([self.plugin.pluginName]) 168 | pItem.addChild(pluginItem) 169 | 170 | pluginPath = self.examplePath 171 | 172 | if pluginPath != None and os.path.exists(pluginPath): 173 | pluginItem.setCheckState(0, Qt.Checked) 174 | pluginItem.setText(1, pluginPath) 175 | pluginItem.setToolTip(0, pluginPath) 176 | else: 177 | pluginItem.setCheckState(0, Qt.Unchecked) 178 | pluginItem.setText(1, "< doubleclick to browse path >") 179 | except Exception as e: 180 | exc_type, exc_obj, exc_tb = sys.exc_info() 181 | msg = QMessageBox.warning( 182 | self.core.messageParent, 183 | "Prism Installation", 184 | "Errors occurred during the installation.\n The installation is possibly incomplete.\n\n%s\n%s\n%s\n%s" 185 | % (__file__, str(e), exc_type, exc_tb.tb_lineno), 186 | ) 187 | return False 188 | 189 | def installerExecute(self, pluginItem, result): 190 | try: 191 | pluginPaths = [] 192 | installLocs = [] 193 | 194 | if pluginItem.checkState(0) != Qt.Checked: 195 | return installLocs 196 | 197 | for i in range(pluginItem.childCount()): 198 | item = pluginItem.child(i) 199 | if item.checkState(0) == Qt.Checked and os.path.exists(item.text(1)): 200 | pluginPaths.append(item.text(1)) 201 | 202 | for i in pluginPaths: 203 | result["UnrealEngine integration"] = self.core.integration.addIntegration(self.plugin.pluginName, path=i, quiet=True) 204 | if result["UnrealEngine integration"]: 205 | installLocs.append(i) 206 | 207 | return installLocs 208 | except Exception as e: 209 | exc_type, exc_obj, exc_tb = sys.exc_info() 210 | msg = QMessageBox.warning( 211 | self.core.messageParent, 212 | "Prism Installation", 213 | "Errors occurred during the installation.\n The installation is possibly incomplete.\n\n%s\n%s\n%s\n%s" 214 | % (__file__, str(e), exc_type, exc_tb.tb_lineno), 215 | ) 216 | return False 217 | -------------------------------------------------------------------------------- /Scripts/Prism_UnrealEngine_Variables.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | #################################################### 4 | # 5 | # PRISM - Pipeline for animation and VFX projects 6 | # 7 | # www.prism-pipeline.com 8 | # 9 | # contact: contact@prism-pipeline.com 10 | # 11 | #################################################### 12 | # 13 | # 14 | # Copyright (C) 2016-2020 Richard Frangenberg 15 | # 16 | # Licensed under GNU GPL-3.0-or-later 17 | # 18 | # This file is part of Prism. 19 | # 20 | # Prism is free software: you can redistribute it and/or modify 21 | # it under the terms of the GNU General Public License as published by 22 | # the Free Software Foundation, either version 3 of the License, or 23 | # (at your option) any later version. 24 | # 25 | # Prism is distributed in the hope that it will be useful, 26 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | # GNU General Public License for more details. 29 | # 30 | # You should have received a copy of the GNU General Public License 31 | # along with Prism. If not, see . 32 | 33 | 34 | class Prism_UnrealEngine_Variables(object): 35 | def __init__(self, core, plugin): 36 | self.version = "v1.3.0.0" 37 | self.pluginName = "UnrealEngine" 38 | self.pluginType = "App" 39 | self.appShortName = "UE4" 40 | self.appType = "3d" 41 | self.hasQtParent = False 42 | self.sceneFormats = [".uprismasset"] 43 | self.appSpecificFormats = self.sceneFormats 44 | self.outputFormats = [".uasset",".abc", ".obj", ".fbx"] 45 | self.appColor = [255, 255, 255] 46 | self.appVersionPresets = ["4.25", "4.26"] 47 | self.renderPasses = [] 48 | self.hasFrameRange = False 49 | self.canOverrideExecuteable = False 50 | self.platforms = ["Windows", "Linux", "Darwin"] 51 | -------------------------------------------------------------------------------- /Scripts/Prism_UnrealEngine_externalAccess_Functions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | #################################################### 4 | # 5 | # PRISM - Pipeline for animation and VFX projects 6 | # 7 | # www.prism-pipeline.com 8 | # 9 | # contact: contact@prism-pipeline.com 10 | # 11 | #################################################### 12 | # 13 | # 14 | # Copyright (C) 2016-2020 Richard Frangenberg 15 | # 16 | # Licensed under GNU GPL-3.0-or-later 17 | # 18 | # This file is part of Prism. 19 | # 20 | # Prism is free software: you can redistribute it and/or modify 21 | # it under the terms of the GNU General Public License as published by 22 | # the Free Software Foundation, either version 3 of the License, or 23 | # (at your option) any later version. 24 | # 25 | # Prism is distributed in the hope that it will be useful, 26 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | # GNU General Public License for more details. 29 | # 30 | # You should have received a copy of the GNU General Public License 31 | # along with Prism. If not, see . 32 | 33 | 34 | from asyncio.log import logger 35 | import os 36 | import platform 37 | 38 | try: 39 | from PySide2.QtCore import * 40 | from PySide2.QtGui import * 41 | from PySide2.QtWidgets import * 42 | except: 43 | from PySide.QtCore import * 44 | from PySide.QtGui import * 45 | 46 | from PrismUtils.Decorators import err_catcher_plugin as err_catcher 47 | 48 | 49 | class Prism_UnrealEngine_externalAccess_Functions(object): 50 | def __init__(self, core, plugin): 51 | self.core = core 52 | self.plugin = plugin 53 | self.callbacks = [] 54 | # self.registerCallbacks() 55 | self._editorpath = "" 56 | self._projectpath = "" 57 | 58 | @property 59 | def editor(self): 60 | return self._editorpath 61 | 62 | @property 63 | def project(self): 64 | return self._projectpath 65 | 66 | @err_catcher(name=__name__) 67 | def setEditorPath(self, value): 68 | self._editorpath = value 69 | 70 | @err_catcher(name=__name__) 71 | def setProjectPath(self, value): 72 | self._projectpath = value 73 | 74 | @err_catcher(name=__name__) 75 | def registerCallbacks(self): 76 | self.callbacks.append(self.core.registerCallback("prismSettings_loadUI", self.prismSettings_loadUI)) 77 | 78 | @err_catcher(name=__name__) 79 | def getAutobackPath(self, origin, tab): 80 | autobackpath = "" 81 | fileStr = "Unreal Engine Scene File (" 82 | for i in self.sceneFormats: 83 | fileStr += "*%s " % i 84 | 85 | fileStr += ")" 86 | 87 | return autobackpath, fileStr 88 | 89 | @err_catcher(name=__name__) 90 | def projectBrowser_loadUI(self, origin): 91 | if self.core.appPlugin.pluginName == "Standalone": 92 | psMenu = QMenu("Unreal Engine") 93 | psAction = QAction("Connect", origin) 94 | psAction.triggered.connect(lambda: self.connectToUnrealEngine(origin)) 95 | psMenu.addAction(psAction) 96 | origin.menuTools.insertSeparator(origin.menuTools.actions()[-2]) 97 | origin.menuTools.insertMenu(origin.menuTools.actions()[-2], psMenu) 98 | 99 | @err_catcher(name=__name__) 100 | def resolvePath(self, filepath): 101 | filepath = self.core.fixPath(filepath) 102 | result = self.core.callback(name="sc_resolvePath", types=["custom", "prjManagers"], args=[self, filepath], **{"as_depot_file": True}) 103 | filepath = result if result else filepath 104 | 105 | @err_catcher(name=__name__) 106 | def browse(self, origin, getFile=False, windowTitle="Browse", fStr="All files (*)", uiEdit=None, subscribe_callable=None): 107 | browse_dir = getattr(uiEdit, "text", lambda : "")() 108 | if getFile: 109 | selectedPath = QFileDialog.getOpenFileName( 110 | origin, windowTitle, browse_dir, fStr 111 | )[0] 112 | else: 113 | selectedPath = QFolderDialog.getExistingDirectory( 114 | origin, windowTitle, browse_dir 115 | )[0] 116 | 117 | if getattr(uiEdit, "setText"): 118 | uiEdit.setText(self.resolvePath(selectedPath)) 119 | if subscribe_callable: 120 | try: 121 | subscribe_callable(self.resolvePath(selectedPath)) 122 | except Exception as why: 123 | logger.error(why) 124 | 125 | 126 | @err_catcher(name=__name__) 127 | def prismSettings_loadUI(self, origin, tab=None): 128 | 129 | w_ue4edior = QWidget() 130 | w_ue4edior.setLayout(QHBoxLayout()) 131 | l_ue4editor = QLabel("UE4 editor path") 132 | origin.le_ue4editor = QLineEdit() 133 | origin.le_ue4editor.setText(self.editor) 134 | b_ue4editor = QPushButton("...") 135 | w_ue4edior.layout().addWidget(l_ue4editor) 136 | w_ue4edior.layout().addWidget(origin.le_ue4editor) 137 | w_ue4edior.layout().addWidget(b_ue4editor) 138 | 139 | w_ue4project = QWidget() 140 | w_ue4project.setLayout(QHBoxLayout()) 141 | l_ue4project = QLabel("UE4 project path") 142 | origin.le_ue4project = QLineEdit() 143 | origin.le_ue4project.setText(self.project) 144 | b_ue4project = QPushButton("...") 145 | w_ue4project.layout().addWidget(l_ue4project) 146 | w_ue4project.layout().addWidget(origin.le_ue4project) 147 | w_ue4project.layout().addWidget(b_ue4project) 148 | 149 | # tab.layout().addWidget(w_ue4edior) 150 | tab.layout().addWidget(w_ue4project) 151 | 152 | origin.le_ue4editor.editingFinished.connect(self.setEditorPath) 153 | origin.le_ue4project.editingFinished.connect(self.setProjectPath) 154 | 155 | b_ue4editor.clicked.connect(lambda : self.browse(origin, True, "Select UE4-Editor.exe", "Executable (*.exe)", origin.le_ue4editor, self.setEditorPath)) 156 | b_ue4project.clicked.connect(lambda : self.browse(origin, True, "Select Uproject file", "Uproject (*.uproject)", origin.le_ue4project, self.setProjectPath)) 157 | 158 | return "" 159 | 160 | @err_catcher(name=__name__) 161 | def prismSettings_savePrjSettings(self, origin, settings): 162 | if "unrealengine" not in settings: 163 | settings["unrealengine"] = {} 164 | if hasattr(origin, "le_ue4editor"): 165 | settings["unrealengine"]["editor"] = self.editor 166 | if hasattr(origin, "le_ue4project"): 167 | settings["unrealengine"]["uproject"] = self.project 168 | 169 | @err_catcher(name=__name__) 170 | def prismSettings_loadPrjSettings(self, origin, settings): 171 | if "unrealengine" in settings: 172 | if "uproject" in settings["unrealengine"]: 173 | self.setProjectPath(settings["unrealengine"]["uproject"]) 174 | if "editor" in settings["unrealengine"]: 175 | self.setEditorPath(settings["unrealengine"]["editor"]) 176 | 177 | @err_catcher(name=__name__) 178 | def customizeExecutable(self, origin, appPath, filepath): 179 | # self.connectToUnrealEngine(origin, filepath=filepath) 180 | fileStarted = False 181 | # if self.core.getConfig("nuke", "usenukex"): 182 | # if appPath == "": 183 | # if not hasattr(self, "nukePath"): 184 | # self.getNukePath(origin) 185 | 186 | # if self.nukePath is not None and os.path.exists(self.nukePath): 187 | # appPath = self.nukePath 188 | # else: 189 | # QMessageBox.warning( 190 | # self.core.messageParent, 191 | # "Warning", 192 | # "Nuke executable doesn't exist:\n\n%s" % self.nukePath, 193 | # ) 194 | 195 | # if appPath is not None and appPath != "": 196 | # subprocess.Popen([appPath, "--nukex", self.core.fixPath(filepath)]) 197 | # fileStarted = True 198 | 199 | return False 200 | 201 | @err_catcher(name=__name__) 202 | def copySceneFile(self, origin, origFile, targetPath, mode="copy"): 203 | pass 204 | 205 | @err_catcher(name=__name__) 206 | def onProjectCreated(self, origin, projectPath, projectName): 207 | pass 208 | 209 | @err_catcher(name=__name__) 210 | def connectToUnrealEngine(self, origin, filepath=""): 211 | pass -------------------------------------------------------------------------------- /Scripts/Prism_UnrealEngine_init.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | #################################################### 4 | # 5 | # PRISM - Pipeline for animation and VFX projects 6 | # 7 | # www.prism-pipeline.com 8 | # 9 | # contact: contact@prism-pipeline.com 10 | # 11 | #################################################### 12 | # 13 | # 14 | # Copyright (C) 2016-2020 Richard Frangenberg 15 | # 16 | # Licensed under GNU GPL-3.0-or-later 17 | # 18 | # This file is part of Prism. 19 | # 20 | # Prism is free software: you can redistribute it and/or modify 21 | # it under the terms of the GNU General Public License as published by 22 | # the Free Software Foundation, either version 3 of the License, or 23 | # (at your option) any later version. 24 | # 25 | # Prism is distributed in the hope that it will be useful, 26 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | # GNU General Public License for more details. 29 | # 30 | # You should have received a copy of the GNU General Public License 31 | # along with Prism. If not, see . 32 | 33 | 34 | from Prism_UnrealEngine_Variables import Prism_UnrealEngine_Variables 35 | from Prism_UnrealEngine_externalAccess_Functions import ( 36 | Prism_UnrealEngine_externalAccess_Functions, 37 | ) 38 | from Prism_UnrealEngine_Functions import Prism_UnrealEngine_Functions 39 | from Prism_UnrealEngine_Integration import Prism_UnrealEngine_Integration 40 | 41 | 42 | class Prism_Plugin_UnrealEngine( 43 | Prism_UnrealEngine_Variables, 44 | Prism_UnrealEngine_externalAccess_Functions, 45 | Prism_UnrealEngine_Functions, 46 | Prism_UnrealEngine_Integration, 47 | ): 48 | def __init__(self, core): 49 | Prism_UnrealEngine_Variables.__init__(self, core, self) 50 | Prism_UnrealEngine_externalAccess_Functions.__init__(self, core, self) 51 | Prism_UnrealEngine_Functions.__init__(self, core, self) 52 | Prism_UnrealEngine_Integration.__init__(self, core, self) 53 | -------------------------------------------------------------------------------- /Scripts/Prism_UnrealEngine_init_unloaded.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | #################################################### 4 | # 5 | # PRISM - Pipeline for animation and VFX projects 6 | # 7 | # www.prism-pipeline.com 8 | # 9 | # contact: contact@prism-pipeline.com 10 | # 11 | #################################################### 12 | # 13 | # 14 | # Copyright (C) 2016-2020 Richard Frangenberg 15 | # 16 | # Licensed under GNU GPL-3.0-or-later 17 | # 18 | # This file is part of Prism. 19 | # 20 | # Prism is free software: you can redistribute it and/or modify 21 | # it under the terms of the GNU General Public License as published by 22 | # the Free Software Foundation, either version 3 of the License, or 23 | # (at your option) any later version. 24 | # 25 | # Prism is distributed in the hope that it will be useful, 26 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | # GNU General Public License for more details. 29 | # 30 | # You should have received a copy of the GNU General Public License 31 | # along with Prism. If not, see . 32 | 33 | 34 | from Prism_UnrealEngine_Variables import Prism_UnrealEngine_Variables 35 | from Prism_UnrealEngine_externalAccess_Functions import ( 36 | Prism_UnrealEngine_externalAccess_Functions, 37 | ) 38 | from Prism_UnrealEngine_Integration import Prism_UnrealEngine_Integration 39 | 40 | 41 | class Prism_UnrealEngine_unloaded( 42 | Prism_UnrealEngine_Variables, 43 | Prism_UnrealEngine_externalAccess_Functions, 44 | Prism_UnrealEngine_Integration, 45 | ): 46 | def __init__(self, core): 47 | Prism_UnrealEngine_Variables.__init__(self, core, self) 48 | Prism_UnrealEngine_externalAccess_Functions.__init__(self, core, self) 49 | Prism_UnrealEngine_Integration.__init__(self, core, self) 50 | -------------------------------------------------------------------------------- /external_modules/remote_execution.py: -------------------------------------------------------------------------------- 1 | # Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | import sys as _sys 4 | import json as _json 5 | import uuid as _uuid 6 | import time as _time 7 | import socket as _socket 8 | import logging as _logging 9 | import threading as _threading 10 | 11 | def hello(): 12 | _logging.debug("Hello from remote") 13 | 14 | # Protocol constants (see PythonScriptRemoteExecution.cpp for the full protocol definition) 15 | _PROTOCOL_VERSION = 1 # Protocol version number 16 | _PROTOCOL_MAGIC = 'ue_py' # Protocol magic identifier 17 | _TYPE_PING = 'ping' # Service discovery request (UDP) 18 | _TYPE_PONG = 'pong' # Service discovery response (UDP) 19 | _TYPE_OPEN_CONNECTION = 'open_connection' # Open a TCP command connection with the requested server (UDP) 20 | _TYPE_CLOSE_CONNECTION = 'close_connection' # Close any active TCP command connection (UDP) 21 | _TYPE_COMMAND = 'command' # Execute a remote Python command (TCP) 22 | _TYPE_COMMAND_RESULT = 'command_result' # Result of executing a remote Python command (TCP) 23 | 24 | _NODE_PING_SECONDS = 1 # Number of seconds to wait before sending another "ping" message to discover remote notes 25 | _NODE_TIMEOUT_SECONDS = 5 # Number of seconds to wait before timing out a remote node that was discovered via UDP and has stopped sending "pong" responses 26 | 27 | DEFAULT_MULTICAST_TTL = 0 # Multicast TTL (0 is limited to the local host, 1 is limited to the local subnet) 28 | DEFAULT_MULTICAST_GROUP_ENDPOINT = ('239.0.0.1', 29 | 6766) # The multicast group endpoint tuple that the UDP multicast socket should join (must match the "Multicast Group Endpoint" setting in the Python plugin) 30 | DEFAULT_MULTICAST_BIND_ADDRESS = '0.0.0.0' # The adapter address that the UDP multicast socket should bind to, or 0.0.0.0 to bind to all adapters (must match the "Multicast Bind Address" setting in the Python plugin) 31 | DEFAULT_COMMAND_ENDPOINT = ('127.0.0.1', 32 | 6776) # The endpoint tuple for the TCP command connection hosted by this client (that the remote client will connect to) 33 | 34 | # Execution modes (these must match the names given to LexToString for EPythonCommandExecutionMode in IPythonScriptPlugin.h) 35 | MODE_EXEC_FILE = 'ExecuteFile' # Execute the Python command as a file. This allows you to execute either a literal Python script containing multiple statements, or a file with optional arguments 36 | MODE_EXEC_STATEMENT = 'ExecuteStatement' # Execute the Python command as a single statement. This will execute a single statement and print the result. This mode cannot run files 37 | MODE_EVAL_STATEMENT = 'EvaluateStatement' # Evaluate the Python command as a single statement. This will evaluate a single statement and return the result. This mode cannot run files 38 | 39 | 40 | class RemoteExecutionConfig(object): 41 | ''' 42 | Configuration data for establishing a remote connection with a UE4 instance running Python. 43 | ''' 44 | 45 | def __init__(self): 46 | self.multicast_ttl = DEFAULT_MULTICAST_TTL 47 | self.multicast_group_endpoint = DEFAULT_MULTICAST_GROUP_ENDPOINT 48 | self.multicast_bind_address = DEFAULT_MULTICAST_BIND_ADDRESS 49 | self.command_endpoint = DEFAULT_COMMAND_ENDPOINT 50 | 51 | 52 | class RemoteExecution(object): 53 | ''' 54 | A remote execution session. This class can discover remote "nodes" (UE4 instances running Python), and allow you to open a command channel to a particular instance. 55 | 56 | Args: 57 | config (RemoteExecutionConfig): Configuration controlling the connection settings for this session. 58 | ''' 59 | 60 | def __init__(self, config=RemoteExecutionConfig()): 61 | self._config = config 62 | self._broadcast_connection = None 63 | self._command_connection = None 64 | self._node_id = str(_uuid.uuid4()) 65 | 66 | @property 67 | def remote_nodes(self): 68 | ''' 69 | Get the current set of discovered remote "nodes" (UE4 instances running Python). 70 | 71 | Returns: 72 | list: A list of dicts containg the node ID and the other data. 73 | ''' 74 | return self._broadcast_connection.remote_nodes if self._broadcast_connection else [] 75 | 76 | def start(self): 77 | ''' 78 | Start the remote execution session. This will begin the discovey process for remote "nodes" (UE4 instances running Python). 79 | ''' 80 | self._broadcast_connection = _RemoteExecutionBroadcastConnection(self._config, self._node_id) 81 | self._broadcast_connection.open() 82 | 83 | def stop(self): 84 | ''' 85 | Stop the remote execution session. This will end the discovey process for remote "nodes" (UE4 instances running Python), and close any open command connection. 86 | ''' 87 | self.close_command_connection() 88 | if self._broadcast_connection: 89 | self._broadcast_connection.close() 90 | self._broadcast_connection = None 91 | 92 | def has_command_connection(self): 93 | ''' 94 | Check whether the remote execution session has an active command connection. 95 | 96 | Returns: 97 | bool: True if the remote execution session has an active command connection, False otherwise. 98 | ''' 99 | return self._command_connection is not None 100 | 101 | def open_command_connection(self, remote_node_id): 102 | ''' 103 | Open a command connection to the given remote "node" (a UE4 instance running Python), closing any command connection that may currently be open. 104 | 105 | Args: 106 | remote_node_id (string): The ID of the remote node (this can be obtained by querying `remote_nodes`). 107 | ''' 108 | self._command_connection = _RemoteExecutionCommandConnection(self._config, self._node_id, remote_node_id) 109 | self._command_connection.open(self._broadcast_connection) 110 | 111 | def close_command_connection(self): 112 | ''' 113 | Close any command connection that may currently be open. 114 | ''' 115 | if self._command_connection: 116 | self._command_connection.close(self._broadcast_connection) 117 | self._command_connection = None 118 | 119 | def run_command(self, command, unattended=True, exec_mode=MODE_EXEC_FILE, raise_on_failure=False): 120 | ''' 121 | Run a command remotely based on the current command connection. 122 | 123 | Args: 124 | command (string): The Python command to run remotely. 125 | unattended (bool): True to run this command in "unattended" mode (suppressing some UI). 126 | exec_mode (string): The execution mode to use as a string value (must be one of MODE_EXEC_FILE, MODE_EXEC_STATEMENT, or MODE_EVAL_STATEMENT). 127 | raise_on_failure (bool): True to raise a RuntimeError if the command fails on the remote target. 128 | 129 | Returns: 130 | dict: The result from running the remote command (see `command_result` from the protocol definition). 131 | ''' 132 | data = self._command_connection.run_command(command, unattended, exec_mode) 133 | if raise_on_failure and not data['success']: 134 | raise RuntimeError('Remote Python Command failed! {0}'.format(data['result'])) 135 | return data 136 | 137 | 138 | class _RemoteExecutionNode(object): 139 | ''' 140 | A discovered remote "node" (aka, a UE4 instance running Python). 141 | 142 | Args: 143 | data (dict): The data representing this node (from its "pong" reponse). 144 | now (float): The timestamp at which this node was last seen. 145 | ''' 146 | 147 | def __init__(self, data, now=None): 148 | self.data = data 149 | self._last_pong = _time_now(now) 150 | 151 | def should_timeout(self, now=None): 152 | ''' 153 | Check to see whether this remote node should be considered timed-out. 154 | 155 | Args: 156 | now (float): The current timestamp. 157 | 158 | Returns: 159 | bool: True of the node has exceeded the timeout limit (`_NODE_TIMEOUT_SECONDS`), False otherwise. 160 | ''' 161 | return (self._last_pong + _NODE_TIMEOUT_SECONDS) < _time_now(now) 162 | 163 | 164 | class _RemoteExecutionBroadcastNodes(object): 165 | ''' 166 | A thread-safe set of remote execution "nodes" (UE4 instances running Python). 167 | ''' 168 | 169 | def __init__(self): 170 | self._remote_nodes = {} 171 | self._remote_nodes_lock = _threading.RLock() 172 | 173 | @property 174 | def remote_nodes(self): 175 | ''' 176 | Get the current set of discovered remote "nodes" (UE4 instances running Python). 177 | 178 | Returns: 179 | list: A list of dicts containg the node ID and the other data. 180 | ''' 181 | with self._remote_nodes_lock: 182 | remote_nodes_list = [] 183 | for node_id, node in self._remote_nodes.items(): 184 | remote_node_data = dict(node.data) 185 | remote_node_data['node_id'] = node_id 186 | remote_nodes_list.append(remote_node_data) 187 | return remote_nodes_list 188 | 189 | def update_remote_node(self, node_id, node_data, now=None): 190 | ''' 191 | Update a remote node, replacing any existing data. 192 | 193 | Args: 194 | node_id (str): The ID of the remote node (from its "pong" reponse). 195 | node_data (dict): The data representing this node (from its "pong" reponse). 196 | now (float): The timestamp at which this node was last seen. 197 | ''' 198 | now = _time_now(now) 199 | with self._remote_nodes_lock: 200 | if node_id not in self._remote_nodes: 201 | _logger.debug('Found Node {0}: {1}'.format(node_id, node_data)) 202 | self._remote_nodes[node_id] = _RemoteExecutionNode(node_data, now) 203 | 204 | def timeout_remote_nodes(self, now=None): 205 | ''' 206 | Check to see whether any remote nodes should be considered timed-out, and if so, remove them from this set. 207 | 208 | Args: 209 | now (float): The current timestamp. 210 | ''' 211 | now = _time_now(now) 212 | with self._remote_nodes_lock: 213 | for node_id, node in list(self._remote_nodes.items()): 214 | if node.should_timeout(now): 215 | _logger.debug('Lost Node {0}: {1}'.format(node_id, node.data)) 216 | del self._remote_nodes[node_id] 217 | 218 | 219 | class _RemoteExecutionBroadcastConnection(object): 220 | ''' 221 | A remote execution broadcast connection (for UDP based messaging and node discovery). 222 | 223 | Args: 224 | config (RemoteExecutionConfig): Configuration controlling the connection settings. 225 | node_id (string): The ID of the local "node" (this session). 226 | ''' 227 | 228 | def __init__(self, config, node_id): 229 | self._config = config 230 | self._node_id = node_id 231 | self._nodes = None 232 | self._running = False 233 | self._broadcast_socket = None 234 | self._broadcast_listen_thread = None 235 | 236 | @property 237 | def remote_nodes(self): 238 | ''' 239 | Get the current set of discovered remote "nodes" (UE4 instances running Python). 240 | 241 | Returns: 242 | list: A list of dicts containg the node ID and the other data. 243 | ''' 244 | return self._nodes.remote_nodes if self._nodes else [] 245 | 246 | def open(self): 247 | ''' 248 | Open the UDP based messaging and discovery connection. This will begin the discovey process for remote "nodes" (UE4 instances running Python). 249 | ''' 250 | self._running = True 251 | self._last_ping = None 252 | self._nodes = _RemoteExecutionBroadcastNodes() 253 | self._init_broadcast_socket() 254 | self._init_broadcast_listen_thread() 255 | 256 | def close(self): 257 | ''' 258 | Close the UDP based messaging and discovery connection. This will end the discovey process for remote "nodes" (UE4 instances running Python). 259 | ''' 260 | self._running = False 261 | if self._broadcast_listen_thread: 262 | self._broadcast_listen_thread.join() 263 | if self._broadcast_socket: 264 | self._broadcast_socket.close() 265 | self._broadcast_socket = None 266 | self._nodes = None 267 | 268 | def _init_broadcast_socket(self): 269 | ''' 270 | Initialize the UDP based broadcast socket based on the current configuration. 271 | ''' 272 | self._broadcast_socket = _socket.socket(_socket.AF_INET, _socket.SOCK_DGRAM, 273 | _socket.IPPROTO_UDP) # UDP/IP socket 274 | if hasattr(_socket, 'SO_REUSEPORT'): 275 | self._broadcast_socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEPORT, 1) 276 | else: 277 | self._broadcast_socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1) 278 | self._broadcast_socket.bind((self._config.multicast_bind_address, self._config.multicast_group_endpoint[1])) 279 | self._broadcast_socket.setsockopt(_socket.IPPROTO_IP, _socket.IP_MULTICAST_LOOP, 1) 280 | self._broadcast_socket.setsockopt(_socket.IPPROTO_IP, _socket.IP_MULTICAST_TTL, self._config.multicast_ttl) 281 | self._broadcast_socket.setsockopt(_socket.IPPROTO_IP, _socket.IP_ADD_MEMBERSHIP, _socket.inet_aton( 282 | self._config.multicast_group_endpoint[0]) + _socket.inet_aton('0.0.0.0')) 283 | self._broadcast_socket.settimeout(0.1) 284 | 285 | def _init_broadcast_listen_thread(self): 286 | ''' 287 | Initialize the listen thread for the UDP based broadcast socket to allow discovery to run async. 288 | ''' 289 | self._broadcast_listen_thread = _threading.Thread(target=self._run_broadcast_listen_thread) 290 | self._broadcast_listen_thread.daemon = True 291 | self._broadcast_listen_thread.start() 292 | 293 | def _run_broadcast_listen_thread(self): 294 | ''' 295 | Main loop for the listen thread that handles processing discovery messages. 296 | ''' 297 | while self._running: 298 | # Receive and process all pending data 299 | while True: 300 | try: 301 | data = self._broadcast_socket.recv(4096) 302 | except _socket.timeout: 303 | data = None 304 | if data: 305 | self._handle_data(data) 306 | else: 307 | break 308 | # Run tick logic 309 | now = _time_now() 310 | self._broadcast_ping(now) 311 | self._nodes.timeout_remote_nodes(now) 312 | _time.sleep(0.1) 313 | 314 | def _broadcast_message(self, message): 315 | ''' 316 | Broadcast the given message over the UDP socket to anything that might be listening. 317 | 318 | Args: 319 | message (_RemoteExecutionMessage): The message to broadcast. 320 | ''' 321 | self._broadcast_socket.sendto(message.to_json_bytes(), self._config.multicast_group_endpoint) 322 | 323 | def _broadcast_ping(self, now=None): 324 | ''' 325 | Broadcast a "ping" message over the UDP socket to anything that might be listening. 326 | 327 | Args: 328 | now (float): The current timestamp. 329 | ''' 330 | now = _time_now(now) 331 | if not self._last_ping or ((self._last_ping + _NODE_PING_SECONDS) < now): 332 | self._last_ping = now 333 | self._broadcast_message(_RemoteExecutionMessage(_TYPE_PING, self._node_id)) 334 | 335 | def broadcast_open_connection(self, remote_node_id): 336 | ''' 337 | Broadcast an "open_connection" message over the UDP socket to be handled by the specified remote node. 338 | 339 | Args: 340 | remote_node_id (string): The ID of the remote node that we want to open a command connection with. 341 | ''' 342 | self._broadcast_message(_RemoteExecutionMessage(_TYPE_OPEN_CONNECTION, self._node_id, remote_node_id, { 343 | 'command_ip': self._config.command_endpoint[0], 344 | 'command_port': self._config.command_endpoint[1], 345 | })) 346 | 347 | def broadcast_close_connection(self, remote_node_id): 348 | ''' 349 | Broadcast a "close_connection" message over the UDP socket to be handled by the specified remote node. 350 | 351 | Args: 352 | remote_node_id (string): The ID of the remote node that we want to close a command connection with. 353 | ''' 354 | self._broadcast_message(_RemoteExecutionMessage(_TYPE_CLOSE_CONNECTION, self._node_id, remote_node_id)) 355 | 356 | def _handle_data(self, data): 357 | ''' 358 | Handle data received from the UDP broadcast socket. 359 | 360 | Args: 361 | data (bytes): The raw bytes received from the socket. 362 | ''' 363 | message = _RemoteExecutionMessage(None, None) 364 | if message.from_json_bytes(data): 365 | self._handle_message(message) 366 | 367 | def _handle_message(self, message): 368 | ''' 369 | Handle a message received from the UDP broadcast socket. 370 | 371 | Args: 372 | message (_RemoteExecutionMessage): The message received from the socket. 373 | ''' 374 | if not message.passes_receive_filter(self._node_id): 375 | return 376 | if message.type_ == _TYPE_PONG: 377 | self._handle_pong_message(message) 378 | return 379 | _logger.debug('Unhandled remote execution message type "{0}"'.format(message.type_)) 380 | 381 | def _handle_pong_message(self, message): 382 | ''' 383 | Handle a "pong" message received from the UDP broadcast socket. 384 | 385 | Args: 386 | message (_RemoteExecutionMessage): The message received from the socket. 387 | ''' 388 | self._nodes.update_remote_node(message.source, message.data) 389 | 390 | 391 | class _RemoteExecutionCommandConnection(object): 392 | ''' 393 | A remote execution command connection (for TCP based command processing). 394 | 395 | Args: 396 | config (RemoteExecutionConfig): Configuration controlling the connection settings. 397 | node_id (string): The ID of the local "node" (this session). 398 | remote_node_id (string): The ID of the remote "node" (the UE4 instance running Python). 399 | ''' 400 | 401 | def __init__(self, config, node_id, remote_node_id): 402 | self._config = config 403 | self._node_id = node_id 404 | self._remote_node_id = remote_node_id 405 | self._command_listen_socket = None 406 | self._command_channel_socket = _socket.socket() # This type is only here to appease PyLint 407 | 408 | def open(self, broadcast_connection): 409 | ''' 410 | Open the TCP based command connection, and wait to accept the connection from the remote party. 411 | 412 | Args: 413 | broadcast_connection (_RemoteExecutionBroadcastConnection): The broadcast connection to send UDP based messages over. 414 | ''' 415 | self._nodes = _RemoteExecutionBroadcastNodes() 416 | self._init_command_listen_socket() 417 | self._try_accept(broadcast_connection) 418 | 419 | def close(self, broadcast_connection): 420 | ''' 421 | Close the TCP based command connection, attempting to notify the remote party. 422 | 423 | Args: 424 | broadcast_connection (_RemoteExecutionBroadcastConnection): The broadcast connection to send UDP based messages over. 425 | ''' 426 | broadcast_connection.broadcast_close_connection(self._remote_node_id) 427 | if self._command_channel_socket: 428 | self._command_channel_socket.close() 429 | self._command_channel_socket = None 430 | if self._command_listen_socket: 431 | self._command_listen_socket.close() 432 | self._command_listen_socket = None 433 | 434 | def run_command(self, command, unattended, exec_mode): 435 | ''' 436 | Run a command on the remote party. 437 | 438 | Args: 439 | command (string): The Python command to run remotely. 440 | unattended (bool): True to run this command in "unattended" mode (suppressing some UI). 441 | exec_mode (string): The execution mode to use as a string value (must be one of MODE_EXEC_FILE, MODE_EXEC_STATEMENT, or MODE_EVAL_STATEMENT). 442 | 443 | Returns: 444 | dict: The result from running the remote command (see `command_result` from the protocol definition). 445 | ''' 446 | self._send_message(_RemoteExecutionMessage(_TYPE_COMMAND, self._node_id, self._remote_node_id, { 447 | 'command': command, 448 | 'unattended': unattended, 449 | 'exec_mode': exec_mode, 450 | })) 451 | result = self._receive_message(_TYPE_COMMAND_RESULT) 452 | return result.data 453 | 454 | def _send_message(self, message): 455 | ''' 456 | Send the given message over the TCP socket to the remote party. 457 | 458 | Args: 459 | message (_RemoteExecutionMessage): The message to send. 460 | ''' 461 | self._command_channel_socket.sendall(message.to_json_bytes()) 462 | 463 | def _receive_message(self, expected_type): 464 | ''' 465 | Receive a message over the TCP socket from the remote party. 466 | 467 | Args: 468 | expected_type (string): The type of message we expect to receive. 469 | 470 | Returns: 471 | The message that was received. 472 | ''' 473 | data = self._command_channel_socket.recv(4096) 474 | if data: 475 | message = _RemoteExecutionMessage(None, None) 476 | if message.from_json_bytes(data) and message.passes_receive_filter( 477 | self._node_id) and message.type_ == expected_type: 478 | return message 479 | raise RuntimeError('Remote party failed to send a valid response!') 480 | 481 | def _init_command_listen_socket(self): 482 | ''' 483 | Initialize the TCP based command socket based on the current configuration, and set it to listen for an incoming connection. 484 | ''' 485 | self._command_listen_socket = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM, 486 | _socket.IPPROTO_TCP) # TCP/IP socket 487 | if hasattr(_socket, 'SO_REUSEPORT'): 488 | self._command_listen_socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEPORT, 1) 489 | else: 490 | self._command_listen_socket.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1) 491 | self._command_listen_socket.bind(self._config.command_endpoint) 492 | self._command_listen_socket.listen(1) 493 | self._command_listen_socket.settimeout(5) 494 | 495 | def _try_accept(self, broadcast_connection): 496 | ''' 497 | Wait to accept a connection on the TCP based command connection. This makes 6 attempts to receive a connection, waiting for 5 seconds between each attempt (30 seconds total). 498 | 499 | Args: 500 | broadcast_connection (_RemoteExecutionBroadcastConnection): The broadcast connection to send UDP based messages over. 501 | ''' 502 | for _n in range(6): 503 | broadcast_connection.broadcast_open_connection(self._remote_node_id) 504 | try: 505 | self._command_channel_socket = self._command_listen_socket.accept()[0] 506 | self._command_channel_socket.setblocking(True) 507 | return 508 | except _socket.timeout: 509 | continue 510 | raise RuntimeError('Remote party failed to attempt the command socket connection!') 511 | 512 | 513 | class _RemoteExecutionMessage(object): 514 | ''' 515 | A message sent or received by remote execution (on either the UDP or TCP connection), as UTF-8 encoded JSON. 516 | 517 | Args: 518 | type_ (string): The type of this message (see the `_TYPE_` constants). 519 | source (string): The ID of the node that sent this message. 520 | dest (string): The ID of the destination node of this message, or None to send to all nodes (for UDP broadcast). 521 | data (dict): The message specific payload data. 522 | ''' 523 | 524 | def __init__(self, type_, source, dest=None, data=None): 525 | self.type_ = type_ 526 | self.source = source 527 | self.dest = dest 528 | self.data = data 529 | 530 | def passes_receive_filter(self, node_id): 531 | ''' 532 | Test to see whether this message should be received by the current node (wasn't sent to itself, and has a compatible destination ID). 533 | 534 | Args: 535 | node_id (string): The ID of the local "node" (this session). 536 | 537 | Returns: 538 | bool: True if this message should be received by the current node, False otherwise. 539 | ''' 540 | return self.source != node_id and (not self.dest or self.dest == node_id) 541 | 542 | def to_json(self): 543 | ''' 544 | Convert this message to its JSON representation. 545 | 546 | Returns: 547 | str: The JSON representation of this message. 548 | ''' 549 | if not self.type_: 550 | raise ValueError('"type" cannot be empty!') 551 | if not self.source: 552 | raise ValueError('"source" cannot be empty!') 553 | json_obj = { 554 | 'version': _PROTOCOL_VERSION, 555 | 'magic': _PROTOCOL_MAGIC, 556 | 'type': self.type_, 557 | 'source': self.source, 558 | } 559 | if self.dest: 560 | json_obj['dest'] = self.dest 561 | if self.data: 562 | json_obj['data'] = self.data 563 | return _json.dumps(json_obj, ensure_ascii=False) 564 | 565 | def to_json_bytes(self): 566 | ''' 567 | Convert this message to its JSON representation as UTF-8 bytes. 568 | 569 | Returns: 570 | bytes: The JSON representation of this message as UTF-8 bytes. 571 | ''' 572 | json_str = self.to_json() 573 | return json_str.encode('utf-8') 574 | 575 | def from_json(self, json_str): 576 | ''' 577 | Parse this message from its JSON representation. 578 | 579 | Args: 580 | json_str (str): The JSON representation of this message. 581 | 582 | Returns: 583 | bool: True if this message could be parsed, False otherwise. 584 | ''' 585 | try: 586 | json_obj = _json.loads(json_str, encoding='utf-8') 587 | # Read and validate required protocol version information 588 | if json_obj['version'] != _PROTOCOL_VERSION: 589 | raise ValueError( 590 | '"version" is incorrect (got {0}, expected {1})!'.format(json_obj['version'], _PROTOCOL_VERSION)) 591 | if json_obj['magic'] != _PROTOCOL_MAGIC: 592 | raise ValueError( 593 | '"magic" is incorrect (got "{0}", expected "{1}")!'.format(json_obj['magic'], _PROTOCOL_MAGIC)) 594 | # Read required fields 595 | local_type = json_obj['type'] 596 | local_source = json_obj['source'] 597 | self.type_ = local_type 598 | self.source = local_source 599 | # Read optional fields 600 | self.dest = json_obj.get('dest') 601 | self.data = json_obj.get('data') 602 | except Exception as e: 603 | _logger.error('Failed to deserialize JSON "{0}": {1}'.format(json_str, str(e))) 604 | return False 605 | return True 606 | 607 | def from_json_bytes(self, json_bytes): 608 | ''' 609 | Parse this message from its JSON representation as UTF-8 bytes. 610 | 611 | Args: 612 | json_bytes (bytes): The JSON representation of this message as UTF-8 bytes. 613 | 614 | Returns: 615 | bool: True if this message could be parsed, False otherwise. 616 | ''' 617 | json_str = json_bytes.decode('utf-8') 618 | return self.from_json(json_str) 619 | 620 | 621 | def _time_now(now=None): 622 | ''' 623 | Utility function to resolve a potentially cached time value. 624 | 625 | Args: 626 | now (float): The cached timestamp, or None to return the current time. 627 | 628 | Returns: 629 | float: The cached timestamp (if set), otherwise the current time. 630 | ''' 631 | return _time.time() if now is None else now 632 | 633 | 634 | # Log handling 635 | _logger = _logging.getLogger(__name__) 636 | _log_handler = _logging.StreamHandler() 637 | _logger.addHandler(_log_handler) 638 | 639 | 640 | def set_log_level(log_level): 641 | _logger.setLevel(log_level) 642 | _log_handler.setLevel(log_level) 643 | -------------------------------------------------------------------------------- /external_modules/ue4.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import glob 5 | from os.path import basename, dirname, splitext 6 | from subprocess import Popen 7 | import json 8 | from functools import partial 9 | 10 | def get_temp_path(): 11 | p = os.path.join(os.getenv("TMP"), os.path.splitext(os.path.basename(__file__))[0]) 12 | if not os.path.exists(p): 13 | os.makedirs(p) 14 | return p 15 | 16 | 17 | class RenderOutputFormat(object): 18 | JPG = "jpg" 19 | BMP = "bmp" 20 | PNG = "png" 21 | Video = "video" 22 | 23 | class ImportData(object): 24 | def __init__(self): 25 | # self.ImportTranslation= [0,0,0] 26 | # self.ImportRotation = [0,0,0] 27 | # self.ImportUniformScale = [1,1,1] 28 | self.bConvertScene = True 29 | self.bConvertSceneUnit = True 30 | self.bConvertAsScene = True 31 | 32 | class StaticMeshImportData(ImportData): 33 | def __init__(self): 34 | ImportData.__init__(self) 35 | self.bRemoveDegenerates = True 36 | self.bBuildAdjacencyBuffer = True 37 | self.bBuildReversedIndexBuffer = False 38 | self.bGenerateLightmapUVs = False 39 | self.bOneConvexHullPerUCX = True 40 | self.bAutoGenerateCollision = False 41 | self.bCombineMeshes = False 42 | 43 | class SkeletalMeshImportData(ImportData): 44 | TargetSkeleton = "" 45 | def __init__(self): 46 | ImportData.__init__(self) 47 | self.bUpdateSkeletonReferencePose = True 48 | self.bUseT0AsRefPose = True 49 | self.bPreserveSmoothingGroups = True 50 | self.bImportMeshesInBoneHierarchy = False 51 | self.bImportMorphTargets = True 52 | self.bKeepOverlappingVertices = True 53 | 54 | 55 | class AnimSequenceImportData(ImportData): 56 | TargetSkeleton = "" 57 | def __init__(self): 58 | ImportData.__init__(self) 59 | self.bImportCustomAttribute = True 60 | self.bDeleteExistingCustomAttributeCurves = True 61 | self.bDeleteExistingNonCurveCustomAttributes = True 62 | self.bImportBoneTracks = True 63 | self.bSetMaterialDriveParameterOnCustomAttribute = True 64 | self.bRemoveRedundantKeys = False 65 | self.bDeleteExistingMorphTargetCurves = False 66 | self.bDoNotImportCurveWithZero = True 67 | self.bPreserveLocalTransform = False 68 | 69 | class TextureImportData(ImportData): 70 | def __init__(self): 71 | self.bInvertNormalMaps = True 72 | 73 | class FBXImportSettings(object): 74 | """ 75 | ImportSettings in Setting Group is UFbxImportUI: 76 | bIsObjImport /** Whether or not the imported file is in OBJ format */ 77 | OriginalImportType /** The original detected type of this import */ 78 | MeshTypeToImport /** Type of asset to import from the FBX file */ 79 | bOverrideFullName : true /** Use the string in "Name" field as full name of mesh. The option only works when the scene contains one mesh. */ 80 | bImportAsSkeletal /** Whether to import the incoming FBX as a skeletal object */ 81 | bImportMesh /** Whether to import the incoming FBX as a Subdivision Surface (could be made a combo box together with bImportAsSkeletal) (Experimental, Early work in progress) */ 82 | /** Whether to import the mesh. Allows animation only import when importing a skeletal mesh. */ 83 | Skeleton /** Skeleton to use for imported asset. When importing a mesh, leaving this as "None" will create a new skeleton. When importing an animation this MUST be specified to import the asset. */ 84 | bCreatePhysicsAsset /** If checked, create new PhysicsAsset if it doesn't have it */ 85 | bAutoComputeLodDistances ** If checked, the editor will automatically compute screen size values for the static mesh's LODs. If unchecked, the user can enter custom screen size values for each LOD. */ 86 | LodDistance0 /** Set a screen size value for LOD 0*/ 87 | LodDistance1 /** Set a screen size value for LOD 1*/ 88 | LodDistance2 /** Set a screen size value for LOD 2*/ 89 | LodDistance3 /** Set a screen size value for LOD 3*/ 90 | LodDistance4 /** Set a screen size value for LOD 4*/ 91 | LodDistance5 /** Set a screen size value for LOD 5*/ 92 | LodDistance6 /** Set a screen size value for LOD 6*/ 93 | LodDistance7 /** Set a screen size value for LOD 7*/ 94 | MinimumLodNumber /** Set the minimum LOD used for rendering. Setting the value to 0 will use the default value of LOD0. */ 95 | LodNumber /** Set the number of LODs for the editor to import. Setting the value to 0 imports the number of LODs found in the file (up to the maximum). */ 96 | bImportAnimations /** True to import animations from the FBX File */ 97 | OverrideAnimationName /** Override for the name of the animation to import. By default, it will be the name of FBX **/ 98 | bImportRigidMesh /** Enables importing of 'rigid skeletalmesh' (unskinned, hierarchy-based animation) from this FBX file, no longer shown, used behind the scenes */ 99 | bImportMaterials /** If no existing materials are found, whether to automatically create Unreal materials for materials found in the FBX scene */ 100 | bImportTextures /** Whether or not we should import textures. This option is disabled when we are importing materials because textures are always imported in that case. */ 101 | bResetToFbxOnMaterialConflict /** If true, the imported material sections will automatically be reset to the imported data in case of a reimport conflict. */ 102 | StaticMeshImportData /** Import data used when importing static meshes */ 103 | SkeletalMeshImportData /** Import data used when importing skeletal meshes */ 104 | AnimSequenceImportData /** Import data used when importing animations */ 105 | TextureImportData /** Import data used when importing textures */ 106 | bAutomatedImportShouldDetectType /** If true the automated import path should detect the import type. If false the import type was specified by the user */ 107 | bIsReimport 108 | bAllowContentTypeImport 109 | """ 110 | def __init__(self): 111 | self.settings = dict(ImportGroups = []) 112 | self._groups = [] 113 | 114 | def _add_group(self, setting_group): 115 | self.settings["ImportGroups"].append(setting_group) 116 | 117 | def addGroup(self, group_name, files_path, destination, import_setting, is_reimport=False): 118 | if not issubclass(import_setting.__class__, ImportData): 119 | print("import_setting input should be of class ImportSetting") 120 | setting_group = { 121 | "GroupName": group_name, 122 | "Filenames": files_path, 123 | "DestinationPath": destination, 124 | "bReplaceExisting": True, 125 | "bSkipReadOnly": True, 126 | "FactoryName": "FbxFactory", 127 | "ImportSettings": {} 128 | } 129 | raw_setting = vars(import_setting) 130 | if hasattr(import_setting, "TargetSkeleton"): 131 | setting_group["ImportSettings"]["Skeleton"] = str(import_setting.TargetSkeleton) 132 | raw_setting.pop("TargetSkeleton") 133 | if is_reimport: 134 | setting_group["ImportSettings"]["bIsReimport"] = True 135 | setting_group["ImportSettings"][import_setting.__class__.__name__] = raw_setting 136 | 137 | self._add_group(setting_group) 138 | return setting_group 139 | 140 | def getGroup(self, group_name): 141 | for setting in self.settings["ImportGroups"]: 142 | if setting["GroupName"] == group_name: 143 | return setting 144 | 145 | def asJson(self, filepath=""): 146 | if not filepath: 147 | import datetime 148 | filepath = os.path.join(os.getenv("TMP"), "UE4CMD", "importsetting.{}.json".format(datetime.datetime.now().strftime("%d%m%Y%H%M%S"))) 149 | try: 150 | os.makedirs(os.path.dirname(filepath)) 151 | except: 152 | pass 153 | try: 154 | with open(filepath, "w+") as f: 155 | json.dump(self.settings, f, sort_keys=True, indent=4, separators=(',', ': ')) 156 | except: 157 | print("Failed to export setting as json") 158 | return filepath 159 | 160 | class NoEditorException(Exception): 161 | pass 162 | 163 | class NoProjectException(Exception): 164 | pass 165 | 166 | 167 | class Unreal4Config(object): 168 | pass 169 | 170 | class UserEngine(Unreal4Config): 171 | content = r""" 172 | [/Script/PythonScriptPlugin.PythonScriptPluginSettings] 173 | {} 174 | bDeveloperMode=True 175 | bRemoteExecution=True 176 | RemoteExecutionMulticastGroupEndpoint={} 177 | RemoteExecutionMulticastBindAddress={} 178 | RemoteExecutionSendBufferSizeBytes={} 179 | RemoteExecutionReceiveBufferSizeBytes={} 180 | RemoteExecutionMulticastTtl=0 181 | """ 182 | multicastendpoint = "239.0.0.1:6766" 183 | multicastbindaddress = "0.0.0.0" 184 | SendBufferSize = 2097152 185 | ReceiveBufferSize = 2097152 186 | def __init__(self): 187 | self.startupScript = "" 188 | self.AdditionalPaths = [] 189 | 190 | def setStarupScript(self, startupscript_path): 191 | if os.path.exists(startupscript_path) and startupscript_path.endswith(".py"): 192 | self.startupScript = "+StartupScripts={}".format(startupscript_path) 193 | 194 | def addAdditionalPythonPath(self, python_path): 195 | if os.path.exists(python_path): 196 | self.AdditionalPaths.append(r"+AdditionalPaths=(Path=\"{}\")".format(python_path)) 197 | 198 | def saveAsConfig(self, outputpath=""): 199 | script_path = [] 200 | if self.startupScript: 201 | script_path.append(self.startupScript) 202 | if self.AdditionalPaths: 203 | script_path.extend(self.AdditionalPaths) 204 | script_path = "\n".join(script_path) 205 | content = self.content.format(script_path, self.multicastendpoint, self.multicastbindaddress, self.SendBufferSize, self.ReceiveBufferSize) 206 | config_path = outputpath 207 | if not config_path: 208 | config_path = os.path.join(get_temp_path(), "UserEngine.ini") 209 | with open(config_path, "w+") as f: 210 | f.write(content) 211 | return config_path 212 | 213 | @classmethod 214 | def asConfig(cls, outputpath=""): 215 | return cls().saveAsConfig(outputpath) 216 | 217 | class Unreal4CMD(object): 218 | def __init__(self, editor="", project=""): 219 | self._editor = editor or os.getenv("UE4Editor", "") 220 | self._project = project or os.getenv("UE4Project", "") 221 | 222 | @property 223 | def editor(self): 224 | return str(self._editor) 225 | 226 | @editor.setter 227 | def editor(self, editor_path): 228 | self._editor = editor_path 229 | 230 | @property 231 | def project(self): 232 | return str(self._project) 233 | 234 | @project.setter 235 | def project(self, project_path): 236 | self._project = project_path 237 | 238 | def getCMD(self): 239 | if self.editor and os.path.exists(self.editor): 240 | if os.path.isfile(self.editor) and os.path.basename(self.editor) == "UE4Editor-Cmd.exe": 241 | return self.editor 242 | for r in glob.glob(os.path.normpath(self.editor) + "/Binaries/Win64/UE4Editor-Cmd.exe"): 243 | return r 244 | 245 | def getEditor(self): 246 | if self.editor and os.path.exists(self.editor): 247 | if os.path.isfile(self.editor) and os.path.basename(self.editor) == "UE4Editor.exe": 248 | return self.editor 249 | for r in (glob.glob(os.path.normpath(self.editor) + "/Binaries/Win64/UE4Editor.exe")): 250 | return r 251 | 252 | def getProject(self): 253 | if self.project and os.path.exists(self.project): 254 | if os.path.isfile(self.project) and self.project.endswith("*.uproject"): 255 | return self.project 256 | for r in glob.glob(self.project + "/*.uproject"): 257 | return r 258 | 259 | def getProjectConfig(self): 260 | projectfile_path = self.getProject() 261 | if projectfile_path: 262 | try: 263 | from StringIO import StringIO 264 | print(projectfile_path) 265 | data = str.decode(open(projectfile_path, 'r').read(), encoding='utf-8-sig') 266 | return json.loads(data) 267 | except Exception as why: 268 | print("Failed to parse project {} as json!\n{}".format(projectfile_path, why)) 269 | else: 270 | raise NoProjectException("No valid file project!") 271 | 272 | def saveProjectConfig(self, project_config={}): 273 | if not project_config: 274 | project_config = self.getProjectConfig() 275 | projectfile_path = self.getProject() 276 | try: 277 | with open(projectfile_path, "w+") as f: 278 | json.dump(project_config, f, sort_keys=True, indent=4, separators=(',', ': ')) 279 | except Exception as why: 280 | print(r"Cannot directly edit {} with error {}".format(os.path.basename(projectfile_path), why)) 281 | projectfile_path = os.path.join(os.path.dirname(projectfile_path), "Edit{}".format(os.path.basename(projectfile_path))) 282 | with open(projectfile_path, "w+") as f: 283 | json.dump(project_config, f, sort_keys=True, indent=4, separators=(',', ': ')) 284 | 285 | def setPlugins(self, projectfile_path, plugins): 286 | project_config = self.getProjectConfig() 287 | for plugin in plugins: 288 | for project_plugin in project_config["Plugins"]: 289 | if project_plugin.get("Name") == plugin.get("Name"): 290 | project_plugin["Enabled"] = plugin.get("Enabled") 291 | break 292 | else: 293 | project_config["Plugins"].append(plugin) 294 | self.saveProjectConfig(project_config) 295 | return projectfile_path 296 | 297 | 298 | def run_editor( 299 | self, 300 | map_path=None, 301 | argv= [], 302 | plugins = [ 303 | { 304 | "Name": "PythonScriptPlugin", 305 | "Enabled": True 306 | }, 307 | { 308 | "Name": "SequencerScripting", 309 | "Enabled": True 310 | }, 311 | { 312 | "Name": "PythonAutomationTest", 313 | "Enabled": True 314 | }, 315 | { 316 | "Name": "EditorScriptingUtilities", 317 | "Enabled": True 318 | } 319 | ], 320 | log= False, 321 | consolevariables= [], 322 | run_process_callable= Popen, 323 | custom_editor_path= "", 324 | custom_project_path= "", 325 | UserConfig = UserEngine.asConfig(), 326 | as_cmd= False, 327 | communicate = False 328 | ): 329 | getEditor = self.getCMD if as_cmd else self.getEditor 330 | editor_path = getEditor() or custom_editor_path 331 | if not editor_path: 332 | raise NoEditorException("No UE4 Editor Define") 333 | 334 | project_path = self.getProject() or custom_project_path 335 | if not project_path: 336 | raise NoEditorException("No Project Define") 337 | 338 | project_path = self.setPlugins(project_path, plugins) 339 | 340 | argv.append(r'-ExecCmds="{}"'.format(";".join(consolevariables))) 341 | if log: 342 | if isinstance(log, str): 343 | if os.path.split(log)[0]: 344 | argv.append(r'ABSLOG="{}"'.format(log)) 345 | else: 346 | argv.append(r'LOG="{}"'.format(log)) 347 | else: 348 | argv.append("-log") 349 | cmd = [editor_path, project_path] 350 | if map_path: 351 | cmd.append(map_path) 352 | if as_cmd: 353 | cmd.append("-silent") 354 | cmd.append("-UNATTENDED") 355 | cmd.append("-NOSPLASH") 356 | cmd.append("-NOLOADSTARTUPPACKAGES") 357 | cmd.append("-targetplatform=WindowsNoEditor") 358 | cmd.extend(argv) 359 | cmd.append("EDITORUSERSETTINGSINI={}".format(UserConfig)) 360 | print("Run Unreal Editor with commands: {}".format(" ".join(cmd))) 361 | p = run_process_callable(cmd) 362 | if run_process_callable == Popen and communicate: 363 | p.communicate() 364 | return p 365 | 366 | def run_render( 367 | self, 368 | map_path, 369 | sequence_path, 370 | output_folder = "render", 371 | output_name = r"Render.{frame}", 372 | output_format= RenderOutputFormat.PNG, 373 | start_frame = 0, 374 | end_frame = 0, 375 | res_x = 1920, 376 | rex_y = 1080, 377 | frame_rate = 30, 378 | quality = 100, 379 | warmup_frames = 30, 380 | delay_frames = 30, 381 | delaybeforeshot_frames = 0, 382 | delayeveryframe_frames = 0, 383 | preview= False, 384 | shot=None, 385 | useburnin=False, 386 | write_editdecisionlist=None, 387 | write_finalcutxml=None, 388 | log=True 389 | ): 390 | cmds = [ 391 | "-game", 392 | '-MovieSceneCaptureType="/Script/MovieSceneCapture.AutomatedLevelSequenceCapture"', 393 | '-LevelSequence="{}"'.format(sequence_path), 394 | "-NoLoadingScreen", 395 | "-ResX={}".format(res_x), 396 | "-ResY={}".format(rex_y), 397 | "-ForceRes", 398 | "-NoVSync" if preview else "-VSync", 399 | "-MovieFrameRate={}".format(frame_rate), 400 | "-NoTextureStreaming", 401 | "-MovieCinematicMode=Yes", 402 | "-MovieWarmUpFrames={}".format(warmup_frames), 403 | "-MovieDelayBeforeWarmUp={}".format(delay_frames), 404 | "-MovieDelayBeforeShotWarmUp={}".format(delaybeforeshot_frames), 405 | "-MovieDelayEveryFrame={}".format(delayeveryframe_frames), 406 | "-MovieQuality={}".format(quality), 407 | '-MovieFolder="{}"'.format(output_folder), 408 | '-MovieName="{}"'.format(output_name), 409 | '-MovieFormat="{}"'.format(output_format), 410 | '-UseBurnIn="{}"'.format(useburnin), 411 | "-NoScreenMessage", 412 | "-WINDOWED" 413 | ] 414 | if shot: 415 | cmds.append('-Shot="{}"'.format(shot)) 416 | if start_frame: 417 | cmds.append("-MovieStartFrame={}".format(start_frame)) 418 | if end_frame: 419 | cmds.append("-MovieEndFrame={}".format(end_frame)) 420 | if write_editdecisionlist: 421 | cmds.append("-WriteEditDecisionList={}".format(write_editdecisionlist)) 422 | if write_finalcutxml: 423 | cmds.append("-WriteFinalCutProXML={}".format(write_finalcutxml)) 424 | 425 | console_commands = [ 426 | "DisableAllScreenMessages", 427 | "r.DepthOfFieldQuality=4", 428 | "r.MotionBlurSeparable=1", 429 | "r.MotionBlurQuality=4" 430 | ] 431 | return self.run_editor(map_path= map_path, argv=cmds, consolevariables=console_commands, as_cmd=True, log=log, communicate=True) 432 | 433 | def run_python( 434 | self, 435 | python_file, 436 | fully_initialize = False, 437 | log = False, 438 | timeout = None, 439 | ): 440 | use_cmd = True 441 | cmd = ["-run=pythonscript", r"-script={}".format(python_file)] 442 | 443 | if fully_initialize: 444 | use_cmd = False 445 | cmd = [r'-ExecutePythonScript="{}"'.format(python_file)] 446 | 447 | return self.run_editor( 448 | argv=cmd, 449 | log=log, 450 | as_cmd=use_cmd, 451 | communicate=True 452 | ) 453 | 454 | def run_import(self, importsettings, use_source_control = False, submit_desc="", log=False): 455 | try: 456 | cmd = ["-run=ImportAssets"] 457 | cmd.append(r'-importsettings="{}"'.format(importsettings)) 458 | cmd.append("-replaceexisting") 459 | if not use_source_control: 460 | cmd.append("-nosourcecontrol") 461 | else: 462 | if submit_desc: 463 | cmd.append(r'-submitdesc="{}"'.format(submit_desc)) 464 | except: 465 | print("OK") 466 | self.run_editor(argv=cmd, as_cmd=True, communicate=True, log=log) --------------------------------------------------------------------------------