PythonEditorFuncs.cpp 43 KB

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