You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/ScriptCanvas/Code/Tools/TranslationGeneration.cpp

1556 lines
62 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "TranslationGeneration.h"
#include <Source/Translation/TranslationBus.h>
#include <rapidjson/rapidjson.h>
#include <rapidjson/document.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/prettywriter.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/std/string/regex.h>
#include <AzFramework/Gem/GemInfo.h>
#include <Libraries/Core/AzEventHandler.h>
#include <Libraries/Libraries.h>
#include <Libraries/Core/GetVariable.h>
#include <Libraries/Core/SetVariable.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzQtComponents/Utilities/DesktopUtilities.h>
#include <Source/Translation/TranslationSerializer.h>
#include "Data/DataRegistry.h"
namespace ScriptCanvasEditorTools
{
namespace Helpers
{
//! Convenience function that writes a key/value string pair into a given JSON value
void WriteString(rapidjson::Value& owner, const AZStd::string& key, const AZStd::string& value, rapidjson::Document& document);
}
TranslationGeneration::TranslationGeneration()
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ::ComponentApplicationBus::BroadcastResult(m_behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
}
void TranslationGeneration::TranslateBehaviorClasses()
{
for (const auto& behaviorClassPair : m_behaviorContext->m_classes)
{
TranslateBehaviorClass(behaviorClassPair.second);
}
}
void TranslationGeneration::TranslateEBus(const AZ::BehaviorEBus* behaviorEBus)
{
if (ShouldSkip(behaviorEBus))
{
return;
}
TranslationFormat translationRoot;
// Get the handlers
if (!TranslateEBusHandler(behaviorEBus, translationRoot))
{
if (behaviorEBus->m_events.empty())
{
return;
}
Entry entry;
// Generate the translation file
entry.m_key = behaviorEBus->m_name;
entry.m_details.m_category = Helpers::GetStringAttribute(behaviorEBus, AZ::Script::Attributes::Category);;
entry.m_details.m_tooltip = behaviorEBus->m_toolTip;
entry.m_details.m_name = behaviorEBus->m_name;
entry.m_context = "EBusSender";
AZStd::string prettyName = Helpers::GetStringAttribute(behaviorEBus, AZ::ScriptCanvasAttributes::PrettyName);
if (!prettyName.empty())
{
entry.m_details.m_name = prettyName;
}
SplitCamelCase(entry.m_details.m_name);
for (auto event : behaviorEBus->m_events)
{
const AZ::BehaviorEBusEventSender& ebusSender = event.second;
AZ::BehaviorMethod* method = ebusSender.m_event;
if (!method)
{
method = ebusSender.m_broadcast;
}
if (!method)
{
AZ_Warning("Script Canvas", false, "Failed to find method: %s", event.first.c_str());
continue;
}
Method eventEntry;
const char* eventName = event.first.c_str();
eventEntry.m_key = eventName;
prettyName = Helpers::GetStringAttribute(behaviorEBus, AZ::ScriptCanvasAttributes::PrettyName);
eventEntry.m_details.m_name = prettyName.empty() ? eventName : prettyName;
eventEntry.m_details.m_tooltip = Helpers::ReadStringAttribute(event.second.m_attributes, AZ::Script::Attributes::ToolTip);
SplitCamelCase(eventEntry.m_details.m_name);
eventEntry.m_entry.m_name = "In";
eventEntry.m_entry.m_tooltip = AZStd::string::format("When signaled, this will invoke %s", eventEntry.m_details.m_name.c_str());
eventEntry.m_exit.m_name = "Out";
eventEntry.m_exit.m_tooltip = AZStd::string::format("Signaled after %s is invoked", eventEntry.m_details.m_name.c_str());
size_t start = method->HasBusId() ? 1 : 0;
for (size_t i = start; i < method->GetNumArguments(); ++i)
{
Argument argument;
auto argumentType = method->GetArgument(i)->m_typeId;
// Check the BC for metadata
Helpers::GetTypeNameAndDescription(argumentType, argument.m_details.m_name, argument.m_details.m_tooltip);
auto name = method->GetArgumentName(i);
if (name && !name->empty())
{
argument.m_details.m_name = *name;
}
auto tooltip = method->GetArgumentToolTip(i);
if (tooltip && !tooltip->empty())
{
argument.m_details.m_tooltip = *tooltip;
}
argument.m_typeId = argumentType.ToString<AZStd::string>();
SplitCamelCase(argument.m_details.m_name);
eventEntry.m_arguments.push_back(argument);
}
if (method->HasResult())
{
Argument result;
auto resultType = method->GetResult()->m_typeId;
Helpers::GetTypeNameAndDescription(resultType, result.m_details.m_name, result.m_details.m_tooltip);
auto tooltip = method->GetArgumentToolTip(0);
if (tooltip && !tooltip->empty())
{
result.m_details.m_tooltip = *tooltip;
}
result.m_typeId = resultType.ToString<AZStd::string>();
SplitCamelCase(result.m_details.m_name);
eventEntry.m_results.push_back(result);
}
entry.m_methods.push_back(eventEntry);
}
translationRoot.m_entries.push_back(entry);
SaveJSONData(AZStd::string::format("EBus/Senders/%s", behaviorEBus->m_name.c_str()), translationRoot);
}
else
{
SaveJSONData(AZStd::string::format("EBus/Handlers/%s", behaviorEBus->m_name.c_str()), translationRoot);
}
}
AZ::Entity* TranslationGeneration::GetAZEventNode(const AZ::BehaviorMethod& method)
{
// Make sure the method returns an AZ::Event by reference or pointer
if (AZ::MethodReturnsAzEventByReferenceOrPointer(method))
{
// Read in AZ Event Description data to retrieve the event name and parameter names
AZ::Attribute* azEventDescAttribute = AZ::FindAttribute(AZ::Script::Attributes::AzEventDescription, method.m_attributes);
AZ::BehaviorAzEventDescription behaviorAzEventDesc;
AZ::AttributeReader azEventDescAttributeReader(nullptr, azEventDescAttribute);
azEventDescAttributeReader.Read<decltype(behaviorAzEventDesc)>(behaviorAzEventDesc);
if (behaviorAzEventDesc.m_eventName.empty())
{
AZ_Error("NodeUtils", false, "Cannot create an AzEvent node with empty event name")
}
auto scriptCanvasEntity = aznew AZ::Entity{ AZStd::string::format("SC-EventNode(%s)", behaviorAzEventDesc.m_eventName.c_str()) };
scriptCanvasEntity->Init();
auto azEventHandler = scriptCanvasEntity->CreateComponent<ScriptCanvas::Nodes::Core::AzEventHandler>();
azEventHandler->InitEventFromMethod(method);
return scriptCanvasEntity;
}
return nullptr;
}
bool TranslationGeneration::TranslateBehaviorClass(const AZ::BehaviorClass* behaviorClass)
{
if (ShouldSkip(behaviorClass))
{
return false;
}
AZStd::string className = behaviorClass->m_name;
AZStd::string prettyName = Helpers::GetStringAttribute(behaviorClass, AZ::ScriptCanvasAttributes::PrettyName);
if (!prettyName.empty())
{
className = prettyName;
}
TranslationFormat translationRoot;
Entry entry;
entry.m_context = "BehaviorClass";
entry.m_key = behaviorClass->m_name;
EntryDetails& details = entry.m_details;
details.m_name = className;
details.m_category = Helpers::GetStringAttribute(behaviorClass, AZ::Script::Attributes::Category);
details.m_tooltip = Helpers::GetStringAttribute(behaviorClass, AZ::Script::Attributes::ToolTip);
SplitCamelCase(details.m_name);
if (!behaviorClass->m_methods.empty())
{
for (const auto& methodPair : behaviorClass->m_methods)
{
const AZ::BehaviorMethod* behaviorMethod = methodPair.second;
if (TranslateSingleAZEvent(behaviorMethod))
{
continue;
}
Method methodEntry;
AZStd::string cleanName = GraphCanvas::TranslationKey::Sanitize(methodPair.first);
methodEntry.m_key = cleanName;
methodEntry.m_details.m_category = "";
methodEntry.m_details.m_tooltip = "";
methodEntry.m_details.m_name = methodPair.second->m_name;
AZStd::string prefix = className + "::";
AZ::StringFunc::Replace(methodEntry.m_details.m_name, prefix.c_str(), "");
SplitCamelCase(methodEntry.m_details.m_name);
methodEntry.m_entry.m_name = "In";
methodEntry.m_entry.m_tooltip = AZStd::string::format("When signaled, this will invoke %s", methodEntry.m_details.m_name.c_str());
methodEntry.m_exit.m_name = "Out";
methodEntry.m_exit.m_tooltip = AZStd::string::format("Signaled after %s is invoked", methodEntry.m_details.m_name.c_str());
if (!Helpers::MethodHasAttribute(behaviorMethod, AZ::ScriptCanvasAttributes::FloatingFunction))
{
methodEntry.m_details.m_category = details.m_category;
}
else if (Helpers::MethodHasAttribute(behaviorMethod, AZ::Script::Attributes::Category))
{
methodEntry.m_details.m_category = Helpers::ReadStringAttribute(behaviorMethod->m_attributes, AZ::Script::Attributes::Category);
}
// Arguments (Input Slots)
if (behaviorMethod->GetNumArguments() > 0)
{
size_t startIndex = behaviorMethod->HasResult() ? 1 : 0;
for (size_t argIndex = startIndex; argIndex < behaviorMethod->GetNumArguments(); ++argIndex)
{
const AZ::BehaviorParameter* parameter = behaviorMethod->GetArgument(argIndex);
Argument argument;
AZStd::string argumentKey = parameter->m_typeId.ToString<AZStd::string>();
AZStd::string argumentName;
AZStd::string argumentDescription;
Helpers::GetTypeNameAndDescription(parameter->m_typeId, argumentName, argumentDescription);
const AZStd::string* argName = behaviorMethod->GetArgumentName(argIndex);
if (argName && !(*argName).empty())
{
argumentName = *argName;
}
const AZStd::string* argDesc = behaviorMethod->GetArgumentToolTip(argIndex);
if (argDesc && !(*argDesc).empty())
{
argumentDescription = *argDesc;
}
argument.m_typeId = argumentKey;
argument.m_details.m_name = argumentName;
argument.m_details.m_tooltip = argumentDescription;
SplitCamelCase(argument.m_details.m_name);
methodEntry.m_arguments.push_back(argument);
}
}
// Results (Output Slots)
const AZ::BehaviorParameter* resultParameter = behaviorMethod->HasResult() ? behaviorMethod->GetResult() : nullptr;
if (resultParameter)
{
Argument result;
AZStd::string resultKey = resultParameter->m_typeId.ToString<AZStd::string>();
AZStd::string resultName = resultParameter->m_name;
AZStd::string resultDescription = "";
Helpers::GetTypeNameAndDescription(resultParameter->m_typeId, resultName, resultDescription);
result.m_typeId = resultKey;
result.m_details.m_name = resultParameter->m_name;
result.m_details.m_tooltip = resultDescription;
SplitCamelCase(result.m_details.m_name);
methodEntry.m_results.push_back(result);
}
entry.m_methods.push_back(methodEntry);
}
}
// Behavior Class properties
if (!behaviorClass->m_properties.empty())
{
for (const auto& propertyEntry : behaviorClass->m_properties)
{
AZ::BehaviorProperty* behaviorProperty = propertyEntry.second;
if (behaviorProperty)
{
TranslateBehaviorProperty(behaviorProperty, behaviorClass->m_name, "BehaviorClass", &entry);
}
}
}
translationRoot.m_entries.push_back(entry);
AZStd::string sanitizedFilename = GraphCanvas::TranslationKey::Sanitize(className);
AZStd::string fileName = AZStd::string::format("Classes/%s", sanitizedFilename.c_str());
SaveJSONData(fileName, translationRoot);
return true;
}
//! Generate the translation data for a specific AZ::Event
bool TranslationGeneration::TranslateSingleAZEvent(const AZ::BehaviorMethod* method)
{
AZ::Entity* node = GetAZEventNode(*method);
if (!node)
{
return false;
}
TranslationFormat translationRoot;
ScriptCanvas::Nodes::Core::AzEventHandler* nodeComponent = node->FindComponent<ScriptCanvas::Nodes::Core::AzEventHandler>();
nodeComponent->Init();
nodeComponent->Configure();
const ScriptCanvas::Nodes::Core::AzEventEntry& azEventEntry{ nodeComponent->GetEventEntry() };
Entry entry;
entry.m_key = azEventEntry.m_eventName;
entry.m_context = "AZEventHandler";
entry.m_details.m_name = azEventEntry.m_eventName;
SplitCamelCase(entry.m_details.m_name);
for (const ScriptCanvas::Slot& slot : nodeComponent->GetSlots())
{
Slot slotEntry;
if (slot.IsVisible())
{
slotEntry.m_key = slot.GetName();
if (slot.GetId() == azEventEntry.m_azEventInputSlotId)
{
slotEntry.m_details.m_name = azEventEntry.m_eventName;
}
else
{
slotEntry.m_details.m_name = slot.GetName();
}
entry.m_slots.push_back(slotEntry);
}
}
translationRoot.m_entries.push_back(entry);
// delete the node, don't need to keep it beyond this point
delete node;
AZStd::string filename = GraphCanvas::TranslationKey::Sanitize(entry.m_key);
AZStd::string targetFile = AZStd::string::format("AZEvents/%s", filename.c_str());
SaveJSONData(targetFile, translationRoot);
translationRoot.m_entries.clear();
return true;
}
void TranslationGeneration::TranslateAZEvents()
{
GraphCanvas::TranslationKey translationKey;
AZStd::vector<AZ::BehaviorMethod*> methods;
// Methods
for (const auto& behaviorMethod : m_behaviorContext->m_methods)
{
const auto method = behaviorMethod.second;
methods.push_back(method);
}
// Methods in classes
for (auto behaviorClass : m_behaviorContext->m_classes)
{
for (auto behaviorMethod : behaviorClass.second->m_methods)
{
const auto method = behaviorMethod.second;
methods.push_back(method);
}
}
for (auto& method : methods)
{
TranslateSingleAZEvent(method);
}
}
void TranslationGeneration::TranslateNodes()
{
GraphCanvas::TranslationKey translationKey;
AZStd::vector<AZ::TypeId> nodes;
auto getNodeClasses = [this, &nodes](const AZ::SerializeContext::ClassData*, const AZ::Uuid& type)
{
bool foundBaseClass = false;
auto baseClassVisitorFn = [&nodes, &type, &foundBaseClass](const AZ::SerializeContext::ClassData* reflectedBase, const AZ::TypeId& /*rttiBase*/)
{
if (!reflectedBase)
{
foundBaseClass = false;
return false; // stop iterating
}
foundBaseClass = (reflectedBase->m_typeId == azrtti_typeid<ScriptCanvas::Node>());
if (foundBaseClass)
{
nodes.push_back(type);
return false; // we have a base, stop iterating
}
return true; // keep iterating
};
AZ::EntityUtils::EnumerateBaseRecursive(m_serializeContext, baseClassVisitorFn, type);
return true;
};
m_serializeContext->EnumerateAll(getNodeClasses);
for (auto& node : nodes)
{
TranslateNode(node);
}
}
void TranslationGeneration::TranslateNode(const AZ::TypeId& nodeTypeId)
{
TranslationFormat translationRoot;
if (const AZ::SerializeContext::ClassData* classData = m_serializeContext->FindClassData(nodeTypeId))
{
Entry entry;
entry.m_key = classData->m_typeId.ToString<AZStd::string>();
entry.m_context = "ScriptCanvas::Node";
EntryDetails& details = entry.m_details;
AZStd::string cleanName = GraphCanvas::TranslationKey::Sanitize(classData->m_name);
if (classData->m_editData)
{
details.m_name = classData->m_editData->m_name;
}
else
{
details.m_name = cleanName;
}
SplitCamelCase(details.m_name);
// Tooltip attribute takes priority over the edit data description
AZStd::string tooltip = Helpers::GetStringAttribute(classData, AZ::Script::Attributes::ToolTip);
if (!tooltip.empty())
{
details.m_tooltip = tooltip;
}
else
{
details.m_tooltip = classData->m_editData ? classData->m_editData->m_description : "";
}
details.m_category = Helpers::GetStringAttribute(classData, AZ::Script::Attributes::Category);
if (details.m_subtitle.empty())
{
details.m_subtitle = details.m_category;
}
if (details.m_category.empty())
{
details.m_category = Helpers::GetStringAttribute(classData, AZ::Script::Attributes::Category);
if (details.m_category.empty() && classData->m_editData)
{
details.m_category = Helpers::GetCategory(classData);
if (details.m_category.empty())
{
auto elementData = classData->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData);
const AZStd::string categoryAttribute = Helpers::ReadStringAttribute(elementData->m_attributes, AZ::Script::Attributes::Category);
if (!categoryAttribute.empty())
{
details.m_category = categoryAttribute;
}
}
}
}
if (details.m_category.empty())
{
// Get the library's name as the category
details.m_category = Helpers::GetLibraryCategory(*m_serializeContext, classData->m_name);
}
if (ScriptCanvas::Node* nodeComponent = reinterpret_cast<ScriptCanvas::Node*>(classData->m_factory->Create(classData->m_name)))
{
nodeComponent->Init();
nodeComponent->Configure();
int exeInputIndex = 0;
int exeOutputIndex = 0;
int dataInputIndex = 0;
int dataOutputIndex = 0;
const auto& allSlots = nodeComponent->GetAllSlots();
for (const auto& slot : allSlots)
{
Slot slotEntry;
if (slot->GetDescriptor().IsExecution())
{
if (slot->GetDescriptor().IsInput())
{
slotEntry.m_key = AZStd::string::format("Input_%s_%d", slot->GetName().c_str(), exeInputIndex);
exeInputIndex++;
slotEntry.m_details.m_name = slot->GetName();
slotEntry.m_details.m_tooltip = slot->GetToolTip();
}
else if (slot->GetDescriptor().IsOutput())
{
slotEntry.m_key = AZStd::string::format("Output_%s_%d", slot->GetName().c_str(), exeOutputIndex);
exeOutputIndex++;
slotEntry.m_details.m_name = slot->GetName();
slotEntry.m_details.m_tooltip = slot->GetToolTip();
}
entry.m_slots.push_back(slotEntry);
}
else
{
AZStd::string slotTypeKey = slot->GetDataType().IsValid() ? ScriptCanvas::Data::GetName(slot->GetDataType()) : "";
if (slotTypeKey.empty())
{
if (!slot->GetDataType().GetAZType().IsNull())
{
slotTypeKey = slot->GetDataType().GetAZType().ToString<AZStd::string>();
}
}
if (slotTypeKey.empty())
{
if (slot->GetDynamicDataType() == ScriptCanvas::DynamicDataType::Container)
{
slotTypeKey = "Container";
}
else if (slot->GetDynamicDataType() == ScriptCanvas::DynamicDataType::Value)
{
slotTypeKey = "Value";
}
else if (slot->GetDynamicDataType() == ScriptCanvas::DynamicDataType::Any)
{
slotTypeKey = "Any";
}
}
Argument& argument = slotEntry.m_data;
if (slot->GetDescriptor().IsInput())
{
slotEntry.m_key = AZStd::string::format("DataInput_%s_%d", slot->GetName().c_str(), dataInputIndex);
dataInputIndex++;
AZStd::string argumentKey = slotTypeKey;
AZStd::string argumentName = slot->GetName();
AZStd::string argumentDescription = slot->GetToolTip();
argument.m_typeId = argumentKey;
argument.m_details.m_name = argumentName;
argument.m_details.m_tooltip = argumentDescription;
}
else if (slot->GetDescriptor().IsOutput())
{
slotEntry.m_key = AZStd::string::format("DataOutput_%s_%d", slot->GetName().c_str(), dataOutputIndex);
dataOutputIndex++;
AZStd::string resultKey = slotTypeKey;
AZStd::string resultName = slot->GetName();
AZStd::string resultDescription = slot->GetToolTip();
argument.m_typeId = resultKey;
argument.m_details.m_name = resultName;
argument.m_details.m_tooltip = resultDescription;
}
entry.m_slots.push_back(slotEntry);
}
}
delete nodeComponent;
}
translationRoot.m_entries.push_back(entry);
if (details.m_category.empty())
{
details.m_category = "Uncategorized";
}
AZStd::string prefix = GraphCanvas::TranslationKey::Sanitize(details.m_category);
AZStd::string filename = GraphCanvas::TranslationKey::Sanitize(details.m_name);
AZStd::string targetFile = AZStd::string::format("Nodes/%s_%s", prefix.c_str(), filename.c_str());
SaveJSONData(targetFile, translationRoot);
translationRoot.m_entries.clear();
}
}
void TranslationGeneration::TranslateOnDemandReflectedTypes(TranslationFormat& translationRoot)
{
AZStd::vector<AZ::Uuid> onDemandReflectedTypes;
for (auto& typePair : m_behaviorContext->m_typeToClassMap)
{
if (m_behaviorContext->IsOnDemandTypeReflected(typePair.first))
{
onDemandReflectedTypes.push_back(typePair.first);
}
// Check for methods that come from node generics
if (typePair.second->HasAttribute(AZ::ScriptCanvasAttributes::Internal::ImplementedAsNodeGeneric))
{
onDemandReflectedTypes.push_back(typePair.first);
}
}
// Now that I know all the on demand reflected, I'll dump it out
for (auto& onDemandReflectedType : onDemandReflectedTypes)
{
AZ::BehaviorClass* behaviorClass = m_behaviorContext->m_typeToClassMap[onDemandReflectedType];
if (behaviorClass)
{
Entry entry;
EntryDetails& details = entry.m_details;
details.m_name = behaviorClass->m_name;
SplitCamelCase(details.m_name);
// Get the pretty name
AZStd::string prettyName;
if (AZ::Attribute* prettyNameAttribute = AZ::FindAttribute(AZ::ScriptCanvasAttributes::PrettyName, behaviorClass->m_attributes))
{
AZ::AttributeReader(nullptr, prettyNameAttribute).Read<AZStd::string>(prettyName, *m_behaviorContext);
}
entry.m_context = "OnDemandReflected";
entry.m_key = behaviorClass->m_typeId.ToString<AZStd::string>().c_str();
if (!prettyName.empty())
{
details.m_name = prettyName;
}
details.m_category = Helpers::GetStringAttribute(behaviorClass, AZ::Script::Attributes::Category);
details.m_tooltip = Helpers::GetStringAttribute(behaviorClass, AZ::Script::Attributes::ToolTip);
for (auto& methodPair : behaviorClass->m_methods)
{
AZ::BehaviorMethod* behaviorMethod = methodPair.second;
if (behaviorMethod)
{
Method methodEntry;
AZStd::string cleanName = GraphCanvas::TranslationKey::Sanitize(methodPair.first);
methodEntry.m_key = cleanName;
methodEntry.m_context = entry.m_key;
methodEntry.m_details.m_tooltip = Helpers::GetStringAttribute(behaviorMethod, AZ::Script::Attributes::ToolTip);
methodEntry.m_details.m_name = methodPair.second->m_name;
SplitCamelCase(methodEntry.m_details.m_name);
// Strip the className from the methodName
AZStd::string qualifiedName = behaviorClass->m_name + "::";
AzFramework::StringFunc::Replace(methodEntry.m_details.m_name, qualifiedName.c_str(), "");
AZStd::string cleanMethodName = methodEntry.m_details.m_name;
methodEntry.m_entry.m_name = "In";
methodEntry.m_entry.m_tooltip = AZStd::string::format("When signaled, this will invoke %s", methodEntry.m_details.m_name.c_str());
methodEntry.m_exit.m_name = "Out";
methodEntry.m_exit.m_tooltip = AZStd::string::format("Signaled after %s is invoked", methodEntry.m_details.m_name.c_str());
// Arguments (Input Slots)
if (behaviorMethod->GetNumArguments() > 0)
{
for (size_t argIndex = 0; argIndex < behaviorMethod->GetNumArguments(); ++argIndex)
{
const AZ::BehaviorParameter* parameter = behaviorMethod->GetArgument(argIndex);
Argument argument;
AZStd::string argumentKey = parameter->m_typeId.ToString<AZStd::string>();
AZStd::string argumentName = parameter->m_name;
AZStd::string argumentDescription = "";
Helpers::GetTypeNameAndDescription(parameter->m_typeId, argumentName, argumentDescription);
argument.m_typeId = argumentKey;
argument.m_details.m_name = argumentName;
argument.m_details.m_category = "";
argument.m_details.m_tooltip = argumentDescription;
SplitCamelCase(argument.m_details.m_name);
methodEntry.m_arguments.push_back(argument);
}
}
const AZ::BehaviorParameter* resultParameter = behaviorMethod->HasResult() ? behaviorMethod->GetResult() : nullptr;
if (resultParameter)
{
Argument result;
AZStd::string resultKey = resultParameter->m_typeId.ToString<AZStd::string>();
AZStd::string resultName = resultParameter->m_name;
AZStd::string resultDescription = "";
Helpers::GetTypeNameAndDescription(resultParameter->m_typeId, resultName, resultDescription);
result.m_typeId = resultKey;
result.m_details.m_name = resultName;
result.m_details.m_tooltip = resultDescription;
SplitCamelCase(result.m_details.m_name);
methodEntry.m_results.push_back(result);
}
entry.m_methods.push_back(methodEntry);
}
}
translationRoot.m_entries.push_back(entry);
}
}
SaveJSONData("Types/OnDemandReflectedTypes", translationRoot);
}
void TranslationGeneration::TranslateBehaviorGlobals()
{
for (const auto& [propertyName, behaviorProperty] : m_behaviorContext->m_properties)
{
TranslateBehaviorProperty(propertyName);
}
}
void TranslationGeneration::TranslateBehaviorProperty(const AZStd::string& propertyName)
{
const auto behaviorPropertyEntry = m_behaviorContext->m_properties.find(propertyName);
if (behaviorPropertyEntry == m_behaviorContext->m_properties.end())
{
return;
}
const AZ::BehaviorProperty* behaviorProperty = behaviorPropertyEntry->second;
Entry entry;
TranslateBehaviorProperty(behaviorProperty, propertyName, "Constant", &entry);
TranslationFormat translationRoot;
translationRoot.m_entries.push_back(entry);
AZStd::string cleanName = GraphCanvas::TranslationKey::Sanitize(behaviorProperty->m_name);
AZStd::string fileName = AZStd::string::format("Properties/%s", cleanName.c_str());
SaveJSONData(fileName, translationRoot);
}
void TranslationGeneration::TranslateDataTypes()
{
TranslationFormat translationRoot;
auto dataRegistry = ScriptCanvas::GetDataRegistry();
for (auto& typePair : dataRegistry->m_creatableTypes)
{
if (ScriptCanvas::Data::IsContainerType(typePair.first))
{
continue;
}
const AZStd::string typeIDStr = typePair.first.GetAZType().ToString<AZStd::string>();
AZStd::string typeName = ScriptCanvas::Data::GetName(typePair.first);
Entry entry;
entry.m_key = typeIDStr;
entry.m_context = "BehaviorType";
entry.m_details.m_name = typeName;
translationRoot.m_entries.emplace_back(entry);
}
SaveJSONData("Types/BehaviorTypes", translationRoot);
}
void TranslationGeneration::TranslateMethod(AZ::BehaviorMethod* behaviorMethod, Method& methodEntry)
{
// Arguments (Input Slots)
if (behaviorMethod->GetNumArguments() > 0)
{
for (size_t argIndex = 0; argIndex < behaviorMethod->GetNumArguments(); ++argIndex)
{
const AZ::BehaviorParameter* parameter = behaviorMethod->GetArgument(argIndex);
Argument argument;
AZStd::string argumentKey = parameter->m_typeId.ToString<AZStd::string>();
AZStd::string argumentName = parameter->m_name;
AZStd::string argumentDescription;
Helpers::GetTypeNameAndDescription(parameter->m_typeId, argumentName, argumentDescription);
const AZStd::string* argName = behaviorMethod->GetArgumentName(argIndex);
argument.m_typeId = argumentKey;
argument.m_details.m_name = (argName && !argName->empty()) ? *argName : argumentName;
argument.m_details.m_category = "";
argument.m_details.m_tooltip = argumentDescription;
SplitCamelCase(argument.m_details.m_name);
methodEntry.m_arguments.push_back(argument);
}
}
// Results (Output Slots)
const AZ::BehaviorParameter* resultParameter = behaviorMethod->HasResult() ? behaviorMethod->GetResult() : nullptr;
if (resultParameter)
{
Argument result;
AZStd::string resultKey = resultParameter->m_typeId.ToString<AZStd::string>();
AZStd::string resultName = resultParameter->m_name;
AZStd::string resultDescription;
Helpers::GetTypeNameAndDescription(resultParameter->m_typeId, resultName, resultDescription);
const AZStd::string* resName = behaviorMethod->GetArgumentName(0);
result.m_typeId = resultKey;
result.m_details.m_name = (resName && !resName->empty()) ? *resName : resultName;
result.m_details.m_tooltip = resultDescription;
SplitCamelCase(result.m_details.m_name);
methodEntry.m_results.push_back(result);
}
}
void TranslationGeneration::TranslateBehaviorProperty(const AZ::BehaviorProperty* behaviorProperty, const AZStd::string& className, const AZStd::string& context, Entry* entry)
{
if (!behaviorProperty->m_getter && !behaviorProperty->m_setter)
{
return;
}
Entry localEntry;
if (!entry)
{
entry = &localEntry;
}
else if (entry->m_key.empty())
{
entry->m_key = className;
entry->m_context = context;
entry->m_details.m_name = className;
SplitCamelCase(entry->m_details.m_name);
}
if (behaviorProperty->m_getter)
{
AZStd::string cleanName = behaviorProperty->m_name;
AZ::StringFunc::Replace(cleanName, "::Getter", "");
Method method;
AZStd::string methodName = "Get";
methodName.append(cleanName);
method.m_key = methodName;
method.m_context = "Getter";
method.m_details.m_name = methodName;
method.m_details.m_tooltip = behaviorProperty->m_getter->m_debugDescription ? behaviorProperty->m_getter->m_debugDescription : "";
SplitCamelCase(method.m_details.m_name);
TranslateMethod(behaviorProperty->m_getter, method);
// We know this is a getter, so there will only be one parameter, we will use the method name as a best
// guess for the argument name
SplitCamelCase(cleanName);
method.m_results[0].m_details.m_name = cleanName;
entry->m_methods.push_back(method);
}
if (behaviorProperty->m_setter)
{
AZStd::string cleanName = behaviorProperty->m_name;
AZ::StringFunc::Replace(cleanName, "::Setter", "");
Method method;
AZStd::string methodName = "Set";
methodName.append(cleanName);
method.m_key = methodName;
method.m_context = "Setter";
method.m_details.m_name = methodName;
method.m_details.m_tooltip = behaviorProperty->m_setter->m_debugDescription ? behaviorProperty->m_getter->m_debugDescription : "";
SplitCamelCase(method.m_details.m_name);
TranslateMethod(behaviorProperty->m_setter, method);
// We know this is a setter, so there will only be one parameter, we will use the method name as a best
// guess for the argument name
SplitCamelCase(cleanName);
method.m_arguments[1].m_details.m_name = cleanName;
entry->m_methods.push_back(method);
}
}
bool TranslationGeneration::TranslateEBusHandler(const AZ::BehaviorEBus* behaviorEbus, TranslationFormat& translationRoot)
{
// Must be a valid ebus handler
if (!behaviorEbus || !behaviorEbus->m_createHandler || !behaviorEbus->m_destroyHandler)
{
return false;
}
// Create the handler in order to get information out of it
AZ::BehaviorEBusHandler* handler(nullptr);
if (behaviorEbus->m_createHandler->InvokeResult(handler))
{
Entry entry;
// Generate the translation file
entry.m_key = behaviorEbus->m_name;
entry.m_context = "EBusHandler";
entry.m_details.m_name = behaviorEbus->m_name;
entry.m_details.m_tooltip = behaviorEbus->m_toolTip;
entry.m_details.m_category = "EBus Handlers";
SplitCamelCase(entry.m_details.m_name);
for (const AZ::BehaviorEBusHandler::BusForwarderEvent& event : handler->GetEvents())
{
Method methodEntry;
AZStd::string cleanName = GraphCanvas::TranslationKey::Sanitize(event.m_name);
methodEntry.m_key = cleanName;
methodEntry.m_details.m_category = "";
methodEntry.m_details.m_tooltip = "";
methodEntry.m_details.m_name = event.m_name;
SplitCamelCase(methodEntry.m_details.m_name);
// Arguments (Input Slots)
if (!event.m_parameters.empty())
{
for (size_t argIndex = AZ::eBehaviorBusForwarderEventIndices::ParameterFirst; argIndex < event.m_parameters.size(); ++argIndex)
{
const AZ::BehaviorParameter& parameter = event.m_parameters[argIndex];
Argument argument;
AZStd::string argumentKey = parameter.m_typeId.ToString<AZStd::string>();
AZStd::string argumentName = event.m_name;
AZStd::string argumentDescription = "";
if (!event.m_metadataParameters.empty() && event.m_metadataParameters.size() > argIndex)
{
argumentName = event.m_metadataParameters[argIndex].m_name;
argumentDescription = event.m_metadataParameters[argIndex].m_toolTip;
}
if (argumentName.empty())
{
Helpers::GetTypeNameAndDescription(parameter.m_typeId, argumentName, argumentDescription);
}
if (!event.m_metadataParameters.empty() && event.m_metadataParameters.size() > argIndex)
{
auto name = event.m_metadataParameters[argIndex].m_name;
auto tooltip = event.m_metadataParameters[argIndex].m_toolTip;
if (!name.empty())
{
argumentName = name;
}
if (!tooltip.empty())
{
argumentDescription = tooltip;
}
}
argument.m_typeId = argumentKey;
argument.m_details.m_name = argumentName;
argument.m_details.m_tooltip = argumentDescription;
SplitCamelCase(argument.m_details.m_name);
methodEntry.m_arguments.push_back(argument);
}
}
auto resultIndex = AZ::eBehaviorBusForwarderEventIndices::Result;
const AZ::BehaviorParameter* resultParameter = event.HasResult() ? &event.m_parameters[resultIndex] : nullptr;
if (resultParameter)
{
Argument result;
AZStd::string resultKey = resultParameter->m_typeId.ToString<AZStd::string>();
AZStd::string resultName = event.m_name;
AZStd::string resultDescription = "";
if (!event.m_metadataParameters.empty() && event.m_metadataParameters.size() > resultIndex)
{
resultName = event.m_metadataParameters[resultIndex].m_name;
resultDescription = event.m_metadataParameters[resultIndex].m_toolTip;
}
if (resultName.empty())
{
Helpers::GetTypeNameAndDescription(resultParameter->m_typeId, resultName, resultDescription);
}
result.m_typeId = resultKey;
result.m_details.m_name = resultName;
result.m_details.m_tooltip = resultDescription;
SplitCamelCase(result.m_details.m_name);
methodEntry.m_results.push_back(result);
}
entry.m_methods.push_back(methodEntry);
}
behaviorEbus->m_destroyHandler->Invoke(handler); // Destroys the Created EbusHandler
translationRoot.m_entries.push_back(entry);
}
if (!translationRoot.m_entries.empty())
{
return true;
}
return false;
}
void TranslationGeneration::SaveJSONData(const AZStd::string& filename, TranslationFormat& translationRoot)
{
rapidjson_ly::Document document;
document.SetObject();
rapidjson_ly::Value entries(rapidjson_ly::kArrayType);
// Here I'll need to parse translationRoot myself and produce the JSON
for (const auto& entrySource : translationRoot.m_entries)
{
rapidjson_ly::Value entry(rapidjson_ly::kObjectType);
rapidjson_ly::Value value(rapidjson_ly::kStringType);
value.SetString(entrySource.m_key.c_str(), document.GetAllocator());
entry.AddMember(GraphCanvas::Schema::Field::key, value, document.GetAllocator());
value.SetString(entrySource.m_context.c_str(), document.GetAllocator());
entry.AddMember(GraphCanvas::Schema::Field::context, value, document.GetAllocator());
value.SetString(entrySource.m_variant.c_str(), document.GetAllocator());
entry.AddMember(GraphCanvas::Schema::Field::variant, value, document.GetAllocator());
rapidjson_ly::Value details(rapidjson_ly::kObjectType);
value.SetString(entrySource.m_details.m_name.c_str(), document.GetAllocator());
details.AddMember("name", value, document.GetAllocator());
Helpers::WriteString(details, "category", entrySource.m_details.m_category, document);
Helpers::WriteString(details, "tooltip", entrySource.m_details.m_tooltip, document);
Helpers::WriteString(details, "subtitle", entrySource.m_details.m_subtitle, document);
entry.AddMember("details", details, document.GetAllocator());
if (!entrySource.m_methods.empty())
{
rapidjson_ly::Value methods(rapidjson_ly::kArrayType);
for (const auto& methodSource : entrySource.m_methods)
{
rapidjson_ly::Value theMethod(rapidjson_ly::kObjectType);
value.SetString(methodSource.m_key.c_str(), document.GetAllocator());
theMethod.AddMember(GraphCanvas::Schema::Field::key, value, document.GetAllocator());
if (!methodSource.m_context.empty())
{
value.SetString(methodSource.m_context.c_str(), document.GetAllocator());
theMethod.AddMember(GraphCanvas::Schema::Field::context, value, document.GetAllocator());
}
if (!methodSource.m_entry.m_name.empty())
{
rapidjson_ly::Value entrySlot(rapidjson_ly::kObjectType);
value.SetString(methodSource.m_entry.m_name.c_str(), document.GetAllocator());
entrySlot.AddMember("name", value, document.GetAllocator());
Helpers::WriteString(entrySlot, "tooltip", methodSource.m_entry.m_tooltip, document);
theMethod.AddMember("entry", entrySlot, document.GetAllocator());
}
if (!methodSource.m_exit.m_name.empty())
{
rapidjson_ly::Value exitSlot(rapidjson_ly::kObjectType);
value.SetString(methodSource.m_exit.m_name.c_str(), document.GetAllocator());
exitSlot.AddMember("name", value, document.GetAllocator());
Helpers::WriteString(exitSlot, "tooltip", methodSource.m_exit.m_tooltip, document);
theMethod.AddMember("exit", exitSlot, document.GetAllocator());
}
rapidjson_ly::Value methodDetails(rapidjson_ly::kObjectType);
value.SetString(methodSource.m_details.m_name.c_str(), document.GetAllocator());
methodDetails.AddMember("name", value, document.GetAllocator());
Helpers::WriteString(methodDetails, "category", methodSource.m_details.m_category, document);
Helpers::WriteString(methodDetails, "tooltip", methodSource.m_details.m_tooltip, document);
theMethod.AddMember("details", methodDetails, document.GetAllocator());
if (!methodSource.m_arguments.empty())
{
rapidjson_ly::Value methodArguments(rapidjson_ly::kArrayType);
[[maybe_unused]] size_t index = 0;
for (const auto& argSource : methodSource.m_arguments)
{
rapidjson_ly::Value argument(rapidjson_ly::kObjectType);
rapidjson_ly::Value argumentDetails(rapidjson_ly::kObjectType);
value.SetString(argSource.m_typeId.c_str(), document.GetAllocator());
argument.AddMember("typeid", value, document.GetAllocator());
value.SetString(argSource.m_details.m_name.c_str(), document.GetAllocator());
argumentDetails.AddMember("name", value, document.GetAllocator());
Helpers::WriteString(argumentDetails, "category", argSource.m_details.m_category, document);
Helpers::WriteString(argumentDetails, "tooltip", argSource.m_details.m_tooltip, document);
argument.AddMember("details", argumentDetails, document.GetAllocator());
methodArguments.PushBack(argument, document.GetAllocator());
}
theMethod.AddMember("params", methodArguments, document.GetAllocator());
}
if (!methodSource.m_results.empty())
{
rapidjson_ly::Value methodArguments(rapidjson_ly::kArrayType);
for (const auto& argSource : methodSource.m_results)
{
rapidjson_ly::Value argument(rapidjson_ly::kObjectType);
rapidjson_ly::Value argumentDetails(rapidjson_ly::kObjectType);
value.SetString(argSource.m_typeId.c_str(), document.GetAllocator());
argument.AddMember("typeid", value, document.GetAllocator());
value.SetString(argSource.m_details.m_name.c_str(), document.GetAllocator());
argumentDetails.AddMember("name", value, document.GetAllocator());
Helpers::WriteString(argumentDetails, "category", argSource.m_details.m_category, document);
Helpers::WriteString(argumentDetails, "tooltip", argSource.m_details.m_tooltip, document);
argument.AddMember("details", argumentDetails, document.GetAllocator());
methodArguments.PushBack(argument, document.GetAllocator());
}
theMethod.AddMember("results", methodArguments, document.GetAllocator());
}
methods.PushBack(theMethod, document.GetAllocator());
}
entry.AddMember("methods", methods, document.GetAllocator());
}
if (!entrySource.m_slots.empty())
{
rapidjson_ly::Value slotsArray(rapidjson_ly::kArrayType);
for (const auto& slotSource : entrySource.m_slots)
{
rapidjson_ly::Value theSlot(rapidjson_ly::kObjectType);
value.SetString(slotSource.m_key.c_str(), document.GetAllocator());
theSlot.AddMember(GraphCanvas::Schema::Field::key, value, document.GetAllocator());
rapidjson_ly::Value sloDetails(rapidjson_ly::kObjectType);
if (!slotSource.m_details.m_name.empty())
{
Helpers::WriteString(sloDetails, "name", slotSource.m_details.m_name, document);
Helpers::WriteString(sloDetails, "tooltip", slotSource.m_details.m_tooltip, document);
theSlot.AddMember("details", sloDetails, document.GetAllocator());
}
if (!slotSource.m_data.m_details.m_name.empty())
{
rapidjson_ly::Value slotDataDetails(rapidjson_ly::kObjectType);
Helpers::WriteString(slotDataDetails, "name", slotSource.m_data.m_details.m_name, document);
theSlot.AddMember("details", slotDataDetails, document.GetAllocator());
}
slotsArray.PushBack(theSlot, document.GetAllocator());
}
entry.AddMember("slots", slotsArray, document.GetAllocator());
}
entries.PushBack(entry, document.GetAllocator());
}
document.AddMember("entries", entries, document.GetAllocator());
AZ::IO::Path gemPath = Helpers::GetGemPath("ScriptCanvas.Editor");
gemPath = gemPath / AZ::IO::Path("TranslationAssets");
gemPath = gemPath / filename;
gemPath.ReplaceExtension(".names");
AZStd::string folderPath;
AZ::StringFunc::Path::GetFolderPath(gemPath.c_str(), folderPath);
if (!AZ::IO::FileIOBase::GetInstance()->Exists(folderPath.c_str()))
{
if (AZ::IO::FileIOBase::GetInstance()->CreatePath(folderPath.c_str()) != AZ::IO::ResultCode::Success)
{
AZ_Error("Translation", false, "Failed to create output folder");
return;
}
}
char resolvedBuffer[AZ_MAX_PATH_LEN] = { 0 };
AZ::IO::FileIOBase::GetInstance()->ResolvePath(gemPath.c_str(), resolvedBuffer, AZ_MAX_PATH_LEN);
AZStd::string endPath = resolvedBuffer;
AZ::StringFunc::Path::Normalize(endPath);
AZ::IO::SystemFile outputFile;
if (!outputFile.Open(endPath.c_str(),
AZ::IO::SystemFile::OpenMode::SF_OPEN_CREATE |
AZ::IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH |
AZ::IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
{
AZ_Error("Translation", false, "Failed to open file for writing: %s", filename.c_str());
return;
}
rapidjson_ly::StringBuffer scratchBuffer;
rapidjson_ly::PrettyWriter<rapidjson_ly::StringBuffer> writer(scratchBuffer);
document.Accept(writer);
outputFile.Write(scratchBuffer.GetString(), scratchBuffer.GetSize());
outputFile.Close();
scratchBuffer.Clear();
// AzQtComponents::ShowFileOnDesktop(endPath.c_str());
}
void TranslationGeneration::SplitCamelCase(AZStd::string& text)
{
AZStd::regex splitRegex(R"(/[a-z]+|[0-9]+|(?:[A-Z][a-z]+)|(?:[A-Z]+(?=(?:[A-Z][a-z])|[^AZa-z]|[$\d\n]))/g)");
text = AZStd::regex_replace(text, splitRegex, " $&");
text = AZ::StringFunc::LStrip(text);
AZ::StringFunc::Replace(text, " ", " ");
}
namespace Helpers
{
AZStd::string ReadStringAttribute(const AZ::AttributeArray& attributes, const AZ::Crc32& attribute)
{
AZStd::string attributeValue = "";
if (auto attributeItem = azrtti_cast<AZ::AttributeData<AZStd::string>*>(AZ::FindAttribute(attribute, attributes)))
{
attributeValue = attributeItem->Get(nullptr);
return attributeValue;
}
if (auto attributeItem = azrtti_cast<AZ::AttributeData<const char*>*>(AZ::FindAttribute(attribute, attributes)))
{
attributeValue = attributeItem->Get(nullptr);
return attributeValue;
}
return {};
}
bool MethodHasAttribute(const AZ::BehaviorMethod* method, AZ::Crc32 attribute)
{
return AZ::FindAttribute(attribute, method->m_attributes) != nullptr; // warning C4800: 'AZ::Attribute *': forcing value to bool 'true' or 'false' (performance warning)
}
void GetTypeNameAndDescription(AZ::TypeId typeId, AZStd::string& outName, AZStd::string& outDescription)
{
AZ::SerializeContext* serializeContext{};
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ_Assert(serializeContext, "Serialize Context is required");
if (const AZ::SerializeContext::ClassData* classData = serializeContext->FindClassData(typeId))
{
if (classData->m_editData)
{
outName = classData->m_editData->m_name ? classData->m_editData->m_name : classData->m_name;
outDescription = classData->m_editData->m_description ? classData->m_editData->m_description : "";
}
else
{
outName = classData->m_name;
}
}
}
AZStd::string GetGemPath(const AZStd::string& gemName)
{
if (auto settingsRegistry = AZ::Interface<AZ::SettingsRegistryInterface>::Get(); settingsRegistry != nullptr)
{
AZ::IO::Path gemSourceAssetDirectories;
AZStd::vector<AzFramework::GemInfo> gemInfos;
if (AzFramework::GetGemsInfo(gemInfos, *settingsRegistry))
{
auto FindGemByName = [gemName](const AzFramework::GemInfo& gemInfo)
{
return gemInfo.m_gemName == gemName;
};
// Gather unique list of Gem Paths from the Settings Registry
auto foundIt = AZStd::find_if(gemInfos.begin(), gemInfos.end(), FindGemByName);
if (foundIt != gemInfos.end())
{
const AzFramework::GemInfo& gemInfo = *foundIt;
for (const AZ::IO::Path& absoluteSourcePath : gemInfo.m_absoluteSourcePaths)
{
gemSourceAssetDirectories = (absoluteSourcePath / gemInfo.GetGemAssetFolder());
}
return gemSourceAssetDirectories.c_str();
}
}
}
return "";
}
AZStd::string GetCategory(const AZ::SerializeContext::ClassData* classData)
{
AZStd::string categoryPath;
if (classData->m_editData)
{
auto editorElementData = classData->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData);
if (editorElementData)
{
if (auto categoryAttribute = editorElementData->FindAttribute(AZ::Edit::Attributes::Category))
{
if (auto categoryAttributeData = azdynamic_cast<const AZ::Edit::AttributeData<const char*>*>(categoryAttribute))
{
categoryPath = categoryAttributeData->Get(nullptr);
}
}
}
}
return categoryPath;
}
AZStd::string GetLibraryCategory(const AZ::SerializeContext& serializeContext, const AZStd::string& nodeName)
{
AZStd::string category;
// Get all the types.
auto EnumerateLibraryDefintionNodes = [&nodeName, &category, &serializeContext](
const AZ::SerializeContext::ClassData* classData, const AZ::Uuid&) -> bool
{
AZStd::string categoryPath = classData->m_editData ? classData->m_editData->m_name : classData->m_name;
if (classData->m_editData)
{
auto editorElementData = classData->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData);
if (editorElementData)
{
if (auto categoryAttribute = editorElementData->FindAttribute(AZ::Edit::Attributes::Category))
{
if (auto categoryAttributeData = azdynamic_cast<const AZ::Edit::AttributeData<const char*>*>(categoryAttribute))
{
categoryPath = categoryAttributeData->Get(nullptr);
}
}
}
}
// Children
for (auto& node : ScriptCanvas::Library::LibraryDefinition::GetNodes(classData->m_typeId))
{
// Pass in the associated class data so we can do more intensive lookups?
const AZ::SerializeContext::ClassData* nodeClassData = serializeContext.FindClassData(node.first);
if (nodeClassData == nullptr)
{
continue;
}
// Skip over some of our more dynamic nodes that we want to populate using different means
else if (nodeClassData->m_azRtti && nodeClassData->m_azRtti->IsTypeOf<ScriptCanvas::Nodes::Core::GetVariableNode>())
{
continue;
}
else if (nodeClassData->m_azRtti && nodeClassData->m_azRtti->IsTypeOf<ScriptCanvas::Nodes::Core::SetVariableNode>())
{
continue;
}
else
{
if (node.second == nodeName)
{
category = categoryPath;
return false;
}
}
}
return true;
};
const AZ::TypeId& libraryDefTypeId = azrtti_typeid<ScriptCanvas::Library::LibraryDefinition>();
serializeContext.EnumerateDerived(EnumerateLibraryDefintionNodes, libraryDefTypeId, libraryDefTypeId);
return category;
}
void WriteString(rapidjson_ly::Value& owner, const AZStd::string& key, const AZStd::string& value, rapidjson_ly::Document& document)
{
if (key.empty() || value.empty())
{
return;
}
rapidjson_ly::Value item(rapidjson_ly::kStringType);
item.SetString(value.c_str(), document.GetAllocator());
rapidjson_ly::Value keyVal(rapidjson_ly::kStringType);
keyVal.SetString(key.c_str(), document.GetAllocator());
owner.AddMember(keyVal, item, document.GetAllocator());
}
}
}