3162 lines
97 KiB
C++
3162 lines
97 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 "UObject/StrongObjectPtr.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 "Styling/AppStyle.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 "Engine/GameViewportClient.h"
|
|
#include "AssetRegistry/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 "AssetRegistry/AssetRegistryModule.h"
|
|
#include "FbxExporter.h"
|
|
#include "DesktopPlatformModule.h"
|
|
#include "Elements/Framework/TypedElementList.h"
|
|
#include "Elements/Framework/TypedElementRegistry.h"
|
|
#include "Elements/Framework/TypedElementCommonActions.h"
|
|
#include "SnappingUtils.h"
|
|
#include "AssetSelection.h"
|
|
#include "HighResScreenshot.h"
|
|
#include "ActorEditorUtils.h"
|
|
#include "Editor/ActorPositioning.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 "ILevelEditor.h"
|
|
#include "Subsystems/BrushEditingSubsystem.h"
|
|
#include "Subsystems/EditorActorSubsystem.h"
|
|
#include "Elements/Framework/TypedElementCommonActions.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(FAppStyle::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->HasThumbnailMap() )
|
|
{
|
|
Pkg->SetThumbnailMap(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::HandleDisasmScriptCommand(const TCHAR* Str, FOutputDevice& Ar)
|
|
{
|
|
FString ClassName;
|
|
|
|
if (FParse::Token(Str, ClassName, false))
|
|
{
|
|
FKismetBytecodeDisassembler::DisassembleAllFunctionsInClasses(Ar, ClassName);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#if UE_ALLOW_EXEC_COMMANDS
|
|
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 );
|
|
}
|
|
//------------------------------------------------------------------------------------
|
|
// ELEMENT: Element-related functions
|
|
//
|
|
else if (FParse::Command(&Str,TEXT("ELEMENT")))
|
|
{
|
|
return Exec_Element( 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("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;
|
|
|
|
FVector::FReal 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(false);
|
|
Brush->SetActorLocation( NewLocation );
|
|
|
|
Brush->Brush->Modify(false);
|
|
for( int32 poly = 0 ; poly < Brush->Brush->Polys->Element.Num() ; poly++ )
|
|
{
|
|
FPoly* Poly = &(Brush->Brush->Polys->Element[poly]);
|
|
|
|
Poly->TextureU /= (FVector3f)ScaleVec;
|
|
Poly->TextureV /= (FVector3f)ScaleVec;
|
|
Poly->Base = ((Poly->Base - (FVector3f)Brush->GetPivotOffset()) * (FVector3f)ScaleVec) + (FVector3f)Brush->GetPivotOffset();
|
|
|
|
for( int32 vtx = 0 ; vtx < Poly->Vertices.Num() ; vtx++ )
|
|
{
|
|
Poly->Vertices[vtx] = ((Poly->Vertices[vtx] - (FVector3f)Brush->GetPivotOffset()) * (FVector3f)ScaleVec) + (FVector3f)Brush->GetPivotOffset();
|
|
|
|
// "Then snap the vertices new positions by the specified Snap amount"
|
|
if ( bSnap )
|
|
{
|
|
FVector VPos = (FVector)Poly->Vertices[vtx]; // LWC_TODO: Perf pessimization
|
|
FSnappingUtils::SnapPointToGrid( VPos, FVector(0, 0, 0) );
|
|
Poly->Vertices[vtx] = (FVector3f)VPos;
|
|
}
|
|
}
|
|
|
|
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") ) )
|
|
{
|
|
// this is HighResShot from the Editor NOT in PIE
|
|
// Editor PIE HighResShot is in GameViewportClient
|
|
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, EAllowShrinking::No);
|
|
}
|
|
|
|
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;
|
|
}
|
|
#endif // UE_ALLOW_EXEC_COMMANDS
|
|
|
|
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 (const FTemplateMapInfo& It : GetTemplateMapInfos())
|
|
{
|
|
if (It.Map.GetLongPackageName() == 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 (TypedElementCommonActionsUtils::IsElementCopyAndPasteEnabled())
|
|
{
|
|
if (GCurrentLevelEditingViewportClient)
|
|
{
|
|
if (TSharedPtr<ILevelEditor> LevelEditor = GCurrentLevelEditingViewportClient->ParentLevelEditor.Pin())
|
|
{
|
|
if (UTypedElementCommonActions* CommonActions = LevelEditor->GetCommonActions())
|
|
{
|
|
// End drags to avoid deleting something out from under one.
|
|
FSlateApplication::Get().CancelDragDrop();
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "Cut", "Cut"));
|
|
|
|
UTypedElementSelectionSet* SelectionSet = LevelEditor->GetMutableElementSelectionSet();
|
|
|
|
if (!bComponentsSelected)
|
|
{
|
|
FEditorDelegates::OnEditCutActorsBegin.Broadcast();
|
|
}
|
|
|
|
CommonActions->CopySelectedElements(SelectionSet);
|
|
const bool bCheckRef = GetDefault<ULevelEditorMiscSettings>()->bCheckReferencesOnDelete;
|
|
FTypedElementDeletionOptions Options;
|
|
Options
|
|
.SetWarnAboutReferences(bCheckRef)
|
|
.SetWarnAboutSoftReferences(bCheckRef);
|
|
CommonActions->DeleteSelectedElements(SelectionSet, InWorld, Options);
|
|
|
|
if (!bComponentsSelected)
|
|
{
|
|
FEditorDelegates::OnEditCutActorsEnd.Broadcast();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (bComponentsSelected)
|
|
{
|
|
// Same transaction language used in CopySelectedActorsToClipboard below
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "Cut", "Cut"));
|
|
|
|
edactCopySelected(InWorld);
|
|
const bool bCheckRef = GetDefault<ULevelEditorMiscSettings>()->bCheckReferencesOnDelete;
|
|
edactDeleteSelected(InWorld, true, bCheckRef, bCheckRef);
|
|
}
|
|
else
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "Cut", "Cut"));
|
|
FEditorDelegates::OnEditCutActorsBegin.Broadcast();
|
|
const bool bCheckRef = GetDefault<ULevelEditorMiscSettings>()->bCheckReferencesOnDelete;
|
|
CopySelectedActorsToClipboard(InWorld, true, false /*bIsMove*/, bCheckRef);
|
|
FEditorDelegates::OnEditCutActorsEnd.Broadcast();
|
|
}
|
|
}
|
|
else if( FParse::Command(&Str,TEXT("COPY")) )
|
|
{
|
|
if (GLevelEditorModeTools().ProcessEditCopy())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (TypedElementCommonActionsUtils::IsElementCopyAndPasteEnabled())
|
|
{
|
|
if (GCurrentLevelEditingViewportClient)
|
|
{
|
|
if (TSharedPtr<ILevelEditor> LevelEditor = GCurrentLevelEditingViewportClient->ParentLevelEditor.Pin())
|
|
{
|
|
if (UTypedElementCommonActions* CommonActions = LevelEditor->GetCommonActions())
|
|
{
|
|
UTypedElementSelectionSet* SelectionSet = LevelEditor->GetMutableElementSelectionSet();
|
|
|
|
if (!bComponentsSelected)
|
|
{
|
|
FEditorDelegates::OnEditCopyActorsBegin.Broadcast();
|
|
}
|
|
|
|
CommonActions->CopySelectedElements(SelectionSet);
|
|
|
|
if (!bComponentsSelected)
|
|
{
|
|
FEditorDelegates::OnEditCopyActorsEnd.Broadcast();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else 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;
|
|
}
|
|
|
|
bool bImportedElements = false;
|
|
if (TypedElementCommonActionsUtils::IsElementCopyAndPasteEnabled())
|
|
{
|
|
if (GCurrentLevelEditingViewportClient)
|
|
{
|
|
if (TSharedPtr<ILevelEditor> LevelEditor = GCurrentLevelEditingViewportClient->ParentLevelEditor.Pin())
|
|
{
|
|
if (UTypedElementCommonActions* CommonActions = LevelEditor->GetCommonActions())
|
|
{
|
|
FText TransDescription = NSLOCTEXT("UnrealEd", "Paste", "Paste");
|
|
|
|
UTypedElementSelectionSet* SelectionSet = LevelEditor->GetMutableElementSelectionSet();
|
|
FTypedElementPasteOptions PasteOptions;
|
|
PasteOptions.SelectionSetToModify = SelectionSet;
|
|
|
|
FString TempStr;
|
|
if (FParse::Value(Str, TEXT("TO="), TempStr))
|
|
{
|
|
if (!FCString::Strcmp(*TempStr, TEXT("HERE")))
|
|
{
|
|
PasteOptions.bPasteAtLocation = true;
|
|
const FSnappedPositioningData PositioningData = FSnappedPositioningData(GCurrentLevelEditingViewportClient, ClickLocation, ClickPlane)
|
|
.AlignToSurfaceRotation(false);
|
|
PasteOptions.PasteLocation = FActorPositioning::GetSnappedSurfaceAlignedTransform(PositioningData).GetLocation();
|
|
|
|
TransDescription = NSLOCTEXT("UnrealEd", "PasteHere", "Paste Here");
|
|
}
|
|
else
|
|
{
|
|
if (!FCString::Strcmp(*TempStr, TEXT("ORIGIN")))
|
|
{
|
|
PasteOptions.bPasteAtLocation = true;
|
|
PasteOptions.PasteLocation = FVector::ZeroVector;
|
|
TransDescription = NSLOCTEXT("UnrealEd", "PasteToWorldOrigin", "Paste To World Origin");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
const FScopedTransaction Transaction(TransDescription);
|
|
|
|
if (!bComponentsSelected)
|
|
{
|
|
FEditorDelegates::OnEditPasteActorsBegin.Broadcast();
|
|
}
|
|
|
|
bImportedElements = !CommonActions->PasteElements(SelectionSet, InWorld, PasteOptions).IsEmpty();
|
|
|
|
if (!bComponentsSelected)
|
|
{
|
|
FEditorDelegates::OnEditPasteActorsEnd.Broadcast();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bImportedElements)
|
|
{
|
|
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((FVector)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;
|
|
}
|
|
|
|
/**
|
|
* 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) FVector3f(ActorToWorld.TransformPosition( (FVector)PositionVertexBuffer.VertexPosition(Idx2) ));
|
|
new(Polygon->Vertices) FVector3f(ActorToWorld.TransformPosition( (FVector)PositionVertexBuffer.VertexPosition(Idx1) ));
|
|
new(Polygon->Vertices) FVector3f(ActorToWorld.TransformPosition( (FVector)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) FVector3f(ActorToWorld.TransformPosition( (FVector)PositionVertexBuffer.VertexPosition(Idx2) ));
|
|
new(Polygon->Vertices) FVector3f(ActorToWorld.TransformPosition( (FVector)PositionVertexBuffer.VertexPosition(Idx0) ));
|
|
new(Polygon->Vertices) FVector3f(ActorToWorld.TransformPosition( (FVector)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 = (FVector)Poly->Vertices[v].GridSnap(GEditor->GetGridSize());
|
|
}
|
|
else
|
|
{
|
|
Vertex = (FVector)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 * UE_OLD_WORLD_MAX); // LWC_TODO: WORLD_MAX misuse?
|
|
FVector V1 = Center + (B * UE_OLD_WORLD_MAX);
|
|
FVector V2 = Center - (((A + B) / 2.0f) * UE_OLD_WORLD_MAX);
|
|
|
|
// Create a triangle that lays on InPlane
|
|
|
|
Triangle->Init();
|
|
Triangle->PolyFlags = PF_DefaultFlags;
|
|
|
|
new(Triangle->Vertices) FVector3f( V0 );
|
|
new(Triangle->Vertices) FVector3f( V2 );
|
|
new(Triangle->Vertices) FVector3f( 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, nullptr ) )
|
|
{
|
|
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(false);
|
|
|
|
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(false);
|
|
|
|
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 );
|
|
|
|
FVector3f 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( (FVector)poly->Vertices[0], (FVector)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( FVector3f( plane->X, plane->Y, plane->Z ) * plane->W, (FVector3f)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((FVector)poly->Vertices[0], (FVector)poly->Vertices[1], (FVector)poly->Vertices[2] );
|
|
|
|
FPoly Front, Back;
|
|
int res = CappingPoly->SplitWithPlane( FVector3f( plane->X, plane->Y, plane->Z ) * plane->W, (FVector3f)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;
|
|
|
|
if (GCurrentLevelEditingViewportClient)
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MirroringActors", "Mirroring Actors"));
|
|
GCurrentLevelEditingViewportClient->MirrorSelectedActors(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 );
|
|
|
|
if (GCurrentLevelEditingViewportClient)
|
|
{
|
|
if (TSharedPtr<ILevelEditor> LevelEditor = GCurrentLevelEditingViewportClient->ParentLevelEditor.Pin())
|
|
{
|
|
FEditorModeTools& Tools = LevelEditor->GetEditorModeManager();
|
|
Tools.SetPivotLocation(Tools.PivotLocation + DeltaMove, false);
|
|
}
|
|
|
|
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, nullptr ) ) // 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
|
|
{
|
|
UEditorActorSubsystem* EditorActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
|
|
|
|
if (EditorActorSubsystem)
|
|
{
|
|
EditorActorSubsystem->SelectAllChildren(false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if( FParse::Command(&Str, TEXT("DESCENDANTS")) ) // ACTOR SELECT ALL DESCENDANTS
|
|
{
|
|
UEditorActorSubsystem* EditorActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
|
|
|
|
if (EditorActorSubsystem)
|
|
{
|
|
EditorActorSubsystem->SelectAllChildren(true);
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
UEditorActorSubsystem* EditorActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
|
|
|
|
if (EditorActorSubsystem)
|
|
{
|
|
EditorActorSubsystem->SelectAll(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
|
|
{
|
|
UEditorActorSubsystem* EditorActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
|
|
|
|
if (EditorActorSubsystem)
|
|
{
|
|
EditorActorSubsystem->InvertSelection(InWorld);
|
|
}
|
|
return true;
|
|
}
|
|
else if( FParse::Command(&Str,TEXT("OFCLASS")) ) // ACTOR SELECT OFCLASS CLASS=<class>
|
|
{
|
|
UClass* Class;
|
|
if( ParseObject<UClass>(Str,TEXT("CLASS="),Class,nullptr) )
|
|
{
|
|
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,nullptr) )
|
|
{
|
|
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 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
|
|
{
|
|
if (GCurrentLevelEditingViewportClient)
|
|
{
|
|
if (TSharedPtr<ILevelEditor> LevelEditor = GCurrentLevelEditingViewportClient->ParentLevelEditor.Pin())
|
|
{
|
|
if (UTypedElementCommonActions* CommonActions = LevelEditor->GetCommonActions())
|
|
{
|
|
UTypedElementSelectionSet* SelectionSet = LevelEditor->GetMutableElementSelectionSet();
|
|
|
|
// End drags to avoid deleting something out from under one.
|
|
FSlateApplication::Get().CancelDragDrop();
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "DeleteElements", "Delete Elements"));
|
|
if (SelectionSet->GetNumSelectedElements() == 0)
|
|
{
|
|
// HACK: Not all modes will select elements, so allow them a shot at deletion if we don't think anything else is selected
|
|
// TODO: Move this logic into FLevelEditorActionCallbacks and have it call into the mode directly
|
|
if (!GLevelEditorModeTools().ProcessEditDelete())
|
|
{
|
|
// HACK: Call these directly for an empty selection so that folder deletion in the outliner still works
|
|
// TODO: Move this logic into FLevelEditorActionCallbacks and have it call into the outliner directly
|
|
FEditorDelegates::OnDeleteActorsBegin.Broadcast();
|
|
FEditorDelegates::OnDeleteActorsEnd.Broadcast();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FEditorDelegates::OnDeleteActorsBegin.Broadcast();
|
|
const bool bCheckRef = GetDefault<ULevelEditorMiscSettings>()->bCheckReferencesOnDelete;
|
|
FTypedElementDeletionOptions Options;
|
|
Options
|
|
.SetWarnAboutReferences(bCheckRef)
|
|
.SetWarnAboutSoftReferences(bCheckRef);
|
|
CommonActions->DeleteSelectedElements(SelectionSet, InWorld, Options);
|
|
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")) )
|
|
{
|
|
if (GCurrentLevelEditingViewportClient)
|
|
{
|
|
if (TSharedPtr<ILevelEditor> LevelEditor = GCurrentLevelEditingViewportClient->ParentLevelEditor.Pin())
|
|
{
|
|
if (UTypedElementCommonActions* CommonActions = LevelEditor->GetCommonActions())
|
|
{
|
|
UTypedElementSelectionSet* SelectionSet = LevelEditor->GetMutableElementSelectionSet();
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "DuplicateElements", "Duplicate Elements"));
|
|
if (SelectionSet->GetNumSelectedElements() == 0)
|
|
{
|
|
// HACK: Not all modes will select elements, so allow them a shot at duplication if we don't think anything else is selected
|
|
// TODO: Move this logic into FLevelEditorActionCallbacks and have it call into the mode directly
|
|
if (!GLevelEditorModeTools().ProcessEditDuplicate())
|
|
{
|
|
// HACK: Call these directly for an empty selection so that folder duplication in the outliner still works
|
|
// TODO: Move this logic into FLevelEditorActionCallbacks and have it call into the outliner directly
|
|
FEditorDelegates::OnDuplicateActorsBegin.Broadcast();
|
|
FEditorDelegates::OnDuplicateActorsEnd.Broadcast();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const FVector DuplicateOffset = GEditor->GetGridLocationOffset(/*bUniformOffset*/false);
|
|
const TArray<FTypedElementHandle> DuplicatedElements = CommonActions->DuplicateSelectedElements(SelectionSet, InWorld, DuplicateOffset);
|
|
if (DuplicatedElements.Num() > 0)
|
|
{
|
|
SelectionSet->SetSelection(DuplicatedElements, FTypedElementSelectionOptions());
|
|
SelectionSet->NotifyPendingChanges();
|
|
|
|
// notify the global mode tools, the selection set should be identical to the new actors at this point
|
|
TArray<AActor*> SelectedActors = SelectionSet->GetSelectedObjects<AActor>();
|
|
GLevelEditorModeTools().ActorsDuplicatedNotify(SelectedActors, SelectedActors, DuplicateOffset != FVector::ZeroVector);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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_Element( 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;
|
|
|
|
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;
|
|
|
|
if (GCurrentLevelEditingViewportClient)
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MirroringElements", "Mirroring Elements"));
|
|
GCurrentLevelEditingViewportClient->MirrorSelectedElements(MirrorScale);
|
|
RebuildAlteredBSP(); // Update the Bsp of any levels containing a modified brush
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (FParse::Command(&Str, TEXT("DELTAMOVE")))
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "DeltaMovElements", "Move Elements by Delta"));
|
|
FVector DeltaMove = FVector::ZeroVector;
|
|
GetFVECTOR(Str, DeltaMove);
|
|
|
|
if (GCurrentLevelEditingViewportClient)
|
|
{
|
|
if (TSharedPtr<ILevelEditor> LevelEditor = GCurrentLevelEditingViewportClient->ParentLevelEditor.Pin())
|
|
{
|
|
FEditorModeTools& Tools = LevelEditor->GetEditorModeManager();
|
|
Tools.SetPivotLocation(Tools.PivotLocation + DeltaMove, false);
|
|
}
|
|
|
|
GCurrentLevelEditingViewportClient->ApplyDeltaToSelectedElements(FTransform(FRotator::ZeroRotator, DeltaMove, FVector::ZeroVector));
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
|
|
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();
|
|
GetMutableDefault<ULevelEditorViewportSettings>()->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
|