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/Code/Sandbox/Editor/PythonEditorFuncs.cpp

1279 lines
47 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include "EditorDefs.h"
#include "PythonEditorFuncs.h"
// Qt
#include <QMessageBox>
#include <QFileDialog>
#include <AzCore/Utils/Utils.h>
// AzToolsFramework
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
#include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
// Editor
#include "CryEdit.h"
#include "GameEngine.h"
#include "ViewManager.h"
#include "StringDlg.h"
#include "GenericSelectItemDialog.h"
#include "Util/Ruler.h"
#include "Objects/BaseObject.h"
#include "Commands/CommandManager.h"
namespace
{
//////////////////////////////////////////////////////////////////////////
const char* PyGetCVar(const char* pName)
{
ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(pName);
if (!pCVar)
{
Warning("PyGetCVar: Attempt to access non-existent CVar '%s'", pName ? pName : "(null)");
throw std::logic_error((QString("\"") + pName + "\" is an invalid cvar.").toUtf8().data());
}
return pCVar->GetString();
}
//////////////////////////////////////////////////////////////////////////
const char* PyGetCVarAsString(const char* pName)
{
ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(pName);
if (!pCVar)
{
AZ_Warning("editor", false, "PyGetCVar: Attempt to access non-existent CVar '%s'", pName ? pName : "(null)");
return "(missing)";
}
return pCVar->GetString();
}
//////////////////////////////////////////////////////////////////////////
// forward declarations
void PySetCVarFromInt(const char* pName, int pValue);
void PySetCVarFromFloat(const char* pName, float pValue);
//////////////////////////////////////////////////////////////////////////
void PySetCVarFromString(const char* pName, const char* pValue)
{
ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(pName);
if (!pCVar)
{
AZ_Warning("editor", false, "Attempt to set non-existent string CVar '%s'", pName ? pName : "(null)");
}
else if (pCVar->GetType() == CVAR_INT)
{
PySetCVarFromInt(pName, std::stol(pValue));
}
else if (pCVar->GetType() == CVAR_FLOAT)
{
PySetCVarFromFloat(pName, std::stod(pValue));
}
else if (pCVar->GetType() != CVAR_STRING)
{
AZ_Warning("editor", false, "Type mismatch while assigning CVar '%s' as a string.", pName ? pName : "(null)");
}
else
{
pCVar->Set(pValue);
}
}
//////////////////////////////////////////////////////////////////////////
void PySetCVarFromInt(const char* pName, int pValue)
{
ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(pName);
if (!pCVar)
{
AZ_Warning("editor", false, "Attempt to set non-existent integer CVar '%s'", pName ? pName : "(null)");
}
else if (pCVar->GetType() == CVAR_FLOAT)
{
PySetCVarFromFloat(pName, float(pValue));
}
else if (pCVar->GetType() == CVAR_STRING)
{
auto stringValue = AZStd::to_string(pValue);
PySetCVarFromString(pName, stringValue.c_str());
}
else if (pCVar->GetType() != CVAR_INT)
{
AZ_Warning("editor", false, "Type mismatch while assigning CVar '%s' as an integer.", pName ? pName : "(null)");
}
else
{
pCVar->Set(pValue);
}
}
//////////////////////////////////////////////////////////////////////////
void PySetCVarFromFloat(const char* pName, float pValue)
{
ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(pName);
if (!pCVar)
{
AZ_Warning("editor", false, "Attempt to set non-existent float CVar '%s'", pName ? pName : "(null)");
}
else if (pCVar->GetType() == CVAR_INT)
{
PySetCVarFromInt(pName, static_cast<int>(pValue));
}
else if (pCVar->GetType() == CVAR_STRING)
{
auto stringValue = AZStd::to_string(pValue);
PySetCVarFromString(pName, stringValue.c_str());
}
else if (pCVar->GetType() != CVAR_FLOAT)
{
AZ_Warning("editor", false, "Type mismatch while assigning CVar '%s' as a float.", pName ? pName : "(null)");
}
else
{
pCVar->Set(pValue);
}
}
//////////////////////////////////////////////////////////////////////////
void PySetCVarFromAny(const char* pName, const AZStd::any& value)
{
ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(pName);
if (!pCVar)
{
AZ_Warning("editor", false, "Attempt to set non-existent float CVar '%s'", pName ? pName : "(null)");
}
else if (pCVar->GetType() == CVAR_INT)
{
PySetCVarFromInt(pName, AZStd::any_cast<AZ::s64>(value));
}
else if (pCVar->GetType() == CVAR_FLOAT)
{
PySetCVarFromFloat(pName, AZStd::any_cast<double>(value));
}
else if (pCVar->GetType() == CVAR_STRING)
{
PySetCVarFromString(pName, AZStd::any_cast<AZStd::string_view>(value).data());
}
else
{
AZ_Warning("editor", false, "Type mismatch while assigning CVar '%s' as a float.", pName ? pName : "(null)");
}
}
//////////////////////////////////////////////////////////////////////////
void PyEnterGameMode()
{
if (GetIEditor()->GetGameEngine())
{
GetIEditor()->GetGameEngine()->RequestSetGameMode(true);
}
}
void PyExitGameMode()
{
if (GetIEditor()->GetGameEngine())
{
GetIEditor()->GetGameEngine()->RequestSetGameMode(false);
}
}
bool PyIsInGameMode()
{
return GetIEditor()->IsInGameMode();
}
//////////////////////////////////////////////////////////////////////////
void PyEnterSimulationMode()
{
if (!GetIEditor()->IsInSimulationMode())
{
CCryEditApp::instance()->OnSwitchPhysics();
}
}
void PyExitSimulationMode()
{
if (GetIEditor()->IsInSimulationMode())
{
CCryEditApp::instance()->OnSwitchPhysics();
}
}
bool PyIsInSimulationMode()
{
return GetIEditor()->IsInSimulationMode();
}
//////////////////////////////////////////////////////////////////////////
QString PyNewObject(const char* typeName, const char* fileName, const char* name, float x, float y, float z)
{
CBaseObject* object = GetIEditor()->NewObject(typeName, fileName, name, x, y, z);
if (object)
{
return object->GetName();
}
else
{
return "";
}
}
//////////////////////////////////////////////////////////////////////////
QString PyNewObjectAtCursor(const char* typeName, const char* fileName, const char* name)
{
CUndo undo("Create new object");
Vec3 pos(0, 0, 0);
QPoint p = QCursor::pos();
CViewport* viewport = GetIEditor()->GetViewManager()->GetViewportAtPoint(p);
if (viewport)
{
viewport->ScreenToClient(p);
if (GetIEditor()->GetAxisConstrains() != AXIS_TERRAIN)
{
pos = viewport->MapViewToCP(p);
}
else
{
// Snap to terrain.
bool hitTerrain;
pos = viewport->ViewToWorld(p, &hitTerrain);
if (hitTerrain)
{
pos.z = GetIEditor()->GetTerrainElevation(pos.x, pos.y) + 1.0f;
}
pos = viewport->SnapToGrid(pos);
}
}
return PyNewObject(typeName, fileName, name, pos.x, pos.y, pos.z);
}
//////////////////////////////////////////////////////////////////////////
void PyRunConsole(const char* text)
{
GetIEditor()->GetSystem()->GetIConsole()->ExecuteString(text);
}
//////////////////////////////////////////////////////////////////////////
bool GetPythonScriptPath(const char* pFile, QString& path)
{
bool bRelativePath = true;
char drive[_MAX_DRIVE];
char fdir[_MAX_DIR];
char fname[_MAX_FNAME];
char fext[_MAX_EXT];
drive[0] = '\0';
_splitpath_s(pFile, drive, fdir, fname, fext);
if (strlen(drive) != 0)
{
bRelativePath = false;
}
if (bRelativePath)
{
// Try to open from user folder
QString userSandboxFolder = Path::GetResolvedUserSandboxFolder();
Path::ConvertBackSlashToSlash(userSandboxFolder);
path = userSandboxFolder + pFile;
// If not found try editor folder
if (!CFileUtil::FileExists(path))
{
AZ::IO::FixedMaxPathString engineRoot = AZ::Utils::GetEnginePath();
QDir engineDir = !engineRoot.empty() ? QDir(QString(engineRoot.c_str())) : QDir::current();
QString scriptFolder = engineDir.absoluteFilePath("Editor/Scripts/");
Path::ConvertBackSlashToSlash(scriptFolder);
path = scriptFolder + pFile;
if (!CFileUtil::FileExists(path))
{
QString error = QString("Could not find '%1'\n in '%2'\n or '%3'\n").arg(pFile).arg(userSandboxFolder).arg(scriptFolder);
AZ_Warning("python", false, error.toUtf8().data());
return false;
}
}
}
else
{
path = pFile;
if (!CFileUtil::FileExists(path))
{
QString error = QString("Could not find '") + pFile + "'\n";
AZ_Warning("python", false, error.toUtf8().data());
return false;
}
}
Path::ConvertBackSlashToSlash(path);
return true;
}
//////////////////////////////////////////////////////////////////////////
void GetPythonArgumentsVector(const char* pArguments, QStringList& inputArguments)
{
if (pArguments == NULL)
{
return;
}
inputArguments = QString(pArguments).split(" ", Qt::SkipEmptyParts);
}
//////////////////////////////////////////////////////////////////////////
void PyRunFileWithParameters(const char* pFile, const char* pArguments)
{
QString path;
QStringList inputArguments;
GetPythonArgumentsVector(pArguments, inputArguments);
if (GetPythonScriptPath(pFile, path))
{
AZStd::vector<AZStd::string_view> argList;
argList.reserve(inputArguments.size() + 1);
QByteArray p = path.toUtf8();
QVector<QByteArray> argData;
argData.reserve(inputArguments.count());
for (auto iter = inputArguments.begin(); iter != inputArguments.end(); ++iter)
{
argData.push_back(iter->toLatin1());
argList.push_back(argData.last().data());
}
using namespace AzToolsFramework;
EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, p.data(), argList);
}
}
void PyRunFile(const char* pFile)
{
PyRunFileWithParameters(pFile, nullptr);
}
//////////////////////////////////////////////////////////////////////////
void PyExecuteCommand(const char* cmdline)
{
GetIEditor()->GetCommandManager()->Execute(cmdline);
}
//////////////////////////////////////////////////////////////////////////
void PyLog(const char* pMessage)
{
if (strcmp(pMessage, "") != 0)
{
CryLogAlways(pMessage);
}
}
//////////////////////////////////////////////////////////////////////////
bool PyMessageBox(const char* pMessage)
{
return QMessageBox::information(QApplication::activeWindow(), QString(), pMessage, QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok;
}
bool PyMessageBoxYesNo(const char* pMessage)
{
return QMessageBox::question(QApplication::activeWindow(), QString(), pMessage) == QMessageBox::Yes;
}
bool PyMessageBoxOK(const char* pMessage)
{
return QMessageBox::information(QApplication::activeWindow(), QString(), pMessage) == QMessageBox::Ok;
}
AZStd::string PyEditBox(AZStd::string_view pTitle)
{
StringDlg stringDialog(pTitle.data());
if (stringDialog.exec() == QDialog::Accepted)
{
return stringDialog.GetString().toUtf8().constData();
}
return "";
}
AZStd::any PyEditBoxAndCheckProperty(const char* pTitle)
{
StringDlg stringDialog(pTitle);
stringDialog.SetString(QStringLiteral(""));
if (stringDialog.exec() == QDialog::Accepted)
{
const QString stringValue = stringDialog.GetString();
// detect data type
QString tempString = stringValue;
int countComa = 0;
int countOpenRoundBraket = 0;
int countCloseRoundBraket = 0;
int countDots = 0;
int posDots = 0;
int posComa = 0;
int posOpenRoundBraket = 0;
int posCloseRoundBraket = 0;
for (int i = 0; i < 3; i++)
{
if (tempString.indexOf(".", posDots) > -1)
{
posDots = tempString.indexOf(".", posDots) + 1;
countDots++;
}
if (tempString.indexOf(",", posComa) > -1)
{
posComa = tempString.indexOf(",", posComa) + 1;
countComa++;
}
if (tempString.indexOf("(", posOpenRoundBraket) > -1)
{
posOpenRoundBraket = tempString.indexOf("(", posOpenRoundBraket) + 1;
countOpenRoundBraket++;
}
if (tempString.indexOf(")", posCloseRoundBraket) > -1)
{
posCloseRoundBraket = tempString.indexOf(")", posCloseRoundBraket) + 1;
countCloseRoundBraket++;
}
}
if (countDots == 3 && countComa == 2 && countOpenRoundBraket == 1 && countCloseRoundBraket == 1)
{
// for example: (1.95, 2.75, 3.36)
QString valueRed = stringValue;
int iStart = valueRed.indexOf("(");
valueRed.remove(0, iStart + 1);
int iEnd = valueRed.indexOf(",");
valueRed.remove(iEnd, valueRed.length());
float fValueRed = valueRed.toFloat();
QString valueGreen = stringValue;
iStart = valueGreen.indexOf(",");
valueGreen.remove(0, iStart + 1);
iEnd = valueGreen.indexOf(",");
valueGreen.remove(iEnd, valueGreen.length());
float fValueGreen = valueGreen.toFloat();
QString valueBlue = stringValue;
valueBlue.remove(0, valueBlue.indexOf(",") + 1);
valueBlue.remove(0, valueBlue.indexOf(",") + 1);
valueBlue.remove(valueBlue.indexOf(")"), valueBlue.length());
float fValueBlue = valueBlue.toFloat();
return AZStd::make_any<AZ::Vector3>(fValueRed, fValueGreen, fValueBlue);
}
else if (countDots == 0 && countComa == 2 && countOpenRoundBraket == 1 && countCloseRoundBraket == 1)
{
// for example: (128, 32, 240)
const AZ::u8 lowColorValue { 0 };
const AZ::u8 highColorValue { 255 };
QString valueRed = stringValue;
int iStart = valueRed.indexOf("(");
valueRed.remove(0, iStart + 1);
int iEnd = valueRed.indexOf(",");
valueRed.remove(iEnd, valueRed.length());
AZ::u8 iValueRed = AZStd::clamp(aznumeric_cast<AZ::u8>(valueRed.toInt()), lowColorValue, highColorValue);
QString valueGreen = stringValue;
iStart = valueGreen.indexOf(",");
valueGreen.remove(0, iStart + 1);
iEnd = valueGreen.indexOf(",");
valueGreen.remove(iEnd, valueGreen.length());
AZ::u8 iValueGreen = AZStd::clamp(aznumeric_cast<AZ::u8>(valueGreen.toInt()), lowColorValue, highColorValue);
QString valueBlue = stringValue;
valueBlue.remove(0, valueBlue.indexOf(",") + 1);
valueBlue.remove(0, valueBlue.indexOf(",") + 1);
valueBlue.remove(valueBlue.indexOf(")"), valueBlue.length());
AZ::u8 iValueBlue = AZStd::clamp(aznumeric_cast<AZ::u8>(valueBlue.toInt()), lowColorValue, highColorValue);
return AZStd::make_any<AZ::Color>(iValueRed, iValueGreen, iValueBlue, highColorValue);
}
else if (countDots == 1 && countComa == 0 && countOpenRoundBraket == 0 && countCloseRoundBraket == 0)
{
// for example: 2.56
return AZStd::make_any<double>(stringValue.toDouble());
}
else if (countDots == 0 && countComa == 0 && countOpenRoundBraket == 0 && countCloseRoundBraket == 0)
{
if (stringValue == "False" || stringValue == "True")
{
// for example: True
return AZStd::make_any<bool>(stringValue == "True");
}
else
{
const bool anyNotDigits = AZStd::any_of(stringValue.begin(), stringValue.end(), [](const QChar& ch) { return !ch.isDigit(); });
// looks like a string value?
if (stringValue.isEmpty() || anyNotDigits)
{
// for example: Hello
return AZStd::make_any<AZStd::string>(stringValue.toUtf8().data());
}
else // then it looks like an integer
{
// for example: 456
return AZStd::make_any<AZ::s64>(stringValue.toInt());
}
}
}
}
QMessageBox::critical(AzToolsFramework::GetActiveWindow(), QObject::tr("Invalid Data"), QObject::tr("Invalid data type."));
return {};
}
AZStd::string PyOpenFileBox()
{
QString path = QFileDialog::getOpenFileName();
if (!path.isEmpty())
{
Path::ConvertBackSlashToSlash(path);
}
return path.toUtf8().constData();
}
AZStd::string PyComboBox(AZStd::string title, AZStd::vector<AZStd::string> values, int selectedIdx = 0)
{
AZStd::string result;
if (title.empty())
{
throw std::runtime_error("Incorrect title argument passed in. ");
return result;
}
if (values.size() == 0)
{
throw std::runtime_error("Empty value list passed in. ");
return result;
}
QStringList list;
for (const AZStd::string& str : values)
{
list.push_back(str.c_str());
}
CGenericSelectItemDialog pyDlg;
pyDlg.setWindowTitle(title.c_str());
pyDlg.SetMode(CGenericSelectItemDialog::eMODE_LIST);
pyDlg.SetItems(list);
pyDlg.PreSelectItem(list[selectedIdx]);
if (pyDlg.exec() == QDialog::Accepted)
{
result = pyDlg.GetSelectedItem().toUtf8().constData();
}
return result;
}
void PyCrash()
{
AZ_Crash();
}
static void PyDrawLabel(int x, int y, float size, float r, float g, float b, float a, const char* pLabel)
{
if (!pLabel)
{
throw std::logic_error("No label given.");
return;
}
if (!r || !g || !b || !a)
{
throw std::logic_error("Invalid color parameters given.");
return;
}
if (!x || !y || !size)
{
throw std::logic_error("Invalid position or size parameters given.");
}
else
{
float color[] = {r, g, b, a};
gEnv->pRenderer->Draw2dLabel(x, y, size, color, false, pLabel);
}
}
//////////////////////////////////////////////////////////////////////////
// Constrain
//////////////////////////////////////////////////////////////////////////
const char* PyGetAxisConstraint()
{
AxisConstrains actualConstrain = GetIEditor()->GetAxisConstrains();
switch (actualConstrain)
{
case AXIS_X:
return "X";
case AXIS_Y:
return "Y";
case AXIS_Z:
return "Z";
case AXIS_XY:
return "XY";
case AXIS_XZ:
return "XZ";
case AXIS_YZ:
return "YZ";
case AXIS_XYZ:
return "XYZ";
case AXIS_TERRAIN:
return (GetIEditor()->IsTerrainAxisIgnoreObjects()) ? "TERRAIN" : "TERRAINSNAP";
default:
throw std::logic_error("Invalid axes.");
}
}
void PySetAxisConstraint(AZStd::string_view pConstrain)
{
if (pConstrain == "X")
{
GetIEditor()->SetAxisConstraints(AXIS_X);
}
else if (pConstrain == "Y")
{
GetIEditor()->SetAxisConstraints(AXIS_Y);
}
else if (pConstrain == "Z")
{
GetIEditor()->SetAxisConstraints(AXIS_Z);
}
else if (pConstrain == "XY")
{
GetIEditor()->SetAxisConstraints(AXIS_XY);
}
else if (pConstrain == "YZ")
{
GetIEditor()->SetAxisConstraints(AXIS_YZ);
}
else if (pConstrain == "XZ")
{
GetIEditor()->SetAxisConstraints(AXIS_XZ);
}
else if (pConstrain == "XYZ")
{
GetIEditor()->SetAxisConstraints(AXIS_XYZ);
}
else if (pConstrain == "TERRAIN")
{
GetIEditor()->SetAxisConstraints(AXIS_TERRAIN);
GetIEditor()->SetTerrainAxisIgnoreObjects(true);
}
else if (pConstrain == "TERRAINSNAP")
{
GetIEditor()->SetAxisConstraints(AXIS_TERRAIN);
GetIEditor()->SetTerrainAxisIgnoreObjects(false);
}
else
{
throw std::logic_error("Invalid axes.");
}
}
//////////////////////////////////////////////////////////////////////////
// Edit Mode
//////////////////////////////////////////////////////////////////////////
const char* PyGetEditMode()
{
int actualEditMode = GetIEditor()->GetEditMode();
switch (actualEditMode)
{
case eEditModeSelect:
return "SELECT";
case eEditModeSelectArea:
return "SELECTAREA";
case eEditModeMove:
return "MOVE";
case eEditModeRotate:
return "ROTATE";
case eEditModeScale:
return "SCALE";
case eEditModeTool:
return "TOOL";
default:
throw std::logic_error("Invalid edit mode.");
}
}
void PySetEditMode(AZStd::string_view pEditMode)
{
if (pEditMode == "MOVE")
{
GetIEditor()->SetEditMode(eEditModeMove);
}
else if (pEditMode == "ROTATE")
{
GetIEditor()->SetEditMode(eEditModeRotate);
}
else if (pEditMode == "SCALE")
{
GetIEditor()->SetEditMode(eEditModeScale);
}
else if (pEditMode == "SELECT")
{
GetIEditor()->SetEditMode(eEditModeSelect);
}
else if (pEditMode == "SELECTAREA")
{
GetIEditor()->SetEditMode(eEditModeSelectArea);
}
else if (pEditMode == "TOOL")
{
GetIEditor()->SetEditMode(eEditModeTool);
}
else if (pEditMode == "RULER")
{
CRuler* pRuler = GetIEditor()->GetRuler();
pRuler->SetActive(!pRuler->IsActive());
}
else
{
throw std::logic_error("Invalid edit mode.");
}
}
//////////////////////////////////////////////////////////////////////////
const char* PyGetPakFromFile(const char* filename)
{
auto pIPak = GetIEditor()->GetSystem()->GetIPak();
AZ::IO::HandleType fileHandle = pIPak->FOpen(filename, "rb");
if (fileHandle == AZ::IO::InvalidHandle)
{
throw std::logic_error("Invalid file name.");
}
const char* pArchPath = pIPak->GetFileArchivePath(fileHandle);
pIPak->FClose(fileHandle);
return pArchPath;
}
//////////////////////////////////////////////////////////////////////////
void PyUndo()
{
GetIEditor()->Undo();
}
//////////////////////////////////////////////////////////////////////////
void PyRedo()
{
GetIEditor()->Redo();
}
}
//////////////////////////////////////////////////////////////////////////
// Temporal, to be removed by LY-101149
AZ::EntityId PyFindEditorEntity(const char* name)
{
AZ::EntityId foundEntityId;
auto searchFunc = [name, &foundEntityId](AZ::Entity* e)
{
if (!foundEntityId.IsValid() && e->GetName() == name)
{
bool isEditorEntity = false;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(isEditorEntity, &AzToolsFramework::EditorEntityContextRequests::IsEditorEntity, e->GetId());
if (isEditorEntity)
{
foundEntityId = e->GetId();
}
}
};
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::EnumerateEntities, searchFunc);
return foundEntityId;
}
AZ::EntityId PyFindGameEntity(const char* name)
{
AZ::EntityId foundEntityId;
auto searchFunc = [name, &foundEntityId](AZ::Entity* e)
{
if (!foundEntityId.IsValid() && e->GetName() == name)
{
bool isEditorEntity = true;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(isEditorEntity, &AzToolsFramework::EditorEntityContextRequests::IsEditorEntity, e->GetId());
if (!isEditorEntity)
{
foundEntityId = e->GetId();
}
}
};
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::EnumerateEntities, searchFunc);
return foundEntityId;
}
struct PyDumpBindings
{
static AZ_INLINE bool IsBehaviorFlaggedForEditor(const AZ::AttributeArray& attributes)
{
// defaults to Launcher
AZ::Script::Attributes::ScopeFlags scopeType = AZ::Script::Attributes::ScopeFlags::Launcher;
AZ::Attribute* scopeAttribute = AZ::FindAttribute(AZ::Script::Attributes::Scope, attributes);
if (scopeAttribute)
{
AZ::AttributeReader scopeAttributeReader(nullptr, scopeAttribute);
scopeAttributeReader.Read<AZ::Script::Attributes::ScopeFlags>(scopeType);
}
return (scopeType == AZ::Script::Attributes::ScopeFlags::Automation || scopeType == AZ::Script::Attributes::ScopeFlags::Common);
}
static AZ_INLINE AZStd::string GetModuleName(const AZ::AttributeArray& attributes)
{
AZStd::string moduleName;
AZ::Attribute* moduleAttribute = AZ::FindAttribute(AZ::Script::Attributes::Module, attributes);
if (moduleAttribute)
{
AZ::AttributeReader scopeAttributeReader(nullptr, moduleAttribute);
scopeAttributeReader.Read<AZStd::string>(moduleName);
}
if (!moduleName.empty())
{
moduleName = "azlmbr." + moduleName;
}
else
{
moduleName = "azlmbr";
}
return moduleName;
}
static AZStd::string ParameterToString(const AZ::BehaviorMethod* method, size_t index)
{
const AZStd::string* argNameStr = method->GetArgumentName(index);
const char* argName = (argNameStr && !argNameStr->empty()) ? argNameStr->c_str() : nullptr;
if (argName)
{
return AZStd::string::format("%s %s", method->GetArgument(index)->m_name, argName);
}
else
{
return method->GetArgument(index)->m_name;
}
}
static AZStd::string MethodArgumentsToString(const AZ::BehaviorMethod* method)
{
AZStd::string ret;
AZStd::string argumentStr;
for (size_t i = 0; i < method->GetNumArguments(); ++i)
{
argumentStr = ParameterToString(method, i);
ret += argumentStr;
if (i < method->GetNumArguments() - 1)
{
ret += ", ";
}
}
return ret;
}
static AZStd::string MethodToString(const AZStd::string& methodName, const AZ::BehaviorMethod* method)
{
AZStd::string methodNameStrip = methodName.data() + methodName.rfind(':') + 1; // remove ClassName:: part as it is redundant
return AZStd::string::format("%s %s(%s)%s", method->GetResult()->m_name, methodNameStrip.c_str(), MethodArgumentsToString(method).c_str(), method->m_isConst ? " const" : "");
}
static AZStd::string GetExposedPythonClasses()
{
AZ::BehaviorContext* behaviorContext(nullptr);
AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
AZStd::string output = "";
output += "// Classes\n\n";
for (auto elem : behaviorContext->m_classes)
{
AZ::BehaviorClass* cls = elem.second;
bool exposedToPython = IsBehaviorFlaggedForEditor(cls->m_attributes);
if (!exposedToPython)
continue;
output += AZStd::string::format("// Module: %s\n", GetModuleName(cls->m_attributes).c_str());
output += AZStd::string::format("class %s\n", cls->m_name.c_str());
output += "{\n";
if (cls->m_methods.size() > 0)
{
output += " // Methods\n";
for (auto method_elem : cls->m_methods)
{
output += AZStd::string::format(" %s;\n", MethodToString(method_elem.first, method_elem.second).c_str());
}
}
if (cls->m_properties.size() > 0)
{
output += " // Properties\n";
for (auto property_elem : cls->m_properties)
{
AZ::BehaviorProperty* bproperty = property_elem.second;
output += AZStd::string::format(" %s %s;\n", bproperty->m_getter->GetResult()->m_name, bproperty->m_name.c_str());
}
}
output += "}\n";
}
output += "\n\n// Ebuses\n\n";
for (auto elem : behaviorContext->m_ebuses)
{
AZ::BehaviorEBus* ebus = elem.second;
bool exposedToPython = IsBehaviorFlaggedForEditor(ebus->m_attributes);
if (!exposedToPython)
continue;
output += AZStd::string::format("// Module: %s\n", GetModuleName(ebus->m_attributes).c_str());
output += AZStd::string::format("ebus %s\n", ebus->m_name.c_str());
output += "{\n";
for (auto event_elem : ebus->m_events)
{
auto method = event_elem.second.m_event ? event_elem.second.m_event : event_elem.second.m_broadcast;
if (method)
{
const char* comment = event_elem.second.m_event ? "/* event */" : "/* broadcast */";
output += AZStd::string::format(" %s %s\n", comment, MethodToString(event_elem.first, method).c_str());
}
else
{
output += AZStd::string::format(" %s %s\n", "/* unknown */", event_elem.first.c_str());
}
}
if (ebus->m_createHandler)
{
AZ::BehaviorEBusHandler* handler = nullptr;
ebus->m_createHandler->InvokeResult(handler);
if (handler)
{
const auto& notifications = handler->GetEvents();
for (const auto& notification : notifications)
{
AZStd::string argsStr;
const size_t paramCount = notification.m_parameters.size();
for (size_t i = 0; i < notification.m_parameters.size(); ++i)
{
AZStd::string argName = notification.m_parameters[i].m_name;
argsStr += argName;
if (i != paramCount - 1)
{
argsStr += ", ";
}
}
AZStd::string funcName = notification.m_name;
output += AZStd::string::format(" /* notification */ %s(%s);\n", funcName.c_str(), argsStr.c_str());
}
ebus->m_destroyHandler->Invoke(handler);
}
}
output += "}\n";
}
AzFramework::StringFunc::Replace(output, "AZStd::basic_string<char, AZStd::char_traits<char>, allocator>", "AZStd::string");
return output;
}
};
//////////////////////////////////////////////////////////////////////////
namespace AzToolsFramework
{
void PythonEditorComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<EditorLayerPythonRequestBus>("PythonEditorBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "python_editor_funcs")
->Event("GetCVar", &EditorLayerPythonRequestBus::Events::GetCVar)
->Event("SetCVar", &EditorLayerPythonRequestBus::Events::SetCVar)
->Event("SetCVarFromString", &EditorLayerPythonRequestBus::Events::SetCVarFromString)
->Event("SetCVarFromInteger", &EditorLayerPythonRequestBus::Events::SetCVarFromInteger)
->Event("SetCVarFromFloat", &EditorLayerPythonRequestBus::Events::SetCVarFromFloat)
->Event("RunConsole", &EditorLayerPythonRequestBus::Events::PyRunConsole)
->Event("EnterGameMode", &EditorLayerPythonRequestBus::Events::EnterGameMode)
->Event("IsInGameMode", &EditorLayerPythonRequestBus::Events::IsInGameMode)
->Event("ExitGameMode", &EditorLayerPythonRequestBus::Events::ExitGameMode)
->Event("EnterSimulationMode", &EditorLayerPythonRequestBus::Events::EnterSimulationMode)
->Event("IsInSimulationMode", &EditorLayerPythonRequestBus::Events::IsInSimulationMode)
->Event("ExitSimulationMode", &EditorLayerPythonRequestBus::Events::ExitSimulationMode)
->Event("RunFile", &EditorLayerPythonRequestBus::Events::RunFile)
->Event("RunFileParameters", &EditorLayerPythonRequestBus::Events::RunFileParameters)
->Event("ExecuteCommand", &EditorLayerPythonRequestBus::Events::ExecuteCommand)
->Event("MessageBoxOkCancel", &EditorLayerPythonRequestBus::Events::MessageBoxOkCancel)
->Event("MessageBoxYesNo", &EditorLayerPythonRequestBus::Events::MessageBoxYesNo)
->Event("MessageBoxOk", &EditorLayerPythonRequestBus::Events::MessageBoxOk)
->Event("EditBox", &EditorLayerPythonRequestBus::Events::EditBox)
->Event("EditBoxCheckDataType", &EditorLayerPythonRequestBus::Events::EditBoxCheckDataType)
->Event("OpenFileBox", &EditorLayerPythonRequestBus::Events::OpenFileBox)
->Event("GetAxisConstraint", &EditorLayerPythonRequestBus::Events::GetAxisConstraint)
->Event("SetAxisConstraint", &EditorLayerPythonRequestBus::Events::SetAxisConstraint)
->Event("GetEditMode", &EditorLayerPythonRequestBus::Events::GetEditMode)
->Event("SetEditMode", &EditorLayerPythonRequestBus::Events::SetEditMode)
->Event("GetPakFromFile", &EditorLayerPythonRequestBus::Events::GetPakFromFile)
->Event("Log", &EditorLayerPythonRequestBus::Events::Log)
->Event("Undo", &EditorLayerPythonRequestBus::Events::Undo)
->Event("Redo", &EditorLayerPythonRequestBus::Events::Redo)
->Event("DrawLabel", &EditorLayerPythonRequestBus::Events::DrawLabel)
->Event("ComboBox", &EditorLayerPythonRequestBus::Events::ComboBox)
;
}
}
void PythonEditorComponent::Activate()
{
EditorLayerPythonRequestBus::Handler::BusConnect(GetEntityId());
}
void PythonEditorComponent::Deactivate()
{
EditorLayerPythonRequestBus::Handler::BusDisconnect();
}
const char* PythonEditorComponent::GetCVar(const char* pName)
{
return PyGetCVarAsString(pName);
}
void PythonEditorComponent::SetCVar(const char* pName, const AZStd::any& value)
{
return PySetCVarFromAny(pName, value);
}
void PythonEditorComponent::SetCVarFromString(const char* pName, const char* pValue)
{
return PySetCVarFromString(pName, pValue);
}
void PythonEditorComponent::SetCVarFromInteger(const char* pName, int pValue)
{
return PySetCVarFromInt(pName, pValue);
}
void PythonEditorComponent::SetCVarFromFloat(const char* pName, float pValue)
{
return PySetCVarFromFloat(pName, pValue);
}
void PythonEditorComponent::PyRunConsole(const char* text)
{
return ::PyRunConsole(text);
}
void PythonEditorComponent::EnterGameMode()
{
return PyEnterGameMode();
}
bool PythonEditorComponent::IsInGameMode()
{
return PyIsInGameMode();
}
void PythonEditorComponent::ExitGameMode()
{
return PyExitGameMode();
}
void PythonEditorComponent::EnterSimulationMode()
{
return PyEnterSimulationMode();
}
bool PythonEditorComponent::IsInSimulationMode()
{
return PyIsInSimulationMode();
}
void PythonEditorComponent::ExitSimulationMode()
{
return PyExitSimulationMode();
}
void PythonEditorComponent::RunFile(const char *pFile)
{
return PyRunFile(pFile);
}
void PythonEditorComponent::RunFileParameters(const char* pFile, const char* pArguments)
{
return PyRunFileWithParameters(pFile, pArguments);
}
void PythonEditorComponent::ExecuteCommand(const char* cmdline)
{
return PyExecuteCommand(cmdline);
}
bool PythonEditorComponent::MessageBoxOkCancel(const char* pMessage)
{
return PyMessageBox(pMessage);
}
bool PythonEditorComponent::MessageBoxYesNo(const char* pMessage)
{
return PyMessageBoxYesNo(pMessage);
}
bool PythonEditorComponent::MessageBoxOk(const char* pMessage)
{
return PyMessageBoxOK(pMessage);
}
AZStd::string PythonEditorComponent::EditBox(AZStd::string_view pTitle)
{
return PyEditBox(pTitle);
}
AZStd::any PythonEditorComponent::EditBoxCheckDataType(const char* pTitle)
{
return PyEditBoxAndCheckProperty(pTitle);
}
AZStd::string PythonEditorComponent::OpenFileBox()
{
return PyOpenFileBox();
}
const char* PythonEditorComponent::GetAxisConstraint()
{
return PyGetAxisConstraint();
}
void PythonEditorComponent::SetAxisConstraint(AZStd::string_view pConstrain)
{
return PySetAxisConstraint(pConstrain);
}
const char* PythonEditorComponent::GetEditMode()
{
return PyGetEditMode();
}
void PythonEditorComponent::SetEditMode(AZStd::string_view pEditMode)
{
return PySetEditMode(pEditMode);
}
const char* PythonEditorComponent::GetPakFromFile(const char* filename)
{
return PyGetPakFromFile(filename);
}
void PythonEditorComponent::Log(const char* pMessage)
{
return PyLog(pMessage);
}
void PythonEditorComponent::Undo()
{
return PyUndo();
}
void PythonEditorComponent::Redo()
{
return PyRedo();
}
void PythonEditorComponent::DrawLabel(int x, int y, float size, float r, float g, float b, float a, const char* pLabel)
{
return PyDrawLabel(x, y, size, r, g, b, a, pLabel);
}
AZStd::string PythonEditorComponent::ComboBox(AZStd::string title, AZStd::vector<AZStd::string> values, int selectedIdx)
{
return PyComboBox(title, values, selectedIdx);
}
}
namespace AzToolsFramework
{
void PythonEditorFuncsHandler::Reflect(AZ::ReflectContext* context)
{
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
// this will put these methods into the 'azlmbr.legacy.general' module
auto addLegacyGeneral = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder)
{
methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Category, "Legacy/General")
->Attribute(AZ::Script::Attributes::Module, "legacy.general");
};
addLegacyGeneral(behaviorContext->Method("get_cvar", PyGetCVarAsString, nullptr, "Gets a CVar value as a string."));
addLegacyGeneral(behaviorContext->Method("set_cvar", PySetCVarFromAny, nullptr, "Sets a CVar value from any simple value."));
addLegacyGeneral(behaviorContext->Method("set_cvar_string", PySetCVarFromString, nullptr, "Sets a CVar value from a string."));
addLegacyGeneral(behaviorContext->Method("set_cvar_integer", PySetCVarFromInt, nullptr, "Sets a CVar value from an integer."));
addLegacyGeneral(behaviorContext->Method("set_cvar_float", PySetCVarFromFloat, nullptr, "Sets a CVar value from a float."));
addLegacyGeneral(behaviorContext->Method("run_console", PyRunConsole, nullptr, "Runs a console command."));
addLegacyGeneral(behaviorContext->Method("enter_game_mode", PyEnterGameMode, nullptr, "Enters the editor game mode."));
addLegacyGeneral(behaviorContext->Method("is_in_game_mode", PyIsInGameMode, nullptr, "Queries if it's in the game mode or not."));
addLegacyGeneral(behaviorContext->Method("exit_game_mode", PyExitGameMode, nullptr, "Exits the editor game mode."));
addLegacyGeneral(behaviorContext->Method("enter_simulation_mode", PyEnterSimulationMode, nullptr, "Enters the editor AI/Physics simulation mode."));
addLegacyGeneral(behaviorContext->Method("is_in_simulation_mode", PyIsInSimulationMode, nullptr, "Queries if the editor is currently in the AI/Physics simulation mode or not."));
addLegacyGeneral(behaviorContext->Method("exit_simulation_mode", PyExitSimulationMode, nullptr, "Exits the editor AI/Physics simulation mode."));
addLegacyGeneral(behaviorContext->Method("run_file", PyRunFile, nullptr, "Runs a script file. A relative path from the editor user folder or an absolute path should be given as an argument."));
addLegacyGeneral(behaviorContext->Method("run_file_parameters", PyRunFileWithParameters, nullptr, "Runs a script file with parameters. A relative path from the editor user folder or an absolute path should be given as an argument. The arguments should be separated by whitespace."));
addLegacyGeneral(behaviorContext->Method("execute_command", PyExecuteCommand, nullptr, "Executes a given string as an editor command."));
addLegacyGeneral(behaviorContext->Method("message_box", PyMessageBox, nullptr, "Shows a confirmation message box with ok|cancel and shows a custom message."));
addLegacyGeneral(behaviorContext->Method("message_box_yes_no", PyMessageBoxYesNo, nullptr, "Shows a confirmation message box with yes|no and shows a custom message."));
addLegacyGeneral(behaviorContext->Method("message_box_ok", PyMessageBoxOK, nullptr, "Shows a confirmation message box with only ok and shows a custom message."));
addLegacyGeneral(behaviorContext->Method("edit_box", PyEditBox, nullptr, "Shows an edit box and returns the value as string."));
addLegacyGeneral(behaviorContext->Method("edit_box_check_data_type", PyEditBoxAndCheckProperty, nullptr, "Shows an edit box and checks the custom value to use the return value with other functions correctly."));
addLegacyGeneral(behaviorContext->Method("open_file_box", PyOpenFileBox, nullptr, "Shows an open file box and returns the selected file path and name."));
addLegacyGeneral(behaviorContext->Method("get_axis_constraint", PyGetAxisConstraint, nullptr, "Gets axis."));
addLegacyGeneral(behaviorContext->Method("set_axis_constraint", PySetAxisConstraint, nullptr, "Sets axis."));
addLegacyGeneral(behaviorContext->Method("get_edit_mode", PyGetEditMode, nullptr, "Gets edit mode."));
addLegacyGeneral(behaviorContext->Method("set_edit_mode", PySetEditMode, nullptr, "Sets edit mode."));
addLegacyGeneral(behaviorContext->Method("get_pak_from_file", PyGetPakFromFile, nullptr, "Finds a pak file name for a given file."));
addLegacyGeneral(behaviorContext->Method("log", PyLog, nullptr, "Prints the message to the editor console window."));
addLegacyGeneral(behaviorContext->Method("undo", PyUndo, nullptr, "Undoes the last operation."));
addLegacyGeneral(behaviorContext->Method("redo", PyRedo, nullptr, "Redoes the last undone operation."));
addLegacyGeneral(behaviorContext->Method("draw_label", PyDrawLabel, nullptr, "Shows a 2d label on the screen at the given position and given color."));
addLegacyGeneral(behaviorContext->Method("combo_box", PyComboBox, nullptr, "Shows a combo box listing each value passed in, returns string value selected by the user."));
addLegacyGeneral(behaviorContext->Method("crash", PyCrash, nullptr, "Crashes the application, useful for testing crash reporting and other automation tools."));
/////////////////////////////////////////////////////////////////////////
// Temporal, to be removed by LY-101149
addLegacyGeneral(behaviorContext->Method("find_editor_entity", PyFindEditorEntity, nullptr, "Retrieves a editor entity id by name"));
addLegacyGeneral(behaviorContext->Method("find_game_entity", PyFindGameEntity, nullptr, "Retrieves a game entity id by name"));
//////////////////////////////////////////////////////////////////////////
addLegacyGeneral(behaviorContext->Method("dump_exposed_classes", PyDumpBindings::GetExposedPythonClasses, nullptr, "Retrieves exposed classes"));
}
}
}