PythonEditorFuncs.cpp 43 KB

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