diff --git a/.gitignore b/.gitignore index a8f0b03d6..e43464f53 100644 --- a/.gitignore +++ b/.gitignore @@ -89,6 +89,7 @@ ENV/ .ropeproject Binaries/ +Intermediate/ python35/ python27/ *.un~ diff --git a/README.md b/README.md index c69d75e89..68ed706f1 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,16 @@ Funny snippets for working with StaticMesh and SkeletalMesh assets: https://gith More tutorials: https://github.com/20tab/UnrealEnginePython/tree/master/tutorials +# Project Status (IMPORTANT) + +Currently (as april 2020) the project is on hold: between 2016 and 2018 20tab invested lot of resources in it but unfortunately epic (during 2018) decided to suddenly release its own implementation and the request made for a megagrant in 2019 by the original plugin author was rejected too. + +As this plugin (still) has way more features than the Epic one and many contributors, **we are currently looking for new maintainers** helping us to keep it alive, checking PR and issues. If you are interested in working on it a few hours a week, drop us a line at info@20tab.com to discuss about it. + +If you are interested in game logic scripting/modding in Unreal Engine 4 consider giving a look at the LuaMachine project (https://github.com/rdeioris/LuaMachine/). + +The plugin should work up to unreal engine version 4.23 and there are forks/pull requests for 4.24. Since 4.25 Epic refactored the UProperty subsystem, so if you want to port the plugin to a version >= 4.25 you should make a lot of search & replace (basically renaming UProperty to FProperty and Cast to CastField should be enough) + # How and Why ? This is a plugin embedding a whole Python VM (versions 3.x [the default and suggested one] and 2.7) In Unreal Engine 4 (both the editor and runtime). @@ -30,11 +40,11 @@ Once the plugin is installed and enabled, you get access to the 'PythonConsole' 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) -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 +The minimal supported Unreal Engine version is 4.12, while the latest is 4.23 We support official python.org releases as well as IntelPython and Anaconda distributions. -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. +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, check here: https://github.com/20tab/UnrealEnginePython/blob/master/docs/Slate_API.md), as well as support for the vast majority of python features like asyncio, coroutines, generators, threads and third party modules. # Binary installation on Windows (64 bit) @@ -686,6 +696,7 @@ The following parameters are supported: * `RelativeAdditionalModulesPath`: like AdditionalModulesPath, but the path is relative to the /Content directory * `ZipPath`: allow to specify a .zip file that is added to sys.path * `RelativeZipPath`: like ZipPath, but the path is relative to the /Content directory +* `ImportModules: comma/space/semicolon separated list of modules to import on startup (after ue_site) Example: @@ -942,9 +953,9 @@ We try to do our best to "protect" the user, but you can effectively crash UE fr Contacts and Commercial Support ------------------------------- -If you want to contact us (for help, support, sponsorship), drop a mail to info at 20tab.com or follow @unbit on twitter +If you need commercial support for UnrealEnginePython just drop a mail to info at 20tab.com -We offer commercial support for both UnrealEngine and UnrealEnginePython, again drop a mail to info at 20tab.com for more infos +Follow @unbit on twitter for news about the project Special Thanks -------------- diff --git a/Source/PythonAutomation/Public/PythonAutomationModule.h b/Source/PythonAutomation/Public/PythonAutomationModule.h index c0f9c6d16..bc19a062e 100644 --- a/Source/PythonAutomation/Public/PythonAutomationModule.h +++ b/Source/PythonAutomation/Public/PythonAutomationModule.h @@ -3,7 +3,11 @@ #pragma once #include "CoreMinimal.h" +#if ENGINE_MAJOR_VERSION==4 && ENGINE_MINOR_VERSION>=22 +#include "Modules/ModuleInterface.h" +#else #include "ModuleInterface.h" +#endif class FPythonAutomationModule : public IModuleInterface { diff --git a/Source/PythonEditor/Private/SPythonEditableText.cpp b/Source/PythonEditor/Private/SPythonEditableText.cpp index 183c6f862..d91ba2a80 100644 --- a/Source/PythonEditor/Private/SPythonEditableText.cpp +++ b/Source/PythonEditor/Private/SPythonEditableText.cpp @@ -34,11 +34,10 @@ FReply SPythonEditableText::OnKeyChar(const FGeometry& MyGeometry, const FCharac return Reply; } Reply = FReply::Handled(); + // substitute tab, with 4 spaces if (Character == TEXT('\t')) { - FString String; - String.AppendChar(Character); - InsertTextAtCursor(String); + InsertTextAtCursor(FString(" ")); } //else if (Character == TEXT('(')) //{ diff --git a/Source/UnrealEnginePython/Private/Blueprint/UEPyEdGraph.cpp b/Source/UnrealEnginePython/Private/Blueprint/UEPyEdGraph.cpp index 6f75053ba..a3e53b7c5 100644 --- a/Source/UnrealEnginePython/Private/Blueprint/UEPyEdGraph.cpp +++ b/Source/UnrealEnginePython/Private/Blueprint/UEPyEdGraph.cpp @@ -381,6 +381,127 @@ PyObject *py_ue_graph_add_node(ue_PyUObject * self, PyObject * args) Py_RETURN_UOBJECT(node); } +PyObject *py_ue_graph_remove_node(ue_PyUObject * self, PyObject * args) +{ + + ue_py_check(self); + + PyObject *py_node_class; + int x = 0; + int y = 0; + + char *metadata = nullptr; + PyObject *py_data = nullptr; + + if (!PyArg_ParseTuple(args, "O|iisO:graph_remove_node", &py_node_class, &x, &y, &metadata, &py_data)) + { + return nullptr; + } + + UEdGraph *graph = ue_py_check_type(self); + if (!graph) + return PyErr_Format(PyExc_Exception, "uobject is not a UEdGraph"); + + UObject *u_obj = ue_py_check_type(py_node_class); + if (!u_obj) + return PyErr_Format(PyExc_Exception, "argument is not a UObject"); + + UEdGraphNode *node = nullptr; + + if (UClass *u_class = Cast(u_obj)) + { + if (!u_class->IsChildOf()) + { + return PyErr_Format(PyExc_Exception, "argument is not a child of UEdGraphNode"); + } + node = NewObject(graph, u_class); + node->PostLoad(); + } + else + { + node = Cast(u_obj); + if (node) + { + if (node->GetOuter() != graph) + + node->Rename(*node->GetName(), graph); + } + } + + if (!node) + return PyErr_Format(PyExc_Exception, "argument is not a supported type"); + + graph->RemoveNode(node); + + if (UBlueprint *bp = Cast(node->GetGraph()->GetOuter())) + { + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(bp); + } + + Py_RETURN_NONE; +} + +PyObject *py_ue_graph_reconstruct_node(ue_PyUObject * self, PyObject * args) +{ + + ue_py_check(self); + + PyObject *py_node_class; + int x = 0; + int y = 0; + + char *metadata = nullptr; + PyObject *py_data = nullptr; + + if (!PyArg_ParseTuple(args, "O|iisO:graph_reconstruct_node", &py_node_class, &x, &y, &metadata, &py_data)) + { + return nullptr; + } + + UEdGraph *graph = ue_py_check_type(self); + if (!graph) + return PyErr_Format(PyExc_Exception, "uobject is not a UEdGraph"); + + UObject *u_obj = ue_py_check_type(py_node_class); + if (!u_obj) + return PyErr_Format(PyExc_Exception, "argument is not a UObject"); + + UEdGraphNode *node = nullptr; + + if (UClass *u_class = Cast(u_obj)) + { + if (!u_class->IsChildOf()) + { + return PyErr_Format(PyExc_Exception, "argument is not a child of UEdGraphNode"); + } + node = NewObject(graph, u_class); + node->PostLoad(); + } + else + { + node = Cast(u_obj); + if (node) + { + if (node->GetOuter() != graph) + + node->Rename(*node->GetName(), graph); + } + } + + if (!node) + return PyErr_Format(PyExc_Exception, "argument is not a supported type"); + + //graph->RemoveNode(node); + node->ReconstructNode(); + + if (UBlueprint *bp = Cast(node->GetGraph()->GetOuter())) + { + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(bp); + } + + Py_RETURN_NONE; +} + PyObject *py_ue_graph_add_node_dynamic_cast(ue_PyUObject * self, PyObject * args) { diff --git a/Source/UnrealEnginePython/Private/Blueprint/UEPyEdGraph.h b/Source/UnrealEnginePython/Private/Blueprint/UEPyEdGraph.h index 03061426b..e0fd0fe69 100644 --- a/Source/UnrealEnginePython/Private/Blueprint/UEPyEdGraph.h +++ b/Source/UnrealEnginePython/Private/Blueprint/UEPyEdGraph.h @@ -16,6 +16,9 @@ PyObject *py_ue_graph_add_node(ue_PyUObject *, PyObject *); PyObject *py_ue_graph_add_node_dynamic_cast(ue_PyUObject *, PyObject *); PyObject *py_ue_graph_add_node_event(ue_PyUObject *, PyObject *); +PyObject *py_ue_graph_reconstruct_node(ue_PyUObject *, PyObject *); +PyObject *py_ue_graph_remove_node(ue_PyUObject *, PyObject *); + PyObject *py_ue_graph_get_good_place_for_new_node(ue_PyUObject *, PyObject *); PyObject *py_ue_node_pins(ue_PyUObject *, PyObject *); diff --git a/Source/UnrealEnginePython/Private/Blueprint/UEPyEdGraphPin.cpp b/Source/UnrealEnginePython/Private/Blueprint/UEPyEdGraphPin.cpp index dca7bf864..283079f31 100644 --- a/Source/UnrealEnginePython/Private/Blueprint/UEPyEdGraphPin.cpp +++ b/Source/UnrealEnginePython/Private/Blueprint/UEPyEdGraphPin.cpp @@ -80,9 +80,49 @@ static PyObject *py_ue_edgraphpin_break_link_to(ue_PyEdGraphPin *self, PyObject Py_RETURN_NONE; } +static PyObject *py_ue_edgraphpin_break_all_pin_links(ue_PyEdGraphPin *self, PyObject * args) +{ + PyObject *py_notify_nodes = nullptr; + if (!PyArg_ParseTuple(args, "O:break_all_pin_links", &py_notify_nodes)) + { + return NULL; + } + + bool notify_nodes = true; + if (py_notify_nodes && !PyObject_IsTrue(py_notify_nodes)) + notify_nodes = false; + + self->pin->BreakAllPinLinks(notify_nodes); + + if (UBlueprint *bp = Cast(self->pin->GetOwningNode()->GetGraph()->GetOuter())) + { + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(bp); + } + + Py_RETURN_NONE; +} + +static PyObject *py_ue_edgraphpin_get_linked_to(ue_PyEdGraphPin * self, PyObject * args) +{ + PyObject *pins = PyList_New(0); + + TArray Links = self->pin->LinkedTo; + + for (int32 i = 0; i < Links.Num(); i++) + { + UEdGraphPin *pin = Links[i]; + ue_PyUObject *item = (ue_PyUObject *)py_ue_new_edgraphpin(pin); + if (item) + PyList_Append(pins, (PyObject *)item); + } + return pins; +} + static PyMethodDef ue_PyEdGraphPin_methods[] = { { "make_link_to", (PyCFunction)py_ue_edgraphpin_make_link_to, METH_VARARGS, "" }, { "break_link_to", (PyCFunction)py_ue_edgraphpin_break_link_to, METH_VARARGS, "" }, + { "break_all_pin_links", (PyCFunction)py_ue_edgraphpin_break_all_pin_links, METH_VARARGS, "" }, + { "get_linked_to", (PyCFunction)py_ue_edgraphpin_get_linked_to, METH_VARARGS, "" }, { "connect", (PyCFunction)py_ue_edgraphpin_connect, METH_VARARGS, "" }, { NULL } /* Sentinel */ }; diff --git a/Source/UnrealEnginePython/Private/PyCommandlet.cpp b/Source/UnrealEnginePython/Private/PyCommandlet.cpp index 9b22ae818..701f05348 100644 --- a/Source/UnrealEnginePython/Private/PyCommandlet.cpp +++ b/Source/UnrealEnginePython/Private/PyCommandlet.cpp @@ -91,7 +91,11 @@ int32 UPyCommandlet::Main(const FString& CommandLine) #if PY_MAJOR_VERSION >= 3 argv[i] = (wchar_t*)malloc(PyArgv[i].Len() + 1); #if PLATFORM_MAC || PLATFORM_LINUX + #if ENGINE_MINOR_VERSION >= 20 + wcsncpy(argv[i], (const wchar_t *) TCHAR_TO_WCHAR(*PyArgv[i].ReplaceEscapedCharWithChar()), PyArgv[i].Len() + 1); + #else wcsncpy(argv[i], *PyArgv[i].ReplaceEscapedCharWithChar(), PyArgv[i].Len() + 1); + #endif #elif PLATFORM_ANDROID wcsncpy(argv[i], (const wchar_t *)*PyArgv[i].ReplaceEscapedCharWithChar(), PyArgv[i].Len() + 1); #else diff --git a/Source/UnrealEnginePython/Private/PythonDelegate.cpp b/Source/UnrealEnginePython/Private/PythonDelegate.cpp index 0ed596d4b..e7e4c6699 100644 --- a/Source/UnrealEnginePython/Private/PythonDelegate.cpp +++ b/Source/UnrealEnginePython/Private/PythonDelegate.cpp @@ -1,6 +1,7 @@ #include "PythonDelegate.h" #include "UEPyModule.h" +#include "UEPyCallable.h" UPythonDelegate::UPythonDelegate() { @@ -101,6 +102,12 @@ void UPythonDelegate::PyInputAxisHandler(float value) Py_DECREF(ret); } +bool UPythonDelegate::UsesPyCallable(PyObject *other) +{ + ue_PyCallable *other_callable = (ue_PyCallable*)other; + ue_PyCallable *this_callable = (ue_PyCallable*)py_callable; + return other_callable->u_function == this_callable->u_function && other_callable->u_target == this_callable->u_target; +} UPythonDelegate::~UPythonDelegate() { @@ -110,4 +117,4 @@ UPythonDelegate::~UPythonDelegate() #if defined(UEPY_MEMORY_DEBUG) UE_LOG(LogPython, Warning, TEXT("PythonDelegate %p callable XDECREF'ed"), this); #endif -} \ No newline at end of file +} diff --git a/Source/UnrealEnginePython/Private/PythonFunction.cpp b/Source/UnrealEnginePython/Private/PythonFunction.cpp index 04b62ea8b..efeb5b059 100644 --- a/Source/UnrealEnginePython/Private/PythonFunction.cpp +++ b/Source/UnrealEnginePython/Private/PythonFunction.cpp @@ -19,13 +19,17 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) FScopePythonGIL gil; +#if ENGINE_MINOR_VERSION <= 18 + UObject *Context = Stack.Object; +#endif + UPythonFunction *function = static_cast(Stack.CurrentNativeFunction); bool on_error = false; bool is_static = function->HasAnyFunctionFlags(FUNC_Static); // count the number of arguments - Py_ssize_t argn = (Stack.Object && !is_static) ? 1 : 0; + Py_ssize_t argn = (Context && !is_static) ? 1 : 0; TFieldIterator IArgs(function); for (; IArgs && ((IArgs->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm); ++IArgs) { argn++; @@ -34,22 +38,22 @@ void UPythonFunction::CallPythonCallable(FFrame& Stack, RESULT_DECL) UE_LOG(LogPython, Warning, TEXT("Initializing %d parameters"), argn); #endif PyObject *py_args = PyTuple_New(argn); + argn = 0; - if (Stack.Object && !is_static) { - PyObject *py_obj = (PyObject *)ue_get_python_uobject(Stack.Object); + if (Context && !is_static) { + PyObject *py_obj = (PyObject *)ue_get_python_uobject(Context); if (!py_obj) { unreal_engine_py_log_error(); on_error = true; } else { Py_INCREF(py_obj); - PyTuple_SetItem(py_args, 0, py_obj); + PyTuple_SetItem(py_args, argn++, py_obj); } } uint8 *frame = Stack.Locals; - argn = (Stack.Object && !is_static) ? 1 : 0; // is it a blueprint call ? if (*Stack.Code == EX_EndFunctionParms) { for (UProperty *prop = (UProperty *)function->Children; prop; prop = (UProperty *)prop->Next) { @@ -127,4 +131,4 @@ UPythonFunction::~UPythonFunction() #if defined(UEPY_MEMORY_DEBUG) UE_LOG(LogPython, Warning, TEXT("PythonFunction callable %p XDECREF'ed"), this); #endif -} \ No newline at end of file +} diff --git a/Source/UnrealEnginePython/Private/PythonHouseKeeper.cpp b/Source/UnrealEnginePython/Private/PythonHouseKeeper.cpp new file mode 100644 index 000000000..dc014e386 --- /dev/null +++ b/Source/UnrealEnginePython/Private/PythonHouseKeeper.cpp @@ -0,0 +1,254 @@ + +#include "PythonHouseKeeper.h" + +void FUnrealEnginePythonHouseKeeper::AddReferencedObjects(FReferenceCollector& InCollector) +{ + InCollector.AddReferencedObjects(PythonTrackedObjects); +} + +FUnrealEnginePythonHouseKeeper *FUnrealEnginePythonHouseKeeper::Get() +{ + static FUnrealEnginePythonHouseKeeper *Singleton; + if (!Singleton) + { + Singleton = new FUnrealEnginePythonHouseKeeper(); + // register a new delegate for the GC +#if ENGINE_MINOR_VERSION >= 18 + FCoreUObjectDelegates::GetPostGarbageCollect().AddRaw(Singleton, &FUnrealEnginePythonHouseKeeper::RunGCDelegate); +#else + FCoreUObjectDelegates::PostGarbageCollect.AddRaw(Singleton, &FUnrealEnginePythonHouseKeeper::RunGCDelegate); +#endif + } + return Singleton; +} + +void FUnrealEnginePythonHouseKeeper::RunGCDelegate() +{ + FScopePythonGIL gil; + RunGC(); +} + +int32 FUnrealEnginePythonHouseKeeper::RunGC() +{ + int32 Garbaged = PyUObjectsGC(); + Garbaged += DelegatesGC(); + return Garbaged; +} + +bool FUnrealEnginePythonHouseKeeper::IsValidPyUObject(ue_PyUObject *PyUObject) +{ + if (!PyUObject) + return false; + + UObject *Object = PyUObject->ue_object; + FPythonUOjectTracker *Tracker = UObjectPyMapping.Find(Object); + if (!Tracker) + { + return false; + } + + if (!Tracker->Owner.IsValid()) + return false; + + return true; + +} + +void FUnrealEnginePythonHouseKeeper::TrackUObject(UObject *Object) +{ + FPythonUOjectTracker *Tracker = UObjectPyMapping.Find(Object); + if (!Tracker) + { + return; + } + if (Tracker->bPythonOwned) + return; + Tracker->bPythonOwned = true; + // when a new ue_PyUObject spawns, it has a reference counting of two + Py_DECREF(Tracker->PyUObject); + Tracker->PyUObject->owned = 1; + PythonTrackedObjects.Add(Object); +} + +void FUnrealEnginePythonHouseKeeper::UntrackUObject(UObject *Object) +{ + PythonTrackedObjects.Remove(Object); +} + +void FUnrealEnginePythonHouseKeeper::RegisterPyUObject(UObject *Object, ue_PyUObject *InPyUObject) +{ + UObjectPyMapping.Add(Object, FPythonUOjectTracker(Object, InPyUObject)); +} + +void FUnrealEnginePythonHouseKeeper::UnregisterPyUObject(UObject *Object) +{ + UObjectPyMapping.Remove(Object); +} + +ue_PyUObject *FUnrealEnginePythonHouseKeeper::GetPyUObject(UObject *Object) +{ + FPythonUOjectTracker *Tracker = UObjectPyMapping.Find(Object); + if (!Tracker) + { + return nullptr; + } + + if (!Tracker->Owner.IsValid(true)) + { +#if defined(UEPY_MEMORY_DEBUG) + UE_LOG(LogPython, Warning, TEXT("DEFREF'ing UObject at %p (refcnt: %d)"), Object, Tracker->PyUObject->ob_base.ob_refcnt); +#endif + if (!Tracker->bPythonOwned) + Py_DECREF((PyObject *)Tracker->PyUObject); + UnregisterPyUObject(Object); + return nullptr; +} + + return Tracker->PyUObject; +} + +uint32 FUnrealEnginePythonHouseKeeper::PyUObjectsGC() +{ + uint32 Garbaged = 0; + TArray BrokenList; + for (auto &UObjectPyItem : UObjectPyMapping) + { + UObject *Object = UObjectPyItem.Key; + FPythonUOjectTracker &Tracker = UObjectPyItem.Value; +#if defined(UEPY_MEMORY_DEBUG) + UE_LOG(LogPython, Warning, TEXT("Checking for UObject at %p"), Object); +#endif + if (!Tracker.Owner.IsValid(true)) + { +#if defined(UEPY_MEMORY_DEBUG) + UE_LOG(LogPython, Warning, TEXT("Removing UObject at %p (refcnt: %d)"), Object, Tracker.PyUObject->ob_base.ob_refcnt); +#endif + BrokenList.Add(Object); + Garbaged++; + } + else + { +#if defined(UEPY_MEMORY_DEBUG) + UE_LOG(LogPython, Error, TEXT("UObject at %p %s is in use"), Object, *Object->GetName()); +#endif +} + } + + for (UObject *Object : BrokenList) + { + FPythonUOjectTracker &Tracker = UObjectPyMapping[Object]; + if (!Tracker.bPythonOwned) + Py_DECREF((PyObject *)Tracker.PyUObject); + UnregisterPyUObject(Object); + } + + return Garbaged; + +} + + +int32 FUnrealEnginePythonHouseKeeper::DelegatesGC() +{ + int32 Garbaged = 0; +#if defined(UEPY_MEMORY_DEBUG) + UE_LOG(LogPython, Display, TEXT("Garbage collecting %d UObject delegates"), PyDelegatesTracker.Num()); +#endif + for (int32 i = PyDelegatesTracker.Num() - 1; i >= 0; --i) + { + FPythonDelegateTracker &Tracker = PyDelegatesTracker[i]; + if (!Tracker.Owner.IsValid(true)) + { + Tracker.Delegate->RemoveFromRoot(); + PyDelegatesTracker.RemoveAt(i); + Garbaged++; + } + + } + +#if defined(UEPY_MEMORY_DEBUG) + UE_LOG(LogPython, Display, TEXT("Garbage collecting %d Slate delegates"), PySlateDelegatesTracker.Num()); +#endif + + for (int32 i = PySlateDelegatesTracker.Num() - 1; i >= 0; --i) + { + FPythonSWidgetDelegateTracker &Tracker = PySlateDelegatesTracker[i]; + if (!Tracker.Owner.IsValid()) + { + PySlateDelegatesTracker.RemoveAt(i); + Garbaged++; + } + + } + return Garbaged; + } + +UPythonDelegate *FUnrealEnginePythonHouseKeeper::FindDelegate(UObject *Owner, PyObject *PyCallable) +{ + for (int32 i = PyDelegatesTracker.Num() - 1; i >= 0; --i) + { + FPythonDelegateTracker &Tracker = PyDelegatesTracker[i]; + if (Tracker.Owner.Get() == Owner && Tracker.Delegate->UsesPyCallable(PyCallable)) + return Tracker.Delegate; + } + return nullptr; +} + +UPythonDelegate *FUnrealEnginePythonHouseKeeper::NewDelegate(UObject *Owner, PyObject *PyCallable, UFunction *Signature) +{ + UPythonDelegate *Delegate = NewObject(); + + Delegate->AddToRoot(); + Delegate->SetPyCallable(PyCallable); + Delegate->SetSignature(Signature); + + FPythonDelegateTracker Tracker(Delegate, Owner); + PyDelegatesTracker.Add(Tracker); + + return Delegate; +} + +TSharedRef FUnrealEnginePythonHouseKeeper::NewSlateDelegate(TSharedRef Owner, PyObject *PyCallable) +{ + TSharedRef Delegate = MakeShareable(new FPythonSlateDelegate()); + Delegate->SetPyCallable(PyCallable); + + FPythonSWidgetDelegateTracker Tracker(Delegate, Owner); + PySlateDelegatesTracker.Add(Tracker); + + return Delegate; +} + +TSharedRef FUnrealEnginePythonHouseKeeper::NewDeferredSlateDelegate(PyObject *PyCallable) +{ + TSharedRef Delegate = MakeShareable(new FPythonSlateDelegate()); + Delegate->SetPyCallable(PyCallable); + + return Delegate; +} + +TSharedRef FUnrealEnginePythonHouseKeeper::NewPythonSmartDelegate(PyObject *PyCallable) +{ + TSharedRef Delegate = MakeShareable(new FPythonSmartDelegate()); + Delegate->SetPyCallable(PyCallable); + + PyStaticSmartDelegatesTracker.Add(Delegate); + + return Delegate; +} + +void FUnrealEnginePythonHouseKeeper::TrackDeferredSlateDelegate(TSharedRef Delegate, TSharedRef Owner) +{ + FPythonSWidgetDelegateTracker Tracker(Delegate, Owner); + PySlateDelegatesTracker.Add(Tracker); +} + +TSharedRef FUnrealEnginePythonHouseKeeper::NewStaticSlateDelegate(PyObject *PyCallable) +{ + TSharedRef Delegate = MakeShareable(new FPythonSlateDelegate()); + Delegate->SetPyCallable(PyCallable); + + PyStaticSlateDelegatesTracker.Add(Delegate); + + return Delegate; +} + diff --git a/Source/UnrealEnginePython/Private/Slate/UEPyFMenuBuilder.cpp b/Source/UnrealEnginePython/Private/Slate/UEPyFMenuBuilder.cpp index d0d5a5549..3e0b978ed 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPyFMenuBuilder.cpp +++ b/Source/UnrealEnginePython/Private/Slate/UEPyFMenuBuilder.cpp @@ -2,10 +2,10 @@ #include "Wrappers/UEPyESlateEnums.h" -static PyObject *py_ue_fmenu_builder_begin_section(ue_PyFMenuBuilder *self, PyObject * args) +static PyObject* py_ue_fmenu_builder_begin_section(ue_PyFMenuBuilder* self, PyObject* args) { - char *name; - char *text; + char* name; + char* text; if (!PyArg_ParseTuple(args, "ss:begin_section", &name, &text)) return nullptr; @@ -14,27 +14,31 @@ static PyObject *py_ue_fmenu_builder_begin_section(ue_PyFMenuBuilder *self, PyOb Py_RETURN_NONE; } -static PyObject *py_ue_fmenu_builder_end_section(ue_PyFMenuBuilder *self, PyObject * args) +static PyObject* py_ue_fmenu_builder_end_section(ue_PyFMenuBuilder* self, PyObject* args) { self->menu_builder.EndSection(); Py_RETURN_NONE; } -static PyObject *py_ue_fmenu_builder_make_widget(ue_PyFMenuBuilder *self, PyObject * args) +static PyObject* py_ue_fmenu_builder_make_widget(ue_PyFMenuBuilder* self, PyObject* args) { - ue_PySWidget *ret = (ue_PySWidget *)PyObject_New(ue_PySWidget, &ue_PySWidgetType); + ue_PySWidget* ret = (ue_PySWidget*)PyObject_New(ue_PySWidget, &ue_PySWidgetType); new (&ret->Widget) TSharedRef(self->menu_builder.MakeWidget()); - return (PyObject *)ret; + return (PyObject*)ret; } -static PyObject *py_ue_fmenu_builder_add_menu_entry(ue_PyFMenuBuilder *self, PyObject * args) +static PyObject* py_ue_fmenu_builder_add_menu_entry(ue_PyFMenuBuilder* self, PyObject* args) { - char *label; - char *tooltip; - PyObject *py_callable; - PyObject *py_obj = nullptr; + char* label; + char* tooltip; + PyObject* py_callable; + PyObject* py_obj = nullptr; +#if ENGINE_MINOR_VERSION >= 23 + int ui_action_type = (int)EUserInterfaceActionType::Button; +#else int ui_action_type = EUserInterfaceActionType::Button; +#endif if (!PyArg_ParseTuple(args, "ssO|Oi:add_menu_entry", &label, &tooltip, &py_callable, &py_obj, &ui_action_type)) return nullptr; @@ -58,14 +62,44 @@ static PyObject *py_ue_fmenu_builder_add_menu_entry(ue_PyFMenuBuilder *self, PyO } self->menu_builder.AddMenuEntry(FText::FromString(UTF8_TO_TCHAR(label)), FText::FromString(UTF8_TO_TCHAR(tooltip)), FSlateIcon(), FUIAction(handler), NAME_None, +#if ENGINE_MINOR_VERSION >= 23 + (EUserInterfaceActionType)ui_action_type); +#else (EUserInterfaceActionType::Type)ui_action_type); +#endif + + Py_RETURN_NONE; +} + +static PyObject* py_ue_fmenu_builder_add_sub_menu(ue_PyFMenuBuilder* self, PyObject* args) +{ + char* label; + char* tooltip; + PyObject* py_callable; + PyObject* py_bool = nullptr; + if (!PyArg_ParseTuple(args, "ssO|O:add_sub_menu", &label, &tooltip, &py_callable, &py_bool)) + return nullptr; + + if (!PyCallable_Check(py_callable)) + { + return PyErr_Format(PyExc_Exception, "argument is not callable"); + } + + + TSharedRef py_delegate = FUnrealEnginePythonHouseKeeper::Get()->NewStaticSlateDelegate(py_callable); + + FNewMenuDelegate menu_delegate; + menu_delegate.BindSP(py_delegate, &FPythonSlateDelegate::SubMenuPyBuilder); + + + self->menu_builder.AddSubMenu(FText::FromString(UTF8_TO_TCHAR(label)), FText::FromString(UTF8_TO_TCHAR(tooltip)), menu_delegate, (py_bool && PyObject_IsTrue(py_bool)) ? true : false); Py_RETURN_NONE; } -static PyObject *py_ue_fmenu_builder_add_menu_separator(ue_PyFMenuBuilder *self, PyObject * args) +static PyObject* py_ue_fmenu_builder_add_menu_separator(ue_PyFMenuBuilder* self, PyObject* args) { - char *name = nullptr; + char* name = nullptr; if (!PyArg_ParseTuple(args, "|s:add_menu_separator", &name)) return nullptr; @@ -81,9 +115,9 @@ static PyObject *py_ue_fmenu_builder_add_menu_separator(ue_PyFMenuBuilder *self, } #if WITH_EDITOR -static PyObject *py_ue_fmenu_builder_add_asset_actions(ue_PyFMenuBuilder *self, PyObject * args) +static PyObject* py_ue_fmenu_builder_add_asset_actions(ue_PyFMenuBuilder* self, PyObject* args) { - PyObject *py_assets; + PyObject* py_assets; if (!PyArg_ParseTuple(args, "O:add_asset_actions", &py_assets)) return nullptr; @@ -94,10 +128,10 @@ static PyObject *py_ue_fmenu_builder_add_asset_actions(ue_PyFMenuBuilder *self, return PyErr_Format(PyExc_Exception, "argument is not iterable"); } - TArray u_objects; - while (PyObject *item = PyIter_Next(py_assets)) + TArray u_objects; + while (PyObject * item = PyIter_Next(py_assets)) { - UObject *u_object = ue_py_check_type(item); + UObject* u_object = ue_py_check_type(item); if (u_object) { u_objects.Add(u_object); @@ -116,7 +150,7 @@ static PyObject *py_ue_fmenu_builder_add_asset_actions(ue_PyFMenuBuilder *self, } #endif -static PyObject *py_ue_fmenu_builder_add_search_widget(ue_PyFMenuBuilder *self, PyObject * args) +static PyObject* py_ue_fmenu_builder_add_search_widget(ue_PyFMenuBuilder* self, PyObject* args) { self->menu_builder.AddSearchWidget(); @@ -130,6 +164,7 @@ static PyMethodDef ue_PyFMenuBuilder_methods[] = { { "add_menu_entry", (PyCFunction)py_ue_fmenu_builder_add_menu_entry, METH_VARARGS, "" }, { "add_menu_separator", (PyCFunction)py_ue_fmenu_builder_add_menu_separator, METH_VARARGS, "" }, { "add_search_widget", (PyCFunction)py_ue_fmenu_builder_add_search_widget, METH_VARARGS, "" }, + { "add_sub_menu", (PyCFunction)py_ue_fmenu_builder_add_sub_menu, METH_VARARGS, "" }, #if WITH_EDITOR { "add_asset_actions", (PyCFunction)py_ue_fmenu_builder_add_asset_actions, METH_VARARGS, "" }, #endif @@ -137,13 +172,13 @@ static PyMethodDef ue_PyFMenuBuilder_methods[] = { }; -static PyObject *ue_PyFMenuBuilder_str(ue_PyFMenuBuilder *self) +static PyObject* ue_PyFMenuBuilder_str(ue_PyFMenuBuilder* self) { return PyUnicode_FromFormat("", &self->menu_builder); } -static void ue_py_fmenu_builder_dealloc(ue_PyFMenuBuilder *self) +static void ue_py_fmenu_builder_dealloc(ue_PyFMenuBuilder* self) { #if PY_MAJOR_VERSION < 3 self->ob_type->tp_free((PyObject*)self); @@ -183,14 +218,14 @@ static PyTypeObject ue_PyFMenuBuilderType = { ue_PyFMenuBuilder_methods, /* tp_methods */ }; -static int ue_py_fmenu_builder_init(ue_PyFMenuBuilder *self, PyObject *args, PyObject *kwargs) +static int ue_py_fmenu_builder_init(ue_PyFMenuBuilder* self, PyObject* args, PyObject* kwargs) { new(&self->menu_builder) FMenuBuilder(true, nullptr); return 0; } -void ue_python_init_fmenu_builder(PyObject *ue_module) +void ue_python_init_fmenu_builder(PyObject* ue_module) { ue_PyFMenuBuilderType.tp_new = PyType_GenericNew; @@ -200,12 +235,12 @@ void ue_python_init_fmenu_builder(PyObject *ue_module) return; Py_INCREF(&ue_PyFMenuBuilderType); - PyModule_AddObject(ue_module, "FMenuBuilder", (PyObject *)&ue_PyFMenuBuilderType); + PyModule_AddObject(ue_module, "FMenuBuilder", (PyObject*)& ue_PyFMenuBuilderType); } -PyObject *py_ue_new_fmenu_builder(FMenuBuilder menu_builder) +PyObject* py_ue_new_fmenu_builder(FMenuBuilder menu_builder) { - ue_PyFMenuBuilder *ret = (ue_PyFMenuBuilder *)PyObject_New(ue_PyFMenuBuilder, &ue_PyFMenuBuilderType); + ue_PyFMenuBuilder* ret = (ue_PyFMenuBuilder*)PyObject_New(ue_PyFMenuBuilder, &ue_PyFMenuBuilderType); new(&ret->menu_builder) FMenuBuilder(menu_builder); - return (PyObject *)ret; + return (PyObject*)ret; } diff --git a/Source/UnrealEnginePython/Private/Slate/UEPyFTabSpawnerEntry.cpp b/Source/UnrealEnginePython/Private/Slate/UEPyFTabSpawnerEntry.cpp index d577a951a..bcf3d7438 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPyFTabSpawnerEntry.cpp +++ b/Source/UnrealEnginePython/Private/Slate/UEPyFTabSpawnerEntry.cpp @@ -36,8 +36,8 @@ static PyMethodDef ue_PyFTabSpawnerEntry_methods[] = { static PyObject *ue_PyFTabSpawnerEntry_str(ue_PyFTabSpawnerEntry *self) { - return PyUnicode_FromFormat("", - PyUnicode_FromString(TCHAR_TO_UTF8(*self->spawner_entry->GetDisplayName().ToString()))); + return PyUnicode_FromFormat("", + TCHAR_TO_UTF8(*self->spawner_entry->GetDisplayName().ToString())); } static PyTypeObject ue_PyFTabSpawnerEntryType = { diff --git a/Source/UnrealEnginePython/Private/Slate/UEPySBorder.cpp b/Source/UnrealEnginePython/Private/Slate/UEPySBorder.cpp index 3aff74d85..b2a8e4d0f 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPySBorder.cpp +++ b/Source/UnrealEnginePython/Private/Slate/UEPySBorder.cpp @@ -153,7 +153,7 @@ static int ue_py_sborder_init(ue_PySBorder *self, PyObject *args, PyObject *kwar ue_py_slate_farguments_optional_struct_ptr("border_image", BorderImage, FSlateBrush); ue_py_slate_farguments_optional_enum("h_align", HAlign, EHorizontalAlignment); ue_py_slate_farguments_optional_enum("v_align", VAlign, EVerticalAlignment); - ue_py_slate_farguments_struct("padding", Padding, FMargin); + ue_py_slate_farguments_padding("padding", Padding); ue_py_slate_farguments_struct("foreground_color", ForegroundColor, FSlateColor); ue_py_slate_farguments_fvector2d("content_scale", ContentScale); ue_py_slate_farguments_fvector2d("desired_size_scale", DesiredSizeScale); diff --git a/Source/UnrealEnginePython/Private/Slate/UEPySGridPanel.cpp b/Source/UnrealEnginePython/Private/Slate/UEPySGridPanel.cpp index 8fd149a40..06df26b6a 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPySGridPanel.cpp +++ b/Source/UnrealEnginePython/Private/Slate/UEPySGridPanel.cpp @@ -11,34 +11,43 @@ static PyObject *py_ue_sgrid_panel_clear_children(ue_PySGridPanel *self, PyObjec Py_RETURN_NONE; } -static PyObject *py_ue_sgrid_panel_add_slot(ue_PySGridPanel *self, PyObject * args) +static PyObject *py_ue_sgrid_panel_add_slot(ue_PySGridPanel *self, PyObject * args, PyObject * kwargs) { ue_py_slate_cast(SGridPanel); + PyObject *py_content; - int col; + int column; int row; int layer = 0; - if (!PyArg_ParseTuple(args, "Oii|i:add_slot", &py_content, &col, &row, &layer)) - { - return NULL; - } + int foobar = 0; + PyObject *py_nudge = nullptr; - TSharedPtr Content = py_ue_is_swidget(py_content); - if (!Content.IsValid()) + char *kwlist[] = { (char *)"widget", (char *)"column", (char *)"row", (char *)"layer", (char *)"column_span", (char *)"nudge", (char *)"row_span", nullptr }; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oii|iiOi:add_slot", kwlist, &py_content, &column, &row, &layer, &foobar, &py_nudge, &foobar)) { return nullptr; } + int32 retCode = [&]() { + ue_py_slate_setup_hack_slot_args_grid(SGridPanel, py_SGridPanel, column, row, SGridPanel::Layer(layer)); + ue_py_slate_farguments_optional_int32("column_span", ColumnSpan); + ue_py_slate_farguments_optional_fvector2d("nudge", Nudge); + ue_py_slate_farguments_optional_int32("row_span", RowSpan); + return 0; + }(); - SGridPanel::FSlot &fslot = py_SGridPanel->AddSlot(col, row, SGridPanel::Layer(layer)); - fslot.AttachWidget(Content.ToSharedRef()); + if (retCode != 0) + { + return PyErr_Format(PyExc_Exception, "could not add GridPanel slot"); + } Py_RETURN_SLATE_SELF; } static PyMethodDef ue_PySGridPanel_methods[] = { { "clear_children", (PyCFunction)py_ue_sgrid_panel_clear_children, METH_VARARGS, "" }, - { "add_slot", (PyCFunction)py_ue_sgrid_panel_add_slot, METH_VARARGS, "" }, +#pragma warning(suppress: 4191) + { "add_slot", (PyCFunction)py_ue_sgrid_panel_add_slot, METH_VARARGS | METH_KEYWORDS, "" }, { NULL } /* Sentinel */ }; diff --git a/Source/UnrealEnginePython/Private/Slate/UEPySImage.h b/Source/UnrealEnginePython/Private/Slate/UEPySImage.h index fb608ef55..584e14e50 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPySImage.h +++ b/Source/UnrealEnginePython/Private/Slate/UEPySImage.h @@ -5,7 +5,11 @@ #include "UEPySLeafWidget.h" +#if ENGINE_MINOR_VERSION > 21 +#include "Runtime/SlateCore/Public/Widgets/Images/SImage.h" +#else #include "Runtime/Slate/Public/Widgets/Images/SImage.h" +#endif #include "Runtime/SlateCore/Public/Styling/SlateBrush.h" extern PyTypeObject ue_PySImageType; diff --git a/Source/UnrealEnginePython/Private/Slate/UEPySPythonEditorViewport.cpp b/Source/UnrealEnginePython/Private/Slate/UEPySPythonEditorViewport.cpp index 649b842ca..b9c19952c 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPySPythonEditorViewport.cpp +++ b/Source/UnrealEnginePython/Private/Slate/UEPySPythonEditorViewport.cpp @@ -4,6 +4,7 @@ #if WITH_EDITOR #include "Components/DirectionalLightComponent.h" +#include "Wrappers/UEPyFEditorViewportClient.h" static PyObject *py_ue_spython_editor_viewport_get_world(ue_PySPythonEditorViewport *self, PyObject * args) { @@ -11,6 +12,12 @@ static PyObject *py_ue_spython_editor_viewport_get_world(ue_PySPythonEditorViewp Py_RETURN_UOBJECT(py_SPythonEditorViewport->GetPythonWorld()); } +static PyObject *py_ue_spython_editor_viewport_get_editor_viewport_client(ue_PySPythonEditorViewport *self, PyObject * args) +{ + ue_py_slate_cast(SPythonEditorViewport); + return py_ue_new_feditor_viewport_client(py_SPythonEditorViewport->GetViewportClient().ToSharedRef()); +} + static PyObject *py_ue_spython_editor_viewport_set_show_bounds(ue_PySPythonEditorViewport *self, PyObject * args) { ue_py_slate_cast(SPythonEditorViewport); @@ -241,6 +248,7 @@ static PyObject *py_ue_spython_editor_viewport_get_light(ue_PySPythonEditorViewp static PyMethodDef ue_PySPythonEditorViewport_methods[] = { { "simulate", (PyCFunction)py_ue_spython_editor_viewport_simulate, METH_VARARGS, "" }, { "get_world", (PyCFunction)py_ue_spython_editor_viewport_get_world, METH_VARARGS, "" }, + { "get_editor_viewport_client", (PyCFunction)py_ue_spython_editor_viewport_get_editor_viewport_client, METH_VARARGS, "" }, { "set_show_bounds", (PyCFunction)py_ue_spython_editor_viewport_set_show_bounds, METH_VARARGS, "" }, { "set_show_stats", (PyCFunction)py_ue_spython_editor_viewport_set_show_stats, METH_VARARGS, "" }, { "set_view_mode", (PyCFunction)py_ue_spython_editor_viewport_set_view_mode, METH_VARARGS, "" }, diff --git a/Source/UnrealEnginePython/Private/Slate/UEPySScrollBox.cpp b/Source/UnrealEnginePython/Private/Slate/UEPySScrollBox.cpp index 3a22a5c92..d365d2e4e 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPySScrollBox.cpp +++ b/Source/UnrealEnginePython/Private/Slate/UEPySScrollBox.cpp @@ -5,34 +5,19 @@ static PyObject *py_ue_sscroll_box_add_slot(ue_PySScrollBox *self, PyObject * args, PyObject *kwargs) { ue_py_slate_cast(SScrollBox); - PyObject *py_content; - int h_align = 0; - int v_align = 0; - - char *kwlist[] = { (char *)"widget", - (char *)"h_align", - (char *)"v_align", - nullptr }; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|ii:add_slot", kwlist, - &py_content, - &h_align, - &v_align)) - { - return nullptr; - } - TSharedPtr Content = py_ue_is_swidget(py_content); - if (!Content.IsValid()) + int32 retCode = [&]() { + ue_py_slate_setup_hack_slot_args(SScrollBox, py_SScrollBox); + ue_py_slate_farguments_optional_enum("h_align", HAlign, EHorizontalAlignment); + ue_py_slate_farguments_optional_enum("v_align", VAlign, EVerticalAlignment); + return 0; + }(); + + if (retCode != 0) { - return nullptr; + return PyErr_Format(PyExc_Exception, "could not add vertical slot"); } - SScrollBox::FSlot &fslot = py_SScrollBox->AddSlot(); - fslot.AttachWidget(Content.ToSharedRef()); - fslot.HAlign((EHorizontalAlignment)h_align); - fslot.VAlign((EVerticalAlignment)v_align); - Py_RETURN_SLATE_SELF; } diff --git a/Source/UnrealEnginePython/Private/Slate/UEPySTextBlock.cpp b/Source/UnrealEnginePython/Private/Slate/UEPySTextBlock.cpp index a994186a5..92df02d50 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPySTextBlock.cpp +++ b/Source/UnrealEnginePython/Private/Slate/UEPySTextBlock.cpp @@ -70,7 +70,11 @@ static int ue_py_stext_block_init(ue_PySTextBlock *self, PyObject *args, PyObjec ue_py_slate_farguments_float("line_height_percentage", LineHeightPercentage); ue_py_slate_farguments_struct("margin", Margin, FMargin); ue_py_slate_farguments_float("min_desired_width", MinDesiredWidth); +#if ENGINE_MINOR_VERSION >= 23 + ue_py_slate_farguments_event("on_double_clicked", OnDoubleClicked, FPointerEventHandler, OnMouseEvent); +#else ue_py_slate_farguments_event("on_double_clicked", OnDoubleClicked, FOnClicked, OnClicked); +#endif ue_py_slate_farguments_flinear_color("shadow_color_and_opacity", ShadowColorAndOpacity); ue_py_slate_farguments_fvector2d("shadow_offset", ShadowOffset); ue_py_slate_farguments_text("text", Text); diff --git a/Source/UnrealEnginePython/Private/Slate/UEPySWidget.cpp b/Source/UnrealEnginePython/Private/Slate/UEPySWidget.cpp index 62c7a3e33..1104338d8 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPySWidget.cpp +++ b/Source/UnrealEnginePython/Private/Slate/UEPySWidget.cpp @@ -323,6 +323,34 @@ static PyObject *py_ue_swidget_new_ref(ue_PySWidget *self, PyObject * args) return (PyObject *)py_ue_new_swidget(Widget.ToSharedRef(), &ue_PySWidgetType); } +static PyObject *py_ue_swidget_assign(ue_PySWidget *self, PyObject * args) +{ + char *global_name; + if (!PyArg_ParseTuple(args, "s:assign", &global_name)) + { + return nullptr; + } + + PyObject *py_globals = PyEval_GetGlobals(); + if (!py_globals) + { + return PyErr_Format(PyExc_Exception, "unable to retrieve globals"); + } + + if (!PyDict_GetItemString(py_globals, global_name)) + { + PyErr_Clear(); + return PyErr_Format(PyExc_Exception, "global \"%s\" not found", global_name); + } + + if (PyDict_SetItemString(py_globals, global_name, (PyObject *)self) < 0) + { + return PyErr_Format(PyExc_Exception, "unable to assign global \"%s\" to SWidget", global_name); + } + + Py_RETURN_SLATE_SELF; +} + static PyMethodDef ue_PySWidget_methods[] = { { "new_ref", (PyCFunction)py_ue_swidget_new_ref, METH_VARARGS, "" }, { "get_shared_reference_count", (PyCFunction)py_ue_swidget_get_shared_reference_count, METH_VARARGS, "" }, @@ -344,6 +372,8 @@ static PyMethodDef ue_PySWidget_methods[] = { #endif { "on_mouse_button_down", (PyCFunction)py_ue_swidget_on_mouse_button_down, METH_VARARGS, "" }, { "on_mouse_button_up", (PyCFunction)py_ue_swidget_on_mouse_button_up, METH_VARARGS, "" }, + + { "assign", (PyCFunction)py_ue_swidget_assign, METH_VARARGS, "" }, { NULL } /* Sentinel */ }; diff --git a/Source/UnrealEnginePython/Private/Slate/UEPySWindow.cpp b/Source/UnrealEnginePython/Private/Slate/UEPySWindow.cpp index 835ded96f..41f6615ab 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPySWindow.cpp +++ b/Source/UnrealEnginePython/Private/Slate/UEPySWindow.cpp @@ -126,6 +126,26 @@ static PyObject *py_ue_swindow_request_destroy(ue_PySWindow *self, PyObject * ar Py_RETURN_NONE; } +static PyObject *py_ue_swindow_bring_to_front(ue_PySWindow *self, PyObject * args) +{ + ue_py_slate_cast(SWindow); + + PyObject *py_force = nullptr; + + if (!PyArg_ParseTuple(args, "|O:set_as_owner", &py_force)) + { + return nullptr; + } + + bool bForce = false; + if (py_force && PyObject_IsTrue(py_force)) + bForce = true; + + py_SWindow->BringToFront(bForce); + + Py_RETURN_NONE; +} + #if WITH_EDITOR static PyObject *py_ue_swindow_add_modal(ue_PySWindow *self, PyObject * args) { @@ -189,6 +209,7 @@ static PyMethodDef ue_PySWindow_methods[] = { { "get_handle", (PyCFunction)py_ue_swindow_get_handle, METH_VARARGS, "" }, { "set_as_owner", (PyCFunction)py_ue_swindow_set_as_owner, METH_VARARGS, "" }, { "request_destroy", (PyCFunction)py_ue_swindow_request_destroy, METH_VARARGS, "" }, + { "bring_to_front", (PyCFunction)py_ue_swindow_bring_to_front, METH_VARARGS, "" }, #if WITH_EDITOR { "add_modal", (PyCFunction)py_ue_swindow_add_modal, METH_VARARGS, "" }, #endif diff --git a/Source/UnrealEnginePython/Private/Slate/UEPySlate.cpp b/Source/UnrealEnginePython/Private/Slate/UEPySlate.cpp index 71dc593ad..2e705a303 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPySlate.cpp +++ b/Source/UnrealEnginePython/Private/Slate/UEPySlate.cpp @@ -416,6 +416,7 @@ void FPythonSlateDelegate::MenuPyAssetBuilder(FMenuBuilder &Builder, TArray FPythonSlateDelegate::OnExtendContentBrowserMenu(const TArray& SelectedAssets) { TSharedRef Extender(new FExtender()); @@ -427,6 +428,21 @@ TSharedRef FPythonSlateDelegate::OnExtendContentBrowserMenu(const TAr #endif + +void FPythonSlateDelegate::SubMenuPyBuilder(FMenuBuilder &Builder) +{ + FScopePythonGIL gil; + + PyObject *ret = PyObject_CallFunction(py_callable, (char *)"N", py_ue_new_fmenu_builder(Builder)); + if (!ret) + { + unreal_engine_py_log_error(); + return; + } + Py_DECREF(ret); +} + + TSharedRef FPythonSlateDelegate::OnGenerateWidget(TSharedPtr py_item) { FScopePythonGIL gil; @@ -756,9 +772,9 @@ FLinearColor FPythonSlateDelegate::GetterFLinearColor() const TSharedRef FPythonSlateDelegate::SpawnPythonTab(const FSpawnTabArgs &args) { + FScopePythonGIL gil; TSharedRef dock_tab = SNew(SDockTab).TabRole(ETabRole::NomadTab); - PyObject *py_dock = (PyObject *)ue_py_get_swidget(dock_tab); - PyObject *ret = PyObject_CallFunction(py_callable, (char *)"O", py_dock); + PyObject *ret = PyObject_CallFunction(py_callable, (char *)"N", ue_py_get_swidget(dock_tab)); if (!ret) { unreal_engine_py_log_error(); @@ -1004,8 +1020,11 @@ class FPythonSlateCommands : public TCommands virtual void RegisterCommands() override { commands = MakeShareable(new FUICommandList); - +#if ENGINE_MINOR_VERSION >= 23 + MakeUICommand_InternalUseOnly(this, command, nullptr, *name, *name, TCHAR_TO_UTF8(*name), *name, *name, EUserInterfaceActionType::Button, FInputGesture()); +#else UI_COMMAND_Function(this, command, nullptr, *name, *name, TCHAR_TO_UTF8(*name), *name, *name, EUserInterfaceActionType::Button, FInputGesture()); +#endif commands->MapAction(command, FExecuteAction::CreateRaw(this, &FPythonSlateCommands::Callback), FCanExecuteAction()); } @@ -1321,6 +1340,7 @@ PyObject *py_unreal_engine_add_menu_bar_extension(PyObject * self, PyObject * ar if (!PyCallable_Check(py_callable)) return PyErr_Format(PyExc_Exception, "argument is not callable"); + TSharedRef *commands = new TSharedRef(new FPythonSlateCommands()); commands->Get().Setup(command_name, py_callable); @@ -1332,8 +1352,7 @@ PyObject *py_unreal_engine_add_menu_bar_extension(PyObject * self, PyObject * ar ExtensibleModule.GetMenuExtensibilityManager()->AddExtender(extender); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } PyObject *py_unreal_engine_add_tool_bar_extension(PyObject * self, PyObject * args) @@ -1400,7 +1419,8 @@ PyObject *py_unreal_engine_register_nomad_tab_spawner(PyObject * self, PyObject char *name; PyObject *py_callable; - if (!PyArg_ParseTuple(args, "sO:register_nomad_tab_spawner", &name, &py_callable)) + PyObject *py_icon = nullptr; + if (!PyArg_ParseTuple(args, "sO|O:register_nomad_tab_spawner", &name, &py_callable, &py_icon)) { return NULL; } @@ -1412,14 +1432,33 @@ PyObject *py_unreal_engine_register_nomad_tab_spawner(PyObject * self, PyObject TSharedRef py_delegate = FUnrealEnginePythonHouseKeeper::Get()->NewStaticSlateDelegate(py_callable); spawn_tab.BindSP(py_delegate, &FPythonSlateDelegate::SpawnPythonTab); - FTabSpawnerEntry *spawner_entry = &FGlobalTabmanager::Get()->RegisterNomadTabSpawner(UTF8_TO_TCHAR(name), spawn_tab) + FName TabName = FName(UTF8_TO_TCHAR(name)); + + // avoid crash if re-registering the same tab + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(TabName); + + FSlateIcon Icon = FSlateIcon(); + if (py_icon) + { + ue_PyFSlateIcon *slate_icon = py_ue_is_fslate_icon(py_icon); + if (!slate_icon) + { + return PyErr_Format(PyExc_Exception, "argument is not a FSlateIcon"); + } + Icon = slate_icon->icon; + } + + FTabSpawnerEntry *SpawnerEntry = &FGlobalTabmanager::Get()->RegisterNomadTabSpawner(TabName, spawn_tab) + .SetDisplayName(FText::FromString((TabName).ToString())) + .SetTooltipText(FText::FromString((TabName).ToString())) + .SetIcon(Icon) // TODO: more generic way to set the group #if WITH_EDITOR .SetGroup(WorkspaceMenu::GetMenuStructure().GetDeveloperToolsMiscCategory()) #endif ; - PyObject *ret = py_ue_new_ftab_spawner_entry(spawner_entry); + PyObject *ret = py_ue_new_ftab_spawner_entry(SpawnerEntry); Py_INCREF(ret); return ret; } diff --git a/Source/UnrealEnginePython/Private/Slate/UEPySlate.h b/Source/UnrealEnginePython/Private/Slate/UEPySlate.h index b7ee58622..bda71c376 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPySlate.h +++ b/Source/UnrealEnginePython/Private/Slate/UEPySlate.h @@ -136,14 +136,14 @@ ue_PySWidget *ue_py_get_swidget(TSharedRef s_widget); #define ue_py_slate_up(_type, _func, _param, _attribute) ue_py_slate_base_up(TAttribute<_type>, _func, _param, _attribute) #define ue_py_slate_farguments_text(param, attribute) ue_py_slate_up(FText, GetterFText, param, attribute)\ - else if (PyUnicode_Check(value)) {\ + else if (PyUnicodeOrString_Check(value)) {\ arguments.attribute(FText::FromString(UTF8_TO_TCHAR(UEPyUnicode_AsUTF8(value))));\ }\ ue_py_slate_down(param) #define ue_py_slate_farguments_string(param, attribute) ue_py_slate_up(FString, GetterFString, param, attribute)\ - else if (PyUnicode_Check(value)) {\ + else if (PyUnicodeOrString_Check(value)) {\ arguments.attribute(UTF8_TO_TCHAR(UEPyUnicode_AsUTF8(value)));\ }\ ue_py_slate_down(param) @@ -196,6 +196,16 @@ ue_PySWidget *ue_py_get_swidget(TSharedRef s_widget); }\ ue_py_slate_down(param) + +#define ue_py_slate_farguments_int32(param, attribute) ue_py_slate_up(int32, GetterInt, param, attribute)\ + else if (PyNumber_Check(value)) {\ + PyObject *py_int = PyNumber_Long(value);\ + arguments.attribute((int32)PyLong_AsLong(py_int)); \ + Py_DECREF(py_int);\ + }\ + ue_py_slate_down(param) + + #define ue_py_slate_farguments_tint(param, attribute) ue_py_slate_up(TOptional, GetterIntT>, param, attribute)\ else if (PyNumber_Check(value)) {\ PyObject *py_int = PyNumber_Long(value);\ @@ -312,6 +322,20 @@ ue_PySWidget *ue_py_get_swidget(TSharedRef s_widget); } +#define ue_py_slate_farguments_optional_int32(param, attribute) { PyObject *value = ue_py_dict_get_item(kwargs, param);\ + if (value) {\ + if (PyNumber_Check(value)) {\ + PyObject *py_int = PyNumber_Long(value);\ + arguments.attribute((int32)PyLong_AsLong(py_int)); \ + Py_DECREF(py_int);\ + }\ + else {\ + PyErr_SetString(PyExc_TypeError, "unsupported type for attribute " param); \ + return -1;\ + }\ + }\ +} + #define ue_py_slate_farguments_optional_float(param, attribute) { PyObject *value = ue_py_dict_get_item(kwargs, param);\ if (value) {\ @@ -342,14 +366,14 @@ ue_PySWidget *ue_py_get_swidget(TSharedRef s_widget); } #define ue_py_slate_farguments_optional_string(param, attribute) { PyObject *value = ue_py_dict_get_item(kwargs, param);\ - if (PyUnicode_Check(value)) {\ + if (PyUnicodeOrString_Check(value)) {\ arguments.attribute(UTF8_TO_TCHAR(UEPyUnicode_AsUTF8(value)));\ }\ } #define ue_py_slate_farguments_optional_text(param, attribute) { PyObject *value = ue_py_dict_get_item(kwargs, param);\ if (value) {\ - if (PyUnicode_Check(value)) {\ + if (PyUnicodeOrString_Check(value)) {\ arguments.attribute(FText::FromString(UTF8_TO_TCHAR(UEPyUnicode_AsUTF8(value))));\ }\ else {\ @@ -450,6 +474,7 @@ ue_PySWidget *ue_py_get_swidget(TSharedRef s_widget); #define ue_py_slate_farguments_required_slot(param) { PyObject *value = ue_py_dict_get_item(kwargs, param);\ value = value ? value : PyTuple_GetItem(args, 0);\ + if (!value) {PyErr_Clear(); PyErr_SetString(PyExc_TypeError, "you need to specify a widget"); return -1;}\ TSharedPtr Widget = py_ue_is_swidget(value);\ if (Widget.IsValid())\ arguments.AttachWidget(Widget.ToSharedRef());\ @@ -464,6 +489,10 @@ ue_PySWidget *ue_py_get_swidget(TSharedRef s_widget); TArray> DeferredSlateDelegates;\ ue_py_slate_farguments_required_slot("widget"); +#define ue_py_slate_setup_hack_slot_args_grid(_type, _swidget_ref, column, row, layer) _type::FSlot &arguments = _swidget_ref->AddSlot(column, row, layer);\ + TArray> DeferredSlateDelegates;\ + ue_py_slate_farguments_required_slot("widget"); + void ue_python_init_slate(PyObject *); diff --git a/Source/UnrealEnginePython/Private/Slate/UEPySlateDelegate.h b/Source/UnrealEnginePython/Private/Slate/UEPySlateDelegate.h index f2290e0d8..f74f9fe34 100644 --- a/Source/UnrealEnginePython/Private/Slate/UEPySlateDelegate.h +++ b/Source/UnrealEnginePython/Private/Slate/UEPySlateDelegate.h @@ -50,6 +50,7 @@ class FPythonSlateDelegate : public FPythonSmartDelegate #endif void OnWindowClosed(const TSharedRef &Window); + void SubMenuPyBuilder(FMenuBuilder &Builder); TSharedPtr OnContextMenuOpening(); TSharedRef OnGenerateWidget(TSharedPtr py_item); diff --git a/Source/UnrealEnginePython/Private/SlateApplication/UEPyFSlateApplication.cpp b/Source/UnrealEnginePython/Private/SlateApplication/UEPyFSlateApplication.cpp index 358bf174d..33982e626 100644 --- a/Source/UnrealEnginePython/Private/SlateApplication/UEPyFSlateApplication.cpp +++ b/Source/UnrealEnginePython/Private/SlateApplication/UEPyFSlateApplication.cpp @@ -157,7 +157,11 @@ static PyObject *py_ue_process_key_char_event(PyObject *cls, PyObject * args) static PyObject *py_ue_create(PyObject *cls, PyObject * args) { #if ENGINE_MINOR_VERSION > 18 +#if ENGINE_MINOR_VERSION > 20 + FSlateApplication::InitHighDPI(true); +#else FSlateApplication::InitHighDPI(); +#endif #endif FSlateApplication::Create(); diff --git a/Source/UnrealEnginePython/Private/UEPyEditor.cpp b/Source/UnrealEnginePython/Private/UEPyEditor.cpp index f23f7c59e..1b3df7bca 100644 --- a/Source/UnrealEnginePython/Private/UEPyEditor.cpp +++ b/Source/UnrealEnginePython/Private/UEPyEditor.cpp @@ -286,7 +286,7 @@ PyObject *py_unreal_engine_editor_play(PyObject * self, PyObject * args) Py_END_ALLOW_THREADS; Py_RETURN_NONE; - } +} PyObject *py_unreal_engine_editor_select_actor(PyObject * self, PyObject * args) { @@ -462,7 +462,7 @@ PyObject *py_unreal_engine_import_asset(PyObject * self, PyObject * args) } Py_RETURN_NONE; - } +} PyObject *py_unreal_engine_editor_tick(PyObject * self, PyObject * args) { @@ -639,10 +639,11 @@ PyObject *py_unreal_engine_create_asset(PyObject * self, PyObject * args) PyObject *py_unreal_engine_get_asset_referencers(PyObject * self, PyObject * args) { char *path; + int depency_type = (int)EAssetRegistryDependencyType::All; - if (!PyArg_ParseTuple(args, "s:get_asset_referencers", &path)) + if (!PyArg_ParseTuple(args, "s|i:get_asset_referencers", &path, &depency_type)) { - return NULL; + return nullptr; } if (!GEditor) @@ -650,7 +651,7 @@ PyObject *py_unreal_engine_get_asset_referencers(PyObject * self, PyObject * arg FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked("AssetRegistry"); TArray referencers; - AssetRegistryModule.Get().GetReferencers(UTF8_TO_TCHAR(path), referencers); + AssetRegistryModule.Get().GetReferencers(UTF8_TO_TCHAR(path), referencers, (EAssetRegistryDependencyType::Type) depency_type); PyObject *referencers_list = PyList_New(0); for (FName name : referencers) @@ -660,11 +661,38 @@ PyObject *py_unreal_engine_get_asset_referencers(PyObject * self, PyObject * arg return referencers_list; } +PyObject *py_unreal_engine_get_asset_identifier_referencers(PyObject * self, PyObject * args) +{ + char *path; + int depency_type = (int)EAssetRegistryDependencyType::All; + + if (!PyArg_ParseTuple(args, "s|i:get_asset_identifier_referencers", &path, &depency_type)) + { + return nullptr; + } + + if (!GEditor) + return PyErr_Format(PyExc_Exception, "no GEditor found"); + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked("AssetRegistry"); + TArray referencers; + AssetRegistryModule.Get().GetReferencers(FAssetIdentifier::FromString(UTF8_TO_TCHAR(path)), referencers, (EAssetRegistryDependencyType::Type) depency_type); + + PyObject *referencers_list = PyList_New(0); + for (FAssetIdentifier identifier : referencers) + { + PyList_Append(referencers_list, PyUnicode_FromString(TCHAR_TO_UTF8(*identifier.ToString()))); + } + return referencers_list; +} + + PyObject *py_unreal_engine_get_asset_dependencies(PyObject * self, PyObject * args) { char *path; + int depency_type = (int)EAssetRegistryDependencyType::All; - if (!PyArg_ParseTuple(args, "s:get_asset_dependencies", &path)) + if (!PyArg_ParseTuple(args, "s|i:get_asset_dependencies", &path, &depency_type)) { return NULL; } @@ -674,7 +702,7 @@ PyObject *py_unreal_engine_get_asset_dependencies(PyObject * self, PyObject * ar FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked("AssetRegistry"); TArray dependencies; - AssetRegistryModule.Get().GetDependencies(UTF8_TO_TCHAR(path), dependencies); + AssetRegistryModule.Get().GetDependencies(UTF8_TO_TCHAR(path), dependencies, (EAssetRegistryDependencyType::Type) depency_type); PyObject *dependencies_list = PyList_New(0); for (FName name : dependencies) @@ -697,6 +725,19 @@ PyObject *py_unreal_engine_get_long_package_path(PyObject * self, PyObject * arg return PyUnicode_FromString(TCHAR_TO_UTF8(*(package_path))); } +PyObject *py_unreal_engine_get_long_package_asset_name(PyObject * self, PyObject * args) +{ + char *path; + if (!PyArg_ParseTuple(args, "s:get_long_package_asset_name", &path)) + { + return NULL; + } + + const FString asset_name = FPackageName::GetLongPackageAssetName(UTF8_TO_TCHAR(path)); + + return PyUnicode_FromString(TCHAR_TO_UTF8(*(asset_name))); +} + PyObject *py_unreal_engine_rename_asset(PyObject * self, PyObject * args) { char *path; @@ -755,7 +796,7 @@ PyObject *py_unreal_engine_rename_asset(PyObject * self, PyObject * args) #endif Py_RETURN_NONE; - } +} PyObject *py_unreal_engine_duplicate_asset(PyObject * self, PyObject * args) { @@ -1432,6 +1473,37 @@ PyObject *py_unreal_engine_get_blueprint_components(PyObject * self, PyObject * } +PyObject *py_unreal_engine_remove_component_from_blueprint(PyObject *self, PyObject *args) +{ + PyObject *py_blueprint; + char *name; + char *parentName = nullptr; + + if (!PyArg_ParseTuple(args, "Os|s:remove_component_from_blueprint", &py_blueprint, &name, &parentName)) + { + return NULL; + } + + if (!ue_is_pyuobject(py_blueprint)) + { + return PyErr_Format(PyExc_Exception, "argument is not a UObject"); + } + + ue_PyUObject *py_obj = (ue_PyUObject *)py_blueprint; + if (!py_obj->ue_object->IsA()) + return PyErr_Format(PyExc_Exception, "uobject is not a UBlueprint"); + UBlueprint *bp = (UBlueprint *)py_obj->ue_object; + + bp->Modify(); + USCS_Node *ComponentNode = bp->SimpleConstructionScript->FindSCSNode(UTF8_TO_TCHAR(name)); + if (ComponentNode) + { + bp->SimpleConstructionScript->RemoveNode(ComponentNode); + } + + Py_RETURN_NONE; +} + PyObject *py_unreal_engine_add_component_to_blueprint(PyObject * self, PyObject * args) { @@ -1838,7 +1910,11 @@ PyObject *py_unreal_engine_editor_on_asset_post_import(PyObject * self, PyObject return PyErr_Format(PyExc_Exception, "object is not a callable"); TSharedRef py_delegate = FUnrealEnginePythonHouseKeeper::Get()->NewPythonSmartDelegate(py_callable); +#if ENGINE_MINOR_VERSION > 21 + GEditor->GetEditorSubsystem()->OnAssetPostImport.AddSP(py_delegate, &FPythonSmartDelegate::PyFOnAssetPostImport); +#else FEditorDelegates::OnAssetPostImport.AddSP(py_delegate, &FPythonSmartDelegate::PyFOnAssetPostImport); +#endif Py_RETURN_NONE; } @@ -2074,7 +2150,7 @@ PyObject *py_unreal_engine_add_level_to_world(PyObject *self, PyObject * args) #endif Py_RETURN_UOBJECT(level_streaming); - } +} PyObject *py_unreal_engine_move_selected_actors_to_level(PyObject *self, PyObject * args) { @@ -2492,7 +2568,11 @@ PyObject *py_unreal_engine_unregister_settings(PyObject * self, PyObject * args) PyObject *py_unreal_engine_all_viewport_clients(PyObject * self, PyObject * args) { +#if ENGINE_MINOR_VERSION > 21 + TArray clients = GEditor->GetAllViewportClients(); +#else TArray clients = GEditor->AllViewportClients; +#endif PyObject *py_list = PyList_New(0); for (FEditorViewportClient *client : clients) { @@ -2606,5 +2686,6 @@ PyObject *py_unreal_engine_export_assets(PyObject * self, PyObject * args) Py_RETURN_NONE; } + #endif diff --git a/Source/UnrealEnginePython/Private/UEPyEditor.h b/Source/UnrealEnginePython/Private/UEPyEditor.h index f179c1788..31c8028e1 100644 --- a/Source/UnrealEnginePython/Private/UEPyEditor.h +++ b/Source/UnrealEnginePython/Private/UEPyEditor.h @@ -42,6 +42,7 @@ PyObject *py_unreal_engine_duplicate_asset(PyObject *, PyObject *); PyObject *py_unreal_engine_delete_asset(PyObject *, PyObject *); PyObject *py_unreal_engine_get_long_package_path(PyObject *, PyObject *); +PyObject *py_unreal_engine_get_long_package_asset_name(PyObject *, PyObject *); PyObject *py_unreal_engine_create_blueprint(PyObject *, PyObject *); PyObject *py_unreal_engine_compile_blueprint(PyObject *, PyObject *); @@ -51,6 +52,7 @@ PyObject *py_unreal_engine_reload_blueprint(PyObject *, PyObject *); PyObject *py_unreal_engine_replace_blueprint(PyObject *, PyObject *); PyObject *py_unreal_engine_create_blueprint_from_actor(PyObject *, PyObject *); PyObject *py_unreal_engine_add_component_to_blueprint(PyObject *, PyObject *); +PyObject *py_unreal_engine_remove_component_from_blueprint(PyObject *, PyObject *); PyObject *py_unreal_engine_blueprint_add_member_variable(PyObject *, PyObject *); PyObject *py_unreal_engine_blueprint_add_new_timeline(PyObject *, PyObject *); @@ -95,6 +97,7 @@ PyObject *py_unreal_engine_create_material_instance(PyObject *, PyObject *); PyObject *py_unreal_engine_allow_actor_script_execution_in_editor(PyObject *, PyObject *); PyObject *py_unreal_engine_get_asset_referencers(PyObject *, PyObject *); +PyObject *py_unreal_engine_get_asset_identifier_referencers(PyObject *, PyObject *); PyObject *py_unreal_engine_get_asset_dependencies(PyObject *, PyObject *); PyObject *py_unreal_engine_open_editor_for_asset(PyObject *, PyObject *); diff --git a/Source/UnrealEnginePython/Private/UEPyModule.cpp b/Source/UnrealEnginePython/Private/UEPyModule.cpp index 97cdc5efa..56954d32e 100644 --- a/Source/UnrealEnginePython/Private/UEPyModule.cpp +++ b/Source/UnrealEnginePython/Private/UEPyModule.cpp @@ -51,6 +51,7 @@ #include "Wrappers/UEPyESlateEnums.h" #include "Wrappers/UEPyFVector.h" +#include "Wrappers/UEPyFVector2D.h" #include "Wrappers/UEPyFHitResult.h" #include "Wrappers/UEPyFRotator.h" #include "Wrappers/UEPyFTransform.h" @@ -136,7 +137,7 @@ static PyModuleDef unreal_engine_module = { -1, NULL, }; -static PyObject *init_unreal_engine(void); +static PyObject* init_unreal_engine(void); @@ -147,21 +148,21 @@ void init_unreal_engine_builtin() #endif -static PyObject *py_unreal_engine_py_gc(PyObject * self, PyObject * args) +static PyObject* py_unreal_engine_py_gc(PyObject* self, PyObject* args) { int32 Garbaged = FUnrealEnginePythonHouseKeeper::Get()->RunGC(); return PyLong_FromLong(Garbaged); } -static PyObject *py_unreal_engine_exec(PyObject * self, PyObject * args) +static PyObject* py_unreal_engine_exec(PyObject* self, PyObject* args) { - char *filename = nullptr; + char* filename = nullptr; if (!PyArg_ParseTuple(args, "s:exec", &filename)) { return NULL; } - FUnrealEnginePythonModule &PythonModule = FModuleManager::GetModuleChecked("UnrealEnginePython"); + FUnrealEnginePythonModule& PythonModule = FModuleManager::GetModuleChecked("UnrealEnginePython"); Py_BEGIN_ALLOW_THREADS; PythonModule.RunFile(filename); Py_END_ALLOW_THREADS; @@ -170,14 +171,14 @@ static PyObject *py_unreal_engine_exec(PyObject * self, PyObject * args) #if PLATFORM_MAC -static PyObject *py_unreal_engine_exec_in_main_thread(PyObject * self, PyObject * args) +static PyObject* py_unreal_engine_exec_in_main_thread(PyObject* self, PyObject* args) { - char *filename = nullptr; + char* filename = nullptr; if (!PyArg_ParseTuple(args, "s:exec_in_main_thread", &filename)) { return NULL; } - FUnrealEnginePythonModule &PythonModule = FModuleManager::GetModuleChecked("UnrealEnginePython"); + FUnrealEnginePythonModule& PythonModule = FModuleManager::GetModuleChecked("UnrealEnginePython"); Py_BEGIN_ALLOW_THREADS; PythonModule.RunFileInMainThread(filename); Py_END_ALLOW_THREADS; @@ -185,7 +186,7 @@ static PyObject *py_unreal_engine_exec_in_main_thread(PyObject * self, PyObject } #endif -static PyObject *py_ue_get_py_proxy(ue_PyUObject *self, PyObject * args) +static PyObject* py_ue_get_py_proxy(ue_PyUObject* self, PyObject* args) { ue_py_check(self); @@ -193,13 +194,13 @@ static PyObject *py_ue_get_py_proxy(ue_PyUObject *self, PyObject * args) if (self->py_proxy) { Py_INCREF(self->py_proxy); - return (PyObject *)self->py_proxy; + return (PyObject*)self->py_proxy; } Py_RETURN_NONE; } -static PyObject *py_unreal_engine_shutdown(PyObject *self, PyObject * args) +static PyObject* py_unreal_engine_shutdown(PyObject* self, PyObject* args) { GIsRequestingExit = true; @@ -207,10 +208,10 @@ static PyObject *py_unreal_engine_shutdown(PyObject *self, PyObject * args) Py_RETURN_NONE; } -static PyObject *py_unreal_engine_set_brutal_finalize(PyObject *self, PyObject * args) +static PyObject* py_unreal_engine_set_brutal_finalize(PyObject* self, PyObject* args) { - PyObject *py_bool = nullptr; + PyObject* py_bool = nullptr; if (!PyArg_ParseTuple(args, "|O:set_brutal_finalize", &py_bool)) { return nullptr; @@ -218,7 +219,7 @@ static PyObject *py_unreal_engine_set_brutal_finalize(PyObject *self, PyObject * bool bBrutalFinalize = !py_bool || PyObject_IsTrue(py_bool); - FUnrealEnginePythonModule &PythonModule = FModuleManager::GetModuleChecked("UnrealEnginePython"); + FUnrealEnginePythonModule& PythonModule = FModuleManager::GetModuleChecked("UnrealEnginePython"); PythonModule.BrutalFinalize = bBrutalFinalize; Py_RETURN_NONE; } @@ -341,6 +342,7 @@ static PyMethodDef unreal_engine_methods[] = { { "sync_browser_to_assets", py_unreal_engine_editor_sync_browser_to_assets, METH_VARARGS, "" }, { "get_asset_referencers", py_unreal_engine_get_asset_referencers, METH_VARARGS, "" }, + { "get_asset_identifier_referencers", py_unreal_engine_get_asset_identifier_referencers, METH_VARARGS, "" }, { "get_asset_dependencies", py_unreal_engine_get_asset_dependencies, METH_VARARGS, "" }, { "rename_asset", py_unreal_engine_rename_asset, METH_VARARGS, "" }, @@ -348,6 +350,7 @@ static PyMethodDef unreal_engine_methods[] = { { "delete_asset", py_unreal_engine_delete_asset, METH_VARARGS, "" }, { "get_long_package_path", py_unreal_engine_get_long_package_path, METH_VARARGS, "" }, + { "get_long_package_asset_name", py_unreal_engine_get_long_package_asset_name, METH_VARARGS, "" }, { "editor_command_build", py_unreal_engine_editor_command_build, METH_VARARGS, "" }, { "editor_command_build_lighting", py_unreal_engine_editor_command_build_lighting, METH_VARARGS, "" }, @@ -386,6 +389,7 @@ static PyMethodDef unreal_engine_methods[] = { { "blueprint_get_all_graphs", py_unreal_engine_blueprint_get_all_graphs, METH_VARARGS, "" }, { "blueprint_mark_as_structurally_modified", py_unreal_engine_blueprint_mark_as_structurally_modified, METH_VARARGS, "" }, { "add_component_to_blueprint", py_unreal_engine_add_component_to_blueprint, METH_VARARGS, "" }, + { "remove_component_from_blueprint", py_unreal_engine_remove_component_from_blueprint, METH_VARARGS, "" }, { "get_blueprint_components", py_unreal_engine_get_blueprint_components, METH_VARARGS, "" }, { "create_material_instance", py_unreal_engine_create_material_instance, METH_VARARGS, "" }, { "message_dialog_open", py_unreal_engine_message_dialog_open, METH_VARARGS, "" }, @@ -604,6 +608,7 @@ static PyMethodDef ue_PyUObject_methods[] = { { "set_name", (PyCFunction)py_ue_set_name, METH_VARARGS, "" }, { "bind_event", (PyCFunction)py_ue_bind_event, METH_VARARGS, "" }, + { "unbind_event", (PyCFunction)py_ue_unbind_event, METH_VARARGS, "" }, { "delegate_bind_ufunction", (PyCFunction)py_ue_delegate_bind_ufunction, METH_VARARGS, "" }, { "get_py_proxy", (PyCFunction)py_ue_get_py_proxy, METH_VARARGS, "" }, @@ -616,6 +621,12 @@ static PyMethodDef ue_PyUObject_methods[] = { #if WITH_EDITOR { "get_thumbnail", (PyCFunction)py_ue_get_thumbnail, METH_VARARGS, "" }, { "render_thumbnail", (PyCFunction)py_ue_render_thumbnail, METH_VARARGS, "" }, + {"get_metadata_tag", (PyCFunction)py_ue_get_metadata_tag, METH_VARARGS, "" }, + {"set_metadata_tag", (PyCFunction)py_ue_set_metadata_tag, METH_VARARGS, "" }, + { "metadata_tags", (PyCFunction)py_ue_metadata_tags, METH_VARARGS, "" }, + { "has_metadata_tag", (PyCFunction)py_ue_has_metadata_tag, METH_VARARGS, "" }, + {"remove_metadata_tag", (PyCFunction)py_ue_remove_metadata_tag, METH_VARARGS, "" }, + #endif #if WITH_EDITOR @@ -654,6 +665,9 @@ static PyMethodDef ue_PyUObject_methods[] = { { "graph_add_node_event", (PyCFunction)py_ue_graph_add_node_event, METH_VARARGS, "" }, { "graph_get_good_place_for_new_node", (PyCFunction)py_ue_graph_get_good_place_for_new_node, METH_VARARGS, "" }, + { "graph_reconstruct_node", (PyCFunction)py_ue_graph_reconstruct_node, METH_VARARGS, "" }, + { "graph_remove_node", (PyCFunction)py_ue_graph_remove_node, METH_VARARGS, "" }, + { "node_pins", (PyCFunction)py_ue_node_pins, METH_VARARGS, "" }, { "node_get_title", (PyCFunction)py_ue_node_get_title, METH_VARARGS, "" }, { "node_find_pin", (PyCFunction)py_ue_node_find_pin, METH_VARARGS, "" }, @@ -730,7 +744,9 @@ static PyMethodDef ue_PyUObject_methods[] = { { "get_raw_animation_data", (PyCFunction)py_ue_anim_sequence_get_raw_animation_data, METH_VARARGS, "" }, { "get_raw_animation_track", (PyCFunction)py_ue_anim_sequence_get_raw_animation_track, METH_VARARGS, "" }, { "add_new_raw_track", (PyCFunction)py_ue_anim_sequence_add_new_raw_track, METH_VARARGS, "" }, +#if ENGINE_MINOR_VERSION <23 { "update_compressed_track_map_from_raw", (PyCFunction)py_ue_anim_sequence_update_compressed_track_map_from_raw, METH_VARARGS, "" }, +#endif { "update_raw_track", (PyCFunction)py_ue_anim_sequence_update_raw_track, METH_VARARGS, "" }, { "apply_raw_anim_changes", (PyCFunction)py_ue_anim_sequence_apply_raw_anim_changes, METH_VARARGS, "" }, { "add_key_to_sequence", (PyCFunction)py_ue_anim_add_key_to_sequence, METH_VARARGS, "" }, @@ -743,6 +759,7 @@ static PyMethodDef ue_PyUObject_methods[] = { { "vlog_cylinder", (PyCFunction)py_ue_vlog_cylinder, METH_VARARGS, "" }, // StaticMesh + { "get_static_mesh_bounds", (PyCFunction)py_ue_static_mesh_get_bounds, METH_VARARGS, "" }, #if WITH_EDITOR { "static_mesh_build", (PyCFunction)py_ue_static_mesh_build, METH_VARARGS, "" }, { "static_mesh_create_body_setup", (PyCFunction)py_ue_static_mesh_create_body_setup, METH_VARARGS, "" }, @@ -871,6 +888,7 @@ static PyMethodDef ue_PyUObject_methods[] = { { "set_current_level", (PyCFunction)py_ue_set_current_level, METH_VARARGS, "" }, #if WITH_EDITOR + { "get_level_script_blueprint", (PyCFunction)py_ue_get_level_script_blueprint, METH_VARARGS, "" }, { "add_foliage_asset", (PyCFunction)py_ue_add_foliage_asset, METH_VARARGS, "" }, { "get_foliage_instances", (PyCFunction)py_ue_get_foliage_instances, METH_VARARGS, "" }, #endif @@ -1053,6 +1071,7 @@ static PyMethodDef ue_PyUObject_methods[] = { { "texture_set_data", (PyCFunction)py_ue_texture_set_data, METH_VARARGS, "" }, { "texture_get_width", (PyCFunction)py_ue_texture_get_width, METH_VARARGS, "" }, { "texture_get_height", (PyCFunction)py_ue_texture_get_height, METH_VARARGS, "" }, + { "texture_has_alpha_channel", (PyCFunction)py_ue_texture_has_alpha_channel, METH_VARARGS, "" }, { "render_target_get_data", (PyCFunction)py_ue_render_target_get_data, METH_VARARGS, "" }, { "render_target_get_data_to_buffer", (PyCFunction)py_ue_render_target_get_data_to_buffer, METH_VARARGS, "" }, { "texture_update_resource", (PyCFunction)py_ue_texture_update_resource, METH_VARARGS, "" }, @@ -1068,7 +1087,11 @@ static PyMethodDef ue_PyUObject_methods[] = { { "sequencer_get_camera_cut_track", (PyCFunction)py_ue_sequencer_get_camera_cut_track, METH_VARARGS, "" }, #if WITH_EDITOR { "sequencer_set_playback_range", (PyCFunction)py_ue_sequencer_set_playback_range, METH_VARARGS, "" }, + { "sequencer_set_view_range", (PyCFunction)py_ue_sequencer_set_view_range, METH_VARARGS, "" }, + { "sequencer_set_working_range", (PyCFunction)py_ue_sequencer_set_working_range, METH_VARARGS, "" }, { "sequencer_set_section_range", (PyCFunction)py_ue_sequencer_set_section_range, METH_VARARGS, "" }, + { "sequencer_get_playback_range", (PyCFunction)py_ue_sequencer_get_playback_range, METH_VARARGS, "" }, + { "sequencer_get_selection_range", (PyCFunction)py_ue_sequencer_get_selection_range, METH_VARARGS, "" }, { "sequencer_folders", (PyCFunction)py_ue_sequencer_folders, METH_VARARGS, "" }, { "sequencer_create_folder", (PyCFunction)py_ue_sequencer_create_folder, METH_VARARGS, "" }, { "sequencer_set_display_name", (PyCFunction)py_ue_sequencer_set_display_name, METH_VARARGS, "" }, @@ -1127,6 +1150,8 @@ static PyMethodDef ue_PyUObject_methods[] = { { "static_mesh_generate_kdop18", (PyCFunction)py_ue_static_mesh_generate_kdop18, METH_VARARGS, "" }, { "static_mesh_generate_kdop26", (PyCFunction)py_ue_static_mesh_generate_kdop26, METH_VARARGS, "" }, + { "static_mesh_import_lod", (PyCFunction)py_ue_static_mesh_import_lod, METH_VARARGS, "" }, + #endif // Viewport @@ -1158,7 +1183,7 @@ static PyMethodDef ue_PyUObject_methods[] = { // destructor -static void ue_pyobject_dealloc(ue_PyUObject *self) +static void ue_pyobject_dealloc(ue_PyUObject* self) { #if defined(UEPY_MEMORY_DEBUG) UE_LOG(LogPython, Warning, TEXT("Destroying ue_PyUObject %p mapped to UObject %p"), self, self->ue_object); @@ -1175,38 +1200,38 @@ static void ue_pyobject_dealloc(ue_PyUObject *self) Py_XDECREF(self->py_dict); - Py_TYPE(self)->tp_free((PyObject *)self); + Py_TYPE(self)->tp_free((PyObject*)self); } -static PyObject *ue_PyUObject_getattro(ue_PyUObject *self, PyObject *attr_name) +static PyObject* ue_PyUObject_getattro(ue_PyUObject* self, PyObject* attr_name) { ue_py_check(self); - PyObject *ret = PyObject_GenericGetAttr((PyObject *)self, attr_name); + PyObject* ret = PyObject_GenericGetAttr((PyObject*)self, attr_name); if (!ret) { if (PyUnicodeOrString_Check(attr_name)) { - const char *attr = UEPyUnicode_AsUTF8(attr_name); + const char* attr = UEPyUnicode_AsUTF8(attr_name); // first check for property - UStruct *u_struct = nullptr; + UStruct* u_struct = nullptr; if (self->ue_object->IsA()) { - u_struct = (UStruct *)self->ue_object; + u_struct = (UStruct*)self->ue_object; } else { - u_struct = (UStruct *)self->ue_object->GetClass(); + u_struct = (UStruct*)self->ue_object->GetClass(); } - UProperty *u_property = u_struct->FindPropertyByName(FName(UTF8_TO_TCHAR(attr))); + UProperty* u_property = u_struct->FindPropertyByName(FName(UTF8_TO_TCHAR(attr))); if (u_property) { // swallow previous exception PyErr_Clear(); - return ue_py_convert_property(u_property, (uint8 *)self->ue_object, 0); + return ue_py_convert_property(u_property, (uint8*)self->ue_object, 0); } - UFunction *function = self->ue_object->FindFunction(FName(UTF8_TO_TCHAR(attr))); + UFunction* function = self->ue_object->FindFunction(FName(UTF8_TO_TCHAR(attr))); // retry wth K2_ prefix if (!function) { @@ -1219,8 +1244,8 @@ static PyObject *ue_PyUObject_getattro(ue_PyUObject *self, PyObject *attr_name) { if (self->ue_object->IsA()) { - UClass *u_class = (UClass *)self->ue_object; - UObject *cdo = u_class->GetDefaultObject(); + UClass* u_class = (UClass*)self->ue_object; + UObject* cdo = u_class->GetDefaultObject(); if (cdo) { function = cdo->FindFunction(FName(UTF8_TO_TCHAR(attr))); @@ -1240,7 +1265,7 @@ static PyObject *ue_PyUObject_getattro(ue_PyUObject *self, PyObject *attr_name) #if ENGINE_MINOR_VERSION >= 15 if (self->ue_object->IsA()) { - UUserDefinedEnum *u_enum = (UUserDefinedEnum *)self->ue_object; + UUserDefinedEnum* u_enum = (UUserDefinedEnum*)self->ue_object; PyErr_Clear(); FString attr_as_string = FString(UTF8_TO_TCHAR(attr)); for (auto item : u_enum->DisplayNameMap) @@ -1254,16 +1279,23 @@ static PyObject *ue_PyUObject_getattro(ue_PyUObject *self, PyObject *attr_name) #endif } } + return PyErr_Format(PyExc_Exception, "unknown enum name \"%s\"", attr); } #endif if (self->ue_object->IsA()) { - UEnum *u_enum = (UEnum *)self->ue_object; + UEnum* u_enum = (UEnum*)self->ue_object; PyErr_Clear(); #if ENGINE_MINOR_VERSION > 15 - return PyLong_FromLong(u_enum->GetIndexByName(FName(UTF8_TO_TCHAR(attr)))); + int32 value = u_enum->GetIndexByName(FName(UTF8_TO_TCHAR(attr))); + if (value == INDEX_NONE) + return PyErr_Format(PyExc_Exception, "unknown enum name \"%s\"", attr); + return PyLong_FromLong(value); #else - return PyLong_FromLong(u_enum->FindEnumIndex(FName(UTF8_TO_TCHAR(attr)))); + int32 value = u_enum->FindEnumIndex(FName(UTF8_TO_TCHAR(attr))); + if (value == INDEX_NONE) + return PyErr_Format(PyExc_Exception, "unknown enum name \"%s\"", attr); + return PyLong_FromLong(value); #endif } } @@ -1279,25 +1311,25 @@ static PyObject *ue_PyUObject_getattro(ue_PyUObject *self, PyObject *attr_name) return ret; } -static int ue_PyUObject_setattro(ue_PyUObject *self, PyObject *attr_name, PyObject *value) +static int ue_PyUObject_setattro(ue_PyUObject* self, PyObject* attr_name, PyObject* value) { ue_py_check_int(self); // first of all check for UProperty if (PyUnicodeOrString_Check(attr_name)) { - const char *attr = UEPyUnicode_AsUTF8(attr_name); + const char* attr = UEPyUnicode_AsUTF8(attr_name); // first check for property - UStruct *u_struct = nullptr; + UStruct* u_struct = nullptr; if (self->ue_object->IsA()) { - u_struct = (UStruct *)self->ue_object; + u_struct = (UStruct*)self->ue_object; } else { - u_struct = (UStruct *)self->ue_object->GetClass(); + u_struct = (UStruct*)self->ue_object->GetClass(); } - UProperty *u_property = u_struct->FindPropertyByName(FName(UTF8_TO_TCHAR(attr))); + UProperty* u_property = u_struct->FindPropertyByName(FName(UTF8_TO_TCHAR(attr))); if (u_property) { #if WITH_EDITOR @@ -1311,9 +1343,9 @@ static int ue_PyUObject_setattro(ue_PyUObject *self, PyObject *attr_name, PyObje if (self->ue_object->HasAnyFlags(RF_ArchetypeObject | RF_ClassDefaultObject)) { - TArray Instances; + TArray Instances; self->ue_object->GetArchetypeInstances(Instances); - for (UObject *Instance : Instances) + for (UObject* Instance : Instances) { Instance->PreEditChange(u_property); if (ue_py_convert_pyobject(value, u_property, (uint8*)Instance, 0)) @@ -1342,10 +1374,10 @@ static int ue_PyUObject_setattro(ue_PyUObject *self, PyObject *attr_name, PyObje return -1; } } - return PyObject_GenericSetAttr((PyObject *)self, attr_name, value); + return PyObject_GenericSetAttr((PyObject*)self, attr_name, value); } -static PyObject *ue_PyUObject_str(ue_PyUObject *self) +static PyObject* ue_PyUObject_str(ue_PyUObject* self) { ue_py_check(self); @@ -1358,13 +1390,13 @@ static PyObject *ue_PyUObject_str(ue_PyUObject *self) #endif } -static PyObject *ue_PyUObject_call(ue_PyUObject *self, PyObject *args, PyObject *kw) +static PyObject* ue_PyUObject_call(ue_PyUObject* self, PyObject* args, PyObject* kw) { ue_py_check(self); // if it is a class, create a new object if (self->ue_object->IsA()) { - UClass *u_class = (UClass *)self->ue_object; + UClass* u_class = (UClass*)self->ue_object; if (u_class->HasAnyClassFlags(CLASS_Abstract)) { return PyErr_Format(PyExc_Exception, "abstract classes cannot be instantiated"); @@ -1373,16 +1405,16 @@ static PyObject *ue_PyUObject_call(ue_PyUObject *self, PyObject *args, PyObject { return PyErr_Format(PyExc_Exception, "you cannot use __call__ on actors, they have to be spawned"); } - PyObject *py_name = nullptr; - PyObject *py_outer = Py_None; + PyObject* py_name = nullptr; + PyObject* py_outer = Py_None; if (!PyArg_ParseTuple(args, "|OO:new_object", &py_name, &py_outer)) { return NULL; } int num_args = py_name ? 3 : 1; - PyObject *py_args = PyTuple_New(num_args); - Py_INCREF((PyObject *)self); - PyTuple_SetItem(py_args, 0, (PyObject *)self); + PyObject* py_args = PyTuple_New(num_args); + Py_INCREF((PyObject*)self); + PyTuple_SetItem(py_args, 0, (PyObject*)self); if (py_name) { Py_INCREF(py_outer); @@ -1390,7 +1422,7 @@ static PyObject *ue_PyUObject_call(ue_PyUObject *self, PyObject *args, PyObject Py_INCREF(py_name); PyTuple_SetItem(py_args, 2, py_name); } - ue_PyUObject *ret = (ue_PyUObject *)py_unreal_engine_new_object(nullptr, py_args); + ue_PyUObject* ret = (ue_PyUObject*)py_unreal_engine_new_object(nullptr, py_args); Py_DECREF(py_args); if (!ret) { @@ -1400,23 +1432,23 @@ static PyObject *ue_PyUObject_call(ue_PyUObject *self, PyObject *args, PyObject // UObject crated explicitely from python, will be managed by python... FUnrealEnginePythonHouseKeeper::Get()->TrackUObject(ret->ue_object); - return (PyObject *)ret; + return (PyObject*)ret; } // if it is a uscriptstruct, instantiate a new struct if (self->ue_object->IsA()) { - UScriptStruct *u_script_struct = (UScriptStruct *)self->ue_object; - uint8 *data = (uint8*)FMemory::Malloc(u_script_struct->GetStructureSize()); + UScriptStruct* u_script_struct = (UScriptStruct*)self->ue_object; + uint8* data = (uint8*)FMemory::Malloc(u_script_struct->GetStructureSize()); u_script_struct->InitializeStruct(data); #if WITH_EDITOR u_script_struct->InitializeDefaultValue(data); #endif if (kw) { - PyObject *struct_keys = PyObject_GetIter(kw); + PyObject* struct_keys = PyObject_GetIter(kw); for (;;) { - PyObject *key = PyIter_Next(struct_keys); + PyObject* key = PyIter_Next(struct_keys); if (!key) { if (PyErr_Occurred()) @@ -1428,9 +1460,9 @@ static PyObject *ue_PyUObject_call(ue_PyUObject *self, PyObject *args, PyObject } if (!PyUnicodeOrString_Check(key)) continue; - const char *struct_key = UEPyUnicode_AsUTF8(key); + const char* struct_key = UEPyUnicode_AsUTF8(key); - PyObject *value = PyDict_GetItem(kw, key); + PyObject* value = PyDict_GetItem(kw, key); if (!value) { if (PyErr_Occurred()) @@ -1441,7 +1473,7 @@ static PyObject *ue_PyUObject_call(ue_PyUObject *self, PyObject *args, PyObject break; } - UProperty *u_property = ue_struct_get_field_from_name(u_script_struct, (char *)struct_key); + UProperty* u_property = ue_struct_get_field_from_name(u_script_struct, (char*)struct_key); if (u_property) { if (!ue_py_convert_pyobject(value, u_property, data, 0)) @@ -1499,12 +1531,12 @@ static PyTypeObject ue_PyUObjectType = { -UClass *unreal_engine_new_uclass(char *name, UClass *outer_parent) +UClass* unreal_engine_new_uclass(char* name, UClass* outer_parent) { bool is_overwriting = false; - UObject *outer = GetTransientPackage(); - UClass *parent = UObject::StaticClass(); + UObject* outer = GetTransientPackage(); + UClass* parent = UObject::StaticClass(); if (outer_parent) { @@ -1512,7 +1544,7 @@ UClass *unreal_engine_new_uclass(char *name, UClass *outer_parent) outer = parent->GetOuter(); } - UClass *new_object = FindObject(ANY_PACKAGE, UTF8_TO_TCHAR(name)); + UClass* new_object = FindObject(ANY_PACKAGE, UTF8_TO_TCHAR(name)); if (!new_object) { new_object = NewObject(outer, UTF8_TO_TCHAR(name), RF_Public | RF_Transient | RF_MarkAsNative); @@ -1527,13 +1559,13 @@ UClass *unreal_engine_new_uclass(char *name, UClass *outer_parent) if (is_overwriting && new_object->Children) { - UField *u_field = new_object->Children; + UField* u_field = new_object->Children; while (u_field) { if (u_field->IsA()) { UE_LOG(LogPython, Warning, TEXT("removing function %s"), *u_field->GetName()); - new_object->RemoveFunctionFromFunctionMap((UFunction *)u_field); + new_object->RemoveFunctionFromFunctionMap((UFunction*)u_field); FLinkerLoad::InvalidateExport(u_field); } u_field = u_field->Next; @@ -1603,14 +1635,14 @@ UClass *unreal_engine_new_uclass(char *name, UClass *outer_parent) -int unreal_engine_py_init(ue_PyUObject *, PyObject *, PyObject *); +int unreal_engine_py_init(ue_PyUObject*, PyObject*, PyObject*); void unreal_engine_init_py_module() { #if PY_MAJOR_VERSION >= 3 - PyObject *new_unreal_engine_module = PyImport_AddModule("unreal_engine"); + PyObject * new_unreal_engine_module = PyImport_AddModule("unreal_engine"); #else - PyObject *new_unreal_engine_module = Py_InitModule3("unreal_engine", NULL, unreal_engine_py_doc); + PyObject* new_unreal_engine_module = Py_InitModule3("unreal_engine", NULL, unreal_engine_py_doc); #endif ue_PyUObjectType.tp_new = PyType_GenericNew; ue_PyUObjectType.tp_init = (initproc)unreal_engine_py_init; @@ -1620,19 +1652,20 @@ void unreal_engine_init_py_module() return; Py_INCREF(&ue_PyUObjectType); - PyModule_AddObject(new_unreal_engine_module, "UObject", (PyObject *)&ue_PyUObjectType); + PyModule_AddObject(new_unreal_engine_module, "UObject", (PyObject*)& ue_PyUObjectType); - PyObject *unreal_engine_dict = PyModule_GetDict(new_unreal_engine_module); + PyObject* unreal_engine_dict = PyModule_GetDict(new_unreal_engine_module); - PyMethodDef *unreal_engine_function; + PyMethodDef* unreal_engine_function; for (unreal_engine_function = unreal_engine_methods; unreal_engine_function->ml_name != NULL; unreal_engine_function++) { - PyObject *func = PyCFunction_New(unreal_engine_function, NULL); + PyObject* func = PyCFunction_New(unreal_engine_function, NULL); PyDict_SetItemString(unreal_engine_dict, unreal_engine_function->ml_name, func); Py_DECREF(func); } ue_python_init_fvector(new_unreal_engine_module); + ue_python_init_fvector2d(new_unreal_engine_module); ue_python_init_frotator(new_unreal_engine_module); ue_python_init_ftransform(new_unreal_engine_module); ue_python_init_fhitresult(new_unreal_engine_module); @@ -1678,9 +1711,9 @@ void unreal_engine_init_py_module() ue_python_init_enumsimporter(new_unreal_engine_module); ue_python_init_ustructsimporter(new_unreal_engine_module); +#if WITH_EDITOR ue_python_init_ffoliage_instance(new_unreal_engine_module); -#if WITH_EDITOR ue_python_init_fslowtask(new_unreal_engine_module); ue_python_init_swidget(new_unreal_engine_module); ue_python_init_farfilter(new_unreal_engine_module); @@ -1714,9 +1747,9 @@ void unreal_engine_init_py_module() ue_python_init_ivoice_capture(new_unreal_engine_module); - ue_py_register_magic_module((char *)"unreal_engine.classes", py_ue_new_uclassesimporter); - ue_py_register_magic_module((char *)"unreal_engine.enums", py_ue_new_enumsimporter); - ue_py_register_magic_module((char *)"unreal_engine.structs", py_ue_new_ustructsimporter); + ue_py_register_magic_module((char*)"unreal_engine.classes", py_ue_new_uclassesimporter); + ue_py_register_magic_module((char*)"unreal_engine.enums", py_ue_new_enumsimporter); + ue_py_register_magic_module((char*)"unreal_engine.structs", py_ue_new_ustructsimporter); PyDict_SetItemString(unreal_engine_dict, "ENGINE_MAJOR_VERSION", PyLong_FromLong(ENGINE_MAJOR_VERSION)); @@ -1820,18 +1853,18 @@ void unreal_engine_init_py_module() // utility functions -ue_PyUObject *ue_get_python_uobject(UObject *ue_obj) +ue_PyUObject* ue_get_python_uobject(UObject* ue_obj) { if (!ue_obj) return nullptr; - ue_PyUObject *ret = FUnrealEnginePythonHouseKeeper::Get()->GetPyUObject(ue_obj); + ue_PyUObject* ret = FUnrealEnginePythonHouseKeeper::Get()->GetPyUObject(ue_obj); if (!ret) { if (!ue_obj->IsValidLowLevel() || ue_obj->IsPendingKillOrUnreachable()) return nullptr; - ue_PyUObject *ue_py_object = (ue_PyUObject *)PyObject_New(ue_PyUObject, &ue_PyUObjectType); + ue_PyUObject* ue_py_object = (ue_PyUObject*)PyObject_New(ue_PyUObject, &ue_PyUObjectType); if (!ue_py_object) { return nullptr; @@ -1848,14 +1881,14 @@ ue_PyUObject *ue_get_python_uobject(UObject *ue_obj) UE_LOG(LogPython, Warning, TEXT("CREATED UPyObject at %p for %p %s"), ue_py_object, ue_obj, *ue_obj->GetName()); #endif return ue_py_object; - } + } return ret; - } +} -ue_PyUObject *ue_get_python_uobject_inc(UObject *ue_obj) +ue_PyUObject* ue_get_python_uobject_inc(UObject* ue_obj) { - ue_PyUObject *ret = ue_get_python_uobject(ue_obj); + ue_PyUObject* ret = ue_get_python_uobject(ue_obj); if (ret) { Py_INCREF(ret); @@ -1865,9 +1898,9 @@ ue_PyUObject *ue_get_python_uobject_inc(UObject *ue_obj) void unreal_engine_py_log_error() { - PyObject *type = NULL; - PyObject *value = NULL; - PyObject *traceback = NULL; + PyObject* type = NULL; + PyObject* value = NULL; + PyObject* traceback = NULL; PyErr_Fetch(&type, &value, &traceback); PyErr_NormalizeException(&type, &value, &traceback); @@ -1878,9 +1911,9 @@ void unreal_engine_py_log_error() return; } - char *msg = NULL; + char* msg = NULL; #if PY_MAJOR_VERSION >= 3 - PyObject *zero = PyUnicode_AsUTF8String(PyObject_Str(value)); + PyObject * zero = PyUnicode_AsUTF8String(PyObject_Str(value)); if (zero) { msg = PyBytes_AsString(zero); @@ -1903,19 +1936,19 @@ void unreal_engine_py_log_error() return; } - PyObject *traceback_module = PyImport_ImportModule("traceback"); + PyObject* traceback_module = PyImport_ImportModule("traceback"); if (!traceback_module) { PyErr_Clear(); return; } - PyObject *traceback_dict = PyModule_GetDict(traceback_module); - PyObject *format_exception = PyDict_GetItemString(traceback_dict, "format_exception"); + PyObject* traceback_dict = PyModule_GetDict(traceback_module); + PyObject* format_exception = PyDict_GetItemString(traceback_dict, "format_exception"); if (format_exception) { - PyObject *ret = PyObject_CallFunctionObjArgs(format_exception, type, value, traceback, NULL); + PyObject* ret = PyObject_CallFunctionObjArgs(format_exception, type, value, traceback, NULL); if (!ret) { PyErr_Clear(); @@ -1925,7 +1958,7 @@ void unreal_engine_py_log_error() { for (int i = 0; i < PyList_Size(ret); i++) { - PyObject *item = PyList_GetItem(ret, i); + PyObject* item = PyList_GetItem(ret, i); if (item) { UE_LOG(LogPython, Error, TEXT("%s"), UTF8_TO_TCHAR(UEPyUnicode_AsUTF8(PyObject_Str(item)))); @@ -1939,26 +1972,26 @@ void unreal_engine_py_log_error() } PyErr_Clear(); - } +} // retrieve a UWorld from a generic UObject (if possible) -UWorld *ue_get_uworld(ue_PyUObject *py_obj) +UWorld* ue_get_uworld(ue_PyUObject* py_obj) { if (py_obj->ue_object->IsA()) { - return (UWorld *)py_obj->ue_object; + return (UWorld*)py_obj->ue_object; } if (py_obj->ue_object->IsA()) { - AActor *actor = (AActor *)py_obj->ue_object; + AActor* actor = (AActor*)py_obj->ue_object; return actor->GetWorld(); } if (py_obj->ue_object->IsA()) { - UActorComponent *component = (UActorComponent *)py_obj->ue_object; + UActorComponent* component = (UActorComponent*)py_obj->ue_object; return component->GetWorld(); } @@ -1966,23 +1999,23 @@ UWorld *ue_get_uworld(ue_PyUObject *py_obj) } // retrieve actor from component (if possible) -AActor *ue_get_actor(ue_PyUObject *py_obj) +AActor* ue_get_actor(ue_PyUObject* py_obj) { if (py_obj->ue_object->IsA()) { - return (AActor *)py_obj->ue_object; + return (AActor*)py_obj->ue_object; } if (py_obj->ue_object->IsA()) { - UActorComponent *tmp_component = (UActorComponent *)py_obj->ue_object; + UActorComponent* tmp_component = (UActorComponent*)py_obj->ue_object; return tmp_component->GetOwner(); } return nullptr; } // convert a property to a python object -PyObject *ue_py_convert_property(UProperty *prop, uint8 *buffer, int32 index) +PyObject* ue_py_convert_property(UProperty* prop, uint8* buffer, int32 index) { if (auto casted_prop = Cast(prop)) { @@ -2033,7 +2066,7 @@ PyObject *ue_py_convert_property(UProperty *prop, uint8 *buffer, int32 index) #if ENGINE_MINOR_VERSION >= 15 if (auto casted_prop = Cast(prop)) { - void *prop_addr = casted_prop->ContainerPtrToValuePtr(buffer, index); + void* prop_addr = casted_prop->ContainerPtrToValuePtr(buffer, index); uint64 enum_index = casted_prop->GetUnderlyingProperty()->GetUnsignedIntPropertyValue(prop_addr); return PyLong_FromUnsignedLong(enum_index); } @@ -2082,12 +2115,16 @@ PyObject *ue_py_convert_property(UProperty *prop, uint8 *buffer, int32 index) { if (auto casted_struct = Cast(casted_prop->Struct)) { - // check for FVector if (casted_struct == TBaseStructure::Get()) { FVector vec = *casted_prop->ContainerPtrToValuePtr(buffer, index); return py_ue_new_fvector(vec); } + if (casted_struct == TBaseStructure::Get()) + { + FVector2D vec = *casted_prop->ContainerPtrToValuePtr(buffer, index); + return py_ue_new_fvector2d(vec); + } if (casted_struct == TBaseStructure::Get()) { FRotator rot = *casted_prop->ContainerPtrToValuePtr(buffer, index); @@ -2121,7 +2158,7 @@ PyObject *ue_py_convert_property(UProperty *prop, uint8 *buffer, int32 index) if (auto casted_prop = Cast(prop)) { auto value = casted_prop->GetPropertyValue_InContainer(buffer, index); - UObject *strong_obj = value.Get(); + UObject* strong_obj = value.Get(); if (strong_obj) { Py_RETURN_UOBJECT(strong_obj); @@ -2144,20 +2181,20 @@ PyObject *ue_py_convert_property(UProperty *prop, uint8 *buffer, int32 index) { FScriptArrayHelper_InContainer array_helper(casted_prop, buffer, index); - UProperty *array_prop = casted_prop->Inner; + UProperty* array_prop = casted_prop->Inner; // check for TArray, so we can use bytearray optimization if (auto uint8_tarray = Cast(array_prop)) { - uint8 *buf = array_helper.GetRawPtr(); - return PyByteArray_FromStringAndSize((char *)buf, array_helper.Num()); + uint8* buf = array_helper.GetRawPtr(); + return PyByteArray_FromStringAndSize((char*)buf, array_helper.Num()); } - PyObject *py_list = PyList_New(0); + PyObject* py_list = PyList_New(0); for (int i = 0; i < array_helper.Num(); i++) { - PyObject *item = ue_py_convert_property(array_prop, array_helper.GetRawPtr(i), 0); + PyObject* item = ue_py_convert_property(array_prop, array_helper.GetRawPtr(i), 0); if (!item) { Py_DECREF(py_list); @@ -2175,23 +2212,23 @@ PyObject *ue_py_convert_property(UProperty *prop, uint8 *buffer, int32 index) { FScriptMapHelper_InContainer map_helper(casted_prop, buffer, index); - PyObject *py_dict = PyDict_New(); + PyObject* py_dict = PyDict_New(); for (int32 i = 0; i < map_helper.Num(); i++) { if (map_helper.IsValidIndex(i)) { - uint8 *ptr = map_helper.GetPairPtr(i); + uint8* ptr = map_helper.GetPairPtr(i); - PyObject *py_key = ue_py_convert_property(map_helper.KeyProp, ptr, 0); + PyObject* py_key = ue_py_convert_property(map_helper.KeyProp, ptr, 0); if (!py_key) { Py_DECREF(py_dict); return NULL; } - PyObject *py_value = ue_py_convert_property(map_helper.ValueProp, ptr, 0); + PyObject* py_value = ue_py_convert_property(map_helper.ValueProp, ptr, 0); if (!py_value) { Py_DECREF(py_dict); @@ -2212,7 +2249,7 @@ PyObject *ue_py_convert_property(UProperty *prop, uint8 *buffer, int32 index) } // convert a python object to a property -bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, int32 index) +bool ue_py_convert_pyobject(PyObject* py_obj, UProperty* prop, uint8* buffer, int32 index) { if (PyBool_Check(py_obj)) @@ -2235,42 +2272,42 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in { if (auto casted_prop = Cast(prop)) { - PyObject *py_long = PyNumber_Long(py_obj); + PyObject* py_long = PyNumber_Long(py_obj); casted_prop->SetPropertyValue_InContainer(buffer, PyLong_AsLong(py_long), index); Py_DECREF(py_long); return true; } if (auto casted_prop = Cast(prop)) { - PyObject *py_long = PyNumber_Long(py_obj); + PyObject* py_long = PyNumber_Long(py_obj); casted_prop->SetPropertyValue_InContainer(buffer, PyLong_AsUnsignedLong(py_long), index); Py_DECREF(py_long); return true; } if (auto casted_prop = Cast(prop)) { - PyObject *py_long = PyNumber_Long(py_obj); + PyObject* py_long = PyNumber_Long(py_obj); casted_prop->SetPropertyValue_InContainer(buffer, PyLong_AsLongLong(py_long), index); Py_DECREF(py_long); return true; } if (auto casted_prop = Cast(prop)) { - PyObject *py_long = PyNumber_Long(py_obj); + PyObject* py_long = PyNumber_Long(py_obj); casted_prop->SetPropertyValue_InContainer(buffer, PyLong_AsUnsignedLongLong(py_long), index); Py_DECREF(py_long); return true; } if (auto casted_prop = Cast(prop)) { - PyObject *py_float = PyNumber_Float(py_obj); + PyObject* py_float = PyNumber_Float(py_obj); casted_prop->SetPropertyValue_InContainer(buffer, PyFloat_AsDouble(py_float), index); Py_DECREF(py_float); return true; } if (auto casted_prop = Cast(prop)) { - PyObject *py_long = PyNumber_Long(py_obj); + PyObject* py_long = PyNumber_Long(py_obj); casted_prop->SetPropertyValue_InContainer(buffer, PyLong_AsUnsignedLong(py_long), index); Py_DECREF(py_long); return true; @@ -2278,8 +2315,8 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in #if ENGINE_MINOR_VERSION >= 15 if (auto casted_prop = Cast(prop)) { - PyObject *py_long = PyNumber_Long(py_obj); - void *prop_addr = casted_prop->ContainerPtrToValuePtr(buffer, index); + PyObject* py_long = PyNumber_Long(py_obj); + void* prop_addr = casted_prop->ContainerPtrToValuePtr(buffer, index); casted_prop->GetUnderlyingProperty()->SetIntPropertyValue(prop_addr, (uint64)PyLong_AsUnsignedLong(py_long)); Py_DECREF(py_long); return true; @@ -2320,7 +2357,7 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in { Py_ssize_t pybytes_len = PyBytes_Size(py_obj); - uint8 *buf = (uint8 *)PyBytes_AsString(py_obj); + uint8* buf = (uint8*)PyBytes_AsString(py_obj); // fix array helper size @@ -2352,7 +2389,7 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in { Py_ssize_t pybytes_len = PyByteArray_Size(py_obj); - uint8 *buf = (uint8 *)PyByteArray_AsString(py_obj); + uint8* buf = (uint8*)PyByteArray_AsString(py_obj); // fix array helper size @@ -2381,7 +2418,7 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in { FScriptArrayHelper_InContainer helper(casted_prop, buffer, index); - UProperty *array_prop = casted_prop->Inner; + UProperty* array_prop = casted_prop->Inner; Py_ssize_t pylist_len = PyList_Size(py_obj); // fix array helper size @@ -2396,7 +2433,7 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in for (int i = 0; i < (int)pylist_len; i++) { - PyObject *py_item = PyList_GetItem(py_obj, i); + PyObject* py_item = PyList_GetItem(py_obj, i); if (!ue_py_convert_pyobject(py_item, array_prop, helper.GetRawPtr(i), 0)) { return false; @@ -2414,7 +2451,7 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in { FScriptArrayHelper_InContainer helper(casted_prop, buffer, index); - UProperty *array_prop = casted_prop->Inner; + UProperty* array_prop = casted_prop->Inner; Py_ssize_t pytuple_len = PyTuple_Size(py_obj); // fix array helper size @@ -2429,7 +2466,7 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in for (int i = 0; i < (int)pytuple_len; i++) { - PyObject *py_item = PyTuple_GetItem(py_obj, i); + PyObject* py_item = PyTuple_GetItem(py_obj, i); if (!ue_py_convert_pyobject(py_item, array_prop, helper.GetRawPtr(i), 0)) { return false; @@ -2448,8 +2485,8 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in { FScriptMapHelper_InContainer map_helper(casted_prop, buffer, index); - PyObject *py_key = nullptr; - PyObject *py_value = nullptr; + PyObject* py_key = nullptr; + PyObject* py_value = nullptr; Py_ssize_t pos = 0; map_helper.EmptyValues(); @@ -2457,7 +2494,7 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in { int32 hindex = map_helper.AddDefaultValue_Invalid_NeedsRehash(); - uint8 *ptr = map_helper.GetPairPtr(hindex); + uint8* ptr = map_helper.GetPairPtr(hindex); if (!ue_py_convert_pyobject(py_key, casted_prop->KeyProp, ptr, 0)) { @@ -2480,7 +2517,7 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in // structs - if (ue_PyFVector *py_vec = py_ue_is_fvector(py_obj)) + if (ue_PyFVector * py_vec = py_ue_is_fvector(py_obj)) { if (auto casted_prop = Cast(prop)) { @@ -2493,7 +2530,20 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in return false; } - if (ue_PyFRotator *py_rot = py_ue_is_frotator(py_obj)) + if (ue_PyFVector2D * py_vec = py_ue_is_fvector2d(py_obj)) + { + if (auto casted_prop = Cast(prop)) + { + if (casted_prop->Struct == TBaseStructure::Get()) + { + *casted_prop->ContainerPtrToValuePtr(buffer, index) = py_vec->vec; + return true; + } + } + return false; + } + + if (ue_PyFRotator * py_rot = py_ue_is_frotator(py_obj)) { if (auto casted_prop = Cast(prop)) { @@ -2506,7 +2556,7 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in return false; } - if (ue_PyFTransform *py_transform = py_ue_is_ftransform(py_obj)) + if (ue_PyFTransform * py_transform = py_ue_is_ftransform(py_obj)) { if (auto casted_prop = Cast(prop)) { @@ -2519,7 +2569,7 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in return false; } - if (ue_PyFColor *py_color = py_ue_is_fcolor(py_obj)) + if (ue_PyFColor * py_color = py_ue_is_fcolor(py_obj)) { if (auto casted_prop = Cast(prop)) { @@ -2533,7 +2583,7 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in return false; } - if (ue_PyFLinearColor *py_color = py_ue_is_flinearcolor(py_obj)) + if (ue_PyFLinearColor * py_color = py_ue_is_flinearcolor(py_obj)) { if (auto casted_prop = Cast(prop)) { @@ -2546,7 +2596,7 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in return false; } - if (ue_PyFHitResult *py_hit = py_ue_is_fhitresult(py_obj)) + if (ue_PyFHitResult * py_hit = py_ue_is_fhitresult(py_obj)) { if (auto casted_prop = Cast(prop)) { @@ -2562,22 +2612,23 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in // generic structs if (py_ue_is_uscriptstruct(py_obj)) { - ue_PyUScriptStruct *py_u_struct = (ue_PyUScriptStruct *)py_obj; + ue_PyUScriptStruct* py_u_struct = (ue_PyUScriptStruct*)py_obj; if (auto casted_prop = Cast(prop)) { if (casted_prop->Struct == py_u_struct->u_struct) { - uint8 *dest = casted_prop->ContainerPtrToValuePtr(buffer, index); - FMemory::Memcpy(dest, py_u_struct->u_struct_ptr, py_u_struct->u_struct->GetStructureSize()); + uint8* dest = casted_prop->ContainerPtrToValuePtr(buffer, index); + py_u_struct->u_struct->InitializeStruct(dest); + py_u_struct->u_struct->CopyScriptStruct(dest, py_u_struct->u_struct_ptr); return true; } } return false; } - if (PyObject_IsInstance(py_obj, (PyObject *)&ue_PyUObjectType)) + if (PyObject_IsInstance(py_obj, (PyObject*)& ue_PyUObjectType)) { - ue_PyUObject *ue_obj = (ue_PyUObject *)py_obj; + ue_PyUObject* ue_obj = (ue_PyUObject*)py_obj; if (ue_obj->ue_object->IsA()) { if (auto casted_prop = Cast(prop)) @@ -2623,9 +2674,18 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in { if (auto casted_prop = Cast(prop)) { - // ensure the object type is correct, otherwise crash could happen (soon or later) - if (!ue_obj->ue_object->IsA(casted_prop->PropertyClass)) - return false; + // if the property specifies an interface, the object must be of a class that implements it + if (casted_prop->PropertyClass->HasAnyClassFlags(CLASS_Interface)) + { + if (!ue_obj->ue_object->GetClass()->ImplementsInterface(casted_prop->PropertyClass)) + return false; + } + else + { + // ensure the object type is correct, otherwise crash could happen (soon or later) + if (!ue_obj->ue_object->IsA(casted_prop->PropertyClass)) + return false; + } casted_prop->SetObjectPropertyValue_InContainer(buffer, ue_obj->ue_object, index); @@ -2681,24 +2741,24 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer, in // check if a python object is a wrapper to a UObject -ue_PyUObject *ue_is_pyuobject(PyObject *obj) +ue_PyUObject* ue_is_pyuobject(PyObject* obj) { - if (!PyObject_IsInstance(obj, (PyObject *)&ue_PyUObjectType)) + if (!PyObject_IsInstance(obj, (PyObject*)& ue_PyUObjectType)) return nullptr; - return (ue_PyUObject *)obj; + return (ue_PyUObject*)obj; } -void ue_bind_events_for_py_class_by_attribute(UObject *u_obj, PyObject *py_class) +void ue_bind_events_for_py_class_by_attribute(UObject* u_obj, PyObject* py_class) { // attempt to register events - PyObject *attrs = PyObject_Dir(py_class); + PyObject* attrs = PyObject_Dir(py_class); if (!attrs) return; - AActor *actor = Cast(u_obj); + AActor* actor = Cast(u_obj); if (!actor) { - UActorComponent *component = Cast(u_obj); + UActorComponent* component = Cast(u_obj); if (!component) return; actor = component->GetOwner(); @@ -2707,14 +2767,14 @@ void ue_bind_events_for_py_class_by_attribute(UObject *u_obj, PyObject *py_class Py_ssize_t len = PyList_Size(attrs); for (Py_ssize_t i = 0; i < len; i++) { - PyObject *py_attr_name = PyList_GetItem(attrs, i); + PyObject* py_attr_name = PyList_GetItem(attrs, i); if (!py_attr_name || !PyUnicodeOrString_Check(py_attr_name)) continue; - PyObject *item = PyObject_GetAttrString(py_class, UEPyUnicode_AsUTF8(py_attr_name)); + PyObject* item = PyObject_GetAttrString(py_class, UEPyUnicode_AsUTF8(py_attr_name)); if (item && PyCallable_Check(item)) { // check for ue_event signature - PyObject *event_signature = PyObject_GetAttrString(item, (char*)"ue_event"); + PyObject* event_signature = PyObject_GetAttrString(item, (char*)"ue_event"); if (event_signature) { if (PyUnicodeOrString_Check(event_signature)) @@ -2739,7 +2799,7 @@ void ue_bind_events_for_py_class_by_attribute(UObject *u_obj, PyObject *py_class else { bool found = false; - for (UActorComponent *component : actor->GetComponents()) + for (UActorComponent* component : actor->GetComponents()) { if (component->GetFName() == FName(*parts[0])) { @@ -2776,23 +2836,23 @@ void ue_bind_events_for_py_class_by_attribute(UObject *u_obj, PyObject *py_class } // automatically bind events based on class methods names -void ue_autobind_events_for_pyclass(ue_PyUObject *u_obj, PyObject *py_class) +void ue_autobind_events_for_pyclass(ue_PyUObject* u_obj, PyObject* py_class) { - PyObject *attrs = PyObject_Dir(py_class); + PyObject* attrs = PyObject_Dir(py_class); if (!attrs) return; Py_ssize_t len = PyList_Size(attrs); for (Py_ssize_t i = 0; i < len; i++) { - PyObject *py_attr_name = PyList_GetItem(attrs, i); + PyObject* py_attr_name = PyList_GetItem(attrs, i); if (!py_attr_name || !PyUnicodeOrString_Check(py_attr_name)) continue; FString attr_name = UTF8_TO_TCHAR(UEPyUnicode_AsUTF8(py_attr_name)); if (!attr_name.StartsWith("on_", ESearchCase::CaseSensitive)) continue; // check if the attr is a callable - PyObject *item = PyObject_GetAttrString(py_class, TCHAR_TO_UTF8(*attr_name)); + PyObject* item = PyObject_GetAttrString(py_class, TCHAR_TO_UTF8(*attr_name)); if (item && PyCallable_Check(item)) { TArray parts; @@ -2816,24 +2876,24 @@ void ue_autobind_events_for_pyclass(ue_PyUObject *u_obj, PyObject *py_class) Py_DECREF(attrs); } -static void py_ue_destroy_params(UFunction *u_function, uint8 *buffer) +static void py_ue_destroy_params(UFunction* u_function, uint8* buffer) { // destroy params TFieldIterator DArgs(u_function); for (; DArgs && (DArgs->PropertyFlags & CPF_Parm); ++DArgs) { - UProperty *prop = *DArgs; + UProperty* prop = *DArgs; prop->DestroyValue_InContainer(buffer); } } -PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject *args, int argn, PyObject *kwargs) +PyObject* py_ue_ufunction_call(UFunction* u_function, UObject* u_obj, PyObject* args, int argn, PyObject* kwargs) { // check for __super call if (kwargs) { - PyObject *is_super_call = PyDict_GetItemString(kwargs, (char *)"__super"); + PyObject* is_super_call = PyDict_GetItemString(kwargs, (char*)"__super"); if (is_super_call) { if (!u_function->GetSuperFunction()) @@ -2845,12 +2905,12 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject * } //NOTE: u_function->PropertiesSize maps to local variable uproperties + ufunction paramaters uproperties - uint8 *buffer = (uint8 *)FMemory_Alloca(u_function->ParmsSize); + uint8* buffer = (uint8*)FMemory_Alloca(u_function->ParmsSize); FMemory::Memzero(buffer, u_function->ParmsSize); // initialize args for (TFieldIterator IArgs(u_function); IArgs && IArgs->HasAnyPropertyFlags(CPF_Parm); ++IArgs) { - UProperty *prop = *IArgs; + UProperty* prop = *IArgs; if (!prop->HasAnyPropertyFlags(CPF_ZeroConstructor)) { prop->InitializeValue_InContainer(buffer); @@ -2875,8 +2935,8 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject * #endif } #endif - } } + } Py_ssize_t tuple_len = PyTuple_Size(args); @@ -2886,10 +2946,10 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject * TFieldIterator PArgs(u_function); for (; PArgs && ((PArgs->PropertyFlags & (CPF_Parm | CPF_ReturnParm)) == CPF_Parm); ++PArgs) { - UProperty *prop = *PArgs; + UProperty* prop = *PArgs; if (argn < tuple_len) { - PyObject *py_arg = PyTuple_GetItem(args, argn); + PyObject* py_arg = PyTuple_GetItem(args, argn); if (!py_arg) { py_ue_destroy_params(u_function, buffer); @@ -2903,8 +2963,8 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject * } else if (kwargs) { - char *prop_name = TCHAR_TO_UTF8(*prop->GetName()); - PyObject *dict_value = PyDict_GetItemString(kwargs, prop_name); + char* prop_name = TCHAR_TO_UTF8(*prop->GetName()); + PyObject* dict_value = PyDict_GetItemString(kwargs, prop_name); if (dict_value) { if (!ue_py_convert_pyobject(dict_value, prop, buffer, 0)) @@ -2928,13 +2988,13 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject * u_obj->ProcessEvent(u_function, buffer); Py_END_ALLOW_THREADS; - PyObject *ret = nullptr; + PyObject* ret = nullptr; int has_ret_param = 0; TFieldIterator Props(u_function); for (; Props; ++Props) { - UProperty *prop = *Props; + UProperty* prop = *Props; if (prop->GetPropertyFlags() & CPF_ReturnParm) { ret = ue_py_convert_property(prop, buffer, 0); @@ -2951,7 +3011,7 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject * if (has_out_params > 0) { - PyObject *multi_ret = PyTuple_New(has_out_params + has_ret_param); + PyObject* multi_ret = PyTuple_New(has_out_params + has_ret_param); if (ret) { PyTuple_SetItem(multi_ret, 0, ret); @@ -2959,13 +3019,13 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject * TFieldIterator OProps(u_function); for (; OProps; ++OProps) { - UProperty *prop = *OProps; + UProperty* prop = *OProps; if (prop->HasAnyPropertyFlags(CPF_OutParm) && (prop->IsA() || prop->HasAnyPropertyFlags(CPF_ConstParm) == false)) { // skip return param as it must be always the first if (prop->GetPropertyFlags() & CPF_ReturnParm) continue; - PyObject *py_out = ue_py_convert_property(prop, buffer, 0); + PyObject* py_out = ue_py_convert_property(prop, buffer, 0); if (!py_out) { Py_DECREF(multi_ret); @@ -2989,12 +3049,60 @@ PyObject *py_ue_ufunction_call(UFunction *u_function, UObject *u_obj, PyObject * return ret; Py_RETURN_NONE; +} + +PyObject* ue_unbind_pyevent(ue_PyUObject* u_obj, FString event_name, PyObject* py_callable, bool fail_on_wrong_property) +{ + UProperty* u_property = u_obj->ue_object->GetClass()->FindPropertyByName(FName(*event_name)); + if (!u_property) + { + if (fail_on_wrong_property) + return PyErr_Format(PyExc_Exception, "unable to find event property %s", TCHAR_TO_UTF8(*event_name)); + Py_RETURN_NONE; } -PyObject *ue_bind_pyevent(ue_PyUObject *u_obj, FString event_name, PyObject *py_callable, bool fail_on_wrong_property) + if (auto casted_prop = Cast(u_property)) + { + UPythonDelegate* py_delegate = FUnrealEnginePythonHouseKeeper::Get()->FindDelegate(u_obj->ue_object, py_callable); + if (py_delegate != nullptr) + { +#if ENGINE_MINOR_VERSION < 23 + FMulticastScriptDelegate multiscript_delegate = casted_prop->GetPropertyValue_InContainer(u_obj->ue_object); +#else + FMulticastScriptDelegate multiscript_delegate = *casted_prop->GetMulticastDelegate(u_obj->ue_object); +#endif + + multiscript_delegate.Remove(py_delegate, FName("PyFakeCallable")); + + // re-assign multicast delegate +#if ENGINE_MINOR_VERSION < 23 + casted_prop->SetPropertyValue_InContainer(u_obj->ue_object, multiscript_delegate); +#else + casted_prop->SetMulticastDelegate(u_obj->ue_object, multiscript_delegate); +#endif + } + } + else if (auto casted_prop_delegate = Cast(u_property)) + { + FScriptDelegate script_delegate = casted_prop_delegate->GetPropertyValue_InContainer(u_obj->ue_object); + script_delegate.Unbind(); + + // re-assign multicast delegate + casted_prop_delegate->SetPropertyValue_InContainer(u_obj->ue_object, script_delegate); + } + else + { + if (fail_on_wrong_property) + return PyErr_Format(PyExc_Exception, "property %s is not an event", TCHAR_TO_UTF8(*event_name)); + } + + Py_RETURN_NONE; +} + +PyObject* ue_bind_pyevent(ue_PyUObject* u_obj, FString event_name, PyObject* py_callable, bool fail_on_wrong_property) { - UProperty *u_property = u_obj->ue_object->GetClass()->FindPropertyByName(FName(*event_name)); + UProperty* u_property = u_obj->ue_object->GetClass()->FindPropertyByName(FName(*event_name)); if (!u_property) { if (fail_on_wrong_property) @@ -3004,10 +3112,14 @@ PyObject *ue_bind_pyevent(ue_PyUObject *u_obj, FString event_name, PyObject *py_ if (auto casted_prop = Cast(u_property)) { +#if ENGINE_MINOR_VERSION < 23 FMulticastScriptDelegate multiscript_delegate = casted_prop->GetPropertyValue_InContainer(u_obj->ue_object); +#else + FMulticastScriptDelegate multiscript_delegate = *casted_prop->GetMulticastDelegate(u_obj->ue_object); +#endif FScriptDelegate script_delegate; - UPythonDelegate *py_delegate = FUnrealEnginePythonHouseKeeper::Get()->NewDelegate(u_obj->ue_object, py_callable, casted_prop->SignatureFunction); + UPythonDelegate* py_delegate = FUnrealEnginePythonHouseKeeper::Get()->NewDelegate(u_obj->ue_object, py_callable, casted_prop->SignatureFunction); // fake UFUNCTION for bypassing checks script_delegate.BindUFunction(py_delegate, FName("PyFakeCallable")); @@ -3015,13 +3127,17 @@ PyObject *ue_bind_pyevent(ue_PyUObject *u_obj, FString event_name, PyObject *py_ multiscript_delegate.Add(script_delegate); // re-assign multicast delegate +#if ENGINE_MINOR_VERSION < 23 casted_prop->SetPropertyValue_InContainer(u_obj->ue_object, multiscript_delegate); +#else + casted_prop->SetMulticastDelegate(u_obj->ue_object, multiscript_delegate); +#endif } else if (auto casted_prop_delegate = Cast(u_property)) { FScriptDelegate script_delegate = casted_prop_delegate->GetPropertyValue_InContainer(u_obj->ue_object); - UPythonDelegate *py_delegate = FUnrealEnginePythonHouseKeeper::Get()->NewDelegate(u_obj->ue_object, py_callable, casted_prop_delegate->SignatureFunction); + UPythonDelegate* py_delegate = FUnrealEnginePythonHouseKeeper::Get()->NewDelegate(u_obj->ue_object, py_callable, casted_prop_delegate->SignatureFunction); // fake UFUNCTION for bypassing checks script_delegate.BindUFunction(py_delegate, FName("PyFakeCallable")); @@ -3037,10 +3153,10 @@ PyObject *ue_bind_pyevent(ue_PyUObject *u_obj, FString event_name, PyObject *py_ Py_RETURN_NONE; } -UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_callable, uint32 function_flags) +UFunction* unreal_engine_add_function(UClass* u_class, char* name, PyObject* py_callable, uint32 function_flags) { - UFunction *parent_function = u_class->GetSuperClass()->FindFunctionByName(UTF8_TO_TCHAR(name)); + UFunction* parent_function = u_class->GetSuperClass()->FindFunctionByName(UTF8_TO_TCHAR(name)); // if the function is not available in the parent // check for name collision if (!parent_function) @@ -3049,10 +3165,10 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ { UE_LOG(LogPython, Error, TEXT("function %s is already registered"), UTF8_TO_TCHAR(name)); return nullptr; + } } -} - UPythonFunction *function = NewObject(u_class, UTF8_TO_TCHAR(name), RF_Public | RF_Transient | RF_MarkAsNative); + UPythonFunction* function = NewObject(u_class, UTF8_TO_TCHAR(name), RF_Public | RF_Transient | RF_MarkAsNative); function->SetPyCallable(py_callable); #if ENGINE_MINOR_VERSION < 18 @@ -3070,34 +3186,34 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ // iterate all arguments using inspect.signature() // this is required to maintaining args order - PyObject *inspect = PyImport_ImportModule("inspect"); + PyObject* inspect = PyImport_ImportModule("inspect"); if (!inspect) { return NULL; } - PyObject *signature = PyObject_CallMethod(inspect, (char *)"signature", (char *)"O", py_callable); + PyObject* signature = PyObject_CallMethod(inspect, (char*)"signature", (char*)"O", py_callable); if (!signature) { return NULL; } - PyObject *parameters = PyObject_GetAttrString(signature, "parameters"); + PyObject* parameters = PyObject_GetAttrString(signature, "parameters"); if (!parameters) { return NULL; } - PyObject *annotations = PyObject_GetAttrString(py_callable, "__annotations__"); + PyObject* annotations = PyObject_GetAttrString(py_callable, "__annotations__"); - UField **next_property = &function->Children; - UProperty **next_property_link = &function->PropertyLink; + UField** next_property = &function->Children; + UProperty** next_property_link = &function->PropertyLink; - PyObject *parameters_keys = PyObject_GetIter(parameters); + PyObject* parameters_keys = PyObject_GetIter(parameters); // do not process args if no annotations are available while (annotations) { - PyObject *key = PyIter_Next(parameters_keys); + PyObject* key = PyIter_Next(parameters_keys); if (!key) { if (PyErr_Occurred()) @@ -3107,60 +3223,138 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ if (!PyUnicodeOrString_Check(key)) continue; - const char *p_name = UEPyUnicode_AsUTF8(key); + const char* p_name = UEPyUnicode_AsUTF8(key); - PyObject *value = PyDict_GetItem(annotations, key); + PyObject* value = PyDict_GetItem(annotations, key); if (!value) continue; - UProperty *prop = nullptr; + UProperty* prop = nullptr; if (PyType_Check(value)) { - if ((PyTypeObject *)value == &PyFloat_Type) + if ((PyTypeObject*)value == &PyFloat_Type) { prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); } - else if ((PyTypeObject *)value == &PyUnicode_Type) + else if ((PyTypeObject*)value == &PyUnicode_Type) { prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); } - else if ((PyTypeObject *)value == &PyBool_Type) + else if ((PyTypeObject*)value == &PyBool_Type) { prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); } - else if ((PyTypeObject *)value == &PyLong_Type) + else if ((PyTypeObject*)value == &PyLong_Type) { prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); } - } - else if (ue_PyUObject *py_obj = ue_is_pyuobject(value)) - { - if (py_obj->ue_object->IsA()) + else if ((PyTypeObject*)value == &ue_PyFVectorType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject*)value == &ue_PyFVector2DType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject*)value == &ue_PyFRotatorType) { - UClass *p_u_class = (UClass *)py_obj->ue_object; - if (p_u_class->IsChildOf()) + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject*)value == &ue_PyFLinearColorType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject*)value == &ue_PyFColorType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject*)value == &ue_PyFTransformType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } +#if ENGINE_MINOR_VERSION > 18 + else if ((PyTypeObject*)value == &ue_PyFQuatType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } +#endif + else if (PyObject_IsInstance(value, (PyObject*)& PyType_Type)) + { + // Method annotation like foo:typing.Type[Pawn] produces annotations like typing.Type[Pawn], with .__args__ = (Pawn,) + PyObject* type_args = PyObject_GetAttrString(value, "__args__"); + if (!type_args) { - UClassProperty *prop_base = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - if (p_u_class == UClass::StaticClass()) - { - prop_base->SetMetaClass(UObject::StaticClass()); - } - else - { - prop_base->SetMetaClass(p_u_class->GetClass()); - } - prop_base->PropertyClass = UClass::StaticClass(); - prop = prop_base; + UE_LOG(LogPython, Error, TEXT("missing type info on %s"), UTF8_TO_TCHAR(name)); + return nullptr; } - else + if (PyTuple_Size(type_args) != 1) { - UObjectProperty *prop_base = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_base->SetPropertyClass(p_u_class); - prop = prop_base; + Py_DECREF(type_args); + UE_LOG(LogPython, Error, TEXT("exactly one class is allowed in type info for %s"), UTF8_TO_TCHAR(name)); + return nullptr; + } + PyObject* py_class = PyTuple_GetItem(type_args, 0); + ue_PyUObject* py_obj = ue_is_pyuobject(py_class); + if (!py_obj) + { + Py_DECREF(type_args); + UE_LOG(LogPython, Error, TEXT("type for %s must be a ue_PyUObject"), UTF8_TO_TCHAR(name)); + return nullptr; + } + if (!py_obj->ue_object->IsA()) + { + Py_DECREF(type_args); + UE_LOG(LogPython, Error, TEXT("type for %s must be a UClass"), UTF8_TO_TCHAR(name)); + return nullptr; } + UClassProperty* prop_class = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_class->SetMetaClass((UClass*)py_obj->ue_object); + prop_class->PropertyClass = UClass::StaticClass(); + prop = prop_class; + Py_DECREF(type_args); + } + } + else if (ue_PyUObject * py_obj = ue_is_pyuobject(value)) + { + if (py_obj->ue_object->IsA()) + { + UClass* p_u_class = (UClass*)py_obj->ue_object; + UObjectProperty* prop_base = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_base->SetPropertyClass(p_u_class); + prop = prop_base; + } +#if ENGINE_MINOR_VERSION > 17 + else if (py_obj->ue_object->IsA()) + { + UEnumProperty* prop_enum = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + UNumericProperty* prop_underlying = NewObject(prop_enum, TEXT("UnderlyingType"), RF_Public); + prop_enum->SetEnum((UEnum*)py_obj->ue_object); + prop_enum->AddCppProperty(prop_underlying); + prop = prop_enum; + } +#endif + else if (py_obj->ue_object->IsA()) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = (UScriptStruct*)py_obj->ue_object; + prop = prop_struct; } } - // TODO add native types (like vectors, rotators...) + if (prop) { prop->SetPropertyFlags(CPF_Parm); @@ -3179,59 +3373,137 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ // check for return value if (annotations) { - PyObject *py_return_value = PyDict_GetItemString(annotations, "return"); + PyObject* py_return_value = PyDict_GetItemString(annotations, "return"); if (py_return_value) { UE_LOG(LogPython, Warning, TEXT("Return Value found")); - UProperty *prop = nullptr; - char *p_name = (char *) "ReturnValue"; + UProperty* prop = nullptr; + char* p_name = (char*) "ReturnValue"; if (PyType_Check(py_return_value)) { - if ((PyTypeObject *)py_return_value == &PyFloat_Type) + if ((PyTypeObject*)py_return_value == &PyFloat_Type) { prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); } - else if ((PyTypeObject *)py_return_value == &PyUnicode_Type) + else if ((PyTypeObject*)py_return_value == &PyUnicode_Type) { prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); } - else if ((PyTypeObject *)py_return_value == &PyBool_Type) + else if ((PyTypeObject*)py_return_value == &PyBool_Type) { prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); } - else if ((PyTypeObject *)py_return_value == &PyLong_Type) + else if ((PyTypeObject*)py_return_value == &PyLong_Type) { prop = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); } - } - else if (ue_PyUObject *py_obj = ue_is_pyuobject(py_return_value)) - { - if (py_obj->ue_object->IsA()) + else if ((PyTypeObject*)py_return_value == &ue_PyFVectorType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject*)py_return_value == &ue_PyFVector2DType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject*)py_return_value == &ue_PyFRotatorType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject*)py_return_value == &ue_PyFLinearColorType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject*)py_return_value == &ue_PyFColorType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } + else if ((PyTypeObject*)py_return_value == &ue_PyFTransformType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } +#if ENGINE_MINOR_VERSION > 18 + else if ((PyTypeObject*)py_return_value == &ue_PyFQuatType) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = TBaseStructure::Get(); + prop = prop_struct; + } +#endif + else if (PyObject_IsInstance(py_return_value, (PyObject*)& PyType_Type)) { - UClass *p_u_class = (UClass *)py_obj->ue_object; - if (p_u_class->IsChildOf()) + // Method annotation like foo:typing.Type[Pawn] produces annotations like typing.Type[Pawn], with .__args__ = (Pawn,) + PyObject* type_args = PyObject_GetAttrString(py_return_value, "__args__"); + if (!type_args) { - UClassProperty *prop_base = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - if (p_u_class == UClass::StaticClass()) - { - prop_base->SetMetaClass(UObject::StaticClass()); - } - else - { - prop_base->SetMetaClass(p_u_class->GetClass()); - } - prop_base->PropertyClass = UClass::StaticClass(); - prop = prop_base; + UE_LOG(LogPython, Error, TEXT("missing type info on %s"), UTF8_TO_TCHAR(name)); + return nullptr; } - else + if (PyTuple_Size(type_args) != 1) + { + Py_DECREF(type_args); + UE_LOG(LogPython, Error, TEXT("exactly one class is allowed in type info for %s"), UTF8_TO_TCHAR(name)); + return nullptr; + } + PyObject* py_class = PyTuple_GetItem(type_args, 0); + ue_PyUObject* py_obj = ue_is_pyuobject(py_class); + if (!py_obj) + { + Py_DECREF(type_args); + UE_LOG(LogPython, Error, TEXT("type for %s must be a ue_PyUObject"), UTF8_TO_TCHAR(name)); + return nullptr; + } + if (!py_obj->ue_object->IsA()) { - UObjectProperty *prop_base = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); - prop_base->SetPropertyClass(p_u_class); - prop = prop_base; + Py_DECREF(type_args); + UE_LOG(LogPython, Error, TEXT("type for %s must be a UClass"), UTF8_TO_TCHAR(name)); + return nullptr; } + UClassProperty* prop_class = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_class->SetMetaClass((UClass*)py_obj->ue_object); + prop_class->PropertyClass = UClass::StaticClass(); + prop = prop_class; + Py_DECREF(type_args); + } + } + else if (ue_PyUObject * py_obj = ue_is_pyuobject(py_return_value)) + { + if (py_obj->ue_object->IsA()) + { + UClass* p_u_class = (UClass*)py_obj->ue_object; + UObjectProperty* prop_base = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_base->SetPropertyClass(p_u_class); + prop = prop_base; + } +#if ENGINE_MINOR_VERSION > 17 + else if (py_obj->ue_object->IsA()) + { + UEnumProperty* prop_enum = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + UNumericProperty* prop_underlying = NewObject(prop_enum, TEXT("UnderlyingType"), RF_Public); + prop_enum->SetEnum((UEnum*)py_obj->ue_object); + prop_enum->AddCppProperty(prop_underlying); + prop = prop_enum; + } +#endif + else if (py_obj->ue_object->IsA()) + { + UStructProperty* prop_struct = NewObject(function, UTF8_TO_TCHAR(p_name), RF_Public); + prop_struct->Struct = (UScriptStruct*)py_obj->ue_object; + prop = prop_struct; } } - // TODO add native types (like vectors, rotators...) + if (prop) { prop->SetPropertyFlags(CPF_Parm | CPF_OutParm | CPF_ReturnParm); @@ -3259,11 +3531,11 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ TFieldIterator It(parent_function); while (It) { - UProperty *p = *It; + UProperty* p = *It; if (p->PropertyFlags & CPF_Parm) { UE_LOG(LogPython, Warning, TEXT("Parent PROP: %s %d/%d %d %d %d %s %p"), *p->GetName(), (int)p->PropertyFlags, (int)UFunction::GetDefaultIgnoredSignatureCompatibilityFlags(), (int)(p->PropertyFlags & ~UFunction::GetDefaultIgnoredSignatureCompatibilityFlags()), p->GetSize(), p->GetOffset_ForGC(), *p->GetClass()->GetName(), p->GetClass()); - UClassProperty *ucp = Cast(p); + UClassProperty* ucp = Cast(p); if (ucp) { UE_LOG(LogPython, Warning, TEXT("Parent UClassProperty = %p %s %p %s"), ucp->PropertyClass, *ucp->PropertyClass->GetName(), ucp->MetaClass, *ucp->MetaClass->GetName()); @@ -3275,11 +3547,11 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ TFieldIterator It2(function); while (It2) { - UProperty *p = *It2; + UProperty* p = *It2; if (p->PropertyFlags & CPF_Parm) { UE_LOG(LogPython, Warning, TEXT("Function PROP: %s %d/%d %d %d %d %s %p"), *p->GetName(), (int)p->PropertyFlags, (int)UFunction::GetDefaultIgnoredSignatureCompatibilityFlags(), (int)(p->PropertyFlags & ~UFunction::GetDefaultIgnoredSignatureCompatibilityFlags()), p->GetSize(), p->GetOffset_ForGC(), *p->GetClass()->GetName(), p->GetClass()); - UClassProperty *ucp = Cast(p); + UClassProperty* ucp = Cast(p); if (ucp) { UE_LOG(LogPython, Warning, TEXT("Function UClassProperty = %p %s %p %s"), ucp->PropertyClass, *ucp->PropertyClass->GetName(), ucp->MetaClass, *ucp->MetaClass->GetName()); @@ -3299,7 +3571,7 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ TFieldIterator props(function, EFieldIteratorFlags::ExcludeSuper); for (; props; ++props) { - UProperty *p = *props; + UProperty* p = *props; if (p->HasAnyPropertyFlags(CPF_Parm)) { function->NumParms++; @@ -3328,9 +3600,9 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ #endif #if ENGINE_MINOR_VERSION > 18 - function->SetNativeFunc((FNativeFuncPtr)&UPythonFunction::CallPythonCallable); + function->SetNativeFunc((FNativeFuncPtr)& UPythonFunction::CallPythonCallable); #else - function->SetNativeFunc((Native)&UPythonFunction::CallPythonCallable); + function->SetNativeFunc((Native)& UPythonFunction::CallPythonCallable); #endif function->Next = u_class->Children; @@ -3371,26 +3643,26 @@ UFunction *unreal_engine_add_function(UClass *u_class, char *name, PyObject *py_ #endif return function; - } +} -FGuid *ue_py_check_fguid(PyObject *py_obj) +FGuid* ue_py_check_fguid(PyObject* py_obj) { - ue_PyUScriptStruct *ue_py_struct = py_ue_is_uscriptstruct(py_obj); + ue_PyUScriptStruct* ue_py_struct = py_ue_is_uscriptstruct(py_obj); if (!ue_py_struct) { return nullptr; } - if (ue_py_struct->u_struct == FindObject(ANY_PACKAGE, UTF8_TO_TCHAR((char *)"Guid"))) + if (ue_py_struct->u_struct == FindObject(ANY_PACKAGE, UTF8_TO_TCHAR((char*)"Guid"))) { return (FGuid*)ue_py_struct->u_struct_ptr; } return nullptr; } -uint8 * do_ue_py_check_struct(PyObject *py_obj, UScriptStruct* chk_u_struct) +uint8* do_ue_py_check_struct(PyObject* py_obj, UScriptStruct* chk_u_struct) { - ue_PyUScriptStruct *ue_py_struct = py_ue_is_uscriptstruct(py_obj); + ue_PyUScriptStruct* ue_py_struct = py_ue_is_uscriptstruct(py_obj); if (!ue_py_struct) { return nullptr; @@ -3404,9 +3676,9 @@ uint8 * do_ue_py_check_struct(PyObject *py_obj, UScriptStruct* chk_u_struct) return nullptr; } -bool do_ue_py_check_childstruct(PyObject *py_obj, UScriptStruct* parent_u_struct) +bool do_ue_py_check_childstruct(PyObject* py_obj, UScriptStruct* parent_u_struct) { - ue_PyUScriptStruct *ue_py_struct = py_ue_is_uscriptstruct(py_obj); + ue_PyUScriptStruct* ue_py_struct = py_ue_is_uscriptstruct(py_obj); if (!ue_py_struct) { return false; @@ -3418,10 +3690,10 @@ bool do_ue_py_check_childstruct(PyObject *py_obj, UScriptStruct* parent_u_struct #if PY_MAJOR_VERSION >= 3 -static PyObject *init_unreal_engine() +static PyObject * init_unreal_engine() { - PyObject *new_unreal_engine_module = PyModule_Create(&unreal_engine_module); + PyObject* new_unreal_engine_module = PyModule_Create(&unreal_engine_module); if (!new_unreal_engine_module) return nullptr; diff --git a/Source/UnrealEnginePython/Private/UEPyModule.h b/Source/UnrealEnginePython/Private/UEPyModule.h index b193e0a10..9d865cb16 100644 --- a/Source/UnrealEnginePython/Private/UEPyModule.h +++ b/Source/UnrealEnginePython/Private/UEPyModule.h @@ -14,6 +14,15 @@ #include "Wrappers/UEPyFColor.h" #include "Wrappers/UEPyFLinearColor.h" +// backward compatibility for UE4.20 TCHAR_TO_WCHAR +#ifndef TCHAR_TO_WCHAR + // SIZEOF_WCHAR_T is provided by pyconfig.h + #if SIZEOF_WCHAR_T == (PLATFORM_TCHAR_IS_4_BYTES ? 4 : 2) + #define TCHAR_TO_WCHAR(str) str + #else + #define TCHAR_TO_WCHAR(str) (wchar_t*)StringCast(static_cast(str)).Get() + #endif +#endif UWorld *ue_get_uworld(ue_PyUObject *); @@ -26,6 +35,7 @@ void ue_bind_events_for_py_class_by_attribute(UObject *, PyObject *); void ue_autobind_events_for_pyclass(ue_PyUObject *, PyObject *); PyObject *ue_bind_pyevent(ue_PyUObject *, FString, PyObject *, bool); +PyObject *ue_unbind_pyevent(ue_PyUObject *, FString, PyObject *, bool); PyObject *py_ue_ufunction_call(UFunction *, UObject *, PyObject *, int, PyObject *); diff --git a/Source/UnrealEnginePython/Private/UEPySubclassing.cpp b/Source/UnrealEnginePython/Private/UEPySubclassing.cpp index 870a62474..548a83935 100644 --- a/Source/UnrealEnginePython/Private/UEPySubclassing.cpp +++ b/Source/UnrealEnginePython/Private/UEPySubclassing.cpp @@ -346,7 +346,12 @@ int unreal_engine_py_init(ue_PyUObject *self, PyObject *args, PyObject *kwds) { if (auto casted_prop = Cast(u_property)) { +#if ENGINE_MINOR_VERSION >= 23 + FMulticastScriptDelegate multiscript_delegate = *casted_prop->GetMulticastDelegate(ObjectInitializer.GetObj()); +#else + FMulticastScriptDelegate multiscript_delegate = casted_prop->GetPropertyValue_InContainer(ObjectInitializer.GetObj()); +#endif FScriptDelegate script_delegate; UPythonDelegate *py_delegate = FUnrealEnginePythonHouseKeeper::Get()->NewDelegate(ObjectInitializer.GetObj(), mc_value, casted_prop->SignatureFunction); @@ -357,7 +362,11 @@ int unreal_engine_py_init(ue_PyUObject *self, PyObject *args, PyObject *kwds) multiscript_delegate.Add(script_delegate); // re-assign multicast delegate +#if ENGINE_MINOR_VERSION >= 23 + casted_prop->SetMulticastDelegate(ObjectInitializer.GetObj(), multiscript_delegate); +#else casted_prop->SetPropertyValue_InContainer(ObjectInitializer.GetObj(), multiscript_delegate); +#endif } else { diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyAnimSequence.cpp b/Source/UnrealEnginePython/Private/UObject/UEPyAnimSequence.cpp index 7eafe7b51..2674c0d1b 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyAnimSequence.cpp +++ b/Source/UnrealEnginePython/Private/UObject/UEPyAnimSequence.cpp @@ -105,6 +105,7 @@ PyObject *py_ue_anim_extract_root_motion(ue_PyUObject * self, PyObject * args) #if WITH_EDITOR #if ENGINE_MINOR_VERSION > 13 +#if ENGINE_MINOR_VERSION < 23 PyObject *py_ue_anim_sequence_update_compressed_track_map_from_raw(ue_PyUObject * self, PyObject * args) { ue_py_check(self); @@ -117,6 +118,7 @@ PyObject *py_ue_anim_sequence_update_compressed_track_map_from_raw(ue_PyUObject Py_RETURN_NONE; } +#endif PyObject *py_ue_anim_sequence_get_raw_animation_data(ue_PyUObject * self, PyObject * args) diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyCapture.cpp b/Source/UnrealEnginePython/Private/UObject/UEPyCapture.cpp index 1977a60a6..26f2194c4 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyCapture.cpp +++ b/Source/UnrealEnginePython/Private/UObject/UEPyCapture.cpp @@ -29,11 +29,14 @@ for a queue of UMovieSceneCapture objects struct FInEditorMultiCapture : TSharedFromThis { - static TWeakPtr CreateInEditorMultiCapture(TArray InCaptureObjects) + static TWeakPtr CreateInEditorMultiCapture(TArray InCaptureObjects, PyObject *py_callable) { // FInEditorCapture owns itself, so should only be kept alive by itself, or a pinned (=> temporary) weakptr FInEditorMultiCapture* Capture = new FInEditorMultiCapture; Capture->CaptureObjects = InCaptureObjects; + Capture->py_callable = py_callable; + if (Capture->py_callable) + Py_INCREF(Capture->py_callable); for (UMovieSceneCapture *SceneCapture : Capture->CaptureObjects) { SceneCapture->AddToRoot(); @@ -55,6 +58,10 @@ struct FInEditorMultiCapture : TSharedFromThis SceneCapture->RemoveFromRoot(); } OnlyStrongReference = nullptr; + { + FScopePythonGIL gil; + Py_XDECREF(py_callable); + } } void Dequeue() @@ -124,6 +131,22 @@ struct FInEditorMultiCapture : TSharedFromThis bool PlaySession(float DeltaTime) { + + + if (py_callable) + { + GEditor->RequestEndPlayMap(); + FScopePythonGIL gil; + ue_PyUObject *py_capture = ue_get_python_uobject(CurrentCaptureObject); + PyObject *py_ret = PyObject_CallFunction(py_callable, "O", py_capture); + if (!py_ret) + { + unreal_engine_py_log_error(); + } + Py_XDECREF(py_ret); + } + + GEditor->RequestPlaySession(true, nullptr, false); return false; } @@ -276,6 +299,7 @@ struct FInEditorMultiCapture : TSharedFromThis { FEditorDelegates::EndPIE.RemoveAll(this); + // remove item from the TArray; CaptureObjects.RemoveAt(0); @@ -309,13 +333,16 @@ struct FInEditorMultiCapture : TSharedFromThis TSubclassOf CachedGameMode; TArray CaptureObjects; + + PyObject *py_callable; }; PyObject *py_unreal_engine_in_editor_capture(PyObject * self, PyObject * args) { PyObject *py_scene_captures; + PyObject *py_callable = nullptr; - if (!PyArg_ParseTuple(args, "O:in_editor_capture", &py_scene_captures)) + if (!PyArg_ParseTuple(args, "O|O:in_editor_capture", &py_scene_captures, &py_callable)) { return nullptr; } @@ -348,7 +375,7 @@ PyObject *py_unreal_engine_in_editor_capture(PyObject * self, PyObject * args) } Py_BEGIN_ALLOW_THREADS - FInEditorMultiCapture::CreateInEditorMultiCapture(Captures); + FInEditorMultiCapture::CreateInEditorMultiCapture(Captures, py_callable); Py_END_ALLOW_THREADS Py_RETURN_NONE; diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyDataTable.cpp b/Source/UnrealEnginePython/Private/UObject/UEPyDataTable.cpp index 6f09a9412..727c02c08 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyDataTable.cpp +++ b/Source/UnrealEnginePython/Private/UObject/UEPyDataTable.cpp @@ -108,7 +108,11 @@ PyObject *py_ue_data_table_as_dict(ue_PyUObject * self, PyObject * args) PyObject *py_dict = PyDict_New(); +#if ENGINE_MINOR_VERSION > 20 + for (TMap::TConstIterator RowMapIter(data_table->GetRowMap().CreateConstIterator()); RowMapIter; ++RowMapIter) +#else for (TMap::TConstIterator RowMapIter(data_table->RowMap.CreateConstIterator()); RowMapIter; ++RowMapIter) +#endif { PyDict_SetItemString(py_dict, TCHAR_TO_UTF8(*RowMapIter->Key.ToString()), py_ue_new_owned_uscriptstruct(data_table->RowStruct, RowMapIter->Value)); } @@ -152,7 +156,11 @@ PyObject *py_ue_data_table_find_row(ue_PyUObject * self, PyObject * args) return PyErr_Format(PyExc_Exception, "uobject is not a UDataTable"); uint8 **data = nullptr; +#if ENGINE_MINOR_VERSION > 20 + data = (uint8 **)data_table->GetRowMap().Find(FName(UTF8_TO_TCHAR(name))); +#else data = data_table->RowMap.Find(FName(UTF8_TO_TCHAR(name))); +#endif if (!data) { return PyErr_Format(PyExc_Exception, "key not found in UDataTable"); @@ -172,7 +180,11 @@ PyObject *py_ue_data_table_get_all_rows(ue_PyUObject * self, PyObject * args) PyObject *py_list = PyList_New(0); +#if ENGINE_MINOR_VERSION > 20 + for (TMap::TConstIterator RowMapIter(data_table->GetRowMap().CreateConstIterator()); RowMapIter; ++RowMapIter) +#else for (TMap::TConstIterator RowMapIter(data_table->RowMap.CreateConstIterator()); RowMapIter; ++RowMapIter) +#endif { PyList_Append(py_list, py_ue_new_owned_uscriptstruct(data_table->RowStruct, RowMapIter->Value)); } diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyFoliage.cpp b/Source/UnrealEnginePython/Private/UObject/UEPyFoliage.cpp index 09855fb31..0a9498d2a 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyFoliage.cpp +++ b/Source/UnrealEnginePython/Private/UObject/UEPyFoliage.cpp @@ -37,8 +37,11 @@ PyObject *py_ue_get_foliage_types(ue_PyUObject *self, PyObject * args) PyObject *py_list = PyList_New(0); TArray FoliageTypes; - +#if ENGINE_MINOR_VERSION >=23 + foliage_actor->FoliageInfos.GetKeys(FoliageTypes); +#else foliage_actor->FoliageMeshes.GetKeys(FoliageTypes); +#endif for (UFoliageType *FoliageType : FoliageTypes) { @@ -68,18 +71,26 @@ PyObject *py_ue_get_foliage_instances(ue_PyUObject *self, PyObject * args) if (!foliage_type) return PyErr_Format(PyExc_Exception, "argument is not a UFoliageType"); +#if ENGINE_MINOR_VERSION >= 23 + if (!foliage_actor->FoliageInfos.Contains(foliage_type)) +#else if (!foliage_actor->FoliageMeshes.Contains(foliage_type)) +#endif { return PyErr_Format(PyExc_Exception, "specified UFoliageType not found in AInstancedFoliageActor"); } +#if ENGINE_MINOR_VERSION >= 23 + FFoliageInfo& info = foliage_actor->FoliageInfos[foliage_type].Get(); +#else FFoliageMeshInfo& info = foliage_actor->FoliageMeshes[foliage_type].Get(); +#endif PyObject *py_list = PyList_New(0); - for (FFoliageInstance& instance : info.Instances) + for (int32 i=0; iIsA()) { +#if ENGINE_MINOR_VERSION >= 23 + foliage_type = ifa->GetLocalFoliageTypeForSource(u_object); +#else foliage_type = ifa->GetLocalFoliageTypeForMesh((UStaticMesh *)u_object); +#endif if (!foliage_type) { ifa->AddMesh((UStaticMesh *)u_object, &foliage_type); diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyLandscape.cpp b/Source/UnrealEnginePython/Private/UObject/UEPyLandscape.cpp index 883a6ba0f..adacab7ec 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyLandscape.cpp +++ b/Source/UnrealEnginePython/Private/UObject/UEPyLandscape.cpp @@ -8,35 +8,35 @@ #include "Runtime/Landscape/Classes/LandscapeInfo.h" #include "GameFramework/GameModeBase.h" -PyObject *py_ue_create_landscape_info(ue_PyUObject *self, PyObject * args) +PyObject* py_ue_create_landscape_info(ue_PyUObject* self, PyObject* args) { ue_py_check(self); - ALandscapeProxy *landscape = ue_py_check_type(self); + ALandscapeProxy* landscape = ue_py_check_type(self); if (!landscape) return PyErr_Format(PyExc_Exception, "uobject is not a ULandscapeProxy"); Py_RETURN_UOBJECT(landscape->CreateLandscapeInfo()); } -PyObject *py_ue_get_landscape_info(ue_PyUObject *self, PyObject * args) +PyObject* py_ue_get_landscape_info(ue_PyUObject* self, PyObject* args) { ue_py_check(self); - ALandscapeProxy *landscape = ue_py_check_type(self); + ALandscapeProxy* landscape = ue_py_check_type(self); if (!landscape) return PyErr_Format(PyExc_Exception, "uobject is not a ULandscapeProxy"); - ULandscapeInfo *info = landscape->GetLandscapeInfo(); + ULandscapeInfo* info = landscape->GetLandscapeInfo(); if (!info) Py_RETURN_NONE; Py_RETURN_UOBJECT(info); } -PyObject *py_ue_landscape_import(ue_PyUObject *self, PyObject * args) +PyObject* py_ue_landscape_import(ue_PyUObject* self, PyObject* args) { ue_py_check(self); @@ -51,7 +51,7 @@ PyObject *py_ue_landscape_import(ue_PyUObject *self, PyObject * args) if (!PyArg_ParseTuple(args, "iiiiy*|i:landscape_import", §ion_size, §ions_per_component, &component_x, &component_y, &heightmap_buffer, &layer_type)) return nullptr; - ALandscapeProxy *landscape = ue_py_check_type(self); + ALandscapeProxy* landscape = ue_py_check_type(self); if (!landscape) return PyErr_Format(PyExc_Exception, "uobject is not a ULandscapeProxy"); @@ -62,16 +62,29 @@ PyObject *py_ue_landscape_import(ue_PyUObject *self, PyObject * args) if (heightmap_buffer.len < (Py_ssize_t)(size_x * size_y * sizeof(uint16))) return PyErr_Format(PyExc_Exception, "not enough heightmap data, expecting %lu bytes", size_x * size_y * sizeof(uint16)); - uint16 *data = (uint16 *)heightmap_buffer.buf; + uint16* data = (uint16*)heightmap_buffer.buf; TArray infos; +#if ENGINE_MINOR_VERSION < 23 landscape->Import(FGuid::NewGuid(), 0, 0, size_x - 1, size_y - 1, sections_per_component, section_size, data, nullptr, infos, (ELandscapeImportAlphamapType)layer_type); +#else + TMap> HeightDataPerLayers; + TArray HeightData; + for (uint32 i = 0; i < (heightmap_buffer.len / sizeof(uint16)); i++) + { + HeightData.Add(data[i]); + } + HeightDataPerLayers.Add(FGuid(), HeightData); + TMap> MaterialLayersInfo; + MaterialLayersInfo.Add(FGuid(), infos); + landscape->Import(FGuid::NewGuid(), 0, 0, size_x - 1, size_y - 1, sections_per_component, section_size, HeightDataPerLayers, nullptr, MaterialLayersInfo, (ELandscapeImportAlphamapType)layer_type); +#endif Py_RETURN_NONE; } -PyObject *py_ue_landscape_export_to_raw_mesh(ue_PyUObject *self, PyObject * args) +PyObject* py_ue_landscape_export_to_raw_mesh(ue_PyUObject* self, PyObject* args) { ue_py_check(self); @@ -81,14 +94,18 @@ PyObject *py_ue_landscape_export_to_raw_mesh(ue_PyUObject *self, PyObject * args if (!PyArg_ParseTuple(args, "|i:landscape_import", &lod)) return nullptr; - ALandscapeProxy *landscape = ue_py_check_type(self); + ALandscapeProxy* landscape = ue_py_check_type(self); if (!landscape) return PyErr_Format(PyExc_Exception, "uobject is not a ULandscapeProxy"); +#if ENGINE_MINOR_VERSION > 21 + return PyErr_Format(PyExc_Exception, "MeshDescription struct is still unsupported");; +#else FRawMesh raw_mesh; if (!landscape->ExportToRawMesh(lod, raw_mesh)) return PyErr_Format(PyExc_Exception, "unable to export landscape to FRawMesh"); return py_ue_new_fraw_mesh(raw_mesh); +#endif } #endif diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyMaterial.cpp b/Source/UnrealEnginePython/Private/UObject/UEPyMaterial.cpp index 90b47da3a..c958094f6 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyMaterial.cpp +++ b/Source/UnrealEnginePython/Private/UObject/UEPyMaterial.cpp @@ -505,7 +505,11 @@ PyObject *py_ue_static_mesh_set_collision_for_lod(ue_PyUObject *self, PyObject * enabled = true; } +#if ENGINE_MINOR_VERSION >= 23 + FMeshSectionInfo info = mesh->GetSectionInfoMap().Get(lod_index, material_index); +#else FMeshSectionInfo info = mesh->SectionInfoMap.Get(lod_index, material_index); +#endif info.bEnableCollision = enabled; mesh->SectionInfoMap.Set(lod_index, material_index, info); diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyObject.cpp b/Source/UnrealEnginePython/Private/UObject/UEPyObject.cpp index 43ef8aeca..3b2983b99 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyObject.cpp +++ b/Source/UnrealEnginePython/Private/UObject/UEPyObject.cpp @@ -482,6 +482,87 @@ PyObject *py_ue_get_metadata(ue_PyUObject * self, PyObject * args) return PyErr_Format(PyExc_TypeError, "the object does not support MetaData"); } +PyObject *py_ue_get_metadata_tag(ue_PyUObject * self, PyObject * args) +{ + + ue_py_check(self); + + char *metadata_tag_key; + if (!PyArg_ParseTuple(args, "s:get_metadata_tag", &metadata_tag_key)) + { + return nullptr; + } + + const FString& Value = self->ue_object->GetOutermost()->GetMetaData()->GetValue(self->ue_object, UTF8_TO_TCHAR(metadata_tag_key)); + return PyUnicode_FromString(TCHAR_TO_UTF8(*Value)); +} + +PyObject *py_ue_has_metadata_tag(ue_PyUObject * self, PyObject * args) +{ + + ue_py_check(self); + + char *metadata_tag_key; + if (!PyArg_ParseTuple(args, "s:has_metadata_tag", &metadata_tag_key)) + { + return nullptr; + } + + if (self->ue_object->GetOutermost()->GetMetaData()->HasValue(self->ue_object, UTF8_TO_TCHAR(metadata_tag_key))) + { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +PyObject *py_ue_remove_metadata_tag(ue_PyUObject * self, PyObject * args) +{ + + ue_py_check(self); + + char *metadata_tag_key; + if (!PyArg_ParseTuple(args, "s:remove_metadata_tag", &metadata_tag_key)) + { + return nullptr; + } + + self->ue_object->GetOutermost()->GetMetaData()->RemoveValue(self->ue_object, UTF8_TO_TCHAR(metadata_tag_key)); + Py_RETURN_NONE; +} + +PyObject *py_ue_set_metadata_tag(ue_PyUObject * self, PyObject * args) +{ + + ue_py_check(self); + + char *metadata_tag_key; + char *metadata_tag_value; + if (!PyArg_ParseTuple(args, "ss:set_metadata_tag", &metadata_tag_key, &metadata_tag_value)) + { + return nullptr; + } + + self->ue_object->GetOutermost()->GetMetaData()->SetValue(self->ue_object, UTF8_TO_TCHAR(metadata_tag_key), UTF8_TO_TCHAR(metadata_tag_value)); + Py_RETURN_NONE; +} + + +PyObject *py_ue_metadata_tags(ue_PyUObject * self, PyObject * args) +{ + ue_py_check(self); + + TMap *TagsMap = self->ue_object->GetOutermost()->GetMetaData()->GetMapForObject(self->ue_object); + if (!TagsMap) + Py_RETURN_NONE; + + PyObject* py_list = PyList_New(0); + for (TPair< FName, FString>& Pair : *TagsMap) + { + PyList_Append(py_list, PyUnicode_FromString(TCHAR_TO_UTF8(*Pair.Key.ToString()))); + } + return py_list; +} + PyObject *py_ue_has_metadata(ue_PyUObject * self, PyObject * args) { @@ -498,11 +579,9 @@ PyObject *py_ue_has_metadata(ue_PyUObject * self, PyObject * args) UClass *u_class = (UClass *)self->ue_object; if (u_class->HasMetaData(FName(UTF8_TO_TCHAR(metadata_key)))) { - Py_INCREF(Py_True); - return Py_True; + Py_RETURN_TRUE; } - Py_INCREF(Py_False); - return Py_False; + Py_RETURN_FALSE; } if (self->ue_object->IsA()) @@ -1005,7 +1084,11 @@ PyObject *py_ue_broadcast(ue_PyUObject *self, PyObject *args) if (auto casted_prop = Cast(u_property)) { +#if ENGINE_MINOR_VERSION >= 23 + FMulticastScriptDelegate multiscript_delegate = *casted_prop->GetMulticastDelegate(self->ue_object); +#else FMulticastScriptDelegate multiscript_delegate = casted_prop->GetPropertyValue_InContainer(self->ue_object); +#endif uint8 *parms = (uint8 *)FMemory_Alloca(casted_prop->SignatureFunction->PropertiesSize); FMemory::Memzero(parms, casted_prop->SignatureFunction->PropertiesSize); @@ -1438,6 +1521,25 @@ PyObject *py_ue_bind_event(ue_PyUObject * self, PyObject * args) return ue_bind_pyevent(self, FString(event_name), py_callable, true); } +PyObject *py_ue_unbind_event(ue_PyUObject * self, PyObject * args) +{ + ue_py_check(self); + + char *event_name; + PyObject *py_callable; + if (!PyArg_ParseTuple(args, "sO:bind_event", &event_name, &py_callable)) + { + return NULL; + } + + if (!PyCallable_Check(py_callable)) + { + return PyErr_Format(PyExc_Exception, "object is not callable"); + } + + return ue_unbind_pyevent(self, FString(event_name), py_callable, true); +} + PyObject *py_ue_delegate_bind_ufunction(ue_PyUObject * self, PyObject * args) { ue_py_check(self); @@ -1850,7 +1952,7 @@ PyObject *py_ue_add_property(ue_PyUObject * self, PyObject * args) // TODO add default value Py_RETURN_UOBJECT(u_property); -} + } PyObject *py_ue_as_dict(ue_PyUObject * self, PyObject * args) { @@ -2037,7 +2139,7 @@ PyObject *py_ue_save_package(ue_PyUObject * self, PyObject * args) package->FileName = *FPackageName::LongPackageNameToFilename(package->GetPathName(), bIsMap ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension()); } - if (UPackage::SavePackage(package, u_object, RF_NoFlags, *package->FileName.ToString())) + if (UPackage::SavePackage(package, u_object, RF_Standalone, *package->FileName.ToString())) { FAssetRegistryModule::AssetCreated(u_object); Py_RETURN_UOBJECT(u_object); diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyObject.h b/Source/UnrealEnginePython/Private/UObject/UEPyObject.h index d43422274..6fb4678fe 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyObject.h +++ b/Source/UnrealEnginePython/Private/UObject/UEPyObject.h @@ -51,6 +51,7 @@ PyObject *py_ue_enum_user_defined_names(ue_PyUObject *, PyObject *); PyObject *py_ue_bind_event(ue_PyUObject *, PyObject *); +PyObject *py_ue_unbind_event(ue_PyUObject *, PyObject *); PyObject *py_ue_add_function(ue_PyUObject *, PyObject *); PyObject *py_ue_add_property(ue_PyUObject *, PyObject *); @@ -97,6 +98,12 @@ PyObject *py_ue_get_metadata(ue_PyUObject *, PyObject *); PyObject *py_ue_set_metadata(ue_PyUObject *, PyObject *); PyObject *py_ue_has_metadata(ue_PyUObject *, PyObject *); +PyObject *py_ue_get_metadata_tag(ue_PyUObject *, PyObject *); +PyObject *py_ue_set_metadata_tag(ue_PyUObject *, PyObject *); +PyObject *py_ue_has_metadata_tag(ue_PyUObject *, PyObject *); +PyObject *py_ue_remove_metadata_tag(ue_PyUObject *, PyObject *); +PyObject *py_ue_metadata_tags(ue_PyUObject *, PyObject *); + PyObject *py_ue_import_custom_properties(ue_PyUObject *, PyObject *); #endif @@ -105,4 +112,4 @@ PyObject *py_ue_render_thumbnail(ue_PyUObject *, PyObject *); PyObject *py_ue_to_bytes(ue_PyUObject *, PyObject *); PyObject *py_ue_to_bytearray(ue_PyUObject *, PyObject *); -PyObject *py_ue_from_bytes(ue_PyUObject *, PyObject *); \ No newline at end of file +PyObject *py_ue_from_bytes(ue_PyUObject *, PyObject *); diff --git a/Source/UnrealEnginePython/Private/UObject/UEPySequencer.cpp b/Source/UnrealEnginePython/Private/UObject/UEPySequencer.cpp index 32b11dd48..8858a7936 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPySequencer.cpp +++ b/Source/UnrealEnginePython/Private/UObject/UEPySequencer.cpp @@ -68,6 +68,51 @@ static bool magic_get_frame_number(UMovieScene *MovieScene, PyObject *py_obj, FF } #if WITH_EDITOR +#if ENGINE_MINOR_VERSION > 21 +static void ImportTransformChannel(const FRichCurve& Source, FMovieSceneFloatChannel* Dest, FFrameRate DestFrameRate, bool bNegateTangents) +{ + TMovieSceneChannelData ChannelData = Dest->GetData(); + ChannelData.Reset(); + double DecimalRate = DestFrameRate.AsDecimal(); + for (int32 KeyIndex = 0; KeyIndex < Source.Keys.Num(); ++KeyIndex) + { + float ArriveTangent = Source.Keys[KeyIndex].ArriveTangent; + if (KeyIndex > 0) + { + ArriveTangent = ArriveTangent / ((Source.Keys[KeyIndex].Value - Source.Keys[KeyIndex - 1].Value) * DecimalRate); + } + + float LeaveTangent = Source.Keys[KeyIndex].LeaveTangent; + if (KeyIndex < Source.Keys.Num() - 1) + { + LeaveTangent = LeaveTangent / ((Source.Keys[KeyIndex + 1].Value - Source.Keys[KeyIndex].Value) * DecimalRate); + } + + if (bNegateTangents) + { + ArriveTangent = -ArriveTangent; + LeaveTangent = -LeaveTangent; + } + + FFrameNumber KeyTime = (Source.Keys[KeyIndex].Value * DestFrameRate).RoundToFrame(); + if (ChannelData.FindKey(KeyTime) == INDEX_NONE) + { + FMovieSceneFloatValue NewKey(Source.Keys[KeyIndex].Value); + + NewKey.InterpMode = Source.Keys[KeyIndex].InterpMode; + NewKey.TangentMode = Source.Keys[KeyIndex].TangentMode; + NewKey.Tangent.ArriveTangent = ArriveTangent / DestFrameRate.AsDecimal(); + NewKey.Tangent.LeaveTangent = LeaveTangent / DestFrameRate.AsDecimal(); + NewKey.Tangent.TangentWeightMode = RCTWM_WeightedNone; + NewKey.Tangent.ArriveTangentWeight = 0.0f; + NewKey.Tangent.LeaveTangentWeight = 0.0f; + ChannelData.AddKey(KeyTime, NewKey); + } + } + + Dest->AutoSetTangents(); +} +#else static void ImportTransformChannel(const FInterpCurveFloat& Source, FMovieSceneFloatChannel* Dest, FFrameRate DestFrameRate, bool bNegateTangents) { TMovieSceneChannelData ChannelData = Dest->GetData(); @@ -94,23 +139,36 @@ static void ImportTransformChannel(const FInterpCurveFloat& Source, FMovieSceneF } FFrameNumber KeyTime = (Source.Points[KeyIndex].InVal * DestFrameRate).RoundToFrame(); +#if ENGINE_MINOR_VERSION > 20 + FMatineeImportTools::SetOrAddKey(ChannelData, KeyTime, Source.Points[KeyIndex].OutVal, ArriveTangent, LeaveTangent, Source.Points[KeyIndex].InterpMode, DestFrameRate); +#else FMatineeImportTools::SetOrAddKey(ChannelData, KeyTime, Source.Points[KeyIndex].OutVal, ArriveTangent, LeaveTangent, Source.Points[KeyIndex].InterpMode); +#endif } Dest->AutoSetTangents(); } +#endif static bool ImportFBXTransform(FString NodeName, UMovieScene3DTransformSection* TransformSection, UnFbx::FFbxCurvesAPI& CurveAPI) { // Look for transforms explicitly + FTransform DefaultTransform; + +#if ENGINE_MINOR_VERSION > 21 + FRichCurve Translation[3]; + FRichCurve EulerRotation[3]; + FRichCurve Scale[3]; +#else FInterpCurveFloat Translation[3]; FInterpCurveFloat EulerRotation[3]; FInterpCurveFloat Scale[3]; - FTransform DefaultTransform; +#endif CurveAPI.GetConvertedTransformCurveData(NodeName, Translation[0], Translation[1], Translation[2], EulerRotation[0], EulerRotation[1], EulerRotation[2], Scale[0], Scale[1], Scale[2], DefaultTransform); + TransformSection->Modify(); FFrameRate FrameRate = TransformSection->GetTypedOuter()->GetTickResolution(); @@ -911,6 +969,71 @@ PyObject *py_ue_sequencer_set_playback_range(ue_PyUObject *self, PyObject * args Py_RETURN_NONE; } +PyObject *py_ue_sequencer_get_playback_range(ue_PyUObject *self, PyObject * args) +{ + + ue_py_check(self); + + ULevelSequence *seq = ue_py_check_type(self); + if (!seq) + return PyErr_Format(PyExc_Exception, "uobject is not a LevelSequence"); + UMovieScene *scene = seq->GetMovieScene(); + +#if ENGINE_MINOR_VERSION < 20 + TRange range = scene->GetPlaybackRange(); + return Py_BuildValue("(ff)", range.GetLowerBoundValue(), range.GetUpperBoundValue()); +#else + TRange range = scene->GetPlaybackRange(); + + return Py_BuildValue("(OO)", py_ue_new_fframe_number(range.GetLowerBoundValue()), py_ue_new_fframe_number(range.GetUpperBoundValue())); + +#endif +} + +PyObject *py_ue_sequencer_set_working_range(ue_PyUObject *self, PyObject * args) +{ + + ue_py_check(self); + + ULevelSequence *seq = ue_py_check_type(self); + if (!seq) + return PyErr_Format(PyExc_Exception, "uobject is not a LevelSequence"); + UMovieScene *scene = seq->GetMovieScene(); + + float start_time; + float end_time; + if (!PyArg_ParseTuple(args, "ff:sequencer_set_working_range", &start_time, &end_time)) + { + return nullptr; + } + + scene->SetWorkingRange(start_time, end_time); + + Py_RETURN_NONE; +} + +PyObject *py_ue_sequencer_set_view_range(ue_PyUObject *self, PyObject * args) +{ + + ue_py_check(self); + + ULevelSequence *seq = ue_py_check_type(self); + if (!seq) + return PyErr_Format(PyExc_Exception, "uobject is not a LevelSequence"); + UMovieScene *scene = seq->GetMovieScene(); + + float start_time; + float end_time; + if (!PyArg_ParseTuple(args, "ff:sequencer_set_view_range", &start_time, &end_time)) + { + return nullptr; + } + + scene->SetViewRange(start_time, end_time); + + Py_RETURN_NONE; +} + PyObject *py_ue_sequencer_set_section_range(ue_PyUObject *self, PyObject * args) { @@ -965,6 +1088,27 @@ PyObject *py_ue_sequencer_set_section_range(ue_PyUObject *self, PyObject * args) Py_RETURN_NONE; } +PyObject *py_ue_sequencer_get_selection_range(ue_PyUObject *self, PyObject * args) +{ + + ue_py_check(self); + + ULevelSequence *seq = ue_py_check_type(self); + if (!seq) + return PyErr_Format(PyExc_Exception, "uobject is not a LevelSequence"); + UMovieScene *scene = seq->GetMovieScene(); + +#if ENGINE_MINOR_VERSION < 20 + TRange range = scene->GetSelectionRange(); + return Py_BuildValue("(ff)", range.GetLowerBoundValue(), range.GetUpperBoundValue()); +#else + TRange range = scene->GetSelectionRange(); + + return Py_BuildValue("(OO)", py_ue_new_fframe_number(range.GetLowerBoundValue()), py_ue_new_fframe_number(range.GetUpperBoundValue())); + +#endif +} + PyObject *py_ue_sequencer_section_add_key(ue_PyUObject *self, PyObject * args) { @@ -1504,9 +1648,15 @@ PyObject *py_ue_sequencer_import_fbx_transform(ue_PyUObject *self, PyObject * ar continue; // Look for transforms explicitly +#if ENGINE_MINOR_VERSION > 21 + FRichCurve Translation[3]; + FRichCurve EulerRotation[3]; + FRichCurve Scale[3]; +#else FInterpCurveFloat Translation[3]; FInterpCurveFloat EulerRotation[3]; FInterpCurveFloat Scale[3]; +#endif FTransform DefaultTransform; #if ENGINE_MINOR_VERSION >= 18 CurveAPI.GetConvertedTransformCurveData(NodeName, Translation[0], Translation[1], Translation[2], EulerRotation[0], EulerRotation[1], EulerRotation[2], Scale[0], Scale[1], Scale[2], DefaultTransform); diff --git a/Source/UnrealEnginePython/Private/UObject/UEPySequencer.h b/Source/UnrealEnginePython/Private/UObject/UEPySequencer.h index ac22a41a7..83185d612 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPySequencer.h +++ b/Source/UnrealEnginePython/Private/UObject/UEPySequencer.h @@ -10,7 +10,11 @@ PyObject *py_ue_sequencer_track_sections(ue_PyUObject *, PyObject *); PyObject *py_ue_sequencer_get_camera_cut_track(ue_PyUObject *, PyObject *); #if WITH_EDITOR PyObject *py_ue_sequencer_set_playback_range(ue_PyUObject *, PyObject *); +PyObject *py_ue_sequencer_set_view_range(ue_PyUObject *, PyObject *); +PyObject *py_ue_sequencer_set_working_range(ue_PyUObject *, PyObject *); PyObject *py_ue_sequencer_set_section_range(ue_PyUObject *, PyObject *); +PyObject *py_ue_sequencer_get_playback_range(ue_PyUObject *, PyObject *); +PyObject *py_ue_sequencer_get_selection_range(ue_PyUObject *, PyObject *); PyObject *py_ue_sequencer_folders(ue_PyUObject *, PyObject *); PyObject *py_ue_sequencer_create_folder(ue_PyUObject *, PyObject *); PyObject *py_ue_sequencer_set_display_name(ue_PyUObject *, PyObject *); diff --git a/Source/UnrealEnginePython/Private/UObject/UEPySkeletal.cpp b/Source/UnrealEnginePython/Private/UObject/UEPySkeletal.cpp index 7cd84c120..3386d4537 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPySkeletal.cpp +++ b/Source/UnrealEnginePython/Private/UObject/UEPySkeletal.cpp @@ -5,6 +5,9 @@ #include "Developer/MeshUtilities/Public/MeshUtilities.h" #include "Wrappers/UEPyFMorphTargetDelta.h" #include "Wrappers/UEPyFSoftSkinVertex.h" +#if ENGINE_MINOR_VERSION > 20 +#include "Runtime/Engine/Public/Rendering/SkeletalMeshLODImporterData.h" +#endif #if ENGINE_MINOR_VERSION > 18 #include "Runtime/Engine/Public/Rendering/SkeletalMeshModel.h" #endif @@ -953,9 +956,15 @@ PyObject *py_ue_skeletal_mesh_build_lod(ue_PyUObject *self, PyObject * args, PyO TArray soft_vertices; TArray points; +#if ENGINE_MINOR_VERSION > 20 + TArray wedges; + TArray faces; + TArray influences; +#else TArray wedges; TArray faces; TArray influences; +#endif TArray points_to_map; TArray tangentsX; @@ -976,7 +985,11 @@ PyObject *py_ue_skeletal_mesh_build_lod(ue_PyUObject *self, PyObject * args, PyO points_to_map.Add(vertex_index); +#if ENGINE_MINOR_VERSION > 20 + SkeletalMeshImportData::FMeshWedge wedge; +#else FMeshWedge wedge; +#endif wedge.iVertex = vertex_index; wedge.Color = ss_vertex->ss_vertex.Color; for (int32 i = 0; i < MAX_TEXCOORDS; i++) @@ -987,7 +1000,11 @@ PyObject *py_ue_skeletal_mesh_build_lod(ue_PyUObject *self, PyObject * args, PyO for (int32 i = 0; i < MAX_TOTAL_INFLUENCES; i++) { +#if ENGINE_MINOR_VERSION > 20 + SkeletalMeshImportData::FVertInfluence influence; +#else FVertInfluence influence; +#endif influence.VertIndex = wedge_index; influence.BoneIndex = ss_vertex->ss_vertex.InfluenceBones[i]; influence.Weight = ss_vertex->ss_vertex.InfluenceWeights[i] / 255.f; @@ -1009,7 +1026,11 @@ PyObject *py_ue_skeletal_mesh_build_lod(ue_PyUObject *self, PyObject * args, PyO for (int32 i = 0; i < wedges.Num(); i += 3) { +#if ENGINE_MINOR_VERSION > 20 + SkeletalMeshImportData::FMeshFace face; +#else FMeshFace face; +#endif face.iWedge[0] = i; face.iWedge[1] = i + 1; face.iWedge[2] = i + 2; diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyStaticMesh.cpp b/Source/UnrealEnginePython/Private/UObject/UEPyStaticMesh.cpp index deb05477e..eaf1a1cf8 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyStaticMesh.cpp +++ b/Source/UnrealEnginePython/Private/UObject/UEPyStaticMesh.cpp @@ -1,11 +1,27 @@ #include "UEPyStaticMesh.h" +#include "Engine/StaticMesh.h" +PyObject *py_ue_static_mesh_get_bounds(ue_PyUObject *self, PyObject * args) +{ + ue_py_check(self); + UStaticMesh *mesh = ue_py_check_type(self); + if (!mesh) + return PyErr_Format(PyExc_Exception, "uobject is not a UStaticMesh"); + + FBoxSphereBounds bounds = mesh->GetBounds(); + UScriptStruct *u_struct = FindObject(ANY_PACKAGE, UTF8_TO_TCHAR("BoxSphereBounds")); + if (!u_struct) + { + return PyErr_Format(PyExc_Exception, "unable to get BoxSphereBounds struct"); + } + return py_ue_new_owned_uscriptstruct(u_struct, (uint8 *)&bounds); +} #if WITH_EDITOR -#include "Engine/StaticMesh.h" #include "Wrappers/UEPyFRawMesh.h" #include "Editor/UnrealEd/Private/GeomFitUtils.h" +#include "FbxMeshUtils.h" static PyObject *generate_kdop(ue_PyUObject *self, const FVector *directions, uint32 num_directions) { @@ -116,4 +132,24 @@ PyObject *py_ue_static_mesh_get_raw_mesh(ue_PyUObject *self, PyObject * args) return py_ue_new_fraw_mesh(raw_mesh); } -#endif \ No newline at end of file +PyObject *py_ue_static_mesh_import_lod(ue_PyUObject *self, PyObject * args) +{ + ue_py_check(self); + + char *filename; + int lod_level; + if (!PyArg_ParseTuple(args, "si:static_mesh_import_lod", &filename, &lod_level)) + return nullptr; + + UStaticMesh *mesh = ue_py_check_type(self); + if (!mesh) + return PyErr_Format(PyExc_Exception, "uobject is not a UStaticMesh"); + + if (FbxMeshUtils::ImportStaticMeshLOD(mesh, FString(UTF8_TO_TCHAR(filename)), lod_level)) + { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +#endif diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyStaticMesh.h b/Source/UnrealEnginePython/Private/UObject/UEPyStaticMesh.h index 219d68761..46a8c76be 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyStaticMesh.h +++ b/Source/UnrealEnginePython/Private/UObject/UEPyStaticMesh.h @@ -4,6 +4,8 @@ #include "UEPyModule.h" +PyObject *py_ue_static_mesh_get_bounds(ue_PyUObject *self, PyObject * args); + #if WITH_EDITOR PyObject *py_ue_static_mesh_build(ue_PyUObject *, PyObject *); PyObject *py_ue_static_mesh_create_body_setup(ue_PyUObject *, PyObject *); @@ -14,4 +16,5 @@ PyObject *py_ue_static_mesh_generate_kdop10y(ue_PyUObject *, PyObject *); PyObject *py_ue_static_mesh_generate_kdop10z(ue_PyUObject *, PyObject *); PyObject *py_ue_static_mesh_generate_kdop18(ue_PyUObject *, PyObject *); PyObject *py_ue_static_mesh_generate_kdop26(ue_PyUObject *, PyObject *); -#endif \ No newline at end of file +PyObject *py_ue_static_mesh_import_lod(ue_PyUObject *, PyObject *); +#endif diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyTexture.cpp b/Source/UnrealEnginePython/Private/UObject/UEPyTexture.cpp index b5172b8ff..44f53890c 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyTexture.cpp +++ b/Source/UnrealEnginePython/Private/UObject/UEPyTexture.cpp @@ -44,6 +44,20 @@ PyObject *py_ue_texture_get_height(ue_PyUObject *self, PyObject * args) return PyLong_FromLong(texture->GetSizeY()); } +PyObject *py_ue_texture_has_alpha_channel(ue_PyUObject *self, PyObject * args) +{ + + ue_py_check(self); + + UTexture2D *texture = ue_py_check_type(self); + if (!texture) + return PyErr_Format(PyExc_Exception, "object is not a Texture"); + + if (texture->HasAlphaChannel()) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + PyObject *py_ue_texture_get_data(ue_PyUObject *self, PyObject * args) { @@ -112,14 +126,23 @@ PyObject *py_ue_texture_set_source_data(ue_PyUObject *self, PyObject * args) UTexture2D *tex = ue_py_check_type(self); if (!tex) + { + PyBuffer_Release(&py_buf); return PyErr_Format(PyExc_Exception, "object is not a Texture2D"); + } if (!py_buf.buf) + { + PyBuffer_Release(&py_buf); return PyErr_Format(PyExc_Exception, "invalid data"); + } if (mipmap >= tex->GetNumMips()) + { + PyBuffer_Release(&py_buf); return PyErr_Format(PyExc_Exception, "invalid mipmap id"); + } int32 wanted_len = py_buf.len; int32 len = tex->Source.GetSizeX() * tex->Source.GetSizeY() * 4; @@ -134,6 +157,8 @@ PyObject *py_ue_texture_set_source_data(ue_PyUObject *self, PyObject * args) FMemory::Memcpy((void *)blob, py_buf.buf, wanted_len); + PyBuffer_Release(&py_buf); + tex->Source.UnlockMip(mipmap); Py_BEGIN_ALLOW_THREADS; tex->MarkPackageDirty(); @@ -200,27 +225,34 @@ PyObject *py_ue_render_target_get_data_to_buffer(ue_PyUObject *self, PyObject * UTextureRenderTarget2D *tex = ue_py_check_type(self); if (!tex) + { + PyBuffer_Release(&py_buf); return PyErr_Format(PyExc_Exception, "object is not a TextureRenderTarget"); + } FTextureRenderTarget2DResource *resource = (FTextureRenderTarget2DResource *)tex->Resource; if (!resource) { + PyBuffer_Release(&py_buf); return PyErr_Format(PyExc_Exception, "cannot get render target resource"); } Py_ssize_t data_len = (Py_ssize_t)(tex->GetSurfaceWidth() * 4 * tex->GetSurfaceHeight()); if (py_buf.len < data_len) { + PyBuffer_Release(&py_buf); return PyErr_Format(PyExc_Exception, "buffer is not big enough"); } TArray pixels; if (!resource->ReadPixels(pixels)) { + PyBuffer_Release(&py_buf); return PyErr_Format(PyExc_Exception, "unable to read pixels"); } FMemory::Memcpy(py_buf.buf, pixels.GetData(), data_len); + PyBuffer_Release(&py_buf); Py_RETURN_NONE; } @@ -239,14 +271,23 @@ PyObject *py_ue_texture_set_data(ue_PyUObject *self, PyObject * args) UTexture2D *tex = ue_py_check_type(self); if (!tex) + { + PyBuffer_Release(&py_buf); return PyErr_Format(PyExc_Exception, "object is not a Texture2D"); + } if (!py_buf.buf) + { + PyBuffer_Release(&py_buf); return PyErr_Format(PyExc_Exception, "invalid data"); + } if (mipmap >= tex->GetNumMips()) + { + PyBuffer_Release(&py_buf); return PyErr_Format(PyExc_Exception, "invalid mipmap id"); + } char *blob = (char*)tex->PlatformData->Mips[mipmap].BulkData.Lock(LOCK_READ_WRITE); int32 len = tex->PlatformData->Mips[mipmap].BulkData.GetBulkDataSize(); @@ -258,6 +299,9 @@ PyObject *py_ue_texture_set_data(ue_PyUObject *self, PyObject * args) wanted_len = len; } FMemory::Memcpy(blob, py_buf.buf, wanted_len); + + PyBuffer_Release(&py_buf); + tex->PlatformData->Mips[mipmap].BulkData.Unlock(); Py_BEGIN_ALLOW_THREADS; @@ -295,6 +339,8 @@ PyObject *py_unreal_engine_compress_image_array(PyObject * self, PyObject * args colors.Add(FColor(buf[i], buf[1 + 1], buf[i + 2], buf[i + 3])); } + PyBuffer_Release(&py_buf); + TArray output; Py_BEGIN_ALLOW_THREADS; @@ -397,6 +443,7 @@ PyObject *py_unreal_engine_create_texture(PyObject * self, PyObject * args) u_package = ue_py_check_type(py_package); if (!u_package) { + PyBuffer_Release(&py_buf); return PyErr_Format(PyExc_Exception, "argument is not a UPackage"); } } @@ -412,6 +459,8 @@ PyObject *py_unreal_engine_create_texture(PyObject * self, PyObject * args) FMemory::Memcpy(colors.GetData(), py_buf.buf, wanted_len); + PyBuffer_Release(&py_buf); + UTexture2D *texture = FImageUtils::CreateTexture2D(width, height, colors, u_package, UTF8_TO_TCHAR(name), RF_Public | RF_Standalone, params); if (!texture) return PyErr_Format(PyExc_Exception, "unable to create texture"); diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyTexture.h b/Source/UnrealEnginePython/Private/UObject/UEPyTexture.h index e14559b3d..54b9f4830 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyTexture.h +++ b/Source/UnrealEnginePython/Private/UObject/UEPyTexture.h @@ -12,6 +12,8 @@ PyObject *py_ue_texture_set_data(ue_PyUObject *, PyObject *); PyObject *py_ue_texture_get_width(ue_PyUObject *, PyObject *); PyObject *py_ue_texture_get_height(ue_PyUObject *, PyObject *); +PyObject *py_ue_texture_has_alpha_channel(ue_PyUObject *, PyObject *); + PyObject *py_unreal_engine_compress_image_array(PyObject *, PyObject *); PyObject *py_unreal_engine_create_checkerboard_texture(PyObject *, PyObject *); diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyWorld.cpp b/Source/UnrealEnginePython/Private/UObject/UEPyWorld.cpp index 0987572ca..19a31299b 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyWorld.cpp +++ b/Source/UnrealEnginePython/Private/UObject/UEPyWorld.cpp @@ -43,7 +43,11 @@ PyObject *py_ue_quit_game(ue_PyUObject *self, PyObject * args) if (!controller) return PyErr_Format(PyExc_Exception, "unable to retrieve the first controller"); +#if ENGINE_MINOR_VERSION > 20 + UKismetSystemLibrary::QuitGame(world, controller, EQuitPreference::Quit, false); +#else UKismetSystemLibrary::QuitGame(world, controller, EQuitPreference::Quit); +#endif Py_RETURN_NONE; } @@ -312,13 +316,30 @@ PyObject *py_ue_set_current_level(ue_PyUObject *self, PyObject * args) if (!level) return PyErr_Format(PyExc_Exception, "argument is not a ULevel"); +#if WITH_EDITOR || ENGINE_MINOR_VERSION < 22 + if (world->SetCurrentLevel(level)) Py_RETURN_TRUE; +#endif Py_RETURN_FALSE; } #if WITH_EDITOR +PyObject *py_ue_get_level_script_blueprint(ue_PyUObject *self, PyObject * args) +{ + + ue_py_check(self); + + ULevel *level = ue_py_check_type(self); + if (!level) + { + return PyErr_Format(PyExc_Exception, "uobject is not a ULevel"); + } + + Py_RETURN_UOBJECT((UObject*)level->GetLevelScriptBlueprint()); +} + PyObject *py_ue_world_create_folder(ue_PyUObject *self, PyObject * args) { diff --git a/Source/UnrealEnginePython/Private/UObject/UEPyWorld.h b/Source/UnrealEnginePython/Private/UObject/UEPyWorld.h index af2c40665..16f07ae7e 100644 --- a/Source/UnrealEnginePython/Private/UObject/UEPyWorld.h +++ b/Source/UnrealEnginePython/Private/UObject/UEPyWorld.h @@ -35,5 +35,6 @@ PyObject *py_ue_world_create_folder(ue_PyUObject *, PyObject *); PyObject *py_ue_world_delete_folder(ue_PyUObject *, PyObject *); PyObject *py_ue_world_rename_folder(ue_PyUObject *, PyObject *); PyObject *py_ue_world_folders(ue_PyUObject *, PyObject *); +PyObject *py_ue_get_level_script_blueprint(ue_PyUObject *, PyObject *); #endif diff --git a/Source/UnrealEnginePython/Private/UnrealEnginePython.cpp b/Source/UnrealEnginePython/Private/UnrealEnginePython.cpp index 8904bb878..211124849 100644 --- a/Source/UnrealEnginePython/Private/UnrealEnginePython.cpp +++ b/Source/UnrealEnginePython/Private/UnrealEnginePython.cpp @@ -34,9 +34,14 @@ const char *ue4_module_options = "linux_global_symbols"; #include "Runtime/Core/Public/Misc/CommandLine.h" #include "Runtime/Core/Public/Misc/ConfigCacheIni.h" #include "Runtime/Core/Public/GenericPlatform/GenericPlatformFile.h" +#include "Runtime/Core/Public/GenericPlatform/GenericPlatformMisc.h" #include "Runtime/Core/Public/HAL/FileManagerGeneric.h" +#if PLATFORM_WINDOWS +#include +#endif + #if PLATFORM_ANDROID #include "Android/AndroidJNI.h" #include "Android/AndroidApplication.h" @@ -116,7 +121,11 @@ void FUnrealEnginePythonModule::UESetupPythonInterpreter(bool verbose) for (int32 i = 0; i < Args.Num(); i++) { #if PY_MAJOR_VERSION >= 3 + #if ENGINE_MINOR_VERSION >= 20 + argv[i] = (wchar_t *)(TCHAR_TO_WCHAR(*Args[i])); + #else argv[i] = (wchar_t *)(*Args[i]); + #endif #else argv[i] = TCHAR_TO_UTF8(*Args[i]); #endif @@ -262,6 +271,8 @@ void FUnrealEnginePythonModule::StartupModule() Py_SetPythonHome(home); } + TArray ImportModules; + FString IniValue; if (GConfig->GetString(UTF8_TO_TCHAR("Python"), UTF8_TO_TCHAR("ProgramName"), IniValue, GEngineIni)) { @@ -316,6 +327,12 @@ void FUnrealEnginePythonModule::StartupModule() ZipPath = FPaths::Combine(*PROJECT_CONTENT_DIR, *IniValue); } + if (GConfig->GetString(UTF8_TO_TCHAR("Python"), UTF8_TO_TCHAR("ImportModules"), IniValue, GEngineIni)) + { + const TCHAR* separators[] = { TEXT(" "), TEXT(";"), TEXT(",") }; + IniValue.ParseIntoArray(ImportModules, separators, 3); + } + FString ProjectScriptsPath = FPaths::Combine(*PROJECT_CONTENT_DIR, UTF8_TO_TCHAR("Scripts")); if (!FPaths::DirectoryExists(ProjectScriptsPath)) { @@ -354,7 +371,11 @@ void FUnrealEnginePythonModule::StartupModule() const int32 MaxPathVarLen = 32768; FString OrigPathVar = FString::ChrN(MaxPathVarLen, TEXT('\0')); +#if ENGINE_MINOR_VERSION >= 21 + OrigPathVar = FPlatformMisc::GetEnvironmentVariable(TEXT("PATH")); +#else FPlatformMisc::GetEnvironmentVariable(TEXT("PATH"), OrigPathVar.GetCharArray().GetData(), MaxPathVarLen); +#endif // Get the current path and remove elements with python in them, we don't want any conflicts const TCHAR* PathDelimiter = FPlatformMisc::GetPathVarDelimiter(); @@ -439,6 +460,22 @@ void FUnrealEnginePythonModule::StartupModule() Py_Initialize(); +#if PLATFORM_WINDOWS + // Restore stdio state after Py_Initialize set it to O_BINARY, otherwise + // everything that the engine will output is going to be encoded in UTF-16. + // The behaviour is described here: https://bugs.python.org/issue16587 + _setmode(_fileno(stdin), O_TEXT); + _setmode(_fileno(stdout), O_TEXT); + _setmode(_fileno(stderr), O_TEXT); + + // Also restore the user-requested UTF-8 flag if relevant (behaviour copied + // from LaunchEngineLoop.cpp). + if (FParse::Param(FCommandLine::Get(), TEXT("UTF8Output"))) + { + FPlatformMisc::SetUTF8Output(); + } +#endif + PyEval_InitThreads(); #if WITH_EDITOR @@ -480,6 +517,19 @@ void FUnrealEnginePythonModule::StartupModule() #endif } + + for (FString ImportModule : ImportModules) + { + if (PyImport_ImportModule(TCHAR_TO_UTF8(*ImportModule))) + { + UE_LOG(LogPython, Log, TEXT("%s Python module successfully imported"), *ImportModule); + } + else + { + unreal_engine_py_log_error(); + } + } + // release the GIL PyThreadState *UEPyGlobalState = PyEval_SaveThread(); } diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyESlateEnums.cpp b/Source/UnrealEnginePython/Private/Wrappers/UEPyESlateEnums.cpp index 4352312ba..7ffa2d05e 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyESlateEnums.cpp +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyESlateEnums.cpp @@ -80,7 +80,11 @@ void ue_python_init_eslate_enums(PyObject *ue_module) }; #if ENGINE_MINOR_VERSION > 15 +#if ENGINE_MINOR_VERSION >= 23 +#define ADD_NATIVE_ENUM(EnumType, EnumVal) add_native_enum(#EnumType "." #EnumVal, (uint8)EnumType::EnumVal) +#else #define ADD_NATIVE_ENUM(EnumType, EnumVal) add_native_enum(#EnumType "." #EnumVal, (uint8)EnumType::Type::EnumVal) +#endif ADD_NATIVE_ENUM(EUserInterfaceActionType, None); ADD_NATIVE_ENUM(EUserInterfaceActionType, Button); ADD_NATIVE_ENUM(EUserInterfaceActionType, ToggleButton); diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFARFilter.cpp b/Source/UnrealEnginePython/Private/Wrappers/UEPyFARFilter.cpp index 7d039f65c..bbdc82b2d 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFARFilter.cpp +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFARFilter.cpp @@ -125,6 +125,8 @@ static int ue_py_farfilter_init(ue_PyFARFilter *self, PyObject *args, PyObject * static void ue_py_farfilter_dealloc(ue_PyFARFilter *self) { + self->filter.~FARFilter(); + Py_XDECREF(self->class_names); Py_XDECREF(self->object_paths); Py_XDECREF(self->package_names); @@ -132,11 +134,8 @@ static void ue_py_farfilter_dealloc(ue_PyFARFilter *self) Py_XDECREF(self->recursive_classes_exclusion_set); Py_XDECREF(self->tags_and_values); -#if PY_MAJOR_VERSION < 3 - self->ob_type->tp_free((PyObject*)self); -#else + Py_TYPE(self)->tp_free((PyObject*)self); -#endif } static PyObject * ue_py_farfilter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) @@ -146,6 +145,9 @@ static PyObject * ue_py_farfilter_new(PyTypeObject *type, PyObject *args, PyObje self = (ue_PyFARFilter *)type->tp_alloc(type, 0); if (self != NULL) { + + new(&self->filter) FARFilter(); + self->class_names = PyList_New(0); if (self->class_names == NULL) { @@ -174,7 +176,7 @@ static PyObject * ue_py_farfilter_new(PyTypeObject *type, PyObject *args, PyObje return NULL; } - self->recursive_classes_exclusion_set = PySet_New(0); + self->recursive_classes_exclusion_set = PySet_New(NULL); if (self->recursive_classes_exclusion_set == NULL) { Py_DECREF(self); @@ -182,26 +184,41 @@ static PyObject * ue_py_farfilter_new(PyTypeObject *type, PyObject *args, PyObje } PyObject *collections = PyImport_ImportModule("collections"); + if (!collections) + { + unreal_engine_py_log_error(); + Py_DECREF(self); + return NULL; + } PyObject *collections_module_dict = PyModule_GetDict(collections); PyObject *defaultdict_cls = PyDict_GetItemString(collections_module_dict, "defaultdict"); - PyObject *main_module = PyImport_ImportModule("__main__"); - PyObject *main_dict = PyModule_GetDict(main_module); - PyObject *builtins_module = PyDict_GetItemString(main_dict, "__builtins__"); + + PyObject *builtins_module = PyImport_ImportModule("builtins"); + if (!builtins_module) + { + unreal_engine_py_log_error(); + Py_DECREF(self); + return NULL; + } PyObject *builtins_dict = PyModule_GetDict(builtins_module); PyObject *set_cls = PyDict_GetItemString(builtins_dict, "set"); + // required because PyTuple_SetItem below will steal a reference + Py_INCREF(set_cls); + PyObject *py_args = PyTuple_New(1); PyTuple_SetItem(py_args, 0, set_cls); PyObject *default_dict = PyObject_CallObject(defaultdict_cls, py_args); - Py_XDECREF(py_args); - - self->tags_and_values = default_dict; - if (self->tags_and_values == NULL) + Py_DECREF(py_args); + if (!default_dict) { + unreal_engine_py_log_error(); Py_DECREF(self); return NULL; } + + self->tags_and_values = default_dict; } return (PyObject *)self; diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFColor.cpp b/Source/UnrealEnginePython/Private/Wrappers/UEPyFColor.cpp index d71188d97..0105dd254 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFColor.cpp +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFColor.cpp @@ -103,7 +103,7 @@ static PyObject *ue_PyFColor_str(ue_PyFColor *self) self->color.R, self->color.G, self->color.B, self->color.A); } -static PyTypeObject ue_PyFColorType = { +PyTypeObject ue_PyFColorType = { PyVarObject_HEAD_INIT(NULL, 0) "unreal_engine.FColor", /* tp_name */ sizeof(ue_PyFColor), /* tp_basicsize */ diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFColor.h b/Source/UnrealEnginePython/Private/Wrappers/UEPyFColor.h index ef75b1832..d3a1013e7 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFColor.h +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFColor.h @@ -11,6 +11,8 @@ typedef struct FColor color; } ue_PyFColor; +extern PyTypeObject ue_PyFColorType; + PyObject *py_ue_new_fcolor(FColor); ue_PyFColor *py_ue_is_fcolor(PyObject *); @@ -18,4 +20,4 @@ void ue_python_init_fcolor(PyObject *); bool py_ue_color_arg(PyObject *, FColor &); -bool py_ue_get_fcolor(PyObject *, FColor &); \ No newline at end of file +bool py_ue_get_fcolor(PyObject *, FColor &); diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFEditorViewportClient.cpp b/Source/UnrealEnginePython/Private/Wrappers/UEPyFEditorViewportClient.cpp index 9e36b459f..43aff5748 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFEditorViewportClient.cpp +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFEditorViewportClient.cpp @@ -37,6 +37,11 @@ static PyObject *py_ue_feditor_viewport_client_get_view_location(ue_PyFEditorVie return py_ue_new_fvector(self->editor_viewport_client->GetViewLocation()); } +static PyObject *py_ue_feditor_viewport_client_get_view_rotation(ue_PyFEditorViewportClient *self, PyObject * args) +{ + return py_ue_new_frotator(self->editor_viewport_client->GetViewRotation()); +} + static PyObject *py_ue_feditor_viewport_client_get_camera_speed(ue_PyFEditorViewportClient *self, PyObject * args) { return PyFloat_FromDouble(self->editor_viewport_client->GetCameraSpeed()); @@ -84,6 +89,15 @@ static PyObject *py_ue_feditor_viewport_client_set_view_location(ue_PyFEditorVie Py_RETURN_NONE; } +static PyObject *py_ue_feditor_viewport_client_set_view_rotation(ue_PyFEditorViewportClient *self, PyObject * args) +{ + FRotator rot; + if (!py_ue_rotator_arg(args, rot)) + return PyErr_Format(PyExc_Exception, "argument is not a FRotator"); + self->editor_viewport_client->SetViewRotation(rot); + Py_RETURN_NONE; +} + static PyObject *py_ue_feditor_viewport_client_set_realtime(ue_PyFEditorViewportClient *self, PyObject * args) { PyObject* bInRealtime; @@ -101,12 +115,14 @@ static PyMethodDef ue_PyFEditorViewportClient_methods[] = { { "tick", (PyCFunction)py_ue_feditor_viewport_client_tick, METH_VARARGS, "" }, { "get_look_at_location", (PyCFunction)py_ue_feditor_viewport_client_get_look_at_location, METH_VARARGS, "" }, { "get_view_location", (PyCFunction)py_ue_feditor_viewport_client_get_view_location, METH_VARARGS, "" }, + { "get_view_rotation", (PyCFunction)py_ue_feditor_viewport_client_get_view_location, METH_VARARGS, "" }, { "get_camera_speed", (PyCFunction)py_ue_feditor_viewport_client_get_camera_speed, METH_VARARGS, "" }, { "get_viewport_dimensions", (PyCFunction)py_ue_feditor_viewport_client_get_viewport_dimensions, METH_VARARGS, "" }, { "is_visible", (PyCFunction)py_ue_feditor_viewport_client_is_visible, METH_VARARGS, "" }, { "get_scene_depth_at_location", (PyCFunction)py_ue_feditor_viewport_client_get_scene_depth_at_location, METH_VARARGS, "" }, { "set_look_at_location", (PyCFunction)py_ue_feditor_viewport_client_set_look_at_location, METH_VARARGS, "" }, { "set_view_location", (PyCFunction)py_ue_feditor_viewport_client_set_view_location, METH_VARARGS, "" }, + { "set_view_rotation", (PyCFunction)py_ue_feditor_viewport_client_set_view_rotation, METH_VARARGS, "" }, { "set_realtime", (PyCFunction)py_ue_feditor_viewport_client_set_realtime, METH_VARARGS, "" }, { nullptr } /* Sentinel */ }; diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFFoliageInstance.cpp b/Source/UnrealEnginePython/Private/Wrappers/UEPyFFoliageInstance.cpp index f777f3172..2ed7ffa2d 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFFoliageInstance.cpp +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFFoliageInstance.cpp @@ -2,51 +2,221 @@ #include "Wrappers/UEPyFVector.h" #include "Wrappers/UEPyFRotator.h" +#include "Wrappers/UEPyFTransform.h" +#include "Components/ActorComponent.h" +#include "Runtime/Foliage/Public/InstancedFoliageActor.h" -static PyObject *ue_PyFFoliageInstance_str(ue_PyFFoliageInstance *self) +#if WITH_EDITOR + +#define get_instance(x) FFoliageInstance *instance = get_foliage_instance(x);\ + if (!instance)\ + return nullptr; + +#define set_instance(x) FFoliageInstance *instance = get_foliage_instance(x);\ + if (!instance)\ + return -1; + + + +static FFoliageInstance* get_foliage_instance(ue_PyFFoliageInstance* self) +{ + if (!self->foliage_actor.IsValid()) + { + PyErr_SetString(PyExc_Exception, "AInstancedFoliageActor is in invalid state"); + return nullptr; + } + + if (!self->foliage_type.IsValid()) + { + PyErr_SetString(PyExc_Exception, "UFoliageType is in invalid state"); + return nullptr; + } + +#if ENGINE_MINOR_VERSION >= 23 + FFoliageInfo& info = self->foliage_actor->FoliageInfos[self->foliage_type.Get()].Get(); +#else + + FFoliageMeshInfo& info = self->foliage_actor->FoliageMeshes[self->foliage_type.Get()].Get(); +#endif + + if (self->instance_id >= 0 && self->instance_id < info.Instances.Num()) + { + return &info.Instances[self->instance_id]; + } + + PyErr_SetString(PyExc_Exception, "invalid foliage instance id"); + return nullptr; +} + +static PyObject* ue_PyFFoliageInstance_str(ue_PyFFoliageInstance* self) +{ + return PyUnicode_FromFormat("", + self->instance_id); +} + + +static PyObject* py_ue_ffoliage_instance_get_location(ue_PyFFoliageInstance* self, void* closure) +{ + get_instance(self); + return py_ue_new_fvector(instance->Location); +} + +static int py_ue_ffoliage_instance_set_location(ue_PyFFoliageInstance* self, PyObject* value, void* closure) +{ + set_instance(self); + if (value) + { + ue_PyFVector* vec = py_ue_is_fvector(value); + if (vec) + { + TArray instances; + instances.Add(self->instance_id); +#if ENGINE_MINOR_VERSION >= 23 + FFoliageInfo& info = self->foliage_actor->FoliageInfos[self->foliage_type.Get()].Get(); +#else + FFoliageMeshInfo& info = self->foliage_actor->FoliageMeshes[self->foliage_type.Get()].Get(); +#endif + info.PreMoveInstances(self->foliage_actor.Get(), instances); + instance->Location = vec->vec; + info.PostMoveInstances(self->foliage_actor.Get(), instances); + return 0; + } + } + PyErr_SetString(PyExc_TypeError, "value is not an FVector"); + return -1; +} + +static int py_ue_ffoliage_instance_set_rotation(ue_PyFFoliageInstance* self, PyObject* value, void* closure) +{ + set_instance(self); + if (value) + { + ue_PyFRotator* rot = py_ue_is_frotator(value); + if (rot) + { + TArray instances; + instances.Add(self->instance_id); +#if ENGINE_MINOR_VERSION >= 23 + FFoliageInfo& info = self->foliage_actor->FoliageInfos[self->foliage_type.Get()].Get(); +#else + FFoliageMeshInfo& info = self->foliage_actor->FoliageMeshes[self->foliage_type.Get()].Get(); +#endif + info.PreMoveInstances(self->foliage_actor.Get(), instances); + instance->Rotation = rot->rot; + info.PostMoveInstances(self->foliage_actor.Get(), instances); + return 0; + } + } + PyErr_SetString(PyExc_TypeError, "value is not an FRotator"); + return -1; +} + +static PyObject* py_ue_ffoliage_instance_get_draw_scale3d(ue_PyFFoliageInstance* self, void* closure) { - return PyUnicode_FromFormat("", - &self->instance); + get_instance(self); + return py_ue_new_fvector(instance->DrawScale3D); } -static PyObject *py_ue_ffoliage_instance_get_location(ue_PyFFoliageInstance *self, void *closure) +static PyObject* py_ue_ffoliage_instance_get_flags(ue_PyFFoliageInstance* self, void* closure) { - return py_ue_new_fvector(self->instance.Location); + get_instance(self); + return PyLong_FromUnsignedLong(instance->Flags); } -static PyObject *py_ue_ffoliage_instance_get_draw_scale3d(ue_PyFFoliageInstance *self, void *closure) +static PyObject* py_ue_ffoliage_instance_get_pre_align_rotation(ue_PyFFoliageInstance* self, void* closure) { - return py_ue_new_fvector(self->instance.DrawScale3D); + get_instance(self); + return py_ue_new_frotator(instance->PreAlignRotation); } -static PyObject *py_ue_ffoliage_instance_get_flags(ue_PyFFoliageInstance *self, void *closure) +static PyObject* py_ue_ffoliage_instance_get_rotation(ue_PyFFoliageInstance* self, void* closure) { - return PyLong_FromUnsignedLong(self->instance.Flags); + get_instance(self); + return py_ue_new_frotator(instance->Rotation); } -static PyObject *py_ue_ffoliage_instance_get_pre_align_rotation(ue_PyFFoliageInstance *self, void *closure) +static PyObject* py_ue_ffoliage_instance_get_zoffset(ue_PyFFoliageInstance* self, void* closure) { - return py_ue_new_frotator(self->instance.PreAlignRotation); + get_instance(self); + return PyFloat_FromDouble(instance->ZOffset); } -static PyObject *py_ue_ffoliage_instance_get_rotation(ue_PyFFoliageInstance *self, void *closure) +static PyObject* py_ue_ffoliage_instance_get_procedural_guid(ue_PyFFoliageInstance* self, void* closure) { - return py_ue_new_frotator(self->instance.Rotation); + get_instance(self); + FGuid guid = instance->ProceduralGuid; + return py_ue_new_owned_uscriptstruct(FindObject(ANY_PACKAGE, UTF8_TO_TCHAR((char*)"Guid")), (uint8*)& guid); } -static PyObject *py_ue_ffoliage_instance_get_zoffset(ue_PyFFoliageInstance *self, void *closure) +static PyObject* py_ue_ffoliage_instance_get_base_id(ue_PyFFoliageInstance* self, void* closure) { - return PyFloat_FromDouble(self->instance.ZOffset); + get_instance(self); + return PyLong_FromLong(instance->BaseId); } +static PyObject* py_ue_ffoliage_instance_get_instance_id(ue_PyFFoliageInstance* self, void* closure) +{ + return PyLong_FromLong(self->instance_id); +} + +#if ENGINE_MINOR_VERSION > 19 +static PyObject * py_ue_ffoliage_instance_get_base_component(ue_PyFFoliageInstance * self, void* closure) +{ + get_instance(self); + Py_RETURN_UOBJECT(instance->BaseComponent); +} +#endif + + static PyGetSetDef ue_PyFFoliageInstance_getseters[] = { - { (char *)"location", (getter)py_ue_ffoliage_instance_get_location, nullptr, (char *)"", NULL }, - { (char *)"draw_scale3d", (getter)py_ue_ffoliage_instance_get_draw_scale3d, nullptr, (char *)"", NULL }, - { (char *)"flags", (getter)py_ue_ffoliage_instance_get_flags, nullptr, (char *)"", NULL }, - { (char *)"pre_align_rotation", (getter)py_ue_ffoliage_instance_get_pre_align_rotation, nullptr, (char *)"", NULL }, - { (char *)"rotation", (getter)py_ue_ffoliage_instance_get_rotation, nullptr, (char *)"", NULL }, - { (char *)"zoffset", (getter)py_ue_ffoliage_instance_get_zoffset, nullptr, (char *)"", NULL }, + { (char*)"location", (getter)py_ue_ffoliage_instance_get_location, (setter)py_ue_ffoliage_instance_set_location, (char*)"", NULL }, + { (char*)"draw_scale3d", (getter)py_ue_ffoliage_instance_get_draw_scale3d, nullptr, (char*)"", NULL }, + { (char*)"flags", (getter)py_ue_ffoliage_instance_get_flags, nullptr, (char*)"", NULL }, + { (char*)"pre_align_rotation", (getter)py_ue_ffoliage_instance_get_pre_align_rotation, nullptr, (char*)"", NULL }, + { (char*)"rotation", (getter)py_ue_ffoliage_instance_get_rotation, (setter)py_ue_ffoliage_instance_set_rotation, (char*)"", NULL }, + { (char*)"zoffset", (getter)py_ue_ffoliage_instance_get_zoffset, nullptr, (char*)"", NULL }, + { (char*)"procedural_guid", (getter)py_ue_ffoliage_instance_get_procedural_guid, nullptr, (char*)"", NULL }, + { (char*)"guid", (getter)py_ue_ffoliage_instance_get_procedural_guid, nullptr, (char*)"", NULL }, + { (char*)"base_id", (getter)py_ue_ffoliage_instance_get_base_id, nullptr, (char*)"", NULL }, + { (char*)"instance_id", (getter)py_ue_ffoliage_instance_get_instance_id, nullptr, (char*)"", NULL }, +#if ENGINE_MINOR_VERSION > 19 + { (char*)"base_component", (getter)py_ue_ffoliage_instance_get_base_component, nullptr, (char*)"", NULL }, +#endif + { NULL } /* Sentinel */ +}; + +static PyObject* py_ue_ffoliage_instance_get_instance_world_transform(ue_PyFFoliageInstance* self, PyObject* args) +{ + get_instance(self); + return py_ue_new_ftransform(instance->GetInstanceWorldTransform()); +} + +static PyObject* py_ue_ffoliage_instance_align_to_normal(ue_PyFFoliageInstance* self, PyObject* args) +{ + get_instance(self); + + PyObject* py_vec; + float align_max_angle = 0; + + if (!PyArg_ParseTuple(args, "O|f:align_to_normal", &py_vec, &align_max_angle)) + return nullptr; + + ue_PyFVector* vec = py_ue_is_fvector(py_vec); + if (!vec) + { + return PyErr_Format(PyExc_Exception, "argument is not an FVector"); + } + + instance->AlignToNormal(vec->vec, align_max_angle); + + Py_RETURN_NONE; +} + + +static PyMethodDef ue_PyFFoliageInstance_methods[] = { + { "get_instance_world_transform", (PyCFunction)py_ue_ffoliage_instance_get_instance_world_transform, METH_VARARGS, "" }, + { "align_to_normal", (PyCFunction)py_ue_ffoliage_instance_align_to_normal, METH_VARARGS, "" }, { NULL } /* Sentinel */ }; @@ -82,12 +252,12 @@ static PyTypeObject ue_PyFFoliageInstanceType = { 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - 0, /* tp_methods */ + ue_PyFFoliageInstance_methods, /* tp_methods */ 0, ue_PyFFoliageInstance_getseters, }; -void ue_python_init_ffoliage_instance(PyObject *ue_module) +void ue_python_init_ffoliage_instance(PyObject* ue_module) { ue_PyFFoliageInstanceType.tp_new = PyType_GenericNew; @@ -95,12 +265,16 @@ void ue_python_init_ffoliage_instance(PyObject *ue_module) return; Py_INCREF(&ue_PyFFoliageInstanceType); - PyModule_AddObject(ue_module, "FoliageInstance", (PyObject *)&ue_PyFFoliageInstanceType); + PyModule_AddObject(ue_module, "FoliageInstance", (PyObject*)& ue_PyFFoliageInstanceType); } -PyObject *py_ue_new_ffoliage_instance(FFoliageInstance instance) +PyObject* py_ue_new_ffoliage_instance(AInstancedFoliageActor* foliage_actor, UFoliageType* foliage_type, int32 instance_id) { - ue_PyFFoliageInstance *ret = (ue_PyFFoliageInstance *)PyObject_New(ue_PyFFoliageInstance, &ue_PyFFoliageInstanceType); - ret->instance = instance; - return (PyObject *)ret; -} \ No newline at end of file + ue_PyFFoliageInstance* ret = (ue_PyFFoliageInstance*)PyObject_New(ue_PyFFoliageInstance, &ue_PyFFoliageInstanceType); + ret->foliage_actor = TWeakObjectPtr(foliage_actor); + ret->foliage_type = TWeakObjectPtr(foliage_type); + ret->instance_id = instance_id; + return (PyObject*)ret; +} + +#endif \ No newline at end of file diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFFoliageInstance.h b/Source/UnrealEnginePython/Private/Wrappers/UEPyFFoliageInstance.h index 64034333f..ab65fc3c8 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFFoliageInstance.h +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFFoliageInstance.h @@ -1,14 +1,18 @@ #pragma once #include "UnrealEnginePython.h" +#if WITH_EDITOR #include "InstancedFoliage.h" typedef struct { PyObject_HEAD - FFoliageInstance instance; + TWeakObjectPtr foliage_actor; + TWeakObjectPtr foliage_type; + int32 instance_id; } ue_PyFFoliageInstance; void ue_python_init_ffoliage_instance(PyObject *); -PyObject *py_ue_new_ffoliage_instance(FFoliageInstance instance); +PyObject *py_ue_new_ffoliage_instance(AInstancedFoliageActor *foliage_actor, UFoliageType *foliage_type, int32 instance_id); +#endif diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFFrameNumber.cpp b/Source/UnrealEnginePython/Private/Wrappers/UEPyFFrameNumber.cpp index bb939c680..b2dc0a0df 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFFrameNumber.cpp +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFFrameNumber.cpp @@ -10,7 +10,7 @@ static PyObject *ue_PyFFrameNumber_str(ue_PyFFrameNumber *self) static PyTypeObject ue_PyFFrameNumberType = { PyVarObject_HEAD_INIT(NULL, 0) - "unreal_engine.FMFrameNumber", /* tp_name */ + "unreal_engine.FFrameNumber", /* tp_name */ sizeof(ue_PyFFrameNumber), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ @@ -80,4 +80,4 @@ PyObject *py_ue_new_fframe_number(FFrameNumber frame_number) return (PyObject *)ret; } -#endif \ No newline at end of file +#endif diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFLinearColor.cpp b/Source/UnrealEnginePython/Private/Wrappers/UEPyFLinearColor.cpp index 056411506..5ca0eb83a 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFLinearColor.cpp +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFLinearColor.cpp @@ -106,7 +106,7 @@ static PyObject *ue_PyFLinearColor_str(ue_PyFLinearColor *self) PyFloat_FromDouble(self->color.A)); } -static PyTypeObject ue_PyFLinearColorType = { +PyTypeObject ue_PyFLinearColorType = { PyVarObject_HEAD_INIT(NULL, 0) "unreal_engine.FLinearColor", /* tp_name */ sizeof(ue_PyFLinearColor), /* tp_basicsize */ diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFLinearColor.h b/Source/UnrealEnginePython/Private/Wrappers/UEPyFLinearColor.h index 986ecd2aa..2ade8c420 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFLinearColor.h +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFLinearColor.h @@ -11,6 +11,8 @@ typedef struct FLinearColor color; } ue_PyFLinearColor; +extern PyTypeObject ue_PyFLinearColorType; + PyObject *py_ue_new_flinearcolor(FLinearColor); ue_PyFLinearColor *py_ue_is_flinearcolor(PyObject *); @@ -18,4 +20,4 @@ void ue_python_init_flinearcolor(PyObject *); bool py_ue_linearcolor_arg(PyObject *, FLinearColor &); -bool py_ue_get_flinearcolor(PyObject *, FLinearColor &); \ No newline at end of file +bool py_ue_get_flinearcolor(PyObject *, FLinearColor &); diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFQuat.cpp b/Source/UnrealEnginePython/Private/Wrappers/UEPyFQuat.cpp index 74d914848..41faf7383 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFQuat.cpp +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFQuat.cpp @@ -152,7 +152,7 @@ static PyObject *ue_PyFQuat_str(ue_PyFQuat *self) PyFloat_FromDouble(self->quat.X), PyFloat_FromDouble(self->quat.Y), PyFloat_FromDouble(self->quat.Z), PyFloat_FromDouble(self->quat.W)); } -static PyTypeObject ue_PyFQuatType = { +PyTypeObject ue_PyFQuatType = { PyVarObject_HEAD_INIT(NULL, 0) "unreal_engine.FQuat", /* tp_name */ sizeof(ue_PyFQuat), /* tp_basicsize */ diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFQuat.h b/Source/UnrealEnginePython/Private/Wrappers/UEPyFQuat.h index 1a39ce0a8..94bd2cf9e 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFQuat.h +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFQuat.h @@ -11,9 +11,11 @@ typedef struct FQuat quat; } ue_PyFQuat; +extern PyTypeObject ue_PyFQuatType; + PyObject *py_ue_new_fquat(FQuat); ue_PyFQuat *py_ue_is_fquat(PyObject *); void ue_python_init_fquat(PyObject *); -bool py_ue_quat_arg(PyObject *, FQuat &); \ No newline at end of file +bool py_ue_quat_arg(PyObject *, FQuat &); diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFRotator.cpp b/Source/UnrealEnginePython/Private/Wrappers/UEPyFRotator.cpp index edcc35348..3558faa5b 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFRotator.cpp +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFRotator.cpp @@ -93,7 +93,7 @@ static PyObject *ue_PyFRotator_str(ue_PyFRotator *self) PyFloat_FromDouble(self->rot.Roll), PyFloat_FromDouble(self->rot.Pitch), PyFloat_FromDouble(self->rot.Yaw)); } -static PyTypeObject ue_PyFRotatorType = { +PyTypeObject ue_PyFRotatorType = { PyVarObject_HEAD_INIT(NULL, 0) "unreal_engine.FRotator", /* tp_name */ sizeof(ue_PyFRotator), /* tp_basicsize */ diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFRotator.h b/Source/UnrealEnginePython/Private/Wrappers/UEPyFRotator.h index a4ea71a84..2d3f6df4c 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFRotator.h +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFRotator.h @@ -10,9 +10,11 @@ typedef struct { FRotator rot; } ue_PyFRotator; +extern PyTypeObject ue_PyFRotatorType; + PyObject *py_ue_new_frotator(FRotator); ue_PyFRotator *py_ue_is_frotator(PyObject *); void ue_python_init_frotator(PyObject *); -bool py_ue_rotator_arg(PyObject *, FRotator &); \ No newline at end of file +bool py_ue_rotator_arg(PyObject *, FRotator &); diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFTransform.cpp b/Source/UnrealEnginePython/Private/Wrappers/UEPyFTransform.cpp index 11548407a..b3a6b654a 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFTransform.cpp +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFTransform.cpp @@ -220,7 +220,7 @@ static PyObject *ue_PyFTransform_str(ue_PyFTransform *self) } -static PyTypeObject ue_PyFTransformType = { +PyTypeObject ue_PyFTransformType = { PyVarObject_HEAD_INIT(NULL, 0) "unreal_engine.FTransform", /* tp_name */ sizeof(ue_PyFTransform), /* tp_basicsize */ @@ -435,4 +435,4 @@ bool py_ue_transform_arg(PyObject *args, FTransform &t) t.SetRotation(FRotator(pitch, yaw, roll).Quaternion()); t.SetScale3D(FVector(sx, sy, sz)); return true; -} \ No newline at end of file +} diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFTransform.h b/Source/UnrealEnginePython/Private/Wrappers/UEPyFTransform.h index 3cb64312f..86f58e0f9 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFTransform.h +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFTransform.h @@ -10,8 +10,10 @@ typedef struct { FTransform transform; } ue_PyFTransform; +extern PyTypeObject ue_PyFTransformType; + PyObject *py_ue_new_ftransform(FTransform); ue_PyFTransform *py_ue_is_ftransform(PyObject *); void ue_python_init_ftransform(PyObject *); -bool py_ue_transform_arg(PyObject *, FTransform &); \ No newline at end of file +bool py_ue_transform_arg(PyObject *, FTransform &); diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector.cpp b/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector.cpp index 3380d82df..113c3bb62 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector.cpp +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector.cpp @@ -151,7 +151,7 @@ static PyObject *ue_PyFVector_str(ue_PyFVector *self) PyFloat_FromDouble(self->vec.X), PyFloat_FromDouble(self->vec.Y), PyFloat_FromDouble(self->vec.Z)); } -static PyTypeObject ue_PyFVectorType = { +PyTypeObject ue_PyFVectorType = { PyVarObject_HEAD_INIT(NULL, 0) "unreal_engine.FVector", /* tp_name */ sizeof(ue_PyFVector), /* tp_basicsize */ @@ -277,6 +277,24 @@ static PyObject *ue_py_fvector_div(ue_PyFVector *self, PyObject *value) return py_ue_new_fvector(vec); } +static PyObject *ue_py_fvector_floor_div(ue_PyFVector *self, PyObject *value) +{ + FVector vec = self->vec; + if (PyNumber_Check(value)) + { + PyObject *f_value = PyNumber_Float(value); + float f = PyFloat_AsDouble(f_value); + if (f == 0) + return PyErr_Format(PyExc_ZeroDivisionError, "division by zero"); + vec.X = floor(vec.X / f); + vec.Y = floor(vec.Y / f); + vec.Z = floor(vec.Z / f); + Py_DECREF(f_value); + return py_ue_new_fvector(vec); + } + return PyErr_Format(PyExc_TypeError, "value is not numeric"); +} + PyNumberMethods ue_PyFVector_number_methods; static Py_ssize_t ue_py_fvector_seq_length(ue_PyFVector *self) @@ -363,7 +381,8 @@ void ue_python_init_fvector(PyObject *ue_module) ue_PyFVector_number_methods.nb_add = (binaryfunc)ue_py_fvector_add; ue_PyFVector_number_methods.nb_subtract = (binaryfunc)ue_py_fvector_sub; ue_PyFVector_number_methods.nb_multiply = (binaryfunc)ue_py_fvector_mul; - ue_PyFVector_number_methods.nb_divmod = (binaryfunc)ue_py_fvector_div; + ue_PyFVector_number_methods.nb_true_divide = (binaryfunc)ue_py_fvector_div; + ue_PyFVector_number_methods.nb_floor_divide = (binaryfunc)ue_py_fvector_floor_div; memset(&ue_PyFVector_sequence_methods, 0, sizeof(PySequenceMethods)); ue_PyFVectorType.tp_as_sequence = &ue_PyFVector_sequence_methods; diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector.h b/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector.h index e23935943..b7b0853e8 100644 --- a/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector.h +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector.h @@ -11,9 +11,11 @@ typedef struct FVector vec; } ue_PyFVector; +extern PyTypeObject ue_PyFVectorType; + PyObject *py_ue_new_fvector(FVector); ue_PyFVector *py_ue_is_fvector(PyObject *); void ue_python_init_fvector(PyObject *); -bool py_ue_vector_arg(PyObject *, FVector &); \ No newline at end of file +bool py_ue_vector_arg(PyObject *, FVector &); diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector2D.cpp b/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector2D.cpp new file mode 100644 index 000000000..20855cf56 --- /dev/null +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector2D.cpp @@ -0,0 +1,368 @@ +#include "UEPyFVector2D.h" + +static PyObject *py_ue_fvector2d_length(ue_PyFVector2D *self, PyObject * args) +{ + return PyFloat_FromDouble(self->vec.Size()); +} + +static PyObject *py_ue_fvector2d_length_squared(ue_PyFVector2D *self, PyObject * args) +{ + return PyFloat_FromDouble(self->vec.SizeSquared()); +} + +static PyObject *py_ue_fvector2d_normalized(ue_PyFVector2D *self, PyObject * args) +{ + FVector2D vec = self->vec; + vec.Normalize(); + return py_ue_new_fvector2d(vec); +} + +static PyObject *py_ue_fvector2d_dot(ue_PyFVector2D *self, PyObject * args) +{ + PyObject *py_obj; + if (!PyArg_ParseTuple(args, "O:dot", &py_obj)) + return NULL; + ue_PyFVector2D *py_vec = py_ue_is_fvector2d(py_obj); + if (!py_vec) + return PyErr_Format(PyExc_TypeError, "argument is not a FVector2D"); + return PyFloat_FromDouble(FVector2D::DotProduct(self->vec, py_vec->vec)); +} + +static PyObject *py_ue_fvector2d_cross(ue_PyFVector2D *self, PyObject * args) +{ + PyObject *py_obj; + if (!PyArg_ParseTuple(args, "O:cross", &py_obj)) + return NULL; + ue_PyFVector2D *py_vec = py_ue_is_fvector2d(py_obj); + if (!py_vec) + return PyErr_Format(PyExc_TypeError, "argument is not a FVector2D"); + return PyFloat_FromDouble(FVector2D::CrossProduct(self->vec, py_vec->vec)); +} + +static PyMethodDef ue_PyFVector2D_methods[] = { + + { "length", (PyCFunction)py_ue_fvector2d_length, METH_VARARGS, "" }, + { "size", (PyCFunction)py_ue_fvector2d_length, METH_VARARGS, "" }, + { "size_squared", (PyCFunction)py_ue_fvector2d_length_squared, METH_VARARGS, "" }, + { "length_squared", (PyCFunction)py_ue_fvector2d_length_squared, METH_VARARGS, "" }, + { "normalized", (PyCFunction)py_ue_fvector2d_normalized, METH_VARARGS, "" }, + { "dot", (PyCFunction)py_ue_fvector2d_dot, METH_VARARGS, "" }, + { "cross", (PyCFunction)py_ue_fvector2d_cross, METH_VARARGS, "" }, + { NULL } /* Sentinel */ +}; + +static PyObject *py_ue_fvector2d_get_x(ue_PyFVector2D *self, void *closure) +{ + return PyFloat_FromDouble(self->vec.X); +} + +static int py_ue_fvector2d_set_x(ue_PyFVector2D *self, PyObject *value, void *closure) +{ + if (value && PyNumber_Check(value)) + { + PyObject *f_value = PyNumber_Float(value); + self->vec.X = PyFloat_AsDouble(f_value); + Py_DECREF(f_value); + return 0; + } + PyErr_SetString(PyExc_TypeError, "value is not numeric"); + return -1; +} + +static PyObject *py_ue_fvector2d_get_y(ue_PyFVector2D *self, void *closure) +{ + return PyFloat_FromDouble(self->vec.Y); +} + +static int py_ue_fvector2d_set_y(ue_PyFVector2D *self, PyObject *value, void *closure) +{ + if (value && PyNumber_Check(value)) + { + PyObject *f_value = PyNumber_Float(value); + self->vec.Y = PyFloat_AsDouble(f_value); + Py_DECREF(f_value); + return 0; + } + PyErr_SetString(PyExc_TypeError, "value is not numeric"); + return -1; +} + + +static PyGetSetDef ue_PyFVector2D_getseters[] = { + {(char *) "x", (getter)py_ue_fvector2d_get_x, (setter)py_ue_fvector2d_set_x, (char *)"", NULL }, + {(char *) "y", (getter)py_ue_fvector2d_get_y, (setter)py_ue_fvector2d_set_y, (char *)"", NULL }, + { NULL } /* Sentinel */ +}; + +static PyObject *ue_PyFVector2D_str(ue_PyFVector2D *self) +{ + return PyUnicode_FromFormat("", + PyFloat_FromDouble(self->vec.X), PyFloat_FromDouble(self->vec.Y)); +} + +PyTypeObject ue_PyFVector2DType = { + PyVarObject_HEAD_INIT(NULL, 0) + "unreal_engine.FVector2D", /* tp_name */ + sizeof(ue_PyFVector2D), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + (reprfunc)ue_PyFVector2D_str, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES, /* tp_flags */ +#else + Py_TPFLAGS_DEFAULT, /* tp_flags */ +#endif + "Unreal Engine FVector2D", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + ue_PyFVector2D_methods, /* tp_methods */ + 0, + ue_PyFVector2D_getseters, +}; + + +static PyObject *ue_py_fvector2d_add(ue_PyFVector2D *self, PyObject *value) +{ + FVector2D vec = self->vec; + ue_PyFVector2D *py_vec = py_ue_is_fvector2d(value); + if (py_vec) + { + vec += py_vec->vec; + } + else if (PyNumber_Check(value)) + { + PyObject *f_value = PyNumber_Float(value); + float f = PyFloat_AsDouble(f_value); + vec.X += f; + vec.Y += f; + Py_DECREF(f_value); + } + return py_ue_new_fvector2d(vec); +} + +static PyObject *ue_py_fvector2d_sub(ue_PyFVector2D *self, PyObject *value) +{ + FVector2D vec = self->vec; + ue_PyFVector2D *py_vec = py_ue_is_fvector2d(value); + if (py_vec) + { + vec -= py_vec->vec; + } + else if (PyNumber_Check(value)) + { + PyObject *f_value = PyNumber_Float(value); + float f = PyFloat_AsDouble(f_value); + vec.X -= f; + vec.Y -= f; + Py_DECREF(f_value); + } + return py_ue_new_fvector2d(vec); +} + +static PyObject *ue_py_fvector2d_mul(ue_PyFVector2D *self, PyObject *value) +{ + FVector2D vec = self->vec; + ue_PyFVector2D *py_vec = py_ue_is_fvector2d(value); + if (py_vec) + { + vec *= py_vec->vec; + } + else if (PyNumber_Check(value)) + { + PyObject *f_value = PyNumber_Float(value); + float f = PyFloat_AsDouble(f_value); + vec *= f; + Py_DECREF(f_value); + } + return py_ue_new_fvector2d(vec); +} + +static PyObject *ue_py_fvector2d_div(ue_PyFVector2D *self, PyObject *value) +{ + FVector2D vec = self->vec; + ue_PyFVector2D *py_vec = py_ue_is_fvector2d(value); + if (py_vec) + { + if (py_vec->vec.X == 0 || py_vec->vec.Y == 0) + return PyErr_Format(PyExc_ZeroDivisionError, "division by zero"); + vec /= py_vec->vec; + } + else if (PyNumber_Check(value)) + { + PyObject *f_value = PyNumber_Float(value); + float f = PyFloat_AsDouble(f_value); + if (f == 0) + return PyErr_Format(PyExc_ZeroDivisionError, "division by zero"); + vec /= f; + Py_DECREF(f_value); + } + return py_ue_new_fvector2d(vec); +} + +static PyObject *ue_py_fvector2d_floor_div(ue_PyFVector2D *self, PyObject *value) +{ + FVector2D vec = self->vec; + if (PyNumber_Check(value)) + { + PyObject *f_value = PyNumber_Float(value); + float f = PyFloat_AsDouble(f_value); + if (f == 0) + return PyErr_Format(PyExc_ZeroDivisionError, "division by zero"); + vec.X = floor(vec.X / f); + vec.Y = floor(vec.Y / f); + Py_DECREF(f_value); + return py_ue_new_fvector2d(vec); + } + return PyErr_Format(PyExc_TypeError, "value is not numeric"); +} + +PyNumberMethods ue_PyFVector2D_number_methods; + +static Py_ssize_t ue_py_fvector2d_seq_length(ue_PyFVector2D *self) +{ + return 2; +} + +static PyObject *ue_py_fvector2d_seq_item(ue_PyFVector2D *self, Py_ssize_t i) +{ + switch (i) + { + case 0: + return PyFloat_FromDouble(self->vec.X); + case 1: + return PyFloat_FromDouble(self->vec.Y); + } + return PyErr_Format(PyExc_IndexError, "FVector2D has only 2 items"); +} + +PySequenceMethods ue_PyFVector2D_sequence_methods; + +static int ue_py_fvector2d_init(ue_PyFVector2D *self, PyObject *args, PyObject *kwargs) +{ + float x = 0, y = 0; + if (!PyArg_ParseTuple(args, "|ff", &x, &y)) + return -1; + + if (PyTuple_Size(args) == 1) + { + y = x; + } + + self->vec.X = x; + self->vec.Y = y; + + return 0; +} + +static PyObject *ue_py_fvector2d_richcompare(ue_PyFVector2D *vec1, PyObject *b, int op) +{ + ue_PyFVector2D *vec2 = py_ue_is_fvector2d(b); + if (!vec2 || (op != Py_EQ && op != Py_NE)) + { + return PyErr_Format(PyExc_NotImplementedError, "can only compare with another FVector2D"); + } + + if (op == Py_EQ) + { + if (vec1->vec.X == vec2->vec.X && + vec1->vec.Y == vec2->vec.Y) + { + Py_INCREF(Py_True); + return Py_True; + } + Py_INCREF(Py_False); + return Py_False; + } + + if (vec1->vec.X == vec2->vec.X && + vec1->vec.Y == vec2->vec.Y) + { + Py_INCREF(Py_False); + return Py_False; + } + Py_INCREF(Py_True); + return Py_True; +} + +void ue_python_init_fvector2d(PyObject *ue_module) +{ + ue_PyFVector2DType.tp_new = PyType_GenericNew; + + ue_PyFVector2DType.tp_init = (initproc)ue_py_fvector2d_init; + ue_PyFVector2DType.tp_richcompare = (richcmpfunc)ue_py_fvector2d_richcompare; + + memset(&ue_PyFVector2D_number_methods, 0, sizeof(PyNumberMethods)); + ue_PyFVector2DType.tp_as_number = &ue_PyFVector2D_number_methods; + ue_PyFVector2D_number_methods.nb_add = (binaryfunc)ue_py_fvector2d_add; + ue_PyFVector2D_number_methods.nb_subtract = (binaryfunc)ue_py_fvector2d_sub; + ue_PyFVector2D_number_methods.nb_multiply = (binaryfunc)ue_py_fvector2d_mul; + ue_PyFVector2D_number_methods.nb_true_divide = (binaryfunc)ue_py_fvector2d_div; + ue_PyFVector2D_number_methods.nb_floor_divide = (binaryfunc)ue_py_fvector2d_floor_div; + + memset(&ue_PyFVector2D_sequence_methods, 0, sizeof(PySequenceMethods)); + ue_PyFVector2DType.tp_as_sequence = &ue_PyFVector2D_sequence_methods; + ue_PyFVector2D_sequence_methods.sq_length = (lenfunc)ue_py_fvector2d_seq_length; + ue_PyFVector2D_sequence_methods.sq_item = (ssizeargfunc)ue_py_fvector2d_seq_item; + + if (PyType_Ready(&ue_PyFVector2DType) < 0) + return; + + Py_INCREF(&ue_PyFVector2DType); + PyModule_AddObject(ue_module, "FVector2D", (PyObject *)&ue_PyFVector2DType); +} + +PyObject *py_ue_new_fvector2d(FVector2D vec) +{ + ue_PyFVector2D *ret = (ue_PyFVector2D *)PyObject_New(ue_PyFVector2D, &ue_PyFVector2DType); + ret->vec = vec; + return (PyObject *)ret; +} + +ue_PyFVector2D *py_ue_is_fvector2d(PyObject *obj) +{ + if (!PyObject_IsInstance(obj, (PyObject *)&ue_PyFVector2DType)) + return nullptr; + return (ue_PyFVector2D *)obj; +} + +bool py_ue_vector2d_arg(PyObject *args, FVector2D &vec) +{ + + if (PyTuple_Size(args) == 1) + { + PyObject *arg = PyTuple_GetItem(args, 0); + ue_PyFVector2D *py_vec = py_ue_is_fvector2d(arg); + if (!py_vec) + { + PyErr_Format(PyExc_TypeError, "argument is not a FVector2D"); + return false; + } + vec = py_vec->vec; + return true; + } + + float x, y; + if (!PyArg_ParseTuple(args, "ff", &x, &y)) + return false; + vec.X = x; + vec.Y = y; + return true; +} + diff --git a/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector2D.h b/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector2D.h new file mode 100644 index 000000000..b9c2f4d73 --- /dev/null +++ b/Source/UnrealEnginePython/Private/Wrappers/UEPyFVector2D.h @@ -0,0 +1,21 @@ +#pragma once + + + +#include "UEPyModule.h" + +typedef struct +{ + PyObject_HEAD + /* Type-specific fields go here. */ + FVector2D vec; +} ue_PyFVector2D; + +extern PyTypeObject ue_PyFVector2DType; + +PyObject *py_ue_new_fvector2d(FVector2D); +ue_PyFVector2D *py_ue_is_fvector2d(PyObject *); + +void ue_python_init_fvector2d(PyObject *); + +bool py_ue_vector2d_arg(PyObject *, FVector2D &); diff --git a/Source/UnrealEnginePython/Public/PythonDelegate.h b/Source/UnrealEnginePython/Public/PythonDelegate.h index 01d0f70b7..be46aa511 100644 --- a/Source/UnrealEnginePython/Public/PythonDelegate.h +++ b/Source/UnrealEnginePython/Public/PythonDelegate.h @@ -13,6 +13,7 @@ class UPythonDelegate : public UObject ~UPythonDelegate(); virtual void ProcessEvent(UFunction *function, void *Parms) override; void SetPyCallable(PyObject *callable); + bool UsesPyCallable(PyObject *callable); void SetSignature(UFunction *original_signature); void PyInputHandler(); diff --git a/Source/UnrealEnginePython/Public/PythonHouseKeeper.h b/Source/UnrealEnginePython/Public/PythonHouseKeeper.h index ed36ecc63..ccd9edb01 100644 --- a/Source/UnrealEnginePython/Public/PythonHouseKeeper.h +++ b/Source/UnrealEnginePython/Public/PythonHouseKeeper.h @@ -11,293 +11,72 @@ class FUnrealEnginePythonHouseKeeper : public FGCObject { - - struct FPythonUOjectTracker - { - FWeakObjectPtr Owner; - ue_PyUObject *PyUObject; - bool bPythonOwned; - - FPythonUOjectTracker(UObject *Object, ue_PyUObject *InPyUObject) - { - Owner = FWeakObjectPtr(Object); - PyUObject = InPyUObject; - bPythonOwned = false; - } - }; - - struct FPythonDelegateTracker - { - FWeakObjectPtr Owner; - UPythonDelegate *Delegate; - - FPythonDelegateTracker(UPythonDelegate *DelegateToTrack, UObject *DelegateOwner) : Owner(DelegateOwner), Delegate(DelegateToTrack) - { - } - - ~FPythonDelegateTracker() - { - } - }; - - - struct FPythonSWidgetDelegateTracker - { - TWeakPtr Owner; - TSharedPtr Delegate; - - FPythonSWidgetDelegateTracker(TSharedRef DelegateToTrack, TSharedRef DelegateOwner) : Owner(DelegateOwner), Delegate(DelegateToTrack) - { - } - - ~FPythonSWidgetDelegateTracker() - { - } - }; + struct FPythonUOjectTracker + { + FWeakObjectPtr Owner; + ue_PyUObject *PyUObject; + bool bPythonOwned; + + FPythonUOjectTracker(UObject *Object, ue_PyUObject *InPyUObject) + { + Owner = FWeakObjectPtr(Object); + PyUObject = InPyUObject; + bPythonOwned = false; + } + }; + + struct FPythonDelegateTracker + { + FWeakObjectPtr Owner; + UPythonDelegate *Delegate; + + FPythonDelegateTracker(UPythonDelegate *DelegateToTrack, UObject *DelegateOwner) : Owner(DelegateOwner), Delegate(DelegateToTrack) + { + } + + ~FPythonDelegateTracker() + { + } + }; + + struct FPythonSWidgetDelegateTracker + { + TWeakPtr Owner; + TSharedPtr Delegate; + + FPythonSWidgetDelegateTracker(TSharedRef DelegateToTrack, TSharedRef DelegateOwner) : Owner(DelegateOwner), Delegate(DelegateToTrack) + { + } + + ~FPythonSWidgetDelegateTracker() + { + } + }; public: - virtual void AddReferencedObjects(FReferenceCollector& InCollector) override - { - InCollector.AddReferencedObjects(PythonTrackedObjects); - } - - static FUnrealEnginePythonHouseKeeper *Get() - { - static FUnrealEnginePythonHouseKeeper *Singleton; - if (!Singleton) - { - Singleton = new FUnrealEnginePythonHouseKeeper(); - // register a new delegate for the GC -#if ENGINE_MINOR_VERSION >= 18 - FCoreUObjectDelegates::GetPostGarbageCollect().AddRaw(Singleton, &FUnrealEnginePythonHouseKeeper::RunGCDelegate); -#else - FCoreUObjectDelegates::PostGarbageCollect.AddRaw(Singleton, &FUnrealEnginePythonHouseKeeper::RunGCDelegate); -#endif - } - return Singleton; - } - - void RunGCDelegate() - { - FScopePythonGIL gil; - RunGC(); - } - - int32 RunGC() - { - int32 Garbaged = PyUObjectsGC(); - Garbaged += DelegatesGC(); - return Garbaged; - } - - bool IsValidPyUObject(ue_PyUObject *PyUObject) - { - if (!PyUObject) - return false; - - UObject *Object = PyUObject->ue_object; - FPythonUOjectTracker *Tracker = UObjectPyMapping.Find(Object); - if (!Tracker) - { - return false; - } - - if (!Tracker->Owner.IsValid()) - return false; - - return true; - - } - - void TrackUObject(UObject *Object) - { - FPythonUOjectTracker *Tracker = UObjectPyMapping.Find(Object); - if (!Tracker) - { - return; - } - if (Tracker->bPythonOwned) - return; - Tracker->bPythonOwned = true; - // when a new ue_PyUObject spawns, it has a reference counting of two - Py_DECREF(Tracker->PyUObject); - Tracker->PyUObject->owned = 1; - PythonTrackedObjects.Add(Object); - } - - void UntrackUObject(UObject *Object) - { - PythonTrackedObjects.Remove(Object); - } - - void RegisterPyUObject(UObject *Object, ue_PyUObject *InPyUObject) - { - UObjectPyMapping.Add(Object, FPythonUOjectTracker(Object, InPyUObject)); - } - - void UnregisterPyUObject(UObject *Object) - { - UObjectPyMapping.Remove(Object); - } - - ue_PyUObject *GetPyUObject(UObject *Object) - { - FPythonUOjectTracker *Tracker = UObjectPyMapping.Find(Object); - if (!Tracker) - { - return nullptr; - } - - if (!Tracker->Owner.IsValid(true)) - { -#if defined(UEPY_MEMORY_DEBUG) - UE_LOG(LogPython, Warning, TEXT("DEFREF'ing UObject at %p (refcnt: %d)"), Object, Tracker->PyUObject->ob_base.ob_refcnt); -#endif - if (!Tracker->bPythonOwned) - Py_DECREF((PyObject *)Tracker->PyUObject); - UnregisterPyUObject(Object); - return nullptr; - } - - return Tracker->PyUObject; -} - - uint32 PyUObjectsGC() - { - uint32 Garbaged = 0; - TArray BrokenList; - for (auto &UObjectPyItem : UObjectPyMapping) - { - UObject *Object = UObjectPyItem.Key; - FPythonUOjectTracker &Tracker = UObjectPyItem.Value; -#if defined(UEPY_MEMORY_DEBUG) - UE_LOG(LogPython, Warning, TEXT("Checking for UObject at %p"), Object); -#endif - if (!Tracker.Owner.IsValid(true)) - { -#if defined(UEPY_MEMORY_DEBUG) - UE_LOG(LogPython, Warning, TEXT("Removing UObject at %p (refcnt: %d)"), Object, Tracker.PyUObject->ob_base.ob_refcnt); -#endif - BrokenList.Add(Object); - Garbaged++; - } - else - { -#if defined(UEPY_MEMORY_DEBUG) - UE_LOG(LogPython, Error, TEXT("UObject at %p %s is in use"), Object, *Object->GetName()); -#endif - } - } - - for (UObject *Object : BrokenList) - { - FPythonUOjectTracker &Tracker = UObjectPyMapping[Object]; - if (!Tracker.bPythonOwned) - Py_DECREF((PyObject *)Tracker.PyUObject); - UnregisterPyUObject(Object); - } - - return Garbaged; - - } - - - int32 DelegatesGC() - { - int32 Garbaged = 0; -#if defined(UEPY_MEMORY_DEBUG) - UE_LOG(LogPython, Display, TEXT("Garbage collecting %d UObject delegates"), PyDelegatesTracker.Num()); -#endif - for (int32 i = PyDelegatesTracker.Num() - 1; i >= 0; --i) - { - FPythonDelegateTracker &Tracker = PyDelegatesTracker[i]; - if (!Tracker.Owner.IsValid(true)) - { - Tracker.Delegate->RemoveFromRoot(); - PyDelegatesTracker.RemoveAt(i); - Garbaged++; - } - - } - -#if defined(UEPY_MEMORY_DEBUG) - UE_LOG(LogPython, Display, TEXT("Garbage collecting %d Slate delegates"), PySlateDelegatesTracker.Num()); -#endif - - for (int32 i = PySlateDelegatesTracker.Num() - 1; i >= 0; --i) - { - FPythonSWidgetDelegateTracker &Tracker = PySlateDelegatesTracker[i]; - if (!Tracker.Owner.IsValid()) - { - PySlateDelegatesTracker.RemoveAt(i); - Garbaged++; - } - - } - return Garbaged; - } - - UPythonDelegate *NewDelegate(UObject *Owner, PyObject *PyCallable, UFunction *Signature) - { - UPythonDelegate *Delegate = NewObject(); - - Delegate->AddToRoot(); - Delegate->SetPyCallable(PyCallable); - Delegate->SetSignature(Signature); - - FPythonDelegateTracker Tracker(Delegate, Owner); - PyDelegatesTracker.Add(Tracker); - - return Delegate; - } - - TSharedRef NewSlateDelegate(TSharedRef Owner, PyObject *PyCallable) - { - TSharedRef Delegate = MakeShareable(new FPythonSlateDelegate()); - Delegate->SetPyCallable(PyCallable); - - FPythonSWidgetDelegateTracker Tracker(Delegate, Owner); - PySlateDelegatesTracker.Add(Tracker); - - return Delegate; - } - - TSharedRef NewDeferredSlateDelegate(PyObject *PyCallable) - { - TSharedRef Delegate = MakeShareable(new FPythonSlateDelegate()); - Delegate->SetPyCallable(PyCallable); - - return Delegate; - } - - TSharedRef NewPythonSmartDelegate(PyObject *PyCallable) - { - TSharedRef Delegate = MakeShareable(new FPythonSmartDelegate()); - Delegate->SetPyCallable(PyCallable); - - PyStaticSmartDelegatesTracker.Add(Delegate); - - return Delegate; - } - - void TrackDeferredSlateDelegate(TSharedRef Delegate, TSharedRef Owner) - { - FPythonSWidgetDelegateTracker Tracker(Delegate, Owner); - PySlateDelegatesTracker.Add(Tracker); - } - - TSharedRef NewStaticSlateDelegate(PyObject *PyCallable) - { - TSharedRef Delegate = MakeShareable(new FPythonSlateDelegate()); - Delegate->SetPyCallable(PyCallable); - - PyStaticSlateDelegatesTracker.Add(Delegate); - - return Delegate; - } + virtual void AddReferencedObjects(FReferenceCollector& InCollector) override; + static FUnrealEnginePythonHouseKeeper *Get(); + int32 RunGC(); + bool IsValidPyUObject(ue_PyUObject *PyUObject); + void TrackUObject(UObject *Object); + void UntrackUObject(UObject *Object); + void RegisterPyUObject(UObject *Object, ue_PyUObject *InPyUObject); + void UnregisterPyUObject(UObject *Object); + ue_PyUObject *GetPyUObject(UObject *Object); + UPythonDelegate *FindDelegate(UObject *Owner, PyObject *PyCallable); + UPythonDelegate *NewDelegate(UObject *Owner, PyObject *PyCallable, UFunction *Signature); + TSharedRef NewSlateDelegate(TSharedRef Owner, PyObject *PyCallable); + TSharedRef NewDeferredSlateDelegate(PyObject *PyCallable); + TSharedRef NewPythonSmartDelegate(PyObject *PyCallable); + void TrackDeferredSlateDelegate(TSharedRef Delegate, TSharedRef Owner); + TSharedRef NewStaticSlateDelegate(PyObject *PyCallable); private: + void RunGCDelegate(); + uint32 PyUObjectsGC(); + int32 DelegatesGC(); + TMap UObjectPyMapping; TArray PyDelegatesTracker; diff --git a/Source/UnrealEnginePython/UnrealEnginePython.Build.cs b/Source/UnrealEnginePython/UnrealEnginePython.Build.cs index 6e7727d0b..a6fe71946 100644 --- a/Source/UnrealEnginePython/UnrealEnginePython.Build.cs +++ b/Source/UnrealEnginePython/UnrealEnginePython.Build.cs @@ -14,11 +14,11 @@ public class UnrealEnginePython : ModuleRules // this is an example for Homebrew on Mac //private string pythonHome = "/usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/"; // on Linux an include;libs syntax is expected: - //private string pythonHome = "/usr/local/include/python3.6;/usr/local/lib/libpython3.6.so" + //private string pythonHome = "/usr/local/include/python3.6;/usr/local/lib/libpython3.6.so"; private string[] windowsKnownPaths = { - "C:/Program Files/Python37", + // "C:/Program Files/Python37", "C:/Program Files/Python36", "C:/Program Files/Python35", "C:/Python27", @@ -100,7 +100,6 @@ public UnrealEnginePython(TargetInfo Target) PublicIncludePaths.AddRange( new string[] { - "UnrealEnginePython/Public", // ... add public include paths required here ... } ); @@ -108,7 +107,6 @@ public UnrealEnginePython(TargetInfo Target) PrivateIncludePaths.AddRange( new string[] { - "UnrealEnginePython/Private", // ... add other private include paths required here ... } ); @@ -292,6 +290,11 @@ private string DiscoverPythonPath(string[] knownPaths, string binaryPath) if (!string.IsNullOrEmpty(environmentPath)) paths.Insert(0, environmentPath); + // look in an alternate custom location + environmentPath = System.Environment.GetEnvironmentVariable("UNREALENGINEPYTHONHOME"); + if (!string.IsNullOrEmpty(environmentPath)) + paths.Insert(0, environmentPath); + foreach (string path in paths) { string actualPath = path; diff --git a/docs/Slate_API.md b/docs/Slate_API.md index 8b1378917..0008d2b53 100644 --- a/docs/Slate_API.md +++ b/docs/Slate_API.md @@ -1 +1,831 @@ +# The Slate API + +Slate is the GUI toolkit in Unreal Engine 4. It allows you to create windows, buttons, sliders and all of the graphics elements you see in the editor. It is the base for the Blueprint Widgets too. + +UnrealEnginePython allows you to build GUI from python using the Slate api. + +The python wrapper development has been sponsored by Kite & Lightning (http://kiteandlightning.la/) + +This document assumes a python3 environment. If you are using python2, just ensure to use unicode when you see strings. + +It is a 'Work In Progress' and its objective is to give the user enough basis to start building its tools. Only a meaningful subset of the available widgets is explained here, pull requests for extending this page are really welcomed. + +## SWidget and ue_PySWidget + +SWidget is the base C++ class for all the Slate widgets, it is wrapped in a python object (PyObject) named ue_PySWidget. + +Each SWidget exposed to python has its ue_PySWidget representation. You can get the list of exposed SWidget from here: https://github.com/20tab/UnrealEnginePython/tree/master/Source/UnrealEnginePython/Private/Slate (the ones stating with 'UEPyS' prefix) + +## SWindow + +this is the first widget you generally use as it will be the container for the others. It represents a window: + +```python +from unreal_engine import SWindow + +window = SWindow(client_size=(512, 512), title='First Slate Window') +``` + +![SWindow](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SWindow.png) + +As you can see just by instancing Slate you will create and show them. + +In the C++ api, each SWidget has its series of FArguments (https://api.unrealengine.com/INT/API/Runtime/Slate/Widgets/Input/SComboBox/FArguments/index.html). + +They are the options of the SWidget, and in the python api you pass them as arguments to the constructor (note the pythonization of the names with lower-case-underscore-delimited conversion). + +In this case the ClientSize FArguments (http://api.unrealengine.com/INT/API/Runtime/SlateCore/Widgets/SWindow/FArguments/ClientSize/index.html) became 'client_size' and Title (http://api.unrealengine.com/INT/API/Runtime/SlateCore/Widgets/SWindow/FArguments/Title/index.html) became 'title'. Note the shortcut from FVector2D to a simple 2-elements float tuple. (both are supported) + + +## STextBlock + +STextBlock (https://api.unrealengine.com/INT/API/Runtime/Slate/Widgets/Text/STextBlock/index.html) shows a simple text label: + +```python +from unreal_engine import SWindow, STextBlock + +window = SWindow(client_size=(512, 512), title='First Slate Window') + +text = STextBlock(text='Hello i am an STextBlock') + +window.set_content(text) +``` + +![STextBlock](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_STextBlock.png) + +Again using FArguments (https://api.unrealengine.com/INT/API/Runtime/Slate/Widgets/Text/STextBlock/FArguments/index.html) you can (as an example) set the color of the text to red: + +```python +from unreal_engine import SWindow, STextBlock, FLinearColor +from unreal_engine.structs import SlateColor + +window = SWindow(client_size=(512, 512), title='First Slate Window') + +red = SlateColor(SpecifiedColor=FLinearColor(1, 0, 0)) + +text = STextBlock(text='Hello i am an STextBlock', color_and_opacity=red) + +window.set_content(text) +``` + +![STextBlock red](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_STextBlock_red.png) + +## Dynamic binding for FArguments + +As in C++ Slate, you can map 'delegates' (well, callables in python) to some of the FArguments. As an example you can dynamically change the 'text' attribute of an STextBlock to be constantly updated with the current time: + +```python +from unreal_engine import SWindow, STextBlock, FLinearColor +from unreal_engine.structs import SlateColor +import time + +window = SWindow(client_size=(512, 512), title='First Slate Window') + +red = SlateColor(SpecifiedColor=FLinearColor(1, 0, 0)) + +text = STextBlock(text=lambda: str(time.time()), color_and_opacity=red) + +window.set_content(text) +``` + +Obviously instead of the lambda you could use a plain function (any callable will works): + +```python +from unreal_engine import SWindow, STextBlock, FLinearColor +from unreal_engine.structs import SlateColor +import time + +def what_time_is_it(): + return str(time.time()) + +window = SWindow(client_size=(512, 512), title='First Slate Window') + +red = SlateColor(SpecifiedColor=FLinearColor(1, 0, 0)) + +text = STextBlock(text=what_time_is_it, color_and_opacity=red) + +window.set_content(text) +``` + +## Content assignment shortcut + +In the previous examples we have seen how we added the STextBlock to the SWindow by using set_content(). + +This is a very 'procedural' way of dealing with GUIs. Technically Slate (as well as other toolkits) enforce the developer to a very 'visual' style when describing user interfaces. For this reason, if you do not like the 'procedural' approach you can abuse a specific feature of ue_PySWidget objects: when they are containers, they are 'callable', and calling them will internally call set_content() (or similar). + +Based on this you can rewrite the first STextBlock example in this way: + +```python +from unreal_engine import SWindow, STextBlock + +window = SWindow(client_size=(512, 512), title='First Slate Window')(STextBlock(text='Hello i am an STextBlock')) +``` +or 'visually' better (note the opened bracked in the same line of SWindow) + +```python +from unreal_engine import SWindow, STextBlock + +window = SWindow(client_size=(512, 512), title='First Slate Window')( + STextBlock(text='Hello i am an STextBlock') +) +``` + +## SVerticalBox + +Boxes are one of the many Slate ways to organize/align multiple widgets in a containers. + +SVerticalBox (https://api.unrealengine.com/INT/API/Runtime/SlateCore/Widgets/SVerticalBox/index.html) is a box allowing you to align widgets vertically in a container (each element of the box is named 'slot', a lot effectively contains the widget as well as various attibutes): + +```python +from unreal_engine import SWindow, STextBlock, SVerticalBox + +window = SWindow(client_size=(512, 512), title='First Slate Window') + +vertical_box = SVerticalBox() + +vertical_box.add_slot(STextBlock(text='Hello i am an STextBlock [line 0]')) +vertical_box.add_slot(STextBlock(text='Hello i am an STextBlock [line 1]')) +vertical_box.add_slot(STextBlock(text='Hello i am an STextBlock [line 2]')) + +window.set_content(vertical_box) +``` + +![SVerticalBox](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SVerticalBox.png) + +or in 'visual' style (always note the first opened bracked in the same line of SWindow to fake the parser): + +```python +from unreal_engine import SWindow, STextBlock, SVerticalBox + +window = SWindow(client_size=(512, 512), title='First Slate Window')( + SVerticalBox() + ( + STextBlock(text='Hello i am an STextBlock [line 0]') + ) + ( + STextBlock(text='Hello i am an STextBlock [line 1]') + ) + ( + STextBlock(text='Hello i am an STextBlock [line 2]') + ) +) +``` + +## Slot attributes + +We have seen that the mapping between a box and the various widgets is managed by 'slots'. Each slot describes the way a widget is managed in a box (mainly alignment and padding): + +```python +from unreal_engine import SWindow, STextBlock, SVerticalBox +from unreal_engine.enums import EVerticalAlignment, EHorizontalAlignment + +window = SWindow(client_size=(512, 512), title='First Slate Window') + +vertical_box = SVerticalBox() + +vertical_box.add_slot(STextBlock(text='Hello i am an STextBlock [line 0]'), auto_height=True, padding=(10, 20, 10, 100)) +vertical_box.add_slot(STextBlock(text='Hello i am an STextBlock [line 1]'), max_height=173, h_align=EHorizontalAlignment.HAlign_Center) +vertical_box.add_slot(STextBlock(text='Hello i am an STextBlock [line 2]'), h_align=EHorizontalAlignment.HAlign_Right, v_align=EVerticalAlignment.VAlign_Bottom, padding=(0, 0, 50, 50)) + +window.set_content(vertical_box) +``` + +![Slot](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_Slot.png) + +The first slot will have its height automatically mapped to the height of the contained widget (the STextBlock) and a padding of 10 units on the top, 20 on the left, 10 on the right and 100 on the bottom. (more on padding, later). + +The second slot starts 100 units below the first one (caused by the padding of the first slot). Its content will be aligned to the center and will have a maximum height of 173 unit (we use 'max' wording here as the SVerticalBox alignment could be forced to make it tinier). + +The third slot is aligned to right and vertically to the bottom with a rght/bottom padding of 50 units. + +Useful links: + +https://api.unrealengine.com/INT/API/Runtime/SlateCore/Widgets/SVerticalBox/FSlot/index.html + +https://api.unrealengine.com/INT/API/Runtime/SlateCore/Types/EHorizontalAlignment/index.html + +https://api.unrealengine.com/INT/API/Runtime/SlateCore/Types/EVerticalAlignment/index.html + + + +It is pretty hard to see what is going on here, but adding an SBorder will clarify things... + +## SBorder + +SBorder is a special container what will add a border around its contained widget: + +```python +from unreal_engine import SWindow, STextBlock, SVerticalBox, SBorder +from unreal_engine.enums import EVerticalAlignment, EHorizontalAlignment + +window = SWindow(client_size=(512, 512), title='First Slate Window') + +vertical_box = SVerticalBox() + +vertical_box.add_slot(SBorder()(STextBlock(text='Hello i am an STextBlock [line 0]')), auto_height=True, padding=(10, 20, 10, 100)) +vertical_box.add_slot(SBorder()(STextBlock(text='Hello i am an STextBlock [line 1]')), max_height=173, h_align=EHorizontalAlignment.HAlign_Center) +vertical_box.add_slot(SBorder()(STextBlock(text='Hello i am an STextBlock [line 2]')), h_align=EHorizontalAlignment.HAlign_Right, v_align=EVerticalAlignment.VAlign_Bottom, padding=(0, 0, 50, 20)) + +window.set_content(SBorder()(vertical_box)) +``` + +![SBorder](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SBorder.png) + +Now slot attributes should be more clear. Note that SBorder has a set_content() method but here we used the 'visual' style (mixed with procedural one): + +```python +SBorder()(STextBlock(text='Hello i am an STextBlock [line 0]')) +``` + +The list of FArguments for SBorder is avalable here: https://api.unrealengine.com/INT/API/Runtime/Slate/Widgets/Layout/SBorder/FArguments/ + +## Padding + +The padding attributes of slots, defines their margin size. + +Padding in the python api accepts various forms: + +```python +from unreal_engine import SWindow, STextBlock, SVerticalBox, SBorder +from unreal_engine.enums import EVerticalAlignment, EHorizontalAlignment + +window = SWindow(client_size=(512, 512), title='First Slate Window') + +vertical_box = SVerticalBox() + +vertical_box.add_slot(SBorder()(STextBlock(text='Hello i am an STextBlock [line 0]')), padding=100, v_align=EVerticalAlignment.VAlign_Fill) + +window.set_content(SBorder()(vertical_box)) +``` + +passing a single float will specify the same amount of units for top, left, bottom and right: + +![Padding](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_Padding.png) + +```python +from unreal_engine import SWindow, STextBlock, SVerticalBox, SBorder +from unreal_engine.enums import EVerticalAlignment, EHorizontalAlignment + +window = SWindow(client_size=(512, 512), title='First Slate Window') + +vertical_box = SVerticalBox() + +vertical_box.add_slot(SBorder()(STextBlock(text='Hello i am an STextBlock [line 0]')), padding=(100, 100), v_align=EVerticalAlignment.VAlign_Fill) + +window.set_content(SBorder()(vertical_box)) +``` + +a 2-items float tuple will specify top and left and will force bottom and right to 0: + +![Padding 2](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_Padding2.png) + +```python +from unreal_engine import SWindow, STextBlock, SVerticalBox, SBorder +from unreal_engine.enums import EVerticalAlignment, EHorizontalAlignment + +window = SWindow(client_size=(512, 512), title='First Slate Window') + +vertical_box = SVerticalBox() + +vertical_box.add_slot(SBorder()(STextBlock(text='Hello i am an STextBlock [line 0]')), padding=(100, 100, 30, 30), v_align=EVerticalAlignment.VAlign_Fill) + +window.set_content(SBorder()(vertical_box)) +``` + +We have alredy seen the 4-items float tuple for specifying top, left, bottom and right: + +![Padding 3](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_Padding3.png) + +In addition to float/float-tuples you can specify padding using the FMargin struct: + +```python +from unreal_engine.structs import Margin +print(Margin.properties()) +``` +returns + +```python +['Left', 'Top', 'Right', 'Bottom'] +``` + +So: + +```python +from unreal_engine import SWindow, STextBlock, SVerticalBox, SBorder +from unreal_engine.enums import EVerticalAlignment, EHorizontalAlignment +from unreal_engine.structs import Margin + +window = SWindow(client_size=(512, 512), title='First Slate Window') + +vertical_box = SVerticalBox() + +vertical_box.add_slot(SBorder()(STextBlock(text='Hello i am an STextBlock [line 0]')), padding=Margin(Left=100, Right=50), v_align=EVerticalAlignment.VAlign_Fill) + +window.set_content(SBorder()(vertical_box)) +``` + +![Padding 4](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_Padding4.png) + +## SHorizontalBox + +SHorizontalBox allows you to horizontally align widgets in a containers: + +```python +from unreal_engine import SWindow, STextBlock, SHorizontalBox, SBorder +from unreal_engine.enums import EVerticalAlignment, EHorizontalAlignment +from unreal_engine.structs import Margin + +window = SWindow(client_size=(512, 512), title='First Slate Window') + +horizontal_box = SHorizontalBox() + +horizontal_box.add_slot(SBorder()(STextBlock(text='Left')), padding=Margin(Left=100, Right=50, Bottom=30), v_align=EVerticalAlignment.VAlign_Fill) +horizontal_box.add_slot(SBorder()(STextBlock(text='Right')), padding=Margin(Left=10, Right=20, Top=50), v_align=EVerticalAlignment.VAlign_Fill) + +window.set_content(SBorder()(horizontal_box)) +``` + +![SHorizontalBox](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SHorizontalBox.png) + +You can obviously combine vertical and horizontal boxes (this time using 'visual' style) + +```python +from unreal_engine import SWindow, STextBlock, SHorizontalBox, SVerticalBox, SBorder, FLinearColor +from unreal_engine.enums import EVerticalAlignment, EHorizontalAlignment, ETextJustify +from unreal_engine.structs import Margin, SlateColor + +SWindow(client_size=(512, 256), title='Slate Window')( + SVerticalBox() + ( + SHorizontalBox() + ( + SBorder()(STextBlock(text='Left top', justification=ETextJustify.Center)), v_align=EVerticalAlignment.VAlign_Fill + ) + ( + SBorder(border_background_color=SlateColor(SpecifiedColor=FLinearColor.Green))(STextBlock(text='Right top', highlight_text='Right')) + ) + ) + ( + SHorizontalBox() + ( + SBorder(border_background_color=SlateColor(SpecifiedColor=FLinearColor(1, 0, 0)), padding=20) + ( + STextBlock(text='Left bottom') + ), v_align=EVerticalAlignment.VAlign_Fill + ) + ( + SBorder()(STextBlock(text='Middle bottom', justification=ETextJustify.Right)), v_align=EVerticalAlignment.VAlign_Center + ) + ( + SBorder(border_background_color=SlateColor(SpecifiedColor=FLinearColor.Yellow))(STextBlock(text='Right bottom')), v_align=EVerticalAlignment.VAlign_Fill + ) + ) +) +``` + +![SHorizontalBox2](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SHorizontalBox2.png) + +More infos on SHorizontalBox: https://api.unrealengine.com/INT/API/Runtime/SlateCore/Widgets/SHorizontalBox/index.html + +## SGridPanel + +This widget allows you to align children in a virtual grid. For each slot you specify the column and row and eventually how much it 'spans' horizontally and verically: + +```python +from unreal_engine import SWindow, STextBlock, SGridPanel, SBorder + +margin = 40 + +SWindow(client_size=(512, 512), title='Slate Window')( + SGridPanel() + ( + SBorder(padding=margin)(STextBlock(text='cell0')), column=0, row=0 + ) + ( + SBorder(padding=margin)(STextBlock(text='cell1')), column=1, row=0 + ) + ( + SBorder(padding=margin)(STextBlock(text='cell2')), column=2, row=0 + ) + ( + SBorder(padding=margin)(STextBlock(text='cell3')), column=0, row=1 + ) + ( + SBorder(padding=margin)(STextBlock(text='cell4')), column=3, row=1, row_span=3 + ) + ( + SBorder(padding=margin)(STextBlock(text='cell5')), column=2, row=2 + ) + ( + SBorder(padding=margin)(STextBlock(text='cell6')), column=0, row=3, column_span=2 + ) +) +``` + +![SGridPanel](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SGridPanel.png) + +## SScrollBox + +This container allows you to scroll on big series of widgets: + +```python +from unreal_engine import SWindow, STextBlock, SGridPanel, SBorder, SScrollBox +from unreal_engine.enums import EOrientation + +margin = 40 + +SWindow(client_size=(512, 256), title='Slate Window')( + SScrollBox(orientation=EOrientation.Orient_Vertical) + ( + SGridPanel() + ( + SBorder(padding=margin)(STextBlock(text='cell0')), column=0, row=0 + ) + ( + SBorder(padding=margin)(STextBlock(text='cell1')), column=1, row=0 + ) + ( + SBorder(padding=margin)(STextBlock(text='cell2')), column=2, row=0 + ) + ( + SBorder(padding=margin)(STextBlock(text='cell3')), column=0, row=1 + ) + ( + SBorder(padding=margin)(STextBlock(text='cell4')), column=3, row=1, row_span=3 + ) + ( + SBorder(padding=margin)(STextBlock(text='cell5')), column=2, row=2 + ) + ( + SBorder(padding=margin)(STextBlock(text='cell6')), column=0, row=3, column_span=2 + ) + ) +) +``` + +![SScrollBox](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SScrollBox.png) + +More infos here: https://api.unrealengine.com/INT/API/Runtime/Slate/Widgets/Layout/SScrollBox/ + +## SButton + +It's time for user interaction. The SButton widget raises an event whenevr the user clicks on it: + +```python +from unreal_engine import SWindow, SVerticalBox, SButton +from unreal_engine.enums import EHorizontalAlignment +import unreal_engine as ue +import time + +window = SWindow(client_size=(512, 256), title='Slate Window')( + SVerticalBox() + ( + SButton(text='Button 001', on_clicked=lambda: ue.log('Hello i am Button001')) + ) + ( + SButton(text='Button 002', h_align=EHorizontalAlignment.HAlign_Center, on_clicked=lambda: (ue.message_dialog_open(ue.APP_MSG_TYPE_OK, 'Hello i am Button002'), window.bring_to_front())) + ) + ( + SButton(text='Update title with current time', on_clicked=lambda: window.set_title(str(time.time()))) + ) + ( + SButton(text='Close Window', on_clicked=lambda: window.request_destroy()) + ) +) +``` + +![SButton](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SButton.png) + +Note that technically SButton's are containers so you can assign to them another widget: + +```python +SButton(on_clicked=lambda: window.request_destroy())(STextBlock(text='Hello')) +``` + +More infos here: https://api.unrealengine.com/INT/API/Runtime/Slate/Widgets/Input/SButton/index.html + +## SImage + +This widget draw graphics resources (images, solid blocks). + +```python +from unreal_engine import SWindow, SImage, SVerticalBox, FLinearColor, SBorder +from unreal_engine.structs import SlateBrush, SlateColor, Vector2D +from unreal_engine.classes import Texture2D +import unreal_engine as ue +import os + +plugin = unreal_engine.find_plugin('UnrealEnginePython') +plugin_base_dir = plugin.get_base_dir() + +image_file = os.path.join(plugin_base_dir, 'Resources/Icon128.png') + +texture = ue.load_object(Texture2D, '/Game/Mannequin/Character/Textures/UE4_LOGO_CARD') + + +window = SWindow(client_size=(128, 512), title='Slate Window', sizing_rule=0)( + SVerticalBox() + ( + SImage(image=SlateBrush(ResourceName=image_file, bIsDynamicallyLoaded=True)), + ) + ( + SImage(image=SlateBrush(ResourceObject=texture)) + ) + ( + SImage(image=SlateBrush(TintColor=SlateColor(SpecifiedColor=FLinearColor(1, 0, 0)))) + ) + ( + SImage(image=SlateBrush(ResourceName=image_file, bIsDynamicallyLoaded=True, TintColor=SlateColor(SpecifiedColor=FLinearColor(0, 1, 0)))) + ) + ( + SBorder()(SImage(image=SlateBrush(ResourceObject=texture, ImageSize=Vector2D(X=64, Y=64)))), auto_height=True + ) +) +``` + +![SImage](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SImage.png) + +Pay attention to the bIsDynamicallyLoaded field of SlateBrush, if you are passing a non-ue4 resource (via ResourceName) you have to instruct UE to load the resource as a texture (by setting bIsDynamicallyLoaded to true). This is not required when using ResourceObject. + +Combining SButton and SImage is pretty handy: + +```python +SButton(on_clicked=lambda: ue.log('Image Clicked'))(SImage(image=SlateBrush(ResourceName=image_file, bIsDynamicallyLoaded=True))) +``` + +More infos about SlateBrush: + +https://api.unrealengine.com/INT/API/Runtime/SlateCore/Styling/FSlateBrush/index.html + + +## SEditableTextBox + +This widget allows the user to input a string: + +```python +from unreal_engine import SWindow, SEditableTextBox, SHorizontalBox, SButton +from unreal_engine.classes import Object +import unreal_engine as ue + +asset_name=SEditableTextBox() + +window = SWindow(client_size=(512, 32), title='Open Asset', sizing_rule=0)( + SHorizontalBox() + ( + asset_name + ) + ( + SButton(text='Ok', on_clicked=lambda: ue.open_editor_for_asset(ue.load_object(Object, asset_name.get_text()))), auto_width=True + ) +) +``` + +![SEditableTextBox](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SEditableTextBox.png) + +The get_text() method will return the currently inserted text. + +When the user click on 'Ok', the asset specified in the SEditableTextBox will be validated, loaded and opened in the related editor. + +More infos (check FArguments) here: + +https://api.unrealengine.com/INT/API/Runtime/Slate/Widgets/Input/SEditableTextBox/index.html + + +## The .assign() hack + +In the previous example we used a 'mixed' visual style to allow the SEditableTextBox to be assigned to a python variable to be able to reference it in the on_clicked event. + +The python SWidget api supports an alternative way for assigning references to SWidget. It is indeed a hack (and honestly not very pythonic), but for big interfaces should simplify the management a lot: + +```python +from unreal_engine import SWindow, SEditableTextBox, SHorizontalBox, SButton +from unreal_engine.classes import Object +import unreal_engine as ue + +asset_name=None + +window = SWindow(client_size=(512, 32), title='Open Asset', sizing_rule=0)( + SHorizontalBox() + ( + SEditableTextBox().assign('asset_name') + ) + ( + SButton(text='Ok', on_clicked=lambda: ue.open_editor_for_asset(ue.load_object(Object, asset_name.get_text()))), auto_width=True + ) +) +``` + +Basically the .assign(global_name) method, will map the SWidget to the global item specified as global_name. The .assign() method will check for validity of the passed name, so typos will not be a problem. + +## SCheckBox + +Very useful for managing boolean values: + +```python +from unreal_engine import SWindow, SEditableTextBox, SHorizontalBox, SButton, SCheckBox, STextBlock +from unreal_engine.classes import Object +from unreal_engine.enums import EVerticalAlignment +import unreal_engine as ue + +asset_name=None +checkbox_bool=False + +def open_or_validate(path, only_validate): + try: + asset = ue.load_object(Object, path) + except: + ue.message_dialog_open(ue.APP_MSG_TYPE_OK, 'invalid path') + return + + if only_validate: + ue.message_dialog_open(ue.APP_MSG_TYPE_OK, 'path is valid') + else: + ue.open_editor_for_asset(asset) + +window = SWindow(client_size=(512, 32), title='Open Asset', sizing_rule=0)( + SHorizontalBox() + ( + SEditableTextBox().assign('asset_name') + ) + ( + STextBlock(text='only validate path'), auto_width=True, v_align=EVerticalAlignment.VAlign_Center + ) + ( + SCheckBox().assign('checkbox_bool'), auto_width=True + ) + ( + SButton(text='Ok', on_clicked=lambda: open_or_validate(asset_name.get_text(), checkbox_bool.is_checked())), auto_width=True + ) +) +``` + +![SCheckBox](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SCheckBox.png) + +## OOP refactoring + +Time to refactor the code to be more elegant, and to allow the reuse of custom/complex widgets: + +```python +from unreal_engine import SWindow, SEditableTextBox, SHorizontalBox, SButton, SCheckBox, STextBlock, SVerticalBox +from unreal_engine.classes import Object +from unreal_engine.enums import EVerticalAlignment +import unreal_engine as ue + +class AssetOpener(SHorizontalBox): + + def __init__(self): + super().__init__(self) + self.asset_name_picker = SEditableTextBox() + self.only_validate_path = SCheckBox() + + self.add_slot(self.asset_name_picker) + self.add_slot(STextBlock(text='only validate path'), auto_width=True, v_align=EVerticalAlignment.VAlign_Center) + self.add_slot(self.only_validate_path, auto_width=True) + self.add_slot(SButton(text='Ok', on_clicked=self.open_or_validate), auto_width=True) + + def open_or_validate(self): + try: + asset = ue.load_object(Object, self.asset_name_picker.get_text()) + except: + ue.message_dialog_open(ue.APP_MSG_TYPE_OK, 'invalid path') + return + + if self.only_validate_path.is_checked(): + ue.message_dialog_open(ue.APP_MSG_TYPE_OK, 'path is valid') + else: + ue.open_editor_for_asset(asset) + + + +window = SWindow(client_size=(512, 64), title='Open Asset', sizing_rule=0)( + SVerticalBox() + ( + STextBlock(text='OOP widget below') + ) + ( + AssetOpener() + ) +) +``` + +![OOP](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_OOP.png) + +As you can see, you can inherit from SWidget. Obviously you can mix 'visual' style, with fully procedural one, but the use of classes will simplify 'context' management. + +## SObjectPropertyEntryBox + +This widget allows the user to select an asset from a specific class + +```python +from unreal_engine import SWindow, SObjectPropertyEntryBox +from unreal_engine.classes import Material +import unreal_engine as ue + + + +window = SWindow(client_size=(512, 256), title='Material Selector', sizing_rule=0)( + ( + SObjectPropertyEntryBox(allowed_class=Material, on_object_changed=lambda choice: ue.open_editor_for_asset(choice.get_asset())) + ) +) +``` + +![SObjectPropertyEntryBox](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SObjectPropertyEntryBox.png) + +note that the callable executed by on_object_changed receives an FAssetData object as argument (this is why we need to call get_asset()) + +More infos here: https://api.unrealengine.com/INT/API/Editor/PropertyEditor/SObjectPropertyEntryBox/index.html + +## SPythonEditorViewport + +This is probably the funniest widget, an EditorViewportClient and a whole World all in a single SWidget: + +```python +from unreal_engine import SWindow, SPythonEditorViewport, FVector, FRotator +from unreal_engine.classes import Blueprint +import unreal_engine as ue + +editor_viewport = SPythonEditorViewport() +world = editor_viewport.get_world() +world.actor_spawn(ue.load_object(Blueprint, '/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter').GeneratedClass) +editor_viewport_client = editor_viewport.get_editor_viewport_client() +editor_viewport_client.set_view_location(FVector(-200, 300, 200)) +editor_viewport_client.set_view_rotation(FRotator(0, -30, -90)) + +window = SWindow(client_size=(512, 256), title='Mannequin Properties', sizing_rule=0)( + ( + editor_viewport + ) +) +``` + +![SPythonEditorViewport](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SPythonEditorViewport.png) + +Note that by calling the .simulate(bool) method on the SPythonEditorViewport instance you can enable/disable the world ticking + +## Nomad Tabs + +If you plan to make a slate tool, very probably you do not want to spawn a plain SWindow, instead you want a dock (SDockTab) that you can move/rearrange in your editor and (more important) that is unique and runnable from an editor menu. + +This can be accomplished in a single step with nomad tab spawner: + +```python +from unreal_engine import SWindow, SPythonEditorViewport, FVector, FRotator +from unreal_engine.classes import Blueprint +import unreal_engine as ue + + +def create_tab(dock_tab): + editor_viewport = SPythonEditorViewport() + world = editor_viewport.get_world() + world.actor_spawn(ue.load_object(Blueprint, '/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter').GeneratedClass) + editor_viewport_client = editor_viewport.get_editor_viewport_client() + editor_viewport_client.set_view_location(FVector(-200, 300, 200)) + editor_viewport_client.set_view_rotation(FRotator(0, -30, -90)) + + editor_viewport.simulate(True) + + dock_tab.set_content(editor_viewport) + +ue.register_nomad_tab_spawner('Hello Nomads !', create_tab) +``` + +![Nomad](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_Nomad.png) + +To spawn the nomad tab you need to search for 'Hello Nomads !' entry in Window/Developer Tools menu. (currently there is no way to place it in other positions) + +If you want to spawn (or give focus) to a specific tab (as an example for running it over a toolbar button extender), just run + +```python +ue.invoke_tab('Hello Nomads !') +``` + +## Properties Editors + +```python +from unreal_engine import SWindow +import unreal_engine as ue + +window = SWindow(client_size=(512, 256), title='Mannequin Properties', sizing_rule=0)( + ( + ue.create_detail_view(uobject=ue.get_selected_assets()[0].GeneratedClass.get_cdo()) + ) +) +``` + +![SDetailView](https://github.com/20tab/UnrealEnginePython/raw/master/docs/screenshots/slate_SDetailView.png) + +## Extenders + +Extenders allows you to 'extend' menus or toolbar with new entries or buttons. + +Check this example: https://github.com/20tab/UnrealEnginePython/blob/master/examples/extenders_example.py + +It shows even how to register new style sets. + +## SPythonListView + +## SPythonTreeView + +## SPythonWidget diff --git a/docs/screenshots/slate_Nomad.png b/docs/screenshots/slate_Nomad.png new file mode 100644 index 000000000..8c6f8da11 Binary files /dev/null and b/docs/screenshots/slate_Nomad.png differ diff --git a/docs/screenshots/slate_OOP.png b/docs/screenshots/slate_OOP.png new file mode 100644 index 000000000..cac3dac26 Binary files /dev/null and b/docs/screenshots/slate_OOP.png differ diff --git a/docs/screenshots/slate_Padding.png b/docs/screenshots/slate_Padding.png new file mode 100644 index 000000000..fa097beb3 Binary files /dev/null and b/docs/screenshots/slate_Padding.png differ diff --git a/docs/screenshots/slate_Padding2.png b/docs/screenshots/slate_Padding2.png new file mode 100644 index 000000000..ab9fb4d58 Binary files /dev/null and b/docs/screenshots/slate_Padding2.png differ diff --git a/docs/screenshots/slate_Padding3.png b/docs/screenshots/slate_Padding3.png new file mode 100644 index 000000000..68f6b1b6d Binary files /dev/null and b/docs/screenshots/slate_Padding3.png differ diff --git a/docs/screenshots/slate_Padding4.png b/docs/screenshots/slate_Padding4.png new file mode 100644 index 000000000..b552e9bec Binary files /dev/null and b/docs/screenshots/slate_Padding4.png differ diff --git a/docs/screenshots/slate_SBorder.png b/docs/screenshots/slate_SBorder.png new file mode 100644 index 000000000..4503a8fee Binary files /dev/null and b/docs/screenshots/slate_SBorder.png differ diff --git a/docs/screenshots/slate_SButton.png b/docs/screenshots/slate_SButton.png new file mode 100644 index 000000000..b813bbb3f Binary files /dev/null and b/docs/screenshots/slate_SButton.png differ diff --git a/docs/screenshots/slate_SCheckBox.png b/docs/screenshots/slate_SCheckBox.png new file mode 100644 index 000000000..beff0cc36 Binary files /dev/null and b/docs/screenshots/slate_SCheckBox.png differ diff --git a/docs/screenshots/slate_SDetailView.png b/docs/screenshots/slate_SDetailView.png new file mode 100644 index 000000000..cbb6ac846 Binary files /dev/null and b/docs/screenshots/slate_SDetailView.png differ diff --git a/docs/screenshots/slate_SEditableTextBox.png b/docs/screenshots/slate_SEditableTextBox.png new file mode 100644 index 000000000..b5a617870 Binary files /dev/null and b/docs/screenshots/slate_SEditableTextBox.png differ diff --git a/docs/screenshots/slate_SGridPanel.png b/docs/screenshots/slate_SGridPanel.png new file mode 100644 index 000000000..1ed20ae0a Binary files /dev/null and b/docs/screenshots/slate_SGridPanel.png differ diff --git a/docs/screenshots/slate_SHorizontalBox.png b/docs/screenshots/slate_SHorizontalBox.png new file mode 100644 index 000000000..a82ec799c Binary files /dev/null and b/docs/screenshots/slate_SHorizontalBox.png differ diff --git a/docs/screenshots/slate_SHorizontalBox2.png b/docs/screenshots/slate_SHorizontalBox2.png new file mode 100644 index 000000000..95dc5c209 Binary files /dev/null and b/docs/screenshots/slate_SHorizontalBox2.png differ diff --git a/docs/screenshots/slate_SImage.png b/docs/screenshots/slate_SImage.png new file mode 100644 index 000000000..9d03b6450 Binary files /dev/null and b/docs/screenshots/slate_SImage.png differ diff --git a/docs/screenshots/slate_SObjectPropertyEntryBox.png b/docs/screenshots/slate_SObjectPropertyEntryBox.png new file mode 100644 index 000000000..b783f4e03 Binary files /dev/null and b/docs/screenshots/slate_SObjectPropertyEntryBox.png differ diff --git a/docs/screenshots/slate_SPythonEditorViewport.png b/docs/screenshots/slate_SPythonEditorViewport.png new file mode 100644 index 000000000..8de4d801f Binary files /dev/null and b/docs/screenshots/slate_SPythonEditorViewport.png differ diff --git a/docs/screenshots/slate_SScrollBox.png b/docs/screenshots/slate_SScrollBox.png new file mode 100644 index 000000000..a011a186f Binary files /dev/null and b/docs/screenshots/slate_SScrollBox.png differ diff --git a/docs/screenshots/slate_STextBlock.png b/docs/screenshots/slate_STextBlock.png new file mode 100644 index 000000000..81c87ebe3 Binary files /dev/null and b/docs/screenshots/slate_STextBlock.png differ diff --git a/docs/screenshots/slate_STextBlock_red.png b/docs/screenshots/slate_STextBlock_red.png new file mode 100644 index 000000000..062b1c4c5 Binary files /dev/null and b/docs/screenshots/slate_STextBlock_red.png differ diff --git a/docs/screenshots/slate_SVerticalBox.png b/docs/screenshots/slate_SVerticalBox.png new file mode 100644 index 000000000..e0b11f2b0 Binary files /dev/null and b/docs/screenshots/slate_SVerticalBox.png differ diff --git a/docs/screenshots/slate_SWindow.png b/docs/screenshots/slate_SWindow.png new file mode 100644 index 000000000..cc4977d44 Binary files /dev/null and b/docs/screenshots/slate_SWindow.png differ diff --git a/docs/screenshots/slate_Slot.png b/docs/screenshots/slate_Slot.png new file mode 100644 index 000000000..a7c473083 Binary files /dev/null and b/docs/screenshots/slate_Slot.png differ diff --git a/examples/asset_metadata_tag.py b/examples/asset_metadata_tag.py new file mode 100644 index 000000000..dfa036f36 --- /dev/null +++ b/examples/asset_metadata_tag.py @@ -0,0 +1,19 @@ +import unreal_engine as ue +from unreal_engine.classes import EditorAssetLibrary + +asset = ue.get_selected_assets()[0] + +# reflection-based api +EditorAssetLibrary.SetMetadataTag(asset, 'Foo', 'Bar') +for value in EditorAssetLibrary.GetMetadataTagValues(asset): + print(value) +print(EditorAssetLibrary.GetMetadataTag(asset, 'Foo')) + + +# native api +asset.set_metadata_tag('Test001', 'Text002') +asset.set_metadata_tag('Test003', 'Text004') +for key in asset.metadata_tags(): + print(key) +print(asset.has_metadata_tag('Foo')) # bool +print(asset.get_metadata_tag('Test001')) diff --git a/examples/edit_level_blueprint.py b/examples/edit_level_blueprint.py new file mode 100644 index 000000000..a7ddd961c --- /dev/null +++ b/examples/edit_level_blueprint.py @@ -0,0 +1,8 @@ +import unreal_engine as ue +from unreal_engine.structs import EdGraphPinType + +world = ue.get_editor_world() +level_bp = world.CurrentLevel.get_level_script_blueprint() +pin = EdGraphPinType(PinCategory='string') +ue.blueprint_add_member_variable(level_bp, 'TestString', pin) +ue.open_editor_for_asset(level_bp) diff --git a/examples/multi_in_editor_capture.py b/examples/multi_in_editor_capture.py new file mode 100644 index 000000000..a19680e11 --- /dev/null +++ b/examples/multi_in_editor_capture.py @@ -0,0 +1,19 @@ +import unreal_engine as ue +from unreal_engine.classes import AutomatedLevelSequenceCapture, World +from unreal_engine.structs import SoftObjectPath + +level_sequence_mappings = { + '/Game/SequenceForDefault001': '/Game/Default001', + '/Game/SequenceForVR001': '/Game/VR001' +} + +def setup_sequence(capture): + ue.open_editor_for_asset(ue.load_object(World, level_sequence_mappings[capture.LevelSequenceAsset.AssetPathName])) + +captures = [] +for sequence_asset in level_sequence_mappings: + capture = AutomatedLevelSequenceCapture() + capture.LevelSequenceAsset = SoftObjectPath(AssetPathName=sequence_asset) + captures.append(capture) + +ue.in_editor_capture(captures, setup_sequence) diff --git a/examples/sub_menu.py b/examples/sub_menu.py new file mode 100644 index 000000000..9d7901f2f --- /dev/null +++ b/examples/sub_menu.py @@ -0,0 +1,30 @@ +import unreal_engine as ue + +def open_submenu001(builder): + builder.begin_section('submenu001', 'i am a tooltip') + builder.add_menu_entry('sub_one', 'tooltip', lambda: ue.log('hello from submenu001')) + builder.add_menu_entry('sub_one_2', 'tooltip 2', lambda: ue.log('hello again')) + builder.end_section() + +def open_sub_submenu(builder): + builder.begin_section('sub_submenu003', 'i am a tooltip for the submenu') + builder.add_menu_entry('sub_sub_three', 'tooltip', lambda: ue.log('hello from sub_submenu003')) + builder.end_section() + +def open_submenu002(builder): + builder.begin_section('submenu002', 'i am a tooltip') + builder.add_menu_entry('sub_two', 'tooltip', lambda: ue.log('hello from submenu002')) + builder.add_sub_menu('sub sub menu', 'tooltip !', open_sub_submenu) + builder.end_section() + + +def open_menu(builder): + builder.begin_section('test1', 'test2') + builder.add_menu_entry('one', 'two', lambda: ue.log('ciao 1')) + builder.add_sub_menu('i am a submenu', 'tooltip for the submenu', open_submenu001) + builder.add_menu_entry('three', 'four', lambda: ue.log('ciao 2')) + builder.add_sub_menu('i am another submenu', 'tooltip for the second submenu', open_submenu002) + builder.end_section() + + +ue.add_menu_bar_extension('SimpleMenuBarExtension', open_menu) \ No newline at end of file diff --git a/tools/release_win64.py b/tools/release_win64.py index c07ffa3f7..057800496 100644 --- a/tools/release_win64.py +++ b/tools/release_win64.py @@ -5,8 +5,11 @@ import shutil import zipfile -UE_VERSIONS = ['4.15', '4.16', '4.17', '4.18', '4.19', '4.20'] -PYTHON_VERSIONS = ["C:/Program Files/Python36", "C:/Program Files/Python37", "C:/Python27"] +UE_VERSIONS = ['4.20', '4.21', '4.22'] +PYTHON_VERSIONS = ["C:/Program Files/Python37", "C:/Program Files/Python36", "C:/Python27"] +MSBUILD = 'C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/MSBuild/15.0/Bin/MSBuild.exe' +UE_PATH = 'C:/Program Files/Epic Games' +PROJECTS_PATH = 'C:/Users/rober/Documents/Unreal Projects' RELEASE_DIR = sys.argv[1].rstrip('/') @@ -20,28 +23,27 @@ def msbuild(project, python_version): base_environ = os.environ base_environ.update({'PYTHONHOME': python_version}) base_environ.update({'UEP_ENABLE_UNITY_BUILD': '1'}) - #vs = '"C:/Program Files (x86)/MSBuild/14.0/Bin/MSBuild.exe"' - vs = '"C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/MSBuild/15.0/Bin/MSBuild.exe"' - process = subprocess.Popen('{0} {1} /m /t:Rebuild /p:Configuration="Development Editor" /p:Platform=Win64'.format(vs, project), env=base_environ) + vs = '"{}"'.format(MSBUILD) + process = subprocess.Popen('{0} "{1}" /m /t:Rebuild /p:Configuration="Development Editor" /p:Platform=Win64'.format(vs, project), env=base_environ) while process.poll() is None: time.sleep(0.5) if process.returncode != 0: sys.exit(process.returncode) def commandlet(version, project): - ue_editor = os.path.join('D:/', 'UE_{0}'.format(version), 'Engine/Binaries/Win64/UE4Editor-Cmd.exe') - process = subprocess.Popen('{0} D:/{1}/{2}.uproject -run=PyCommandlet D:/{3}/Plugins/UnrealEnginePython/tools/release_check.py'.format(ue_editor, project, project, project)) + ue_editor = os.path.join(UE_PATH, 'UE_{0}'.format(version), 'Engine/Binaries/Win64/UE4Editor-Cmd.exe') + process = subprocess.Popen('{0} {1}/{2}/{3}.uproject -run=PyCommandlet {1}/{4}/Plugins/UnrealEnginePython/tools/release_check.py'.format(ue_editor, PROJECTS_PATH, project, project, project)) while process.poll() is None: time.sleep(0.5) # ignore return code, has too much different meanings for commandlets def git(project): - process = subprocess.Popen('git checkout master', cwd='D:/{0}/Plugins/UnrealEnginePython'.format(project)) + process = subprocess.Popen('git checkout master', cwd='{0}/{1}/Plugins/UnrealEnginePython'.format(PROJECTS_PATH, project)) while process.poll() is None: time.sleep(0.5) if process.returncode != 0: sys.exit(process.returncode) - process = subprocess.Popen('git pull', cwd='D:/{0}/Plugins/UnrealEnginePython'.format(project)) + process = subprocess.Popen('git pull', cwd='{0}/{1}/Plugins/UnrealEnginePython'.format(PROJECTS_PATH, project)) while process.poll() is None: time.sleep(0.5) if process.returncode != 0: @@ -51,7 +53,7 @@ def git(project): main_start = time.time() for ue_version in UE_VERSIONS: project = 'PyTest{0}'.format(ue_version.replace('.', '')) - sln = os.path.join('D:/', project, '{0}.sln'.format(project)) + sln = os.path.join(PROJECTS_PATH, project, '{0}.sln'.format(project)) git(project) for python_version in PYTHON_VERSIONS: python_sanitized = os.path.basename(python_version).lower() @@ -62,9 +64,9 @@ def git(project): commandlet(ue_version, project) end = time.time() for item in ('UE4Editor.modules', 'UE4Editor-UnrealEnginePython.dll', 'UE4Editor-PythonConsole.dll', 'UE4Editor-PythonEditor.dll', 'UE4Editor-PythonAutomation.dll'): - shutil.copyfile('D:/{0}/Plugins/UnrealEnginePython/Binaries/Win64/{1}'.format(project, item), '{0}/UnrealEnginePython/Binaries/Win64/{1}'.format(RELEASE_DIR, item)) + shutil.copyfile('{0}/{1}/Plugins/UnrealEnginePython/Binaries/Win64/{2}'.format(PROJECTS_PATH, project, item), '{0}/UnrealEnginePython/Binaries/Win64/{1}'.format(RELEASE_DIR, item)) if python_sanitized == 'python36': - shutil.copyfile('D:/{0}/Plugins/UnrealEnginePython/Binaries/Win64/{1}'.format(project, item), '{0}/Embedded/UnrealEnginePython/Binaries/Win64/{1}'.format(RELEASE_DIR, item)) + shutil.copyfile('{0}/{1}/Plugins/UnrealEnginePython/Binaries/Win64/{2}'.format(PROJECTS_PATH, project, item), '{0}/Embedded/UnrealEnginePython/Binaries/Win64/{1}'.format(RELEASE_DIR, item)) filename = 'UnrealEnginePython_{0}_{1}_{2}_win64.zip'.format(os.path.basename(RELEASE_DIR), ue_version.replace('.','_'), python_sanitized) zh = zipfile.ZipFile(os.path.join(RELEASE_DIR, filename), 'w', zipfile.ZIP_DEFLATED) zipdir(os.path.join(RELEASE_DIR, 'UnrealEnginePython'), zh, RELEASE_DIR) diff --git a/tutorials/PlottingGraphsWithMatplotlibAndUnrealEnginePython.md b/tutorials/PlottingGraphsWithMatplotlibAndUnrealEnginePython.md index 8ff7795c7..4d9c5246a 100644 --- a/tutorials/PlottingGraphsWithMatplotlibAndUnrealEnginePython.md +++ b/tutorials/PlottingGraphsWithMatplotlibAndUnrealEnginePython.md @@ -194,7 +194,7 @@ class PlotComponent: dpi = 72.0 self.texture = ue.create_transient_texture(width, height, EPixelFormat.PF_R8G8B8A8) - self.uobject.get_owner().StaticMesh.OverrideMaterials[0].set_material_texture_parameter('Graph', self.texture) + self.uobject.get_owner().StaticMeshComponent.OverrideMaterials[0].set_material_texture_parameter('Graph', self.texture) self.fig = plt.figure(1) self.fig.set_dpi(dpi) diff --git a/tutorials/PlottingGraphsWithMatplotlibAndUnrealEnginePython_Assets/plotter.py b/tutorials/PlottingGraphsWithMatplotlibAndUnrealEnginePython_Assets/plotter.py index e8e8418a4..6466adee2 100644 --- a/tutorials/PlottingGraphsWithMatplotlibAndUnrealEnginePython_Assets/plotter.py +++ b/tutorials/PlottingGraphsWithMatplotlibAndUnrealEnginePython_Assets/plotter.py @@ -14,7 +14,7 @@ def begin_play(self): dpi = 72.0 self.texture = ue.create_transient_texture(width, height, EPixelFormat.PF_R8G8B8A8) - self.uobject.get_owner().StaticMesh.OverrideMaterials[0].set_material_texture_parameter('Graph', self.texture) + self.uobject.get_owner().StaticMeshComponent.OverrideMaterials[0].set_material_texture_parameter('Graph', self.texture) self.fig = plt.figure(1) self.fig.set_dpi(dpi)