diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonConsoleBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonConsoleBus.h index 4f5a349518..de4ff396b2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonConsoleBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonConsoleBus.h @@ -13,6 +13,12 @@ #include #include +#include + +namespace AZ +{ + struct BehaviorParameter; +} namespace AzToolsFramework { @@ -40,6 +46,8 @@ namespace AzToolsFramework }; using GlobalFunctionCollection = AZStd::vector; virtual void GetGlobalFunctionList(GlobalFunctionCollection& globalFunctionCollection) const = 0; + + virtual AZStd::string FetchPythonTypeName(const AZ::BehaviorParameter& param) = 0; }; //! Interface to signal the phases for the Python virtual machine diff --git a/Code/Tools/SceneAPI/SceneCore/Containers/GraphObjectProxy.cpp b/Code/Tools/SceneAPI/SceneCore/Containers/GraphObjectProxy.cpp index f2fda63ec4..989eccd526 100644 --- a/Code/Tools/SceneAPI/SceneCore/Containers/GraphObjectProxy.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Containers/GraphObjectProxy.cpp @@ -13,9 +13,135 @@ #include #include #include +#include +#include +#include namespace AZ { + namespace Python + { + static const char* const None = "None"; + + class PythonBehaviorInfo final + { + public: + AZ_RTTI(PythonBehaviorInfo, "{8055BD03-5B3B-490D-AEC5-1B1E2616D529}"); + AZ_CLASS_ALLOCATOR(PythonBehaviorInfo, AZ::SystemAllocator, 0); + + static void Reflect(AZ::ReflectContext* context); + + PythonBehaviorInfo(const AZ::BehaviorClass* behaviorClass); + PythonBehaviorInfo() = delete; + + protected: + bool IsMemberLike(const AZ::BehaviorMethod& method, const AZ::TypeId& typeId) const; + AZStd::string FetchPythonType(const AZ::BehaviorParameter& param) const; + void WriteMethod(AZStd::string_view methodName, const AZ::BehaviorMethod& behaviorMethod); + + private: + const AZ::BehaviorClass* m_behaviorClass = nullptr; + AZStd::vector m_methodList; + }; + + PythonBehaviorInfo::PythonBehaviorInfo(const AZ::BehaviorClass* behaviorClass) + : m_behaviorClass(behaviorClass) + { + AZ_Assert(m_behaviorClass, "PythonBehaviorInfo requires a valid behaviorClass pointer"); + for (const auto& entry : behaviorClass->m_methods) + { + WriteMethod(entry.first, *entry.second); + } + } + + void PythonBehaviorInfo::Reflect(AZ::ReflectContext* context) + { + AZ::BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene.graph") + ->Property("className", [](const PythonBehaviorInfo& self) + { return self.m_behaviorClass->m_name; }, nullptr) + ->Property("classUuid", [](const PythonBehaviorInfo& self) + { return self.m_behaviorClass->m_typeId.ToString(); }, nullptr) + ->Property("methodList", BehaviorValueGetter(&PythonBehaviorInfo::m_methodList), nullptr); + } + } + + bool PythonBehaviorInfo::IsMemberLike(const AZ::BehaviorMethod& method, const AZ::TypeId& typeId) const + { + return method.IsMember() || (method.GetNumArguments() > 0 && method.GetArgument(0)->m_typeId == typeId); + } + + AZStd::string PythonBehaviorInfo::FetchPythonType(const AZ::BehaviorParameter& param) const + { + using namespace AzToolsFramework; + EditorPythonConsoleInterface* editorPythonConsoleInterface = AZ::Interface::Get(); + if (editorPythonConsoleInterface) + { + return editorPythonConsoleInterface->FetchPythonTypeName(param); + } + return None; + } + + void PythonBehaviorInfo::WriteMethod(AZStd::string_view methodName, const AZ::BehaviorMethod& behaviorMethod) + { + // if the method is a static method then it is not a part of the abstract class + const bool isMemberLike = IsMemberLike(behaviorMethod, m_behaviorClass->m_typeId); + if (isMemberLike == false) + { + return; + } + + AZStd::string buffer; + AZStd::vector pythonArgs; + + AzFramework::StringFunc::Append(buffer, "def "); + AzFramework::StringFunc::Append(buffer, methodName.data()); + AzFramework::StringFunc::Append(buffer, "("); + + pythonArgs.emplace_back("self"); + + AZStd::string bufferArg; + for (size_t argIndex = 1; argIndex < behaviorMethod.GetNumArguments(); ++argIndex) + { + const AZStd::string* name = behaviorMethod.GetArgumentName(argIndex); + if (!name || name->empty()) + { + bufferArg = AZStd::string::format(" arg%zu", argIndex); + } + else + { + bufferArg = *name; + } + + AZStd::string_view type = FetchPythonType(*behaviorMethod.GetArgument(argIndex)); + if (!type.empty()) + { + AzFramework::StringFunc::Append(bufferArg, ": "); + AzFramework::StringFunc::Append(bufferArg, type.data()); + } + + pythonArgs.push_back(bufferArg); + bufferArg.clear(); + } + + AZStd::string resultValue{ None }; + if (behaviorMethod.HasResult() && behaviorMethod.GetResult()) + { + resultValue = FetchPythonType(*behaviorMethod.GetResult()); + } + + AZStd::string argsList; + AzFramework::StringFunc::Join(buffer, pythonArgs.begin(), pythonArgs.end(), ","); + AzFramework::StringFunc::Append(buffer, ") -> "); + AzFramework::StringFunc::Append(buffer, resultValue.c_str()); + m_methodList.emplace_back(buffer); + } + } + namespace SceneAPI { namespace Containers @@ -25,6 +151,8 @@ namespace AZ AZ::BehaviorContext* behaviorContext = azrtti_cast(context); if (behaviorContext) { + Python::PythonBehaviorInfo::Reflect(context); + behaviorContext->Class(); behaviorContext->Class() @@ -33,6 +161,19 @@ namespace AZ ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) ->Method("CastWithTypeName", &GraphObjectProxy::CastWithTypeName) ->Method("Invoke", &GraphObjectProxy::Invoke) + ->Method("GetClassInfo", [](GraphObjectProxy& self) -> Python::PythonBehaviorInfo* + { + if (self.m_pythonBehaviorInfo) + { + return self.m_pythonBehaviorInfo.get(); + } + if (self.m_behaviorClass) + { + self.m_pythonBehaviorInfo = AZStd::make_shared(self.m_behaviorClass); + return self.m_pythonBehaviorInfo.get(); + } + return nullptr; + }) ; } } diff --git a/Code/Tools/SceneAPI/SceneCore/Containers/GraphObjectProxy.h b/Code/Tools/SceneAPI/SceneCore/Containers/GraphObjectProxy.h index 094b896364..d83a147e5f 100644 --- a/Code/Tools/SceneAPI/SceneCore/Containers/GraphObjectProxy.h +++ b/Code/Tools/SceneAPI/SceneCore/Containers/GraphObjectProxy.h @@ -16,6 +16,11 @@ namespace AZ { + namespace Python + { + class PythonBehaviorInfo; + } + namespace SceneAPI { namespace Containers @@ -44,6 +49,7 @@ namespace AZ private: AZStd::shared_ptr m_graphObject; const AZ::BehaviorClass* m_behaviorClass = nullptr; + AZStd::shared_ptr m_pythonBehaviorInfo; }; } // Containers diff --git a/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp b/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp index cb23e73b0e..8d7fb63baf 100644 --- a/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -378,6 +379,25 @@ namespace AZ MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&)); }; + class MockEditorPythonConsoleInterface final + : public AzToolsFramework::EditorPythonConsoleInterface + { + public: + MockEditorPythonConsoleInterface() + { + AZ::Interface::Register(this); + } + + ~MockEditorPythonConsoleInterface() + { + AZ::Interface::Unregister(this); + } + + MOCK_CONST_METHOD1(GetModuleList, void(AZStd::vector&)); + MOCK_CONST_METHOD1(GetGlobalFunctionList, void(GlobalFunctionCollection&)); + MOCK_METHOD1(FetchPythonTypeName, AZStd::string(const AZ::BehaviorParameter&)); + }; + // // SceneGraphBehaviorScriptTest // @@ -386,6 +406,7 @@ namespace AZ { public: AZStd::unique_ptr m_componentApplication; + AZStd::unique_ptr m_editorPythonConsoleInterface; AZStd::unique_ptr m_scriptContext; AZStd::unique_ptr m_behaviorContext; AZStd::unique_ptr m_serializeContext; @@ -454,6 +475,15 @@ namespace AZ { return this->m_serializeContext.get(); })); + + m_editorPythonConsoleInterface = AZStd::make_unique(); + } + + void SetupEditorPythonConsoleInterface() + { + EXPECT_CALL(*m_editorPythonConsoleInterface, FetchPythonTypeName(::testing::_)) + .Times(4) + .WillRepeatedly(::testing::Invoke([](const AZ::BehaviorParameter&) {return "int"; })); } void TearDown() override @@ -562,6 +592,38 @@ namespace AZ ExpectExecute("TestExpectEquals(value, 17)"); } + TEST_F(SceneGraphBehaviorScriptTest, GraphObjectProxy_GetClassInfo_Loads) + { + SetupEditorPythonConsoleInterface(); + + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); + ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); + ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); + ExpectExecute("info = proxy:GetClassInfo()"); + ExpectExecute("TestExpectTrue(info ~= nil)"); + } + + TEST_F(SceneGraphBehaviorScriptTest, GraphObjectProxy_GetClassInfo_CorrectFormats) + { + SetupEditorPythonConsoleInterface(); + + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); + ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); + ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); + ExpectExecute("info = proxy:GetClassInfo()"); + ExpectExecute("TestExpectTrue(info.className == 'MockIGraphObject')"); + ExpectExecute("TestExpectTrue(info.classUuid == '{66A082CC-851D-4E1F-ABBD-45B58A216CFA}')"); + ExpectExecute("TestExpectTrue(info.methodList[1] == 'def GetId(self) -> int')"); + ExpectExecute("TestExpectTrue(info.methodList[2] == 'def SetId(self, arg1: int) -> None')"); + ExpectExecute("TestExpectTrue(info.methodList[3] == 'def AddAndSet(self, arg1: int, arg2: int) -> None')"); + } + // // SceneManifestBehaviorScriptTest is meant to test the script abilities of the SceneManifest // diff --git a/Code/Tools/SceneAPI/SceneData/GraphData/BlendShapeData.h b/Code/Tools/SceneAPI/SceneData/GraphData/BlendShapeData.h index 9ae287da46..403ab867f2 100644 --- a/Code/Tools/SceneAPI/SceneData/GraphData/BlendShapeData.h +++ b/Code/Tools/SceneAPI/SceneData/GraphData/BlendShapeData.h @@ -31,7 +31,7 @@ namespace AZ public: AZ_RTTI(BlendShapeData, "{FF875C22-2E4F-4CE3-BA49-09BF78C70A09}", SceneAPI::DataTypes::IBlendShapeData) - SCENE_DATA_API static void Reflect(ReflectContext* context); + static void Reflect(ReflectContext* context); // Maximum number of color sets matches limitation set in assImp (AI_MAX_NUMBER_OF_COLOR_SETS) static constexpr AZ::u8 MaxNumColorSets = 8; diff --git a/Gems/EditorPythonBindings/Code/Source/PythonLogSymbolsComponent.cpp b/Gems/EditorPythonBindings/Code/Source/PythonLogSymbolsComponent.cpp index 2b7676e4db..a22064cfe2 100644 --- a/Gems/EditorPythonBindings/Code/Source/PythonLogSymbolsComponent.cpp +++ b/Gems/EditorPythonBindings/Code/Source/PythonLogSymbolsComponent.cpp @@ -161,7 +161,7 @@ namespace EditorPythonBindings bufferArg = *name; } - AZStd::string_view type = FetchPythonType(*behaviorMethod.GetArgument(argIndex)); + AZStd::string type = FetchPythonTypeName(*behaviorMethod.GetArgument(argIndex)); if (!type.empty()) { AzFramework::StringFunc::Append(bufferArg, ": "); @@ -289,7 +289,7 @@ namespace EditorPythonBindings bool isBroadcast = false; if (sender.m_event) { - AZStd::string_view addressType = FetchPythonType(behaviorEBus->m_idParam); + AZStd::string addressType = FetchPythonTypeName(behaviorEBus->m_idParam); if (addressType.empty()) { AzFramework::StringFunc::Append(buffer, "(busCallType: int, busEventName: str, address: Any, args: Tuple[Any])"); @@ -338,7 +338,7 @@ namespace EditorPythonBindings } const AZ::BehaviorParameter* resultParam = behaviorMethod->GetResult(); - AZStd::string_view returnType = FetchPythonType(*resultParam); + AZStd::string returnType = FetchPythonTypeName(*resultParam); AZStd::string returnTypeStr = AZStd::string::format(") -> " AZ_STRING_FORMAT" \n", AZ_STRING_ARG(returnType)); AzFramework::StringFunc::Append(inOutStrBuffer, returnTypeStr.c_str()); }; @@ -664,9 +664,9 @@ namespace EditorPythonBindings return m_typeCache[typeId]; } - AZStd::string_view PythonLogSymbolsComponent::FetchPythonType(const AZ::BehaviorParameter& param) + AZStd::string PythonLogSymbolsComponent::FetchPythonTypeName(const AZ::BehaviorParameter& param) { - AZStd::string_view pythonType = FetchPythonTypeAndTraits(param.m_typeId, param.m_traits); + AZStd::string pythonType = FetchPythonTypeAndTraits(param.m_typeId, param.m_traits); if (pythonType.empty()) { diff --git a/Gems/EditorPythonBindings/Code/Source/PythonLogSymbolsComponent.h b/Gems/EditorPythonBindings/Code/Source/PythonLogSymbolsComponent.h index 2972e3c9e0..e52cb00570 100644 --- a/Gems/EditorPythonBindings/Code/Source/PythonLogSymbolsComponent.h +++ b/Gems/EditorPythonBindings/Code/Source/PythonLogSymbolsComponent.h @@ -62,6 +62,7 @@ namespace EditorPythonBindings void LogGlobalMethod(AZStd::string_view moduleName, AZStd::string_view methodName, AZ::BehaviorMethod* behaviorMethod) override; void LogGlobalProperty(AZStd::string_view moduleName, AZStd::string_view propertyName, AZ::BehaviorProperty* behaviorProperty) override; void Finalize() override; + AZStd::string FetchPythonTypeName(const AZ::BehaviorParameter& param) override; //////////////////////////////////////////////////////////////////////// // EditorPythonConsoleInterface @@ -71,7 +72,6 @@ namespace EditorPythonBindings //////////////////////////////////////////////////////////////////////// // Python type deduction AZStd::string_view FetchPythonTypeAndTraits(const AZ::TypeId& typeId, AZ::u32 traits); - AZStd::string_view FetchPythonType(const AZ::BehaviorParameter& param); private: using ModuleSet = AZStd::unordered_set; diff --git a/Gems/EditorPythonBindings/Code/Tests/PythonLogSymbolsComponentTests.cpp b/Gems/EditorPythonBindings/Code/Tests/PythonLogSymbolsComponentTests.cpp index 97f4dd02d0..8297bad094 100644 --- a/Gems/EditorPythonBindings/Code/Tests/PythonLogSymbolsComponentTests.cpp +++ b/Gems/EditorPythonBindings/Code/Tests/PythonLogSymbolsComponentTests.cpp @@ -41,9 +41,9 @@ namespace UnitTest return FetchPythonTypeAndTraits(typeId, traits); } - AZStd::string_view FetchPythonTypeWrapper(const AZ::BehaviorParameter& param) + AZStd::string FetchPythonTypeWrapper(const AZ::BehaviorParameter& param) { - return FetchPythonType(param); + return FetchPythonTypeName(param); } };