PythonEditorFuncs.cpp 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "EditorDefs.h"
  9. #include "PythonEditorFuncs.h"
  10. // Qt
  11. #include <QInputDialog>
  12. #include <QMessageBox>
  13. #include <QFileDialog>
  14. #include <AzCore/Utils/Utils.h>
  15. // AzToolsFramework
  16. #include <AzCore/RTTI/BehaviorContext.h>
  17. #include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
  18. #include <AzToolsFramework/Entity/EditorEntityContextBus.h>
  19. #include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
  20. // Editor
  21. #include "CryEdit.h"
  22. #include "GameEngine.h"
  23. #include "ViewManager.h"
  24. #include "GenericSelectItemDialog.h"
  25. #include "Objects/BaseObject.h"
  26. #include "Commands/CommandManager.h"
  27. AZ_CVAR_EXTERNED(bool, ed_previewGameInFullscreen_once);
  28. namespace
  29. {
  30. //////////////////////////////////////////////////////////////////////////
  31. const char* PyGetCVarAsString(const char* pName)
  32. {
  33. ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(pName);
  34. if (!pCVar)
  35. {
  36. AZ_Warning("editor", false, "PyGetCVar: Attempt to access non-existent CVar '%s'", pName ? pName : "(null)");
  37. return "(missing)";
  38. }
  39. return pCVar->GetString();
  40. }
  41. //////////////////////////////////////////////////////////////////////////
  42. // forward declarations
  43. void PySetCVarFromInt(const char* pName, int pValue);
  44. void PySetCVarFromFloat(const char* pName, float pValue);
  45. //////////////////////////////////////////////////////////////////////////
  46. void PySetCVarFromString(const char* pName, const char* pValue)
  47. {
  48. ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(pName);
  49. if (!pCVar)
  50. {
  51. AZ_Warning("editor", false, "Attempt to set non-existent string CVar '%s'", pName ? pName : "(null)");
  52. }
  53. else if (pCVar->GetType() == CVAR_INT)
  54. {
  55. PySetCVarFromInt(pName, static_cast<int>(std::stol(pValue)));
  56. }
  57. else if (pCVar->GetType() == CVAR_FLOAT)
  58. {
  59. PySetCVarFromFloat(pName, static_cast<float>(std::stod(pValue)));
  60. }
  61. else if (pCVar->GetType() != CVAR_STRING)
  62. {
  63. AZ_Warning("editor", false, "Type mismatch while assigning CVar '%s' as a string.", pName ? pName : "(null)");
  64. }
  65. else
  66. {
  67. pCVar->Set(pValue);
  68. }
  69. }
  70. //////////////////////////////////////////////////////////////////////////
  71. void PySetCVarFromInt(const char* pName, int pValue)
  72. {
  73. ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(pName);
  74. if (!pCVar)
  75. {
  76. AZ_Warning("editor", false, "Attempt to set non-existent integer CVar '%s'", pName ? pName : "(null)");
  77. }
  78. else if (pCVar->GetType() == CVAR_FLOAT)
  79. {
  80. PySetCVarFromFloat(pName, float(pValue));
  81. }
  82. else if (pCVar->GetType() == CVAR_STRING)
  83. {
  84. auto stringValue = AZStd::to_string(pValue);
  85. PySetCVarFromString(pName, stringValue.c_str());
  86. }
  87. else if (pCVar->GetType() != CVAR_INT)
  88. {
  89. AZ_Warning("editor", false, "Type mismatch while assigning CVar '%s' as an integer.", pName ? pName : "(null)");
  90. }
  91. else
  92. {
  93. pCVar->Set(pValue);
  94. }
  95. }
  96. //////////////////////////////////////////////////////////////////////////
  97. void PySetCVarFromFloat(const char* pName, float pValue)
  98. {
  99. ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(pName);
  100. if (!pCVar)
  101. {
  102. AZ_Warning("editor", false, "Attempt to set non-existent float CVar '%s'", pName ? pName : "(null)");
  103. }
  104. else if (pCVar->GetType() == CVAR_INT)
  105. {
  106. PySetCVarFromInt(pName, static_cast<int>(pValue));
  107. }
  108. else if (pCVar->GetType() == CVAR_STRING)
  109. {
  110. auto stringValue = AZStd::to_string(pValue);
  111. PySetCVarFromString(pName, stringValue.c_str());
  112. }
  113. else if (pCVar->GetType() != CVAR_FLOAT)
  114. {
  115. AZ_Warning("editor", false, "Type mismatch while assigning CVar '%s' as a float.", pName ? pName : "(null)");
  116. }
  117. else
  118. {
  119. pCVar->Set(pValue);
  120. }
  121. }
  122. //////////////////////////////////////////////////////////////////////////
  123. void PySetCVarFromAny(const char* pName, const AZStd::any& value)
  124. {
  125. ICVar* pCVar = GetIEditor()->GetSystem()->GetIConsole()->GetCVar(pName);
  126. if (!pCVar)
  127. {
  128. AZ_Warning("editor", false, "Attempt to set non-existent float CVar '%s'", pName ? pName : "(null)");
  129. }
  130. else if (pCVar->GetType() == CVAR_INT)
  131. {
  132. PySetCVarFromInt(pName, static_cast<int>(AZStd::any_cast<AZ::s64>(value)));
  133. }
  134. else if (pCVar->GetType() == CVAR_FLOAT)
  135. {
  136. PySetCVarFromFloat(pName, static_cast<float>(AZStd::any_cast<double>(value)));
  137. }
  138. else if (pCVar->GetType() == CVAR_STRING)
  139. {
  140. PySetCVarFromString(pName, AZStd::any_cast<AZStd::string_view>(value).data());
  141. }
  142. else
  143. {
  144. AZ_Warning("editor", false, "Type mismatch while assigning CVar '%s' as a float.", pName ? pName : "(null)");
  145. }
  146. }
  147. //////////////////////////////////////////////////////////////////////////
  148. void PyEnterGameMode()
  149. {
  150. if (GetIEditor()->GetGameEngine())
  151. {
  152. GetIEditor()->GetGameEngine()->RequestSetGameMode(true);
  153. }
  154. }
  155. void PyEnterGameModeFullscreen()
  156. {
  157. ed_previewGameInFullscreen_once = true;
  158. PyEnterGameMode();
  159. }
  160. void PyExitGameMode()
  161. {
  162. if (GetIEditor()->GetGameEngine())
  163. {
  164. GetIEditor()->GetGameEngine()->RequestSetGameMode(false);
  165. }
  166. }
  167. bool PyIsInGameMode()
  168. {
  169. return GetIEditor()->IsInGameMode();
  170. }
  171. //////////////////////////////////////////////////////////////////////////
  172. void PyEnterSimulationMode()
  173. {
  174. if (!GetIEditor()->IsInSimulationMode())
  175. {
  176. CCryEditApp::instance()->OnSwitchPhysics();
  177. }
  178. }
  179. void PyExitSimulationMode()
  180. {
  181. if (GetIEditor()->IsInSimulationMode())
  182. {
  183. CCryEditApp::instance()->OnSwitchPhysics();
  184. }
  185. }
  186. bool PyIsInSimulationMode()
  187. {
  188. return GetIEditor()->IsInSimulationMode();
  189. }
  190. //////////////////////////////////////////////////////////////////////////
  191. void PyRunConsole(const char* text)
  192. {
  193. GetIEditor()->GetSystem()->GetIConsole()->ExecuteString(text);
  194. }
  195. //////////////////////////////////////////////////////////////////////////
  196. bool GetPythonScriptPath(const char* pFile, QString& path)
  197. {
  198. bool bRelativePath = true;
  199. char drive[_MAX_DRIVE];
  200. char fdir[_MAX_DIR];
  201. char fname[_MAX_FNAME];
  202. char fext[_MAX_EXT];
  203. drive[0] = '\0';
  204. _splitpath_s(pFile, drive, fdir, fname, fext);
  205. if (strlen(drive) != 0)
  206. {
  207. bRelativePath = false;
  208. }
  209. if (bRelativePath)
  210. {
  211. // Try to open from user folder
  212. QString userSandboxFolder = Path::GetResolvedUserSandboxFolder();
  213. Path::ConvertBackSlashToSlash(userSandboxFolder);
  214. path = userSandboxFolder + pFile;
  215. // If not found try editor folder
  216. if (!CFileUtil::FileExists(path))
  217. {
  218. AZ::IO::FixedMaxPathString engineRoot = AZ::Utils::GetEnginePath();
  219. QDir engineDir = !engineRoot.empty() ? QDir(QString(engineRoot.c_str())) : QDir::current();
  220. QString scriptFolder = engineDir.absoluteFilePath("Assets/Editor/Scripts/");
  221. Path::ConvertBackSlashToSlash(scriptFolder);
  222. path = scriptFolder + pFile;
  223. if (!CFileUtil::FileExists(path))
  224. {
  225. QString error = QString("Could not find '%1'\n in '%2'\n or '%3'\n").arg(pFile).arg(userSandboxFolder).arg(scriptFolder);
  226. AZ_Warning("python", false, error.toUtf8().data());
  227. return false;
  228. }
  229. }
  230. }
  231. else
  232. {
  233. path = pFile;
  234. if (!CFileUtil::FileExists(path))
  235. {
  236. QString error = QString("Could not find '") + pFile + "'\n";
  237. AZ_Warning("python", false, error.toUtf8().data());
  238. return false;
  239. }
  240. }
  241. Path::ConvertBackSlashToSlash(path);
  242. return true;
  243. }
  244. //////////////////////////////////////////////////////////////////////////
  245. void GetPythonArgumentsVector(const char* pArguments, QStringList& inputArguments)
  246. {
  247. if (pArguments == nullptr)
  248. {
  249. return;
  250. }
  251. inputArguments = QString(pArguments).split(" ", Qt::SkipEmptyParts);
  252. }
  253. //////////////////////////////////////////////////////////////////////////
  254. void PyRunFileWithParameters(const char* pFile, const char* pArguments)
  255. {
  256. QString path;
  257. QStringList inputArguments;
  258. GetPythonArgumentsVector(pArguments, inputArguments);
  259. if (GetPythonScriptPath(pFile, path))
  260. {
  261. AZStd::vector<AZStd::string_view> argList;
  262. argList.reserve(inputArguments.size() + 1);
  263. QByteArray p = path.toUtf8();
  264. QVector<QByteArray> argData;
  265. argData.reserve(inputArguments.count());
  266. for (auto iter = inputArguments.begin(); iter != inputArguments.end(); ++iter)
  267. {
  268. argData.push_back(iter->toLatin1());
  269. argList.push_back(argData.last().data());
  270. }
  271. using namespace AzToolsFramework;
  272. EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, p.data(), argList);
  273. }
  274. }
  275. void PyRunFile(const char* pFile)
  276. {
  277. PyRunFileWithParameters(pFile, nullptr);
  278. }
  279. //////////////////////////////////////////////////////////////////////////
  280. void PyExecuteCommand(const char* cmdline)
  281. {
  282. GetIEditor()->GetCommandManager()->Execute(cmdline);
  283. }
  284. //////////////////////////////////////////////////////////////////////////
  285. void PyLog(const char* pMessage)
  286. {
  287. if (strcmp(pMessage, "") != 0)
  288. {
  289. CryLogAlways("%s", pMessage);
  290. }
  291. }
  292. //////////////////////////////////////////////////////////////////////////
  293. bool PyMessageBox(const char* pMessage)
  294. {
  295. return QMessageBox::information(QApplication::activeWindow(), QString(), pMessage, QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok;
  296. }
  297. bool PyMessageBoxYesNo(const char* pMessage)
  298. {
  299. return QMessageBox::question(QApplication::activeWindow(), QString(), pMessage) == QMessageBox::Yes;
  300. }
  301. bool PyMessageBoxOK(const char* pMessage)
  302. {
  303. return QMessageBox::information(QApplication::activeWindow(), QString(), pMessage) == QMessageBox::Ok;
  304. }
  305. AZStd::string PyEditBox(AZStd::string_view pTitle)
  306. {
  307. QString stringValue = QInputDialog::getText(AzToolsFramework::GetActiveWindow(), pTitle.data(), QString());
  308. if (!stringValue.isEmpty())
  309. {
  310. return stringValue.toUtf8().constData();
  311. }
  312. return "";
  313. }
  314. AZStd::any PyEditBoxAndCheckProperty(const char* pTitle)
  315. {
  316. QString stringValue = QInputDialog::getText(AzToolsFramework::GetActiveWindow(), pTitle, QString());
  317. if (!stringValue.isEmpty())
  318. {
  319. // detect data type
  320. QString tempString = stringValue;
  321. int countComa = 0;
  322. int countOpenRoundBraket = 0;
  323. int countCloseRoundBraket = 0;
  324. int countDots = 0;
  325. int posDots = 0;
  326. int posComa = 0;
  327. int posOpenRoundBraket = 0;
  328. int posCloseRoundBraket = 0;
  329. for (int i = 0; i < 3; i++)
  330. {
  331. if (tempString.indexOf(".", posDots) > -1)
  332. {
  333. posDots = tempString.indexOf(".", posDots) + 1;
  334. countDots++;
  335. }
  336. if (tempString.indexOf(",", posComa) > -1)
  337. {
  338. posComa = tempString.indexOf(",", posComa) + 1;
  339. countComa++;
  340. }
  341. if (tempString.indexOf("(", posOpenRoundBraket) > -1)
  342. {
  343. posOpenRoundBraket = tempString.indexOf("(", posOpenRoundBraket) + 1;
  344. countOpenRoundBraket++;
  345. }
  346. if (tempString.indexOf(")", posCloseRoundBraket) > -1)
  347. {
  348. posCloseRoundBraket = tempString.indexOf(")", posCloseRoundBraket) + 1;
  349. countCloseRoundBraket++;
  350. }
  351. }
  352. if (countDots == 3 && countComa == 2 && countOpenRoundBraket == 1 && countCloseRoundBraket == 1)
  353. {
  354. // for example: (1.95, 2.75, 3.36)
  355. QString valueRed = stringValue;
  356. int iStart = valueRed.indexOf("(");
  357. valueRed.remove(0, iStart + 1);
  358. int iEnd = valueRed.indexOf(",");
  359. valueRed.remove(iEnd, valueRed.length());
  360. float fValueRed = valueRed.toFloat();
  361. QString valueGreen = stringValue;
  362. iStart = valueGreen.indexOf(",");
  363. valueGreen.remove(0, iStart + 1);
  364. iEnd = valueGreen.indexOf(",");
  365. valueGreen.remove(iEnd, valueGreen.length());
  366. float fValueGreen = valueGreen.toFloat();
  367. QString valueBlue = stringValue;
  368. valueBlue.remove(0, valueBlue.indexOf(",") + 1);
  369. valueBlue.remove(0, valueBlue.indexOf(",") + 1);
  370. valueBlue.remove(valueBlue.indexOf(")"), valueBlue.length());
  371. float fValueBlue = valueBlue.toFloat();
  372. return AZStd::make_any<AZ::Vector3>(fValueRed, fValueGreen, fValueBlue);
  373. }
  374. else if (countDots == 0 && countComa == 2 && countOpenRoundBraket == 1 && countCloseRoundBraket == 1)
  375. {
  376. // for example: (128, 32, 240)
  377. const AZ::u8 lowColorValue { 0 };
  378. const AZ::u8 highColorValue { 255 };
  379. QString valueRed = stringValue;
  380. int iStart = valueRed.indexOf("(");
  381. valueRed.remove(0, iStart + 1);
  382. int iEnd = valueRed.indexOf(",");
  383. valueRed.remove(iEnd, valueRed.length());
  384. AZ::u8 iValueRed = AZStd::clamp(aznumeric_cast<AZ::u8>(valueRed.toInt()), lowColorValue, highColorValue);
  385. QString valueGreen = stringValue;
  386. iStart = valueGreen.indexOf(",");
  387. valueGreen.remove(0, iStart + 1);
  388. iEnd = valueGreen.indexOf(",");
  389. valueGreen.remove(iEnd, valueGreen.length());
  390. AZ::u8 iValueGreen = AZStd::clamp(aznumeric_cast<AZ::u8>(valueGreen.toInt()), lowColorValue, highColorValue);
  391. QString valueBlue = stringValue;
  392. valueBlue.remove(0, valueBlue.indexOf(",") + 1);
  393. valueBlue.remove(0, valueBlue.indexOf(",") + 1);
  394. valueBlue.remove(valueBlue.indexOf(")"), valueBlue.length());
  395. AZ::u8 iValueBlue = AZStd::clamp(aznumeric_cast<AZ::u8>(valueBlue.toInt()), lowColorValue, highColorValue);
  396. return AZStd::make_any<AZ::Color>(iValueRed, iValueGreen, iValueBlue, highColorValue);
  397. }
  398. else if (countDots == 1 && countComa == 0 && countOpenRoundBraket == 0 && countCloseRoundBraket == 0)
  399. {
  400. // for example: 2.56
  401. return AZStd::make_any<double>(stringValue.toDouble());
  402. }
  403. else if (countDots == 0 && countComa == 0 && countOpenRoundBraket == 0 && countCloseRoundBraket == 0)
  404. {
  405. if (stringValue == "False" || stringValue == "True")
  406. {
  407. // for example: True
  408. return AZStd::make_any<bool>(stringValue == "True");
  409. }
  410. else
  411. {
  412. const bool anyNotDigits = AZStd::any_of(stringValue.begin(), stringValue.end(), [](const QChar& ch) { return !ch.isDigit(); });
  413. // looks like a string value?
  414. if (stringValue.isEmpty() || anyNotDigits)
  415. {
  416. // for example: Hello
  417. return AZStd::make_any<AZStd::string>(stringValue.toUtf8().data());
  418. }
  419. else // then it looks like an integer
  420. {
  421. // for example: 456
  422. return AZStd::make_any<AZ::s64>(stringValue.toInt());
  423. }
  424. }
  425. }
  426. }
  427. QMessageBox::critical(AzToolsFramework::GetActiveWindow(), QObject::tr("Invalid Data"), QObject::tr("Invalid data type."));
  428. return {};
  429. }
  430. AZStd::string PyOpenFileBox()
  431. {
  432. QString path = QFileDialog::getOpenFileName();
  433. if (!path.isEmpty())
  434. {
  435. Path::ConvertBackSlashToSlash(path);
  436. }
  437. return path.toUtf8().constData();
  438. }
  439. AZStd::string PyComboBox(AZStd::string title, AZStd::vector<AZStd::string> values, int selectedIdx = 0)
  440. {
  441. AZStd::string result;
  442. if (title.empty())
  443. {
  444. throw std::runtime_error("Incorrect title argument passed in. ");
  445. }
  446. if (values.size() == 0)
  447. {
  448. throw std::runtime_error("Empty value list passed in. ");
  449. }
  450. QStringList list;
  451. for (const AZStd::string& str : values)
  452. {
  453. list.push_back(str.c_str());
  454. }
  455. CGenericSelectItemDialog pyDlg;
  456. pyDlg.setWindowTitle(title.c_str());
  457. pyDlg.SetMode(CGenericSelectItemDialog::eMODE_LIST);
  458. pyDlg.SetItems(list);
  459. pyDlg.PreSelectItem(list[selectedIdx]);
  460. if (pyDlg.exec() == QDialog::Accepted)
  461. {
  462. result = pyDlg.GetSelectedItem().toUtf8().constData();
  463. }
  464. return result;
  465. }
  466. void PyCrash()
  467. {
  468. AZ_Crash();
  469. }
  470. static void PyDrawLabel(int x, int y, float size, float r, float g, float b, float a, const char* pLabel)
  471. {
  472. if (!pLabel)
  473. {
  474. throw std::logic_error("No label given.");
  475. return;
  476. }
  477. if (!r || !g || !b || !a)
  478. {
  479. throw std::logic_error("Invalid color parameters given.");
  480. return;
  481. }
  482. if (!x || !y || !size)
  483. {
  484. throw std::logic_error("Invalid position or size parameters given.");
  485. }
  486. else
  487. {
  488. // ToDo: Remove function or update to work with Atom? LYN-3672
  489. // float color[] = {r, g, b, a};
  490. // ???->Draw2dLabel(x, y, size, color, false, pLabel);
  491. }
  492. }
  493. //////////////////////////////////////////////////////////////////////////
  494. // Constrain
  495. //////////////////////////////////////////////////////////////////////////
  496. const char* PyGetAxisConstraint()
  497. {
  498. AxisConstrains actualConstrain = GetIEditor()->GetAxisConstrains();
  499. switch (actualConstrain)
  500. {
  501. case AXIS_X:
  502. return "X";
  503. case AXIS_Y:
  504. return "Y";
  505. case AXIS_Z:
  506. return "Z";
  507. case AXIS_XY:
  508. return "XY";
  509. case AXIS_XZ:
  510. return "XZ";
  511. case AXIS_YZ:
  512. return "YZ";
  513. case AXIS_XYZ:
  514. return "XYZ";
  515. case AXIS_TERRAIN:
  516. return (GetIEditor()->IsTerrainAxisIgnoreObjects()) ? "TERRAIN" : "TERRAINSNAP";
  517. default:
  518. throw std::logic_error("Invalid axes.");
  519. }
  520. }
  521. void PySetAxisConstraint(AZStd::string_view pConstrain)
  522. {
  523. if (pConstrain == "X")
  524. {
  525. GetIEditor()->SetAxisConstraints(AXIS_X);
  526. }
  527. else if (pConstrain == "Y")
  528. {
  529. GetIEditor()->SetAxisConstraints(AXIS_Y);
  530. }
  531. else if (pConstrain == "Z")
  532. {
  533. GetIEditor()->SetAxisConstraints(AXIS_Z);
  534. }
  535. else if (pConstrain == "XY")
  536. {
  537. GetIEditor()->SetAxisConstraints(AXIS_XY);
  538. }
  539. else if (pConstrain == "YZ")
  540. {
  541. GetIEditor()->SetAxisConstraints(AXIS_YZ);
  542. }
  543. else if (pConstrain == "XZ")
  544. {
  545. GetIEditor()->SetAxisConstraints(AXIS_XZ);
  546. }
  547. else if (pConstrain == "XYZ")
  548. {
  549. GetIEditor()->SetAxisConstraints(AXIS_XYZ);
  550. }
  551. else if (pConstrain == "TERRAIN")
  552. {
  553. GetIEditor()->SetAxisConstraints(AXIS_TERRAIN);
  554. GetIEditor()->SetTerrainAxisIgnoreObjects(true);
  555. }
  556. else if (pConstrain == "TERRAINSNAP")
  557. {
  558. GetIEditor()->SetAxisConstraints(AXIS_TERRAIN);
  559. GetIEditor()->SetTerrainAxisIgnoreObjects(false);
  560. }
  561. else
  562. {
  563. throw std::logic_error("Invalid axes.");
  564. }
  565. }
  566. //////////////////////////////////////////////////////////////////////////
  567. AZ::IO::Path PyGetPakFromFile(const char* filename)
  568. {
  569. auto pIPak = GetIEditor()->GetSystem()->GetIPak();
  570. AZ::IO::HandleType fileHandle = pIPak->FOpen(filename, "rb");
  571. if (fileHandle == AZ::IO::InvalidHandle)
  572. {
  573. throw std::logic_error("Invalid file name.");
  574. }
  575. AZ::IO::Path pArchPath = pIPak->GetFileArchivePath(fileHandle);
  576. pIPak->FClose(fileHandle);
  577. return pArchPath;
  578. }
  579. //////////////////////////////////////////////////////////////////////////
  580. void PyUndo()
  581. {
  582. GetIEditor()->Undo();
  583. }
  584. //////////////////////////////////////////////////////////////////////////
  585. void PyRedo()
  586. {
  587. GetIEditor()->Redo();
  588. }
  589. }
  590. //////////////////////////////////////////////////////////////////////////
  591. // Temporal, to be removed by LY-101149
  592. AZ::EntityId PyFindEditorEntity(const char* name)
  593. {
  594. AZ::EntityId foundEntityId;
  595. auto searchFunc = [name, &foundEntityId](AZ::Entity* e)
  596. {
  597. if (!foundEntityId.IsValid() && e->GetName() == name)
  598. {
  599. bool isEditorEntity = false;
  600. AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(isEditorEntity, &AzToolsFramework::EditorEntityContextRequests::IsEditorEntity, e->GetId());
  601. if (isEditorEntity)
  602. {
  603. foundEntityId = e->GetId();
  604. }
  605. }
  606. };
  607. AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::EnumerateEntities, searchFunc);
  608. return foundEntityId;
  609. }
  610. AZ::EntityId PyFindGameEntity(const char* name)
  611. {
  612. AZ::EntityId foundEntityId;
  613. auto searchFunc = [name, &foundEntityId](AZ::Entity* e)
  614. {
  615. if (!foundEntityId.IsValid() && e->GetName() == name)
  616. {
  617. bool isEditorEntity = true;
  618. AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(isEditorEntity, &AzToolsFramework::EditorEntityContextRequests::IsEditorEntity, e->GetId());
  619. if (!isEditorEntity)
  620. {
  621. foundEntityId = e->GetId();
  622. }
  623. }
  624. };
  625. AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::EnumerateEntities, searchFunc);
  626. return foundEntityId;
  627. }
  628. struct PyDumpBindings
  629. {
  630. static AZ_INLINE bool IsBehaviorFlaggedForEditor(const AZ::AttributeArray& attributes)
  631. {
  632. // defaults to Launcher
  633. AZ::Script::Attributes::ScopeFlags scopeType = AZ::Script::Attributes::ScopeFlags::Launcher;
  634. AZ::Attribute* scopeAttribute = AZ::FindAttribute(AZ::Script::Attributes::Scope, attributes);
  635. if (scopeAttribute)
  636. {
  637. AZ::AttributeReader scopeAttributeReader(nullptr, scopeAttribute);
  638. scopeAttributeReader.Read<AZ::Script::Attributes::ScopeFlags>(scopeType);
  639. }
  640. return (scopeType == AZ::Script::Attributes::ScopeFlags::Automation || scopeType == AZ::Script::Attributes::ScopeFlags::Common);
  641. }
  642. static AZ_INLINE AZStd::string GetModuleName(const AZ::AttributeArray& attributes)
  643. {
  644. AZStd::string moduleName;
  645. AZ::Attribute* moduleAttribute = AZ::FindAttribute(AZ::Script::Attributes::Module, attributes);
  646. if (moduleAttribute)
  647. {
  648. AZ::AttributeReader scopeAttributeReader(nullptr, moduleAttribute);
  649. scopeAttributeReader.Read<AZStd::string>(moduleName);
  650. }
  651. if (!moduleName.empty())
  652. {
  653. moduleName = "azlmbr." + moduleName;
  654. }
  655. else
  656. {
  657. moduleName = "azlmbr";
  658. }
  659. return moduleName;
  660. }
  661. static AZStd::string ParameterToString(const AZ::BehaviorMethod* method, size_t index)
  662. {
  663. const AZStd::string* argNameStr = method->GetArgumentName(index);
  664. const char* argName = (argNameStr && !argNameStr->empty()) ? argNameStr->c_str() : nullptr;
  665. if (argName)
  666. {
  667. return AZStd::string::format("%s %s", method->GetArgument(index)->m_name, argName);
  668. }
  669. else
  670. {
  671. return method->GetArgument(index)->m_name;
  672. }
  673. }
  674. static AZStd::string MethodArgumentsToString(const AZ::BehaviorMethod* method)
  675. {
  676. AZStd::string ret;
  677. AZStd::string argumentStr;
  678. for (size_t i = 0; i < method->GetNumArguments(); ++i)
  679. {
  680. argumentStr = ParameterToString(method, i);
  681. ret += argumentStr;
  682. if (i < method->GetNumArguments() - 1)
  683. {
  684. ret += ", ";
  685. }
  686. }
  687. return ret;
  688. }
  689. static AZStd::string MethodToString(const AZStd::string& methodName, const AZ::BehaviorMethod* method)
  690. {
  691. AZStd::string methodNameStrip = methodName.data() + methodName.rfind(':') + 1; // remove ClassName:: part as it is redundant
  692. return AZStd::string::format("%s %s(%s)%s", method->GetResult()->m_name, methodNameStrip.c_str(), MethodArgumentsToString(method).c_str(), method->m_isConst ? " const" : "");
  693. }
  694. static AZStd::string GetExposedPythonClasses()
  695. {
  696. AZ::BehaviorContext* behaviorContext(nullptr);
  697. AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationRequests::GetBehaviorContext);
  698. AZStd::string output = "";
  699. output += "// Classes\n\n";
  700. for (auto elem : behaviorContext->m_classes)
  701. {
  702. AZ::BehaviorClass* cls = elem.second;
  703. bool exposedToPython = IsBehaviorFlaggedForEditor(cls->m_attributes);
  704. if (!exposedToPython)
  705. continue;
  706. output += AZStd::string::format("// Module: %s\n", GetModuleName(cls->m_attributes).c_str());
  707. output += AZStd::string::format("class %s\n", cls->m_name.c_str());
  708. output += "{\n";
  709. if (cls->m_methods.size() > 0)
  710. {
  711. output += " // Methods\n";
  712. for (auto method_elem : cls->m_methods)
  713. {
  714. output += AZStd::string::format(" %s;\n", MethodToString(method_elem.first, method_elem.second).c_str());
  715. }
  716. }
  717. if (cls->m_properties.size() > 0)
  718. {
  719. output += " // Properties\n";
  720. for (auto property_elem : cls->m_properties)
  721. {
  722. AZ::BehaviorProperty* bproperty = property_elem.second;
  723. output += AZStd::string::format(" %s %s;\n", bproperty->m_getter->GetResult()->m_name, bproperty->m_name.c_str());
  724. }
  725. }
  726. output += "}\n";
  727. }
  728. output += "\n\n// Ebuses\n\n";
  729. for (auto elem : behaviorContext->m_ebuses)
  730. {
  731. AZ::BehaviorEBus* ebus = elem.second;
  732. bool exposedToPython = IsBehaviorFlaggedForEditor(ebus->m_attributes);
  733. if (!exposedToPython)
  734. continue;
  735. output += AZStd::string::format("// Module: %s\n", GetModuleName(ebus->m_attributes).c_str());
  736. output += AZStd::string::format("ebus %s\n", ebus->m_name.c_str());
  737. output += "{\n";
  738. for (auto event_elem : ebus->m_events)
  739. {
  740. auto method = event_elem.second.m_event ? event_elem.second.m_event : event_elem.second.m_broadcast;
  741. if (method)
  742. {
  743. const char* comment = event_elem.second.m_event ? "/* event */" : "/* broadcast */";
  744. output += AZStd::string::format(" %s %s\n", comment, MethodToString(event_elem.first, method).c_str());
  745. }
  746. else
  747. {
  748. output += AZStd::string::format(" %s %s\n", "/* unknown */", event_elem.first.c_str());
  749. }
  750. }
  751. if (ebus->m_createHandler)
  752. {
  753. AZ::BehaviorEBusHandler* handler = nullptr;
  754. ebus->m_createHandler->InvokeResult(handler);
  755. if (handler)
  756. {
  757. const auto& notifications = handler->GetEvents();
  758. for (const auto& notification : notifications)
  759. {
  760. AZStd::string argsStr;
  761. const size_t paramCount = notification.m_parameters.size();
  762. for (size_t i = 0; i < notification.m_parameters.size(); ++i)
  763. {
  764. AZStd::string argName = notification.m_parameters[i].m_name;
  765. argsStr += argName;
  766. if (i != paramCount - 1)
  767. {
  768. argsStr += ", ";
  769. }
  770. }
  771. AZStd::string funcName = notification.m_name;
  772. output += AZStd::string::format(" /* notification */ %s(%s);\n", funcName.c_str(), argsStr.c_str());
  773. }
  774. ebus->m_destroyHandler->Invoke(handler);
  775. }
  776. }
  777. output += "}\n";
  778. }
  779. AzFramework::StringFunc::Replace(output, "AZStd::basic_string<char, AZStd::char_traits<char>, allocator>", "AZStd::string");
  780. return output;
  781. }
  782. };
  783. //////////////////////////////////////////////////////////////////////////
  784. namespace AzToolsFramework
  785. {
  786. void PythonEditorComponent::Reflect(AZ::ReflectContext* context)
  787. {
  788. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  789. {
  790. behaviorContext->EBus<EditorLayerPythonRequestBus>("PythonEditorBus")
  791. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  792. ->Attribute(AZ::Script::Attributes::Module, "python_editor_funcs")
  793. ->Event("GetCVar", &EditorLayerPythonRequestBus::Events::GetCVar)
  794. ->Event("SetCVar", &EditorLayerPythonRequestBus::Events::SetCVar)
  795. ->Event("SetCVarFromString", &EditorLayerPythonRequestBus::Events::SetCVarFromString)
  796. ->Event("SetCVarFromInteger", &EditorLayerPythonRequestBus::Events::SetCVarFromInteger)
  797. ->Event("SetCVarFromFloat", &EditorLayerPythonRequestBus::Events::SetCVarFromFloat)
  798. ->Event("RunConsole", &EditorLayerPythonRequestBus::Events::PyRunConsole)
  799. ->Event("EnterGameMode", &EditorLayerPythonRequestBus::Events::EnterGameMode)
  800. ->Event("IsInGameMode", &EditorLayerPythonRequestBus::Events::IsInGameMode)
  801. ->Event("ExitGameMode", &EditorLayerPythonRequestBus::Events::ExitGameMode)
  802. ->Event("EnterSimulationMode", &EditorLayerPythonRequestBus::Events::EnterSimulationMode)
  803. ->Event("IsInSimulationMode", &EditorLayerPythonRequestBus::Events::IsInSimulationMode)
  804. ->Event("ExitSimulationMode", &EditorLayerPythonRequestBus::Events::ExitSimulationMode)
  805. ->Event("RunFile", &EditorLayerPythonRequestBus::Events::RunFile)
  806. ->Event("RunFileParameters", &EditorLayerPythonRequestBus::Events::RunFileParameters)
  807. ->Event("ExecuteCommand", &EditorLayerPythonRequestBus::Events::ExecuteCommand)
  808. ->Event("MessageBoxOkCancel", &EditorLayerPythonRequestBus::Events::MessageBoxOkCancel)
  809. ->Event("MessageBoxYesNo", &EditorLayerPythonRequestBus::Events::MessageBoxYesNo)
  810. ->Event("MessageBoxOk", &EditorLayerPythonRequestBus::Events::MessageBoxOk)
  811. ->Event("EditBox", &EditorLayerPythonRequestBus::Events::EditBox)
  812. ->Event("EditBoxCheckDataType", &EditorLayerPythonRequestBus::Events::EditBoxCheckDataType)
  813. ->Event("OpenFileBox", &EditorLayerPythonRequestBus::Events::OpenFileBox)
  814. ->Event("GetAxisConstraint", &EditorLayerPythonRequestBus::Events::GetAxisConstraint)
  815. ->Event("SetAxisConstraint", &EditorLayerPythonRequestBus::Events::SetAxisConstraint)
  816. ->Event("GetPakFromFile", &EditorLayerPythonRequestBus::Events::GetPakFromFile)
  817. ->Event("Log", &EditorLayerPythonRequestBus::Events::Log)
  818. ->Event("Undo", &EditorLayerPythonRequestBus::Events::Undo)
  819. ->Event("Redo", &EditorLayerPythonRequestBus::Events::Redo)
  820. ->Event("DrawLabel", &EditorLayerPythonRequestBus::Events::DrawLabel)
  821. ->Event("ComboBox", &EditorLayerPythonRequestBus::Events::ComboBox)
  822. ;
  823. }
  824. }
  825. void PythonEditorComponent::Activate()
  826. {
  827. EditorLayerPythonRequestBus::Handler::BusConnect(GetEntityId());
  828. }
  829. void PythonEditorComponent::Deactivate()
  830. {
  831. EditorLayerPythonRequestBus::Handler::BusDisconnect();
  832. }
  833. const char* PythonEditorComponent::GetCVar(const char* pName)
  834. {
  835. return PyGetCVarAsString(pName);
  836. }
  837. void PythonEditorComponent::SetCVar(const char* pName, const AZStd::any& value)
  838. {
  839. return PySetCVarFromAny(pName, value);
  840. }
  841. void PythonEditorComponent::SetCVarFromString(const char* pName, const char* pValue)
  842. {
  843. return PySetCVarFromString(pName, pValue);
  844. }
  845. void PythonEditorComponent::SetCVarFromInteger(const char* pName, int pValue)
  846. {
  847. return PySetCVarFromInt(pName, pValue);
  848. }
  849. void PythonEditorComponent::SetCVarFromFloat(const char* pName, float pValue)
  850. {
  851. return PySetCVarFromFloat(pName, pValue);
  852. }
  853. void PythonEditorComponent::PyRunConsole(const char* text)
  854. {
  855. return ::PyRunConsole(text);
  856. }
  857. void PythonEditorComponent::EnterGameMode()
  858. {
  859. return PyEnterGameMode();
  860. }
  861. bool PythonEditorComponent::IsInGameMode()
  862. {
  863. return PyIsInGameMode();
  864. }
  865. void PythonEditorComponent::ExitGameMode()
  866. {
  867. return PyExitGameMode();
  868. }
  869. void PythonEditorComponent::EnterSimulationMode()
  870. {
  871. return PyEnterSimulationMode();
  872. }
  873. bool PythonEditorComponent::IsInSimulationMode()
  874. {
  875. return PyIsInSimulationMode();
  876. }
  877. void PythonEditorComponent::ExitSimulationMode()
  878. {
  879. return PyExitSimulationMode();
  880. }
  881. void PythonEditorComponent::RunFile(const char *pFile)
  882. {
  883. return PyRunFile(pFile);
  884. }
  885. void PythonEditorComponent::RunFileParameters(const char* pFile, const char* pArguments)
  886. {
  887. return PyRunFileWithParameters(pFile, pArguments);
  888. }
  889. void PythonEditorComponent::ExecuteCommand(const char* cmdline)
  890. {
  891. return PyExecuteCommand(cmdline);
  892. }
  893. bool PythonEditorComponent::MessageBoxOkCancel(const char* pMessage)
  894. {
  895. return PyMessageBox(pMessage);
  896. }
  897. bool PythonEditorComponent::MessageBoxYesNo(const char* pMessage)
  898. {
  899. return PyMessageBoxYesNo(pMessage);
  900. }
  901. bool PythonEditorComponent::MessageBoxOk(const char* pMessage)
  902. {
  903. return PyMessageBoxOK(pMessage);
  904. }
  905. AZStd::string PythonEditorComponent::EditBox(AZStd::string_view pTitle)
  906. {
  907. return PyEditBox(pTitle);
  908. }
  909. AZStd::any PythonEditorComponent::EditBoxCheckDataType(const char* pTitle)
  910. {
  911. return PyEditBoxAndCheckProperty(pTitle);
  912. }
  913. AZStd::string PythonEditorComponent::OpenFileBox()
  914. {
  915. return PyOpenFileBox();
  916. }
  917. const char* PythonEditorComponent::GetAxisConstraint()
  918. {
  919. return PyGetAxisConstraint();
  920. }
  921. void PythonEditorComponent::SetAxisConstraint(AZStd::string_view pConstrain)
  922. {
  923. return PySetAxisConstraint(pConstrain);
  924. }
  925. AZ::IO::Path PythonEditorComponent::GetPakFromFile(const char* filename)
  926. {
  927. return PyGetPakFromFile(filename);
  928. }
  929. void PythonEditorComponent::Log(const char* pMessage)
  930. {
  931. return PyLog(pMessage);
  932. }
  933. void PythonEditorComponent::Undo()
  934. {
  935. return PyUndo();
  936. }
  937. void PythonEditorComponent::Redo()
  938. {
  939. return PyRedo();
  940. }
  941. void PythonEditorComponent::DrawLabel(int x, int y, float size, float r, float g, float b, float a, const char* pLabel)
  942. {
  943. return PyDrawLabel(x, y, size, r, g, b, a, pLabel);
  944. }
  945. AZStd::string PythonEditorComponent::ComboBox(AZStd::string title, AZStd::vector<AZStd::string> values, int selectedIdx)
  946. {
  947. return PyComboBox(title, values, selectedIdx);
  948. }
  949. }
  950. namespace AzToolsFramework
  951. {
  952. void PythonEditorFuncsHandler::Reflect(AZ::ReflectContext* context)
  953. {
  954. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  955. {
  956. // this will put these methods into the 'azlmbr.legacy.general' module
  957. auto addLegacyGeneral = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder)
  958. {
  959. methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  960. ->Attribute(AZ::Script::Attributes::Category, "Legacy/General")
  961. ->Attribute(AZ::Script::Attributes::Module, "legacy.general");
  962. };
  963. addLegacyGeneral(behaviorContext->Method("get_cvar", PyGetCVarAsString, nullptr, "Gets a CVar value as a string."));
  964. addLegacyGeneral(behaviorContext->Method("set_cvar", PySetCVarFromAny, nullptr, "Sets a CVar value from any simple value."));
  965. addLegacyGeneral(behaviorContext->Method("set_cvar_string", PySetCVarFromString, nullptr, "Sets a CVar value from a string."));
  966. addLegacyGeneral(behaviorContext->Method("set_cvar_integer", PySetCVarFromInt, nullptr, "Sets a CVar value from an integer."));
  967. addLegacyGeneral(behaviorContext->Method("set_cvar_float", PySetCVarFromFloat, nullptr, "Sets a CVar value from a float."));
  968. addLegacyGeneral(behaviorContext->Method("run_console", PyRunConsole, nullptr, "Runs a console command."));
  969. addLegacyGeneral(behaviorContext->Method("enter_game_mode", PyEnterGameMode, nullptr, "Enters the editor game mode."));
  970. addLegacyGeneral(behaviorContext->Method("enter_game_mode_fullscreen", PyEnterGameModeFullscreen, nullptr, "Enters the editor game mode in fullscreen."));
  971. addLegacyGeneral(behaviorContext->Method("is_in_game_mode", PyIsInGameMode, nullptr, "Queries if it's in the game mode or not."));
  972. addLegacyGeneral(behaviorContext->Method("exit_game_mode", PyExitGameMode, nullptr, "Exits the editor game mode."));
  973. addLegacyGeneral(behaviorContext->Method("enter_simulation_mode", PyEnterSimulationMode, nullptr, "Enters the editor AI/Physics simulation mode."));
  974. addLegacyGeneral(behaviorContext->Method("is_in_simulation_mode", PyIsInSimulationMode, nullptr, "Queries if the editor is currently in the AI/Physics simulation mode or not."));
  975. addLegacyGeneral(behaviorContext->Method("exit_simulation_mode", PyExitSimulationMode, nullptr, "Exits the editor AI/Physics simulation mode."));
  976. 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."));
  977. 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."));
  978. addLegacyGeneral(behaviorContext->Method("execute_command", PyExecuteCommand, nullptr, "Executes a given string as an editor command."));
  979. addLegacyGeneral(behaviorContext->Method("message_box", PyMessageBox, nullptr, "Shows a confirmation message box with ok|cancel and shows a custom message."));
  980. addLegacyGeneral(behaviorContext->Method("message_box_yes_no", PyMessageBoxYesNo, nullptr, "Shows a confirmation message box with yes|no and shows a custom message."));
  981. addLegacyGeneral(behaviorContext->Method("message_box_ok", PyMessageBoxOK, nullptr, "Shows a confirmation message box with only ok and shows a custom message."));
  982. addLegacyGeneral(behaviorContext->Method("edit_box", PyEditBox, nullptr, "Shows an edit box and returns the value as string."));
  983. 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."));
  984. addLegacyGeneral(behaviorContext->Method("open_file_box", PyOpenFileBox, nullptr, "Shows an open file box and returns the selected file path and name."));
  985. addLegacyGeneral(behaviorContext->Method("get_axis_constraint", PyGetAxisConstraint, nullptr, "Gets axis."));
  986. addLegacyGeneral(behaviorContext->Method("set_axis_constraint", PySetAxisConstraint, nullptr, "Sets axis."));
  987. addLegacyGeneral(behaviorContext->Method("get_pak_from_file", [](const char* filename) -> AZStd::string { return PyGetPakFromFile(filename).Native(); }, nullptr, "Finds a pak file name for a given file."));
  988. addLegacyGeneral(behaviorContext->Method("log", PyLog, nullptr, "Prints the message to the editor console window."));
  989. addLegacyGeneral(behaviorContext->Method("undo", PyUndo, nullptr, "Undoes the last operation."));
  990. addLegacyGeneral(behaviorContext->Method("redo", PyRedo, nullptr, "Redoes the last undone operation."));
  991. addLegacyGeneral(behaviorContext->Method("draw_label", PyDrawLabel, nullptr, "Shows a 2d label on the screen at the given position and given color."));
  992. addLegacyGeneral(behaviorContext->Method("combo_box", PyComboBox, nullptr, "Shows a combo box listing each value passed in, returns string value selected by the user."));
  993. addLegacyGeneral(behaviorContext->Method("crash", PyCrash, nullptr, "Crashes the application, useful for testing crash reporting and other automation tools."));
  994. /////////////////////////////////////////////////////////////////////////
  995. // Temporal, to be removed by LY-101149
  996. addLegacyGeneral(behaviorContext->Method("find_editor_entity", PyFindEditorEntity, nullptr, "Retrieves a editor entity id by name"));
  997. addLegacyGeneral(behaviorContext->Method("find_game_entity", PyFindGameEntity, nullptr, "Retrieves a game entity id by name"));
  998. //////////////////////////////////////////////////////////////////////////
  999. addLegacyGeneral(behaviorContext->Method("dump_exposed_classes", PyDumpBindings::GetExposedPythonClasses, nullptr, "Retrieves exposed classes"));
  1000. }
  1001. }
  1002. }