RobotImporterWidget.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  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 <AzCore/IO/FileIO.h>
  9. #include <AzCore/IO/Path/Path.h>
  10. #include <AzCore/Utils/Utils.h>
  11. #include "RobotImporterWidget.h"
  12. #include "URDF/URDFPrefabMaker.h"
  13. #include "URDF/UrdfParser.h"
  14. #include "Utils/RobotImporterUtils.h"
  15. #include <QApplication>
  16. #include <QScreen>
  17. #include <QTranslator>
  18. namespace ROS2
  19. {
  20. RobotImporterWidget::RobotImporterWidget(QWidget* parent)
  21. : QWizard(parent)
  22. {
  23. m_introPage = new IntroPage(this);
  24. m_fileSelectPage = new FileSelectionPage(this);
  25. m_checkUrdfPage = new CheckUrdfPage(this);
  26. m_assetPage = new CheckAssetPage(this);
  27. m_prefabMakerPage = new PrefabMakerPage(this);
  28. m_xacroParamsPage = new XacroParamsPage(this);
  29. addPage(m_introPage);
  30. addPage(m_fileSelectPage);
  31. addPage(m_xacroParamsPage);
  32. addPage(m_checkUrdfPage);
  33. addPage(m_assetPage);
  34. addPage(m_prefabMakerPage);
  35. connect(this, &QWizard::currentIdChanged, this, &RobotImporterWidget::onCurrentIdChanged);
  36. connect(m_prefabMakerPage, &QWizardPage::completeChanged, this, &RobotImporterWidget::OnUrdfCreated);
  37. connect(m_prefabMakerPage, &PrefabMakerPage::onCreateButtonPressed, this, &RobotImporterWidget::onCreateButtonPressed);
  38. connect(
  39. this,
  40. &QWizard::customButtonClicked,
  41. this,
  42. [this](int id)
  43. {
  44. if (id == PrefabCreationButtonId)
  45. {
  46. this->onCreateButtonPressed();
  47. }
  48. });
  49. void onCreateButtonPressed();
  50. setWindowTitle(tr("Robot Import Wizard"));
  51. connect(
  52. this,
  53. &QDialog::finished,
  54. [this](int id)
  55. {
  56. AZ_Printf("page", "QDialog::finished : %d", id);
  57. parentWidget()->close();
  58. });
  59. }
  60. void RobotImporterWidget::OnUrdfCreated()
  61. {
  62. // hide cancel and back buttons when last page succeed
  63. if (currentPage() == m_prefabMakerPage && m_prefabMakerPage->isComplete())
  64. {
  65. QWizard::button(QWizard::CancelButton)->hide();
  66. QWizard::button(QWizard::BackButton)->hide();
  67. QWizard::button(PrefabCreationButtonId)->hide();
  68. }
  69. }
  70. void RobotImporterWidget::OpenUrdf()
  71. {
  72. QString report;
  73. if (!m_urdfPath.empty())
  74. {
  75. if (IsFileXacro(m_urdfPath))
  76. {
  77. Utils::xacro::ExecutionOutcome outcome = Utils::xacro::ParseXacro(m_urdfPath.String(), m_params);
  78. if (outcome)
  79. {
  80. m_parsedUrdf = outcome.m_urdfHandle;
  81. report += "# " + tr("XACRO execution succeeded") + "\n";
  82. m_assetPage->ClearAssetsList();
  83. }
  84. else
  85. {
  86. report += "# " + tr("XACRO parsing failed") + "\n";
  87. report += "\n\n## " + tr("Command called") + "\n\n`" + QString::fromUtf8(outcome.m_called.data()) + "`";
  88. report += "\n\n" + tr("Process failed");
  89. report += "\n\n## " + tr("Error output") + "\n\n";
  90. report += "```\n";
  91. if (outcome.m_logErrorOutput.size())
  92. {
  93. report +=
  94. QString::fromLocal8Bit(outcome.m_logErrorOutput.data(), static_cast<int>(outcome.m_logErrorOutput.size()));
  95. }
  96. else
  97. {
  98. report += tr("(EMPTY)");
  99. }
  100. report += "\n```";
  101. report += "\n\n## " + tr("Standard output") + "\n\n";
  102. report += "```\n";
  103. if (outcome.m_logStandardOutput.size())
  104. {
  105. report += QString::fromLocal8Bit(
  106. outcome.m_logStandardOutput.data(), static_cast<int>(outcome.m_logStandardOutput.size()));
  107. }
  108. else
  109. {
  110. report += tr("(EMPTY)");
  111. }
  112. report += "\n```";
  113. m_checkUrdfPage->ReportURDFResult(report, false);
  114. m_parsedUrdf = nullptr;
  115. return;
  116. }
  117. }
  118. else if (IsFileUrdf(m_urdfPath))
  119. {
  120. // standard URDF
  121. m_parsedUrdf = UrdfParser::ParseFromFile(m_urdfPath.Native());
  122. }
  123. else
  124. {
  125. AZ_Assert(false, "Unknown file extension : %s \n", m_urdfPath.c_str());
  126. }
  127. const auto log = UrdfParser::GetUrdfParsingLog();
  128. if (m_parsedUrdf)
  129. {
  130. report += "# " + tr("The URDF was parsed and opened successfully") + "\n";
  131. m_prefabMaker.reset();
  132. // Report the status of skipping this page
  133. AZ_Printf("Wizard", "Wizard skips m_checkUrdfPage since there is no errors in URDF\n");
  134. m_meshNames = Utils::GetMeshesFilenames(m_parsedUrdf->getRoot(), true, true);
  135. m_assetPage->ClearAssetsList();
  136. }
  137. else
  138. {
  139. report += "# " + tr("The URDF was not opened") + "\n";
  140. report += tr("URDF parser returned following errors:") + "\n\n";
  141. }
  142. if (!log.empty())
  143. {
  144. report += "`";
  145. report += QString::fromUtf8(log.data(), int(log.size()));
  146. report += "`";
  147. }
  148. m_checkUrdfPage->ReportURDFResult(report, m_parsedUrdf != nullptr);
  149. }
  150. }
  151. void RobotImporterWidget::onCurrentIdChanged(int id)
  152. {
  153. AZ_Printf("Wizard", "Wizard at page %d", id);
  154. if (currentPage() == m_assetPage)
  155. {
  156. FillAssetPage();
  157. }
  158. else if (currentPage() == m_prefabMakerPage)
  159. {
  160. FillPrefabMakerPage();
  161. }
  162. }
  163. void RobotImporterWidget::FillAssetPage()
  164. {
  165. if (m_parsedUrdf && m_assetPage->IsEmpty())
  166. {
  167. auto collidersNames = Utils::GetMeshesFilenames(m_parsedUrdf->getRoot(), false, true);
  168. auto visualNames = Utils::GetMeshesFilenames(m_parsedUrdf->getRoot(), true, false);
  169. if (m_importAssetWithUrdf)
  170. {
  171. m_urdfAssetsMapping = AZStd::make_shared<Utils::UrdfAssetMap>(
  172. Utils::CopyAssetForURDFAndCreateAssetMap(m_meshNames, m_urdfPath.String(), collidersNames, visualNames));
  173. }
  174. else
  175. {
  176. m_urdfAssetsMapping = AZStd::make_shared<Utils::UrdfAssetMap>(Utils::FindAssetsForUrdf(m_meshNames, m_urdfPath.String()));
  177. for (const AZStd::string& meshPath : m_meshNames)
  178. {
  179. if (m_urdfAssetsMapping->contains(meshPath))
  180. {
  181. const auto& asset = m_urdfAssetsMapping->at(meshPath);
  182. bool visual = visualNames.contains(meshPath);
  183. bool collider = collidersNames.contains(meshPath);
  184. Utils::CreateSceneManifest(asset.m_availableAssetInfo.m_sourceAssetGlobalPath, collider, visual);
  185. }
  186. }
  187. };
  188. for (const AZStd::string& meshPath : m_meshNames)
  189. {
  190. const QString kNotFound = tr("not found");
  191. const AZStd::string kNotFoundAz(kNotFound.toUtf8());
  192. AZ::Uuid sourceAssetUuid;
  193. if (m_urdfAssetsMapping->contains(meshPath))
  194. {
  195. QString type = kNotFound;
  196. AZStd::string sourcePath(kNotFoundAz);
  197. AZStd::string resolvedPath(kNotFoundAz);
  198. QString productAssetText;
  199. auto crc = AZ::Crc32();
  200. QString tooltip = kNotFound;
  201. bool visual = visualNames.contains(meshPath);
  202. bool collider = collidersNames.contains(meshPath);
  203. if (visual && collider)
  204. {
  205. type = tr("Visual and Collider");
  206. }
  207. else if (visual)
  208. {
  209. type = tr("Visual");
  210. }
  211. else if (collider)
  212. {
  213. type = tr("Collider");
  214. }
  215. if (m_urdfAssetsMapping->contains(meshPath))
  216. {
  217. const auto& asset = m_urdfAssetsMapping->at(meshPath);
  218. sourceAssetUuid = asset.m_availableAssetInfo.m_sourceGuid;
  219. sourcePath = asset.m_availableAssetInfo.m_sourceAssetRelativePath;
  220. resolvedPath = asset.m_resolvedUrdfPath.data();
  221. crc = asset.m_urdfFileCRC;
  222. tooltip = QString::fromUtf8(resolvedPath.data(), resolvedPath.size());
  223. }
  224. m_assetPage->ReportAsset(sourceAssetUuid, meshPath, type, sourcePath, crc, resolvedPath);
  225. }
  226. else
  227. {
  228. m_assetPage->ReportAsset(sourceAssetUuid, meshPath, kNotFound, kNotFoundAz, AZ::Crc32(), kNotFoundAz);
  229. };
  230. }
  231. m_assetPage->StartWatchAsset();
  232. }
  233. }
  234. void RobotImporterWidget::FillPrefabMakerPage()
  235. {
  236. if (m_parsedUrdf)
  237. {
  238. AZStd::string robotName = AZStd::string(m_parsedUrdf->getName().c_str(), m_parsedUrdf->getName().size()) + ".prefab";
  239. m_prefabMakerPage->setProposedPrefabName(robotName);
  240. QWizard::button(PrefabCreationButtonId)->setText(tr("Create Prefab"));
  241. QWizard::setOption(HavePrefabCreationButton, true);
  242. }
  243. }
  244. bool RobotImporterWidget::validateCurrentPage()
  245. {
  246. if (currentPage() == m_fileSelectPage)
  247. {
  248. m_params.clear();
  249. m_urdfPath = AZStd::string(m_fileSelectPage->getFileName().toUtf8().constData());
  250. if (IsFileXacro(m_urdfPath))
  251. {
  252. m_params = Utils::xacro::GetParameterFromXacroFile(m_urdfPath.String());
  253. AZ_Printf("RobotImporterWidget", "Xacro has %d arguments\n", m_params.size());
  254. m_xacroParamsPage->SetXacroParameters(m_params);
  255. }
  256. // no need to wait for param page - parse urdf now, nextId will skip unnecessary pages
  257. if (m_params.empty())
  258. {
  259. OpenUrdf();
  260. }
  261. m_importAssetWithUrdf = m_fileSelectPage->getIfCopyAssetsDuringUrdfImport();
  262. }
  263. if (currentPage() == m_xacroParamsPage)
  264. {
  265. m_params = m_xacroParamsPage->GetXacroParameters();
  266. OpenUrdf();
  267. }
  268. if (currentPage() == m_introPage)
  269. {
  270. AZ::EntityId levelEntityId;
  271. AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
  272. levelEntityId, &AzToolsFramework::ToolsApplicationRequests::GetCurrentLevelEntityId);
  273. AZ::Entity* levelEntity{ nullptr };
  274. AZ::ComponentApplicationBus::BroadcastResult(levelEntity, &AZ::ComponentApplicationRequests::FindEntity, levelEntityId);
  275. if (!levelEntityId.IsValid() || levelEntity == nullptr)
  276. {
  277. QMessageBox noLevelLoadedMessage;
  278. noLevelLoadedMessage.critical(0, "No level opened", "A level must be opened before using URDF Importer");
  279. noLevelLoadedMessage.setFixedSize(500, 200);
  280. return false;
  281. }
  282. }
  283. return currentPage()->validatePage();
  284. }
  285. int RobotImporterWidget::nextId() const
  286. {
  287. if ((currentPage() == m_fileSelectPage && m_params.empty()) || currentPage() == m_xacroParamsPage)
  288. {
  289. if (m_parsedUrdf)
  290. {
  291. if (m_meshNames.size() == 0)
  292. {
  293. // skip two pages when urdf is parsed without problems, and it has no meshes
  294. return m_assetPage->nextId();
  295. }
  296. else
  297. {
  298. // skip one page when urdf is parsed without problems
  299. return m_checkUrdfPage->nextId();
  300. }
  301. }
  302. if (m_params.empty())
  303. {
  304. return m_xacroParamsPage->nextId();
  305. }
  306. }
  307. return currentPage()->nextId();
  308. }
  309. void RobotImporterWidget::CreatePrefab(AZStd::string prefabName)
  310. {
  311. const AZ::IO::Path prefabPathRealative(AZ::IO::Path("Assets") / "Importer" / prefabName);
  312. const AZ::IO::Path prefabPath(AZ::IO::Path(AZ::Utils::GetProjectPath()) / prefabPathRealative);
  313. bool fileExists = AZ::IO::FileIOBase::GetInstance()->Exists(prefabPath.c_str());
  314. if (CheckCyclicalDependency(prefabPathRealative))
  315. {
  316. m_prefabMakerPage->setSuccess(false);
  317. return;
  318. }
  319. if (fileExists)
  320. {
  321. QMessageBox msgBox;
  322. msgBox.setText(tr("Prefab with this name already exists"));
  323. msgBox.setInformativeText(tr("Do you want to overwrite existing prefab?"));
  324. msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
  325. msgBox.setDefaultButton(QMessageBox::Cancel);
  326. int ret = msgBox.exec();
  327. if (ret == QMessageBox::Cancel)
  328. {
  329. m_prefabMakerPage->setSuccess(false);
  330. return;
  331. }
  332. }
  333. const bool useArticulation = m_prefabMakerPage->IsUseArticulations();
  334. m_prefabMaker = AZStd::make_unique<URDFPrefabMaker>(
  335. m_urdfPath.String(), m_parsedUrdf, prefabPath.String(), m_urdfAssetsMapping, useArticulation);
  336. auto prefabOutcome = m_prefabMaker->CreatePrefabFromURDF();
  337. if (prefabOutcome.IsSuccess())
  338. {
  339. AZStd::string status = m_prefabMaker->GetStatus();
  340. m_prefabMakerPage->reportProgress(status);
  341. m_prefabMakerPage->setSuccess(true);
  342. }
  343. else
  344. {
  345. AZStd::string status = "Failed to create prefab\n";
  346. status += prefabOutcome.GetError() + "\n";
  347. status += m_prefabMaker->GetStatus();
  348. m_prefabMakerPage->reportProgress(status);
  349. m_prefabMakerPage->setSuccess(false);
  350. }
  351. }
  352. void RobotImporterWidget::onCreateButtonPressed()
  353. {
  354. CreatePrefab(m_prefabMakerPage->getPrefabName());
  355. }
  356. bool RobotImporterWidget::CheckCyclicalDependency(AZ::IO::Path importedPrefabPath)
  357. {
  358. AzFramework::EntityContextId contextId;
  359. AzFramework::EntityIdContextQueryBus::BroadcastResult(contextId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
  360. AZ_Printf("CheckCyclicalDependency", "CheckCyclicalDependency %s\n", importedPrefabPath.Native().c_str());
  361. auto focusInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabFocusInterface>::Get();
  362. if (!focusInterface)
  363. {
  364. ReportError(tr("Imported prefab could not be validated.\nImport aborted."));
  365. return true;
  366. }
  367. auto focusedPrefabInstance = focusInterface->GetFocusedPrefabInstance(contextId);
  368. if (!focusedPrefabInstance)
  369. {
  370. ReportError(tr("Imported prefab could not be validated.\nImport aborted."));
  371. return true;
  372. }
  373. auto focusPrefabFilename = focusedPrefabInstance.value().get().GetTemplateSourcePath();
  374. if (focusPrefabFilename == importedPrefabPath)
  375. {
  376. ReportError(
  377. tr("Cyclical dependency detected.\nSelected URDF model is currently being edited. Exit prefab edit mode and try again."));
  378. return true;
  379. }
  380. return false;
  381. }
  382. void RobotImporterWidget::ReportError(const QString& errorMessage)
  383. {
  384. QMessageBox::critical(this, QObject::tr("Error"), errorMessage);
  385. AZ_Error("RobotImporterWidget", false, "%s", errorMessage.toUtf8().constData());
  386. }
  387. AZStd::string RobotImporterWidget::GetCapitalizedExtension(const AZ::IO::Path& filename) const
  388. {
  389. AZStd::string extension{ filename.Extension().Native() };
  390. AZStd::to_upper(extension.begin(), extension.end());
  391. return extension;
  392. }
  393. bool RobotImporterWidget::IsFileXacro(const AZ::IO::Path& filename) const
  394. {
  395. return filename.HasExtension() && GetCapitalizedExtension(filename) == ".XACRO";
  396. }
  397. bool RobotImporterWidget::IsFileUrdf(const AZ::IO::Path& filename) const
  398. {
  399. return filename.HasExtension() && GetCapitalizedExtension(filename) == ".URDF";
  400. }
  401. } // namespace ROS2