You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1802 lines
73 KiB
C++
1802 lines
73 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
|
|
#include <AzCore/RTTI/BehaviorContext.h>
|
|
#include <AzCore/Asset/AssetSerializer.h>
|
|
#include <AzCore/Serialization/EditContext.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzCore/IO/FileIO.h>
|
|
#include <AzCore/Debug/Profiler.h>
|
|
|
|
#include <AzFramework/TargetManagement/TargetManagementComponent.h>
|
|
#include <AzFramework/StringFunc/StringFunc.h>
|
|
|
|
#include <AzToolsFramework/AzToolsFrameworkModule.h>
|
|
#include <AzToolsFramework/Undo/UndoSystem.h>
|
|
#include <AzToolsFramework/Application/ToolsApplication.h>
|
|
#include <AzToolsFramework/Commands/EntityStateCommand.h>
|
|
#include <AzToolsFramework/Commands/SelectionCommand.h>
|
|
#include <AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.h>
|
|
#include <AzToolsFramework/ToolsComponents/AzToolsFrameworkConfigurationSystemComponent.h>
|
|
#include <AzToolsFramework/ToolsComponents/EditorAssetMimeDataContainer.h>
|
|
#include <AzToolsFramework/ToolsComponents/ComponentAssetMimeDataContainer.h>
|
|
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
|
#include <AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h>
|
|
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
|
|
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
|
|
#include <AzToolsFramework/FocusMode/FocusModeSystemComponent.h>
|
|
#include <AzToolsFramework/Slice/SliceMetadataEntityContextComponent.h>
|
|
#include <AzToolsFramework/Prefab/PrefabSystemComponent.h>
|
|
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h>
|
|
#include <AzToolsFramework/Component/EditorComponentAPIComponent.h>
|
|
#include <AzToolsFramework/Component/EditorLevelComponentAPIComponent.h>
|
|
#include <AzToolsFramework/Entity/EditorEntityActionComponent.h>
|
|
#include <AzToolsFramework/Entity/EditorEntityFixupComponent.h>
|
|
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
|
|
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
|
|
#include <AzToolsFramework/Archive/ArchiveComponent.h>
|
|
#include <AzToolsFramework/Asset/AssetSystemComponent.h>
|
|
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
|
|
#include <AzToolsFramework/ComponentMode/ComponentModeDelegate.h>
|
|
#include <AzToolsFramework/AssetBundle/AssetBundleComponent.h>
|
|
#include <AzToolsFramework/UI/UICore/QTreeViewStateSaver.hxx>
|
|
#include <AzToolsFramework/UI/UICore/QWidgetSavedState.h>
|
|
#include <AzToolsFramework/UI/UICore/ProgressShield.hxx>
|
|
#include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
|
|
#include <AzToolsFramework/Slice/SliceUtilities.h>
|
|
#include <AzToolsFramework/ToolsComponents/EditorInspectorComponent.h>
|
|
#include <AzToolsFramework/API/ComponentEntityObjectBus.h>
|
|
#include <AzToolsFramework/API/EditorCameraBus.h>
|
|
#include <AzToolsFramework/API/ViewPaneOptions.h>
|
|
#include <AzToolsFramework/Entity/EditorEntitySearchComponent.h>
|
|
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
|
|
#include <AzToolsFramework/Entity/EditorEntityModelComponent.h>
|
|
#include <AzToolsFramework/Slice/SliceDependencyBrowserComponent.h>
|
|
#include <AzToolsFramework/Slice/SliceRequestComponent.h>
|
|
#include <AzToolsFramework/UI/LegacyFramework/MainWindowSavedState.h>
|
|
#include <AzToolsFramework/AssetEditor/AssetEditorWidget.h>
|
|
#include <AzToolsFramework/Viewport/ViewportMessages.h>
|
|
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemComponent.h>
|
|
#include <AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h>
|
|
#include <AzToolsFramework/AssetEditor/AssetEditorBus.h>
|
|
#include <AzToolsFramework/Render/EditorIntersectorComponent.h>
|
|
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiSystemComponent.h>
|
|
#include <AzToolsFramework/Undo/UndoCacheInterface.h>
|
|
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
|
|
#include <Entity/EntityUtilityComponent.h>
|
|
|
|
#include <QtWidgets/QMessageBox>
|
|
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QFileInfo::d_ptr': class 'QSharedDataPointer<QFileInfoPrivate>' needs to have dll-interface to be used by clients of class 'QFileInfo'
|
|
#include <QDir>
|
|
AZ_POP_DISABLE_WARNING
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QMap>
|
|
|
|
// Not possible to use AZCore's operator new overrides until we address the overall problems
|
|
// with allocators, or more likely convert AzToolsFramework to a DLL and restrict overloading to
|
|
// within the DLL. Since this is currently linked as a lib, overriding new and delete would require
|
|
// us to hunt down all static allocs in Sandbox and Qt code.
|
|
// For now we'll stick with the CRT new/delete in tools.
|
|
//#include <AzCore/Memory/NewAndDelete.inl>
|
|
|
|
namespace AzToolsFramework
|
|
{
|
|
namespace Internal
|
|
{
|
|
template<typename IdContainerType>
|
|
void DeleteEntities(const IdContainerType& entityIds)
|
|
{
|
|
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
|
|
|
if (entityIds.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
UndoSystem::URSequencePoint* currentUndoBatch = nullptr;
|
|
ToolsApplicationRequests::Bus::BroadcastResult(
|
|
currentUndoBatch, &ToolsApplicationRequests::Bus::Events::GetCurrentUndoBatch);
|
|
|
|
bool createdUndo = false;
|
|
if (!currentUndoBatch)
|
|
{
|
|
createdUndo = true;
|
|
ToolsApplicationRequests::Bus::BroadcastResult(
|
|
currentUndoBatch, &ToolsApplicationRequests::Bus::Events::BeginUndoBatch, "Delete Selected");
|
|
AZ_Assert(currentUndoBatch, "Failed to create new undo batch.");
|
|
}
|
|
|
|
UndoSystem::UndoStack* undoStack = nullptr;
|
|
PreemptiveUndoCache* preemptiveUndoCache = nullptr;
|
|
EBUS_EVENT_RESULT(undoStack, ToolsApplicationRequests::Bus, GetUndoStack);
|
|
EBUS_EVENT_RESULT(preemptiveUndoCache, ToolsApplicationRequests::Bus, GetUndoCache);
|
|
AZ_Assert(undoStack, "Failed to retrieve undo stack.");
|
|
AZ_Assert(preemptiveUndoCache, "Failed to retrieve preemptive undo cache.");
|
|
if (undoStack && preemptiveUndoCache)
|
|
{
|
|
// In order to undo DeleteSelected, we have to create a selection command which selects the current selection
|
|
// and then add the deletion as children.
|
|
// Commands always execute themselves first and then their children (when going forwards)
|
|
// and do the opposite when going backwards.
|
|
AzToolsFramework::EntityIdList selection;
|
|
EBUS_EVENT_RESULT(selection, ToolsApplicationRequests::Bus, GetSelectedEntities);
|
|
AzToolsFramework::SelectionCommand* selCommand = aznew AzToolsFramework::SelectionCommand(selection, "Delete Entities");
|
|
|
|
// We insert a "deselect all" command before we delete the entities. This ensures the delete operations aren't changing
|
|
// selection state, which triggers expensive UI updates. By deselecting up front, we are able to do those expensive
|
|
// UI updates once at the start instead of once for each entity.
|
|
{
|
|
AzToolsFramework::EntityIdList deselection;
|
|
AzToolsFramework::SelectionCommand* deselectAllCommand = aznew AzToolsFramework::SelectionCommand(deselection, "Deselect Entities");
|
|
deselectAllCommand->SetParent(selCommand);
|
|
}
|
|
|
|
{
|
|
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:UndoCaptureAndPurgeEntities");
|
|
for (const auto& entityId : entityIds)
|
|
{
|
|
AZ::Entity* entity = nullptr;
|
|
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, entityId);
|
|
|
|
if (entity)
|
|
{
|
|
EntityDeleteCommand* command = aznew EntityDeleteCommand(static_cast<AZ::u64>(entityId));
|
|
command->Capture(entity);
|
|
command->SetParent(selCommand);
|
|
}
|
|
|
|
preemptiveUndoCache->PurgeCache(entityId);
|
|
}
|
|
}
|
|
|
|
selCommand->SetParent(currentUndoBatch);
|
|
{
|
|
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:RunRedo");
|
|
selCommand->RunRedo();
|
|
}
|
|
}
|
|
|
|
if (createdUndo)
|
|
{
|
|
EBUS_EVENT(ToolsApplicationRequests::Bus, EndUndoBatch);
|
|
}
|
|
}
|
|
|
|
struct ToolsApplicationNotificationBusHandler final
|
|
: public ToolsApplicationNotificationBus::Handler
|
|
, public AZ::BehaviorEBusHandler
|
|
{
|
|
AZ_EBUS_BEHAVIOR_BINDER(ToolsApplicationNotificationBusHandler, "{7EB67956-FF86-461A-91E2-7B08279CFACF}", AZ::SystemAllocator,
|
|
EntityRegistered, EntityDeregistered, AfterEntitySelectionChanged);
|
|
|
|
void EntityRegistered(AZ::EntityId entityId) override
|
|
{
|
|
Call(FN_EntityRegistered, entityId);
|
|
}
|
|
|
|
void EntityDeregistered(AZ::EntityId entityId) override
|
|
{
|
|
Call(FN_EntityDeregistered, entityId);
|
|
}
|
|
|
|
void AfterEntitySelectionChanged(const EntityIdList& newlySelectedEntities, const EntityIdList& newlyDeselectedEntities) override
|
|
{
|
|
Call(FN_AfterEntitySelectionChanged, newlySelectedEntities, newlyDeselectedEntities);
|
|
}
|
|
};
|
|
|
|
struct ViewPaneCallbackBusHandler final
|
|
: public ViewPaneCallbackBus::Handler
|
|
, public AZ::BehaviorEBusHandler
|
|
{
|
|
AZ_EBUS_BEHAVIOR_BINDER(ViewPaneCallbackBusHandler, "{3C4C85D4-0F0F-4ECA-A635-142F5DF68CF8}", AZ::SystemAllocator,
|
|
CreateViewPaneWidget);
|
|
|
|
AZ::u64 CreateViewPaneWidget() override
|
|
{
|
|
AZ::u64 widgetWindowId;
|
|
CallResult(widgetWindowId, FN_CreateViewPaneWidget);
|
|
|
|
return widgetWindowId;
|
|
}
|
|
};
|
|
|
|
struct EditorEventsBusHandler final
|
|
: public EditorEventsBus::Handler
|
|
, public AZ::BehaviorEBusHandler
|
|
{
|
|
AZ_EBUS_BEHAVIOR_BINDER(EditorEventsBusHandler, "{352F80BB-469A-40B6-B322-FE57AB51E4DA}", AZ::SystemAllocator,
|
|
NotifyRegisterViews);
|
|
|
|
void NotifyRegisterViews() override
|
|
{
|
|
Call(FN_NotifyRegisterViews);
|
|
}
|
|
};
|
|
|
|
} // Internal
|
|
|
|
ToolsApplication::ToolsApplication(int* argc, char*** argv)
|
|
: AzFramework::Application(argc, argv)
|
|
, m_selectionBounds(AZ::Aabb())
|
|
, m_undoStack(nullptr)
|
|
, m_currentBatchUndo(nullptr)
|
|
, m_isDuringUndoRedo(false)
|
|
, m_isInIsolationMode(false)
|
|
{
|
|
ToolsApplicationRequests::Bus::Handler::BusConnect();
|
|
|
|
m_undoCache.RegisterToUndoCacheInterface();
|
|
}
|
|
|
|
ToolsApplication::~ToolsApplication()
|
|
{
|
|
ToolsApplicationRequests::Bus::Handler::BusDisconnect();
|
|
Stop();
|
|
}
|
|
|
|
void ToolsApplication::CreateStaticModules(AZStd::vector<AZ::Module*>& outModules)
|
|
{
|
|
AzFramework::Application::CreateStaticModules(outModules);
|
|
|
|
outModules.emplace_back(aznew AzToolsFrameworkModule);
|
|
}
|
|
|
|
AZ::ComponentTypeList ToolsApplication::GetRequiredSystemComponents() const
|
|
{
|
|
AZ::ComponentTypeList components = AzFramework::Application::GetRequiredSystemComponents();
|
|
|
|
components.insert(components.end(), {
|
|
azrtti_typeid<EditorEntityContextComponent>(),
|
|
azrtti_typeid<Components::EditorEntityUiSystemComponent>(),
|
|
azrtti_typeid<FocusModeSystemComponent>(),
|
|
azrtti_typeid<ContainerEntitySystemComponent>(),
|
|
azrtti_typeid<SliceMetadataEntityContextComponent>(),
|
|
azrtti_typeid<Prefab::PrefabSystemComponent>(),
|
|
azrtti_typeid<EditorEntityFixupComponent>(),
|
|
azrtti_typeid<Components::EditorComponentAPIComponent>(),
|
|
azrtti_typeid<Components::EditorLevelComponentAPIComponent>(),
|
|
azrtti_typeid<Components::EditorEntityActionComponent>(),
|
|
azrtti_typeid<Components::PropertyManagerComponent>(),
|
|
azrtti_typeid<AzFramework::TargetManagementComponent>(),
|
|
azrtti_typeid<AssetSystem::AssetSystemComponent>(),
|
|
azrtti_typeid<PerforceComponent>(),
|
|
azrtti_typeid<AzToolsFramework::AssetBundleComponent>(),
|
|
azrtti_typeid<AzToolsFramework::ArchiveComponent>(),
|
|
azrtti_typeid<AzToolsFramework::SliceDependencyBrowserComponent>(),
|
|
azrtti_typeid<AzToolsFramework::AzToolsFrameworkConfigurationSystemComponent>(),
|
|
azrtti_typeid<Components::EditorEntityModelComponent>(),
|
|
azrtti_typeid<AzToolsFramework::EditorInteractionSystemComponent>(),
|
|
azrtti_typeid<Components::EditorEntitySearchComponent>(),
|
|
azrtti_typeid<Components::EditorIntersectorComponent>(),
|
|
azrtti_typeid<AzToolsFramework::SliceRequestComponent>(),
|
|
azrtti_typeid<AzToolsFramework::EntityUtilityComponent>()
|
|
});
|
|
|
|
return components;
|
|
}
|
|
|
|
void ToolsApplication::Start(const Descriptor& descriptor, const StartupParameters& startupParameters/* = StartupParameters()*/)
|
|
{
|
|
Application::Start(descriptor, startupParameters);
|
|
if (!m_isStarted)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_editorEntityManager.Start();
|
|
|
|
m_editorEntityAPI = AZ::Interface<EditorEntityAPI>::Get();
|
|
AZ_Assert(m_editorEntityAPI, "ToolsApplication - Could not retrieve instance of EditorEntityAPI");
|
|
}
|
|
|
|
void ToolsApplication::StartCommon(AZ::Entity* systemEntity)
|
|
{
|
|
Application::StartCommon(systemEntity);
|
|
|
|
m_undoStack = new UndoSystem::UndoStack(10, nullptr);
|
|
}
|
|
|
|
void ToolsApplication::Stop()
|
|
{
|
|
if (m_isStarted)
|
|
{
|
|
FlushUndo();
|
|
|
|
auto undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
|
|
if (undoCacheInterface)
|
|
{
|
|
undoCacheInterface->Clear();
|
|
}
|
|
|
|
delete m_undoStack;
|
|
m_undoStack = nullptr;
|
|
|
|
// Release any memory used by ToolsApplication before Application::Stop() destroys the allocators.
|
|
m_selectedEntities.set_capacity(0);
|
|
m_highlightedEntities.set_capacity(0);
|
|
m_dirtyEntities = {};
|
|
|
|
bool isPrefabSystemEnabled = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
|
isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
|
|
|
|
if (isPrefabSystemEnabled)
|
|
{
|
|
// This resets the editor context thereby asking the systems that own the entities to destroy them. By doing this, we are
|
|
// duly giving the authority to delete the entities to the systems that owns them, rather than leaving it to the
|
|
// ComponentApplication to do the cleanup.
|
|
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
|
|
&AzToolsFramework::EditorEntityContextRequestBus::Events::ResetEditorContext);
|
|
}
|
|
|
|
GetSerializeContext()->DestroyEditContext();
|
|
|
|
Application::Stop();
|
|
}
|
|
}
|
|
|
|
void ToolsApplication::CreateReflectionManager()
|
|
{
|
|
Application::CreateReflectionManager();
|
|
|
|
GetSerializeContext()->CreateEditContext();
|
|
}
|
|
|
|
void ToolsApplication::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
Application::Reflect(context);
|
|
|
|
Components::EditorComponentBase::Reflect(context);
|
|
EditorAssetMimeDataContainer::Reflect(context);
|
|
ComponentAssetMimeDataContainer::Reflect(context);
|
|
|
|
AssetBrowser::AssetBrowserEntry::Reflect(context);
|
|
AssetBrowser::RootAssetBrowserEntry::Reflect(context);
|
|
AssetBrowser::FolderAssetBrowserEntry::Reflect(context);
|
|
AssetBrowser::SourceAssetBrowserEntry::Reflect(context);
|
|
AssetBrowser::ProductAssetBrowserEntry::Reflect(context);
|
|
|
|
AssetEditor::AssetEditorWindowSettings::Reflect(context);
|
|
AssetEditor::AssetEditorWidgetUserSettings::Reflect(context);
|
|
|
|
QTreeViewWithStateSaving::Reflect(context);
|
|
QWidgetSavedState::Reflect(context);
|
|
SliceUtilities::Reflect(context);
|
|
Prefab::PrefabIntegrationManager::Reflect(context);
|
|
|
|
ComponentModeFramework::ComponentModeDelegate::Reflect(context);
|
|
|
|
ViewportInteraction::ViewportInteractionReflect(context);
|
|
|
|
Camera::EditorCameraRequests::Reflect(context);
|
|
AzToolsFramework::EditorTransformComponentSelectionRequests::Reflect(context);
|
|
|
|
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
|
{
|
|
behaviorContext->EBus<ToolsApplicationRequestBus>("ToolsApplicationRequestBus")
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
|
->Attribute(AZ::Script::Attributes::Category, "Editor")
|
|
->Attribute(AZ::Script::Attributes::Module, "editor")
|
|
->Event("CreateNewEntity", &ToolsApplicationRequests::CreateNewEntity)
|
|
->Event("CreateNewEntityAtPosition", &ToolsApplicationRequests::CreateNewEntityAtPosition)
|
|
->Event("GetCurrentLevelEntityId", &ToolsApplicationRequests::GetCurrentLevelEntityId)
|
|
->Event("GetExistingEntity", &ToolsApplicationRequests::GetExistingEntity)
|
|
->Event("EntityExists", &ToolsApplicationRequests::EntityExists)
|
|
->Event("DeleteEntityById", &ToolsApplicationRequests::DeleteEntityById)
|
|
->Event("DeleteEntities", &ToolsApplicationRequests::DeleteEntities)
|
|
->Event("DeleteEntityAndAllDescendants", &ToolsApplicationRequests::DeleteEntityAndAllDescendants)
|
|
->Event("DeleteEntitiesAndAllDescendants", &ToolsApplicationRequests::DeleteEntitiesAndAllDescendants)
|
|
->Event("SetSelectedEntities", &ToolsApplicationRequests::SetSelectedEntities)
|
|
->Event("GetSelectedEntities", &ToolsApplicationRequests::GetSelectedEntities)
|
|
->Event("MarkEntitiesSelected", &ToolsApplicationRequests::MarkEntitiesSelected)
|
|
->Event("MarkEntitiesDeselected", &ToolsApplicationRequests::MarkEntitiesDeselected)
|
|
->Event("MarkEntitySelected", &ToolsApplicationRequests::MarkEntitySelected)
|
|
->Event("MarkEntityDeselected", &ToolsApplicationRequests::MarkEntityDeselected)
|
|
->Event("IsSelected", &ToolsApplicationRequests::IsSelected)
|
|
->Event("AreAnyEntitiesSelected", &ToolsApplicationRequests::AreAnyEntitiesSelected)
|
|
->Event("GetSelectedEntitiesCount", &ToolsApplicationRequests::GetSelectedEntitiesCount)
|
|
;
|
|
|
|
behaviorContext->EBus<ToolsApplicationNotificationBus>("ToolsApplicationNotificationBus")
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
|
->Attribute(AZ::Script::Attributes::Category, "Editor")
|
|
->Attribute(AZ::Script::Attributes::Module, "editor")
|
|
->Handler<Internal::ToolsApplicationNotificationBusHandler>()
|
|
->Event("EntityRegistered", &ToolsApplicationEvents::EntityRegistered)
|
|
->Event("EntityDeregistered", &ToolsApplicationEvents::EntityDeregistered)
|
|
->Event("AfterEntitySelectionChanged", &ToolsApplicationEvents::AfterEntitySelectionChanged)
|
|
;
|
|
|
|
behaviorContext->Class<ViewPaneOptions>()
|
|
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
|
->Attribute(AZ::Script::Attributes::Module, "editor")
|
|
->Property("isDeletable", BehaviorValueProperty(&ViewPaneOptions::isDeletable))
|
|
->Property("showInMenu", BehaviorValueProperty(&ViewPaneOptions::showInMenu))
|
|
->Property("canHaveMultipleInstances", BehaviorValueProperty(&ViewPaneOptions::canHaveMultipleInstances))
|
|
->Property("isPreview", BehaviorValueProperty(&ViewPaneOptions::isPreview))
|
|
->Property("showOnToolsToolbar", BehaviorValueProperty(&ViewPaneOptions::showOnToolsToolbar))
|
|
->Property("toolbarIcon", BehaviorValueProperty(&ViewPaneOptions::toolbarIcon))
|
|
;
|
|
|
|
behaviorContext->EBus<EditorRequestBus>("EditorRequestBus")
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
|
->Attribute(AZ::Script::Attributes::Category, "Editor")
|
|
->Attribute(AZ::Script::Attributes::Module, "editor")
|
|
->Event("RegisterCustomViewPane", &EditorRequests::RegisterCustomViewPane)
|
|
->Event("UnregisterViewPane", &EditorRequests::UnregisterViewPane)
|
|
->Event("GetComponentTypeEditorIcon", &EditorRequests::GetComponentTypeEditorIcon)
|
|
;
|
|
|
|
behaviorContext->EBus<EditorEventsBus>("EditorEventBus")
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
|
->Attribute(AZ::Script::Attributes::Category, "Editor")
|
|
->Attribute(AZ::Script::Attributes::Module, "editor")
|
|
->Handler<Internal::EditorEventsBusHandler>()
|
|
->Event("NotifyRegisterViews", &EditorEvents::NotifyRegisterViews)
|
|
;
|
|
|
|
behaviorContext->EBus<ViewPaneCallbackBus>("ViewPaneCallbackBus")
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
|
->Attribute(AZ::Script::Attributes::Category, "Editor")
|
|
->Attribute(AZ::Script::Attributes::Module, "editor")
|
|
->Handler<Internal::ViewPaneCallbackBusHandler>()
|
|
->Event("CreateViewPaneWidget", &ViewPaneCallbacks::CreateViewPaneWidget)
|
|
;
|
|
|
|
}
|
|
}
|
|
|
|
bool ToolsApplication::AddEntity(AZ::Entity* entity)
|
|
{
|
|
const bool result = AzFramework::Application::AddEntity(entity);
|
|
|
|
if (result)
|
|
{
|
|
EBUS_EVENT(ToolsApplicationEvents::Bus, EntityRegistered, entity->GetId());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool ToolsApplication::RemoveEntity(AZ::Entity* entity)
|
|
{
|
|
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
|
|
|
auto undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
|
|
if (undoCacheInterface)
|
|
{
|
|
undoCacheInterface->PurgeCache(entity->GetId());
|
|
}
|
|
|
|
MarkEntityDeselected(entity->GetId());
|
|
SetEntityHighlighted(entity->GetId(), false);
|
|
|
|
EBUS_EVENT(ToolsApplicationEvents::Bus, EntityDeregistered, entity->GetId());
|
|
|
|
{
|
|
AZ_PROFILE_SCOPE(AzToolsFramework, "ToolsApplication::RemoveEntity:CallApplicationRemoveEntity");
|
|
if (AzFramework::Application::RemoveEntity(entity))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ToolsApplication::PreExportEntity(AZ::Entity& source, AZ::Entity& target)
|
|
{
|
|
AZ::SerializeContext* serializeContext = nullptr;
|
|
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
|
|
AZ_Assert(serializeContext, "No serialization context found");
|
|
|
|
const AZ::Entity::ComponentArrayType& editorComponents = source.GetComponents();
|
|
|
|
// Propagate components from source entity to target, in preparation for exporting target.
|
|
for (AZ::Component* component : editorComponents)
|
|
{
|
|
Components::EditorComponentBase* asEditorComponent =
|
|
azrtti_cast<Components::EditorComponentBase*>(component);
|
|
|
|
if (asEditorComponent)
|
|
{
|
|
const size_t oldComponentCount = target.GetComponents().size();
|
|
|
|
asEditorComponent->BuildGameEntity(&target);
|
|
|
|
// Applying same Id persistence trick as we do in the slice compiler. Once we're off levels,
|
|
// this code all goes away and everything runs through the slice compiler.
|
|
if (target.GetComponents().size() > oldComponentCount)
|
|
{
|
|
AZ::Component* newComponent = target.GetComponents().back();
|
|
AZ_Error("Export", asEditorComponent->GetId() != AZ::InvalidComponentId, "For entity \"%s\", component \"%s\" doesn't have a valid component id",
|
|
source.GetName().c_str(), asEditorComponent->RTTI_GetType().ToString<AZStd::string>().c_str());
|
|
newComponent->SetId(asEditorComponent->GetId());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The component is already runtime-ready. I.e. it is not an editor component.
|
|
// Clone the component and add it to the export entity
|
|
AZ::Component* clonedComponent = serializeContext->CloneObject(component);
|
|
target.AddComponent(clonedComponent);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ToolsApplication::PostExportEntity(AZ::Entity& /*source*/, AZ::Entity& /*target*/)
|
|
{
|
|
}
|
|
|
|
const char* ToolsApplication::GetCurrentConfigurationName() const
|
|
{
|
|
#if defined(_RELEASE)
|
|
return "ReleaseEditor";
|
|
#elif defined(_DEBUG)
|
|
return "DebugEditor";
|
|
#else
|
|
return "ProfileEditor";
|
|
#endif
|
|
}
|
|
|
|
void ToolsApplication::SetSettingsRegistrySpecializations(AZ::SettingsRegistryInterface::Specializations& specializations)
|
|
{
|
|
AzFramework::Application::SetSettingsRegistrySpecializations(specializations);
|
|
specializations.Append("tools");
|
|
}
|
|
|
|
void ToolsApplication::MarkEntitySelected(AZ::EntityId entityId)
|
|
{
|
|
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
|
AZ_Assert(entityId.IsValid(), "Invalid entity Id being marked as selected.");
|
|
|
|
EntityIdList::iterator foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
|
|
if (foundIter == m_selectedEntities.end())
|
|
{
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);
|
|
|
|
m_selectedEntities.push_back(entityId);
|
|
EntitySelectionEvents::Bus::Event(entityId, &EntitySelectionEvents::OnSelected);
|
|
|
|
const AzToolsFramework::EntityIdList newlySelectedEntities = { entityId };
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntitySelectionChanged, newlySelectedEntities, AzToolsFramework::EntityIdList());
|
|
}
|
|
}
|
|
|
|
void ToolsApplication::MarkEntitiesSelected(const EntityIdList& entitiesToSelect)
|
|
{
|
|
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
|
|
|
EntityIdList entitiesSelected;
|
|
entitiesSelected.reserve(entitiesToSelect.size());
|
|
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);
|
|
|
|
for (AZ::EntityId entityId : entitiesToSelect)
|
|
{
|
|
AZ_Assert(entityId.IsValid(), "Invalid entity Id being marked as selected.");
|
|
EntityIdList::iterator foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
|
|
if (foundIter == m_selectedEntities.end())
|
|
{
|
|
m_selectedEntities.push_back(entityId);
|
|
EntitySelectionEvents::Bus::Event(entityId, &EntitySelectionEvents::OnSelected);
|
|
entitiesSelected.push_back(entityId);
|
|
}
|
|
}
|
|
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntitySelectionChanged, entitiesSelected, EntityIdList());
|
|
}
|
|
|
|
void ToolsApplication::MarkEntityDeselected(AZ::EntityId entityId)
|
|
{
|
|
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
|
auto foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
|
|
if (foundIter != m_selectedEntities.end())
|
|
{
|
|
AZ_PROFILE_SCOPE(AzToolsFramework, "ToolsApplication::MarkEntityDeselected:Deselect");
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);
|
|
m_selectedEntities.erase(foundIter);
|
|
|
|
EntitySelectionEvents::Bus::Event(entityId, &EntitySelectionEvents::OnDeselected);
|
|
AzToolsFramework::EntityIdList newlyDeselectedEntities = { entityId };
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntitySelectionChanged, AzToolsFramework::EntityIdList(), newlyDeselectedEntities);
|
|
}
|
|
}
|
|
|
|
void ToolsApplication::MarkEntitiesDeselected(const EntityIdList& entitiesToDeselect)
|
|
{
|
|
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
|
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);
|
|
|
|
EntityIdSet entitySetToDeselect(entitiesToDeselect.begin(), entitiesToDeselect.end());
|
|
|
|
for (auto selectedEntityIt = m_selectedEntities.begin(); selectedEntityIt != m_selectedEntities.end(); )
|
|
{
|
|
auto foundIt = entitySetToDeselect.find(*selectedEntityIt);
|
|
if (foundIt == entitySetToDeselect.end())
|
|
{
|
|
// not trying to deselect this entity, advance iterator
|
|
++selectedEntityIt;
|
|
}
|
|
else
|
|
{
|
|
EntitySelectionEvents::Bus::Event(*selectedEntityIt, &EntitySelectionEvents::OnDeselected);
|
|
|
|
// swap with last element and pop back to avoid moving all vector elements, don't advance iterator
|
|
// the order of m_selectedEntities does not matter
|
|
AZStd::iter_swap(selectedEntityIt, m_selectedEntities.rbegin());
|
|
m_selectedEntities.pop_back();
|
|
}
|
|
}
|
|
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntitySelectionChanged, EntityIdList(), entitiesToDeselect);
|
|
}
|
|
|
|
void ToolsApplication::SetEntityHighlighted(AZ::EntityId entityId, bool highlighted)
|
|
{
|
|
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
|
|
|
auto foundIter = AZStd::find(m_highlightedEntities.begin(), m_highlightedEntities.end(), entityId);
|
|
if (foundIter != m_highlightedEntities.end())
|
|
{
|
|
if (!highlighted)
|
|
{
|
|
AZ_PROFILE_SCOPE(AzToolsFramework, "ToolsApplication::SetEntityHighlighted:RemoveHighlight");
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntityHighlightingChanged);
|
|
m_highlightedEntities.erase(foundIter);
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntityHighlightingChanged);
|
|
}
|
|
}
|
|
else if (highlighted)
|
|
{
|
|
AZ_PROFILE_SCOPE(AzToolsFramework, "ToolsApplication::SetEntityHighlighted:AddHighlight");
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntityHighlightingChanged);
|
|
m_highlightedEntities.push_back(entityId);
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntityHighlightingChanged);
|
|
}
|
|
}
|
|
|
|
void ToolsApplication::SetSelectedEntities(const EntityIdList& selectedEntities)
|
|
{
|
|
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
|
|
|
// We're setting the selection set as a batch from an external caller.
|
|
// * Filter out any unselectable entities
|
|
// * Calculate selection/deselection delta so we can notify specific entities only on change.
|
|
// * Filter any duplicates.
|
|
|
|
// Filter out any invalid or non-selectable entities
|
|
EntityIdList selectedEntitiesFiltered;
|
|
selectedEntitiesFiltered.reserve(selectedEntities.size());
|
|
|
|
// if the new viewport interaction model is enabled we do not want to
|
|
// filter out locked entities as this breaks with the logic of being
|
|
// able to select locked entities in the entity outliner
|
|
selectedEntitiesFiltered.insert(
|
|
selectedEntitiesFiltered.begin(), selectedEntities.begin(), selectedEntities.end());
|
|
|
|
EntityIdList newlySelectedIds;
|
|
EntityIdList newlyDeselectedIds;
|
|
|
|
// Populate list of newly selected entities
|
|
for (AZ::EntityId nowSelectedId : selectedEntitiesFiltered)
|
|
{
|
|
auto alreadySelectedIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), nowSelectedId);
|
|
|
|
if (alreadySelectedIter == m_selectedEntities.end())
|
|
{
|
|
newlySelectedIds.push_back(nowSelectedId);
|
|
}
|
|
}
|
|
|
|
// Populate list of newly deselected entities
|
|
for (AZ::EntityId currentlySelectedId : m_selectedEntities)
|
|
{
|
|
auto stillSelectedIter = AZStd::find(selectedEntitiesFiltered.begin(), selectedEntitiesFiltered.end(), currentlySelectedId);
|
|
|
|
if (stillSelectedIter == selectedEntitiesFiltered.end())
|
|
{
|
|
newlyDeselectedIds.push_back(currentlySelectedId);
|
|
}
|
|
}
|
|
|
|
// Apply selection changes in bulk
|
|
if (!newlySelectedIds.empty() || !newlyDeselectedIds.empty())
|
|
{
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);
|
|
|
|
m_selectedEntities.clear();
|
|
|
|
// Guarantee no dupes, since external caller is unknown.
|
|
for (AZ::EntityId id : selectedEntitiesFiltered)
|
|
{
|
|
auto foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), id);
|
|
if (foundIter == m_selectedEntities.end())
|
|
{
|
|
m_selectedEntities.push_back(id);
|
|
}
|
|
}
|
|
|
|
for (AZ::EntityId id : newlySelectedIds)
|
|
{
|
|
EntitySelectionEvents::Bus::Event(id, &EntitySelectionEvents::OnSelected);
|
|
}
|
|
|
|
for (AZ::EntityId id : newlyDeselectedIds)
|
|
{
|
|
EntitySelectionEvents::Bus::Event(id, &EntitySelectionEvents::OnDeselected);
|
|
}
|
|
|
|
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::AfterEntitySelectionChanged, newlySelectedIds, newlyDeselectedIds);
|
|
}
|
|
|
|
// ensure parent expansion happens if necessary, even if selection hasn't changed
|
|
for (AZ::EntityId nowSelectedId : selectedEntities)
|
|
{
|
|
EditorEntityInfoNotificationBus::Broadcast(&EditorEntityInfoNotificationBus::Events::OnEntityInfoUpdatedSelection, nowSelectedId, true);
|
|
}
|
|
}
|
|
|
|
EntityIdSet ToolsApplication::GatherEntitiesAndAllDescendents(const EntityIdList& inputEntities)
|
|
{
|
|
EntityIdSet output;
|
|
EntityIdList tempList;
|
|
for (const AZ::EntityId& id : inputEntities)
|
|
{
|
|
output.insert(id);
|
|
|
|
tempList.clear();
|
|
EBUS_EVENT_ID_RESULT(tempList, id, AZ::TransformBus, GetAllDescendants);
|
|
output.insert(tempList.begin(), tempList.end());
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
bool ToolsApplication::IsSelectable(const AZ::EntityId& /*entityId*/)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool ToolsApplication::IsSelected(const AZ::EntityId& entityId)
|
|
{
|
|
return m_selectedEntities.end() != AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
|
|
}
|
|
|
|
bool ToolsApplication::IsSliceRootEntity(const AZ::EntityId& entityId)
|
|
{
|
|
return SliceUtilities::IsSliceOrSubsliceRootEntity(entityId);
|
|
}
|
|
|
|
AZ::EntityId ToolsApplication::CreateNewEntity(AZ::EntityId parentId)
|
|
{
|
|
AZ::EntityId createdEntityId;
|
|
EditorRequestBus::BroadcastResult(createdEntityId, &EditorRequests::CreateNewEntity, parentId);
|
|
|
|
return createdEntityId;
|
|
}
|
|
|
|
AZ::EntityId ToolsApplication::CreateNewEntityAtPosition(const AZ::Vector3& pos, AZ::EntityId parentId)
|
|
{
|
|
AZ::EntityId createdEntityId;
|
|
EditorRequestBus::BroadcastResult(createdEntityId, &EditorRequests::CreateNewEntityAtPosition, pos, parentId);
|
|
|
|
return createdEntityId;
|
|
}
|
|
|
|
AZ::EntityId ToolsApplication::GetExistingEntity(AZ::u64 id)
|
|
{
|
|
return AZ::EntityId{id};
|
|
}
|
|
|
|
bool ToolsApplication::EntityExists(AZ::EntityId id)
|
|
{
|
|
return FindEntity(id) != nullptr;
|
|
}
|
|
|
|
void ToolsApplication::DeleteSelected()
|
|
{
|
|
if (IsPrefabSystemEnabled())
|
|
{
|
|
m_editorEntityAPI->DeleteSelected();
|
|
return;
|
|
}
|
|
|
|
Internal::DeleteEntities(m_selectedEntities);
|
|
}
|
|
|
|
void ToolsApplication::DeleteEntityAndAllDescendants(AZ::EntityId entityId)
|
|
{
|
|
if (IsPrefabSystemEnabled())
|
|
{
|
|
m_editorEntityAPI->DeleteEntityAndAllDescendants(entityId);
|
|
return;
|
|
}
|
|
|
|
DeleteEntitiesAndAllDescendants({ entityId });
|
|
}
|
|
|
|
void ToolsApplication::DeleteEntitiesAndAllDescendants(const EntityIdList& entities)
|
|
{
|
|
if (IsPrefabSystemEnabled())
|
|
{
|
|
m_editorEntityAPI->DeleteEntitiesAndAllDescendants(entities);
|
|
return;
|
|
}
|
|
|
|
const EntityIdSet entitiesAndDescendants = GatherEntitiesAndAllDescendents(entities);
|
|
Internal::DeleteEntities(entitiesAndDescendants);
|
|
}
|
|
|
|
bool ToolsApplication::DetachEntities(const AZStd::vector<AZ::EntityId>& entitiesToDetach, AZStd::vector<AZStd::pair<AZ::EntityId, AZ::SliceComponent::EntityRestoreInfo>>& restoreInfos)
|
|
{
|
|
AZStd::vector<AZStd::pair<AZ::Entity*, AZ::SliceComponent::SliceReference*>> pendingSliceChanges;
|
|
|
|
AZ::SliceComponent* editorRootSlice = nullptr;
|
|
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(editorRootSlice,
|
|
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
|
|
AZ_Assert(editorRootSlice, "Failed to retrieve editor root slice.");
|
|
|
|
// Gather desired changes without modifying slices or entities
|
|
for (const AZ::EntityId& entityId : entitiesToDetach)
|
|
{
|
|
AZ::SliceComponent::SliceInstanceAddress sliceAddress(nullptr, nullptr);
|
|
AzFramework::SliceEntityRequestBus::EventResult(sliceAddress, entityId,
|
|
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
|
|
|
|
AZ::SliceComponent::SliceReference* sliceReference = sliceAddress.first;
|
|
AZ::SliceComponent::SliceInstance* sliceInstance = sliceAddress.second;
|
|
if (!sliceReference || !sliceInstance)
|
|
{
|
|
AZ_Error("DetachSliceEntity", false, "Entity with Id %s is not part of a slice. \"Detach\" action cancelled. No slices or entities were modified.", entityId.ToString().c_str());
|
|
return false;
|
|
}
|
|
|
|
AZ::Entity* entity = nullptr;
|
|
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
|
|
AZ_Error("DetachSliceEntity", entity, "Unable to find entity for Entity Id %s. \"Detach\" action cancelled. No slices or entities were modified.", entityId.ToString().c_str());
|
|
if (!entity)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
AZ::SliceComponent::EntityRestoreInfo restoreInfo;
|
|
if (editorRootSlice->GetEntityRestoreInfo(entityId, restoreInfo))
|
|
{
|
|
restoreInfos.emplace_back(entityId, restoreInfo);
|
|
pendingSliceChanges.emplace_back(entity, sliceReference);
|
|
}
|
|
else
|
|
{
|
|
AZ_Error("DetachSliceEntity", entity, "Failed to prepare restore information for entity of Id %s. \"Detach\" action cancelled. No slices or entities were modified.", entityId.ToString().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Apply pending changes
|
|
for (AZStd::pair<AZ::Entity*, AZ::SliceComponent::SliceReference*>& pendingSliceChange : pendingSliceChanges)
|
|
{
|
|
// Remove entity from current slice instance without deleting the entity. Delete slice instance if the detached entity is the last one
|
|
// in the slice instance. The slice instance will be reconstructed upon undo.
|
|
bool success = pendingSliceChange.second->GetSliceComponent()->RemoveEntity(pendingSliceChange.first->GetId(), false, true);
|
|
if (success)
|
|
{
|
|
editorRootSlice->AddEntity(pendingSliceChange.first); // Add back as loose entity
|
|
}
|
|
else
|
|
{
|
|
AZ_Error("DetachSliceEntity", success, "Entity with Id %s could not be removed from the slice. The Slice Instance is now in an unknown state, and saving it may result in data loss.", pendingSliceChange.first->GetId().ToString().c_str());
|
|
}
|
|
}
|
|
|
|
SliceEditorEntityOwnershipServiceNotificationBus::Broadcast(
|
|
&SliceEditorEntityOwnershipServiceNotifications::OnEditorEntitiesSliceOwnershipChanged, entitiesToDetach);
|
|
return true;
|
|
}
|
|
|
|
bool ToolsApplication::DetachSubsliceInstances(const AZ::SliceComponent::SliceInstanceEntityIdRemapList& subsliceRootList, AZStd::vector<AZStd::pair<AZ::EntityId, AZ::SliceComponent::EntityRestoreInfo>>& restoreInfos)
|
|
{
|
|
AZ::SliceComponent* editorRootSlice = nullptr;
|
|
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(editorRootSlice,
|
|
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
|
|
|
|
if (!editorRootSlice)
|
|
{
|
|
AZ_Assert(false,
|
|
"ToolsApplication::DetachSubsliceInstances: Failed to retrieve editor root slice");
|
|
|
|
return false;
|
|
}
|
|
|
|
AZStd::vector<AZ::EntityId> entitiesToUpdate;
|
|
for (const auto& subsliceRoot : subsliceRootList)
|
|
{
|
|
if (!subsliceRoot.first.IsValid())
|
|
{
|
|
AZ_Assert(false,
|
|
"ToolsApplication::DetachSubsliceInstances: Invalid subslice root was passed in. Unable to proceed");
|
|
|
|
return false;
|
|
}
|
|
|
|
// Gather EntityRestoreInfo for all entities in the subslice about to be detached
|
|
for (const AZStd::pair<AZ::EntityId, AZ::EntityId>& liveToSubsliceEntityIdMapping : subsliceRoot.second)
|
|
{
|
|
const AZ::EntityId& liveEntityId = liveToSubsliceEntityIdMapping.first;
|
|
|
|
bool isMetaDataEntity = false;
|
|
AzToolsFramework::SliceMetadataEntityContextRequestBus::BroadcastResult(isMetaDataEntity, &AzToolsFramework::SliceMetadataEntityContextRequestBus::Events::IsSliceMetadataEntity, liveEntityId);
|
|
|
|
// Skip gathering restore info for meta data entities
|
|
if (isMetaDataEntity)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AZ::SliceComponent::EntityRestoreInfo restoreInfo;
|
|
if (editorRootSlice->GetEntityRestoreInfo(liveEntityId, restoreInfo))
|
|
{
|
|
restoreInfos.emplace_back(liveEntityId, restoreInfo);
|
|
entitiesToUpdate.emplace_back(liveEntityId);
|
|
}
|
|
else
|
|
{
|
|
AZ_Error("ToolsApplication::DetachSubsliceInstances",
|
|
false,
|
|
"Failed to prepare restore information for entity of Id %s. \"DetachSubsliceInstance\" action cancelled. No slices or entities were modified.",
|
|
liveEntityId.ToString().c_str());
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Perform the detach operation by extracting each subslice from its current slice and adding it as a standalone instance owned by the root slice
|
|
for (const auto& subsliceRoot : subsliceRootList)
|
|
{
|
|
const AZ::Data::Asset<AZ::SliceAsset>& subsliceAsset = subsliceRoot.first.GetReference()->GetSliceAsset();
|
|
|
|
if (!editorRootSlice->AddSliceUsingExistingEntities(subsliceAsset, subsliceRoot.second).IsValid())
|
|
{
|
|
AZ_Error("ToolsApplication::DetachSubsliceInstances",
|
|
false,
|
|
"Subslice Instance of Asset with Id %s could not be detached from source slice. The Slice instance is now in an unknown state, and saving it may result in data loss.",
|
|
subsliceAsset.ToString<AZStd::string>().c_str());
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
SliceEditorEntityOwnershipServiceNotificationBus::Broadcast(
|
|
&SliceEditorEntityOwnershipServiceNotificationBus::Events::OnEditorEntitiesSliceOwnershipChanged, entitiesToUpdate);
|
|
return true;
|
|
}
|
|
|
|
bool ToolsApplication::FindCommonRoot(const AzToolsFramework::EntityIdSet& entitiesToBeChecked, AZ::EntityId& commonRootEntityId
|
|
, AzToolsFramework::EntityIdList* topLevelEntities)
|
|
{
|
|
// Return value
|
|
bool entitiesHaveCommonRoot = true;
|
|
|
|
bool rootFound = false;
|
|
commonRootEntityId.SetInvalid();
|
|
|
|
// Finding the common root and top level entities
|
|
for (const AZ::EntityId& id : entitiesToBeChecked)
|
|
{
|
|
AZ_Warning("Slices", AZ::TransformBus::FindFirstHandler(id) != nullptr,
|
|
"Entity with id %llu is not active, or does not have a transform component. This method is only valid for active entities. with a Transform component",
|
|
id);
|
|
|
|
AZ::EntityId parentId;
|
|
AZ::TransformBus::EventResult(parentId, id, &AZ::TransformBus::Events::GetParentId);
|
|
|
|
// If this entity is not a child of entities to be checked
|
|
if (entitiesToBeChecked.find(parentId) == entitiesToBeChecked.end())
|
|
{
|
|
if (topLevelEntities)
|
|
{
|
|
// Add it to the top level entities
|
|
topLevelEntities->push_back(id);
|
|
}
|
|
|
|
if (rootFound)
|
|
{
|
|
// If the entities checked until now have a common root and the parent id of this entity is not the same as the common root
|
|
if (entitiesHaveCommonRoot && (parentId != commonRootEntityId))
|
|
{
|
|
// Entities do not have a common root
|
|
commonRootEntityId.SetInvalid();
|
|
entitiesHaveCommonRoot = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
commonRootEntityId = parentId;
|
|
rootFound = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return entitiesHaveCommonRoot;
|
|
}
|
|
|
|
bool ToolsApplication::FindCommonRootInactive(const AzToolsFramework::EntityList& entitiesToBeChecked, AZ::EntityId& commonRootEntityId, AzToolsFramework::EntityList* topLevelEntities)
|
|
{
|
|
// Return value
|
|
bool entitiesHaveCommonRoot = true;
|
|
|
|
bool rootFound = false;
|
|
commonRootEntityId.SetInvalid();
|
|
|
|
AzToolsFramework::EntityIdSet entityIds;
|
|
for (const auto& entity : entitiesToBeChecked)
|
|
{
|
|
entityIds.insert(entity->GetId());
|
|
}
|
|
|
|
// Finding the common root and top level entities
|
|
for (const AZ::Entity* entity : entitiesToBeChecked)
|
|
{
|
|
Components::TransformComponent* transformComponent =
|
|
entity->FindComponent<Components::TransformComponent>();
|
|
|
|
if (transformComponent)
|
|
{
|
|
const AZ::EntityId& parentId = transformComponent->GetParentId();
|
|
// If this entity is not a child of entities to be checked
|
|
if (entityIds.find(parentId) == entityIds.end())
|
|
{
|
|
if (topLevelEntities)
|
|
{
|
|
topLevelEntities->push_back(const_cast<AZ::Entity*>(entity));
|
|
}
|
|
|
|
if (rootFound)
|
|
{
|
|
if (entitiesHaveCommonRoot && (parentId != commonRootEntityId))
|
|
{
|
|
commonRootEntityId.SetInvalid();
|
|
entitiesHaveCommonRoot = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
commonRootEntityId = parentId;
|
|
rootFound = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return entitiesHaveCommonRoot;
|
|
}
|
|
|
|
void ToolsApplication::FindTopLevelEntityIdsInactive(const EntityIdList& entityIdsToCheck, EntityIdList& topLevelEntityIds)
|
|
{
|
|
for (AZ::EntityId entityId : entityIdsToCheck)
|
|
{
|
|
AZ::Entity* entity = nullptr;
|
|
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
|
|
if (!entity)
|
|
{
|
|
continue;
|
|
}
|
|
Components::TransformComponent* transformComponent = entity->FindComponent<Components::TransformComponent>();
|
|
if (!transformComponent)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool isTopLevel = true;
|
|
AZ::EntityId parentId = transformComponent->GetParentId();
|
|
while (parentId.IsValid())
|
|
{
|
|
if (AZStd::find(entityIdsToCheck.begin(), entityIdsToCheck.end(), parentId) != entityIdsToCheck.end())
|
|
{
|
|
isTopLevel = false;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
AZ::Entity* parentEntity = nullptr;
|
|
AZ::ComponentApplicationBus::BroadcastResult(parentEntity, &AZ::ComponentApplicationBus::Events::FindEntity, parentId);
|
|
parentId.SetInvalid();
|
|
if (parentEntity)
|
|
{
|
|
transformComponent = parentEntity->FindComponent<Components::TransformComponent>();
|
|
if (transformComponent)
|
|
{
|
|
parentId = transformComponent->GetParentId();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isTopLevel)
|
|
{
|
|
topLevelEntityIds.push_back(entityId);
|
|
}
|
|
}
|
|
}
|
|
|
|
AZ::SliceComponent::SliceInstanceAddress ToolsApplication::FindCommonSliceInstanceAddress(const EntityIdList& entityIds)
|
|
{
|
|
AZ::SliceComponent::SliceInstanceAddress result;
|
|
|
|
if (entityIds.empty())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
AzFramework::SliceEntityRequestBus::EventResult(result, entityIds[0],
|
|
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
|
|
|
|
for (int index = 1; index < entityIds.size(); index++)
|
|
{
|
|
AZ::SliceComponent::SliceInstanceAddress sliceAddressTemp;
|
|
AzFramework::SliceEntityRequestBus::EventResult(sliceAddressTemp, entityIds[index],
|
|
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
|
|
|
|
if (sliceAddressTemp != result)
|
|
{
|
|
// Entities come from different slice instances.
|
|
return AZ::SliceComponent::SliceInstanceAddress();
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
AZ::EntityId ToolsApplication::GetRootEntityIdOfSliceInstance(AZ::SliceComponent::SliceInstanceAddress sliceAddress)
|
|
{
|
|
AZ::EntityId result;
|
|
if (!sliceAddress.IsValid())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
const AZ::Data::Asset<AZ::SliceAsset>& sliceAsset = sliceAddress.GetReference()->GetSliceAsset();
|
|
AZ::SliceComponent::EntityList sliceAssetEntities;
|
|
sliceAsset.Get()->GetComponent()->GetEntities(sliceAssetEntities);
|
|
|
|
AZ::EntityId commonRootEntityId;
|
|
AzToolsFramework::EntityList topLevelEntities;
|
|
|
|
FindCommonRootInactive(sliceAssetEntities, commonRootEntityId, &topLevelEntities);
|
|
if (!topLevelEntities.empty())
|
|
{
|
|
const AZ::SliceComponent::EntityIdToEntityIdMap& baseToInstanceEntityIdMap = sliceAddress.GetInstance()->GetEntityIdMap();
|
|
auto foundItr = baseToInstanceEntityIdMap.find(topLevelEntities[0]->GetId());
|
|
if (foundItr != baseToInstanceEntityIdMap.end())
|
|
{
|
|
result = foundItr->second;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
AZ::EntityId ToolsApplication::GetCurrentLevelEntityId()
|
|
{
|
|
AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull();
|
|
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId);
|
|
AZ::SliceComponent* rootSliceComponent = nullptr;
|
|
AzFramework::SliceEntityOwnershipServiceRequestBus::EventResult(rootSliceComponent, editorEntityContextId,
|
|
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice);
|
|
if (rootSliceComponent && rootSliceComponent->GetMetadataEntity())
|
|
{
|
|
return rootSliceComponent->GetMetadataEntity()->GetId();
|
|
}
|
|
|
|
return AZ::EntityId();
|
|
}
|
|
|
|
bool ToolsApplication::CheckSourceControlConnectionAndRequestEditForFileBlocking(const char* assetPath, const char* progressMessage, const ToolsApplicationRequests::RequestEditProgressCallback& progressCallback)
|
|
{
|
|
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
|
|
SourceControlState state = SourceControlState::Disabled;
|
|
SourceControlConnectionRequestBus::BroadcastResult(state, &SourceControlConnectionRequestBus::Events::GetSourceControlState);
|
|
if (state != SourceControlState::Active && (!fileIO || fileIO->IsReadOnly(assetPath)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return RequestEditForFileBlocking(assetPath, progressMessage, progressCallback);
|
|
}
|
|
|
|
bool ToolsApplication::RequestEditForFileBlocking(const char* assetPath, const char* progressMessage, const ToolsApplicationRequests::RequestEditProgressCallback& progressCallback)
|
|
{
|
|
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
|
|
if (fileIO && !fileIO->IsReadOnly(assetPath))
|
|
{
|
|
SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestEdit, assetPath, true, [](bool, const SourceControlFileInfo&) {});
|
|
return true;
|
|
}
|
|
|
|
bool editSuccess = false;
|
|
bool editComplete = false;
|
|
SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestEdit, assetPath, true,
|
|
[&editSuccess, &editComplete](bool success, const AzToolsFramework::SourceControlFileInfo& /*info*/)
|
|
{
|
|
editSuccess = success;
|
|
editComplete = true;
|
|
}
|
|
);
|
|
|
|
QWidget* mainWindow = nullptr;
|
|
EditorRequests::Bus::BroadcastResult(mainWindow, &EditorRequests::Bus::Events::GetMainWindow);
|
|
AZ_Warning("RequestEdit", mainWindow, "Failed to retrieve main application window.");
|
|
ProgressShield::LegacyShowAndWait(mainWindow, mainWindow ? mainWindow->tr(progressMessage) : QString(progressMessage),
|
|
[&editComplete, &progressCallback](int& current, int& max)
|
|
{
|
|
if (progressCallback)
|
|
{
|
|
progressCallback(current, max);
|
|
}
|
|
else
|
|
{
|
|
current = 0;
|
|
max = 0;
|
|
}
|
|
return editComplete;
|
|
}
|
|
);
|
|
|
|
return (fileIO && !fileIO->IsReadOnly(assetPath));
|
|
}
|
|
|
|
void ToolsApplication::RequestEditForFile(const char* assetPath, RequestEditResultCallback resultCallback)
|
|
{
|
|
AZ_Error("RequestEdit", resultCallback != nullptr, "User result callback is required.");
|
|
|
|
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
|
|
if (fileIO && !fileIO->IsReadOnly(assetPath))
|
|
{
|
|
if (resultCallback)
|
|
{
|
|
resultCallback(true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestEdit, assetPath, true,
|
|
[resultCallback](bool success, const AzToolsFramework::SourceControlFileInfo& info)
|
|
{
|
|
if (resultCallback)
|
|
{
|
|
success = !info.IsReadOnly();
|
|
resultCallback(success);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
void ToolsApplication::CheckSourceControlConnectionAndRequestEditForFile(const char* assetPath, RequestEditResultCallback resultCallback)
|
|
{
|
|
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
|
|
SourceControlState state = SourceControlState::Disabled;
|
|
SourceControlConnectionRequestBus::BroadcastResult(state, &SourceControlConnectionRequestBus::Events::GetSourceControlState);
|
|
if (state != SourceControlState::Active && (!fileIO || fileIO->IsReadOnly(assetPath)))
|
|
{
|
|
resultCallback(false);
|
|
return;
|
|
}
|
|
|
|
RequestEditForFile(assetPath, resultCallback);
|
|
}
|
|
|
|
void ToolsApplication::DeleteEntityById(AZ::EntityId entityId)
|
|
{
|
|
if (IsPrefabSystemEnabled())
|
|
{
|
|
m_editorEntityAPI->DeleteEntityById(entityId);
|
|
return;
|
|
}
|
|
|
|
DeleteEntities({ entityId });
|
|
}
|
|
|
|
void ToolsApplication::DeleteEntities(const EntityIdList& entities)
|
|
{
|
|
if (IsPrefabSystemEnabled())
|
|
{
|
|
m_editorEntityAPI->DeleteEntities(entities);
|
|
return;
|
|
}
|
|
|
|
Internal::DeleteEntities(entities);
|
|
}
|
|
|
|
void ToolsApplication::AddDirtyEntity(AZ::EntityId entityId)
|
|
{
|
|
// If we're already in undo redo, we don't want the user to have to check for this each time.
|
|
if (m_isDuringUndoRedo)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_dirtyEntities.insert(entityId);
|
|
|
|
// Check if this dirty entity is in a layer by walking up its parenting hierarchy.
|
|
// If it's in a layer, mark that layer as having unsaved changes.
|
|
do
|
|
{
|
|
bool isLayerEntity = false;
|
|
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
|
|
isLayerEntity,
|
|
entityId,
|
|
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
|
|
if (isLayerEntity)
|
|
{
|
|
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
|
|
entityId,
|
|
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::MarkLayerWithUnsavedChanges);
|
|
break;
|
|
}
|
|
AZ::EntityId parentId;
|
|
AZ::TransformBus::EventResult(
|
|
parentId,
|
|
entityId,
|
|
&AZ::TransformBus::Events::GetParentId);
|
|
if (entityId == parentId)
|
|
{
|
|
// Stop when the first parent layer is found.
|
|
// If this layer is nested in another layer, only the most immediate
|
|
// layer should be marked as dirty. Layer hierarchy and parenting is
|
|
// mostly for visual organization, entities only exist in one layer on disk.
|
|
break;
|
|
}
|
|
entityId = parentId;
|
|
} while (entityId.IsValid());
|
|
}
|
|
|
|
int ToolsApplication::RemoveDirtyEntity(AZ::EntityId entityId)
|
|
{
|
|
return static_cast<int>(m_dirtyEntities.erase(entityId));
|
|
}
|
|
|
|
void ToolsApplication::ClearDirtyEntities()
|
|
{
|
|
m_dirtyEntities.clear();
|
|
}
|
|
|
|
void ToolsApplication::UndoPressed()
|
|
{
|
|
if (m_undoStack)
|
|
{
|
|
if (m_undoStack->CanUndo())
|
|
{
|
|
m_isDuringUndoRedo = true;
|
|
EBUS_EVENT(ToolsApplicationEvents::Bus, BeforeUndoRedo);
|
|
m_undoStack->Undo();
|
|
EBUS_EVENT(ToolsApplicationEvents::Bus, AfterUndoRedo);
|
|
m_isDuringUndoRedo = false;
|
|
|
|
#if defined(ENABLE_UNDOCACHE_CONSISTENCY_CHECKS)
|
|
ConsistencyCheckUndoCache();
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void ToolsApplication::RedoPressed()
|
|
{
|
|
if (m_undoStack)
|
|
{
|
|
if (m_undoStack->CanRedo())
|
|
{
|
|
m_isDuringUndoRedo = true;
|
|
EBUS_EVENT(ToolsApplicationEvents::Bus, BeforeUndoRedo);
|
|
m_undoStack->Redo();
|
|
EBUS_EVENT(ToolsApplicationEvents::Bus, AfterUndoRedo);
|
|
m_isDuringUndoRedo = false;
|
|
|
|
#if defined(ENABLE_UNDOCACHE_CONSISTENCY_CHECKS)
|
|
ConsistencyCheckUndoCache();
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void ToolsApplication::FlushUndo()
|
|
{
|
|
if (m_undoStack)
|
|
{
|
|
m_undoStack->Reset();
|
|
}
|
|
|
|
if (m_currentBatchUndo)
|
|
{
|
|
delete m_currentBatchUndo;
|
|
m_currentBatchUndo = nullptr;
|
|
}
|
|
|
|
m_dirtyEntities.clear();
|
|
m_isDuringUndoRedo = false;
|
|
}
|
|
|
|
void ToolsApplication::FlushRedo()
|
|
{
|
|
if (m_undoStack)
|
|
{
|
|
m_undoStack->Slice();
|
|
}
|
|
}
|
|
|
|
UndoSystem::URSequencePoint* ToolsApplication::BeginUndoBatch(const char* label)
|
|
{
|
|
AZ_Error("Tools Application", !m_isDuringUndoRedo, "Can not create a new Undo/Redo bach while an Undo or Redo operation is running.");
|
|
|
|
if (!m_currentBatchUndo)
|
|
{
|
|
m_currentBatchUndo = aznew UndoSystem::BatchCommand(label, 0);
|
|
|
|
// notify Cry undo has started (SandboxIntegrationManager)
|
|
// Only do this at the root level. OnEndUndo will be called at the root
|
|
// level when EndUndoBatch is called.
|
|
EBUS_EVENT(ToolsApplicationEvents::Bus, OnBeginUndo, label);
|
|
}
|
|
else
|
|
{
|
|
UndoSystem::URSequencePoint* current = m_currentBatchUndo;
|
|
|
|
m_currentBatchUndo = aznew UndoSystem::BatchCommand(label, 0);
|
|
m_currentBatchUndo->SetParent(current);
|
|
}
|
|
|
|
return m_currentBatchUndo;
|
|
}
|
|
|
|
UndoSystem::URSequencePoint* ToolsApplication::ResumeUndoBatch(UndoSystem::URSequencePoint* expected, const char* label)
|
|
{
|
|
if (m_currentBatchUndo)
|
|
{
|
|
if (m_undoStack->GetTop() == m_currentBatchUndo)
|
|
{
|
|
m_undoStack->PopTop();
|
|
}
|
|
|
|
return m_currentBatchUndo;
|
|
}
|
|
|
|
if (m_undoStack)
|
|
{
|
|
const auto ptr = m_undoStack->GetTop();
|
|
if (ptr && ptr == expected)
|
|
{
|
|
m_currentBatchUndo = ptr;
|
|
m_undoStack->PopTop();
|
|
|
|
return m_currentBatchUndo;
|
|
}
|
|
}
|
|
|
|
return BeginUndoBatch(label);
|
|
}
|
|
|
|
/**
|
|
* Iterate over all sequence points for this undo batch, checking in each
|
|
* case if it changed or not - if no change was recorded, discard this undo.
|
|
*/
|
|
static bool DidSequencePointChange(const UndoSystem::URSequencePoint* root)
|
|
{
|
|
bool changed = false;
|
|
const UndoSystem::URSequencePoint::ChildVec& children = root->GetChildren();
|
|
if (!children.empty())
|
|
{
|
|
for (const UndoSystem::URSequencePoint* child : children)
|
|
{
|
|
changed = DidSequencePointChange(child);
|
|
if (changed)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return root->Changed() || changed;
|
|
}
|
|
|
|
void ToolsApplication::EndUndoBatch()
|
|
{
|
|
AZ_Assert(m_currentBatchUndo, "Cannot end batch - no batch current");
|
|
|
|
if (m_currentBatchUndo->GetParent())
|
|
{
|
|
// pop one up
|
|
m_currentBatchUndo = m_currentBatchUndo->GetParent();
|
|
}
|
|
else
|
|
{
|
|
// we're at the root
|
|
// only undo at bottom of scope (first invoked ScopedUndoBatch in
|
|
// chain/hierarchy must go out of scope)
|
|
CreateUndosForDirtyEntities();
|
|
m_dirtyEntities.clear();
|
|
|
|
// check if any change was recorded during this undo
|
|
bool changed = DidSequencePointChange(m_currentBatchUndo);
|
|
|
|
// notify Cry undo has ended (SandboxIntegrationManager)
|
|
ToolsApplicationEvents::Bus::Broadcast(
|
|
&ToolsApplicationEvents::Bus::Events::OnEndUndo, m_currentBatchUndo->GetName().c_str(), changed);
|
|
|
|
// record each undo batch
|
|
if (m_undoStack && changed)
|
|
{
|
|
m_undoStack->Post(m_currentBatchUndo);
|
|
}
|
|
else
|
|
{
|
|
delete m_currentBatchUndo;
|
|
}
|
|
|
|
#if defined(ENABLE_UNDOCACHE_CONSISTENCY_CHECKS)
|
|
ConsistencyCheckUndoCache();
|
|
#endif
|
|
m_currentBatchUndo = nullptr;
|
|
}
|
|
}
|
|
|
|
void ToolsApplication::CreateUndosForDirtyEntities()
|
|
{
|
|
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
|
|
|
AZ_Assert(!m_isDuringUndoRedo, "Cannot add dirty entities during undo/redo.");
|
|
if (m_dirtyEntities.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!IsPrefabSystemEnabled())
|
|
{
|
|
// If the current undo batch has commands in it, then we have to check that we do not add duplicates
|
|
// However if it starts out empty, we can just add things straight from the Set to the undo batch
|
|
bool mustCheckDuplicates = !m_currentBatchUndo->GetChildren().empty();
|
|
|
|
for (AZ::EntityId entityId : m_dirtyEntities)
|
|
{
|
|
AZ::Entity* entity = nullptr;
|
|
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, entityId);
|
|
|
|
if (entity)
|
|
{
|
|
EntityStateCommand* state = nullptr;
|
|
|
|
if (mustCheckDuplicates)
|
|
{
|
|
// Check if this entity is already in the current undo batch
|
|
state = azdynamic_cast<EntityStateCommand*>(
|
|
m_currentBatchUndo->Find(static_cast<AZ::u64>(entityId), AZ::AzTypeInfo<EntityStateCommand>::Uuid()));
|
|
}
|
|
|
|
if (!state)
|
|
{
|
|
state = aznew EntityStateCommand(static_cast<AZ::u64>(entityId));
|
|
state->SetParent(m_currentBatchUndo);
|
|
|
|
// capture initial state of entity (before undo)
|
|
state->Capture(entity, true);
|
|
}
|
|
|
|
// capture last state of entity (after undo) - for redo
|
|
state->Capture(entity, false);
|
|
}
|
|
|
|
m_undoCache.UpdateCache(entityId);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get();
|
|
if (prefabPublicInterface)
|
|
{
|
|
// Compared to the preemptive undo cache, we can avoid the duplicate check.
|
|
// Multiple changes to the same entity are just split between different undo nodes.
|
|
for (AZ::EntityId entityId : m_dirtyEntities)
|
|
{
|
|
auto outcome = prefabPublicInterface->GenerateUndoNodesForEntityChangeAndUpdateCache(entityId, m_currentBatchUndo);
|
|
|
|
if (!outcome.IsSuccess())
|
|
{
|
|
QMessageBox::warning(
|
|
AzToolsFramework::GetActiveWindow(), QString("Error"), QString(outcome.GetError().c_str()), QMessageBox::Ok, QMessageBox::Ok);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ToolsApplication::ConsistencyCheckUndoCache()
|
|
{
|
|
auto undoCacheInterface = AZ::Interface<UndoSystem::UndoCacheInterface>::Get();
|
|
if (undoCacheInterface)
|
|
{
|
|
for (auto&& entityEntry : m_entities)
|
|
{
|
|
undoCacheInterface->Validate((entityEntry.second)->GetId());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ToolsApplication::IsEntityEditable(AZ::EntityId entityId)
|
|
{
|
|
(void)entityId;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ToolsApplication::AreEntitiesEditable(const EntityIdList& entityIds)
|
|
{
|
|
for (AZ::EntityId entityId : entityIds)
|
|
{
|
|
if (!IsEntityEditable(entityId))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ToolsApplication::CheckoutPressed()
|
|
{
|
|
}
|
|
|
|
SourceControlFileInfo ToolsApplication::GetSceneSourceControlInfo()
|
|
{
|
|
return SourceControlFileInfo();
|
|
}
|
|
|
|
void ToolsApplication::EnterEditorIsolationMode()
|
|
{
|
|
if (!m_isInIsolationMode && m_selectedEntities.size() > 0)
|
|
{
|
|
m_isInIsolationMode = true;
|
|
|
|
m_isolatedEntityIdSet.insert(m_selectedEntities.begin(), m_selectedEntities.end());
|
|
|
|
// Add also entities in between selected entities along the transform hierarchy chain.
|
|
// For example A has child B and B has child C, and if A and C are isolated, B is too.
|
|
AZStd::vector<AZ::EntityId> inbetweenEntityIds;
|
|
|
|
for (AZ::EntityId entityId : m_selectedEntities)
|
|
{
|
|
bool addInbetweenEntityIds = false;
|
|
inbetweenEntityIds.clear();
|
|
|
|
AZ::EntityId parentEntityId;
|
|
AZ::TransformBus::EventResult(parentEntityId, entityId, &AZ::TransformBus::Events::GetParentId);
|
|
|
|
while (parentEntityId.IsValid())
|
|
{
|
|
const EntityIdSet::iterator found = m_isolatedEntityIdSet.find(parentEntityId);
|
|
if (found != m_isolatedEntityIdSet.end())
|
|
{
|
|
addInbetweenEntityIds = true;
|
|
break;
|
|
}
|
|
inbetweenEntityIds.push_back(parentEntityId);
|
|
|
|
const AZ::EntityId currentParentId = parentEntityId;
|
|
parentEntityId.SetInvalid();
|
|
AZ::TransformBus::EventResult(parentEntityId, currentParentId, &AZ::TransformBus::Events::GetParentId);
|
|
}
|
|
|
|
if (addInbetweenEntityIds)
|
|
{
|
|
m_isolatedEntityIdSet.insert(inbetweenEntityIds.begin(), inbetweenEntityIds.end());
|
|
}
|
|
}
|
|
|
|
|
|
for (AZ::EntityId entityId : m_isolatedEntityIdSet)
|
|
{
|
|
ComponentEntityEditorRequestBus::Event(entityId, &ComponentEntityEditorRequestBus::Events::SetSandBoxObjectIsolated, true);
|
|
}
|
|
|
|
ToolsApplicationNotificationBus::Broadcast(&ToolsApplicationNotificationBus::Events::OnEnterEditorIsolationMode);
|
|
}
|
|
}
|
|
|
|
void ToolsApplication::ExitEditorIsolationMode()
|
|
{
|
|
if (m_isInIsolationMode)
|
|
{
|
|
m_isInIsolationMode = false;
|
|
|
|
for (AZ::EntityId entityId : m_isolatedEntityIdSet)
|
|
{
|
|
ComponentEntityEditorRequestBus::Event(entityId, &ComponentEntityEditorRequestBus::Events::SetSandBoxObjectIsolated, false);
|
|
}
|
|
|
|
m_isolatedEntityIdSet.clear();
|
|
|
|
ToolsApplicationNotificationBus::Broadcast(&ToolsApplicationNotificationBus::Events::OnExitEditorIsolationMode);
|
|
}
|
|
}
|
|
|
|
bool ToolsApplication::IsEditorInIsolationMode()
|
|
{
|
|
return m_isInIsolationMode;
|
|
}
|
|
|
|
void ToolsApplication::CreateAndAddEntityFromComponentTags(const AZStd::vector<AZ::Crc32>& requiredTags, const char* entityName)
|
|
{
|
|
if (!entityName || !entityName[0])
|
|
{
|
|
entityName = "ToolsApplication Entity";
|
|
}
|
|
|
|
AZ::SerializeContext* context = GetSerializeContext();
|
|
AZ_Assert(context, "Unable to retrieve serialize context. Ensure the application is initialized before attempting to add components by tag.");
|
|
|
|
AZ::Entity* entity = aznew AZ::Entity(entityName);
|
|
AZStd::unordered_set<AZ::Uuid> componentsToAddToEntity;
|
|
AZ::Edit::GetComponentUuidsWithSystemComponentTag(context, requiredTags, componentsToAddToEntity);
|
|
|
|
for (const AZ::Uuid& typeId : componentsToAddToEntity)
|
|
{
|
|
entity->CreateComponent(typeId);
|
|
}
|
|
entity->Init();
|
|
entity->Activate();
|
|
}
|
|
|
|
void ToolsApplication::RunRedoSeparately(UndoSystem::URSequencePoint* redoCommand)
|
|
{
|
|
if (redoCommand)
|
|
{
|
|
m_isDuringUndoRedo = true;
|
|
redoCommand->RunRedo();
|
|
m_isDuringUndoRedo = false;
|
|
}
|
|
}
|
|
|
|
AzToolsFramework::ToolsApplicationRequests::ResolveToolPathOutcome ToolsApplication::ResolveConfigToolsPath(const char* toolApplicationName) const
|
|
{
|
|
if (!GetExecutableFolder() || !GetEngineRoot())
|
|
{
|
|
return AZ::Failure(AZStd::string("Cannot use ToolsApplicationRequets::ResolveConfigToolsPath until the component application has finished starting up."));
|
|
}
|
|
|
|
AZStd::vector<AZStd::string> toolTargetSearchPaths;
|
|
|
|
// Add the current executable's folder to search paths
|
|
{
|
|
AZStd::string localAppPath;
|
|
if (AzFramework::StringFunc::Path::ConstructFull(GetExecutableFolder(), toolApplicationName, localAppPath, true))
|
|
{
|
|
toolTargetSearchPaths.push_back(localAppPath);
|
|
}
|
|
}
|
|
|
|
for (const AZStd::string& searchAbsPath : toolTargetSearchPaths)
|
|
{
|
|
if (AZ::IO::SystemFile::Exists(searchAbsPath.c_str()))
|
|
{
|
|
return AZ::Success(AZStd::string(searchAbsPath));
|
|
}
|
|
}
|
|
|
|
return AZ::Failure(AZStd::string::format("Unable to resolve tool application path for '%s'", toolApplicationName));
|
|
}
|
|
|
|
void ToolsApplication::QueryApplicationType(AZ::ApplicationTypeQuery& appType) const
|
|
{
|
|
appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Tool;
|
|
};
|
|
} // namespace AzToolsFramework
|