You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This change consists of multiple changes: Core: - Deprecation of ANY_PACKAGE macro. Added ANY_PACKAGE_DEPRECATED macro which can still be used for backwards compatibility purposes (only used in CoreUObject) - Deprecation of StaticFindObjectFast* functions that take bAnyPackage parameter - Added UStruct::GetStructPathName function that returns FTopLevelAssetPath representing the path name (package + object FName, super quick compared to UObject::GetPathName) + wrapper UClass::GetClassPathName to make it look better when used with UClasses - Added (Static)FindFirstObject* functions that find a first object given its Name (no Outer). These functions are used in places I consider valid to do global UObject (UClass) lookups like parsing command line parameters / checking for unique object names - Added static UClass::TryFindType function which serves a similar purpose as FindFirstObject however it's going to throw a warning (with a callstack / maybe ensure in the future?) if short class name is provided. This function is used in places that used to use short class names but now should have been converted to use path names to catch any potential regressions and or edge cases I missed. - Added static UClass::TryConvertShortNameToPathName utility function - Added static UClass::TryFixShortClassNameExportPath utility function - Object text export paths will now also include class path (Texture2D'/Game/Textures/Grass.Grass' -> /Script/Engine.Texture2D'/Game/Textures/Grass.Grass') - All places that manually generated object export paths for objects will now use FObjectPropertyBase::GetExportPath - Added a new startup test that checks for short type names in UClass/FProperty MetaData values AssetRegistry: - Deprecated any member variables (FAssetData / FARFilter) or functions that use FNames to represent class names and replaced them with FTopLevelAssetPath - Added new member variables and new function overloads that use FTopLevelAssetPath to represent class names - This also applies to a few other modules' APIs to match AssetRegistry changes Everything else: - Updated code that used ANY_PACKAGE (depending on the use case) to use FindObject(nullptr, PathToObject), UClass::TryFindType (used when path name is expected, warns if it's a short name) or FindFirstObject (usually for finding types based on user input but there's been a few legitimate use cases not related to user input) - Updated code that used AssetRegistry API to use FTopLevelAssetPaths and USomeClass::StaticClass()->GetClassPathName() instead of GetFName() - Updated meta data and hardcoded FindObject(ANY_PACKAGE, "EEnumNameOrClassName") calls to use path names #jira UE-99463 #rb many.people [FYI] Marcus.Wassmer #preflight 629248ec2256738f75de9b32 #codereviewnumbers 20320742, 20320791, 20320799, 20320756, 20320809, 20320830, 20320840, 20320846, 20320851, 20320863, 20320780, 20320765, 20320876, 20320786 #ROBOMERGE-OWNER: robert.manuszewski #ROBOMERGE-AUTHOR: robert.manuszewski #ROBOMERGE-SOURCE: CL 20430220 via CL 20433854 via CL 20435474 via CL 20435484 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v949-20362246) [CL 20448496 by robert manuszewski in ue5-main branch]
3020 lines
92 KiB
C++
3020 lines
92 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 "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"
|
|
|
|
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;
|
|
}
|
|
|
|
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();
|
|
Brush->SetActorLocation( NewLocation );
|
|
|
|
Brush->Brush->Modify();
|
|
for( int32 poly = 0 ; poly < Brush->Brush->Polys->Element.Num() ; poly++ )
|
|
{
|
|
FPoly* Poly = &(Brush->Brush->Polys->Element[poly]);
|
|
|
|
Poly->TextureU /= (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, false);
|
|
}
|
|
|
|
if( CurrentWindowName == WindowNameStr )
|
|
{
|
|
Local::TakeScreenShotOfWidget(OpenWindows[WindowId]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<SWindow> ActiveWindow = FSlateApplication::Get().GetActiveTopLevelWindow();
|
|
if( ActiveWindow.IsValid() )
|
|
{
|
|
Local::TakeScreenShotOfWidget(ActiveWindow.ToSharedRef());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UUnrealEdEngine::AnyWorldsAreDirty( UWorld* InWorld ) const
|
|
{
|
|
// Get the set of all reference worlds.
|
|
TArray<UWorld*> WorldsArray;
|
|
EditorLevelUtils::GetWorlds( InWorld, WorldsArray, true );
|
|
|
|
if ( WorldsArray.Num() > 0 )
|
|
{
|
|
FString FinalFilename;
|
|
for ( int32 WorldIndex = 0 ; WorldIndex < WorldsArray.Num() ; ++WorldIndex )
|
|
{
|
|
UWorld* World = WorldsArray[ WorldIndex ];
|
|
UPackage* Package = Cast<UPackage>( World->GetOuter() );
|
|
check( Package );
|
|
|
|
// The world needs saving if...
|
|
if ( Package->IsDirty() )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool UUnrealEdEngine::AnyContentPackagesAreDirty() const
|
|
{
|
|
const UPackage* TransientPackage = GetTransientPackage();
|
|
|
|
// Check all packages for dirty, non-map, non-transient packages
|
|
for ( TObjectIterator<UPackage> PackageIter; PackageIter; ++PackageIter )
|
|
{
|
|
UPackage* CurPackage = *PackageIter;
|
|
|
|
// The package needs saving if it's not the transient package
|
|
if ( CurPackage && ( CurPackage != TransientPackage ) && CurPackage->IsDirty() )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool UUnrealEdEngine::IsTemplateMap( const FString& MapName ) const
|
|
{
|
|
for (const FTemplateMapInfo& It : GetTemplateMapInfos())
|
|
{
|
|
if (It.Map == MapName)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool UUnrealEdEngine::IsUserInteracting()
|
|
{
|
|
// Check to see if the user is in the middle of a drag operation.
|
|
bool bUserIsInteracting = false;
|
|
for (const FEditorViewportClient* VC : GetAllViewportClients())
|
|
{
|
|
// Check for tracking and capture. If a viewport has mouse capture, it could be locking the mouse to the viewport, which means if we prompt with a dialog
|
|
// while the mouse is locked to a viewport, we wont be able to interact with the dialog.
|
|
if (VC->IsTracking() || (VC->Viewport && VC->Viewport->HasMouseCapture()))
|
|
{
|
|
bUserIsInteracting = true;
|
|
break;
|
|
}
|
|
}
|
|
return bUserIsInteracting;
|
|
}
|
|
|
|
void UUnrealEdEngine::ShowPackageNotification()
|
|
{
|
|
if( !FApp::IsUnattended() )
|
|
{
|
|
// Defer prompting for checkout if we cant prompt because of the following:
|
|
// The user is interacting with something,
|
|
// We are performing a slow task
|
|
// We have a play world
|
|
// The user disabled prompting on package modification
|
|
// A window has capture on the mouse
|
|
bool bCanPrompt = !IsUserInteracting() && !GIsSlowTask && !PlayWorld && GetDefault<UEditorLoadingSavingSettings>()->bPromptForCheckoutOnAssetModification && (FSlateApplication::Get().GetMouseCaptureWindow() == NULL);
|
|
|
|
if( bCanPrompt )
|
|
{
|
|
bShowPackageNotification = false;
|
|
bool bNeedWarningDialog = false;
|
|
for (const auto& Entry : PackageToNotifyState)
|
|
{
|
|
if (Entry.Value == NS_PendingWarning)
|
|
{
|
|
bNeedWarningDialog = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// The user is not interacting with anything, prompt to checkout packages that have been modified
|
|
|
|
struct Local
|
|
{
|
|
static void OpenCheckOutDialog()
|
|
{
|
|
GUnrealEd->PromptToCheckoutModifiedPackages(true);
|
|
}
|
|
};
|
|
|
|
if (bNeedWarningDialog)
|
|
{
|
|
Local::OpenCheckOutDialog();
|
|
}
|
|
else
|
|
{
|
|
int32 NumPackagesToCheckOut = GetNumDirtyPackagesThatNeedCheckout();
|
|
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("NumFiles"), NumPackagesToCheckOut);
|
|
|
|
FText ErrorText = FText::Format(NSLOCTEXT("SourceControl", "CheckOutNotification", "{NumFiles} files need check-out!"), Args);
|
|
|
|
if (!CheckOutNotificationWeakPtr.IsValid())
|
|
{
|
|
FNotificationInfo ErrorNotification(ErrorText);
|
|
ErrorNotification.bFireAndForget = true;;
|
|
ErrorNotification.Hyperlink = FSimpleDelegate::CreateStatic(&Local::OpenCheckOutDialog);
|
|
ErrorNotification.HyperlinkText = NSLOCTEXT("SourceControl", "CheckOutHyperlinkText", "Check-Out");
|
|
ErrorNotification.ExpireDuration = 10.0f; // Need this message to last a little longer than normal since the user will probably want to click the hyperlink to check out files
|
|
ErrorNotification.bUseThrobber = true;
|
|
|
|
// For adding notifications.
|
|
CheckOutNotificationWeakPtr = FSlateNotificationManager::Get().AddNotification(ErrorNotification);
|
|
}
|
|
else
|
|
{
|
|
CheckOutNotificationWeakPtr.Pin()->SetText(ErrorText);
|
|
CheckOutNotificationWeakPtr.Pin()->ExpireAndFadeout();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UUnrealEdEngine::PromptToCheckoutModifiedPackages( bool bPromptAll )
|
|
{
|
|
TArray<UPackage*> PackagesToCheckout;
|
|
if( bPromptAll )
|
|
{
|
|
for( TMap<TWeakObjectPtr<UPackage>,uint8>::TIterator It(PackageToNotifyState); It; ++It )
|
|
{
|
|
if( It.Key().IsValid() )
|
|
{
|
|
PackagesToCheckout.Add( It.Key().Get() );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( TMap<TWeakObjectPtr<UPackage>,uint8>::TIterator It(PackageToNotifyState); It; ++It )
|
|
{
|
|
if( It.Key().IsValid() && (It.Value() == NS_PendingWarning || It.Value() == NS_PendingPrompt) )
|
|
{
|
|
PackagesToCheckout.Add( It.Key().Get() );
|
|
It.Value() = NS_DialogPrompted;
|
|
}
|
|
}
|
|
}
|
|
|
|
const bool bCheckDirty = true;
|
|
const bool bPromptingAfterModify = true;
|
|
FEditorFileUtils::PromptToCheckoutPackages( bCheckDirty, PackagesToCheckout, NULL, NULL, bPromptingAfterModify );
|
|
}
|
|
|
|
int32 UUnrealEdEngine::InternalGetNumDirtyPackagesThatNeedCheckout(bool bCheckIfAny) const
|
|
{
|
|
int32 PackageCount = 0;
|
|
|
|
if (ISourceControlModule::Get().IsEnabled())
|
|
{
|
|
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
|
for (TMap<TWeakObjectPtr<UPackage>, uint8>::TConstIterator It(PackageToNotifyState); It; ++It)
|
|
{
|
|
const UPackage* Package = It.Key().Get();
|
|
if (Package != NULL)
|
|
{
|
|
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(Package, EStateCacheUsage::Use);
|
|
if (SourceControlState.IsValid() && (SourceControlState->CanCheckout() || !SourceControlState->IsCurrent() || SourceControlState->IsCheckedOutOther()))
|
|
{
|
|
++PackageCount;
|
|
if (bCheckIfAny)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return PackageCount;
|
|
}
|
|
|
|
int32 UUnrealEdEngine::GetNumDirtyPackagesThatNeedCheckout() const
|
|
{
|
|
return InternalGetNumDirtyPackagesThatNeedCheckout(false);
|
|
}
|
|
|
|
bool UUnrealEdEngine::DoDirtyPackagesNeedCheckout() const
|
|
{
|
|
return InternalGetNumDirtyPackagesThatNeedCheckout(true) > 0;
|
|
}
|
|
|
|
bool UUnrealEdEngine::Exec_Edit( UWorld* InWorld, const TCHAR* Str, FOutputDevice& Ar )
|
|
{
|
|
const bool bComponentsSelected = GetSelectedComponentCount() > 0;
|
|
|
|
if( FParse::Command(&Str,TEXT("CUT")) )
|
|
{
|
|
if (GLevelEditorModeTools().ProcessEditCut())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bComponentsSelected)
|
|
{
|
|
// Same transaction language used in CopySelectedActorsToClipboard below
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "Cut", "Cut"));
|
|
|
|
edactCopySelected(InWorld);
|
|
edactDeleteSelected(InWorld);
|
|
}
|
|
else
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "Cut", "Cut"));
|
|
FEditorDelegates::OnEditCutActorsBegin.Broadcast();
|
|
CopySelectedActorsToClipboard(InWorld, true);
|
|
FEditorDelegates::OnEditCutActorsEnd.Broadcast();
|
|
}
|
|
}
|
|
else if( FParse::Command(&Str,TEXT("COPY")) )
|
|
{
|
|
if (GLevelEditorModeTools().ProcessEditCopy())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bComponentsSelected)
|
|
{
|
|
edactCopySelected(InWorld);
|
|
}
|
|
else
|
|
{
|
|
FEditorDelegates::OnEditCopyActorsBegin.Broadcast();
|
|
CopySelectedActorsToClipboard(InWorld, false);
|
|
FEditorDelegates::OnEditCopyActorsEnd.Broadcast();
|
|
}
|
|
}
|
|
else if( FParse::Command(&Str,TEXT("PASTE")) )
|
|
{
|
|
if (GLevelEditorModeTools().ProcessEditPaste())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bComponentsSelected)
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "PasteComponents", "Paste Components"));
|
|
edactPasteSelected(InWorld, false, false, true);
|
|
}
|
|
else
|
|
{
|
|
// How should this paste be handled
|
|
EPasteTo PasteTo = PT_OriginalLocation;
|
|
FText TransDescription = NSLOCTEXT("UnrealEd", "Paste", "Paste");
|
|
|
|
FString TempStr;
|
|
if (FParse::Value(Str, TEXT("TO="), TempStr))
|
|
{
|
|
if (!FCString::Strcmp(*TempStr, TEXT("HERE")))
|
|
{
|
|
PasteTo = PT_Here;
|
|
TransDescription = NSLOCTEXT("UnrealEd", "PasteHere", "Paste Here");
|
|
}
|
|
else
|
|
{
|
|
if (!FCString::Strcmp(*TempStr, TEXT("ORIGIN")))
|
|
{
|
|
PasteTo = PT_WorldOrigin;
|
|
TransDescription = NSLOCTEXT("UnrealEd", "PasteToWorldOrigin", "Paste To World Origin");
|
|
}
|
|
}
|
|
}
|
|
|
|
const FScopedTransaction Transaction(TransDescription);
|
|
FEditorDelegates::OnEditPasteActorsBegin.Broadcast();
|
|
PasteSelectedActorsFromClipboard(InWorld, TransDescription, PasteTo);
|
|
FEditorDelegates::OnEditPasteActorsEnd.Broadcast();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UUnrealEdEngine::Exec_Pivot( const TCHAR* Str, FOutputDevice& Ar )
|
|
{
|
|
if( FParse::Command(&Str,TEXT("HERE")) )
|
|
{
|
|
NoteActorMovement();
|
|
SetPivot( ClickLocation, false, false );
|
|
FinishAllSnaps();
|
|
SetPivotMovedIndependently(true);
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
else if( FParse::Command(&Str,TEXT("SNAPPED")) )
|
|
{
|
|
NoteActorMovement();
|
|
SetPivot( ClickLocation, true, false );
|
|
FinishAllSnaps();
|
|
SetPivotMovedIndependently(true);
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
else if( FParse::Command(&Str,TEXT("CENTERSELECTION")) )
|
|
{
|
|
NoteActorMovement();
|
|
|
|
// Figure out the center location of all selections
|
|
|
|
int32 Count = 0;
|
|
FVector Center(0,0,0);
|
|
|
|
for ( FSelectionIterator It( GetSelectedActorIterator() ) ; It ; ++It )
|
|
{
|
|
AActor* Actor = CastChecked<AActor>(*It);
|
|
|
|
if (ABrush* Brush = Cast<ABrush>(Actor))
|
|
{
|
|
// Treat brushes as a special case; calculate an effective position from the center point of the vertices.
|
|
// This way, "Center on Selection" has a special meaning for brushes.
|
|
TSet<FVector> UniqueVertices;
|
|
FVector VertexCenter = FVector::ZeroVector;
|
|
|
|
if (Brush->Brush && Brush->Brush->Polys)
|
|
{
|
|
for (const auto& Element : Brush->Brush->Polys->Element)
|
|
{
|
|
for (const auto& Vertex : Element.Vertices)
|
|
{
|
|
UniqueVertices.Add((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 * WORLD_MAX);
|
|
FVector V1 = Center + (B * WORLD_MAX);
|
|
FVector V2 = Center - (((A + B) / 2.0f) * WORLD_MAX);
|
|
|
|
// Create a triangle that lays on InPlane
|
|
|
|
Triangle->Init();
|
|
Triangle->PolyFlags = PF_DefaultFlags;
|
|
|
|
new(Triangle->Vertices) 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();
|
|
|
|
bool bSnapToGrid=0;
|
|
FParse::Bool( Str, TEXT("SNAPTOGRID="), bSnapToGrid );
|
|
|
|
// Create a bounding box for the selected static mesh triangles and set the builder brush to match it
|
|
|
|
TArray<FPoly*> SelectedPolys = GetSelectedPolygons();
|
|
CreateBoundingBoxBuilderBrush( InWorld, SelectedPolys, bSnapToGrid );
|
|
|
|
// Create the blocking volume
|
|
|
|
GUnrealEd->Exec( InWorld, TEXT("BRUSH ADDVOLUME CLASS=BlockingVolume") );
|
|
|
|
// Clean up memory
|
|
|
|
for( int x = 0 ; x < SelectedPolys.Num() ; ++x )
|
|
{
|
|
delete SelectedPolys[x];
|
|
}
|
|
|
|
SelectedPolys.Empty();
|
|
|
|
// Finish up
|
|
|
|
RedrawLevelEditingViewports();
|
|
return true;
|
|
}
|
|
else if( FParse::Command(&Str,TEXT("CREATE_BV_CONVEXVOLUME")) )
|
|
{
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "CreateConvexBlockingVolume", "Create Convex Blocking Volume") );
|
|
InWorld->GetDefaultBrush()->Modify();
|
|
|
|
bool bSnapToGrid=0;
|
|
FParse::Bool( Str, TEXT("SNAPTOGRID="), bSnapToGrid );
|
|
|
|
// The rejection tolerance. When figuring out which planes to cut the blocking volume cube with
|
|
// the code will reject any planes that are less than "NormalTolerance" different in their normals.
|
|
//
|
|
// This cuts down on the number of planes that will be used for generating the cutting planes and,
|
|
// as a side effect, eliminates duplicates.
|
|
|
|
float NormalTolerance = 0.25f;
|
|
FParse::Value( Str, TEXT("NORMALTOLERANCE="), NormalTolerance );
|
|
|
|
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();
|
|
|
|
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
|
|
{
|
|
CommonActions->DeleteSelectedElements(SelectionSet, InWorld, FTypedElementDeletionOptions());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 TArray<FTypedElementHandle> DuplicatedElements = CommonActions->DuplicateSelectedElements(SelectionSet, InWorld, GEditor->GetGridLocationOffset(/*bUniformOffset*/false));
|
|
if (DuplicatedElements.Num() > 0)
|
|
{
|
|
SelectionSet->SetSelection(DuplicatedElements, FTypedElementSelectionOptions());
|
|
SelectionSet->NotifyPendingChanges();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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();
|
|
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
|