AssetImporterWindow.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  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 <AssetImporterWindow.h>
  9. #include <ui_AssetImporterWindow.h>
  10. #include <AssetImporterPlugin.h>
  11. #include <ImporterRootDisplay.h>
  12. #include <QCloseEvent>
  13. #include <QDesktopServices>
  14. #include <QDockWidget>
  15. #include <QFile>
  16. #include <QFileDialog>
  17. #include <QLabel>
  18. #include <QMessageBox>
  19. #include <QTimer>
  20. class IXMLDOMDocumentPtr; // Needed for settings.h
  21. class CXTPDockingPaneLayout; // Needed for settings.h
  22. #include <Settings.h>
  23. #include <QScrollArea>
  24. #include <ActionOutput.h>
  25. #include <AssetImporterDocument.h>
  26. #include <AzCore/Component/ComponentApplicationBus.h>
  27. #include <AzCore/IO/Path/Path.h>
  28. #include <AzCore/std/functional.h>
  29. #include <AzCore/std/string/conversions.h>
  30. #include <AzCore/Utils/Utils.h>
  31. #include <AzFramework/StringFunc/StringFunc.h>
  32. #include <AzToolsFramework/SourceControl/SourceControlAPI.h>
  33. #include <AzQtComponents/Components/StyledDetailsTableModel.h>
  34. #include <AzQtComponents/Components/StylesheetPreprocessor.h>
  35. #include <AzQtComponents/Components/Widgets/CardHeader.h>
  36. #include <AzQtComponents/Components/Widgets/TableView.h>
  37. #include <Util/PathUtil.h>
  38. #include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
  39. #include <SceneAPI/SceneCore/DataTypes/Rules/IScriptProcessorRule.h>
  40. #include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
  41. #include <SceneAPI/SceneCore/Events/SceneSerializationBus.h>
  42. #include <SceneAPI/SceneCore/Utilities/Reporting.h>
  43. #include <SceneAPI/SceneData/Rules/ScriptProcessorRule.h>
  44. #include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/AsyncOperationProcessingHandler.h>
  45. #include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/ExportJobProcessingHandler.h>
  46. #include <SceneAPI/SceneUI/SceneWidgets/ManifestWidget.h>
  47. #include <SceneAPI/SceneUI/SceneWidgets/SceneGraphInspectWidget.h>
  48. const char* AssetImporterWindow::s_documentationWebAddress = "https://www.o3de.org/docs/user-guide/assets/scene-settings/";
  49. const AZ::Uuid AssetImporterWindow::s_browseTag = AZ::Uuid::CreateString("{C240D2E1-BFD2-4FFA-BB5B-CC0FA389A5D3}");
  50. AssetImporterWindow::AssetImporterWindow()
  51. : AssetImporterWindow(nullptr)
  52. {
  53. }
  54. AssetImporterWindow::AssetImporterWindow(QWidget* parent)
  55. : QMainWindow(parent)
  56. , ui(new Ui::AssetImporterWindow)
  57. , m_assetImporterDocument(new AssetImporterDocument())
  58. , m_serializeContext(nullptr)
  59. , m_rootDisplay(nullptr)
  60. , m_overlay(nullptr)
  61. , m_isClosed(false)
  62. {
  63. Init();
  64. }
  65. AssetImporterWindow::~AssetImporterWindow()
  66. {
  67. disconnect();
  68. }
  69. void AssetImporterWindow::OpenFile(const AZStd::string& filePath)
  70. {
  71. if (m_sceneSettingsCardOverlay != AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex)
  72. {
  73. QMessageBox::warning(this, "In progress", "Please wait for the previous task to complete before opening a new file.");
  74. return;
  75. }
  76. if (!m_overlay->CanClose())
  77. {
  78. QMessageBox::warning(this, "In progress", "Unable to close one or more windows at this time.");
  79. return;
  80. }
  81. // Make sure we are not browsing *over* a current editing operation
  82. if (!IsAllowedToChangeSourceFile())
  83. {
  84. // Issue will already have been reported to the user.
  85. return;
  86. }
  87. if (!m_overlay->PopAllLayers())
  88. {
  89. QMessageBox::warning(this, "In progress", "Unable to close one or more windows at this time.");
  90. return;
  91. }
  92. OpenFileInternal(filePath);
  93. }
  94. bool AssetImporterWindow::CanClose()
  95. {
  96. if (m_isClosed)
  97. {
  98. return true;
  99. }
  100. if (m_sceneSettingsCardOverlay != AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex)
  101. {
  102. QMessageBox::critical(this, "Processing In Progress", "Please wait until processing has completed to try again.",
  103. QMessageBox::Ok, QMessageBox::Ok);
  104. return false;
  105. }
  106. if (!m_overlay->CanClose())
  107. {
  108. QMessageBox::critical(this, "Unable to close", "Unable to close one or more windows at this time.",
  109. QMessageBox::Ok, QMessageBox::Ok);
  110. return false;
  111. }
  112. if (ShouldSaveBeforeClose())
  113. {
  114. return false;
  115. }
  116. m_isClosed = true;
  117. return true;
  118. }
  119. void AssetImporterWindow::Init()
  120. {
  121. // Serialization and reflection framework setup
  122. AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  123. AZ_Assert(m_serializeContext, "Serialization context not available");
  124. // Load the style sheets
  125. AzQtComponents::StylesheetPreprocessor styleSheetProcessor(nullptr);
  126. auto mainWindowQSSPath = AZ::IO::Path(AZ::Utils::GetEnginePath()) / "Assets";
  127. mainWindowQSSPath /= "Editor/Styles/AssetImporterWindow.qss";
  128. mainWindowQSSPath.MakePreferred();
  129. QFile mainWindowStyleSheetFile(mainWindowQSSPath.c_str());
  130. if (mainWindowStyleSheetFile.open(QFile::ReadOnly))
  131. {
  132. setStyleSheet(styleSheetProcessor.ProcessStyleSheet(mainWindowStyleSheetFile.readAll()));
  133. }
  134. ui->setupUi(this);
  135. // Setup the overlay system, and set the root to be the root display. The root display has the browse,
  136. // the Import button & the cancel button, which are handled here by the window.
  137. m_overlay.reset(aznew AZ::SceneAPI::UI::OverlayWidget(this));
  138. m_rootDisplay.reset(aznew ImporterRootDisplayWidget(m_serializeContext));
  139. connect(
  140. m_rootDisplay.data()->GetManifestWidget(),
  141. &AZ::SceneAPI::UI::ManifestWidget::SaveClicked,
  142. this,
  143. &AssetImporterWindow::SaveClicked);
  144. connect(
  145. m_rootDisplay.data()->GetManifestWidget(),
  146. &AZ::SceneAPI::UI::ManifestWidget::OnInspect,
  147. this,
  148. &AssetImporterWindow::OnInspect);
  149. connect(
  150. m_rootDisplay.data()->GetManifestWidget(),
  151. &AZ::SceneAPI::UI::ManifestWidget::OnSceneResetRequested,
  152. this,
  153. &AssetImporterWindow::OnSceneResetRequested);
  154. connect(
  155. m_rootDisplay.data()->GetManifestWidget(),
  156. &AZ::SceneAPI::UI::ManifestWidget::OnClearUnsavedChangesRequested,
  157. this,
  158. &AssetImporterWindow::OnClearUnsavedChangesRequested);
  159. connect(
  160. m_rootDisplay.data()->GetManifestWidget(),
  161. &AZ::SceneAPI::UI::ManifestWidget::OnAssignScript,
  162. this,
  163. &AssetImporterWindow::OnAssignScript);
  164. connect(m_overlay.data(), &AZ::SceneAPI::UI::OverlayWidget::LayerAdded, this, &AssetImporterWindow::OverlayLayerAdded);
  165. connect(m_overlay.data(), &AZ::SceneAPI::UI::OverlayWidget::LayerRemoved, this, &AssetImporterWindow::OverlayLayerRemoved);
  166. m_overlay->SetRoot(m_rootDisplay.data());
  167. ui->m_settingsAreaLayout->addWidget(m_overlay.data());
  168. // Filling the initial browse prompt text to be programmatically set from available extensions
  169. AZStd::unordered_set<AZStd::string> extensions;
  170. AZ::SceneAPI::Events::AssetImportRequestBus::Broadcast(
  171. &AZ::SceneAPI::Events::AssetImportRequestBus::Events::GetSupportedFileExtensions, extensions);
  172. AZ_Error(AZ::SceneAPI::Utilities::ErrorWindow, !extensions.empty(), "No file extensions defined for assets.");
  173. if (!extensions.empty())
  174. {
  175. for (AZStd::string& extension : extensions)
  176. {
  177. extension = extension.substr(1);
  178. AZStd::to_upper(extension.begin(), extension.end());
  179. }
  180. AZStd::string joinedExtensions;
  181. AzFramework::StringFunc::Join(joinedExtensions, extensions.begin(), extensions.end(), " or ");
  182. AZStd::string firstLineText =
  183. AZStd::string::format(
  184. "%s files are available for use after placing them in any folder within your game project. "
  185. "These files will automatically be processed and may be accessed via the Asset Browser. <a href=\"%s\">Learn more...</a>",
  186. joinedExtensions.c_str(), s_documentationWebAddress);
  187. ui->m_initialPromptFirstLine->setText(firstLineText.c_str());
  188. AZStd::string secondLineText =
  189. AZStd::string::format("To adjust the %s settings, right-click the file in the Asset Browser and select \"Edit Settings\" from the context menu.", joinedExtensions.c_str());
  190. ui->m_initialPromptSecondLine->setText(secondLineText.c_str());
  191. }
  192. else
  193. {
  194. AZStd::string firstLineText =
  195. AZStd::string::format(
  196. "Files are available for use after placing them in any folder within your game project. "
  197. "These files will automatically be processed and may be accessed via the Asset Browser. <a href=\"%s\">Learn more...</a>", s_documentationWebAddress);
  198. ui->m_initialPromptFirstLine->setText(firstLineText.c_str());
  199. AZStd::string secondLineText = "To adjust the settings, right-click the file in the Asset Browser and select \"Edit Settings\" from the context menu.";
  200. ui->m_initialPromptSecondLine->setText(secondLineText.c_str());
  201. // Hide the initial browse container so we can show the error (it will be shown again when the overlay pops)
  202. ui->m_initialBrowseContainer->hide();
  203. QMessageBox::critical(this, "No Extensions Detected",
  204. "No importable file types were detected. This likely means an internal error has taken place which has broken the "
  205. "registration of valid import types (e.g. FBX). This type of issue requires engineering support.");
  206. }
  207. QObject::connect(&m_qtFileWatcher, &QFileSystemWatcher::fileChanged, this, &AssetImporterWindow::FileChanged);
  208. }
  209. void AssetImporterWindow::OpenFileInternal(const AZStd::string& filePath)
  210. {
  211. using namespace AZ::SceneAPI::SceneUI;
  212. // Clear all previously watched files
  213. m_qtFileWatcher.removePaths(m_qtFileWatcher.files());
  214. auto asyncLoadHandler = AZStd::make_shared<AZ::SceneAPI::SceneUI::AsyncOperationProcessingHandler>(
  215. s_browseTag,
  216. [this, filePath]()
  217. {
  218. // this is invoked across threads, so ensure that nothing touches the main thread that isn't thread safe.
  219. // Qt objects, in particular, should talk via timers or queued connections.
  220. m_assetImporterDocument->LoadScene(filePath);
  221. QMetaObject::invokeMethod(this, &AssetImporterWindow::UpdateDefaultSceneDisplay, Qt::QueuedConnection);
  222. },
  223. [this]()
  224. {
  225. QMetaObject::invokeMethod(this, &AssetImporterWindow::HandleAssetLoadingCompleted, Qt::QueuedConnection);
  226. }, this);
  227. QFileInfo fileInfo(filePath.c_str());
  228. SceneSettingsCard* card = CreateSceneSettingsCard(fileInfo.fileName(), SceneSettingsCard::Layout::Loading, SceneSettingsCard::State::Loading);
  229. card->SetAndStartProcessingHandler(asyncLoadHandler);
  230. }
  231. SceneSettingsCard* AssetImporterWindow::CreateSceneSettingsCard(
  232. QString fileName,
  233. SceneSettingsCard::Layout layout,
  234. SceneSettingsCard::State state)
  235. {
  236. while (QLayoutItem* cardToDelete = ui->m_cardAreaLayout->takeAt(0))
  237. {
  238. delete cardToDelete->widget();
  239. delete cardToDelete;
  240. }
  241. SceneSettingsCard* card = new SceneSettingsCard(s_browseTag, fileName, layout, ui->m_cardAreaLayoutWidget);
  242. card->setExpanded(false);
  243. ui->m_notificationAreaLayoutWidget->show();
  244. card->SetState(state);
  245. ui->m_cardAreaLayout->addWidget(card);
  246. ui->m_cardAreaLayoutWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
  247. ++m_openSceneSettingsCards;
  248. connect(card, &QObject::destroyed, this, &AssetImporterWindow::SceneSettingsCardDestroyed);
  249. connect(card, &SceneSettingsCard::ProcessingCompleted, this, &AssetImporterWindow::SceneSettingsCardProcessingCompleted);
  250. // Not passing in a QLabel to display because with a QLabel it won't darken the rest of the interface, which is preferred.
  251. m_sceneSettingsCardOverlay = m_overlay->PushLayer(nullptr, nullptr, "Waiting for file to finish processing", AzQtComponents::OverlayWidgetButtonList());
  252. return card;
  253. }
  254. void AssetImporterWindow::SceneSettingsCardDestroyed()
  255. {
  256. if (m_isClosed)
  257. {
  258. return;
  259. }
  260. if (m_openSceneSettingsCards > 0)
  261. {
  262. --m_openSceneSettingsCards;
  263. }
  264. if (m_openSceneSettingsCards <= 0)
  265. {
  266. ui->m_notificationAreaLayoutWidget->hide();
  267. }
  268. }
  269. void AssetImporterWindow::SceneSettingsCardProcessingCompleted()
  270. {
  271. if (m_isClosed)
  272. {
  273. return;
  274. }
  275. m_isSaving = false;
  276. m_overlay->PopLayer(m_sceneSettingsCardOverlay);
  277. m_sceneSettingsCardOverlay = AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex;
  278. }
  279. bool AssetImporterWindow::IsAllowedToChangeSourceFile()
  280. {
  281. if (!m_rootDisplay->HasUnsavedChanges())
  282. {
  283. return true;
  284. }
  285. const int result = QMessageBox::question(
  286. this,
  287. tr("Save Asset Changes?"),
  288. tr("Changes have been made to the asset in the Inspector Scene Settings. Would you like to save these changes prior to switching assets?"),
  289. QMessageBox::Yes,
  290. QMessageBox::No);
  291. if (result == QMessageBox::No)
  292. {
  293. return true;
  294. }
  295. m_isSaving = true;
  296. AZStd::shared_ptr<AZ::ActionOutput> output = AZStd::make_shared<AZ::ActionOutput>();
  297. m_assetImporterDocument->SaveScene(
  298. output,
  299. [](bool wasSuccessful)
  300. {
  301. if (!wasSuccessful)
  302. {
  303. QMessageBox messageBox(
  304. QMessageBox::Icon::Warning,
  305. tr("Failed to save"),
  306. tr("An error has been encountered saving this file. See the logs for details."));
  307. messageBox.exec();
  308. }
  309. });
  310. return true;
  311. }
  312. bool AssetImporterWindow::ShouldSaveBeforeClose()
  313. {
  314. if (!m_rootDisplay->HasUnsavedChanges())
  315. {
  316. return false;
  317. }
  318. const int result = QMessageBox::question(
  319. this,
  320. tr("Save Asset Changes?"),
  321. tr("Changes have been made to the asset in the Inspector Scene Settings. Would you like to save these changes prior to closing the window?"),
  322. QMessageBox::Yes,
  323. QMessageBox::No);
  324. if (result == QMessageBox::No)
  325. {
  326. return false;
  327. }
  328. m_isSaving = true;
  329. AZStd::shared_ptr<AZ::ActionOutput> output = AZStd::make_shared<AZ::ActionOutput>();
  330. m_assetImporterDocument->SaveScene(
  331. output,
  332. [this](bool wasSuccessful)
  333. {
  334. if (!wasSuccessful)
  335. {
  336. QMessageBox messageBox(
  337. QMessageBox::Icon::Warning,
  338. tr("Failed to save"),
  339. tr("An error has been encountered saving this file. See the logs for details."));
  340. messageBox.exec();
  341. }
  342. QWidget* dock = parentWidget();
  343. while (dock)
  344. {
  345. QDockWidget* converted = qobject_cast<QDockWidget*>(dock);
  346. if (converted)
  347. {
  348. m_isClosed = true;
  349. converted->deleteLater();
  350. break;
  351. }
  352. else
  353. {
  354. dock = dock->parentWidget();
  355. }
  356. }
  357. });
  358. return true;
  359. }
  360. void AssetImporterWindow::SaveClicked()
  361. {
  362. using namespace AZ::SceneAPI::SceneUI;
  363. // There are specific measures in place to block re-entry, applying asserts to be safe
  364. if (m_sceneSettingsCardOverlay != AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex)
  365. {
  366. return;
  367. }
  368. else if (!m_scriptProcessorRuleFilename.empty())
  369. {
  370. AZ_TracePrintf(AZ::SceneAPI::Utilities::WarningWindow, "A script updates the manifest; will not save.");
  371. QMessageBox messageBox(
  372. QMessageBox::Icon::Warning,
  373. tr("Failed to save"),
  374. tr("A script updates this file; will not save."));
  375. messageBox.exec();
  376. return;
  377. }
  378. SceneSettingsCard* card = CreateSceneSettingsCard(m_rootDisplay->GetHeaderFileName(), SceneSettingsCard::Layout::Exporting, SceneSettingsCard::State::Processing);
  379. bool isSourceControlActive = false;
  380. {
  381. using SCRequestBus = AzToolsFramework::SourceControlConnectionRequestBus;
  382. SCRequestBus::BroadcastResult(isSourceControlActive, &SCRequestBus::Events::IsActive);
  383. }
  384. AZStd::shared_ptr<AZ::ActionOutput> output = AZStd::make_shared<AZ::ActionOutput>();
  385. m_isSaving = true;
  386. m_assetImporterDocument->SaveScene(output,
  387. [output, this, isSourceControlActive, card](bool wasSuccessful)
  388. {
  389. m_rootDisplay->UpdateTimeStamp(m_assetImporterDocument->GetScene()->GetManifestFilename().c_str(), gSettings.enableSceneInspector);
  390. if (output->HasAnyWarnings())
  391. {
  392. AZ_TracePrintf(AZ::SceneAPI::Utilities::WarningWindow, "%s", output->BuildWarningMessage().c_str());
  393. }
  394. if (output->HasAnyErrors())
  395. {
  396. AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "%s", output->BuildErrorMessage().c_str());
  397. }
  398. if (wasSuccessful)
  399. {
  400. if (!isSourceControlActive)
  401. {
  402. AZ_TracePrintf(AZ::SceneAPI::Utilities::SuccessWindow, "Saving complete");
  403. }
  404. else
  405. {
  406. AZ_TracePrintf(AZ::SceneAPI::Utilities::SuccessWindow, "Saving & source control operations complete");
  407. }
  408. m_rootDisplay->HandleSaveWasSuccessful();
  409. // Don't attach the job processor until all files are saved.
  410. card->SetAndStartProcessingHandler(AZStd::make_shared<ExportJobProcessingHandler>(s_browseTag, m_fullSourcePath));
  411. }
  412. }
  413. );
  414. }
  415. void AssetImporterWindow::OnClearUnsavedChangesRequested()
  416. {
  417. ReloadCurrentScene(false);
  418. }
  419. void AssetImporterWindow::OnSceneResetRequested()
  420. {
  421. using namespace AZ::SceneAPI::Events;
  422. using namespace AZ::SceneAPI::SceneUI;
  423. using namespace AZ::SceneAPI::Utilities;
  424. auto asyncLoadHandler = AZStd::make_shared<AZ::SceneAPI::SceneUI::AsyncOperationProcessingHandler>(
  425. s_browseTag,
  426. [this]()
  427. {
  428. m_assetImporterDocument->GetScene()->GetManifest().Clear();
  429. AZ::SceneAPI::Events::ProcessingResultCombiner result;
  430. AssetImportRequestBus::BroadcastResult(
  431. result,
  432. &AssetImportRequestBus::Events::UpdateManifest,
  433. *m_assetImporterDocument->GetScene(),
  434. AssetImportRequest::ManifestAction::ConstructDefault,
  435. AssetImportRequest::RequestingApplication::Editor);
  436. // Specifically using success, because ignore would be an invalid case.
  437. // Whenever we do construct default, it should always be done
  438. if (result.GetResult() == ProcessingResult::Success)
  439. {
  440. AZ_TracePrintf(SuccessWindow, "Successfully reset the manifest.");
  441. }
  442. else
  443. {
  444. m_assetImporterDocument->ClearScene();
  445. AZ_TracePrintf(ErrorWindow, "Manifest reset returned in '%s'",
  446. result.GetResult() == ProcessingResult::Failure ? "Failure" : "Ignored");
  447. }
  448. },
  449. [this]()
  450. {
  451. m_rootDisplay->HandleSceneWasReset(m_assetImporterDocument->GetScene());
  452. }, this);
  453. // reset the script rule from the .assetinfo file if it exists
  454. if (!m_scriptProcessorRuleFilename.empty())
  455. {
  456. m_scriptProcessorRuleFilename.clear();
  457. if (QFile::exists(m_assetImporterDocument->GetScene()->GetManifestFilename().c_str()))
  458. {
  459. QFile file(m_assetImporterDocument->GetScene()->GetManifestFilename().c_str());
  460. file.remove();
  461. }
  462. }
  463. SceneSettingsCard* card = CreateSceneSettingsCard(m_rootDisplay->GetHeaderFileName(), SceneSettingsCard::Layout::Resetting, SceneSettingsCard::State::Loading);
  464. card->SetAndStartProcessingHandler(asyncLoadHandler);
  465. }
  466. void AssetImporterWindow::OnAssignScript()
  467. {
  468. using namespace AZ::SceneAPI;
  469. using namespace AZ::SceneAPI::Events;
  470. using namespace AZ::SceneAPI::SceneUI;
  471. using namespace AZ::SceneAPI::Utilities;
  472. // use QFileDialog to select a Python script to embed into a scene manifest file
  473. QString pyFilename = QFileDialog::getOpenFileName(this,
  474. tr("Select scene builder Python script"),
  475. Path::GetEditingGameDataFolder().c_str(),
  476. tr("Python (*.py)"));
  477. if (pyFilename.isNull())
  478. {
  479. return;
  480. }
  481. // reset the script rule from the .assetinfo file if it exists
  482. if (!m_scriptProcessorRuleFilename.empty())
  483. {
  484. m_scriptProcessorRuleFilename.clear();
  485. if (QFile::exists(m_assetImporterDocument->GetScene()->GetManifestFilename().c_str()))
  486. {
  487. QFile file(m_assetImporterDocument->GetScene()->GetManifestFilename().c_str());
  488. file.remove();
  489. }
  490. }
  491. // find the path relative to the project folder
  492. pyFilename = Path::GetRelativePath(pyFilename, true);
  493. // create a script rule
  494. auto scriptProcessorRule = AZStd::make_shared<SceneData::ScriptProcessorRule>();
  495. scriptProcessorRule->SetScriptFilename(pyFilename.toUtf8().toStdString().c_str());
  496. // add the script rule to the manifest & save off the scene manifest
  497. Containers::SceneManifest sceneManifest;
  498. sceneManifest.AddEntry(scriptProcessorRule);
  499. if (sceneManifest.SaveToFile(m_assetImporterDocument->GetScene()->GetManifestFilename()))
  500. {
  501. OpenFile(m_assetImporterDocument->GetScene()->GetSourceFilename());
  502. }
  503. }
  504. void AssetImporterWindow::OnInspect()
  505. {
  506. AZ::SceneAPI::UI::OverlayWidgetButtonList buttons;
  507. AZ::SceneAPI::UI::OverlayWidgetButton closeButton;
  508. closeButton.m_text = "Close";
  509. closeButton.m_triggersPop = true;
  510. buttons.push_back(&closeButton);
  511. QLabel* label = new QLabel("Please close the inspector to continue editing the settings.");
  512. label->setWordWrap(true);
  513. label->setAlignment(Qt::AlignCenter);
  514. // make sure the inspector doesn't outlive the AssetImporterWindow, since we own the data it will be inspecting.
  515. auto* theInspectWidget = aznew AZ::SceneAPI::UI::SceneGraphInspectWidget(*m_assetImporterDocument->GetScene());
  516. QObject::connect(this, &QObject::destroyed, theInspectWidget, [theInspectWidget]() { theInspectWidget->window()->close(); } );
  517. m_overlay->PushLayer(label, theInspectWidget, "Scene Inspector", buttons);
  518. }
  519. void AssetImporterWindow::OverlayLayerAdded()
  520. {
  521. setCursor(Qt::WaitCursor);
  522. }
  523. void AssetImporterWindow::OverlayLayerRemoved()
  524. {
  525. if (m_isClosed && !m_overlay->IsAtRoot())
  526. {
  527. return;
  528. }
  529. setCursor(Qt::ArrowCursor);
  530. if (!m_assetImporterDocument->GetScene())
  531. {
  532. ui->m_initialBrowseContainer->show();
  533. m_rootDisplay->hide();
  534. }
  535. }
  536. void AssetImporterWindow::UpdateDefaultSceneDisplay()
  537. {
  538. UpdateSceneDisplay({});
  539. }
  540. void AssetImporterWindow::UpdateSceneDisplay(const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene> scene) const
  541. {
  542. QString sceneHeaderText;
  543. if(scene.get())
  544. {
  545. sceneHeaderText = QString::fromUtf8(scene->GetManifestFilename().c_str(), static_cast<int>(scene->GetManifestFilename().size()));
  546. }
  547. if (scene)
  548. {
  549. m_rootDisplay->SetSceneDisplay(sceneHeaderText, scene);
  550. }
  551. else
  552. {
  553. m_rootDisplay->SetSceneHeaderText(sceneHeaderText);
  554. }
  555. m_rootDisplay->SetPythonBuilderText(m_scriptProcessorRuleFilename.c_str());
  556. // UpdateSceneDisplay gets called both when the file is saved from this tool, as well as when it's modified externally.
  557. m_rootDisplay->UpdateTimeStamp(m_assetImporterDocument->GetScene()->GetManifestFilename().c_str(), gSettings.enableSceneInspector);
  558. }
  559. void AssetImporterWindow::HandleAssetLoadingCompleted()
  560. {
  561. if (!m_assetImporterDocument->GetScene())
  562. {
  563. AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Failed to load scene.");
  564. return;
  565. }
  566. m_fullSourcePath = m_assetImporterDocument->GetScene()->GetSourceFilename();
  567. using namespace AZ::SceneAPI;
  568. m_scriptProcessorRuleFilename.clear();
  569. // load up the source scene manifest file
  570. Containers::SceneManifest sceneManifest;
  571. if (sceneManifest.LoadFromFile(m_assetImporterDocument->GetScene()->GetManifestFilename()))
  572. {
  573. // check a Python script rule is in that source manifest
  574. auto view = Containers::MakeDerivedFilterView<DataTypes::IScriptProcessorRule>(sceneManifest.GetValueStorage());
  575. if (!view.empty())
  576. {
  577. // record the info about the rule in the class
  578. const auto scriptProcessorRule = &*view.begin();
  579. m_scriptProcessorRuleFilename = scriptProcessorRule->GetScriptFilename();
  580. }
  581. }
  582. UpdateSceneDisplay(m_assetImporterDocument->GetScene());
  583. // Once we've browsed to something successfully, we need to hide the initial browse button layer and
  584. // show the main area where all the actual work takes place
  585. ui->m_initialBrowseContainer->hide();
  586. m_rootDisplay->show();
  587. m_qtFileWatcher.addPath(m_fullSourcePath.c_str());
  588. m_qtFileWatcher.addPath(m_assetImporterDocument->GetScene()->GetManifestFilename().c_str());
  589. }
  590. void AssetImporterWindow::ReloadCurrentScene(bool warnUser)
  591. {
  592. if (m_isSaving)
  593. {
  594. return;
  595. }
  596. QString promptMessage(
  597. [this]()
  598. {
  599. if (m_rootDisplay->HasUnsavedChanges())
  600. {
  601. return tr("The file %1 has been changed outside of the scene settings tool. This tool will be reloaded and any unsaved "
  602. "changes will be lost. \n\n"
  603. "To prevent this from occuring in the future, do not modify the scene file or scene manifest outside of this "
  604. "tool while this tool has unsaved work.");
  605. }
  606. return tr("The file %1 has been changed outside of the scene settings tool. This tool will be reloaded.");
  607. }());
  608. // The scene system holds weak pointers to any previously loaded scenes,
  609. // and will return a previously cached scene on a requested load.
  610. // In this case, it's known the scene file is different than what's in memory, so make sure to flush
  611. // any cached scene info, so it is freshly reloaded from disk.
  612. m_assetImporterDocument->ClearScene();
  613. m_rootDisplay->GetManifestWidget()->ResetScene();
  614. // Verify nothing is left holding a shared pointer to the scene.
  615. namespace SceneEvents = AZ::SceneAPI::Events;
  616. bool foundSharedScene = true; // If the ebus fails, default to true to assume there's something sharing the scene still.
  617. SceneEvents::SceneSerializationBus::BroadcastResult(
  618. foundSharedScene, &SceneEvents::SceneSerializationBus::Events::IsSceneCached, m_fullSourcePath);
  619. // The scene is still cached, somewhere. Warn the user.
  620. if (foundSharedScene)
  621. {
  622. QString sharedSceneWarningMessage(tr("This scene file is still cached and will not reload correctly. The Editor should be shut down and "
  623. "re-launched to properly load the modified external data."));
  624. if (warnUser)
  625. {
  626. promptMessage = QString("%1\n\n%2").arg(promptMessage).arg(sharedSceneWarningMessage);
  627. }
  628. else
  629. {
  630. promptMessage = sharedSceneWarningMessage;
  631. }
  632. }
  633. if (warnUser || foundSharedScene)
  634. {
  635. QMessageBox::question(this, tr("Reloading Scene Settings"), promptMessage.arg(m_fullSourcePath.c_str()), QMessageBox::Ok);
  636. }
  637. OpenFileInternal(m_fullSourcePath);
  638. }
  639. void AssetImporterWindow::FileChanged([[maybe_unused]] QString path)
  640. {
  641. ReloadCurrentScene(true);
  642. }
  643. #include <moc_AssetImporterWindow.cpp>