Skip to content
This repository was archived by the owner on Mar 25, 2025. It is now read-only.

Commit 3b04a35

Browse files
committed
Merge branch 'pr/9'
2 parents b488657 + ca07c3f commit 3b04a35

File tree

386 files changed

+8932
-4653
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

386 files changed

+8932
-4653
lines changed

README.md

Lines changed: 82 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ Embed Python in Unreal Engine 4
77
This fork is meant to encapsulate python + pip + scripts fully in the plugin and to allow dependency plugins to be built on top of the python plugin. Specifically it means adding automatic pip dependency resolution and automatic sys.path additions such that the resulting two plugins can be fully drag and dropped into a new project.
88

99

10-
Teaser: https://twitter.com/KNLstudio/status/932657812466843648
10+
11+
Teaser (by Kite & Lightning): https://twitter.com/KNLstudio/status/932657812466843648
12+
13+
Fixing Mixamo RootMotion tuturial: https://github.com/20tab/UnrealEnginePython/blob/master/tutorials/FixingMixamoRootMotionWithPython.md
14+
15+
Funny snippets for working with StaticMesh and SkeletalMesh assets: https://github.com/20tab/UnrealEnginePython/blob/master/tutorials/SnippetsForStaticAndSkeletalMeshes.md
16+
17+
More tutorials: https://github.com/20tab/UnrealEnginePython/tree/master/tutorials
1118

1219
# How and Why ?
1320

@@ -29,10 +36,12 @@ Once the plugin is installed and enabled, you get access to the 'PythonConsole'
2936

3037
All of the exposed engine features are under the 'unreal_engine' virtual module (it is completely coded in c into the plugin, so do not expect to run 'import unreal_engine' from a standard python shell)
3138

32-
The currently supported Unreal Engine versions are 4.12, 4.13, 4.14, 4.15, 4.16, 4.17, 4.18 and 4.19
39+
The currently supported Unreal Engine versions are 4.12, 4.13, 4.14, 4.15, 4.16, 4.17, 4.18, 4.19 and 4.20
3340

3441
We support official python.org releases as well as IntelPython and Anaconda distributions.
3542

43+
Note: this plugin has nothing to do with the experimental 'PythonScriptPlugin' included in Unreal Engine >= 4.19. We aim at full integration with engine and editor (included the Slate api), as well as support for the vast majority of python features like asyncio, coroutines, generators, threads and third party modules.
44+
3645
# Binary installation on Windows (64 bit)
3746

3847
Check in the releases page (https://github.com/20tab/UnrealEnginePython/releases) if there is a binary version that matches your configuration (otherwise open an issue asking us for it [please specify the python version too]) and download it.
@@ -516,6 +525,8 @@ ts.dumb = DumbStruct(Foo=17, Bar=22)
516525
ts.ref().dumb.foo().Foo = 22
517526
```
518527

528+
More details here: https://github.com/20tab/UnrealEnginePython/blob/master/docs/MemoryManagement.md
529+
519530
The ue_site.py file
520531
-------------------
521532

@@ -783,55 +794,81 @@ It allows you to run, create, modify and delete scripts directly from the UE edi
783794

784795
The first pull request for the editor has been issued by https://github.com/sun5471 so many thanks to him ;)
785796

786-
Integration with PyQT
787-
---------------------
797+
Integration with Qt4/Qt5/PySide2
798+
--------------------------------
788799

789-
To correctly integrates PyQT with UnrealEngine the python plugin must correctly setup the GIL (and this is done) and exceptions must be managed ad-hoc (not doing it will result in a deadlock whenever a qt signal handler raises an exception)
800+
Thanks to solid GIL management, you can integrate Qt python apps in Unreal Engine 4.
790801

791-
This is an example of having a QT window along the editor to trigger asset reimporting (pay attention to the sys.excepthook usage):
802+
Pay attention to not call app.exec_() as it will result in Qt taking control of the UE loop. Instead use a ticker to integrate the Qt loop in the editor loop:
792803

793-
```py
794-
from PyQt5.QtWidgets import QApplication, QWidget, QListWidget
795-
import unreal_engine as ue
804+
```python
805+
806+
# save it as ueqt.py
796807
import sys
797-
import traceback
808+
import unreal_engine as ue
809+
import PySide2
810+
from PySide2 import QtWidgets
798811

799-
def ue_exception(_type, value, back):
800-
ue.log_error(value)
801-
tb_lines = traceback.format_exception(_type, value, back)
802-
for line in tb_lines:
803-
ue.log_error(line)
812+
app = QtWidgets.QApplication(sys.argv)
804813

805-
sys.excepthook = ue_exception
814+
def ticker_loop(delta_time):
815+
app.processEvents()
816+
return True
806817

807-
skeletal_mappings = {}
818+
ticker = ue.add_ticker(ticker_loop)
819+
```
820+
now you can start writing your gui (this is a simple example loading asset thumbnail):
808821

809-
def selected_skeletal_mesh(item):
810-
uobject = skeletal_mappings[item.data()]
811-
ue.log('Ready to reimport: ' + uobject.get_name())
812-
uobject.asset_reimport()
822+
```python
823+
import ueqt
824+
from PySide2 import QtCore, QtWidgets, QtGui
825+
import unreal_engine as ue
813826

814-
#check if an instance of the application is already running
815-
app = QApplication.instance()
816-
if app is None:
817-
app = QApplication([])
818-
else:
819-
print("App already running.")
827+
from unreal_engine import FARFilter
820828

821-
win = QWidget()
822-
win.setWindowTitle('Unreal Engine 4 skeletal meshes reimporter')
829+
_filter = FARFilter()
830+
_filter.class_names = ['SkeletalMesh', 'Material']
823831

824-
wlist = QListWidget(win)
825-
for asset in ue.get_assets_by_class('SkeletalMesh'):
826-
wlist.addItem(asset.get_name())
827-
skeletal_mappings[asset.get_name()] = asset
828-
829-
wlist.clicked.connect(selected_skeletal_mesh)
830-
wlist.show()
832+
class MyWidget(QtWidgets.QWidget):
833+
def __init__(self):
834+
super().__init__()
835+
self.vertical = QtWidgets.QVBoxLayout()
836+
self.scroll = QtWidgets.QScrollArea()
837+
self.content = QtWidgets.QWidget()
838+
self.scroll.setWidget(self.content)
839+
self.scroll.setWidgetResizable(True)
840+
self.layout = QtWidgets.QVBoxLayout()
841+
842+
for asset_data in ue.get_assets_by_filter(_filter, True):
843+
try:
844+
thumbnail = asset_data.get_thumbnail()
845+
except:
846+
continue
831847

832-
win.show()
848+
label = QtWidgets.QLabel()
849+
data = thumbnail.get_uncompressed_image_data()
850+
image = QtGui.QImage(data, 256, 256, QtGui.QImage.Format_RGB32)
851+
label.setPixmap(QtGui.QPixmap.fromImage(image).scaled(256, 256))
852+
self.layout.addWidget(label)
853+
854+
self.content.setLayout(self.layout)
855+
self.vertical.addWidget(self.scroll)
856+
self.setLayout(self.vertical)
857+
858+
859+
860+
widget = MyWidget()
861+
widget.resize(800, 600)
862+
widget.show()
863+
864+
root_window = ue.get_editor_window()
865+
root_window.set_as_owner(widget.winId())
833866
```
834867

868+
(no need to allocate a new Qt app, or start it, as the UE4 Editor, thanks to to ueqt module is now the Qt app itself)
869+
870+
Note the 2 final lines: they 'attach' the Qt window as a 'child' of the editor root window. Note that on windows platform this is not simple parenting but 'ownership'.
871+
835872
Memory management
836873
-----------------
837874

@@ -841,6 +878,7 @@ Starting from release 20180226 a new memory management system has been added (FU
841878

842879
The same system works for delegates, as well as Slate.
843880

881+
More details here: https://github.com/20tab/UnrealEnginePython/blob/master/docs/MemoryManagement.md
844882

845883
Unit Testing
846884
------------
@@ -851,25 +889,14 @@ To run the unit tests (ensure to run them on an empty/useless project to avoid m
851889

852890
```python
853891
import unreal_engine as ue
854-
ue.sandbox_exec(ue.find_plugin('UnrealEnginePython').get_base_dir() + '/run_tests.py')
892+
ue.py_exec(ue.find_plugin('UnrealEnginePython').get_base_dir() + '/run_tests.py')
855893
```
856894
if you plan to add new features to the plugin, including a test suite in your pull request will be really appreciated ;)
857895

858-
Threading (Experimental)
896+
Threading
859897
------------------------
860898

861-
By default the plugin is compiled without effective python threads support. This is for 2 main reasons:
862-
863-
* we still do not have numbers about the performance impact of constantly acquiring and releasing the GIL
864-
* we need a better test suite
865-
866-
By the way, if you want to play with experimental threading support, just uncomment
867-
868-
```c
869-
//#define UEPY_THREADING 1
870-
```
871-
872-
on top of UnrealEnginePythonPrivatePCH.h and rebuild the plugin.
899+
Since release 20180624 threading is fully supported.
873900

874901
As with native threads, do not modify (included deletion) UObjects from non-main threads.
875902

@@ -885,7 +912,7 @@ Sometimes you may have a UObject and know that it is backed by a python object.
885912

886913
This would be resolved as shown below:
887914

888-
```
915+
```python
889916
import unreal_engine as ue
890917

891918
class Explosive:
@@ -911,12 +938,12 @@ What is going on here in `BadGuy` is that self.uobject is a reference to the PyA
911938
Status and Known issues
912939
-----------------------
913940

914-
The project could be considered in beta state.
915-
916941
Exposing the full ue4 api is a huge amount of work, feel free to make pull requests for your specific needs.
917942

918943
We still do not have a plugin icon ;)
919944

945+
We try to do our best to "protect" the user, but you can effectively crash UE from python as you are effectively calling the C/C++ api
946+
920947
Contacts and Commercial Support
921948
-------------------------------
922949

@@ -933,3 +960,4 @@ Such a big project requires constant sponsorship, special thanks go to:
933960

934961
* GoodTH.INC https://www.goodthinc.com/ (they are sponsoring the sequencer api)
935962

963+
* Quixel AB https://megascans.se/ (built their integration tool over UnrealEnginePython giving us tons of useful feedbacks and ideas)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
2+
3+
#include "PythonAutomationModule.h"
4+
#include "UnrealEnginePython.h"
5+
#include "UEPyFAutomationEditorCommonUtils.h"
6+
7+
IMPLEMENT_MODULE(FPythonAutomationModule, PythonAutomation);
8+
9+
10+
void FPythonAutomationModule::StartupModule()
11+
{
12+
FScopePythonGIL gil;
13+
PyObject *py_automation_module = ue_py_register_module("unreal_engine.automation");
14+
ue_python_init_fautomation_editor_common_utils(py_automation_module);
15+
}
16+
17+
void FPythonAutomationModule::ShutdownModule()
18+
{
19+
20+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
2+
#include "UEPyFAutomationEditorCommonUtils.h"
3+
4+
#include "UnrealEnginePython.h"
5+
#include "Engine/World.h"
6+
7+
8+
static PyObject *py_ue_fautomation_editor_common_utils_run_pie(PyObject *cls, PyObject * args)
9+
{
10+
Py_BEGIN_ALLOW_THREADS;
11+
FAutomationEditorCommonUtils::RunPIE();
12+
Py_END_ALLOW_THREADS;
13+
Py_RETURN_NONE;
14+
}
15+
16+
static PyObject *py_ue_fautomation_editor_common_utils_load_map(PyObject *cls, PyObject * args)
17+
{
18+
char *map_name;
19+
if (!PyArg_ParseTuple(args, "s:load_map", &map_name))
20+
return nullptr;
21+
Py_BEGIN_ALLOW_THREADS;
22+
FAutomationEditorCommonUtils::LoadMap(FString(UTF8_TO_TCHAR(map_name)));
23+
Py_END_ALLOW_THREADS;
24+
Py_RETURN_NONE;
25+
}
26+
27+
static PyObject *py_ue_fautomation_editor_common_utils_create_new_map(PyObject *cls, PyObject * args)
28+
{
29+
Py_RETURN_UOBJECT(FAutomationEditorCommonUtils::CreateNewMap());
30+
}
31+
32+
static PyMethodDef ue_PyFAutomationEditorCommonUtils_methods[] = {
33+
{ "run_pie", (PyCFunction)py_ue_fautomation_editor_common_utils_run_pie, METH_VARARGS | METH_CLASS, "" },
34+
{ "load_map", (PyCFunction)py_ue_fautomation_editor_common_utils_load_map, METH_VARARGS | METH_CLASS, "" },
35+
{ "create_new_map", (PyCFunction)py_ue_fautomation_editor_common_utils_create_new_map, METH_VARARGS | METH_CLASS, "" },
36+
37+
{ NULL } /* Sentinel */
38+
};
39+
40+
41+
static PyTypeObject ue_PyFAutomationEditorCommonUtilsType = {
42+
PyVarObject_HEAD_INIT(NULL, 0)
43+
"unreal_engine.FAutomationEditorCommonUtils", /* tp_name */
44+
sizeof(ue_PyFAutomationEditorCommonUtils), /* tp_basicsize */
45+
0, /* tp_itemsize */
46+
0, /* tp_dealloc */
47+
0, /* tp_print */
48+
0, /* tp_getattr */
49+
0, /* tp_setattr */
50+
0, /* tp_reserved */
51+
0, /* tp_repr */
52+
0, /* tp_as_number */
53+
0, /* tp_as_sequence */
54+
0, /* tp_as_mapping */
55+
0, /* tp_hash */
56+
0, /* tp_call */
57+
0, /* tp_str */
58+
0, /* tp_getattro */
59+
0, /* tp_setattro */
60+
0, /* tp_as_buffer */
61+
Py_TPFLAGS_DEFAULT, /* tp_flags */
62+
"Unreal Engine AutomationEditorCommonUtils", /* tp_doc */
63+
0, /* tp_traverse */
64+
0, /* tp_clear */
65+
0, /* tp_richcompare */
66+
0, /* tp_weaklistoffset */
67+
0, /* tp_iter */
68+
0, /* tp_iternext */
69+
ue_PyFAutomationEditorCommonUtils_methods, /* tp_methods */
70+
0,
71+
0,
72+
};
73+
74+
static int py_ue_fautomation_editor_common_utils_init(ue_PyFAutomationEditorCommonUtils *self, PyObject * args)
75+
{
76+
PyErr_SetString(PyExc_Exception, "FAutomationEditorCommonUtils is a singleton");
77+
return -1;
78+
}
79+
80+
void ue_python_init_fautomation_editor_common_utils(PyObject *ue_module)
81+
{
82+
ue_PyFAutomationEditorCommonUtilsType.tp_new = PyType_GenericNew;
83+
ue_PyFAutomationEditorCommonUtilsType.tp_init = (initproc)py_ue_fautomation_editor_common_utils_init;
84+
85+
if (PyType_Ready(&ue_PyFAutomationEditorCommonUtilsType) < 0)
86+
return;
87+
88+
Py_INCREF(&ue_PyFAutomationEditorCommonUtilsType);
89+
PyModule_AddObject(ue_module, "FAutomationEditorCommonUtils", (PyObject *)&ue_PyFAutomationEditorCommonUtilsType);
90+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
3+
#include "UnrealEnginePython.h"
4+
5+
#include "Tests/AutomationEditorCommon.h"
6+
7+
typedef struct
8+
{
9+
PyObject_HEAD
10+
/* Type-specific fields go here. */
11+
} ue_PyFAutomationEditorCommonUtils;
12+
13+
void ue_python_init_fautomation_editor_common_utils(PyObject *);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 1998-2018 20Tab S.r.l. All Rights Reserved.
2+
3+
#pragma once
4+
5+
#include "CoreMinimal.h"
6+
#include "ModuleInterface.h"
7+
8+
class FPythonAutomationModule : public IModuleInterface
9+
{
10+
public:
11+
virtual void StartupModule();
12+
virtual void ShutdownModule();
13+
14+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 1998-2018 20Tab S.r.l All Rights Reserved.
2+
3+
using UnrealBuildTool;
4+
using System.IO;
5+
6+
public class PythonAutomation : ModuleRules
7+
{
8+
#if WITH_FORWARDED_MODULE_RULES_CTOR
9+
public PythonAutomation(ReadOnlyTargetRules Target) : base(Target)
10+
#else
11+
public PythonAutomation(TargetInfo Target)
12+
#endif
13+
{
14+
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
15+
string enableUnityBuild = System.Environment.GetEnvironmentVariable("UEP_ENABLE_UNITY_BUILD");
16+
bFasterWithoutUnity = string.IsNullOrEmpty(enableUnityBuild);
17+
18+
PrivateIncludePaths.AddRange(
19+
new string[] {
20+
"PythonConsole/Private",
21+
// ... add other private include paths required here ...
22+
}
23+
);
24+
25+
PrivateDependencyModuleNames.AddRange(
26+
new string[] {
27+
"Core",
28+
"CoreUObject", // @todo Mac: for some reason it's needed to link in debug on Mac
29+
"Engine",
30+
"UnrealEd",
31+
"UnrealEnginePython"
32+
}
33+
);
34+
35+
}
36+
}

0 commit comments

Comments
 (0)