// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. #include "PyGenUtil.h" #include "PyUtil.h" #include "PyWrapperBase.h" #include "Internationalization/BreakIterator.h" #include "UObject/UnrealType.h" #include "UObject/Class.h" #include "UObject/Package.h" #include "Engine/BlueprintGeneratedClass.h" #include "Engine/UserDefinedStruct.h" #include "Engine/UserDefinedEnum.h" #if WITH_PYTHON namespace PyGenUtil { const FName ScriptNameMetaDataKey = TEXT("ScriptName"); const FName ScriptNoExportMetaDataKey = TEXT("ScriptNoExport"); const FName BlueprintTypeMetaDataKey = TEXT("BlueprintType"); const FName NotBlueprintTypeMetaDataKey = TEXT("NotBlueprintType"); const FName BlueprintSpawnableComponentMetaDataKey = TEXT("BlueprintSpawnableComponent"); const FName BlueprintGetterMetaDataKey = TEXT("BlueprintGetter"); const FName BlueprintSetterMetaDataKey = TEXT("BlueprintSetter"); TSharedPtr NameBreakIterator; void FGeneratedWrappedMethod::ToPython(FPyMethodWithClosureDef& OutPyMethod) const { OutPyMethod.MethodName = MethodName.GetData(); OutPyMethod.MethodDoc = MethodDoc.GetData(); OutPyMethod.MethodCallback = MethodCallback; OutPyMethod.MethodFlags = MethodFlags; OutPyMethod.MethodClosure = (void*)this; } void FGeneratedWrappedGetSet::ToPython(PyGetSetDef& OutPyGetSet) const { OutPyGetSet.name = (char*)GetSetName.GetData(); OutPyGetSet.doc = (char*)GetSetDoc.GetData(); OutPyGetSet.get = GetCallback; OutPyGetSet.set = SetCallback; OutPyGetSet.closure = (void*)this; } FGeneratedWrappedPropertyDoc::FGeneratedWrappedPropertyDoc(const UProperty* InProp) { PythonPropName = PyGenUtil::GetPropertyPythonName(InProp); const FString PropTooltip = PyGenUtil::GetFieldTooltip(InProp); DocString = PyGenUtil::PythonizePropertyTooltip(PropTooltip, InProp); EditorDocString = PyGenUtil::PythonizePropertyTooltip(PropTooltip, InProp, CPF_EditConst); } bool FGeneratedWrappedPropertyDoc::SortPredicate(const FGeneratedWrappedPropertyDoc& InOne, const FGeneratedWrappedPropertyDoc& InTwo) { return InOne.PythonPropName < InTwo.PythonPropName; } FString FGeneratedWrappedPropertyDoc::BuildDocString(const TArray& InDocs, const bool bEditorVariant) { FString Str; AppendDocString(InDocs, Str, bEditorVariant); return Str; } void FGeneratedWrappedPropertyDoc::AppendDocString(const TArray& InDocs, FString& OutStr, const bool bEditorVariant) { if (!InDocs.Num()) { return; } if (!OutStr.IsEmpty()) { if (OutStr[OutStr.Len() - 1] != TEXT('\n')) { OutStr += TEXT('\n'); } OutStr += TEXT("\n----------------------------------------------------------------------\n"); } OutStr += TEXT("Editor Properties: (see get_editor_property/set_editor_property)\n"); for (const FGeneratedWrappedPropertyDoc& Doc : InDocs) { TArray DocStringLines; (bEditorVariant ? Doc.EditorDocString : Doc.DocString).ParseIntoArrayLines(DocStringLines, /*bCullEmpty*/false); OutStr += TEXT('\n'); OutStr += Doc.PythonPropName; for (const FString& DocStringLine : DocStringLines) { OutStr += TEXT("\n "); OutStr += DocStringLine; } OutStr += TEXT('\n'); } OutStr += TEXT("\n----------------------------------------------------------------------"); } bool FGeneratedWrappedType::Finalize() { PyType.tp_name = TypeName.GetData(); PyType.tp_doc = TypeDoc.GetData(); PyMethods.Reserve(TypeMethods.Num() + 1); for (const FGeneratedWrappedMethod& TypeMethod : TypeMethods) { FPyMethodWithClosureDef& PyMethod = PyMethods[PyMethods.AddZeroed()]; TypeMethod.ToPython(PyMethod); } PyMethods.AddZeroed(); // null terminator PyMethods.Reserve(PyGetSets.Num() + 1); for (const FGeneratedWrappedGetSet& TypeGetSet : TypeGetSets) { PyGetSetDef& PyGetSet = PyGetSets[PyGetSets.AddZeroed()]; TypeGetSet.ToPython(PyGetSet); } PyGetSets.AddZeroed(); // null terminator PyType.tp_getset = PyGetSets.GetData(); if (PyType_Ready(&PyType) == 0) { FPyMethodWithClosureDef::AddMethods(PyMethods.GetData(), &PyType); FPyWrapperBaseMetaData::SetMetaData(&PyType, MetaData.Get()); return true; } return false; } FUTF8Buffer TCHARToUTF8Buffer(const TCHAR* InStr) { auto ToUTF8Buffer = [](const char* InUTF8Str) { int32 UTF8StrLen = 0; while (InUTF8Str[UTF8StrLen++] != 0) {} // Count includes the null terminator FUTF8Buffer UTF8Buffer; UTF8Buffer.Append(InUTF8Str, UTF8StrLen); return UTF8Buffer; }; return ToUTF8Buffer(TCHAR_TO_UTF8(InStr)); } PyObject* GetPostInitFunc(PyTypeObject* InPyType) { FPyObjectPtr PostInitFunc = FPyObjectPtr::StealReference(PyObject_GetAttrString((PyObject*)InPyType, PostInitFuncName)); if (!PostInitFunc) { PyUtil::SetPythonError(PyExc_TypeError, InPyType, *FString::Printf(TEXT("Python type has no '%s' function"), UTF8_TO_TCHAR(PostInitFuncName))); return nullptr; } if (!PyCallable_Check(PostInitFunc)) { PyUtil::SetPythonError(PyExc_TypeError, InPyType, *FString::Printf(TEXT("Python type attribute '%s' is not callable"), UTF8_TO_TCHAR(PostInitFuncName))); return nullptr; } // Only test arguments for actual functions and methods (the base type exposed from C will be a 'method_descriptor') if (PyFunction_Check(PostInitFunc) || PyMethod_Check(PostInitFunc)) { TArray FuncArgNames; if (!PyUtil::InspectFunctionArgs(PostInitFunc, FuncArgNames)) { PyUtil::SetPythonError(PyExc_Exception, InPyType, *FString::Printf(TEXT("Failed to inspect the arguments for '%s'"), UTF8_TO_TCHAR(PostInitFuncName))); return nullptr; } if (FuncArgNames.Num() != 1) { PyUtil::SetPythonError(PyExc_TypeError, InPyType, *FString::Printf(TEXT("'%s' must take a single parameter ('self')"), UTF8_TO_TCHAR(PostInitFuncName))); return nullptr; } } return PostInitFunc.Release(); } void AddStructInitParam(const TCHAR* InUnrealPropName, const TCHAR* InPythonAttrName, TArray& OutInitParams) { FGeneratedWrappedMethodParameter& InitParam = OutInitParams[OutInitParams.AddDefaulted()]; InitParam.ParamName = TCHARToUTF8Buffer(InPythonAttrName); InitParam.ParamPropName = InUnrealPropName; InitParam.ParamDefaultValue = FString(); } bool ParseMethodParameters(PyObject* InArgs, PyObject* InKwds, const TArray& InParamDef, const char* InPyMethodName, TArray& OutPyParams) { if (!InArgs || !PyTuple_Check(InArgs) || (InKwds && !PyDict_Check(InKwds)) || !InPyMethodName) { PyErr_BadInternalCall(); return false; } const Py_ssize_t NumArgs = PyTuple_GET_SIZE(InArgs); const Py_ssize_t NumKeywords = InKwds ? PyDict_Size(InKwds) : 0; if (NumArgs + NumKeywords > InParamDef.Num()) { PyErr_Format(PyExc_TypeError, "%s() takes at most %d argument%s (%d given)", InPyMethodName, InParamDef.Num(), (InParamDef.Num() == 1 ? "" : "s"), (int32)(NumArgs + NumKeywords)); return false; } // Parse both keyword and index args in the same loop (favor keywords, fallback to index) Py_ssize_t RemainingKeywords = NumKeywords; for (int32 Index = 0; Index < InParamDef.Num(); ++Index) { const FGeneratedWrappedMethodParameter& ParamDef = InParamDef[Index]; PyObject* ParsedArg = nullptr; if (RemainingKeywords > 0) { ParsedArg = PyDict_GetItemString(InKwds, ParamDef.ParamName.GetData()); } if (ParsedArg) { --RemainingKeywords; if (Index < NumArgs) { PyErr_Format(PyExc_TypeError, "Argument given by name ('%s') and position (%d)", ParamDef.ParamName.GetData(), Index + 1); return false; } } else if (RemainingKeywords > 0 && PyErr_Occurred()) { return false; } else if (Index < NumArgs) { ParsedArg = PyTuple_GET_ITEM(InArgs, Index); } if (ParsedArg || ParamDef.ParamDefaultValue.IsSet()) { OutPyParams.Add(ParsedArg); continue; } PyErr_Format(PyExc_TypeError, "Required argument '%s' (pos %d) not found", ParamDef.ParamName.GetData(), Index + 1); return false; } // Report any extra keyword args if (RemainingKeywords > 0) { PyObject* Key = nullptr; PyObject* Value = nullptr; Py_ssize_t Index = 0; while (PyDict_Next(InKwds, &Index, &Key, &Value)) { const FUTF8Buffer Keyword = TCHARToUTF8Buffer(*PyUtil::PyObjectToUEString(Key)); const bool bIsExpected = InParamDef.ContainsByPredicate([&Keyword](const FGeneratedWrappedMethodParameter& ParamDef) { return FCStringAnsi::Strcmp(Keyword.GetData(), ParamDef.ParamName.GetData()) == 0; }); if (!bIsExpected) { PyErr_Format(PyExc_TypeError, "'%s' is an invalid keyword argument for this function", Keyword.GetData()); return false; } } } return true; } bool IsBlueprintExposedClass(const UClass* InClass) { for (const UClass* ParentClass = InClass; ParentClass; ParentClass = ParentClass->GetSuperClass()) { if (ParentClass->GetBoolMetaData(BlueprintTypeMetaDataKey) || ParentClass->HasMetaData(BlueprintSpawnableComponentMetaDataKey)) { return true; } if (ParentClass->GetBoolMetaData(NotBlueprintTypeMetaDataKey)) { return false; } } return false; } bool IsBlueprintExposedStruct(const UStruct* InStruct) { for (const UStruct* ParentStruct = InStruct; ParentStruct; ParentStruct = ParentStruct->GetSuperStruct()) { if (ParentStruct->GetBoolMetaData(BlueprintTypeMetaDataKey)) { return true; } if (ParentStruct->GetBoolMetaData(NotBlueprintTypeMetaDataKey)) { return false; } } return false; } bool IsBlueprintExposedEnum(const UEnum* InEnum) { if (InEnum->GetBoolMetaData(BlueprintTypeMetaDataKey)) { return true; } if (InEnum->GetBoolMetaData(NotBlueprintTypeMetaDataKey)) { return false; } return false; } bool IsBlueprintExposedProperty(const UProperty* InProp) { return InProp->HasAnyPropertyFlags(CPF_BlueprintVisible); } bool IsBlueprintExposedFunction(const UFunction* InFunc) { return InFunc->HasAnyFunctionFlags(FUNC_BlueprintCallable | FUNC_BlueprintEvent) && !InFunc->HasMetaData(BlueprintGetterMetaDataKey) && !InFunc->HasMetaData(BlueprintSetterMetaDataKey); } bool IsBlueprintExposedField(const UField* InField) { if (const UProperty* Prop = Cast(InField)) { return IsBlueprintExposedProperty(Prop); } if (const UFunction* Func = Cast(InField)) { return IsBlueprintExposedFunction(Func); } return false; } bool HasBlueprintExposedFields(const UStruct* InStruct) { for (TFieldIterator FieldIt(InStruct); FieldIt; ++FieldIt) { if (IsBlueprintExposedField(*FieldIt)) { return true; } } return false; } bool IsBlueprintGeneratedClass(const UClass* InClass) { // Need to use IsA rather than IsChildOf since we want to test the type of InClass itself *NOT* the class instance represented by InClass const UObject* ClassObject = InClass; return ClassObject->IsA(); } bool IsBlueprintGeneratedStruct(const UStruct* InStruct) { return InStruct->IsA(); } bool IsBlueprintGeneratedEnum(const UEnum* InEnum) { return InEnum->IsA(); } bool ShouldExportClass(const UClass* InClass) { return IsBlueprintExposedClass(InClass) || HasBlueprintExposedFields(InClass); } bool ShouldExportStruct(const UStruct* InStruct) { return IsBlueprintExposedStruct(InStruct) || HasBlueprintExposedFields(InStruct); } bool ShouldExportEnum(const UEnum* InEnum) { return IsBlueprintExposedEnum(InEnum); } bool ShouldExportProperty(const UProperty* InProp) { const bool bCanScriptExport = !InProp->HasMetaData(ScriptNoExportMetaDataKey); return bCanScriptExport && IsBlueprintExposedProperty(InProp); } bool ShouldExportEditorOnlyProperty(const UProperty* InProp) { const bool bCanScriptExport = !InProp->HasMetaData(ScriptNoExportMetaDataKey); return bCanScriptExport && GIsEditor && InProp->HasAnyPropertyFlags(CPF_Edit); } bool ShouldExportFunction(const UFunction* InFunc) { const bool bCanScriptExport = !InFunc->HasMetaData(ScriptNoExportMetaDataKey); return bCanScriptExport && IsBlueprintExposedFunction(InFunc); } FString PythonizeName(const FString& InName, const EPythonizeNameCase InNameCase) { FString PythonizedName; PythonizedName.Reserve(InName.Len() + 10); if (!NameBreakIterator.IsValid()) { NameBreakIterator = FBreakIterator::CreateCamelCaseBreakIterator(); } NameBreakIterator->SetString(InName); for (int32 PrevBreak = 0, NameBreak = NameBreakIterator->MoveToNext(); NameBreak != INDEX_NONE; NameBreak = NameBreakIterator->MoveToNext()) { const int32 OrigPythonizedNameLen = PythonizedName.Len(); // Append an underscore if this was a break between two parts of the identifier, *and* the previous character isn't already an underscore if (OrigPythonizedNameLen > 0 != 0 && PythonizedName[OrigPythonizedNameLen - 1] != TEXT('_')) { PythonizedName += TEXT('_'); } // Append this part of the identifier PythonizedName.AppendChars(&InName[PrevBreak], NameBreak - PrevBreak); // Remove any trailing underscores in the last part of the identifier while (PythonizedName.Len() > OrigPythonizedNameLen) { const int32 CharIndex = PythonizedName.Len() - 1; if (PythonizedName[CharIndex] != TEXT('_')) { break; } PythonizedName.RemoveAt(CharIndex, 1, false); } PrevBreak = NameBreak; } NameBreakIterator->ClearString(); if (InNameCase == EPythonizeNameCase::Lower) { PythonizedName.ToLowerInline(); } else if (InNameCase == EPythonizeNameCase::Upper) { PythonizedName.ToUpperInline(); } return PythonizedName; } FString PythonizePropertyName(const FString& InName, const EPythonizeNameCase InNameCase) { int32 NameOffset = 0; for (;;) { // Strip the "b" prefix from bool names if (InName.Len() - NameOffset >= 2 && InName[NameOffset] == TEXT('b') && FChar::IsUpper(InName[NameOffset + 1])) { NameOffset += 1; continue; } // Strip the "In" prefix from names if (InName.Len() - NameOffset >= 3 && InName[NameOffset] == TEXT('I') && InName[NameOffset + 1] == TEXT('n') && FChar::IsUpper(InName[NameOffset + 2])) { NameOffset += 2; continue; } // Strip the "Out" prefix from names if (InName.Len() - NameOffset >= 4 && InName[NameOffset] == TEXT('O') && InName[NameOffset + 1] == TEXT('u') && InName[NameOffset + 2] == TEXT('t') && FChar::IsUpper(InName[NameOffset + 3])) { NameOffset += 3; continue; } // Nothing more to strip break; } return PythonizeName(NameOffset ? InName.RightChop(NameOffset) : InName, InNameCase); } FString PythonizePropertyTooltip(const FString& InTooltip, const UProperty* InProp, const uint64 InReadOnlyFlags) { return PythonizeTooltip(InTooltip, FPythonizeTooltipContext(InProp, nullptr, InReadOnlyFlags)); } FString PythonizeFunctionTooltip(const FString& InTooltip, const UStruct* InParamsStruct) { return PythonizeTooltip(InTooltip, FPythonizeTooltipContext(nullptr, InParamsStruct)); } FString PythonizeTooltip(const FString& InTooltip, const FPythonizeTooltipContext& InContext) { FString PythonizedTooltip; PythonizedTooltip.Reserve(InTooltip.Len()); int32 TooltipIndex = 0; const int32 TooltipLen = InTooltip.Len(); TArray, TInlineAllocator<4>> ParsedMiscTokens; TArray, TInlineAllocator<8>> ParsedParamTokens; FString ReturnToken; auto SkipToNextToken = [&InTooltip, &TooltipIndex, &TooltipLen]() { while (TooltipIndex < TooltipLen && (FChar::IsWhitespace(InTooltip[TooltipIndex]) || InTooltip[TooltipIndex] == TEXT('-'))) { ++TooltipIndex; } }; auto ParseSimpleToken = [&InTooltip, &TooltipIndex, &TooltipLen](FString& OutToken) { while (TooltipIndex < TooltipLen && !FChar::IsWhitespace(InTooltip[TooltipIndex])) { OutToken += InTooltip[TooltipIndex++]; } }; auto ParseComplexToken = [&InTooltip, &TooltipIndex, &TooltipLen](FString& OutToken) { while (TooltipIndex < TooltipLen && InTooltip[TooltipIndex] != TEXT('@')) { // Convert a new-line within a token to a space if (FChar::IsLinebreak(InTooltip[TooltipIndex])) { while (TooltipIndex < TooltipLen && FChar::IsLinebreak(InTooltip[TooltipIndex])) { ++TooltipIndex; } while (TooltipIndex < TooltipLen && FChar::IsWhitespace(InTooltip[TooltipIndex])) { ++TooltipIndex; } OutToken += TEXT(' '); } // Sanity check in case the first character after the new-line is @ if (TooltipIndex < TooltipLen && InTooltip[TooltipIndex] != TEXT('@')) { OutToken += InTooltip[TooltipIndex++]; } } OutToken.TrimEndInline(); }; // Append the property type (if given) if (InContext.Prop) { PythonizedTooltip += TEXT("type: "); AppendPropertyPythonType(InContext.Prop, PythonizedTooltip, /*bIncludeReadWriteState*/true, InContext.ReadOnlyFlags); PythonizedTooltip += TEXT('\n'); } // Parse the tooltip for its tokens and values (basic content goes directly into PythonizedTooltip) for (; TooltipIndex < TooltipLen;) { if (InTooltip[TooltipIndex] == TEXT('@')) { ++TooltipIndex; // Walk over the @ if (InTooltip[TooltipIndex] == TEXT('@')) { // Literal @ character PythonizedTooltip += TEXT('@'); continue; } // Parse out the token name FString TokenName; SkipToNextToken(); ParseSimpleToken(TokenName); if (TokenName == TEXT("param")) { // Parse out the parameter name FString ParamName; SkipToNextToken(); ParseSimpleToken(ParamName); // Parse out the parameter comment FString ParamComment; SkipToNextToken(); ParseComplexToken(ParamComment); ParsedParamTokens.Add(MakeTuple(FName(*ParamName), MoveTemp(ParamComment))); } else if (TokenName == TEXT("return") || TokenName == TEXT("returns")) { // Parse out the return value token SkipToNextToken(); ParseComplexToken(ReturnToken); } else { // Parse out the token value FString TokenValue; SkipToNextToken(); ParseComplexToken(TokenValue); ParsedMiscTokens.Add(MakeTuple(MoveTemp(TokenName), MoveTemp(TokenValue))); } } else { // Convert duplicate new-lines to a single new-line if (FChar::IsLinebreak(InTooltip[TooltipIndex])) { while (TooltipIndex < TooltipLen && FChar::IsLinebreak(InTooltip[TooltipIndex])) { ++TooltipIndex; } PythonizedTooltip += TEXT('\n'); } else { // Normal character PythonizedTooltip += InTooltip[TooltipIndex++]; } } } PythonizedTooltip.TrimEndInline(); // Process the misc tokens into PythonizedTooltip for (const auto& MiscTokenPair : ParsedMiscTokens) { PythonizedTooltip += TEXT('\n'); PythonizedTooltip += MiscTokenPair.Key; PythonizedTooltip += TEXT(": "); PythonizedTooltip += MiscTokenPair.Value; } // Process the param tokens into PythonizedTooltip auto AppendParamTypeDoc = [&PythonizedTooltip](const UProperty* InParamProp) { PythonizedTooltip += TEXT(" ("); AppendPropertyPythonType(InParamProp, PythonizedTooltip); PythonizedTooltip += TEXT(')'); }; for (const auto& ParamTokenPair : ParsedParamTokens) { PythonizedTooltip += TEXT('\n'); PythonizedTooltip += TEXT("param: "); PythonizedTooltip += PythonizePropertyName(ParamTokenPair.Key.ToString(), EPythonizeNameCase::Lower); if (InContext.ParamsStruct) { if (const UProperty* ParamProp = InContext.ParamsStruct->FindPropertyByName(ParamTokenPair.Key)) { AppendParamTypeDoc(ParamProp); } } if (!ParamTokenPair.Value.IsEmpty()) { PythonizedTooltip += TEXT(" -- "); PythonizedTooltip += ParamTokenPair.Value; } } if (InContext.ParamsStruct) { for (TFieldIterator ParamIt(InContext.ParamsStruct); ParamIt; ++ParamIt) { const UProperty* ParamProp = *ParamIt; const bool bHasProcessedParamProp = ParsedParamTokens.ContainsByPredicate([&ParamProp](const TTuple& ParamTokenPair) { return ParamTokenPair.Key == ParamProp->GetFName(); }); if (bHasProcessedParamProp) { continue; } PythonizedTooltip += TEXT('\n'); PythonizedTooltip += TEXT("param: "); PythonizedTooltip += PythonizePropertyName(ParamProp->GetName(), EPythonizeNameCase::Lower); AppendParamTypeDoc(ParamProp); } } // Process the return token into PythonizedTooltip if (!ReturnToken.IsEmpty()) { PythonizedTooltip += TEXT('\n'); PythonizedTooltip += TEXT("return: "); PythonizedTooltip += ReturnToken; } PythonizedTooltip.TrimEndInline(); return PythonizedTooltip; } FString GetFieldModule(const UField* InField) { // todo: should have meta-data on the type that can override this for scripting UPackage* ScriptPackage = InField->GetOutermost(); const FString PackageName = ScriptPackage->GetName(); if (PackageName.StartsWith(TEXT("/Script/"))) { return PackageName.RightChop(8); // Chop "/Script/" from the name } check(PackageName[0] == TEXT('/')); int32 RootNameEnd = 1; for (; PackageName[RootNameEnd] != TEXT('/'); ++RootNameEnd) {} return PackageName.Mid(1, RootNameEnd - 1); } FString GetModulePythonName(const FName InModuleName, const bool bIncludePrefix) { // Some modules are mapped to others in Python static const FName PythonModuleMappings[][2] = { { TEXT("CoreUObject"), TEXT("Core") }, { TEXT("SlateCore"), TEXT("Slate") }, { TEXT("UnrealEd"), TEXT("Editor") }, { TEXT("PythonScriptPlugin"), TEXT("Python") }, }; FName MappedModuleName = InModuleName; for (const auto& PythonModuleMapping : PythonModuleMappings) { if (InModuleName == PythonModuleMapping[0]) { MappedModuleName = PythonModuleMapping[1]; break; } } const FString ModulePythonName = MappedModuleName.ToString().ToLower(); return bIncludePrefix ? FString::Printf(TEXT("_unreal_%s"), *ModulePythonName) : ModulePythonName; } FString GetClassPythonName(const UClass* InClass) { FString ClassName = InClass->GetMetaData(ScriptNameMetaDataKey); if (ClassName.IsEmpty()) { ClassName = InClass->GetName(); } return ClassName; } FString GetStructPythonName(const UStruct* InStruct) { FString StructName = InStruct->GetMetaData(ScriptNameMetaDataKey); if (StructName.IsEmpty()) { StructName = InStruct->GetName(); } return StructName; } FString GetEnumPythonName(const UEnum* InEnum) { FString EnumName = InEnum->UField::GetMetaData(ScriptNameMetaDataKey); if (EnumName.IsEmpty()) { EnumName = InEnum->GetName(); // Strip the "E" prefix from enum names if (EnumName.Len() >= 2 && EnumName[0] == TEXT('E') && FChar::IsUpper(EnumName[1])) { EnumName.RemoveAt(0, 1, /*bAllowShrinking*/false); } } return EnumName; } FString GetDelegatePythonName(const UFunction* InDelegateSignature) { return InDelegateSignature->GetName().LeftChop(19); // Trim the "__DelegateSignature" suffix from the name } FString GetFunctionPythonName(const UFunction* InFunc) { FString FuncName = InFunc->GetMetaData(ScriptNameMetaDataKey); if (FuncName.IsEmpty()) { FuncName = InFunc->GetName(); } return PythonizeName(FuncName, EPythonizeNameCase::Lower); } FString GetPropertyPythonName(const UProperty* InProp) { FString PropName = InProp->GetMetaData(ScriptNameMetaDataKey); if (PropName.IsEmpty()) { PropName = InProp->GetName(); } return PythonizePropertyName(PropName, EPythonizeNameCase::Lower); } FString GetPropertyPythonType(const UProperty* InProp, const bool bIncludeReadWriteState, const uint64 InReadOnlyFlags) { FString RetStr; AppendPropertyPythonType(InProp, RetStr, bIncludeReadWriteState, InReadOnlyFlags); return RetStr; } void AppendPropertyPythonType(const UProperty* InProp, FString& OutStr, const bool bIncludeReadWriteState, const uint64 InReadOnlyFlags) { FString PropExtendedType; const FString PropType = InProp->GetCPPType(&PropExtendedType, CPPF_ArgumentOrReturnValue); OutStr += PropType; OutStr += PropExtendedType; if (bIncludeReadWriteState) { OutStr += (InProp->HasAnyPropertyFlags(InReadOnlyFlags) ? TEXT(" [Read-Only]") : TEXT(" [Read-Write]")); } } FString GetFieldTooltip(const UField* InField) { // We use the source string here as the culture may change while the editor is running, and also because some versions // of Python (<3.4) can't override the default encoding to UTF-8 so produce errors when trying to print the help docs return *FTextInspector::GetSourceString(InField->GetToolTipText()); } FString GetEnumEntryTooltip(const UEnum* InEnum, const int64 InEntryIndex) { // We use the source string here as the culture may change while the editor is running, and also because some versions // of Python (<3.4) can't override the default encoding to UTF-8 so produce errors when trying to print the help docs return *FTextInspector::GetSourceString(InEnum->GetToolTipTextByIndex(InEntryIndex)); } } #endif // WITH_PYTHON