2942 lines
111 KiB
C++
2942 lines
111 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "EngineDefines.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Misc/App.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "UObject/ObjectMacros.h"
|
|
#include "UObject/Object.h"
|
|
#include "UObject/Class.h"
|
|
#include "UObject/Package.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "InputCoreTypes.h"
|
|
#include "Input/Reply.h"
|
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
|
#include "Widgets/SWidget.h"
|
|
#include "Widgets/SCompoundWidget.h"
|
|
#include "Widgets/SBoxPanel.h"
|
|
#include "Widgets/SWindow.h"
|
|
#include "Layout/WidgetPath.h"
|
|
#include "Framework/Application/MenuStack.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Textures/SlateIcon.h"
|
|
#include "Framework/Commands/UIAction.h"
|
|
#include "Widgets/Layout/SBorder.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "EditorStyleSet.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "RawIndexBuffer.h"
|
|
#include "Model.h"
|
|
#include "CookOnTheSide/CookOnTheFlyServer.h"
|
|
#include "Builders/CubeBuilder.h"
|
|
#include "Settings/LevelEditorViewportSettings.h"
|
|
#include "Settings/LevelEditorMiscSettings.h"
|
|
#include "Engine/Brush.h"
|
|
#include "AssetData.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "ISourceControlModule.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "Settings/EditorLoadingSavingSettings.h"
|
|
#include "EditorFramework/AssetImportData.h"
|
|
#include "Animation/SkeletalMeshActor.h"
|
|
#include "Components/CapsuleComponent.h"
|
|
#include "Components/SphereComponent.h"
|
|
#include "Components/BoxComponent.h"
|
|
#include "Components/PointLightComponent.h"
|
|
#include "Engine/StaticMeshActor.h"
|
|
#include "Components/BrushComponent.h"
|
|
#include "PhysicsEngine/RadialForceComponent.h"
|
|
#include "Engine/Polys.h"
|
|
#include "Engine/Selection.h"
|
|
#include "Editor.h"
|
|
#include "LevelEditorViewport.h"
|
|
#include "EditorModeManager.h"
|
|
#include "EditorModes.h"
|
|
#include "EditorDirectories.h"
|
|
#include "FileHelpers.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "StaticMeshResources.h"
|
|
#include "EditorSupportDelegates.h"
|
|
#include "BusyCursor.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "LevelUtils.h"
|
|
#include "ObjectTools.h"
|
|
#include "PackageTools.h"
|
|
#include "Interfaces/IMainFrameModule.h"
|
|
#include "EditorLevelUtils.h"
|
|
#include "EditorBuildUtils.h"
|
|
#include "ScriptDisassembler.h"
|
|
#include "IAssetTools.h"
|
|
#include "AssetToolsModule.h"
|
|
#include "IContentBrowserSingleton.h"
|
|
#include "ContentBrowserModule.h"
|
|
#include "AssetRegistryModule.h"
|
|
#include "Matinee/MatineeActor.h"
|
|
#include "MatineeExporter.h"
|
|
#include "FbxExporter.h"
|
|
#include "DesktopPlatformModule.h"
|
|
#include "SnappingUtils.h"
|
|
#include "AssetSelection.h"
|
|
#include "HighResScreenshot.h"
|
|
#include "ActorEditorUtils.h"
|
|
#include "Editor/ActorPositioning.h"
|
|
#include "Matinee/InterpData.h"
|
|
#include "LandscapeInfo.h"
|
|
#include "LandscapeInfoMap.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Logging/LogScopedCategoryAndVerbosityOverride.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "EngineUtils.h"
|
|
#include "AutoReimport/AssetSourceFilenameCache.h"
|
|
#if PLATFORM_WINDOWS
|
|
#include "Windows/WindowsHWrapper.h"
|
|
#endif
|
|
#include "ActorGroupingUtils.h"
|
|
#include "EdMode.h"
|
|
#include "Subsystems/BrushEditingSubsystem.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogUnrealEdSrv, Log, All);
|
|
|
|
#define LOCTEXT_NAMESPACE "UnrealEdSrv"
|
|
|
|
/**
|
|
* Dumps a set of selected objects to debugf.
|
|
*/
|
|
static void PrivateDumpSelection(USelection* Selection)
|
|
{
|
|
for (FSelectionIterator Itor(*Selection); Itor; ++Itor)
|
|
{
|
|
UObject* CurObject = *Itor;
|
|
if (CurObject)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT(" %s"), *CurObject->GetClass()->GetName());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT(" NULL object"));
|
|
}
|
|
}
|
|
}
|
|
|
|
class SModalWindowTest: public SCompoundWidget
|
|
{
|
|
public:
|
|
SLATE_BEGIN_ARGS(SModalWindowTest) {}
|
|
SLATE_END_ARGS()
|
|
|
|
void Construct(const FArguments& InArgs)
|
|
{
|
|
this->ChildSlot
|
|
[SNew(SBorder)
|
|
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight()[SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Center)[SNew(STextBlock).Text(LOCTEXT("ModelTestWindowLabel", "This is a modal window test"))] + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Center)[SNew(SButton).Text(LOCTEXT("NewModalTestWindowButtonLabel", "New Modal Window")).OnClicked(this, &SModalWindowTest::OnNewModalWindowClicked)]] + SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Right)[SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth()[SNew(SButton).Text(NSLOCTEXT("UnrealEd", "OK", "OK")).OnClicked(this, &SModalWindowTest::OnOKClicked)] + SHorizontalBox::Slot().AutoWidth()[SNew(SButton).Text(NSLOCTEXT("UnrealEd", "Cancel", "Cancel")).OnClicked(this, &SModalWindowTest::OnCancelClicked)]]]];
|
|
}
|
|
|
|
SModalWindowTest()
|
|
: bUserResponse(false)
|
|
{
|
|
}
|
|
|
|
void SetWindow(TSharedPtr<SWindow> InWindow)
|
|
{
|
|
MyWindow = InWindow;
|
|
}
|
|
|
|
bool GetResponse() const { return bUserResponse; }
|
|
|
|
private:
|
|
FReply OnOKClicked()
|
|
{
|
|
bUserResponse = true;
|
|
MyWindow->RequestDestroyWindow();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply OnCancelClicked()
|
|
{
|
|
bUserResponse = false;
|
|
MyWindow->RequestDestroyWindow();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply OnNewModalWindowClicked()
|
|
{
|
|
TSharedRef<SModalWindowTest> ModalWindowContent = SNew(SModalWindowTest);
|
|
TSharedRef<SWindow> ModalWindow = SNew(SWindow)
|
|
.Title(LOCTEXT("TestModalWindowTitle", "Modal Window"))
|
|
.ClientSize(FVector2D(250, 100))
|
|
[ModalWindowContent];
|
|
|
|
ModalWindowContent->SetWindow(ModalWindow);
|
|
|
|
FSlateApplication::Get().AddModalWindow(ModalWindow, AsShared());
|
|
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("Modal Window Returned"));
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
|
{
|
|
struct Local
|
|
{
|
|
static void FillSubMenuEntries(FMenuBuilder& MenuBuilder)
|
|
{
|
|
MenuBuilder.AddMenuEntry(LOCTEXT("TestItem2", "Test Item 2"), LOCTEXT("TestToolTip", "TestToolTip"), FSlateIcon(), FUIAction());
|
|
|
|
MenuBuilder.AddMenuEntry(LOCTEXT("TestItem3", "Test Item 3"), LOCTEXT("TestToolTip", "TestToolTip"), FSlateIcon(), FUIAction());
|
|
|
|
MenuBuilder.AddSubMenu(LOCTEXT("SubMenu", "Sub Menu"), LOCTEXT("OpensASubmenu", "Opens a submenu"), FNewMenuDelegate::CreateStatic(&Local::FillSubMenuEntries));
|
|
|
|
MenuBuilder.AddSubMenu(LOCTEXT("SubMenu2", "Sub Menu2"), LOCTEXT("OpensASubmenu", "Opens a submenu"), FNewMenuDelegate::CreateStatic(&Local::FillSubMenuEntries));
|
|
}
|
|
};
|
|
|
|
FMenuBuilder NewMenu(true, NULL);
|
|
NewMenu.BeginSection("TestMenuModalWindow", LOCTEXT("MenuInAModalWindow", "Menu in a modal window"));
|
|
{
|
|
NewMenu.AddMenuEntry(LOCTEXT("TestItem1", "Test Item 1"), FText::GetEmpty(), FSlateIcon(), FUIAction());
|
|
NewMenu.AddSubMenu(LOCTEXT("SubMenu", "Sub Menu"), LOCTEXT("OpenASubmenu", "Opens a sub menu"), FNewMenuDelegate::CreateStatic(&Local::FillSubMenuEntries));
|
|
}
|
|
NewMenu.EndSection();
|
|
|
|
FWidgetPath WidgetPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath();
|
|
FSlateApplication::Get().PushMenu(SharedThis(this), WidgetPath, NewMenu.MakeWidget(), MouseEvent.GetScreenSpacePosition(), FPopupTransitionEffect(FPopupTransitionEffect::None));
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
TSharedPtr<SWindow> MyWindow;
|
|
bool bUserResponse;
|
|
};
|
|
|
|
UPackage* UUnrealEdEngine::GeneratePackageThumbnailsIfRequired(const TCHAR* Str, FOutputDevice& Ar, TArray<FString>& GeneratedThumbNamesList)
|
|
{
|
|
UPackage* Pkg = NULL;
|
|
if (FParse::Command(&Str, TEXT("SavePackage")))
|
|
{
|
|
FString TempFname;
|
|
if (FParse::Value(Str, TEXT("FILE="), TempFname) && ParseObject<UPackage>(Str, TEXT("Package="), Pkg, NULL))
|
|
{
|
|
if (Pkg == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Update any thumbnails for objects in this package that were modified or generate
|
|
// new thumbnails for objects that don't have any
|
|
|
|
bool bSilent = false;
|
|
FParse::Bool(Str, TEXT("SILENT="), bSilent);
|
|
|
|
// Make a list of packages to query (in our case, just the package we're saving)
|
|
TArray<UPackage*> Packages;
|
|
Packages.Add(Pkg);
|
|
|
|
// Allocate a new thumbnail map if we need one
|
|
if (!Pkg->ThumbnailMap)
|
|
{
|
|
Pkg->ThumbnailMap = MakeUnique<FThumbnailMap>();
|
|
}
|
|
|
|
// OK, now query all of the browsable objects in the package we're about to save
|
|
TArray<UObject*> BrowsableObjectsInPackage;
|
|
|
|
// Load the asset tools module to get access to thumbnail tools
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
|
|
|
|
// NOTE: The package should really be fully loaded before we try to generate thumbnails
|
|
UPackageTools::GetObjectsInPackages(
|
|
&Packages, // Packages to search
|
|
BrowsableObjectsInPackage); // Out: Objects
|
|
|
|
// Check to see if any of the objects need thumbnails generated
|
|
TSet<UObject*> ObjectsMissingThumbnails;
|
|
TSet<UObject*> ObjectsWithThumbnails;
|
|
for (int32 CurObjectIndex = 0; CurObjectIndex < BrowsableObjectsInPackage.Num(); ++CurObjectIndex)
|
|
{
|
|
UObject* CurObject = BrowsableObjectsInPackage[CurObjectIndex];
|
|
check(CurObject != NULL);
|
|
|
|
bool bUsesGenericThumbnail = AssetToolsModule.Get().AssetUsesGenericThumbnail(FAssetData(CurObject));
|
|
|
|
// Archetypes always use a shared thumbnail
|
|
if (CurObject->HasAllFlags(RF_ArchetypeObject))
|
|
{
|
|
bUsesGenericThumbnail = true;
|
|
}
|
|
|
|
bool bPrintThumbnailDiagnostics = false;
|
|
GConfig->GetBool(TEXT("Thumbnails"), TEXT("Debug"), bPrintThumbnailDiagnostics, GEditorPerProjectIni);
|
|
|
|
const FObjectThumbnail* ExistingThumbnail = ThumbnailTools::FindCachedThumbnail(CurObject->GetFullName());
|
|
if (bPrintThumbnailDiagnostics)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("Saving Thumb for %s"), *CurObject->GetFullName());
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT(" Thumb existed = %d"), (ExistingThumbnail != NULL) ? 1 : 0);
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT(" Shared Thumb = %d"), (bUsesGenericThumbnail) ? 1 : 0);
|
|
}
|
|
// if it's not generatable, let's make sure it doesn't have a custom thumbnail before saving
|
|
if (!ExistingThumbnail && bUsesGenericThumbnail)
|
|
{
|
|
// let it load the custom icons from disk
|
|
// @todo CB: Batch up requests for multiple thumbnails!
|
|
TArray<FName> ObjectFullNames;
|
|
FName ObjectFullNameFName(*CurObject->GetFullName());
|
|
ObjectFullNames.Add(ObjectFullNameFName);
|
|
|
|
// Load thumbnails
|
|
FThumbnailMap& LoadedThumbnails = Pkg->AccessThumbnailMap();
|
|
if (ThumbnailTools::ConditionallyLoadThumbnailsForObjects(ObjectFullNames, LoadedThumbnails))
|
|
{
|
|
// store off the names of the thumbnails that were loaded as part of a save so we can delete them after the save
|
|
GeneratedThumbNamesList.Add(ObjectFullNameFName.ToString());
|
|
|
|
if (bPrintThumbnailDiagnostics)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT(" Unloaded thumb loaded successfully"));
|
|
}
|
|
|
|
ExistingThumbnail = LoadedThumbnails.Find(ObjectFullNameFName);
|
|
if (bPrintThumbnailDiagnostics)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT(" Newly loaded thumb exists = %d"), (ExistingThumbnail != NULL) ? 1 : 0);
|
|
if (ExistingThumbnail)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT(" Thumb created after proper version = %d"), (ExistingThumbnail->IsCreatedAfterCustomThumbsEnabled()) ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
if (ExistingThumbnail && !ExistingThumbnail->IsCreatedAfterCustomThumbsEnabled())
|
|
{
|
|
if (bPrintThumbnailDiagnostics)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT(" WIPING OUT THUMBNAIL!!!!"));
|
|
}
|
|
|
|
// Casting away const to save memory behind the scenes
|
|
FObjectThumbnail* ThumbToClear = (FObjectThumbnail*)ExistingThumbnail;
|
|
ThumbToClear->SetImageSize(0, 0);
|
|
ThumbToClear->AccessImageData().Empty();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bPrintThumbnailDiagnostics)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT(" Unloaded thumb does not exist"));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bUsesGenericThumbnail)
|
|
{
|
|
// This is a generic thumbnail object, but it may have a custom thumbnail.
|
|
if (ExistingThumbnail != NULL && !ExistingThumbnail->IsEmpty())
|
|
{
|
|
ObjectsWithThumbnails.Add(CurObject);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This is not a generic thumbnail object, so if it is dirty or missing we will render it.
|
|
if (ExistingThumbnail != NULL && !ExistingThumbnail->IsEmpty() && !ExistingThumbnail->IsDirty())
|
|
{
|
|
ObjectsWithThumbnails.Add(CurObject);
|
|
}
|
|
else
|
|
{
|
|
ObjectsMissingThumbnails.Add(CurObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (BrowsableObjectsInPackage.Num() > 0)
|
|
{
|
|
// Missing some thumbnails, so go ahead and try to generate them now
|
|
|
|
// Start a busy cursor
|
|
const FScopedBusyCursor BusyCursor;
|
|
|
|
if (!bSilent)
|
|
{
|
|
const bool bWantProgressMeter = true;
|
|
GWarn->BeginSlowTask(NSLOCTEXT("UnrealEd", "SavingPackage_GeneratingThumbnails", "Generating thumbnails..."), bWantProgressMeter);
|
|
}
|
|
|
|
Ar.Logf(TEXT("OBJ SavePackage: Generating thumbnails for [%i] asset(s) in package [%s] ([%i] browsable assets)..."), ObjectsMissingThumbnails.Num(), *Pkg->GetName(), BrowsableObjectsInPackage.Num());
|
|
|
|
for (int32 CurObjectIndex = 0; CurObjectIndex < BrowsableObjectsInPackage.Num(); ++CurObjectIndex)
|
|
{
|
|
UObject* CurObject = BrowsableObjectsInPackage[CurObjectIndex];
|
|
check(CurObject != NULL);
|
|
|
|
if (!bSilent)
|
|
{
|
|
GWarn->UpdateProgress(CurObjectIndex, BrowsableObjectsInPackage.Num());
|
|
}
|
|
|
|
bool bNeedEmptyThumbnail = false;
|
|
if (ObjectsMissingThumbnails.Contains(CurObject) && !GIsAutomationTesting)
|
|
{
|
|
// Generate a thumbnail!
|
|
FObjectThumbnail* GeneratedThumbnail = ThumbnailTools::GenerateThumbnailForObjectToSaveToDisk(CurObject);
|
|
if (GeneratedThumbnail != NULL)
|
|
{
|
|
Ar.Logf(TEXT("OBJ SavePackage: Rendered thumbnail for [%s]"), *CurObject->GetFullName());
|
|
}
|
|
else
|
|
{
|
|
// Couldn't generate a thumb; perhaps this object doesn't support thumbnails?
|
|
bNeedEmptyThumbnail = true;
|
|
}
|
|
}
|
|
else if (!ObjectsWithThumbnails.Contains(CurObject))
|
|
{
|
|
// Even though this object uses a shared thumbnail, we'll add a "dummy thumbnail" to
|
|
// the package (zero dimension) for all browsable assets so that the Content Browser
|
|
// can quickly verify that existence of assets on the fly.
|
|
bNeedEmptyThumbnail = true;
|
|
}
|
|
|
|
// Create an empty thumbnail if we need to. All browsable assets need at least a placeholder
|
|
// thumbnail so the Content Browser can check for non-existent assets in the background
|
|
if (bNeedEmptyThumbnail)
|
|
{
|
|
UPackage* MyOutermostPackage = CurObject->GetOutermost();
|
|
ThumbnailTools::CacheEmptyThumbnail(CurObject->GetFullName(), MyOutermostPackage);
|
|
}
|
|
}
|
|
|
|
Ar.Logf(TEXT("OBJ SavePackage: Finished generating thumbnails for package [%s]"), *Pkg->GetName());
|
|
|
|
if (!bSilent)
|
|
{
|
|
GWarn->UpdateProgress(1, 1);
|
|
GWarn->EndSlowTask();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Pkg;
|
|
}
|
|
|
|
bool UUnrealEdEngine::HandleDumpModelGUIDCommand(const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
for (TObjectIterator<UModel> It; It; ++It)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("%s Guid = '%s'"), *It->GetFullName(), *It->LightingGuid.ToString());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::HandleModalTestCommand(const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
TSharedRef<SModalWindowTest> MessageBox = SNew(SModalWindowTest);
|
|
TSharedRef<SWindow> ModalWindow = SNew(SWindow)
|
|
.Title(LOCTEXT("WindowTitle", "Modal Window"))
|
|
.ClientSize(FVector2D(250, 100))
|
|
[MessageBox];
|
|
|
|
MessageBox->SetWindow(ModalWindow);
|
|
|
|
GEditor->EditorAddModalWindow(ModalWindow);
|
|
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("User response was: %s"), MessageBox->GetResponse() ? TEXT("OK") : TEXT("Cancel"));
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::HandleDisallowExportCommand(const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
|
|
|
TArray<FAssetData> SelectedAssets;
|
|
ContentBrowserModule.Get().GetSelectedAssets(SelectedAssets);
|
|
|
|
for (const FAssetData& AssetData: SelectedAssets)
|
|
{
|
|
UObject* Object = AssetData.GetAsset();
|
|
if (Object)
|
|
{
|
|
UPackage* Package = Object->GetOutermost();
|
|
Package->SetPackageFlags(EPackageFlags::PKG_DisallowExport);
|
|
Package->MarkPackageDirty();
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("Marked '%s' as not exportable"), *Object->GetName());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::HandleDumpBPClassesCommand(const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("--- Listing all blueprint generated classes ---"));
|
|
for (TObjectIterator<UClass> it; it; ++it)
|
|
{
|
|
const UClass* CurrentClass = *it;
|
|
if (CurrentClass && CurrentClass->ClassGeneratedBy)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT(" %s (%s)"), *CurrentClass->GetName(), *CurrentClass->GetOutermost()->GetName());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::HandleFindOutdateInstancesCommand(const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("--- Finding all actor instances with outdated classes ---"));
|
|
int32 NumFound = 0;
|
|
for (TObjectIterator<UObject> it; it; ++it)
|
|
{
|
|
const UObject* CurrentObj = *it;
|
|
if (CurrentObj->GetClass()->HasAnyClassFlags(CLASS_NewerVersionExists))
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT(" %s (%s)"), *CurrentObj->GetName(), *CurrentObj->GetClass()->GetName());
|
|
NumFound++;
|
|
}
|
|
}
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("Found %d instance(s)."), NumFound);
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::HandleDumpSelectionCommand(const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("Selected Actors:"));
|
|
PrivateDumpSelection(GetSelectedActors());
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("Selected Non-Actors:"));
|
|
PrivateDumpSelection(GetSelectedObjects());
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::HandleBuildLightingCommand(const TCHAR* Str, FOutputDevice& Ar, UWorld* InWorld)
|
|
{
|
|
return FEditorBuildUtils::EditorBuild(InWorld, FBuildOptions::BuildLighting);
|
|
}
|
|
|
|
bool UUnrealEdEngine::HandleBuildPathsCommand(const TCHAR* Str, FOutputDevice& Ar, UWorld* InWorld)
|
|
{
|
|
return FEditorBuildUtils::EditorBuild(InWorld, FBuildOptions::BuildAIPaths);
|
|
}
|
|
|
|
bool UUnrealEdEngine::HandleRecreateLandscapeCollisionCommand(const TCHAR* Str, FOutputDevice& Ar, UWorld* InWorld)
|
|
{
|
|
if (!PlayWorld && InWorld && InWorld->GetWorldSettings())
|
|
{
|
|
for (auto It = ULandscapeInfoMap::GetLandscapeInfoMap(InWorld).Map.CreateIterator(); It; ++It)
|
|
{
|
|
ULandscapeInfo* Info = It.Value();
|
|
Info->RecreateCollisionComponents();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::HandleRemoveLandscapeXYOffsetsCommand(const TCHAR* Str, FOutputDevice& Ar, UWorld* InWorld)
|
|
{
|
|
if (!PlayWorld && InWorld && InWorld->GetWorldSettings())
|
|
{
|
|
for (auto It = ULandscapeInfoMap::GetLandscapeInfoMap(InWorld).Map.CreateIterator(); It; ++It)
|
|
{
|
|
ULandscapeInfo* Info = It.Value();
|
|
Info->RemoveXYOffsets();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::HandleConvertMatineesCommand(const TCHAR* Str, FOutputDevice& Ar, UWorld* InWorld)
|
|
{
|
|
FVector StartLocation = FVector::ZeroVector;
|
|
if (InWorld)
|
|
{
|
|
ULevel* Level = InWorld->GetCurrentLevel();
|
|
if (!Level)
|
|
{
|
|
Level = InWorld->PersistentLevel;
|
|
}
|
|
check(Level);
|
|
for (TObjectIterator<UInterpData> It; It; ++It)
|
|
{
|
|
UInterpData* InterpData = *It;
|
|
if (InterpData->IsIn(Level))
|
|
{
|
|
// We dont care about renaming references or adding redirectors. References to this will be old seqact_interps
|
|
GEditor->RenameObject(InterpData, Level->GetOutermost(), *InterpData->GetName());
|
|
|
|
AMatineeActor* MatineeActor = Level->OwningWorld->SpawnActor<AMatineeActor>(StartLocation, FRotator::ZeroRotator);
|
|
StartLocation.Y += 50;
|
|
|
|
MatineeActor->MatineeData = InterpData;
|
|
FProperty* MatineeDataProp = NULL;
|
|
for (FProperty* Property = MatineeActor->GetClass()->PropertyLink; Property != NULL; Property = Property->PropertyLinkNext)
|
|
{
|
|
if (Property->GetName() == TEXT("MatineeData"))
|
|
{
|
|
MatineeDataProp = Property;
|
|
break;
|
|
}
|
|
}
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent(MatineeDataProp);
|
|
MatineeActor->PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::HandleDisasmScriptCommand(const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
FString ClassName;
|
|
|
|
if (FParse::Token(Str, ClassName, false))
|
|
{
|
|
FKismetBytecodeDisassembler::DisassembleAllFunctionsInClasses(Ar, ClassName);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::Exec(UWorld* InWorld, const TCHAR* Stream, FOutputDevice& Ar)
|
|
{
|
|
const TCHAR* Str = Stream;
|
|
// disallow set commands in the editor as that modifies the default object, affecting object serialization
|
|
if (FParse::Command(&Str, TEXT("SET")) || FParse::Command(&Str, TEXT("SETNOPEC")))
|
|
{
|
|
Ar.Logf(TEXT("Set commands not allowed in the editor"));
|
|
return true;
|
|
}
|
|
|
|
// for thumbnail reclamation post save
|
|
UPackage* Pkg = NULL;
|
|
// thumbs that are loaded expressly for the sake of saving. To be deleted again post-save
|
|
TArray<FString> ThumbNamesToUnload;
|
|
|
|
// Peek for the SavePackage command and generate thumbnails for the package if we need to
|
|
// NOTE: The actual package saving happens in the UEditorEngine::Exec_Obj, but we do the
|
|
// thumbnail generation here in UnrealEd
|
|
if (FParse::Command(&Str, TEXT("OBJ")) && !IsRunningCommandlet())
|
|
{
|
|
Pkg = GeneratePackageThumbnailsIfRequired(Str, Ar, ThumbNamesToUnload);
|
|
}
|
|
|
|
// If we don't have a viewport specified to catch the stat commands, use to the active viewport. If there is a game viewport ignore this as we do not want
|
|
if (GStatProcessingViewportClient == NULL && (GameViewport == NULL || GameViewport->IsSimulateInEditorViewport()))
|
|
{
|
|
GStatProcessingViewportClient = GLastKeyLevelEditingViewportClient ? GLastKeyLevelEditingViewportClient : GCurrentLevelEditingViewportClient;
|
|
}
|
|
|
|
bool bExecSucceeded = UEditorEngine::Exec(InWorld, Stream, Ar);
|
|
|
|
GStatProcessingViewportClient = NULL;
|
|
|
|
// if we loaded thumbs for saving, purge them back from the package
|
|
// append loaded thumbs onto the existing thumbs list
|
|
if (Pkg)
|
|
{
|
|
for (int32 ThumbRemoveIndex = 0; ThumbRemoveIndex < ThumbNamesToUnload.Num(); ++ThumbRemoveIndex)
|
|
{
|
|
ThumbnailTools::CacheThumbnail(ThumbNamesToUnload[ThumbRemoveIndex], NULL, Pkg);
|
|
}
|
|
}
|
|
|
|
if (bExecSucceeded)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (FParse::Command(&Str, TEXT("DUMPMODELGUIDS")))
|
|
{
|
|
HandleDumpModelGUIDCommand(Str, Ar);
|
|
}
|
|
|
|
if (FParse::Command(&Str, TEXT("ModalTest")))
|
|
{
|
|
HandleModalTestCommand(Str, Ar);
|
|
return true;
|
|
}
|
|
|
|
if (FParse::Command(&Str, TEXT("DisallowExport")))
|
|
{
|
|
HandleDisallowExportCommand(Str, Ar);
|
|
return true;
|
|
}
|
|
|
|
if (FParse::Command(&Str, TEXT("DumpBPClasses")))
|
|
{
|
|
HandleDumpBPClassesCommand(Str, Ar);
|
|
}
|
|
|
|
if (FParse::Command(&Str, TEXT("FindOutdatedInstances")))
|
|
{
|
|
HandleFindOutdateInstancesCommand(Str, Ar);
|
|
}
|
|
|
|
if (FParse::Command(&Str, TEXT("DUMPSELECTION")))
|
|
{
|
|
HandleDumpSelectionCommand(Str, Ar);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// EDIT
|
|
//
|
|
if (FParse::Command(&Str, TEXT("EDIT")))
|
|
{
|
|
return Exec_Edit(InWorld, Str, Ar);
|
|
}
|
|
//------------------------------------------------------------------------------------
|
|
// ACTOR: Actor-related functions
|
|
//
|
|
else if (FParse::Command(&Str, TEXT("ACTOR")))
|
|
{
|
|
return Exec_Actor(InWorld, Str, Ar);
|
|
}
|
|
//------------------------------------------------------------------------------------
|
|
// MODE management (Global EDITOR mode):
|
|
//
|
|
else if (FParse::Command(&Str, TEXT("MODE")))
|
|
{
|
|
return Exec_Mode(Str, Ar);
|
|
}
|
|
//----------------------------------------------------------------------------------
|
|
// PIVOT
|
|
//
|
|
else if (FParse::Command(&Str, TEXT("PIVOT")))
|
|
{
|
|
return Exec_Pivot(Str, Ar);
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("BUILDLIGHTING")))
|
|
{
|
|
HandleBuildLightingCommand(Str, Ar, InWorld);
|
|
}
|
|
// BUILD PATHS
|
|
else if (FParse::Command(&Str, TEXT("BUILDPATHS")))
|
|
{
|
|
HandleBuildPathsCommand(Str, Ar, InWorld);
|
|
}
|
|
#if WITH_EDITOR
|
|
else if (FParse::Command(&Str, TEXT("RecreateLandscapeCollision")))
|
|
{
|
|
// InWorld above is the PIE world if PIE is active, but this is specifically an editor command
|
|
UWorld* World = GetEditorWorldContext().World();
|
|
return HandleRecreateLandscapeCollisionCommand(Str, Ar, World);
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("RemoveLandscapeXYOffsets")))
|
|
{
|
|
// InWorld above is the PIE world if PIE is active, but this is specifically an editor command
|
|
UWorld* World = GetEditorWorldContext().World();
|
|
return HandleRemoveLandscapeXYOffsetsCommand(Str, Ar, World);
|
|
}
|
|
#endif // WITH_EDITOR
|
|
else if (FParse::Command(&Str, TEXT("CONVERTMATINEES")))
|
|
{
|
|
return HandleConvertMatineesCommand(Str, Ar, InWorld);
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("DISASMSCRIPT")))
|
|
{
|
|
return HandleDisasmScriptCommand(Str, Ar);
|
|
}
|
|
#if WITH_EDITOR
|
|
else if (FParse::Command(&Str, TEXT("cook")))
|
|
{
|
|
if (CookServer)
|
|
{
|
|
return CookServer->Exec(InWorld, Str, Ar);
|
|
}
|
|
}
|
|
#endif
|
|
else if (FParse::Command(&Str, TEXT("GROUPS")))
|
|
{
|
|
return Exec_Group(Str, Ar);
|
|
}
|
|
// #ttp 322815 - GDC, temp exec command for scaling the level
|
|
else if (FParse::Command(&Str, TEXT("SCALELEVEL")))
|
|
{
|
|
// e.g. ScaleLevel Scale=1,2,3 Snap=4 // Non-uniform scaling
|
|
// e.g. ScaleLevel Scale=2 Snap=4 // Uniform scaling
|
|
|
|
// We can only scale radii if the level is given uniform scaling
|
|
bool bScale = false;
|
|
bool bScaleRadii = false;
|
|
|
|
float Scale = 1.0f;
|
|
FString ScaleStr;
|
|
FVector ScaleVec(Scale);
|
|
if (FParse::Value(Str, TEXT("Scale="), ScaleStr, false) && GetFVECTOR(*ScaleStr, ScaleVec))
|
|
{
|
|
// Update uniform incase the user used uniform scale with a vector parm
|
|
Scale = ScaleVec.X;
|
|
bScaleRadii = (Scale == ScaleVec.Y && Scale == ScaleVec.Z ? true : false);
|
|
bScale = true;
|
|
}
|
|
else if (FParse::Value(Str, TEXT("Scale="), Scale))
|
|
{
|
|
// Copy the uniform scale to our vector param
|
|
ScaleVec = FVector(Scale);
|
|
bScaleRadii = true;
|
|
bScale = true;
|
|
}
|
|
|
|
// Can we scale the level?
|
|
if (bScale)
|
|
{
|
|
// See if a snap value was specified for the grid
|
|
float NewGridSize;
|
|
const bool bSnap = FParse::Value(Str, TEXT("Snap="), NewGridSize);
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ScalingLevel", "Scaling Level"));
|
|
|
|
// If it was, force the grid size to be this value temporarily
|
|
const ULevelEditorViewportSettings* ViewportSettings = GetDefault<ULevelEditorViewportSettings>();
|
|
TArray<float>& PosGridSizes = const_cast<TArray<float>&>(GetCurrentPositionGridArray());
|
|
float& CurGridSize = PosGridSizes[ViewportSettings->CurrentPosGridSize];
|
|
const float OldGridSize = CurGridSize;
|
|
if (bSnap)
|
|
{
|
|
CurGridSize = NewGridSize;
|
|
}
|
|
|
|
// "iterates through each actor in the current level"
|
|
bool bBuildBSPs = false;
|
|
for (TActorIterator<AActor> It(InWorld); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
|
|
// "It should skip all static meshes. The reason for this is that they will scale the static meshes via the static mesh editor with the new BuildScale setting."
|
|
if (Actor)
|
|
{
|
|
/*if (AStaticMeshActor* StaticMesh = Cast< AStaticMeshActor >( Actor ))
|
|
{
|
|
// Skip static meshes?
|
|
}
|
|
else*/
|
|
if (ABrush* Brush = Cast<ABrush>(Actor))
|
|
{
|
|
// "For volumes and brushes scale each vertex by the specified amount."
|
|
if (!FActorEditorUtils::IsABuilderBrush(Brush) && Brush->Brush)
|
|
{
|
|
const FVector OldLocation = Brush->GetActorLocation();
|
|
const FVector NewLocation = OldLocation * ScaleVec;
|
|
Brush->Modify();
|
|
Brush->SetActorLocation(NewLocation);
|
|
|
|
Brush->Brush->Modify();
|
|
for (int32 poly = 0; poly < Brush->Brush->Polys->Element.Num(); poly++)
|
|
{
|
|
FPoly* Poly = &(Brush->Brush->Polys->Element[poly]);
|
|
|
|
Poly->TextureU /= ScaleVec;
|
|
Poly->TextureV /= ScaleVec;
|
|
Poly->Base = ((Poly->Base - Brush->GetPivotOffset()) * ScaleVec) + Brush->GetPivotOffset();
|
|
|
|
for (int32 vtx = 0; vtx < Poly->Vertices.Num(); vtx++)
|
|
{
|
|
Poly->Vertices[vtx] = ((Poly->Vertices[vtx] - Brush->GetPivotOffset()) * ScaleVec) + Brush->GetPivotOffset();
|
|
|
|
// "Then snap the vertices new positions by the specified Snap amount"
|
|
if (bSnap)
|
|
{
|
|
FSnappingUtils::SnapPointToGrid(Poly->Vertices[vtx], FVector(0, 0, 0));
|
|
}
|
|
}
|
|
|
|
Poly->CalcNormal();
|
|
}
|
|
|
|
Brush->Brush->BuildBound();
|
|
Brush->MarkPackageDirty();
|
|
bBuildBSPs = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// "Do not scale any child components."
|
|
if (Actor->GetAttachParentActor() == NULL)
|
|
{
|
|
// "Only the root component"
|
|
if (USceneComponent* RootComponent = Actor->GetRootComponent())
|
|
{
|
|
RootComponent->Modify();
|
|
|
|
// "scales root component by the specified amount."
|
|
const FVector OldLocation = RootComponent->GetComponentLocation();
|
|
const FVector NewLocation = OldLocation * ScaleVec;
|
|
RootComponent->SetWorldLocation(NewLocation);
|
|
|
|
// Scale up the triggers
|
|
if (UBoxComponent* BoxComponent = Cast<UBoxComponent>(RootComponent))
|
|
{
|
|
const FVector OldExtent = BoxComponent->GetUnscaledBoxExtent();
|
|
const FVector NewExtent = OldExtent * ScaleVec;
|
|
BoxComponent->SetBoxExtent(NewExtent);
|
|
}
|
|
|
|
if (bScaleRadii)
|
|
{
|
|
if (USphereComponent* SphereComponent = Cast<USphereComponent>(RootComponent))
|
|
{
|
|
const float OldRadius = SphereComponent->GetUnscaledSphereRadius();
|
|
const float NewRadius = OldRadius * Scale;
|
|
SphereComponent->SetSphereRadius(NewRadius);
|
|
}
|
|
else if (UCapsuleComponent* CapsuleComponent = Cast<UCapsuleComponent>(RootComponent))
|
|
{
|
|
float OldRadius, OldHalfHeight;
|
|
CapsuleComponent->GetUnscaledCapsuleSize(OldRadius, OldHalfHeight);
|
|
const float NewRadius = OldRadius * Scale;
|
|
const float NewHalfHeight = OldHalfHeight * Scale;
|
|
CapsuleComponent->SetCapsuleSize(NewRadius, NewHalfHeight);
|
|
}
|
|
else if (UPointLightComponent* PointLightComponent = Cast<UPointLightComponent>(RootComponent))
|
|
{
|
|
PointLightComponent->AttenuationRadius *= Scale;
|
|
PointLightComponent->SourceRadius *= Scale;
|
|
PointLightComponent->SourceLength *= Scale;
|
|
}
|
|
else if (URadialForceComponent* RadialForceComponent = Cast<URadialForceComponent>(RootComponent))
|
|
{
|
|
RadialForceComponent->Radius *= Scale;
|
|
}
|
|
/* Other components that have radii
|
|
UPathFollowingComponent
|
|
USmartNavLinkComponent
|
|
UPawnSensingComponent
|
|
USphereReflectionCaptureComponent
|
|
UAIPerceptionComponent
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restore snap
|
|
if (bSnap)
|
|
{
|
|
CurGridSize = OldGridSize;
|
|
}
|
|
|
|
// Kick off a rebuild if any of the bsps have changed
|
|
if (bBuildBSPs)
|
|
{
|
|
GUnrealEd->Exec(InWorld, TEXT("MAP REBUILD ALLVISIBLE"));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("ScaleMeshes")))
|
|
{
|
|
bool bScale = false;
|
|
bool bScaleVec = false;
|
|
|
|
// Was just a scale specified
|
|
float Scale = 1.0f;
|
|
FVector BoxVec(Scale);
|
|
if (FParse::Value(Str, TEXT("Scale="), Scale))
|
|
{
|
|
bScale = true;
|
|
}
|
|
else
|
|
{
|
|
// or was a bounding box specified instead
|
|
FString BoxStr;
|
|
if ((FParse::Value(Str, TEXT("BBOX="), BoxStr, false) || FParse::Value(Str, TEXT("FFD="), BoxStr, false)) && GetFVECTOR(*BoxStr, BoxVec))
|
|
{
|
|
bScaleVec = true;
|
|
}
|
|
}
|
|
|
|
if (bScale || bScaleVec)
|
|
{
|
|
USelection* SelectedObjects = GetSelectedObjects();
|
|
TArray<UStaticMesh*> SelectedMeshes;
|
|
SelectedObjects->GetSelectedObjects(SelectedMeshes);
|
|
|
|
if (SelectedMeshes.Num())
|
|
{
|
|
GWarn->BeginSlowTask(NSLOCTEXT("UnrealEd", "ScalingStaticMeshes", "Scaling Static Meshes"), true, true);
|
|
|
|
for (int32 MeshIndex = 0; MeshIndex < SelectedMeshes.Num(); ++MeshIndex)
|
|
{
|
|
UStaticMesh* Mesh = SelectedMeshes[MeshIndex];
|
|
|
|
if (Mesh && Mesh->GetNumSourceModels() > 0)
|
|
{
|
|
Mesh->Modify();
|
|
|
|
GWarn->StatusUpdate(MeshIndex + 1, SelectedMeshes.Num(), FText::Format(NSLOCTEXT("UnrealEd", "ScalingStaticMeshes_Value", "Static Mesh: {0}"), FText::FromString(Mesh->GetName())));
|
|
|
|
FStaticMeshSourceModel& Model = Mesh->GetSourceModel(0);
|
|
|
|
FVector ScaleVec(Scale, Scale, Scale); // bScale
|
|
if (bScaleVec)
|
|
{
|
|
FBoxSphereBounds Bounds = Mesh->GetBounds();
|
|
ScaleVec = BoxVec / (Bounds.BoxExtent * 2.0f); // x2 as artists wanted length not radius
|
|
}
|
|
Model.BuildSettings.BuildScale3D *= ScaleVec; // Scale by the current modification
|
|
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("Rescaling mesh '%s' with scale: %s"), *Mesh->GetName(), *Model.BuildSettings.BuildScale3D.ToString());
|
|
|
|
Mesh->Build();
|
|
}
|
|
}
|
|
GWarn->EndSlowTask();
|
|
}
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("ClearSourceFiles")))
|
|
{
|
|
struct Local
|
|
{
|
|
static bool RemoveSourcePath(const FAssetImportInfo& ImportInfo, const FAssetData& AssetData, const TArray<FString>* SearchTerms)
|
|
{
|
|
FAssetImportInfo AssetImportInfo;
|
|
|
|
bool bModified = false;
|
|
for (const auto& File: ImportInfo.SourceFiles)
|
|
{
|
|
const bool bRemoveFile = File.RelativeFilename.IsEmpty() || !SearchTerms ||
|
|
SearchTerms->ContainsByPredicate([&](const FString& SearchTerm)
|
|
{
|
|
return File.RelativeFilename.Contains(SearchTerm);
|
|
});
|
|
|
|
if (bRemoveFile)
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("Removing Path: %s"), *File.RelativeFilename);
|
|
bModified = true;
|
|
}
|
|
else
|
|
{
|
|
AssetImportInfo.Insert(File);
|
|
}
|
|
}
|
|
|
|
if (bModified)
|
|
{
|
|
if (UObject* Asset = AssetData.GetAsset())
|
|
{
|
|
UAssetImportData* ImportData = nullptr;
|
|
|
|
// Root out the asset import data property
|
|
for (FObjectProperty* Property: TFieldRange<FObjectProperty>(Asset->GetClass()))
|
|
{
|
|
ImportData = Cast<UAssetImportData>(Property->GetObjectPropertyValue(Property->ContainerPtrToValuePtr<UObject*>(Asset)));
|
|
if (ImportData)
|
|
{
|
|
Asset->Modify();
|
|
ImportData->SourceData = AssetImportInfo;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void RemoveSourcePaths(const TArray<FAssetData>& AllAssets, const TArray<FString>* SearchTerms)
|
|
{
|
|
FScopedSlowTask SlowTask(AllAssets.Num(), NSLOCTEXT("UnrealEd", "ClearingSourceFiles", "Clearing Source Files"));
|
|
SlowTask.MakeDialog(true);
|
|
|
|
for (const FAssetData& Asset: AllAssets)
|
|
{
|
|
SlowTask.EnterProgressFrame();
|
|
|
|
// Optimization - check the asset has import information before loading it
|
|
TOptional<FAssetImportInfo> ImportInfo = FAssetSourceFilenameCache::ExtractAssetImportInfo(Asset);
|
|
if (ImportInfo.IsSet() && ImportInfo->SourceFiles.Num())
|
|
{
|
|
RemoveSourcePath(ImportInfo.GetValue(), Asset, SearchTerms);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
|
|
FString Path;
|
|
FParse::Value(Str, TEXT("Path="), Path, false);
|
|
|
|
TArray<FAssetData> AllAssets;
|
|
if (!Path.IsEmpty())
|
|
{
|
|
AssetRegistryModule.Get().GetAssetsByPath(*Path, AllAssets, true);
|
|
}
|
|
else
|
|
{
|
|
AssetRegistryModule.Get().GetAllAssets(AllAssets);
|
|
}
|
|
|
|
FString SearchTermStr;
|
|
if (FParse::Value(Str, TEXT("Find="), SearchTermStr, false))
|
|
{
|
|
// Searching for particular paths to remove
|
|
TArray<FString> SearchTerms;
|
|
SearchTermStr.ParseIntoArray(SearchTerms, TEXT(","), true);
|
|
|
|
TArray<UObject*> ModifiedObjects;
|
|
if (SearchTerms.Num())
|
|
{
|
|
Local::RemoveSourcePaths(AllAssets, &SearchTerms);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove every source path on any asset
|
|
Local::RemoveSourcePaths(AllAssets, nullptr);
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("RenameAssets")))
|
|
{
|
|
FString SearchTermStr;
|
|
if (FParse::Value(Str, TEXT("Find="), SearchTermStr))
|
|
{
|
|
FString ReplaceStr;
|
|
FParse::Value(Str, TEXT("Replace="), ReplaceStr);
|
|
|
|
FString AutoCheckOutStr;
|
|
FParse::Value(Str, TEXT("AutoCheckOut="), AutoCheckOutStr);
|
|
AutoCheckOutStr = AutoCheckOutStr.ToLower();
|
|
bool bAutoCheckOut = (AutoCheckOutStr == "yes" || AutoCheckOutStr == "true" || AutoCheckOutStr == "1");
|
|
|
|
GWarn->BeginSlowTask(NSLOCTEXT("UnrealEd", "RenamingAssets", "Renaming Assets"), true, true);
|
|
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
|
|
|
TArray<FAssetData> AllAssets;
|
|
AssetRegistryModule.Get().GetAllAssets(AllAssets);
|
|
|
|
TArray<FAssetRenameData> AssetsToRename;
|
|
for (const FAssetData& Asset: AllAssets)
|
|
{
|
|
bool bRenamedPath = false;
|
|
bool bRenamedAsset = false;
|
|
FString NewAssetName = Asset.AssetName.ToString();
|
|
FString NewPathName = Asset.PackagePath.ToString();
|
|
if (NewAssetName.Contains(SearchTermStr))
|
|
{
|
|
FString TempPathName = NewAssetName.Replace(*SearchTermStr, *ReplaceStr);
|
|
if (!TempPathName.IsEmpty())
|
|
{
|
|
NewAssetName = TempPathName;
|
|
bRenamedAsset = true;
|
|
}
|
|
}
|
|
|
|
if (NewPathName.Contains(SearchTermStr))
|
|
{
|
|
FString TempPathName = NewPathName.Replace(*SearchTermStr, *ReplaceStr);
|
|
FPaths::RemoveDuplicateSlashes(TempPathName);
|
|
|
|
if (!TempPathName.IsEmpty())
|
|
{
|
|
NewPathName = TempPathName;
|
|
bRenamedPath = true;
|
|
}
|
|
}
|
|
|
|
if (bRenamedAsset || bRenamedPath)
|
|
{
|
|
FAssetRenameData RenameData(Asset.GetAsset(), NewPathName, NewAssetName);
|
|
AssetsToRename.Add(RenameData);
|
|
}
|
|
}
|
|
|
|
if (AssetsToRename.Num() > 0)
|
|
{
|
|
AssetTools.RenameAssetsWithDialog(AssetsToRename, bAutoCheckOut);
|
|
}
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("HighResShot")))
|
|
{
|
|
if (GetHighResScreenshotConfig().ParseConsoleCommand(Str, Ar))
|
|
{
|
|
TakeHighResScreenShots();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("EditorShot")) || FParse::Command(&Str, TEXT("EditorScreenShot")))
|
|
{
|
|
struct Local
|
|
{
|
|
static void TakeScreenShotOfWidget(TSharedRef<SWidget> InWidget)
|
|
{
|
|
TArray<FColor> OutImageData;
|
|
FIntVector OutImageSize;
|
|
if (FSlateApplication::Get().TakeScreenshot(InWidget, OutImageData, OutImageSize))
|
|
{
|
|
FString FileName;
|
|
const FString BaseFileName = GetDefault<ULevelEditorMiscSettings>()->EditorScreenshotSaveDirectory.Path / TEXT("EditorScreenshot");
|
|
FFileHelper::GenerateNextBitmapFilename(BaseFileName, TEXT("bmp"), FileName);
|
|
FFileHelper::CreateBitmap(*FileName, OutImageSize.X, OutImageSize.Y, OutImageData.GetData());
|
|
}
|
|
}
|
|
};
|
|
|
|
if (FSlateApplication::IsInitialized())
|
|
{
|
|
if (FParse::Command(&Str, TEXT("All")))
|
|
{
|
|
TArray<TSharedRef<SWindow>> OpenWindows;
|
|
FSlateApplication::Get().GetAllVisibleWindowsOrdered(OpenWindows);
|
|
for (int32 WindowId = 0; WindowId < OpenWindows.Num(); ++WindowId)
|
|
{
|
|
Local::TakeScreenShotOfWidget(OpenWindows[WindowId]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FString WindowNameStr;
|
|
if (FParse::Value(Str, TEXT("Name="), WindowNameStr))
|
|
{
|
|
TArray<TSharedRef<SWindow>> OpenWindows;
|
|
FSlateApplication::Get().GetAllVisibleWindowsOrdered(OpenWindows);
|
|
for (int32 WindowId = 0; WindowId < OpenWindows.Num(); ++WindowId)
|
|
{
|
|
FString CurrentWindowName = OpenWindows[WindowId]->GetTitle().ToString();
|
|
|
|
// Strip off the * from the end if it exists
|
|
if (CurrentWindowName.EndsWith(TEXT("*"), ESearchCase::CaseSensitive))
|
|
{
|
|
CurrentWindowName.LeftChopInline(1, false);
|
|
}
|
|
|
|
if (CurrentWindowName == WindowNameStr)
|
|
{
|
|
Local::TakeScreenShotOfWidget(OpenWindows[WindowId]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<SWindow> ActiveWindow = FSlateApplication::Get().GetActiveTopLevelWindow();
|
|
if (ActiveWindow.IsValid())
|
|
{
|
|
Local::TakeScreenShotOfWidget(ActiveWindow.ToSharedRef());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UUnrealEdEngine::AnyWorldsAreDirty(UWorld* InWorld) const
|
|
{
|
|
// Get the set of all reference worlds.
|
|
TArray<UWorld*> WorldsArray;
|
|
EditorLevelUtils::GetWorlds(InWorld, WorldsArray, true);
|
|
|
|
if (WorldsArray.Num() > 0)
|
|
{
|
|
FString FinalFilename;
|
|
for (int32 WorldIndex = 0; WorldIndex < WorldsArray.Num(); ++WorldIndex)
|
|
{
|
|
UWorld* World = WorldsArray[WorldIndex];
|
|
UPackage* Package = Cast<UPackage>(World->GetOuter());
|
|
check(Package);
|
|
|
|
// The world needs saving if...
|
|
if (Package->IsDirty())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UUnrealEdEngine::AnyContentPackagesAreDirty() const
|
|
{
|
|
const UPackage* TransientPackage = GetTransientPackage();
|
|
|
|
// Check all packages for dirty, non-map, non-transient packages
|
|
for (TObjectIterator<UPackage> PackageIter; PackageIter; ++PackageIter)
|
|
{
|
|
UPackage* CurPackage = *PackageIter;
|
|
|
|
// The package needs saving if it's not the transient package
|
|
if (CurPackage && (CurPackage != TransientPackage) && CurPackage->IsDirty())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UUnrealEdEngine::IsTemplateMap(const FString& MapName) const
|
|
{
|
|
for (TArray<FTemplateMapInfo>::TConstIterator It(TemplateMapInfos); It; ++It)
|
|
{
|
|
if (It->Map == MapName)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UUnrealEdEngine::IsUserInteracting()
|
|
{
|
|
// Check to see if the user is in the middle of a drag operation.
|
|
bool bUserIsInteracting = false;
|
|
for (const FEditorViewportClient* VC: GetAllViewportClients())
|
|
{
|
|
// Check for tracking and capture. If a viewport has mouse capture, it could be locking the mouse to the viewport, which means if we prompt with a dialog
|
|
// while the mouse is locked to a viewport, we wont be able to interact with the dialog.
|
|
if (VC->IsTracking() || (VC->Viewport && VC->Viewport->HasMouseCapture()))
|
|
{
|
|
bUserIsInteracting = true;
|
|
break;
|
|
}
|
|
}
|
|
return bUserIsInteracting;
|
|
}
|
|
|
|
void UUnrealEdEngine::ShowPackageNotification()
|
|
{
|
|
if (!FApp::IsUnattended())
|
|
{
|
|
// Defer prompting for checkout if we cant prompt because of the following:
|
|
// The user is interacting with something,
|
|
// We are performing a slow task
|
|
// We have a play world
|
|
// The user disabled prompting on package modification
|
|
// A window has capture on the mouse
|
|
bool bCanPrompt = !IsUserInteracting() && !GIsSlowTask && !PlayWorld && GetDefault<UEditorLoadingSavingSettings>()->bPromptForCheckoutOnAssetModification && (FSlateApplication::Get().GetMouseCaptureWindow() == NULL);
|
|
|
|
if (bCanPrompt)
|
|
{
|
|
bShowPackageNotification = false;
|
|
bool bNeedWarningDialog = false;
|
|
for (const auto& Entry: PackageToNotifyState)
|
|
{
|
|
if (Entry.Value == NS_PendingWarning)
|
|
{
|
|
bNeedWarningDialog = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// The user is not interacting with anything, prompt to checkout packages that have been modified
|
|
|
|
struct Local
|
|
{
|
|
static void OpenCheckOutDialog()
|
|
{
|
|
GUnrealEd->PromptToCheckoutModifiedPackages(true);
|
|
}
|
|
};
|
|
|
|
if (bNeedWarningDialog)
|
|
{
|
|
Local::OpenCheckOutDialog();
|
|
}
|
|
else
|
|
{
|
|
int32 NumPackagesToCheckOut = GetNumDirtyPackagesThatNeedCheckout();
|
|
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("NumFiles"), NumPackagesToCheckOut);
|
|
|
|
FText ErrorText = FText::Format(NSLOCTEXT("SourceControl", "CheckOutNotification", "{NumFiles} files need check-out!"), Args);
|
|
|
|
if (!CheckOutNotificationWeakPtr.IsValid())
|
|
{
|
|
FNotificationInfo ErrorNotification(ErrorText);
|
|
ErrorNotification.bFireAndForget = true;
|
|
;
|
|
ErrorNotification.Hyperlink = FSimpleDelegate::CreateStatic(&Local::OpenCheckOutDialog);
|
|
ErrorNotification.HyperlinkText = NSLOCTEXT("SourceControl", "CheckOutHyperlinkText", "Check-Out");
|
|
ErrorNotification.ExpireDuration = 10.0f; // Need this message to last a little longer than normal since the user will probably want to click the hyperlink to check out files
|
|
ErrorNotification.bUseThrobber = true;
|
|
|
|
// For adding notifications.
|
|
CheckOutNotificationWeakPtr = FSlateNotificationManager::Get().AddNotification(ErrorNotification);
|
|
}
|
|
else
|
|
{
|
|
CheckOutNotificationWeakPtr.Pin()->SetText(ErrorText);
|
|
CheckOutNotificationWeakPtr.Pin()->ExpireAndFadeout();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UUnrealEdEngine::PromptToCheckoutModifiedPackages(bool bPromptAll)
|
|
{
|
|
TArray<UPackage*> PackagesToCheckout;
|
|
if (bPromptAll)
|
|
{
|
|
for (TMap<TWeakObjectPtr<UPackage>, uint8>::TIterator It(PackageToNotifyState); It; ++It)
|
|
{
|
|
if (It.Key().IsValid())
|
|
{
|
|
PackagesToCheckout.Add(It.Key().Get());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (TMap<TWeakObjectPtr<UPackage>, uint8>::TIterator It(PackageToNotifyState); It; ++It)
|
|
{
|
|
if (It.Key().IsValid() && (It.Value() == NS_PendingWarning || It.Value() == NS_PendingPrompt))
|
|
{
|
|
PackagesToCheckout.Add(It.Key().Get());
|
|
It.Value() = NS_DialogPrompted;
|
|
}
|
|
}
|
|
}
|
|
|
|
const bool bCheckDirty = true;
|
|
const bool bPromptingAfterModify = true;
|
|
FEditorFileUtils::PromptToCheckoutPackages(bCheckDirty, PackagesToCheckout, NULL, NULL, bPromptingAfterModify);
|
|
}
|
|
|
|
int32 UUnrealEdEngine::InternalGetNumDirtyPackagesThatNeedCheckout(bool bCheckIfAny) const
|
|
{
|
|
int32 PackageCount = 0;
|
|
|
|
if (ISourceControlModule::Get().IsEnabled())
|
|
{
|
|
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
|
for (TMap<TWeakObjectPtr<UPackage>, uint8>::TConstIterator It(PackageToNotifyState); It; ++It)
|
|
{
|
|
const UPackage* Package = It.Key().Get();
|
|
if (Package != NULL)
|
|
{
|
|
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(Package, EStateCacheUsage::Use);
|
|
if (SourceControlState.IsValid() && (SourceControlState->CanCheckout() || !SourceControlState->IsCurrent() || SourceControlState->IsCheckedOutOther()))
|
|
{
|
|
++PackageCount;
|
|
if (bCheckIfAny)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return PackageCount;
|
|
}
|
|
|
|
int32 UUnrealEdEngine::GetNumDirtyPackagesThatNeedCheckout() const
|
|
{
|
|
return InternalGetNumDirtyPackagesThatNeedCheckout(false);
|
|
}
|
|
|
|
bool UUnrealEdEngine::DoDirtyPackagesNeedCheckout() const
|
|
{
|
|
return InternalGetNumDirtyPackagesThatNeedCheckout(true) > 0;
|
|
}
|
|
|
|
bool UUnrealEdEngine::Exec_Edit(UWorld* InWorld, const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
const bool bComponentsSelected = GetSelectedComponentCount() > 0;
|
|
|
|
if (FParse::Command(&Str, TEXT("CUT")))
|
|
{
|
|
if (GLevelEditorModeTools().ProcessEditCut())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bComponentsSelected)
|
|
{
|
|
// Same transaction language used in CopySelectedActorsToClipboard below
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "Cut", "Cut"));
|
|
|
|
edactCopySelected(InWorld);
|
|
edactDeleteSelected(InWorld);
|
|
}
|
|
else
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "Cut", "Cut"));
|
|
FEditorDelegates::OnEditCutActorsBegin.Broadcast();
|
|
CopySelectedActorsToClipboard(InWorld, true);
|
|
FEditorDelegates::OnEditCutActorsEnd.Broadcast();
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("COPY")))
|
|
{
|
|
if (GLevelEditorModeTools().ProcessEditCopy())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bComponentsSelected)
|
|
{
|
|
edactCopySelected(InWorld);
|
|
}
|
|
else
|
|
{
|
|
FEditorDelegates::OnEditCopyActorsBegin.Broadcast();
|
|
CopySelectedActorsToClipboard(InWorld, false);
|
|
FEditorDelegates::OnEditCopyActorsEnd.Broadcast();
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("PASTE")))
|
|
{
|
|
if (GLevelEditorModeTools().ProcessEditPaste())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bComponentsSelected)
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "PasteComponents", "Paste Components"));
|
|
edactPasteSelected(InWorld, false, false, true);
|
|
}
|
|
else
|
|
{
|
|
// How should this paste be handled
|
|
EPasteTo PasteTo = PT_OriginalLocation;
|
|
FText TransDescription = NSLOCTEXT("UnrealEd", "Paste", "Paste");
|
|
|
|
FString TempStr;
|
|
if (FParse::Value(Str, TEXT("TO="), TempStr))
|
|
{
|
|
if (!FCString::Strcmp(*TempStr, TEXT("HERE")))
|
|
{
|
|
PasteTo = PT_Here;
|
|
TransDescription = NSLOCTEXT("UnrealEd", "PasteHere", "Paste Here");
|
|
}
|
|
else
|
|
{
|
|
if (!FCString::Strcmp(*TempStr, TEXT("ORIGIN")))
|
|
{
|
|
PasteTo = PT_WorldOrigin;
|
|
TransDescription = NSLOCTEXT("UnrealEd", "PasteToWorldOrigin", "Paste To World Origin");
|
|
}
|
|
}
|
|
}
|
|
|
|
const FScopedTransaction Transaction(TransDescription);
|
|
FEditorDelegates::OnEditPasteActorsBegin.Broadcast();
|
|
PasteSelectedActorsFromClipboard(InWorld, TransDescription, PasteTo);
|
|
FEditorDelegates::OnEditPasteActorsEnd.Broadcast();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UUnrealEdEngine::Exec_Pivot(const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
if (FParse::Command(&Str, TEXT("HERE")))
|
|
{
|
|
NoteActorMovement();
|
|
SetPivot(ClickLocation, false, false);
|
|
FinishAllSnaps();
|
|
SetPivotMovedIndependently(true);
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("SNAPPED")))
|
|
{
|
|
NoteActorMovement();
|
|
SetPivot(ClickLocation, true, false);
|
|
FinishAllSnaps();
|
|
SetPivotMovedIndependently(true);
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("CENTERSELECTION")))
|
|
{
|
|
NoteActorMovement();
|
|
|
|
// Figure out the center location of all selections
|
|
|
|
int32 Count = 0;
|
|
FVector Center(0, 0, 0);
|
|
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = CastChecked<AActor>(*It);
|
|
|
|
if (ABrush* Brush = Cast<ABrush>(Actor))
|
|
{
|
|
// Treat brushes as a special case; calculate an effective position from the center point of the vertices.
|
|
// This way, "Center on Selection" has a special meaning for brushes.
|
|
TSet<FVector> UniqueVertices;
|
|
FVector VertexCenter = FVector::ZeroVector;
|
|
|
|
if (Brush->Brush && Brush->Brush->Polys)
|
|
{
|
|
for (const auto& Element: Brush->Brush->Polys->Element)
|
|
{
|
|
for (const auto& Vertex: Element.Vertices)
|
|
{
|
|
UniqueVertices.Add(Vertex);
|
|
}
|
|
}
|
|
|
|
for (const auto& Vertex: UniqueVertices)
|
|
{
|
|
VertexCenter += Vertex;
|
|
}
|
|
|
|
if (UniqueVertices.Num() > 0)
|
|
{
|
|
VertexCenter /= UniqueVertices.Num();
|
|
}
|
|
}
|
|
|
|
Center += Brush->GetTransform().TransformPosition(VertexCenter);
|
|
}
|
|
else
|
|
{
|
|
Center += Actor->GetActorLocation();
|
|
}
|
|
|
|
Count++;
|
|
}
|
|
|
|
if (Count > 0)
|
|
{
|
|
FVector CenterLocation = Center / Count;
|
|
UnsnappedClickLocation = CenterLocation;
|
|
ClickLocation = CenterLocation;
|
|
ClickPlane = FPlane(0.f, 0.f, 0.f, 0.f);
|
|
|
|
SetPivot(ClickLocation, false, false);
|
|
FinishAllSnaps();
|
|
SetPivotMovedIndependently(true);
|
|
}
|
|
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void MirrorActors(const FVector& MirrorScale)
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MirroringActors", "Mirroring Actors"));
|
|
|
|
// Fires ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
const FVector PivotLocation = GLevelEditorModeTools().PivotLocation;
|
|
|
|
Actor->Modify();
|
|
Actor->EditorApplyMirror(MirrorScale, PivotLocation);
|
|
|
|
ABrush* Brush = Cast<ABrush>(Actor);
|
|
if (Brush && Brush->GetBrushComponent())
|
|
{
|
|
Brush->GetBrushComponent()->RequestUpdateBrushCollision();
|
|
}
|
|
|
|
Actor->InvalidateLightingCache();
|
|
Actor->PostEditMove(true);
|
|
|
|
Actor->MarkPackageDirty();
|
|
LevelDirtyCallback.Request();
|
|
}
|
|
|
|
if (UBrushEditingSubsystem* BrushSubsystem = GEditor->GetEditorSubsystem<UBrushEditingSubsystem>())
|
|
{
|
|
BrushSubsystem->UpdateGeometryFromSelectedBrushes();
|
|
}
|
|
|
|
GEditor->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
/**
|
|
* Gathers up a list of selection FPolys from selected static meshes.
|
|
*
|
|
* @return A TArray containing FPolys representing the triangles in the selected static meshes (note that these
|
|
* triangles are transformed into world space before being added to the array.
|
|
*/
|
|
|
|
TArray<FPoly*> GetSelectedPolygons()
|
|
{
|
|
// Build a list of polygons from all selected static meshes
|
|
|
|
TArray<FPoly*> SelectedPolys;
|
|
|
|
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
FTransform ActorToWorld = Actor->ActorToWorld();
|
|
|
|
for (UActorComponent* Component: Actor->GetComponents())
|
|
{
|
|
// If its a static mesh component, with a static mesh
|
|
UStaticMeshComponent* SMComp = Cast<UStaticMeshComponent>(Component);
|
|
if (SMComp && SMComp->IsRegistered() && SMComp->GetStaticMesh())
|
|
{
|
|
UStaticMesh* StaticMesh = SMComp->GetStaticMesh();
|
|
if (StaticMesh)
|
|
{
|
|
int32 NumLods = StaticMesh->GetNumLODs();
|
|
if (NumLods)
|
|
{
|
|
const FStaticMeshLODResources& MeshLodZero = StaticMesh->GetLODForExport(0);
|
|
int32 NumTriangles = MeshLodZero.GetNumTriangles();
|
|
int32 NumVertices = MeshLodZero.GetNumVertices();
|
|
|
|
const FPositionVertexBuffer& PositionVertexBuffer = MeshLodZero.VertexBuffers.PositionVertexBuffer;
|
|
FIndexArrayView Indices = MeshLodZero.DepthOnlyIndexBuffer.GetArrayView();
|
|
|
|
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; TriangleIndex++)
|
|
{
|
|
const uint32 Idx0 = Indices[(TriangleIndex * 3) + 0];
|
|
const uint32 Idx1 = Indices[(TriangleIndex * 3) + 1];
|
|
const uint32 Idx2 = Indices[(TriangleIndex * 3) + 2];
|
|
|
|
FPoly* Polygon = new FPoly;
|
|
|
|
// Add the poly
|
|
Polygon->Init();
|
|
Polygon->PolyFlags = PF_DefaultFlags;
|
|
|
|
new (Polygon->Vertices) FVector(ActorToWorld.TransformPosition(PositionVertexBuffer.VertexPosition(Idx2)));
|
|
new (Polygon->Vertices) FVector(ActorToWorld.TransformPosition(PositionVertexBuffer.VertexPosition(Idx1)));
|
|
new (Polygon->Vertices) FVector(ActorToWorld.TransformPosition(PositionVertexBuffer.VertexPosition(Idx0)));
|
|
|
|
Polygon->CalcNormal(1);
|
|
Polygon->Fix();
|
|
if (Polygon->Vertices.Num() > 2)
|
|
{
|
|
if (!Polygon->Finalize(NULL, 1))
|
|
{
|
|
SelectedPolys.Add(Polygon);
|
|
}
|
|
}
|
|
|
|
// And add a flipped version of it to account for negative scaling
|
|
Polygon = new FPoly;
|
|
Polygon->Init();
|
|
Polygon->PolyFlags = PF_DefaultFlags;
|
|
|
|
new (Polygon->Vertices) FVector(ActorToWorld.TransformPosition(PositionVertexBuffer.VertexPosition(Idx2)));
|
|
new (Polygon->Vertices) FVector(ActorToWorld.TransformPosition(PositionVertexBuffer.VertexPosition(Idx0)));
|
|
new (Polygon->Vertices) FVector(ActorToWorld.TransformPosition(PositionVertexBuffer.VertexPosition(Idx1)));
|
|
Polygon->CalcNormal(1);
|
|
Polygon->Fix();
|
|
if (Polygon->Vertices.Num() > 2)
|
|
{
|
|
if (!Polygon->Finalize(NULL, 1))
|
|
{
|
|
SelectedPolys.Add(Polygon);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return SelectedPolys;
|
|
}
|
|
|
|
/**
|
|
* Creates an axis aligned bounding box based on the bounds of SelectedPolys. This bounding box
|
|
* is then copied into the builder brush. This function is a set up function that the blocking volume
|
|
* creation execs will call before doing anything fancy.
|
|
*
|
|
* @param InWorld The world in which the builder brush needs to be created
|
|
* @param SelectedPolys The list of selected FPolys to create the bounding box from.
|
|
* @param bSnapVertsToGrid Should the brush verts snap to grid
|
|
*/
|
|
|
|
void CreateBoundingBoxBuilderBrush(UWorld* InWorld, const TArray<FPoly*> SelectedPolys, bool bSnapVertsToGrid)
|
|
{
|
|
int x;
|
|
FPoly* Poly;
|
|
FBox BBox(ForceInit);
|
|
FVector Vertex;
|
|
|
|
for (x = 0; x < SelectedPolys.Num(); ++x)
|
|
{
|
|
Poly = SelectedPolys[x];
|
|
|
|
for (int v = 0; v < Poly->Vertices.Num(); ++v)
|
|
{
|
|
if (bSnapVertsToGrid)
|
|
{
|
|
Vertex = Poly->Vertices[v].GridSnap(GEditor->GetGridSize());
|
|
}
|
|
else
|
|
{
|
|
Vertex = Poly->Vertices[v];
|
|
}
|
|
|
|
BBox += Vertex;
|
|
}
|
|
}
|
|
|
|
// Change the builder brush to match the bounding box so that it exactly envelops the selected meshes
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "BrushSet", "Brush Set"));
|
|
|
|
UCubeBuilder* CubeBuilder = NewObject<UCubeBuilder>(GetTransientPackage(), NAME_None, RF_Transactional);
|
|
FVector Extent = BBox.GetExtent();
|
|
CubeBuilder->X = Extent.X * 2;
|
|
CubeBuilder->Y = Extent.Y * 2;
|
|
CubeBuilder->Z = Extent.Z * 2;
|
|
CubeBuilder->Build(InWorld);
|
|
|
|
ABrush* DefaultBrush = InWorld->GetDefaultBrush();
|
|
check(DefaultBrush != nullptr);
|
|
DefaultBrush->SetActorLocation(BBox.GetCenter(), false);
|
|
DefaultBrush->ReregisterAllComponents();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Take a plane and creates a gigantic triangle polygon that lies along it. The blocking
|
|
* volume creation routines call this when they are cutting geometry and need to create
|
|
* capping polygons.
|
|
*
|
|
* This polygon is so huge that it doesn't matter where the vertices actually land.
|
|
*
|
|
* @param InPlane The plane to lay the polygon on
|
|
* @return An FPoly representing the giant triangle we created (NULL if there was a problem)
|
|
*/
|
|
|
|
FPoly* CreateHugeTrianglePolygonOnPlane(const FPlane* InPlane)
|
|
{
|
|
// Using the plane normal, get 2 good axis vectors
|
|
|
|
FVector A, B;
|
|
InPlane->GetSafeNormal().FindBestAxisVectors(A, B);
|
|
|
|
// Create 4 vertices from the plane origin and the 2 axis generated above
|
|
|
|
FPoly* Triangle = new FPoly();
|
|
|
|
FVector Center = FVector(InPlane->X, InPlane->Y, InPlane->Z) * InPlane->W;
|
|
FVector V0 = Center + (A * WORLD_MAX);
|
|
FVector V1 = Center + (B * WORLD_MAX);
|
|
FVector V2 = Center - (((A + B) / 2.0f) * WORLD_MAX);
|
|
|
|
// Create a triangle that lays on InPlane
|
|
|
|
Triangle->Init();
|
|
Triangle->PolyFlags = PF_DefaultFlags;
|
|
|
|
new (Triangle->Vertices) FVector(V0);
|
|
new (Triangle->Vertices) FVector(V2);
|
|
new (Triangle->Vertices) FVector(V1);
|
|
|
|
Triangle->CalcNormal(1);
|
|
Triangle->Fix();
|
|
if (Triangle->Finalize(NULL, 1))
|
|
{
|
|
delete Triangle;
|
|
Triangle = NULL;
|
|
}
|
|
|
|
return Triangle;
|
|
}
|
|
|
|
bool UUnrealEdEngine::Exec_Actor(UWorld* InWorld, const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
// Keep a pointer to the beginning of the string to use for message displaying purposes
|
|
const TCHAR* const FullStr = Str;
|
|
|
|
// Determine whether or not components are selected (used to properly label transaction names)
|
|
const bool bComponentsSelected = GetSelectedComponentCount() > 0;
|
|
|
|
if (FParse::Command(&Str, TEXT("ADD")))
|
|
{
|
|
UClass* Class;
|
|
if (ParseObject<UClass>(Str, TEXT("CLASS="), Class, ANY_PACKAGE))
|
|
{
|
|
int32 bSnap = 1;
|
|
FParse::Value(Str, TEXT("SNAP="), bSnap);
|
|
|
|
AActor* Default = Class->GetDefaultObject<AActor>();
|
|
const FTransform ActorTransform = FActorPositioning::GetCurrentViewportPlacementTransform(*Default, !!bSnap);
|
|
|
|
AddActor(InWorld->GetCurrentLevel(), Class, ActorTransform);
|
|
RedrawLevelEditingViewports();
|
|
return true;
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("CREATE_BV_BOUNDINGBOX")))
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "CreateBoundingBoxBlockingVolume", "Create Bounding Box Blocking Volume"));
|
|
InWorld->GetDefaultBrush()->Modify();
|
|
|
|
bool bSnapToGrid = 0;
|
|
FParse::Bool(Str, TEXT("SNAPTOGRID="), bSnapToGrid);
|
|
|
|
// Create a bounding box for the selected static mesh triangles and set the builder brush to match it
|
|
|
|
TArray<FPoly*> SelectedPolys = GetSelectedPolygons();
|
|
CreateBoundingBoxBuilderBrush(InWorld, SelectedPolys, bSnapToGrid);
|
|
|
|
// Create the blocking volume
|
|
|
|
GUnrealEd->Exec(InWorld, TEXT("BRUSH ADDVOLUME CLASS=BlockingVolume"));
|
|
|
|
// Clean up memory
|
|
|
|
for (int x = 0; x < SelectedPolys.Num(); ++x)
|
|
{
|
|
delete SelectedPolys[x];
|
|
}
|
|
|
|
SelectedPolys.Empty();
|
|
|
|
// Finish up
|
|
|
|
RedrawLevelEditingViewports();
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("CREATE_BV_CONVEXVOLUME")))
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "CreateConvexBlockingVolume", "Create Convex Blocking Volume"));
|
|
InWorld->GetDefaultBrush()->Modify();
|
|
|
|
bool bSnapToGrid = 0;
|
|
FParse::Bool(Str, TEXT("SNAPTOGRID="), bSnapToGrid);
|
|
|
|
// The rejection tolerance. When figuring out which planes to cut the blocking volume cube with
|
|
// the code will reject any planes that are less than "NormalTolerance" different in their normals.
|
|
//
|
|
// This cuts down on the number of planes that will be used for generating the cutting planes and,
|
|
// as a side effect, eliminates duplicates.
|
|
|
|
float NormalTolerance = 0.25f;
|
|
FParse::Value(Str, TEXT("NORMALTOLERANCE="), NormalTolerance);
|
|
|
|
FVector NormalLimits(1.0f, 1.0f, 1.0f);
|
|
FParse::Value(Str, TEXT("NLIMITX="), NormalLimits.X);
|
|
FParse::Value(Str, TEXT("NLIMITY="), NormalLimits.Y);
|
|
FParse::Value(Str, TEXT("NLIMITZ="), NormalLimits.Z);
|
|
|
|
// Create a bounding box for the selected static mesh triangles and set the builder brush to match it
|
|
|
|
TArray<FPoly*> SelectedPolys = GetSelectedPolygons();
|
|
CreateBoundingBoxBuilderBrush(InWorld, SelectedPolys, bSnapToGrid);
|
|
|
|
// Get a list of the polygons that make up the builder brush
|
|
|
|
FPoly* poly;
|
|
TArray<FPoly>* BuilderBrushPolys = new TArray<FPoly>(InWorld->GetDefaultBrush()->Brush->Polys->Element);
|
|
|
|
// Create a list of valid splitting planes
|
|
|
|
TArray<FPlane*> SplitterPlanes;
|
|
|
|
for (int p = 0; p < SelectedPolys.Num(); ++p)
|
|
{
|
|
// Get a splitting plane from the first poly in our selection
|
|
|
|
poly = SelectedPolys[p];
|
|
FPlane* SplittingPlane = new FPlane(poly->Vertices[0], poly->Normal);
|
|
|
|
// Make sure this poly doesn't clip any other polys in the selection. If it does, we can't use it for generating the convex volume.
|
|
|
|
bool bUseThisSplitter = true;
|
|
|
|
for (int pp = 0; pp < SelectedPolys.Num() && bUseThisSplitter; ++pp)
|
|
{
|
|
FPoly* ppoly = SelectedPolys[pp];
|
|
|
|
if (p != pp && !(poly->Normal - ppoly->Normal).IsNearlyZero())
|
|
{
|
|
int res = ppoly->SplitWithPlaneFast(*SplittingPlane, NULL, NULL);
|
|
|
|
if (res == SP_Split || res == SP_Front)
|
|
{
|
|
// Whoops, this plane clips polygons (and/or sits between static meshes) in the selection so it can't be used
|
|
bUseThisSplitter = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If this polygons plane doesn't clip the selection in any way, we can carve the builder brush with it. Save it.
|
|
|
|
if (bUseThisSplitter)
|
|
{
|
|
// Move the plane into the same coordinate space as the builder brush
|
|
|
|
*SplittingPlane = SplittingPlane->TransformBy(InWorld->GetDefaultBrush()->ActorToWorld().ToMatrixWithScale().InverseFast());
|
|
|
|
// Before keeping this plane, make sure there aren't any existing planes that have a normal within the rejection tolerance.
|
|
|
|
bool bAddPlaneToList = true;
|
|
|
|
for (int x = 0; x < SplitterPlanes.Num(); ++x)
|
|
{
|
|
FPlane* plane = SplitterPlanes[x];
|
|
|
|
if (plane->GetSafeNormal().Equals(SplittingPlane->GetSafeNormal(), NormalTolerance))
|
|
{
|
|
bAddPlaneToList = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// As a final test, make sure that this planes normal falls within the normal limits that were defined
|
|
|
|
if (FMath::Abs(SplittingPlane->GetSafeNormal().X) > NormalLimits.X)
|
|
{
|
|
bAddPlaneToList = false;
|
|
}
|
|
if (FMath::Abs(SplittingPlane->GetSafeNormal().Y) > NormalLimits.Y)
|
|
{
|
|
bAddPlaneToList = false;
|
|
}
|
|
if (FMath::Abs(SplittingPlane->GetSafeNormal().Z) > NormalLimits.Z)
|
|
{
|
|
bAddPlaneToList = false;
|
|
}
|
|
|
|
// If this plane passed every test - it's a keeper!
|
|
|
|
if (bAddPlaneToList)
|
|
{
|
|
SplitterPlanes.Add(SplittingPlane);
|
|
}
|
|
else
|
|
{
|
|
delete SplittingPlane;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The builder brush is a bounding box at this point that fully surrounds the selected static meshes.
|
|
// Now we will carve away at it using the splitting planes we collected earlier. When this process
|
|
// is complete, we will have a convex volume inside of the builder brush that can then be used to add
|
|
// a blocking volume.
|
|
|
|
TArray<FPoly> NewBuilderBrushPolys;
|
|
|
|
for (int sp = 0; sp < SplitterPlanes.Num(); ++sp)
|
|
{
|
|
FPlane* plane = SplitterPlanes[sp];
|
|
|
|
// Carve the builder brush with each splitting plane we collected. We place the results into
|
|
// NewBuilderBrushPolys since we don't want to overwrite the original array just yet.
|
|
|
|
bool bNeedCapPoly = false;
|
|
|
|
for (int bp = 0; bp < BuilderBrushPolys->Num(); ++bp)
|
|
{
|
|
poly = &(*BuilderBrushPolys)[bp];
|
|
|
|
FPoly Front, Back;
|
|
int res = poly->SplitWithPlane(FVector(plane->X, plane->Y, plane->Z) * plane->W, plane->GetSafeNormal(), &Front, &Back, true);
|
|
switch (res)
|
|
{
|
|
// Ignore these results. We don't want them.
|
|
case SP_Coplanar:
|
|
case SP_Front:
|
|
break;
|
|
|
|
// In the case of a split, keep the polygon on the back side of the plane.
|
|
case SP_Split: {
|
|
NewBuilderBrushPolys.Add(Back);
|
|
bNeedCapPoly = true;
|
|
}
|
|
break;
|
|
|
|
// By default, just keep the polygon that we had.
|
|
default: {
|
|
NewBuilderBrushPolys.Add((*BuilderBrushPolys)[bp]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// NewBuilderBrushPolys contains the newly clipped polygons so copy those into
|
|
// the real array of polygons.
|
|
|
|
BuilderBrushPolys = new TArray<FPoly>(NewBuilderBrushPolys);
|
|
NewBuilderBrushPolys.Empty();
|
|
|
|
// If any splitting occured, we need to generate a cap polygon to cover the hole.
|
|
|
|
if (bNeedCapPoly)
|
|
{
|
|
// Create a large triangle polygon that covers the newly formed hole in the builder brush.
|
|
|
|
FPoly* CappingPoly = CreateHugeTrianglePolygonOnPlane(plane);
|
|
|
|
if (CappingPoly)
|
|
{
|
|
// Now we do the clipping the other way around. We are going to use the polygons in the builder brush to
|
|
// create planes which will clip the huge triangle polygon we just created. When this process is over,
|
|
// we will be left with a new polygon that covers the newly formed hole in the builder brush.
|
|
|
|
for (int bp = 0; bp < BuilderBrushPolys->Num(); ++bp)
|
|
{
|
|
poly = &((*BuilderBrushPolys)[bp]);
|
|
plane = new FPlane(poly->Vertices[0], poly->Vertices[1], poly->Vertices[2]);
|
|
|
|
FPoly Front, Back;
|
|
int res = CappingPoly->SplitWithPlane(FVector(plane->X, plane->Y, plane->Z) * plane->W, plane->GetSafeNormal(), &Front, &Back, true);
|
|
switch (res)
|
|
{
|
|
case SP_Split: {
|
|
*CappingPoly = Back;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Add that new polygon into the builder brush polys as a capping polygon.
|
|
|
|
BuilderBrushPolys->Add(*CappingPoly);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a new builder brush from the freshly clipped polygons.
|
|
|
|
InWorld->GetDefaultBrush()->Brush->Polys->Element.Empty();
|
|
|
|
for (int x = 0; x < BuilderBrushPolys->Num(); ++x)
|
|
{
|
|
InWorld->GetDefaultBrush()->Brush->Polys->Element.Add((*BuilderBrushPolys)[x]);
|
|
}
|
|
|
|
InWorld->GetDefaultBrush()->ReregisterAllComponents();
|
|
|
|
// Create the blocking volume
|
|
|
|
GUnrealEd->Exec(InWorld, TEXT("BRUSH ADDVOLUME CLASS=BlockingVolume"));
|
|
|
|
// Clean up memory
|
|
|
|
for (int x = 0; x < SelectedPolys.Num(); ++x)
|
|
{
|
|
delete SelectedPolys[x];
|
|
}
|
|
|
|
SelectedPolys.Empty();
|
|
|
|
for (int x = 0; x < SplitterPlanes.Num(); ++x)
|
|
{
|
|
delete SplitterPlanes[x];
|
|
}
|
|
|
|
SplitterPlanes.Empty();
|
|
|
|
delete BuilderBrushPolys;
|
|
|
|
// Finish up
|
|
|
|
RedrawLevelEditingViewports();
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("MIRROR")))
|
|
{
|
|
FVector MirrorScale(1, 1, 1);
|
|
GetFVECTOR(Str, MirrorScale);
|
|
// We can't have zeroes in the vector
|
|
if (!MirrorScale.X)
|
|
MirrorScale.X = 1;
|
|
if (!MirrorScale.Y)
|
|
MirrorScale.Y = 1;
|
|
if (!MirrorScale.Z)
|
|
MirrorScale.Z = 1;
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MirroringActors", "Mirroring Actors"));
|
|
MirrorActors(MirrorScale);
|
|
RebuildAlteredBSP(); // Update the Bsp of any levels containing a modified brush
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("DELTAMOVE")))
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "DeltaMoveActors", "Move Actors by Delta"));
|
|
FVector DeltaMove = FVector::ZeroVector;
|
|
GetFVECTOR(Str, DeltaMove);
|
|
|
|
FEditorModeTools& Tools = GLevelEditorModeTools();
|
|
Tools.SetPivotLocation(Tools.PivotLocation + DeltaMove, false);
|
|
|
|
if (GCurrentLevelEditingViewportClient)
|
|
{
|
|
GCurrentLevelEditingViewportClient->ApplyDeltaToActors(DeltaMove, FRotator::ZeroRotator, FVector::ZeroVector);
|
|
}
|
|
RedrawLevelEditingViewports();
|
|
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("HIDE")))
|
|
{
|
|
if (FParse::Command(&Str, TEXT("SELECTED"))) // ACTOR HIDE SELECTED
|
|
{
|
|
if (FParse::Command(&Str, TEXT("STARTUP"))) // ACTOR HIDE SELECTED STARTUP
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "HideSelectedAtStartup", "Hide Selected at Editor Startup"));
|
|
edactHideSelectedStartup(InWorld);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "HideSelected", "Hide Selected"));
|
|
edactHideSelected(InWorld);
|
|
SelectNone(true, true);
|
|
return true;
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("UNSELECTED"))) // ACTOR HIDE UNSELECTEED
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "HideUnselected", "Hide Unselected"));
|
|
edactHideUnselected(InWorld);
|
|
SelectNone(true, true);
|
|
return true;
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("UNHIDE")))
|
|
{
|
|
if (FParse::Command(&Str, TEXT("ALL"))) // ACTOR UNHIDE ALL
|
|
{
|
|
if (FParse::Command(&Str, TEXT("STARTUP"))) // ACTOR UNHIDE ALL STARTUP
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ShowAllAtStartup", "Show All at Editor Startup"));
|
|
edactUnHideAllStartup(InWorld);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "UnHideAll", "UnHide All"));
|
|
edactUnHideAll(InWorld);
|
|
return true;
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("SELECTED"))) // ACTOR UNHIDE SELECTED
|
|
{
|
|
if (FParse::Command(&Str, TEXT("STARTUP"))) // ACTOR UNHIDE SELECTED STARTUP
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ShowSelectedAtStartup", "Show Selected at Editor Startup"));
|
|
edactUnHideSelectedStartup(InWorld);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "UnhideSelected", "Unhide Selected"));
|
|
edactUnhideSelected(InWorld);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("APPLYTRANSFORM")))
|
|
{
|
|
CommandIsDeprecated(TEXT("ACTOR APPLYTRANSFORM"), Ar);
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("REPLACE")))
|
|
{
|
|
UClass* Class;
|
|
if (FParse::Command(&Str, TEXT("BRUSH"))) // ACTOR REPLACE BRUSH
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ReplaceSelectedBrushActors", "Replace Selected Brush Actors"));
|
|
edactReplaceSelectedBrush(InWorld);
|
|
return true;
|
|
}
|
|
else if (ParseObject<UClass>(Str, TEXT("CLASS="), Class, ANY_PACKAGE)) // ACTOR REPLACE CLASS=<class>
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ReplaceSelectedNonBrushActors", "Replace Selected Non-Brush Actors"));
|
|
edactReplaceSelectedNonBrushWithClass(Class);
|
|
return true;
|
|
}
|
|
}
|
|
//@todo locked levels - handle the rest of these....is this required, or can we assume that actors in locked levels can't be selected
|
|
else if (FParse::Command(&Str, TEXT("SELECT")))
|
|
{
|
|
if (FParse::Command(&Str, TEXT("NONE"))) // ACTOR SELECT NONE
|
|
{
|
|
return Exec(InWorld, TEXT("SELECT NONE"));
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("ALL"))) // ACTOR SELECT ALL
|
|
{
|
|
if (FParse::Command(&Str, TEXT("FROMOBJ"))) // ACTOR SELECT ALL FROMOBJ
|
|
{
|
|
bool bHasStaticMeshes = false;
|
|
TArray<UClass*> ClassesToSelect;
|
|
|
|
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
if (Actor->IsA(AStaticMeshActor::StaticClass()))
|
|
{
|
|
bHasStaticMeshes = true;
|
|
}
|
|
else
|
|
{
|
|
ClassesToSelect.AddUnique(Actor->GetClass());
|
|
}
|
|
}
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectAll", "Select All"));
|
|
if (bHasStaticMeshes)
|
|
{
|
|
edactSelectMatchingStaticMesh(false);
|
|
}
|
|
|
|
if (ClassesToSelect.Num() > 0)
|
|
{
|
|
for (int Index = 0; Index < ClassesToSelect.Num(); ++Index)
|
|
{
|
|
edactSelectOfClass(InWorld, ClassesToSelect[Index]);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("CHILDREN"))) // ACTOR SELECT ALL CHILDREN
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectAllChildren", "Select All Children"));
|
|
edactSelectAllChildren(false);
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("DESCENDANTS"))) // ACTOR SELECT ALL DESCENDANTS
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectAllDescendants", "Select All Descendants"));
|
|
edactSelectAllChildren(true);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectAll", "Select All"));
|
|
edactSelectAll(InWorld);
|
|
return true;
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("INSIDE"))) // ACTOR SELECT INSIDE
|
|
{
|
|
CommandIsDeprecated(TEXT("ACTOR SELECT INSIDE"), Ar);
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("INVERT"))) // ACTOR SELECT INVERT
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectInvert", "Select Invert"));
|
|
edactSelectInvert(InWorld);
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("OFCLASS"))) // ACTOR SELECT OFCLASS CLASS=<class>
|
|
{
|
|
UClass* Class;
|
|
if (ParseObject<UClass>(Str, TEXT("CLASS="), Class, ANY_PACKAGE))
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectOfClass", "Select Of Class"));
|
|
edactSelectOfClass(InWorld, Class);
|
|
}
|
|
else
|
|
{
|
|
UE_SUPPRESS(LogExec, Warning, Ar.Log(TEXT("Missing class")));
|
|
}
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("OFSUBCLASS"))) // ACTOR SELECT OFSUBCLASS CLASS=<class>
|
|
{
|
|
UClass* Class;
|
|
if (ParseObject<UClass>(Str, TEXT("CLASS="), Class, ANY_PACKAGE))
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectSubclassOfClass", "Select Subclass Of Class"));
|
|
edactSelectSubclassOf(InWorld, Class);
|
|
}
|
|
else
|
|
{
|
|
UE_SUPPRESS(LogExec, Warning, Ar.Log(TEXT("Missing class")));
|
|
}
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("BASED"))) // ACTOR SELECT BASED
|
|
{
|
|
// @TODO UE4 - no longer meaningful
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("BYPROPERTY"))) // ACTOR SELECT BYPROPERTY
|
|
{
|
|
GEditor->SelectByPropertyColoration(InWorld);
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("DELETED"))) // ACTOR SELECT DELETED
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectDeleted", "Select Deleted"));
|
|
edactSelectDeleted(InWorld);
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("MATCHINGSTATICMESH"))) // ACTOR SELECT MATCHINGSTATICMESH
|
|
{
|
|
const bool bAllClasses = FParse::Command(&Str, TEXT("ALLCLASSES"));
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectMatchingStaticMesh", "Select Matching Static Mesh"));
|
|
edactSelectMatchingStaticMesh(bAllClasses);
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("MATCHINGSKELETALMESH"))) // ACTOR SELECT MATCHINGSKELETALMESH
|
|
{
|
|
const bool bAllClasses = FParse::Command(&Str, TEXT("ALLCLASSES"));
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectMatchingSkeletalMesh", "Select Matching Skeletal Mesh"));
|
|
edactSelectMatchingSkeletalMesh(bAllClasses);
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("MATCHINGMATERIAL")))
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectAllWithMatchingMaterial", "Select All With Matching Material"));
|
|
edactSelectMatchingMaterial();
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("MATCHINGEMITTER")))
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectMatchingEmitter", "Select Matching Emitters"));
|
|
edactSelectMatchingEmitter();
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("RELEVANTLIGHTS"))) // ACTOR SELECT RELEVANTLIGHTS
|
|
{
|
|
UE_LOG(LogUnrealEdSrv, Log, TEXT("Select relevant lights!"));
|
|
edactSelectRelevantLights(InWorld);
|
|
}
|
|
else
|
|
{
|
|
// Get actor name.
|
|
FName ActorName(NAME_None);
|
|
if (FParse::Value(Str, TEXT("NAME="), ActorName))
|
|
{
|
|
AActor* Actor = FindObject<AActor>(InWorld->GetCurrentLevel(), *ActorName.ToString());
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "SelectToggleSingleActor", "Select Toggle Single Actor"));
|
|
SelectActor(Actor, !(Actor && Actor->IsSelected()), false, true);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("DELETE"))) // ACTOR SELECT DELETE
|
|
{
|
|
|
|
bool bHandled = false;
|
|
bHandled |= GLevelEditorModeTools().ProcessEditDelete();
|
|
|
|
// if not specially handled by the current editing mode,
|
|
if (!bHandled)
|
|
{
|
|
const FScopedTransaction Transaction(bComponentsSelected ? NSLOCTEXT("UnrealEd", "DeleteComponents", "Delete Components") : NSLOCTEXT("UnrealEd", "DeleteActors", "Delete Actors"));
|
|
FEditorDelegates::OnDeleteActorsBegin.Broadcast();
|
|
const bool bCheckRef = GetDefault<ULevelEditorMiscSettings>()->bCheckReferencesOnDelete;
|
|
edactDeleteSelected(InWorld, true, bCheckRef, bCheckRef);
|
|
FEditorDelegates::OnDeleteActorsEnd.Broadcast();
|
|
}
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("UPDATE"))) // ACTOR SELECT UPDATE
|
|
{
|
|
bool bLockedLevel = false;
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
if (!Actor->IsTemplate() && FLevelUtils::IsLevelLocked(Actor))
|
|
{
|
|
bLockedLevel = true;
|
|
}
|
|
else
|
|
{
|
|
Actor->PreEditChange(NULL);
|
|
Actor->PostEditChange();
|
|
}
|
|
}
|
|
|
|
if (bLockedLevel)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_OperationDisallowedOnLockedLevelUpdateActor", "Update Actor: The requested operation could not be completed because the level is locked."));
|
|
}
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("SET")))
|
|
{
|
|
// @todo DB: deprecate the ACTOR SET exec.
|
|
RedrawLevelEditingViewports();
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("BAKEPREPIVOT")))
|
|
{
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
FScopedActorPropertiesChange ActorPropertiesChangeCallback;
|
|
|
|
// Bakes the current pivot position into all selected actors
|
|
|
|
FEditorModeTools& EditorModeTools = GLevelEditorModeTools();
|
|
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
FVector Delta(EditorModeTools.PivotLocation - Actor->GetActorLocation());
|
|
|
|
Actor->Modify();
|
|
Actor->SetPivotOffset(Actor->GetTransform().InverseTransformVector(Delta));
|
|
SetPivotMovedIndependently(false);
|
|
Actor->PostEditMove(true);
|
|
}
|
|
|
|
GUnrealEd->NoteSelectionChange();
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("UNBAKEPREPIVOT")))
|
|
{
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
FScopedActorPropertiesChange ActorPropertiesChangeCallback;
|
|
|
|
// Resets the PrePivot of the selected actors to 0,0,0 while leaving them in the same world location.
|
|
|
|
FEditorModeTools& EditorModeTools = GLevelEditorModeTools();
|
|
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
Actor->Modify();
|
|
Actor->SetPivotOffset(FVector::ZeroVector);
|
|
SetPivotMovedIndependently(false);
|
|
Actor->PostEditMove(true);
|
|
}
|
|
|
|
GUnrealEd->NoteSelectionChange();
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("RESET")))
|
|
{
|
|
FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ResetActors", "Reset Actors"));
|
|
|
|
bool bLocation = false;
|
|
bool bPivot = false;
|
|
bool bRotation = false;
|
|
bool bScale = false;
|
|
if (FParse::Command(&Str, TEXT("LOCATION")))
|
|
{
|
|
bLocation = true;
|
|
ResetPivot();
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("PIVOT")))
|
|
{
|
|
bPivot = true;
|
|
ResetPivot();
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("ROTATION")))
|
|
{
|
|
bRotation = true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("SCALE")))
|
|
{
|
|
bScale = true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("ALL")))
|
|
{
|
|
bLocation = bRotation = bScale = true;
|
|
ResetPivot();
|
|
}
|
|
|
|
// Fires ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
bool bHadLockedLevels = false;
|
|
bool bModifiedActors = false;
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
if (!Actor->IsTemplate() && FLevelUtils::IsLevelLocked(Actor))
|
|
{
|
|
bHadLockedLevels = true;
|
|
}
|
|
else
|
|
{
|
|
bModifiedActors = true;
|
|
|
|
Actor->PreEditChange(NULL);
|
|
Actor->Modify();
|
|
|
|
if (bLocation)
|
|
{
|
|
Actor->SetActorLocation(FVector::ZeroVector, false);
|
|
}
|
|
if (bPivot)
|
|
{
|
|
Actor->SetPivotOffset(FVector::ZeroVector);
|
|
}
|
|
|
|
if (bScale && Actor->GetRootComponent() != NULL)
|
|
{
|
|
Actor->GetRootComponent()->SetRelativeScale3D(FVector(1.f));
|
|
}
|
|
|
|
Actor->MarkPackageDirty();
|
|
LevelDirtyCallback.Request();
|
|
}
|
|
}
|
|
|
|
if (bHadLockedLevels)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_OperationDisallowedOnLockedLevelResetActor", "Reset Actor: The requested operation could not be completed because the level is locked."));
|
|
}
|
|
|
|
if (bModifiedActors)
|
|
{
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
else
|
|
{
|
|
Transaction.Cancel();
|
|
}
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("DUPLICATE")))
|
|
{
|
|
bool bHandled = false;
|
|
bHandled |= GLevelEditorModeTools().ProcessEditDuplicate();
|
|
|
|
// if not specially handled by the current editing mode,
|
|
if (!bHandled)
|
|
{
|
|
//@todo locked levels - if all actor levels are locked, cancel the transaction
|
|
const FScopedTransaction Transaction(bComponentsSelected ? NSLOCTEXT("UnrealEd", "DuplicateComponents", "Duplicate Components") : NSLOCTEXT("UnrealEd", "DuplicateActors", "Duplicate Actors"));
|
|
|
|
FEditorDelegates::OnDuplicateActorsBegin.Broadcast();
|
|
|
|
// duplicate selected
|
|
ABrush::SetSuppressBSPRegeneration(true);
|
|
edactDuplicateSelected(InWorld->GetCurrentLevel(), GetDefault<ULevelEditorViewportSettings>()->GridEnabled);
|
|
ABrush::SetSuppressBSPRegeneration(false);
|
|
|
|
// Find out if any of the selected actors will change the BSP.
|
|
// and only then rebuild BSP as this is expensive.
|
|
const FSelectedActorInfo& SelectedActors = AssetSelectionUtils::GetSelectedActorInfo();
|
|
if (SelectedActors.bHaveBrush)
|
|
{
|
|
RebuildAlteredBSP(); // Update the Bsp of any levels containing a modified brush
|
|
}
|
|
|
|
FEditorDelegates::OnDuplicateActorsEnd.Broadcast();
|
|
}
|
|
RedrawLevelEditingViewports();
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("ALIGN")))
|
|
{
|
|
if (FParse::Command(&Str, TEXT("ORIGIN")))
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "Undo_SnapBrushOrigin", "Snap Brush Origin"));
|
|
edactAlignOrigin();
|
|
RedrawLevelEditingViewports();
|
|
return true;
|
|
}
|
|
else // "VERTS" (default)
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "Undo_SnapBrushVertices", "Snap Brush Vertices"));
|
|
edactAlignVertices();
|
|
RedrawLevelEditingViewports();
|
|
RebuildAlteredBSP(); // Update the Bsp of any levels containing a modified brush
|
|
return true;
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("TOGGLE")))
|
|
{
|
|
if (FParse::Command(&Str, TEXT("LOCKMOVEMENT"))) // ACTOR TOGGLE LOCKMOVEMENT
|
|
{
|
|
ToggleSelectedActorMovementLock();
|
|
}
|
|
|
|
RedrawLevelEditingViewports();
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("LEVELCURRENT")))
|
|
{
|
|
MakeSelectedActorsLevelCurrent();
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("MOVETOCURRENT")))
|
|
{
|
|
UEditorLevelUtils::MoveSelectedActorsToLevel(InWorld->GetCurrentLevel());
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("DESELECT")))
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "DeselectActors", "Deselect Actor(s)"));
|
|
GEditor->GetSelectedActors()->Modify();
|
|
|
|
// deselects everything in UnrealEd
|
|
GUnrealEd->SelectNone(true, true);
|
|
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("EXPORT")))
|
|
{
|
|
if (FParse::Command(&Str, TEXT("FBX")))
|
|
{
|
|
TArray<FString> SaveFilenames;
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
bool bSave = false;
|
|
if (DesktopPlatform)
|
|
{
|
|
void* ParentWindowWindowHandle = NULL;
|
|
|
|
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
|
|
const TSharedPtr<SWindow>& MainFrameParentWindow = MainFrameModule.GetParentWindow();
|
|
if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid())
|
|
{
|
|
ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle();
|
|
}
|
|
|
|
bSave = DesktopPlatform->SaveFileDialog(
|
|
ParentWindowWindowHandle,
|
|
NSLOCTEXT("UnrealEd", "StaticMeshEditor_ExportToPromptTitle", "Export to...").ToString(),
|
|
*FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_EXPORT),
|
|
TEXT(""),
|
|
TEXT("FBX document|*.fbx"),
|
|
EFileDialogFlags::None,
|
|
SaveFilenames);
|
|
}
|
|
|
|
// Show dialog and execute the export if the user did not cancel out
|
|
if (bSave)
|
|
{
|
|
// Get the filename from dialog
|
|
FString FileName = SaveFilenames[0];
|
|
FEditorDirectories::Get().SetLastDirectory(ELastDirectory::GENERIC_EXPORT, FPaths::GetPath(FileName)); // Save path as default for next time.
|
|
|
|
INodeNameAdapter NodeNameAdapter;
|
|
UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance();
|
|
// Show the fbx export dialog options
|
|
bool ExportCancel = false;
|
|
bool ExportAll = false;
|
|
Exporter->FillExportOptions(false, true, FileName, ExportCancel, ExportAll);
|
|
if (!ExportCancel)
|
|
{
|
|
Exporter->CreateDocument();
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
if (Actor->IsA(AActor::StaticClass()))
|
|
{
|
|
if (Actor->IsA(AStaticMeshActor::StaticClass()))
|
|
{
|
|
Exporter->ExportStaticMesh(Actor, CastChecked<AStaticMeshActor>(Actor)->GetStaticMeshComponent(), NodeNameAdapter);
|
|
}
|
|
else if (Actor->IsA(ASkeletalMeshActor::StaticClass()))
|
|
{
|
|
Exporter->ExportSkeletalMesh(Actor, CastChecked<ASkeletalMeshActor>(Actor)->GetSkeletalMeshComponent(), NodeNameAdapter);
|
|
}
|
|
else if (Actor->IsA(ABrush::StaticClass()))
|
|
{
|
|
Exporter->ExportBrush(CastChecked<ABrush>(Actor), NULL, true, NodeNameAdapter);
|
|
}
|
|
}
|
|
}
|
|
Exporter->WriteToFile(*FileName);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("SNAP"))) // ACTOR SNAP
|
|
{
|
|
FSnappingUtils::EnableActorSnap(!FSnappingUtils::IsSnapToActorEnabled());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UUnrealEdEngine::Exec_Mode(const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
int32 DWord1;
|
|
|
|
if (FParse::Command(&Str, TEXT("WIDGETCOORDSYSTEMCYCLE")))
|
|
{
|
|
const bool bGetRawValue = true;
|
|
int32 Wk = GLevelEditorModeTools().GetCoordSystem(bGetRawValue);
|
|
Wk++;
|
|
|
|
if (Wk == COORD_Max)
|
|
{
|
|
Wk -= COORD_Max;
|
|
}
|
|
|
|
GLevelEditorModeTools().SetCoordSystem((ECoordSystem)Wk);
|
|
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
|
|
FEditorSupportDelegates::UpdateUI.Broadcast();
|
|
}
|
|
|
|
if (FParse::Command(&Str, TEXT("WIDGETMODECYCLE")))
|
|
{
|
|
GLevelEditorModeTools().CycleWidgetMode();
|
|
}
|
|
|
|
if (FParse::Value(Str, TEXT("GRID="), DWord1))
|
|
{
|
|
FinishAllSnaps();
|
|
|
|
ULevelEditorViewportSettings* ViewportSettings = GetMutableDefault<ULevelEditorViewportSettings>();
|
|
ViewportSettings->GridEnabled = DWord1;
|
|
ViewportSettings->PostEditChange();
|
|
|
|
FEditorDelegates::OnGridSnappingChanged.Broadcast(ViewportSettings->GridEnabled, GetGridSize());
|
|
FEditorSupportDelegates::UpdateUI.Broadcast();
|
|
}
|
|
|
|
if (FParse::Value(Str, TEXT("ROTGRID="), DWord1))
|
|
{
|
|
FinishAllSnaps();
|
|
|
|
ULevelEditorViewportSettings* ViewportSettings = GetMutableDefault<ULevelEditorViewportSettings>();
|
|
ViewportSettings->RotGridEnabled = DWord1;
|
|
ViewportSettings->PostEditChange();
|
|
|
|
FEditorSupportDelegates::UpdateUI.Broadcast();
|
|
}
|
|
|
|
if (FParse::Value(Str, TEXT("SCALEGRID="), DWord1))
|
|
{
|
|
FinishAllSnaps();
|
|
|
|
ULevelEditorViewportSettings* ViewportSettings = GetMutableDefault<ULevelEditorViewportSettings>();
|
|
ViewportSettings->SnapScaleEnabled = DWord1;
|
|
ViewportSettings->PostEditChange();
|
|
|
|
FEditorSupportDelegates::UpdateUI.Broadcast();
|
|
}
|
|
|
|
if (FParse::Value(Str, TEXT("SNAPVERTEX="), DWord1))
|
|
{
|
|
FinishAllSnaps();
|
|
|
|
ULevelEditorViewportSettings* ViewportSettings = GetMutableDefault<ULevelEditorViewportSettings>();
|
|
ViewportSettings->bSnapVertices = !!DWord1;
|
|
ViewportSettings->PostEditChange();
|
|
|
|
FEditorSupportDelegates::UpdateUI.Broadcast();
|
|
}
|
|
|
|
if (FParse::Value(Str, TEXT("SHOWBRUSHMARKERPOLYS="), DWord1))
|
|
{
|
|
FinishAllSnaps();
|
|
bShowBrushMarkerPolys = DWord1;
|
|
}
|
|
|
|
if (FParse::Value(Str, TEXT("SELECTIONLOCK="), DWord1))
|
|
{
|
|
FinishAllSnaps();
|
|
// If -1 is passed in, treat it as a toggle. Otherwise, use the value as a literal assignment.
|
|
if (DWord1 == -1)
|
|
{
|
|
GEdSelectionLock = (GEdSelectionLock == 0) ? 1 : 0;
|
|
}
|
|
else
|
|
{
|
|
GEdSelectionLock = !!DWord1;
|
|
}
|
|
}
|
|
|
|
if (FParse::Value(Str, TEXT("USESIZINGBOX="), DWord1))
|
|
{
|
|
FinishAllSnaps();
|
|
// If -1 is passed in, treat it as a toggle. Otherwise, use the value as a literal assignment.
|
|
if (DWord1 == -1)
|
|
UseSizingBox = (UseSizingBox == 0) ? 1 : 0;
|
|
else
|
|
UseSizingBox = DWord1;
|
|
}
|
|
|
|
if (GCurrentLevelEditingViewportClient)
|
|
{
|
|
int32 NewCameraSpeed = 1;
|
|
if (FParse::Value(Str, TEXT("SPEED="), NewCameraSpeed))
|
|
{
|
|
NewCameraSpeed = FMath::Clamp<int32>(NewCameraSpeed, 1, FLevelEditorViewportClient::MaxCameraSpeeds);
|
|
GetMutableDefault<ULevelEditorViewportSettings>()->CameraSpeed = NewCameraSpeed;
|
|
}
|
|
}
|
|
|
|
FParse::Value(Str, TEXT("SNAPDIST="), GetMutableDefault<ULevelEditorViewportSettings>()->SnapDistance);
|
|
|
|
//
|
|
// Major modes:
|
|
//
|
|
FEditorModeID EditorMode = FBuiltinEditorModes::EM_None;
|
|
|
|
FString CommandToken = FParse::Token(Str, false);
|
|
FEdMode* FoundMode = GLevelEditorModeTools().GetActiveMode(FName(*CommandToken));
|
|
|
|
if (FoundMode != NULL)
|
|
{
|
|
EditorMode = FName(*CommandToken);
|
|
}
|
|
|
|
if (EditorMode != FBuiltinEditorModes::EM_None)
|
|
{
|
|
FEditorDelegates::ChangeEditorMode.Broadcast(EditorMode);
|
|
}
|
|
|
|
// Reset the roll on all viewport cameras
|
|
for (FLevelEditorViewportClient* ViewportClient: GetLevelViewportClients())
|
|
{
|
|
if (ViewportClient->IsPerspective())
|
|
{
|
|
ViewportClient->RemoveCameraRoll();
|
|
}
|
|
}
|
|
|
|
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::Exec_Group(const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
if (UActorGroupingUtils::IsGroupingActive())
|
|
{
|
|
if (FParse::Command(&Str, TEXT("REGROUP")))
|
|
{
|
|
UActorGroupingUtils::Get()->GroupSelected();
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Str, TEXT("UNGROUP")))
|
|
{
|
|
UActorGroupingUtils::Get()->UngroupSelected();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|