2121#include " UEPyEditor.h"
2222#endif
2323
24+ #include " PythonDelegate.h"
2425
2526
2627DEFINE_LOG_CATEGORY (LogPython);
@@ -125,6 +126,9 @@ static PyMethodDef ue_PyUObject_methods[] = {
125126 { " get_owner" , (PyCFunction)py_ue_get_owner, METH_VARARGS , " " },
126127 { " get_name" , (PyCFunction)py_ue_get_name, METH_VARARGS , " " },
127128 { " get_full_name" , (PyCFunction)py_ue_get_full_name, METH_VARARGS , " " },
129+
130+ { " bind_event" , (PyCFunction)py_ue_bind_event, METH_VARARGS , " " },
131+
128132#if WITH_EDITOR
129133 { " get_actor_label" , (PyCFunction)py_ue_get_actor_label, METH_VARARGS , " " },
130134 { " set_actor_label" , (PyCFunction)py_ue_set_actor_label, METH_VARARGS , " " },
@@ -557,6 +561,11 @@ bool ue_py_convert_pyobject(PyObject *py_obj, UProperty *prop, uint8 *buffer) {
557561 return true ;
558562 }
559563
564+ // encode a dictionary to a struct
565+ if (PyDict_Check (py_obj)) {
566+ return false ;
567+ }
568+
560569 if (PyObject_IsInstance (py_obj, (PyObject *)&ue_PyUObjectType)) {
561570 ue_PyUObject *ue_obj = (ue_PyUObject *)py_obj;
562571 if (ue_obj->ue_object ->IsA <UClass>()) {
@@ -586,3 +595,73 @@ ue_PyUObject *ue_is_pyuobject(PyObject *obj) {
586595 return nullptr ;
587596 return (ue_PyUObject *)obj;
588597}
598+
599+ // automatically bind events based on class methods names
600+ void ue_autobind_events_for_class (UObject *u_obj, PyObject *py_class) {
601+ PyObject *attrs = PyObject_Dir (py_class);
602+ if (!attrs)
603+ return ;
604+
605+ Py_ssize_t len = PyList_Size (attrs);
606+ for (Py_ssize_t i = 0 ; i < len; i++) {
607+ PyObject *py_attr_name = PyList_GetItem (attrs, i);
608+ if (!py_attr_name || !PyUnicode_Check (py_attr_name))
609+ continue ;
610+ FString attr_name = UTF8_TO_TCHAR (PyUnicode_AsUTF8 (py_attr_name));
611+ if (!attr_name.StartsWith (" on_" , ESearchCase::CaseSensitive))
612+ continue ;
613+ // check if the attr is a callable
614+ PyObject *item = PyObject_GetAttrString (py_class, TCHAR_TO_UTF8 (*attr_name));
615+ if (item && PyCallable_Check (item)) {
616+ TArray<FString> parts;
617+ if (attr_name.ParseIntoArray (parts, UTF8_TO_TCHAR (" _" )) > 1 ) {
618+ FString event_name;
619+ for (FString part : parts) {
620+ FString first_letter = part.Left (1 ).ToUpper ();
621+ part.RemoveAt (0 );
622+ event_name = event_name.Append (first_letter);
623+ event_name = event_name.Append (part);
624+ }
625+ // do not fail on wrong properties
626+ ue_bind_event (u_obj, event_name, item, false );
627+ }
628+ }
629+ }
630+ }
631+
632+ PyObject *ue_bind_event (UObject *u_obj, FString event_name, PyObject *py_callable, bool fail_on_wrong_property) {
633+
634+ UProperty *u_property = u_obj->GetClass ()->FindPropertyByName (FName (*event_name));
635+ if (!u_property) {
636+ if (fail_on_wrong_property)
637+ return PyErr_Format (PyExc_Exception, " unable to find event property %s" , event_name);
638+ Py_INCREF (Py_None);
639+ return Py_None;
640+ }
641+
642+ if (auto casted_prop = Cast<UMulticastDelegateProperty>(u_property)) {
643+ FMulticastScriptDelegate multiscript_delegate = casted_prop->GetPropertyValue_InContainer (u_obj);
644+
645+ FScriptDelegate script_delegate;
646+ UPythonDelegate *py_delegate = NewObject<UPythonDelegate>();
647+ py_delegate->SetPyCallable (py_callable);
648+ py_delegate->SetSignature (casted_prop->SignatureFunction );
649+
650+ // fake UFUNCTION for bypassing checks
651+ script_delegate.BindUFunction (py_delegate, FName (" PyFakeCallable" ));
652+
653+ // add the new delegate
654+ multiscript_delegate.Add (script_delegate);
655+
656+ // re-assign multicast delegate
657+ casted_prop->SetPropertyValue_InContainer (u_obj, multiscript_delegate);
658+
659+ }
660+ else {
661+ if (fail_on_wrong_property)
662+ return PyErr_Format (PyExc_Exception, " property %s is not an event" , event_name);
663+ }
664+
665+ Py_INCREF (Py_None);
666+ return Py_None;
667+ }
0 commit comments