PrefabIntegrationManager.cpp 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427
  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 <AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h>
  9. #include <AzCore/IO/FileIO.h>
  10. #include <AzCore/IO/SystemFile.h>
  11. #include <AzCore/StringFunc/StringFunc.h>
  12. #include <AzCore/Component/TransformBus.h>
  13. #include <AzCore/std/smart_ptr/make_shared.h>
  14. #include <AzCore/Asset/AssetManager.h>
  15. #include <AzFramework/API/ApplicationAPI.h>
  16. #include <AzFramework/Asset/AssetSystemBus.h>
  17. #include <AzFramework/StringFunc/StringFunc.h>
  18. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  19. #include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
  20. #include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
  21. #include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h>
  22. #include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
  23. #include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
  24. #include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
  25. #include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
  26. #include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
  27. #include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
  28. #include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h>
  29. #include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
  30. #include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
  31. #include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
  32. #include <AzQtComponents/Components/Widgets/CheckBox.h>
  33. #include <AzQtComponents/Components/FlowLayout.h>
  34. #include <AzQtComponents/Components/StyleManager.h>
  35. #include <AzQtComponents/Components/Widgets/CardHeader.h>
  36. #include <QApplication>
  37. #include <QCheckBox>
  38. #include <QDialog>
  39. #include <QDialogButtonBox>
  40. #include <QFileDialog>
  41. #include <QFileInfo>
  42. #include <QFrame>
  43. #include <QHBoxLayout>
  44. #include <QLabel>
  45. #include <QMainWindow>
  46. #include <QMenu>
  47. #include <QMessageBox>
  48. #include <QScrollArea>
  49. #include <QVBoxLayout>
  50. #include <QWidget>
  51. namespace AzToolsFramework
  52. {
  53. namespace Prefab
  54. {
  55. ContainerEntityInterface* PrefabIntegrationManager::s_containerEntityInterface = nullptr;
  56. EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr;
  57. PrefabFocusInterface* PrefabIntegrationManager::s_prefabFocusInterface = nullptr;
  58. PrefabLoaderInterface* PrefabIntegrationManager::s_prefabLoaderInterface = nullptr;
  59. PrefabPublicInterface* PrefabIntegrationManager::s_prefabPublicInterface = nullptr;
  60. PrefabSystemComponentInterface* PrefabIntegrationManager::s_prefabSystemComponentInterface = nullptr;
  61. const AZStd::string PrefabIntegrationManager::s_prefabFileExtension = ".prefab";
  62. static const char* const ClosePrefabDialog = "ClosePrefabDialog";
  63. static const char* const FooterSeparatorLine = "FooterSeparatorLine";
  64. static const char* const PrefabSavedMessageFrame = "PrefabSavedMessageFrame";
  65. static const char* const PrefabSavePreferenceHint = "PrefabSavePreferenceHint";
  66. static const char* const PrefabSaveWarningFrame = "PrefabSaveWarningFrame";
  67. static const char* const SaveDependentPrefabsCard = "SaveDependentPrefabsCard";
  68. static const char* const SavePrefabDialog = "SavePrefabDialog";
  69. static const char* const UnsavedPrefabFileName = "UnsavedPrefabFileName";
  70. void PrefabUserSettings::Reflect(AZ::ReflectContext* context)
  71. {
  72. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  73. if (serializeContext)
  74. {
  75. serializeContext->Class<PrefabUserSettings>()
  76. ->Version(1)
  77. ->Field("m_saveLocation", &PrefabUserSettings::m_saveLocation)
  78. ->Field("m_autoNumber", &PrefabUserSettings::m_autoNumber);
  79. }
  80. }
  81. PrefabIntegrationManager::PrefabIntegrationManager()
  82. {
  83. s_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get();
  84. if (s_containerEntityInterface == nullptr)
  85. {
  86. AZ_Assert(false, "Prefab - could not get ContainerEntityInterface on PrefabIntegrationManager construction.");
  87. return;
  88. }
  89. s_editorEntityUiInterface = AZ::Interface<EditorEntityUiInterface>::Get();
  90. if (s_editorEntityUiInterface == nullptr)
  91. {
  92. AZ_Assert(false, "Prefab - could not get EditorEntityUiInterface on PrefabIntegrationManager construction.");
  93. return;
  94. }
  95. s_prefabPublicInterface = AZ::Interface<PrefabPublicInterface>::Get();
  96. if (s_prefabPublicInterface == nullptr)
  97. {
  98. AZ_Assert(false, "Prefab - could not get PrefabPublicInterface on PrefabIntegrationManager construction.");
  99. return;
  100. }
  101. s_prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
  102. if (s_prefabLoaderInterface == nullptr)
  103. {
  104. AZ_Assert(false, "Prefab - could not get PrefabLoaderInterface on PrefabIntegrationManager construction.");
  105. return;
  106. }
  107. s_prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
  108. if (s_prefabSystemComponentInterface == nullptr)
  109. {
  110. AZ_Assert(false, "Prefab - could not get PrefabSystemComponentInterface on PrefabIntegrationManager construction.");
  111. return;
  112. }
  113. s_prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
  114. if (s_prefabFocusInterface == nullptr)
  115. {
  116. AZ_Assert(false, "Prefab - could not get PrefabFocusInterface on PrefabIntegrationManager construction.");
  117. return;
  118. }
  119. EditorContextMenuBus::Handler::BusConnect();
  120. PrefabInstanceContainerNotificationBus::Handler::BusConnect();
  121. AZ::Interface<PrefabIntegrationInterface>::Register(this);
  122. AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension);
  123. }
  124. PrefabIntegrationManager::~PrefabIntegrationManager()
  125. {
  126. AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect();
  127. AZ::Interface<PrefabIntegrationInterface>::Unregister(this);
  128. PrefabInstanceContainerNotificationBus::Handler::BusDisconnect();
  129. EditorContextMenuBus::Handler::BusDisconnect();
  130. }
  131. void PrefabIntegrationManager::Reflect(AZ::ReflectContext* context)
  132. {
  133. PrefabUserSettings::Reflect(context);
  134. }
  135. int PrefabIntegrationManager::GetMenuPosition() const
  136. {
  137. return aznumeric_cast<int>(EditorContextMenuOrdering::MIDDLE);
  138. }
  139. AZStd::string PrefabIntegrationManager::GetMenuIdentifier() const
  140. {
  141. return "Prefabs";
  142. }
  143. void PrefabIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, [[maybe_unused]] const AZ::Vector2& point, [[maybe_unused]] int flags)
  144. {
  145. AzToolsFramework::EntityIdList selectedEntities;
  146. AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
  147. selectedEntities, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
  148. bool prefabWipFeaturesEnabled = false;
  149. AzFramework::ApplicationRequests::Bus::BroadcastResult(
  150. prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
  151. // Create Prefab
  152. {
  153. if (!selectedEntities.empty())
  154. {
  155. // Hide if the only selected entity is the Level Container
  156. if (selectedEntities.size() > 1 || !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntities[0]))
  157. {
  158. bool layerInSelection = false;
  159. for (AZ::EntityId entityId : selectedEntities)
  160. {
  161. if (!layerInSelection)
  162. {
  163. AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
  164. layerInSelection, entityId,
  165. &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
  166. if (layerInSelection)
  167. {
  168. break;
  169. }
  170. }
  171. }
  172. // Layers can't be in prefabs.
  173. if (!layerInSelection)
  174. {
  175. QAction* createAction = menu->addAction(QObject::tr("Create Prefab..."));
  176. createAction->setToolTip(QObject::tr("Creates a prefab out of the currently selected entities."));
  177. QObject::connect(createAction, &QAction::triggered, createAction, [selectedEntities] {
  178. ContextMenu_CreatePrefab(selectedEntities);
  179. });
  180. }
  181. }
  182. }
  183. }
  184. // Instantiate Prefab
  185. {
  186. QAction* instantiateAction = menu->addAction(QObject::tr("Instantiate Prefab..."));
  187. instantiateAction->setToolTip(QObject::tr("Instantiates a prefab file in the scene."));
  188. QObject::connect(
  189. instantiateAction, &QAction::triggered, instantiateAction, [] { ContextMenu_InstantiatePrefab(); });
  190. }
  191. // Instantiate Procedural Prefab
  192. if (AZ::Prefab::ProceduralPrefabAsset::UseProceduralPrefabs())
  193. {
  194. QAction* action = menu->addAction(QObject::tr("Instantiate Procedural Prefab..."));
  195. action->setToolTip(QObject::tr("Instantiates a procedural prefab file in a prefab."));
  196. QObject::connect(
  197. action, &QAction::triggered, action, [] { ContextMenu_InstantiateProceduralPrefab(); });
  198. }
  199. menu->addSeparator();
  200. bool itemWasShown = false;
  201. // Edit/Save Prefab
  202. {
  203. if (selectedEntities.size() == 1)
  204. {
  205. AZ::EntityId selectedEntity = selectedEntities[0];
  206. if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity))
  207. {
  208. // Edit Prefab
  209. if (prefabWipFeaturesEnabled)
  210. {
  211. bool beingEdited = s_prefabFocusInterface->IsOwningPrefabBeingFocused(selectedEntity);
  212. if (!beingEdited)
  213. {
  214. QAction* editAction = menu->addAction(QObject::tr("Edit Prefab"));
  215. editAction->setToolTip(QObject::tr("Edit the prefab in focus mode."));
  216. QObject::connect(editAction, &QAction::triggered, editAction, [selectedEntity] {
  217. ContextMenu_EditPrefab(selectedEntity);
  218. });
  219. itemWasShown = true;
  220. }
  221. }
  222. // Save Prefab
  223. AZ::IO::Path prefabFilePath = s_prefabPublicInterface->GetOwningInstancePrefabPath(selectedEntity);
  224. auto dirtyOutcome = s_prefabPublicInterface->HasUnsavedChanges(prefabFilePath);
  225. if (dirtyOutcome.IsSuccess() && dirtyOutcome.GetValue() == true)
  226. {
  227. QAction* saveAction = menu->addAction(QObject::tr("Save Prefab to file"));
  228. saveAction->setToolTip(QObject::tr("Save the changes to the prefab to disk."));
  229. QObject::connect(saveAction, &QAction::triggered, saveAction, [selectedEntity] {
  230. ContextMenu_SavePrefab(selectedEntity);
  231. });
  232. itemWasShown = true;
  233. }
  234. }
  235. }
  236. }
  237. if (itemWasShown)
  238. {
  239. menu->addSeparator();
  240. }
  241. QAction* deleteAction = menu->addAction(QObject::tr("Delete"));
  242. QObject::connect(deleteAction, &QAction::triggered, deleteAction, [] { ContextMenu_DeleteSelected(); });
  243. if (selectedEntities.size() == 0 ||
  244. (selectedEntities.size() == 1 && s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntities[0])))
  245. {
  246. deleteAction->setDisabled(true);
  247. }
  248. // Detach Prefab
  249. if (selectedEntities.size() == 1)
  250. {
  251. AZ::EntityId selectedEntity = selectedEntities[0];
  252. if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity) &&
  253. !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntity))
  254. {
  255. QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab..."));
  256. QObject::connect(
  257. detachPrefabAction, &QAction::triggered, detachPrefabAction,
  258. [selectedEntity]
  259. {
  260. ContextMenu_DetachPrefab(selectedEntity);
  261. });
  262. }
  263. }
  264. }
  265. void PrefabIntegrationManager::HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const
  266. {
  267. auto instantiatePrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(sourceFilePath, parentId, position);
  268. if (!instantiatePrefabOutcome.IsSuccess())
  269. {
  270. WarnUserOfError("Prefab Instantiation Error", instantiatePrefabOutcome.GetError());
  271. }
  272. }
  273. void PrefabIntegrationManager::ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities)
  274. {
  275. // Save a reference to our currently active window since it will be
  276. // temporarily null after QFileDialogs close, which we need in order to
  277. // be able to parent our message dialogs properly
  278. QWidget* activeWindow = QApplication::activeWindow();
  279. const AZStd::string prefabFilesPath = "@projectroot@/Prefabs";
  280. // Remove Level entity if it's part of the list
  281. auto levelContainerIter =
  282. AZStd::find(selectedEntities.begin(), selectedEntities.end(), s_prefabPublicInterface->GetLevelInstanceContainerEntityId());
  283. if (levelContainerIter != selectedEntities.end())
  284. {
  285. selectedEntities.erase(levelContainerIter);
  286. }
  287. // Set default folder for prefabs
  288. AZ::IO::FileIOBase* fileIoBaseInstance = AZ::IO::FileIOBase::GetInstance();
  289. if (fileIoBaseInstance == nullptr)
  290. {
  291. AZ_Assert(false, "Prefab - could not find FileIoBaseInstance on CreatePrefab.");
  292. return;
  293. }
  294. if (!fileIoBaseInstance->Exists(prefabFilesPath.c_str()))
  295. {
  296. fileIoBaseInstance->CreatePath(prefabFilesPath.c_str());
  297. }
  298. char targetDirectory[AZ_MAX_PATH_LEN] = { 0 };
  299. fileIoBaseInstance->ResolvePath(prefabFilesPath.c_str(), targetDirectory, AZ_MAX_PATH_LEN);
  300. AzToolsFramework::EntityIdSet entitiesToIncludeInAsset(selectedEntities.begin(), selectedEntities.end());
  301. {
  302. AzToolsFramework::EntityIdSet allReferencedEntities;
  303. bool hasExternalReferences = false;
  304. GatherAllReferencedEntitiesAndCompare(entitiesToIncludeInAsset, allReferencedEntities, hasExternalReferences);
  305. if (hasExternalReferences)
  306. {
  307. bool useAllReferencedEntities = false;
  308. bool continueCreation = QueryAndPruneMissingExternalReferences(entitiesToIncludeInAsset, allReferencedEntities, useAllReferencedEntities);
  309. if (!continueCreation)
  310. {
  311. // User canceled the operation
  312. return;
  313. }
  314. if (useAllReferencedEntities)
  315. {
  316. entitiesToIncludeInAsset = allReferencedEntities;
  317. }
  318. }
  319. }
  320. // Determine prefab asset file name/path - come up with default suggested name, ask user
  321. AZStd::string prefabName;
  322. AZStd::string prefabFilePath;
  323. {
  324. AZStd::string suggestedName;
  325. AzToolsFramework::EntityIdList prefabRootEntities;
  326. {
  327. AZ::EntityId commonRoot;
  328. bool hasCommonRoot = false;
  329. AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(hasCommonRoot,
  330. &AzToolsFramework::ToolsApplicationRequests::FindCommonRoot, entitiesToIncludeInAsset, commonRoot, &prefabRootEntities);
  331. if (hasCommonRoot && commonRoot.IsValid() && entitiesToIncludeInAsset.find(commonRoot) != entitiesToIncludeInAsset.end())
  332. {
  333. prefabRootEntities.insert(prefabRootEntities.begin(), commonRoot);
  334. }
  335. }
  336. GenerateSuggestedFilenameFromEntities(prefabRootEntities, suggestedName);
  337. if (!QueryUserForPrefabSaveLocation(
  338. suggestedName, targetDirectory, AZ_CRC("PrefabUserSettings"), activeWindow, prefabName, prefabFilePath))
  339. {
  340. // User canceled prefab creation, or error prevented continuation.
  341. return;
  342. }
  343. }
  344. auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefabInDisk(selectedEntities, prefabFilePath.data());
  345. if (!createPrefabOutcome.IsSuccess())
  346. {
  347. WarnUserOfError("Prefab Creation Error", createPrefabOutcome.GetError());
  348. }
  349. }
  350. void PrefabIntegrationManager::ContextMenu_InstantiatePrefab()
  351. {
  352. AZStd::string prefabFilePath;
  353. bool hasUserSelectedValidSourceFile = QueryUserForPrefabFilePath(prefabFilePath);
  354. if (hasUserSelectedValidSourceFile)
  355. {
  356. AZ::EntityId parentId;
  357. AZ::Vector3 position = AZ::Vector3::CreateZero();
  358. EntityIdList selectedEntities;
  359. ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
  360. // if one entity is selected, instantiate prefab as its child and place it at same position as parent
  361. if (selectedEntities.size() == 1)
  362. {
  363. parentId = selectedEntities.front();
  364. AZ::TransformBus::EventResult(position, parentId, &AZ::TransformInterface::GetWorldTranslation);
  365. }
  366. // otherwise instantiate it at root level and center of viewport
  367. else
  368. {
  369. EditorRequestBus::BroadcastResult(position, &EditorRequestBus::Events::GetWorldPositionAtViewportCenter);
  370. }
  371. // Instantiating from context menu always puts the instance at the root level
  372. auto createPrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(prefabFilePath, parentId, position);
  373. if (!createPrefabOutcome.IsSuccess())
  374. {
  375. WarnUserOfError("Prefab Instantiation Error",createPrefabOutcome.GetError());
  376. }
  377. }
  378. }
  379. void PrefabIntegrationManager::ContextMenu_InstantiateProceduralPrefab()
  380. {
  381. AZStd::string prefabAssetPath;
  382. bool hasUserForProceduralPrefabAsset = QueryUserForProceduralPrefabAsset(prefabAssetPath);
  383. if (hasUserForProceduralPrefabAsset)
  384. {
  385. AZ::EntityId parentId;
  386. AZ::Vector3 position = AZ::Vector3::CreateZero();
  387. EntityIdList selectedEntities;
  388. ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
  389. if (selectedEntities.size() == 1)
  390. {
  391. parentId = selectedEntities.front();
  392. AZ::TransformBus::EventResult(position, parentId, &AZ::TransformInterface::GetWorldTranslation);
  393. }
  394. else
  395. {
  396. // otherwise return since it needs to be inside an authored prefab
  397. return;
  398. }
  399. // Instantiating from context menu always puts the instance at the root level
  400. auto createPrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(prefabAssetPath, parentId, position);
  401. if (!createPrefabOutcome.IsSuccess())
  402. {
  403. WarnUserOfError("Prefab Instantiation Error", createPrefabOutcome.GetError());
  404. }
  405. }
  406. }
  407. void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity)
  408. {
  409. s_prefabFocusInterface->FocusOnOwningPrefab(containerEntity);
  410. }
  411. void PrefabIntegrationManager::ContextMenu_SavePrefab(AZ::EntityId containerEntity)
  412. {
  413. auto prefabPath = s_prefabPublicInterface->GetOwningInstancePrefabPath(containerEntity);
  414. auto savePrefabOutcome = s_prefabPublicInterface->SavePrefab(prefabPath);
  415. if (!savePrefabOutcome.IsSuccess())
  416. {
  417. WarnUserOfError("Prefab Save Error", savePrefabOutcome.GetError());
  418. }
  419. }
  420. void PrefabIntegrationManager::ContextMenu_DeleteSelected()
  421. {
  422. AzToolsFramework::EntityIdList selectedEntityIds;
  423. AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
  424. selectedEntityIds, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
  425. PrefabOperationResult deleteSelectedResult =
  426. s_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(selectedEntityIds);
  427. if (!deleteSelectedResult.IsSuccess())
  428. {
  429. WarnUserOfError("Delete selected entities error", deleteSelectedResult.GetError());
  430. }
  431. }
  432. void PrefabIntegrationManager::ContextMenu_DetachPrefab(AZ::EntityId containerEntity)
  433. {
  434. PrefabOperationResult detachPrefabResult =
  435. s_prefabPublicInterface->DetachPrefab(containerEntity);
  436. if (!detachPrefabResult.IsSuccess())
  437. {
  438. WarnUserOfError("Detach Prefab error", detachPrefabResult.GetError());
  439. }
  440. }
  441. void PrefabIntegrationManager::GenerateSuggestedFilenameFromEntities(const EntityIdList& entityIds, AZStd::string& outName)
  442. {
  443. AZ_PROFILE_FUNCTION(AzToolsFramework);
  444. AZStd::string suggestedName;
  445. for (const AZ::EntityId& entityId : entityIds)
  446. {
  447. if (!AppendEntityToSuggestedFilename(suggestedName, entityId))
  448. {
  449. break;
  450. }
  451. }
  452. if (suggestedName.size() == 0 || AzFramework::StringFunc::Utf8::CheckNonAsciiChar(suggestedName))
  453. {
  454. suggestedName = "NewPrefab";
  455. }
  456. outName = suggestedName;
  457. }
  458. bool PrefabIntegrationManager::AppendEntityToSuggestedFilename(AZStd::string& filename, AZ::EntityId entityId)
  459. {
  460. // When naming a prefab after its entities, we stop appending additional names once we've reached this cutoff length
  461. size_t prefabNameCutoffLength = 32;
  462. AzToolsFramework::EntityIdSet usedNameEntities;
  463. if (usedNameEntities.find(entityId) == usedNameEntities.end())
  464. {
  465. AZ::Entity* entity = nullptr;
  466. AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
  467. if (entity)
  468. {
  469. AZStd::string entityNameFiltered = entity->GetName();
  470. // Convert spaces in entity names to underscores
  471. for (size_t i = 0; i < entityNameFiltered.size(); ++i)
  472. {
  473. char& character = entityNameFiltered.at(i);
  474. if (character == ' ')
  475. {
  476. character = '_';
  477. }
  478. }
  479. filename.append(entityNameFiltered);
  480. usedNameEntities.insert(entityId);
  481. if (filename.size() > prefabNameCutoffLength)
  482. {
  483. return false;
  484. }
  485. }
  486. }
  487. return true;
  488. }
  489. bool PrefabIntegrationManager::QueryUserForPrefabSaveLocation(
  490. const AZStd::string& suggestedName,
  491. const char* initialTargetDirectory,
  492. AZ::u32 prefabUserSettingsId,
  493. QWidget* activeWindow,
  494. AZStd::string& outPrefabName,
  495. AZStd::string& outPrefabFilePath
  496. )
  497. {
  498. AZStd::string saveAsInitialSuggestedDirectory;
  499. if (!GetPrefabSaveLocation(saveAsInitialSuggestedDirectory, prefabUserSettingsId))
  500. {
  501. saveAsInitialSuggestedDirectory = initialTargetDirectory;
  502. }
  503. AZStd::string saveAsInitialSuggestedFullPath;
  504. GenerateSuggestedPrefabPath(suggestedName, saveAsInitialSuggestedDirectory, saveAsInitialSuggestedFullPath);
  505. QString saveAs;
  506. AZStd::string targetPath;
  507. QFileInfo prefabSaveFileInfo;
  508. QString prefabName;
  509. while (true)
  510. {
  511. {
  512. AZ_PROFILE_FUNCTION(AzToolsFramework);
  513. saveAs = QFileDialog::getSaveFileName(nullptr, QString("Save As..."), saveAsInitialSuggestedFullPath.c_str(), QString("Prefabs (*.prefab)"));
  514. }
  515. prefabSaveFileInfo = saveAs;
  516. prefabName = prefabSaveFileInfo.baseName();
  517. if (saveAs.isEmpty())
  518. {
  519. return false;
  520. }
  521. targetPath = saveAs.toUtf8().constData();
  522. if (AzFramework::StringFunc::Utf8::CheckNonAsciiChar(targetPath))
  523. {
  524. WarnUserOfError(
  525. "Prefab Creation Failed.",
  526. "Unicode file name is not supported. \r\n"
  527. "Please use ASCII characters to name your prefab."
  528. );
  529. return false;
  530. }
  531. PrefabSaveResult saveResult = IsPrefabPathValidForAssets(activeWindow, saveAs, saveAsInitialSuggestedFullPath);
  532. if (saveResult == PrefabSaveResult::Cancel)
  533. {
  534. // The error was already reported if this failed.
  535. return false;
  536. }
  537. else if (saveResult == PrefabSaveResult::Continue)
  538. {
  539. // The prefab save name is valid, continue with the save attempt.
  540. break;
  541. }
  542. }
  543. // If the prefab already exists, notify the user and bail
  544. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  545. if (fileIO && fileIO->Exists(targetPath.c_str()))
  546. {
  547. const AZStd::string message = AZStd::string::format(
  548. "You are attempting to overwrite an existing prefab: \"%s\".\r\n\r\n"
  549. "This will damage instances or cascades of this prefab. \r\n\r\n"
  550. "Instead, either push entities/fields to the prefab, or save to a different location.",
  551. targetPath.c_str());
  552. WarnUserOfError("Prefab Already Exists", message);
  553. return false;
  554. }
  555. // We prevent users from creating a new prefab with the same relative path that's already
  556. // been used by an existing prefab in other places (e.g. Gems) because the AssetProcessor
  557. // generates asset ids based on relative paths. This is unnecessary once AssetProcessor
  558. // starts to generate UUID to every asset regardless of paths.
  559. {
  560. AZStd::string prefabRelativeName;
  561. bool relativePathFound;
  562. AssetSystemRequestBus::BroadcastResult(relativePathFound, &AssetSystemRequestBus::Events::GetRelativeProductPathFromFullSourceOrProductPath, targetPath, prefabRelativeName);
  563. AZ::Data::AssetId prefabAssetId;
  564. AZ::Data::AssetCatalogRequestBus::BroadcastResult(prefabAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, prefabRelativeName.c_str(), AZ::Data::s_invalidAssetType, false);
  565. if (prefabAssetId.IsValid())
  566. {
  567. const AZStd::string message = AZStd::string::format(
  568. "A prefab with the relative path \"%s\" already exists in the Asset Database. \r\n\r\n"
  569. "Overriding it will damage instances or cascades of this prefab. \r\n\r\n"
  570. "Instead, either push entities/fields to the prefab, or save to a different location.",
  571. prefabRelativeName.c_str());
  572. WarnUserOfError("Prefab Path Error", message);
  573. return false;
  574. }
  575. }
  576. AZStd::string saveDir(prefabSaveFileInfo.absoluteDir().absolutePath().toUtf8().constData());
  577. SetPrefabSaveLocation(saveDir, prefabUserSettingsId);
  578. outPrefabName = prefabName.toUtf8().constData();
  579. outPrefabFilePath = targetPath.c_str();
  580. return true;
  581. }
  582. bool PrefabIntegrationManager::QueryUserForPrefabFilePath(AZStd::string& outPrefabFilePath)
  583. {
  584. AssetSelectionModel selection;
  585. // Note, stringfilter will match every source file CONTAINING ".prefab".
  586. // If this causes issues, we will need to create a new filter class for regex matching.
  587. // We'll need to check if the file contents are actually a prefab later in the flow anyways,
  588. // so this should not be an issue.
  589. StringFilter* stringFilter = new StringFilter();
  590. stringFilter->SetName("Prefab");
  591. stringFilter->SetFilterString(".prefab");
  592. stringFilter->SetFilterPropagation(AssetBrowserEntryFilter::PropagateDirection::Down);
  593. auto stringFilterPtr = FilterConstType(stringFilter);
  594. EntryTypeFilter* sourceFilter = new EntryTypeFilter();
  595. sourceFilter->SetName("Source");
  596. sourceFilter->SetEntryType(AssetBrowserEntry::AssetEntryType::Source);
  597. sourceFilter->SetFilterPropagation(AssetBrowserEntryFilter::PropagateDirection::Down);
  598. auto sourceFilterPtr = FilterConstType(sourceFilter);
  599. CompositeFilter* compositeFilter = new CompositeFilter(CompositeFilter::LogicOperatorType::AND);
  600. compositeFilter->SetName("Prefab");
  601. compositeFilter->AddFilter(sourceFilterPtr);
  602. compositeFilter->AddFilter(stringFilterPtr);
  603. auto compositeFilterPtr = FilterConstType(compositeFilter);
  604. selection.SetDisplayFilter(compositeFilterPtr);
  605. selection.SetSelectionFilter(compositeFilterPtr);
  606. AssetBrowserComponentRequestBus::Broadcast(&AssetBrowserComponentRequests::PickAssets, selection, AzToolsFramework::GetActiveWindow());
  607. if (!selection.IsValid())
  608. {
  609. // User closed the dialog without selecting, just return.
  610. return false;
  611. }
  612. auto source = azrtti_cast<const SourceAssetBrowserEntry*>(selection.GetResult());
  613. if (source == nullptr)
  614. {
  615. AZ_Assert(false, "Prefab - Incorrect entry type selected during prefab instantiation. Expected source.");
  616. return false;
  617. }
  618. outPrefabFilePath = source->GetFullPath();
  619. return true;
  620. }
  621. bool PrefabIntegrationManager::QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath)
  622. {
  623. using namespace AzToolsFramework;
  624. auto selection = AssetBrowser::AssetSelectionModel::AssetTypeSelection(azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>());
  625. EditorRequests::Bus::Broadcast(&AzToolsFramework::EditorRequests::BrowseForAssets, selection);
  626. if (!selection.IsValid())
  627. {
  628. return false;
  629. }
  630. auto product = azrtti_cast<const ProductAssetBrowserEntry*>(selection.GetResult());
  631. if (product == nullptr)
  632. {
  633. return false;
  634. }
  635. outPrefabAssetPath = product->GetRelativePath();
  636. auto asset = AZ::Data::AssetManager::Instance().GetAsset(
  637. product->GetAssetId(),
  638. azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>(),
  639. AZ::Data::AssetLoadBehavior::Default);
  640. return asset.BlockUntilLoadComplete() != AZ::Data::AssetData::AssetStatus::Error;
  641. }
  642. void PrefabIntegrationManager::WarnUserOfError(AZStd::string_view title, AZStd::string_view message)
  643. {
  644. QWidget* activeWindow = QApplication::activeWindow();
  645. QMessageBox::warning(
  646. activeWindow,
  647. QString(title.data()),
  648. QString(message.data()),
  649. QMessageBox::Ok,
  650. QMessageBox::Ok
  651. );
  652. }
  653. PrefabIntegrationManager::PrefabSaveResult PrefabIntegrationManager::IsPrefabPathValidForAssets(QWidget* activeWindow,
  654. QString prefabPath, AZStd::string& retrySavePath)
  655. {
  656. bool assetSetFoldersRetrieved = false;
  657. AZStd::vector<AZStd::string> assetSafeFolders;
  658. AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
  659. assetSetFoldersRetrieved,
  660. &AzToolsFramework::AssetSystemRequestBus::Events::GetAssetSafeFolders,
  661. assetSafeFolders);
  662. if (!assetSetFoldersRetrieved)
  663. {
  664. // If the asset safe list couldn't be retrieved, don't block the user but warn them.
  665. AZ_Warning("Prefab", false, "Unable to verify that the prefab file to create is in a valid path.");
  666. }
  667. else
  668. {
  669. QString cleanSaveAs(QDir::cleanPath(prefabPath));
  670. bool isPathSafeForAssets = false;
  671. for (AZStd::string assetSafeFolder : assetSafeFolders)
  672. {
  673. QString cleanAssetSafeFolder(QDir::cleanPath(assetSafeFolder.c_str()));
  674. // Compare using clean paths so slash direction does not matter.
  675. // Note that this comparison is case sensitive because some file systems
  676. // Open 3D Engine supports are case sensitive.
  677. if (cleanSaveAs.startsWith(cleanAssetSafeFolder))
  678. {
  679. isPathSafeForAssets = true;
  680. break;
  681. }
  682. }
  683. if (!isPathSafeForAssets)
  684. {
  685. // Put an error in the console, so the log files have info about this error, or the user can look up the error after dismissing it.
  686. AZStd::string errorMessage = "You can only save prefabs to either your game project folder or the Gems folder. Update the location and try again.\n\n"
  687. "You can also review and update your save locations in the AssetProcessorPlatformConfig.ini file.";
  688. AZ_Error("Prefab", false, errorMessage.c_str());
  689. // Display a pop-up, the logs are easy to miss. This will make sure a user who encounters this error immediately knows their prefab save has failed.
  690. QMessageBox msgBox(activeWindow);
  691. msgBox.setIcon(QMessageBox::Icon::Warning);
  692. msgBox.setTextFormat(Qt::RichText);
  693. msgBox.setWindowTitle(QObject::tr("Invalid save location"));
  694. msgBox.setText(QObject::tr(errorMessage.c_str()));
  695. msgBox.setStandardButtons(QMessageBox::Cancel | QMessageBox::Retry);
  696. msgBox.setDefaultButton(QMessageBox::Retry);
  697. const int response = msgBox.exec();
  698. switch (response)
  699. {
  700. case QMessageBox::Retry:
  701. // If the user wants to retry, they probably want to save to a valid location,
  702. // so set the suggested save path to a known valid location.
  703. if (assetSafeFolders.size() > 0)
  704. {
  705. retrySavePath = assetSafeFolders[0];
  706. }
  707. return PrefabSaveResult::Retry;
  708. case QMessageBox::Cancel:
  709. default:
  710. return PrefabSaveResult::Cancel;
  711. }
  712. }
  713. }
  714. // Valid prefab save location, continue with the save attempt.
  715. return PrefabSaveResult::Continue;
  716. }
  717. void PrefabIntegrationManager::GenerateSuggestedPrefabPath(const AZStd::string& prefabName, const AZStd::string& targetDirectory, AZStd::string& suggestedFullPath)
  718. {
  719. // Generate full suggested path from prefabName - if given NewPrefab as prefabName,
  720. // NewPrefab_001.prefab would be tried, and if that already existed we would suggest
  721. // the first unused number value (NewPrefab_002.prefab etc.)
  722. AZStd::string normalizedTargetDirectory = targetDirectory;
  723. AZ::StringFunc::Path::Normalize(normalizedTargetDirectory);
  724. // Convert spaces in entity names to underscores
  725. AZStd::string prefabNameFiltered = prefabName;
  726. AZ::StringFunc::Replace(prefabNameFiltered, ' ', '_');
  727. auto settings = AZ::UserSettings::CreateFind<PrefabUserSettings>(AZ_CRC("PrefabUserSettings"), AZ::UserSettings::CT_LOCAL);
  728. if (settings->m_autoNumber)
  729. {
  730. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  731. const AZ::u32 maxPrefabNumber = 1000;
  732. for (AZ::u32 prefabNumber = 1; prefabNumber < maxPrefabNumber; ++prefabNumber)
  733. {
  734. AZStd::string possiblePath;
  735. AZ::StringFunc::Path::Join(
  736. normalizedTargetDirectory.c_str(),
  737. AZStd::string::format("%s_%3.3u%s", prefabNameFiltered.c_str(), prefabNumber, s_prefabFileExtension.c_str()).c_str(),
  738. possiblePath
  739. );
  740. if (!fileIO || !fileIO->Exists(possiblePath.c_str()))
  741. {
  742. suggestedFullPath = possiblePath;
  743. break;
  744. }
  745. }
  746. }
  747. else
  748. {
  749. // use the entity name as the file name regardless of it already existing, the OS will ask the user to overwrite the file in that case.
  750. AZ::StringFunc::Path::Join(
  751. normalizedTargetDirectory.c_str(),
  752. AZStd::string::format("%s%s", prefabNameFiltered.c_str(), s_prefabFileExtension.c_str()).c_str(),
  753. suggestedFullPath
  754. );
  755. }
  756. }
  757. void PrefabIntegrationManager::SetPrefabSaveLocation(const AZStd::string& path, AZ::u32 settingsId)
  758. {
  759. auto settings = AZ::UserSettings::CreateFind<PrefabUserSettings>(settingsId, AZ::UserSettings::CT_LOCAL);
  760. settings->m_saveLocation = path;
  761. }
  762. bool PrefabIntegrationManager::GetPrefabSaveLocation(AZStd::string& path, AZ::u32 settingsId)
  763. {
  764. auto settings = AZ::UserSettings::Find<PrefabUserSettings>(settingsId, AZ::UserSettings::CT_LOCAL);
  765. if (settings)
  766. {
  767. path = settings->m_saveLocation;
  768. return true;
  769. }
  770. return false;
  771. }
  772. void PrefabIntegrationManager::GatherAllReferencedEntitiesAndCompare(const EntityIdSet& entities,
  773. EntityIdSet& entitiesAndReferencedEntities, bool& hasExternalReferences)
  774. {
  775. AZ::SerializeContext* serializeContext;
  776. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  777. entitiesAndReferencedEntities.clear();
  778. entitiesAndReferencedEntities = entities;
  779. GatherAllReferencedEntities(entitiesAndReferencedEntities, *serializeContext);
  780. // NOTE: that AZStd::unordered_set equality operator only returns true if they are in the same order
  781. // (which appears to deviate from the standard). So we have to do the comparison ourselves.
  782. hasExternalReferences = (entitiesAndReferencedEntities.size() > entities.size());
  783. if (!hasExternalReferences)
  784. {
  785. for (AZ::EntityId id : entitiesAndReferencedEntities)
  786. {
  787. if (entities.find(id) == entities.end())
  788. {
  789. hasExternalReferences = true;
  790. break;
  791. }
  792. }
  793. }
  794. }
  795. AZ::u32 PrefabIntegrationManager::GetSliceFlags(const AZ::Edit::ElementData* editData, const AZ::Edit::ClassData* classData)
  796. {
  797. AZ::u32 sliceFlags = 0;
  798. if (editData)
  799. {
  800. AZ::Edit::Attribute* slicePushAttribute = editData->FindAttribute(AZ::Edit::Attributes::SliceFlags);
  801. if (slicePushAttribute)
  802. {
  803. AZ::u32 elementSliceFlags = 0;
  804. AzToolsFramework::PropertyAttributeReader reader(nullptr, slicePushAttribute);
  805. reader.Read<AZ::u32>(elementSliceFlags);
  806. sliceFlags |= elementSliceFlags;
  807. }
  808. }
  809. const AZ::Edit::ElementData* classEditData = classData ? classData->FindElementData(AZ::Edit::ClassElements::EditorData) : nullptr;
  810. if (classEditData)
  811. {
  812. AZ::Edit::Attribute* slicePushAttribute = classEditData->FindAttribute(AZ::Edit::Attributes::SliceFlags);
  813. if (slicePushAttribute)
  814. {
  815. AZ::u32 classSliceFlags = 0;
  816. AzToolsFramework::PropertyAttributeReader reader(nullptr, slicePushAttribute);
  817. reader.Read<AZ::u32>(classSliceFlags);
  818. sliceFlags |= classSliceFlags;
  819. }
  820. }
  821. return sliceFlags;
  822. }
  823. void PrefabIntegrationManager::GatherAllReferencedEntities(EntityIdSet& entitiesWithReferences, AZ::SerializeContext& serializeContext)
  824. {
  825. AZ_PROFILE_FUNCTION(AzToolsFramework);
  826. AZStd::vector<AZ::EntityId> floodQueue;
  827. floodQueue.reserve(entitiesWithReferences.size());
  828. // Seed with all provided entity Ids
  829. for (const AZ::EntityId& entityId : entitiesWithReferences)
  830. {
  831. floodQueue.push_back(entityId);
  832. }
  833. // Flood-fill via outgoing entity references and gather all unique visited entities.
  834. while (!floodQueue.empty())
  835. {
  836. const AZ::EntityId id = floodQueue.back();
  837. floodQueue.pop_back();
  838. AZ::Entity* entity = nullptr;
  839. AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, id);
  840. if (entity)
  841. {
  842. AZStd::vector<const AZ::SerializeContext::ClassData*> parentStack;
  843. parentStack.reserve(30);
  844. auto beginCB = [&](void* ptr, const AZ::SerializeContext::ClassData* classData, const AZ::SerializeContext::ClassElement* elementData) -> bool
  845. {
  846. parentStack.push_back(classData);
  847. AZ::u32 sliceFlags = GetSliceFlags(elementData ? elementData->m_editData : nullptr, classData ? classData->m_editData : nullptr);
  848. // Skip any class or element marked as don't gather references
  849. if (0 != (sliceFlags & AZ::Edit::SliceFlags::DontGatherReference))
  850. {
  851. return false;
  852. }
  853. if (classData->m_typeId == AZ::SerializeTypeInfo<AZ::EntityId>::GetUuid())
  854. {
  855. if (!parentStack.empty() && parentStack.back()->m_typeId == AZ::SerializeTypeInfo<AZ::Entity>::GetUuid())
  856. {
  857. // Ignore the entity's actual Id field. We're only looking for references.
  858. }
  859. else
  860. {
  861. AZ::EntityId* entityIdPtr = (elementData->m_flags & AZ::SerializeContext::ClassElement::FLG_POINTER) ?
  862. *reinterpret_cast<AZ::EntityId**>(ptr) : reinterpret_cast<AZ::EntityId*>(ptr);
  863. if (entityIdPtr)
  864. {
  865. const AZ::EntityId id = *entityIdPtr;
  866. if (id.IsValid())
  867. {
  868. if (entitiesWithReferences.insert(id).second)
  869. {
  870. floodQueue.push_back(id);
  871. }
  872. }
  873. }
  874. }
  875. }
  876. // Keep recursing.
  877. return true;
  878. };
  879. auto endCB = [&]() -> bool
  880. {
  881. parentStack.pop_back();
  882. return true;
  883. };
  884. AZ::SerializeContext::EnumerateInstanceCallContext callContext(
  885. beginCB,
  886. endCB,
  887. &serializeContext,
  888. AZ::SerializeContext::ENUM_ACCESS_FOR_READ,
  889. nullptr
  890. );
  891. serializeContext.EnumerateInstanceConst(
  892. &callContext,
  893. entity,
  894. azrtti_typeid<AZ::Entity>(),
  895. nullptr,
  896. nullptr
  897. );
  898. }
  899. }
  900. }
  901. bool PrefabIntegrationManager::QueryAndPruneMissingExternalReferences(EntityIdSet& entities, EntityIdSet& selectedAndReferencedEntities,
  902. bool& useReferencedEntities, bool defaultMoveExternalRefs)
  903. {
  904. AZ_PROFILE_FUNCTION(AzToolsFramework);
  905. useReferencedEntities = false;
  906. AZStd::string includedEntities;
  907. AZStd::string referencedEntities;
  908. AzToolsFramework::EntityIdList missingEntityIds;
  909. for (const AZ::EntityId& id : selectedAndReferencedEntities)
  910. {
  911. AZ::Entity* entity = nullptr;
  912. AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, id);
  913. if (entity)
  914. {
  915. if (entities.find(id) != entities.end())
  916. {
  917. includedEntities.append(" ");
  918. includedEntities.append(entity->GetName());
  919. includedEntities.append("\r\n");
  920. }
  921. else
  922. {
  923. referencedEntities.append(" ");
  924. referencedEntities.append(entity->GetName());
  925. referencedEntities.append("\r\n");
  926. }
  927. }
  928. else
  929. {
  930. missingEntityIds.push_back(id);
  931. }
  932. }
  933. if (!referencedEntities.empty())
  934. {
  935. if (!defaultMoveExternalRefs)
  936. {
  937. AZ_PROFILE_FUNCTION(AzToolsFramework);
  938. const AZStd::string message = AZStd::string::format(
  939. "Entity references may not be valid if the entity IDs change or if the entities do not exist when the prefab is instantiated.\r\n\r\nSelected Entities\n%s\nReferenced Entities\n%s\n",
  940. includedEntities.c_str(),
  941. referencedEntities.c_str());
  942. QMessageBox msgBox(AzToolsFramework::GetActiveWindow());
  943. msgBox.setWindowTitle("External Entity References");
  944. msgBox.setText("The prefab contains references to external entities that are not selected.");
  945. msgBox.setInformativeText("You can move the referenced entities into this prefab or retain the external references.");
  946. QAbstractButton* moveButton = (QAbstractButton*)msgBox.addButton("Move", QMessageBox::YesRole);
  947. QAbstractButton* retainButton = (QAbstractButton*)msgBox.addButton("Retain", QMessageBox::NoRole);
  948. msgBox.setStandardButtons(QMessageBox::Cancel);
  949. msgBox.setDefaultButton(QMessageBox::Yes);
  950. msgBox.setDetailedText(message.c_str());
  951. msgBox.exec();
  952. if (msgBox.clickedButton() == moveButton)
  953. {
  954. useReferencedEntities = true;
  955. }
  956. else if (msgBox.clickedButton() != retainButton)
  957. {
  958. return false;
  959. }
  960. }
  961. else
  962. {
  963. useReferencedEntities = true;
  964. }
  965. }
  966. for (const AZ::EntityId& missingEntityId : missingEntityIds)
  967. {
  968. entities.erase(missingEntityId);
  969. selectedAndReferencedEntities.erase(missingEntityId);
  970. }
  971. return true;
  972. }
  973. void PrefabIntegrationManager::OnPrefabComponentActivate(AZ::EntityId entityId)
  974. {
  975. // Register entity to appropriate UI Handler for UI overrides
  976. if (s_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId))
  977. {
  978. s_editorEntityUiInterface->RegisterEntity(entityId, m_levelRootUiHandler.GetHandlerId());
  979. }
  980. else
  981. {
  982. s_editorEntityUiInterface->RegisterEntity(entityId, m_prefabUiHandler.GetHandlerId());
  983. bool prefabWipFeaturesEnabled = false;
  984. AzFramework::ApplicationRequests::Bus::BroadcastResult(
  985. prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
  986. if (prefabWipFeaturesEnabled)
  987. {
  988. // Register entity as a container
  989. s_containerEntityInterface->RegisterEntityAsContainer(entityId);
  990. }
  991. }
  992. }
  993. void PrefabIntegrationManager::OnPrefabComponentDeactivate(AZ::EntityId entityId)
  994. {
  995. bool prefabWipFeaturesEnabled = false;
  996. AzFramework::ApplicationRequests::Bus::BroadcastResult(
  997. prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
  998. if (prefabWipFeaturesEnabled && !s_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId))
  999. {
  1000. // Unregister entity as a container
  1001. s_containerEntityInterface->UnregisterEntityAsContainer(entityId);
  1002. }
  1003. // Unregister entity from UI Handler
  1004. s_editorEntityUiInterface->UnregisterEntity(entityId);
  1005. }
  1006. AZ::EntityId PrefabIntegrationManager::CreateNewEntityAtPosition(const AZ::Vector3& position, AZ::EntityId parentId)
  1007. {
  1008. Prefab::PrefabPublicInterface* prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get();
  1009. auto createResult = prefabPublicInterface->CreateEntity(parentId, position);
  1010. if (createResult.IsSuccess())
  1011. {
  1012. return createResult.GetValue();
  1013. }
  1014. else
  1015. {
  1016. WarnUserOfError("Entity Creation Error", createResult.GetError());
  1017. return AZ::EntityId();
  1018. }
  1019. }
  1020. int PrefabIntegrationManager::ExecuteClosePrefabDialog(TemplateId templateId)
  1021. {
  1022. if (s_prefabSystemComponentInterface->AreDirtyTemplatesPresent(templateId))
  1023. {
  1024. auto prefabSaveSelectionDialog = ConstructClosePrefabDialog(templateId);
  1025. int prefabSaveSelection = prefabSaveSelectionDialog->exec();
  1026. if (prefabSaveSelection == QDialog::Accepted)
  1027. {
  1028. SavePrefabsInDialog(prefabSaveSelectionDialog.get());
  1029. }
  1030. return prefabSaveSelection;
  1031. }
  1032. return QDialogButtonBox::DestructiveRole;
  1033. }
  1034. void PrefabIntegrationManager::ExecuteSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference)
  1035. {
  1036. auto prefabTemplate = s_prefabSystemComponentInterface->FindTemplate(templateId);
  1037. AZ::IO::Path prefabTemplatePath = prefabTemplate->get().GetFilePath();
  1038. if (s_prefabSystemComponentInterface->IsTemplateDirty(templateId))
  1039. {
  1040. if (s_prefabLoaderInterface->SaveTemplate(templateId) == false)
  1041. {
  1042. AZ_Error("Prefab", false, "Template '%s' could not be saved successfully.", prefabTemplatePath.c_str());
  1043. return;
  1044. }
  1045. }
  1046. if (s_prefabSystemComponentInterface->AreDirtyTemplatesPresent(templateId))
  1047. {
  1048. if (useSaveAllPrefabsPreference)
  1049. {
  1050. SaveAllPrefabsPreference saveAllPrefabsPreference = s_prefabLoaderInterface->GetSaveAllPrefabsPreference();
  1051. if (saveAllPrefabsPreference == SaveAllPrefabsPreference::SaveAll)
  1052. {
  1053. s_prefabSystemComponentInterface->SaveAllDirtyTemplates(templateId);
  1054. return;
  1055. }
  1056. else if (saveAllPrefabsPreference == SaveAllPrefabsPreference::SaveNone)
  1057. {
  1058. return;
  1059. }
  1060. }
  1061. AZStd::unique_ptr<QDialog> savePrefabDialog = ConstructSavePrefabDialog(templateId, useSaveAllPrefabsPreference);
  1062. if (savePrefabDialog)
  1063. {
  1064. int prefabSaveSelection = savePrefabDialog->exec();
  1065. if (prefabSaveSelection == QDialog::Accepted)
  1066. {
  1067. SavePrefabsInDialog(savePrefabDialog.get());
  1068. }
  1069. }
  1070. }
  1071. }
  1072. void PrefabIntegrationManager::SavePrefabsInDialog(QDialog* unsavedPrefabsDialog)
  1073. {
  1074. QList<QLabel*> unsavedPrefabFileLabels = unsavedPrefabsDialog->findChildren<QLabel*>(UnsavedPrefabFileName);
  1075. if (unsavedPrefabFileLabels.size() > 0)
  1076. {
  1077. for (const QLabel* unsavedPrefabFileLabel : unsavedPrefabFileLabels)
  1078. {
  1079. AZStd::string unsavedPrefabFileName = unsavedPrefabFileLabel->property("FilePath").toString().toUtf8().data();
  1080. AzToolsFramework::Prefab::TemplateId unsavedPrefabTemplateId =
  1081. s_prefabSystemComponentInterface->GetTemplateIdFromFilePath(unsavedPrefabFileName.data());
  1082. [[maybe_unused]] bool isTemplateSavedSuccessfully = s_prefabLoaderInterface->SaveTemplate(unsavedPrefabTemplateId);
  1083. AZ_Error("Prefab", isTemplateSavedSuccessfully, "Prefab '%s' could not be saved successfully.", unsavedPrefabFileName.c_str());
  1084. }
  1085. }
  1086. }
  1087. AZStd::unique_ptr<QDialog> PrefabIntegrationManager::ConstructSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference)
  1088. {
  1089. AZStd::unique_ptr<QDialog> savePrefabDialog = AZStd::make_unique<QDialog>(AzToolsFramework::GetActiveWindow());
  1090. savePrefabDialog->setWindowTitle("Unsaved files detected");
  1091. // Main Content section begins.
  1092. savePrefabDialog->setObjectName(SavePrefabDialog);
  1093. QBoxLayout* contentLayout = new QVBoxLayout(savePrefabDialog.get());
  1094. QFrame* prefabSavedMessageFrame = new QFrame(savePrefabDialog.get());
  1095. QHBoxLayout* prefabSavedMessageLayout = new QHBoxLayout(savePrefabDialog.get());
  1096. prefabSavedMessageFrame->setObjectName(PrefabSavedMessageFrame);
  1097. prefabSavedMessageFrame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
  1098. // Add a checkMark icon next to the level entities saved message.
  1099. QPixmap checkMarkIcon(QString(":/Notifications/checkmark.svg"));
  1100. QLabel* prefabSavedSuccessfullyIconContainer = new QLabel(savePrefabDialog.get());
  1101. prefabSavedSuccessfullyIconContainer->setPixmap(checkMarkIcon);
  1102. prefabSavedSuccessfullyIconContainer->setFixedWidth(checkMarkIcon.width());
  1103. // Add a message that level entities are saved successfully.
  1104. auto prefabTemplate = s_prefabSystemComponentInterface->FindTemplate(templateId);
  1105. AZ::IO::Path prefabTemplatePath = prefabTemplate->get().GetFilePath();
  1106. QLabel* prefabSavedSuccessfullyLabel = new QLabel(
  1107. QString("Prefab '<b>%1</b>' has been saved. Do you want to save the below dependent prefabs too?").arg(prefabTemplatePath.c_str()),
  1108. savePrefabDialog.get());
  1109. prefabSavedMessageLayout->addWidget(prefabSavedSuccessfullyIconContainer);
  1110. prefabSavedMessageLayout->addWidget(prefabSavedSuccessfullyLabel);
  1111. prefabSavedMessageFrame->setLayout(prefabSavedMessageLayout);
  1112. contentLayout->addWidget(prefabSavedMessageFrame);
  1113. AZStd::unique_ptr<AzQtComponents::Card> unsavedPrefabsContainer = ConstructUnsavedPrefabsCard(templateId);
  1114. contentLayout->addWidget(unsavedPrefabsContainer.release());
  1115. contentLayout->addStretch();
  1116. // Footer section begins.
  1117. QHBoxLayout* footerLayout = new QHBoxLayout(savePrefabDialog.get());
  1118. if (useSaveAllPrefabsPreference)
  1119. {
  1120. QFrame* footerSeparatorLine = new QFrame(savePrefabDialog.get());
  1121. footerSeparatorLine->setObjectName(FooterSeparatorLine);
  1122. footerSeparatorLine->setFrameShape(QFrame::HLine);
  1123. contentLayout->addWidget(footerSeparatorLine);
  1124. QLabel* prefabSavePreferenceHint = new QLabel(
  1125. "<u>You can prevent this window from showing in the future by updating your global save preferences.</u>",
  1126. savePrefabDialog.get());
  1127. prefabSavePreferenceHint->setToolTip(
  1128. "Go to 'Edit > Editor Settings > Global Preferences... > Global save preferences' to update your preference");
  1129. prefabSavePreferenceHint->setObjectName(PrefabSavePreferenceHint);
  1130. footerLayout->addWidget(prefabSavePreferenceHint);
  1131. }
  1132. QDialogButtonBox* prefabSaveConfirmationButtons =
  1133. new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::No, savePrefabDialog.get());
  1134. footerLayout->addWidget(prefabSaveConfirmationButtons);
  1135. contentLayout->addLayout(footerLayout);
  1136. connect(prefabSaveConfirmationButtons, &QDialogButtonBox::accepted, savePrefabDialog.get(), &QDialog::accept);
  1137. connect(prefabSaveConfirmationButtons, &QDialogButtonBox::rejected, savePrefabDialog.get(), &QDialog::reject);
  1138. AzQtComponents::StyleManager::setStyleSheet(savePrefabDialog->parentWidget(), QStringLiteral("style:Editor.qss"));
  1139. savePrefabDialog->setLayout(contentLayout);
  1140. return AZStd::move(savePrefabDialog);
  1141. }
  1142. AZStd::shared_ptr<QDialog> PrefabIntegrationManager::ConstructClosePrefabDialog(TemplateId templateId)
  1143. {
  1144. AZStd::shared_ptr<QDialog> closePrefabDialog = AZStd::make_shared<QDialog>(AzToolsFramework::GetActiveWindow());
  1145. closePrefabDialog->setWindowTitle("Unsaved files detected");
  1146. AZStd::weak_ptr<QDialog> closePrefabDialogWeakPtr(closePrefabDialog);
  1147. closePrefabDialog->setObjectName(ClosePrefabDialog);
  1148. // Main Content section begins.
  1149. QVBoxLayout* contentLayout = new QVBoxLayout(closePrefabDialog.get());
  1150. QFrame* prefabSaveWarningFrame = new QFrame(closePrefabDialog.get());
  1151. QHBoxLayout* levelEntitiesSaveQuestionLayout = new QHBoxLayout(closePrefabDialog.get());
  1152. prefabSaveWarningFrame->setObjectName(PrefabSaveWarningFrame);
  1153. // Add a warning icon next to save prefab warning.
  1154. prefabSaveWarningFrame->setLayout(levelEntitiesSaveQuestionLayout);
  1155. QPixmap warningIcon(QString(":/Notifications/warning.svg"));
  1156. QLabel* warningIconContainer = new QLabel(closePrefabDialog.get());
  1157. warningIconContainer->setPixmap(warningIcon);
  1158. warningIconContainer->setFixedWidth(warningIcon.width());
  1159. levelEntitiesSaveQuestionLayout->addWidget(warningIconContainer);
  1160. // Ask user if they want to save entities in level.
  1161. QLabel* prefabSaveQuestionLabel = new QLabel("Do you want to save the below unsaved prefabs?", closePrefabDialog.get());
  1162. levelEntitiesSaveQuestionLayout->addWidget(prefabSaveQuestionLabel);
  1163. contentLayout->addWidget(prefabSaveWarningFrame);
  1164. auto templateToSave = s_prefabSystemComponentInterface->FindTemplate(templateId);
  1165. AZ::IO::Path templateToSaveFilePath = templateToSave->get().GetFilePath();
  1166. AZStd::unique_ptr<AzQtComponents::Card> unsavedPrefabsCard = ConstructUnsavedPrefabsCard(templateId);
  1167. contentLayout->addWidget(unsavedPrefabsCard.release());
  1168. contentLayout->addStretch();
  1169. QHBoxLayout* footerLayout = new QHBoxLayout(closePrefabDialog.get());
  1170. QDialogButtonBox* prefabSaveConfirmationButtons = new QDialogButtonBox(
  1171. QDialogButtonBox::Save | QDialogButtonBox::Discard | QDialogButtonBox::Cancel, closePrefabDialog.get());
  1172. footerLayout->addWidget(prefabSaveConfirmationButtons);
  1173. contentLayout->addLayout(footerLayout);
  1174. QObject::connect(prefabSaveConfirmationButtons, &QDialogButtonBox::accepted, closePrefabDialog.get(), &QDialog::accept);
  1175. QObject::connect(prefabSaveConfirmationButtons, &QDialogButtonBox::rejected, closePrefabDialog.get(), &QDialog::reject);
  1176. QObject::connect(
  1177. prefabSaveConfirmationButtons, &QDialogButtonBox::clicked, closePrefabDialog.get(),
  1178. [closePrefabDialogWeakPtr, prefabSaveConfirmationButtons](QAbstractButton* button)
  1179. {
  1180. int prefabSaveSelection = prefabSaveConfirmationButtons->buttonRole(button);
  1181. closePrefabDialogWeakPtr.lock()->done(prefabSaveSelection);
  1182. });
  1183. AzQtComponents::StyleManager::setStyleSheet(closePrefabDialog.get(), QStringLiteral("style:Editor.qss"));
  1184. closePrefabDialog->setLayout(contentLayout);
  1185. return closePrefabDialog;
  1186. }
  1187. AZStd::unique_ptr<AzQtComponents::Card> PrefabIntegrationManager::ConstructUnsavedPrefabsCard(TemplateId templateId)
  1188. {
  1189. FlowLayout* unsavedPrefabsLayout = new FlowLayout(AzToolsFramework::GetActiveWindow());
  1190. AZStd::set<AZ::IO::PathView> dirtyTemplatePaths = s_prefabSystemComponentInterface->GetDirtyTemplatePaths(templateId);
  1191. for (AZ::IO::PathView dirtyTemplatePath : dirtyTemplatePaths)
  1192. {
  1193. QLabel* prefabNameLabel =
  1194. new QLabel(QString("<u>%1</u>").arg(dirtyTemplatePath.Filename().Native().data()), AzToolsFramework::GetActiveWindow());
  1195. prefabNameLabel->setObjectName(UnsavedPrefabFileName);
  1196. prefabNameLabel->setWordWrap(true);
  1197. prefabNameLabel->setToolTip(dirtyTemplatePath.Native().data());
  1198. prefabNameLabel->setProperty("FilePath", dirtyTemplatePath.Native().data());
  1199. unsavedPrefabsLayout->addWidget(prefabNameLabel);
  1200. }
  1201. AZStd::unique_ptr<AzQtComponents::Card> unsavedPrefabsContainer = AZStd::make_unique<AzQtComponents::Card>(AzToolsFramework::GetActiveWindow());
  1202. unsavedPrefabsContainer->setObjectName(SaveDependentPrefabsCard);
  1203. unsavedPrefabsContainer->setTitle("Unsaved Prefabs");
  1204. unsavedPrefabsContainer->header()->setHasContextMenu(false);
  1205. unsavedPrefabsContainer->header()->setIcon(QIcon(QStringLiteral(":/Entity/prefab_edit.svg")));
  1206. QFrame* unsavedPrefabsFrame = new QFrame(unsavedPrefabsContainer.get());
  1207. unsavedPrefabsFrame->setLayout(unsavedPrefabsLayout);
  1208. QScrollArea* unsavedPrefabsScrollArea = new QScrollArea(unsavedPrefabsContainer.get());
  1209. unsavedPrefabsScrollArea->setWidget(unsavedPrefabsFrame);
  1210. unsavedPrefabsScrollArea->setWidgetResizable(true);
  1211. unsavedPrefabsContainer->setContentWidget(unsavedPrefabsScrollArea);
  1212. return AZStd::move(unsavedPrefabsContainer);
  1213. }
  1214. }
  1215. }