Files
o3de/Code/Sandbox/Editor/PythonEditorFuncs.cpp
T
lumberyard-employee-dm 3dec5d3b71 LYN-2537 engine assets (#254)
* LYN-2537 Moved the Engine and Editor folder to be within the EngineAssets folder

* Fixed Documentation in bootstrap.cfg to correct the path to the user project specific registry file

* Adding a newline to the output of AssetCatalog 'Registering asset..., but type is not set' message

* Updating the AssetProcessorPlatformConfig.setreg Scan Folder to detect
the @ENGINEROOT@/EngineAssets/Engine path for engine runtime assets and
@ENGINEROOT@/EngineAssets/Editor path for engine tool assets

* Updating references to Icons and other assets to account for moving the
Engine and Editor folder under a single EngineAssets folder

* Moving the Engine Settings Registry folder from Engine/Registry -> Registry

* Removed the LY_PROJECT_CMAKE_PATH define as it is not portable to other locations. It is hard coded to the project location that was used for the CMake configuration. Furthermore it paths with backslashes within it are treated as escape characters and not a path separator

* Updated the LyTestTools asset_processor.py script to copy the exclude.filetag from the EngineAssets/Engine directory now

* Fixed Atom Shader Preprocessing when running using an External Project

* Updated the TSGenerateAction.cpp to fix the build error with using a renamed variable

* Updated the Install_Common.cmake ly_setup_others function to install the
EngineAssets directory and the each of the Gem's Assets directory while
maintaining the relative directory structure to the Engine Root
Also updated the install step to install the Registry folder at the
engine root

* Fixed the copying of the Registry folder to be in the install root, instead of under a second 'Registry' folder

* Moving the AssetProcessorPlatformConfig.setreg file over to the Registry folder

* Updated the LyTestTools and C++ code to point that the new location of
the AssetProcessorPlatformConfig.setreg file inside of the Registry
folder

* Renamed Test AssetProcessor*Config.ini files to have the .setreg extension

* Converted the AssetProcessor test setreg files from ini format to json
format using the SerializeContextTools convert-ini command

* Updated the AssetProcessor CMakeLists.txt to copy over the test setreg files to the build folder

* Updated the assetprocessor test file list to point at the renamed AsssetProcessor*Config setreg filenames

* Removed the Output Prefix code from the AssetProcessor. The complexity that it brought to the AP code is not needed, as users can replicate the behavior by just moving there assets underneath a another folder, underneath the scan folder

* Adding back support to read the AssetProcessorPlatformConfig.setreg file from the asset root. This is only needed for C++ UnitTests as they run in an environment where the accessing the Engine Settings Registry is not available

* Updating the Install_common.cmake logic to copy any "Assets" folder to
the install layout.
The Script has also been updated to copy over the "Assets" folder in the
Engine Root to the install layout instead of an "EngineAssets" folder

* Updating References to EngineAssets source asset folder in code to be the Assets source folder

* Moved the Engine Source Asset folder of 'EngineAssets' to a new folder name of 'Assets'. This is inline with the naming scheme we use for Gem asset folders

* Adding the EngineFinder.cmake to the AutomatedTesting project to allow it to work in a project centric manner

* Updating the LyTestTools copy_assets_to_project function to be able to copy assets with folders to the temporary project root
Fixed an issue in LyTestTools where the temporary log directory could have shutil.rmtree being called twice on it leading to an exception which fails an automated test

Updated the asset_procesor_gui_tests_2 AddScanFolder test to not use the
output prefix, but instead place the source asset root into a
subdirectory

* Correct the AssetProcessorPlatformConfig Scan Folders for the EngineAssets directory to point at the Assets directory

* Updated the asset procesor batch dependency test scan folder to point at the 'Assets' folder instead of 'EngineAssets'
2021-04-28 21:38:43 -05:00

1202 lines
45 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("Assets/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.");
}
}
//////////////////////////////////////////////////////////////////////////
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("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::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_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"));
}
}
}