├── .gitignore ├── Config ├── DefaultEditor.ini ├── DefaultEngine.ini ├── DefaultGame.ini └── HoloLens │ └── HoloLensEngine.ini ├── Content ├── ActorClickBP │ └── BP_FlipNormals.uasset ├── M_White.uasset ├── MeshEditBP │ ├── BP_Boxes.uasset │ └── BP_Noise.uasset ├── SM_Bunny.uasset └── SM_Sphere.uasset ├── LICENSE ├── Plugins └── SampleModelingModeExtension │ ├── Content │ └── Icons │ │ ├── ClickActorBPTool.svg │ │ ├── MeshProcessingBPTool.svg │ │ ├── NoiseTool.svg │ │ └── PlaneTool.svg │ ├── Resources │ └── Icon128.png │ ├── SampleModelingModeExtension.uplugin │ └── Source │ └── SampleModelingModeExtension │ ├── Private │ ├── SampleModelingModeExtensionCommands.cpp │ ├── SampleModelingModeExtensionModule.cpp │ ├── SampleModelingModeExtensionStyle.cpp │ └── Tools │ │ ├── ActorClickedBPTool.cpp │ │ ├── MeshNoiseTool.cpp │ │ ├── MeshPlaneCutTool.cpp │ │ └── MeshProcessingBPTool.cpp │ ├── Public │ ├── SampleModelingModeExtensionCommands.h │ ├── SampleModelingModeExtensionModule.h │ ├── SampleModelingModeExtensionStyle.h │ └── Tools │ │ ├── ActorClickedBPTool.h │ │ ├── MeshNoiseTool.h │ │ ├── MeshPlaneCutTool.h │ │ └── MeshProcessingBPTool.h │ └── SampleModelingModeExtension.Build.cs ├── README.md ├── Source ├── UE5ToolPluginDemo.Target.cs ├── UE5ToolPluginDemo │ ├── UE5ToolPluginDemo.Build.cs │ ├── UE5ToolPluginDemo.cpp │ ├── UE5ToolPluginDemo.h │ ├── UE5ToolPluginDemoGameModeBase.cpp │ └── UE5ToolPluginDemoGameModeBase.h └── UE5ToolPluginDemoEditor.Target.cs └── UE5ToolPluginDemo.uproject /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.xcworkspace 37 | *.sln 38 | *.suo 39 | *.opensdf 40 | *.sdf 41 | *.VC.db 42 | *.VC.opendb 43 | 44 | # Precompiled Assets 45 | SourceArt/**/*.png 46 | SourceArt/**/*.tga 47 | 48 | # Binary Files 49 | Binaries/* 50 | Plugins/*/Binaries/* 51 | 52 | # Builds 53 | Build/* 54 | 55 | # Whitelist PakBlacklist-.txt files 56 | !Build/*/ 57 | Build/*/** 58 | !Build/*/PakBlacklist*.txt 59 | 60 | # Don't ignore icon files in Build 61 | !Build/**/*.ico 62 | 63 | # Built data for maps 64 | *_BuiltData.uasset 65 | 66 | # Configuration files generated by the Editor 67 | Saved/* 68 | 69 | # Compiled source files for the engine to use 70 | Intermediate/* 71 | Plugins/*/Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | -------------------------------------------------------------------------------- /Config/DefaultEditor.ini: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Config/DefaultEngine.ini: -------------------------------------------------------------------------------- 1 | 2 | 3 | [/Script/EngineSettings.GameMapsSettings] 4 | GameDefaultMap=/Engine/Maps/Templates/OpenWorld 5 | 6 | 7 | [/Script/HardwareTargeting.HardwareTargetingSettings] 8 | TargetedHardwareClass=Desktop 9 | AppliedTargetedHardwareClass=Desktop 10 | DefaultGraphicsPerformance=Maximum 11 | AppliedDefaultGraphicsPerformance=Maximum 12 | 13 | [/Script/WindowsTargetPlatform.WindowsTargetSettings] 14 | DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 15 | 16 | [/Script/Engine.RendererSettings] 17 | r.GenerateMeshDistanceFields=False 18 | r.DynamicGlobalIlluminationMethod=0 19 | r.ReflectionMethod=0 20 | r.Shadow.Virtual.Enable=0 21 | r.DefaultFeature.AutoExposure.Method=0 22 | r.DefaultFeature.AutoExposure=False 23 | r.AntiAliasingMethod=1 24 | 25 | [/Script/WorldPartitionEditor.WorldPartitionEditorSettings] 26 | CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet' 27 | 28 | [/Script/Engine.Engine] 29 | +ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/UE5ToolPluginDemo") 30 | +ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/UE5ToolPluginDemo") 31 | +ActiveClassRedirects=(OldClassName="TP_BlankGameModeBase",NewClassName="UE5ToolPluginDemoGameModeBase") 32 | 33 | [/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] 34 | bEnablePlugin=True 35 | bAllowNetworkConnection=True 36 | SecurityToken=BB4BE5484B0AAA859FE244AF8912552F 37 | bIncludeInShipping=False 38 | bAllowExternalStartInShipping=False 39 | bCompileAFSProject=False 40 | bUseCompression=False 41 | bLogFiles=False 42 | bReportStats=False 43 | ConnectionType=USBOnly 44 | bUseManualIPAddress=False 45 | ManualIPAddress= 46 | 47 | -------------------------------------------------------------------------------- /Config/DefaultGame.ini: -------------------------------------------------------------------------------- 1 | 2 | [/Script/EngineSettings.GeneralProjectSettings] 3 | ProjectID=AB79525E4D8131DD036CA3884FECE1E0 4 | -------------------------------------------------------------------------------- /Config/HoloLens/HoloLensEngine.ini: -------------------------------------------------------------------------------- 1 | 2 | 3 | [/Script/HoloLensPlatformEditor.HoloLensTargetSettings] 4 | bBuildForEmulation=False 5 | bBuildForDevice=True 6 | bUseNameForLogo=True 7 | bBuildForRetailWindowsStore=False 8 | bAutoIncrementVersion=False 9 | bShouldCreateAppInstaller=False 10 | AppInstallerInstallationURL= 11 | HoursBetweenUpdateChecks=0 12 | bEnablePIXProfiling=False 13 | TileBackgroundColor=(B=64,G=0,R=0,A=255) 14 | SplashScreenBackgroundColor=(B=64,G=0,R=0,A=255) 15 | +PerCultureResources=(CultureId="",Strings=(PackageDisplayName="",PublisherDisplayName="",PackageDescription="",ApplicationDisplayName="",ApplicationDescription=""),Images=()) 16 | TargetDeviceFamily=Windows.Holographic 17 | MinimumPlatformVersion= 18 | MaximumPlatformVersionTested=10.0.18362.0 19 | MaxTrianglesPerCubicMeter=500.000000 20 | SpatialMeshingVolumeSize=20.000000 21 | CompilerVersion=Default 22 | Windows10SDKVersion=10.0.18362.0 23 | +CapabilityList=internetClientServer 24 | +CapabilityList=privateNetworkClientServer 25 | +Uap2CapabilityList=spatialPerception 26 | bSetDefaultCapabilities=False 27 | SpatializationPlugin= 28 | ReverbPlugin= 29 | OcclusionPlugin= 30 | SoundCueCookQualityIndex=-1 31 | 32 | -------------------------------------------------------------------------------- /Content/ActorClickBP/BP_FlipNormals.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradientspace/UE5ModelingModeExtensionDemo/eef4f369a64090dd09117ceae2dbd34948066be6/Content/ActorClickBP/BP_FlipNormals.uasset -------------------------------------------------------------------------------- /Content/M_White.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradientspace/UE5ModelingModeExtensionDemo/eef4f369a64090dd09117ceae2dbd34948066be6/Content/M_White.uasset -------------------------------------------------------------------------------- /Content/MeshEditBP/BP_Boxes.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradientspace/UE5ModelingModeExtensionDemo/eef4f369a64090dd09117ceae2dbd34948066be6/Content/MeshEditBP/BP_Boxes.uasset -------------------------------------------------------------------------------- /Content/MeshEditBP/BP_Noise.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradientspace/UE5ModelingModeExtensionDemo/eef4f369a64090dd09117ceae2dbd34948066be6/Content/MeshEditBP/BP_Noise.uasset -------------------------------------------------------------------------------- /Content/SM_Bunny.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradientspace/UE5ModelingModeExtensionDemo/eef4f369a64090dd09117ceae2dbd34948066be6/Content/SM_Bunny.uasset -------------------------------------------------------------------------------- /Content/SM_Sphere.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradientspace/UE5ModelingModeExtensionDemo/eef4f369a64090dd09117ceae2dbd34948066be6/Content/SM_Sphere.uasset -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Content/Icons/ClickActorBPTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BP 72 | -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Content/Icons/MeshProcessingBPTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BP 62 | -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Content/Icons/NoiseTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 42 | 46 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Content/Icons/PlaneTool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 42 | 49 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradientspace/UE5ModelingModeExtensionDemo/eef4f369a64090dd09117ceae2dbd34948066be6/Plugins/SampleModelingModeExtension/Resources/Icon128.png -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/SampleModelingModeExtension.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "SampleModelingModeExtension", 6 | "Description": "Sample Modeling Mode Extension Plugin", 7 | "Category": "Other", 8 | "CreatedBy": "Gradientspace Corp", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "SampleModelingModeExtension", 20 | "Type": "Editor", 21 | "LoadingPhase": "Default" 22 | } 23 | ], 24 | "Plugins": [ 25 | { 26 | "Name": "GeometryProcessing", 27 | "Enabled": true 28 | }, 29 | { 30 | "Name": "MeshModelingToolset", 31 | "Enabled": true, 32 | "TargetAllowList": [ "Editor" ] 33 | }, 34 | { 35 | "Name": "ModelingToolsEditorMode", 36 | "Enabled": true 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Private/SampleModelingModeExtensionCommands.cpp: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #include "SampleModelingModeExtensionCommands.h" 5 | #include "EditorStyleSet.h" 6 | #include "InputCoreTypes.h" 7 | #include "SampleModelingModeExtensionStyle.h" 8 | 9 | #define LOCTEXT_NAMESPACE "SampleModelingModeExtensionCommands" 10 | 11 | 12 | 13 | FSampleModelingModeExtensionCommands::FSampleModelingModeExtensionCommands() : 14 | TCommands( 15 | "SampleModelingModeExtensionCommands", // Context name for fast lookup 16 | NSLOCTEXT("Contexts", "SampleModelingModeExtensionCommands", "Sample Modeling Mode Extension"), // Localized context name for displaying 17 | NAME_None, // Parent 18 | FSampleModelingModeExtensionStyle::Get()->GetStyleSetName() // Icon Style Set 19 | ) 20 | { 21 | } 22 | 23 | 24 | void FSampleModelingModeExtensionCommands::RegisterCommands() 25 | { 26 | UI_COMMAND(BeginMeshNoiseTool, "Noise", "Add Noise to selected Mesh", EUserInterfaceActionType::ToggleButton, FInputChord()); 27 | UI_COMMAND(BeginMeshPlaneCutTool, "Cut", "Cut the selected Mesh with a Plane", EUserInterfaceActionType::ToggleButton, FInputChord()); 28 | UI_COMMAND(BeginActorClickedBPTool, "ClickBP", "Run BP on clicked Actors", EUserInterfaceActionType::ToggleButton, FInputChord()); 29 | UI_COMMAND(BeginMeshProcessingBPTool, "MeshEdBP", "Run BP Mesh Processing Operation on selected Mesh", EUserInterfaceActionType::ToggleButton, FInputChord()); 30 | } 31 | 32 | 33 | 34 | 35 | #undef LOCTEXT_NAMESPACE 36 | -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Private/SampleModelingModeExtensionModule.cpp: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #include "SampleModelingModeExtensionModule.h" 5 | #include "SampleModelingModeExtensionStyle.h" 6 | #include "SampleModelingModeExtensionCommands.h" 7 | 8 | #include "Tools/MeshNoiseTool.h" 9 | #include "Tools/MeshPlaneCutTool.h" 10 | #include "Tools/ActorClickedBPTool.h" 11 | #include "Tools/MeshProcessingBPTool.h" 12 | 13 | #define LOCTEXT_NAMESPACE "FSampleModelingModeExtensionModule" 14 | 15 | 16 | 17 | // IModuleInterface API implementation 18 | 19 | void FSampleModelingModeExtensionModule::StartupModule() 20 | { 21 | FSampleModelingModeExtensionStyle::Initialize(); 22 | FSampleModelingModeExtensionCommands::Register(); 23 | 24 | IModularFeatures::Get().RegisterModularFeature(IModelingModeToolExtension::GetModularFeatureName(), this); 25 | } 26 | 27 | void FSampleModelingModeExtensionModule::ShutdownModule() 28 | { 29 | IModularFeatures::Get().UnregisterModularFeature(IModelingModeToolExtension::GetModularFeatureName(), this); 30 | 31 | FSampleModelingModeExtensionCommands::Unregister(); 32 | FSampleModelingModeExtensionStyle::Shutdown(); 33 | } 34 | 35 | 36 | 37 | // IModelingModeToolExtension API implementation 38 | 39 | FText FSampleModelingModeExtensionModule::GetExtensionName() 40 | { 41 | return LOCTEXT("ExtensionName", "Sample Modeling Extension"); 42 | } 43 | 44 | FText FSampleModelingModeExtensionModule::GetToolSectionName() 45 | { 46 | return LOCTEXT("SectionName", "Extension Demo"); 47 | } 48 | 49 | void FSampleModelingModeExtensionModule::GetExtensionTools(const FExtensionToolQueryInfo& QueryInfo, TArray& ToolsOut) 50 | { 51 | FExtensionToolDescription MeshNoiseToolInfo; 52 | MeshNoiseToolInfo.ToolName = LOCTEXT("MeshNoiseTool", "MeshNoise"); 53 | MeshNoiseToolInfo.ToolCommand = FSampleModelingModeExtensionCommands::Get().BeginMeshNoiseTool; 54 | MeshNoiseToolInfo.ToolBuilder = NewObject(); 55 | ToolsOut.Add(MeshNoiseToolInfo); 56 | 57 | FExtensionToolDescription MeshPlaneCutToolInfo; 58 | MeshPlaneCutToolInfo.ToolName = LOCTEXT("MeshPlaneCut", "PlaneCut"); 59 | MeshPlaneCutToolInfo.ToolCommand = FSampleModelingModeExtensionCommands::Get().BeginMeshPlaneCutTool; 60 | MeshPlaneCutToolInfo.ToolBuilder = NewObject(); 61 | ToolsOut.Add(MeshPlaneCutToolInfo); 62 | 63 | FExtensionToolDescription BPActionToolInfo; 64 | BPActionToolInfo.ToolName = LOCTEXT("ActorClickedBPTool", "ActorClickedBP"); 65 | BPActionToolInfo.ToolCommand = FSampleModelingModeExtensionCommands::Get().BeginActorClickedBPTool; 66 | BPActionToolInfo.ToolBuilder = NewObject(); 67 | ToolsOut.Add(BPActionToolInfo); 68 | 69 | FExtensionToolDescription MeshProcessingBPToolInfo; 70 | MeshProcessingBPToolInfo.ToolName = LOCTEXT("MeshProcessingBPTool", "MeshProcessingBP"); 71 | MeshProcessingBPToolInfo.ToolCommand = FSampleModelingModeExtensionCommands::Get().BeginMeshProcessingBPTool; 72 | MeshProcessingBPToolInfo.ToolBuilder = NewObject(); 73 | ToolsOut.Add(MeshProcessingBPToolInfo); 74 | 75 | } 76 | 77 | #undef LOCTEXT_NAMESPACE 78 | 79 | IMPLEMENT_MODULE(FSampleModelingModeExtensionModule, SampleModelingModeExtension) -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Private/SampleModelingModeExtensionStyle.cpp: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #include "SampleModelingModeExtensionStyle.h" 5 | #include "Styling/SlateStyleRegistry.h" 6 | #include "Styling/SlateTypes.h" 7 | #include "Styling/CoreStyle.h" 8 | #include "EditorStyleSet.h" 9 | #include "Interfaces/IPluginManager.h" 10 | #include "SlateOptMacros.h" 11 | #include "Styling/SlateStyleMacros.h" 12 | 13 | // IMAGE_BRUSH_SVG macro used below needs RootToContentDir to be defined? 14 | #define RootToContentDir StyleSet->RootToContentDir 15 | 16 | FString FSampleModelingModeExtensionStyle::InContent(const FString& RelativePath, const ANSICHAR* Extension) 17 | { 18 | static FString ContentDir = IPluginManager::Get().FindPlugin(TEXT("SampleModelingModeExtension"))->GetContentDir(); 19 | return (ContentDir / RelativePath) + Extension; 20 | } 21 | 22 | TSharedPtr< FSlateStyleSet > FSampleModelingModeExtensionStyle::StyleSet = nullptr; 23 | TSharedPtr< class ISlateStyle > FSampleModelingModeExtensionStyle::Get() { return StyleSet; } 24 | 25 | FName FSampleModelingModeExtensionStyle::GetStyleSetName() 26 | { 27 | static FName StyleName(TEXT("SampleModelingModeExtensionStyle")); 28 | return StyleName; 29 | } 30 | 31 | BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION 32 | 33 | void FSampleModelingModeExtensionStyle::Initialize() 34 | { 35 | const FVector2D DefaultIconSize(20.0f, 20.0f); 36 | 37 | // Only register once 38 | if (StyleSet.IsValid()) 39 | { 40 | return; 41 | } 42 | 43 | StyleSet = MakeShareable(new FSlateStyleSet(GetStyleSetName())); 44 | StyleSet->SetContentRoot(IPluginManager::Get().FindPlugin(TEXT("SampleModelingModeExtension"))->GetContentDir()); 45 | StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); 46 | 47 | StyleSet->Set("SampleModelingModeExtensionCommands.BeginMeshNoiseTool", new IMAGE_BRUSH_SVG("Icons/NoiseTool", DefaultIconSize)); 48 | StyleSet->Set("SampleModelingModeExtensionCommands.BeginMeshPlaneCutTool", new IMAGE_BRUSH_SVG("Icons/PlaneTool", DefaultIconSize)); 49 | StyleSet->Set("SampleModelingModeExtensionCommands.BeginActorClickedBPTool", new IMAGE_BRUSH_SVG("Icons/ClickActorBPTool", DefaultIconSize)); 50 | StyleSet->Set("SampleModelingModeExtensionCommands.BeginMeshProcessingBPTool", new IMAGE_BRUSH_SVG("Icons/MeshProcessingBPTool", DefaultIconSize)); 51 | 52 | FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); 53 | }; 54 | 55 | END_SLATE_FUNCTION_BUILD_OPTIMIZATION 56 | 57 | #undef IMAGE_BRUSH 58 | #undef BOX_BRUSH 59 | #undef DEFAULT_FONT 60 | 61 | void FSampleModelingModeExtensionStyle::Shutdown() 62 | { 63 | if (StyleSet.IsValid()) 64 | { 65 | FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); 66 | ensure(StyleSet.IsUnique()); 67 | StyleSet.Reset(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Private/Tools/ActorClickedBPTool.cpp: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #include "Tools/ActorClickedBPTool.h" 5 | #include "ToolSceneQueriesUtil.h" 6 | 7 | 8 | 9 | UInteractiveTool* UActorClickedBPToolBuilder::BuildTool(const FToolBuilderState& SceneState) const 10 | { 11 | UActorClickedBPTool* NewTool = NewObject(SceneState.ToolManager); 12 | return NewTool; 13 | } 14 | 15 | 16 | void UActorClickedBPTool::Setup() 17 | { 18 | USingleClickTool::Setup(); 19 | 20 | PropertySet = NewObject(this); 21 | AddToolPropertySource(PropertySet); 22 | PropertySet->RestoreProperties(this); 23 | } 24 | 25 | void UActorClickedBPTool::Shutdown(EToolShutdownType ShutdownType) 26 | { 27 | PropertySet->SaveProperties(this); 28 | 29 | USingleClickTool::Shutdown(ShutdownType); 30 | } 31 | 32 | 33 | FInputRayHit UActorClickedBPTool::IsHitByClick(const FInputDeviceRay& ClickPos) 34 | { 35 | // cast ray into scene 36 | FHitResult Result; 37 | if (ToolSceneQueriesUtil::FindNearestVisibleObjectHit(this, Result, ClickPos.WorldRay)) 38 | { 39 | return FInputRayHit(Result.Distance); 40 | } 41 | return FInputRayHit(); 42 | } 43 | 44 | 45 | void UActorClickedBPTool::OnClicked(const FInputDeviceRay& ClickPos) 46 | { 47 | FHitResult Result; 48 | if (ToolSceneQueriesUtil::FindNearestVisibleObjectHit(this, Result, ClickPos.WorldRay)) 49 | { 50 | AActor* Actor = Result.HitObjectHandle.FetchActor(); 51 | if (Actor != nullptr && PropertySet->Operation != nullptr) 52 | { 53 | UClass* ClassType = PropertySet->Operation; 54 | UActorClickedBPToolOperation* ClassObject = NewObject((UObject*)GetTransientPackage(), ClassType); 55 | if (ClassObject) 56 | { 57 | ClassObject->OnApplyActionToActor(Actor); 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Private/Tools/MeshNoiseTool.cpp: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #include "Tools/MeshNoiseTool.h" 5 | #include "InteractiveToolManager.h" 6 | #include "ToolBuilderUtil.h" 7 | #include "DynamicMesh/DynamicMesh3.h" 8 | #include "DynamicMesh/MeshNormals.h" 9 | #include "Operations/PNTriangles.h" 10 | #include "ModelingOperators.h" 11 | 12 | using namespace UE::Geometry; 13 | 14 | #define LOCTEXT_NAMESPACE "UMeshNoiseTool" 15 | 16 | 17 | 18 | 19 | UMeshNoiseTool::UMeshNoiseTool() 20 | { 21 | SetToolDisplayName(LOCTEXT("ToolName", "Noise")); 22 | } 23 | 24 | 25 | 26 | void UMeshNoiseTool::InitializeProperties() 27 | { 28 | NoiseProperties = NewObject(this); 29 | AddToolPropertySource(NoiseProperties); 30 | NoiseProperties->RestoreProperties(this); 31 | NoiseProperties->WatchProperty(NoiseProperties->Subdivisions, [&](int) { InvalidateResult(); }); 32 | NoiseProperties->WatchProperty(NoiseProperties->NoiseType, [&](EMeshNoiseToolNoiseType) { InvalidateResult(); }); 33 | NoiseProperties->WatchProperty(NoiseProperties->Scale, [&](float) { InvalidateResult(); }); 34 | NoiseProperties->WatchProperty(NoiseProperties->Frequency, [&](float) { InvalidateResult(); }); 35 | NoiseProperties->WatchProperty(NoiseProperties->Seed, [&](int NewSeed) { InvalidateResult(); }); 36 | } 37 | 38 | 39 | 40 | void UMeshNoiseTool::OnShutdown(EToolShutdownType ShutdownType) 41 | { 42 | NoiseProperties->SaveProperties(this); 43 | } 44 | 45 | 46 | FText UMeshNoiseTool::GetToolMessageString() const 47 | { 48 | return LOCTEXT("StartMeshNoiseMessage", "Add noise to the mesh vertex positions."); 49 | } 50 | 51 | FText UMeshNoiseTool::GetAcceptTransactionName() const 52 | { 53 | return LOCTEXT("MeshNoiseToolTransactionName", "Mesh Noise"); 54 | } 55 | 56 | 57 | bool UMeshNoiseTool::HasMeshTopologyChanged() const 58 | { 59 | return false; 60 | } 61 | 62 | 63 | 64 | namespace Local 65 | { 66 | 67 | /** 68 | * FMeshPlaneCutOp actually computes the mesh deformation. The constructor runs on the game thread 69 | * (called in MakeNewOperator below) however the CalculateResult function is run from a 70 | * background compute thread. 71 | */ 72 | class FMeshNoiseOp : public FDynamicMeshOperator 73 | { 74 | public: 75 | struct FOptions 76 | { 77 | int32 Subdivisions = 0; 78 | 79 | EMeshNoiseToolNoiseType NoiseType; 80 | double Magnitude = 1.0; 81 | int32 RandomSeed = 0; 82 | double Frequency = 1.0; 83 | }; 84 | 85 | TSharedPtr BaseMeshNormals; 86 | 87 | FMeshNoiseOp(const FDynamicMesh3* Mesh, FOptions Options) 88 | { 89 | UseOptions = Options; 90 | ResultMesh->Copy(*Mesh); // copy input mesh into output mesh. Do not hold onto input Mesh reference as it is temporary!! 91 | } 92 | 93 | virtual ~FMeshNoiseOp() override {} 94 | 95 | // set ability on protected transform. 96 | void SetTransform(const FTransformSRT3d& XForm) 97 | { 98 | ResultTransform = XForm; 99 | } 100 | 101 | // Called on background thread to compute the mesh op result. 102 | // The input mesh is stored and returned via .ResultMesh member. 103 | // .ResultInfo member is used to indicate success/failure 104 | virtual void CalculateResult(FProgressCancel* Progress) override 105 | { 106 | ResultInfo = FGeometryResult(); 107 | 108 | FMeshNormals SubdividedMeshNormals; 109 | 110 | // If subdivisions were requested, compute it 111 | // (Excercise for the reader: This mesh could be cached at the Tool level...) 112 | if (UseOptions.Subdivisions > 0) 113 | { 114 | FPNTriangles PNTriangles(ResultMesh.Get()); 115 | PNTriangles.TessellationLevel = UseOptions.Subdivisions; 116 | PNTriangles.Progress = Progress; 117 | if (PNTriangles.Validate() == EOperationValidationResult::Ok) 118 | { 119 | PNTriangles.Compute(); 120 | } 121 | 122 | // once we have subdivided, we will need to recompute vertex normals on the base mesh... 123 | if (Progress->Cancelled() == false) 124 | { 125 | SubdividedMeshNormals = FMeshNormals(ResultMesh.Get()); 126 | SubdividedMeshNormals.ComputeVertexNormals(); 127 | } 128 | } 129 | 130 | // abort if we were cancelled 131 | if (ResultInfo.CheckAndSetCancelled(Progress)) 132 | { 133 | return; 134 | } 135 | 136 | // create stream for randomization 137 | FRandomStream Stream(UseOptions.RandomSeed); 138 | 139 | // Loop over vertices and update positions in-place 140 | for (int32 vid : ResultMesh->VertexIndicesItr()) 141 | { 142 | FVector3d Position = ResultMesh->GetVertex(vid); 143 | FVector3d Normal = (UseOptions.Subdivisions > 0) ? SubdividedMeshNormals[vid] : (*BaseMeshNormals)[vid]; 144 | 145 | FVector3d NewPosition = Position; 146 | if (UseOptions.NoiseType == EMeshNoiseToolNoiseType::Random) 147 | { 148 | double RandomAlpha = Stream.GetFraction(); 149 | NewPosition = Position + (UseOptions.Magnitude * RandomAlpha * Normal); 150 | } 151 | else 152 | { 153 | // Frequency is manipulated here to provide a nicer range for the slider. This is scale-dependent, though! 154 | double NoiseValue = FMath::PerlinNoise3D( FMathd::Pow(UseOptions.Frequency * 0.1, 2.0) * Position); 155 | NewPosition = Position + (UseOptions.Magnitude * NoiseValue * Normal); 156 | } 157 | 158 | ResultMesh->SetVertex(vid, NewPosition); 159 | 160 | // don't check for cancel every iteration because it is somewhat expensive 161 | if ( vid % 1000 && ResultInfo.CheckAndSetCancelled(Progress)) 162 | { 163 | return; 164 | } 165 | } 166 | 167 | // recalculate normals 168 | if (Progress->Cancelled() == false) 169 | { 170 | if (ResultMesh->HasAttributes()) 171 | { 172 | FMeshNormals::QuickRecomputeOverlayNormals(*ResultMesh); 173 | } 174 | else 175 | { 176 | FMeshNormals::QuickComputeVertexNormals(*ResultMesh); 177 | } 178 | } 179 | 180 | ResultInfo.SetSuccess(true, Progress); 181 | } 182 | 183 | 184 | protected: 185 | FOptions UseOptions; 186 | }; 187 | 188 | 189 | } 190 | 191 | 192 | 193 | TUniquePtr UMeshNoiseTool::MakeNewOperator() 194 | { 195 | // Copy options from the Property Sets. Note that it is not safe to pass the PropertySet directly 196 | // to the MeshOp because the property set may be modified while the MeshOp computes in the background! 197 | Local::FMeshNoiseOp::FOptions Options; 198 | Options.Magnitude = NoiseProperties->Scale; 199 | Options.NoiseType = NoiseProperties->NoiseType; 200 | Options.RandomSeed = NoiseProperties->Seed; 201 | Options.Frequency = NoiseProperties->Frequency; 202 | Options.Subdivisions = NoiseProperties->Subdivisions; 203 | 204 | TUniquePtr MeshOp = MakeUnique(&GetInitialMesh(), Options); 205 | MeshOp->SetTransform( (FTransform3d)GetPreviewTransform() ); 206 | MeshOp->BaseMeshNormals = GetInitialVtxNormals(); 207 | 208 | return MeshOp; 209 | } 210 | 211 | 212 | 213 | 214 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Private/Tools/MeshPlaneCutTool.cpp: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #include "Tools/MeshPlaneCutTool.h" 5 | #include "InteractiveToolManager.h" 6 | #include "InteractiveGizmoManager.h" 7 | #include "DynamicMesh/DynamicMesh3.h" 8 | #include "DynamicMesh/MeshNormals.h" 9 | #include "DynamicMesh/MeshTransforms.h" 10 | #include "ModelingOperators.h" 11 | #include "BaseGizmos/TransformGizmoUtil.h" 12 | 13 | #include "Operations/MeshPlaneCut.h" 14 | #include "ConstrainedDelaunay2.h" 15 | 16 | using namespace UE::Geometry; 17 | 18 | #define LOCTEXT_NAMESPACE "UMeshPlaneCutTool" 19 | 20 | 21 | 22 | 23 | UMeshPlaneCutTool::UMeshPlaneCutTool() 24 | { 25 | SetToolDisplayName(LOCTEXT("ToolName", "Plane Cut")); 26 | } 27 | 28 | 29 | 30 | void UMeshPlaneCutTool::InitializeProperties() 31 | { 32 | // create the tool options property set 33 | Properties = NewObject(this); 34 | AddToolPropertySource(Properties); 35 | Properties->RestoreProperties(this); 36 | 37 | // initialize the plane to be at the center of the target object bounding box, in world space 38 | FAxisAlignedBox3d Bounds = GetInitialMesh().GetBounds(); 39 | FVector3d WorldSpaceOrigin = GetPreviewTransform().TransformPosition(Bounds.Center()); 40 | PlaneTransform = FTransform3d(WorldSpaceOrigin); 41 | 42 | // Initialize the Position and Rotation tool properties. This should be done before 43 | // setting up the Gizmo to avoid triggering an initial change event 44 | Properties->Position = PlaneTransform.GetTranslation(); 45 | Properties->Rotation = PlaneTransform.Rotator(); 46 | 47 | // create and configure the Gizmo 48 | InitializeTransformGizmo(); 49 | 50 | // Watch for changes to the Position and Rotation properties, and update the Transform & Gizmo accordingly 51 | Properties->WatchProperty(Properties->Position, [this](const FVector& NewPosition) 52 | { 53 | PlaneTransform.SetTranslation(NewPosition); 54 | PlaneTransformGizmo->SetNewGizmoTransform(PlaneTransform); 55 | InvalidateResult(); 56 | }); 57 | Properties->WatchProperty(Properties->Rotation, [this](const FRotator& NewRotation) 58 | { 59 | PlaneTransform = FTransform3d(NewRotation, PlaneTransform.GetTranslation()); 60 | PlaneTransformGizmo->SetNewGizmoTransform(PlaneTransform); 61 | InvalidateResult(); 62 | }); 63 | } 64 | 65 | 66 | void UMeshPlaneCutTool::InitializeTransformGizmo() 67 | { 68 | // create a "transform proxy" for the Gizmo to manipulate 69 | PlaneTransformProxy = NewObject(this); 70 | // now create a Gizmo 71 | PlaneTransformGizmo = UE::TransformGizmoUtil::CreateCustomTransformGizmo(GetToolManager(), 72 | ETransformGizmoSubElements::StandardTranslateRotate, this); 73 | // listen for changes to the TransformProxy caused by the Gizmo 74 | PlaneTransformProxy->OnTransformChanged.AddUObject(this, &UMeshPlaneCutTool::OnGizmoTransformChanged); 75 | // now set the Proxy as the target of the Gizmo 76 | PlaneTransformGizmo->SetActiveTarget(PlaneTransformProxy, GetToolManager()); 77 | 78 | // update the Gizmo with the new Plane/Transform 79 | PlaneTransformGizmo->ReinitializeGizmoTransform(PlaneTransform); 80 | } 81 | 82 | 83 | void UMeshPlaneCutTool::OnGizmoTransformChanged(UTransformProxy* Proxy, FTransform Transform) 84 | { 85 | PlaneTransform = Transform; 86 | 87 | // update the Position and Rotation properties 88 | Properties->Position = PlaneTransform.GetTranslation(); 89 | Properties->Rotation = PlaneTransform.Rotator(); 90 | // The changes above will trigger the Property Watches on Position and Rotation that 91 | // were configured in ::InitializeProperties() on the next Tick. This will result in 92 | // unnecessary change events. Doing a SilentUpdate of the PropertySet will prevent that from happening. 93 | Properties->SilentUpdateWatched(); 94 | // We have changed the property values, but the Editor-level details panel will not pick up 95 | // these changes automatically, so we notify it here (note: this is expensive! but unavoidable, unfortunately) 96 | NotifyOfPropertyChangeByTool(Properties); 97 | 98 | // kick off a recompute of the mesh 99 | InvalidateResult(); 100 | } 101 | 102 | 103 | void UMeshPlaneCutTool::OnShutdown(EToolShutdownType ShutdownType) 104 | { 105 | // destroy the gizmo we created 106 | GetToolManager()->GetPairedGizmoManager()->DestroyAllGizmosByOwner(this); 107 | 108 | // save tool settings 109 | Properties->SaveProperties(this); 110 | } 111 | 112 | 113 | FText UMeshPlaneCutTool::GetToolMessageString() const 114 | { 115 | return LOCTEXT("StartToolMessage", "Do things to the current mesh with a Plane."); 116 | } 117 | 118 | FText UMeshPlaneCutTool::GetAcceptTransactionName() const 119 | { 120 | return LOCTEXT("ToolTransactionName", "Plane Op"); 121 | } 122 | 123 | 124 | bool UMeshPlaneCutTool::HasMeshTopologyChanged() const 125 | { 126 | return true; 127 | } 128 | 129 | 130 | 131 | namespace Local 132 | { 133 | 134 | /** 135 | * FMeshPlaneCutOp actually computes the plane cut. The constructor runs on the game thread 136 | * (called in MakeNewOperator below) however the CalculateResult function is run from a 137 | * background compute thread. 138 | */ 139 | class FMeshPlaneCutOp : public FDynamicMeshOperator 140 | { 141 | public: 142 | struct FOptions 143 | { 144 | FTransform LocalToWorld; 145 | FTransform WorldPlane; 146 | bool bFillHole; 147 | }; 148 | 149 | FMeshPlaneCutOp(const FDynamicMesh3* Mesh, FOptions Options) 150 | { 151 | UseOptions = Options; 152 | ResultMesh->Copy(*Mesh); // copy input mesh into output mesh. Do not hold onto input Mesh reference as it is temporary!! 153 | } 154 | 155 | virtual ~FMeshPlaneCutOp() override {} 156 | 157 | // set ability on protected transform. 158 | void SetTransform(const FTransformSRT3d& XForm) 159 | { 160 | ResultTransform = XForm; 161 | } 162 | 163 | // base class overrides this. Results in updated ResultMesh. This function runs in a background thread!! 164 | virtual void CalculateResult(FProgressCancel* Progress) override 165 | { 166 | // transform mesh to world space because that is where plane is (this will correctly handle nonuniform scale) 167 | MeshTransforms::ApplyTransform(*ResultMesh, (FTransformSRT3d)UseOptions.LocalToWorld); 168 | 169 | FFrame3d Frame(UseOptions.WorldPlane); 170 | FMeshPlaneCut Cut(ResultMesh.Get(), Frame.Origin, Frame.Z()); 171 | Cut.Cut(); 172 | 173 | if (UseOptions.bFillHole) 174 | { 175 | Cut.HoleFill(ConstrainedDelaunayTriangulate, false); 176 | } 177 | 178 | MeshTransforms::ApplyTransformInverse(*ResultMesh, (FTransformSRT3d)UseOptions.LocalToWorld); 179 | } 180 | 181 | 182 | protected: 183 | FOptions UseOptions; 184 | }; 185 | 186 | 187 | } 188 | 189 | 190 | 191 | TUniquePtr UMeshPlaneCutTool::MakeNewOperator() 192 | { 193 | // Copy options from the Property Sets. Note that it is not safe to pass the PropertySet directly 194 | // to the MeshOp because the property set may be modified while the MeshOp computes in the background! 195 | Local::FMeshPlaneCutOp::FOptions Options; 196 | Options.LocalToWorld = GetPreviewTransform(); 197 | Options.WorldPlane = PlaneTransform; 198 | Options.bFillHole = Properties->bFillHole; 199 | 200 | const FDynamicMesh3* Mesh = &GetInitialMesh(); 201 | TUniquePtr MeshOp = MakeUnique(Mesh, Options); 202 | 203 | FTransform3d XForm3d(GetPreviewTransform()); 204 | MeshOp->SetTransform(XForm3d); 205 | 206 | return MeshOp; 207 | } 208 | 209 | 210 | 211 | 212 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Private/Tools/MeshProcessingBPTool.cpp: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #include "Tools/MeshProcessingBPTool.h" 5 | #include "InteractiveToolManager.h" 6 | #include "DynamicMesh/DynamicMesh3.h" 7 | #include "DynamicMesh/MeshNormals.h" 8 | #include "Operations/PNTriangles.h" 9 | #include "ModelingOperators.h" 10 | #include "Util/ProgressCancel.h" 11 | #include "UObject/StrongObjectPtr.h" 12 | 13 | using namespace UE::Geometry; 14 | 15 | #define LOCTEXT_NAMESPACE "UMeshProcessingBPTool" 16 | 17 | 18 | 19 | /** 20 | * FBackgroundMeshProcessingExecutor is a helper class that the UMeshProcessingBPTool and 21 | * FBPMeshProcessingOp's can use to force the UMeshProcessingBPToolOperation blueprints subclasses 22 | * to execute on the Game Thread. Basically if the UMeshProcessingBPToolOperation::GetEnableBackgroundExecution() 23 | * function returns true (default false), then instead of directly calling UMeshProcessingBPToolOperation::OnRecomputeMesh, 24 | * the Op will call QueueForMainThread() below, and then busy-wait. The Tool calls ExecuteOneOperationOnGameThread() 25 | * once per tick, allowing a BP execution on the game thread, which then cancels the busy-wait 26 | * in the background-thread FBPMeshProcessingOp, allowing it to complete (if not cancelled). 27 | * 28 | * FBackgroundMeshProcessingExecutor is also used to keep the UMeshProcessingBPToolOperation and UDynamicMesh 29 | * for a pending FBPMeshProcessingOp execution alive while the background thread needs them. 30 | * Something needs to do this, otherwise they will be randomly garbage-collected, as UMeshProcessingBPTool 31 | * does not directly reference these temp objects (and cannot easily, as multiple may be active). 32 | * So, we make this class a FGCObject, and have it own references to the active Operations/DynamicMeshes. 33 | * This all works relatively well, except that WaitForAllPendingToFinish() may block Tool shutdown 34 | * while it waits for pending operations to be completed. 35 | */ 36 | class FBackgroundMeshProcessingExecutor : public FGCObject 37 | { 38 | public: 39 | // FBPMeshProcessingOp implements this interface so that we can force it's BP to be executed 40 | class IExecuteTarget 41 | { 42 | public: 43 | virtual void ExecuteBlueprint(FProgressCancel* Progress) = 0; 44 | }; 45 | 46 | // just a handy tuple struct 47 | struct FPendingOperation 48 | { 49 | IExecuteTarget* Target; 50 | FProgressCancel* Progress; 51 | }; 52 | 53 | // list of background executions that are busy-waiting for their BPs to be executed 54 | TArray PendingOperations; 55 | FCriticalSection PendingLock; 56 | 57 | // FBPMeshProcessingOp calls this from background thread during CalculateResult() to 58 | // request a game-thread BP execution 59 | void QueueForMainThread(FPendingOperation Operation) 60 | { 61 | PendingLock.Lock(); 62 | PendingOperations.Add(Operation); 63 | PendingLock.Unlock(); 64 | } 65 | 66 | // UMeshProcessingBPTool calls this once per Tick to do a game-thread BP execution 67 | void ExecuteOneOperationOnGameThread() 68 | { 69 | PendingLock.Lock(); 70 | if (PendingOperations.Num() > 0) 71 | { 72 | FPendingOperation Operation = PendingOperations.Pop(false); 73 | Operation.Target->ExecuteBlueprint(Operation.Progress); 74 | } 75 | PendingLock.Unlock(); 76 | } 77 | 78 | // call this to clear out any pending computes 79 | void ClearOnToolShutdown() 80 | { 81 | PendingLock.Lock(); 82 | for (FPendingOperation Operation : PendingOperations) 83 | { 84 | Operation.Target->ExecuteBlueprint(Operation.Progress); 85 | } 86 | PendingLock.Unlock(); 87 | } 88 | 89 | 90 | 91 | TArray TempMeshes; 92 | FCriticalSection TempMeshesLock; 93 | 94 | TArray TempOperations; 95 | FCriticalSection TempOperationsLock; 96 | 97 | virtual FString GetReferencerName() const override { return TEXT("FBackgroundMeshProcessingExecutor"); } 98 | 99 | virtual void AddReferencedObjects(FReferenceCollector& Collector) override 100 | { 101 | TempMeshesLock.Lock(); 102 | for (UDynamicMesh* Mesh : TempMeshes) 103 | { 104 | Collector.AddReferencedObject(Mesh); 105 | } 106 | TempMeshesLock.Unlock(); 107 | 108 | TempOperationsLock.Lock(); 109 | for (UMeshProcessingBPToolOperation* Operation: TempOperations) 110 | { 111 | Collector.AddReferencedObject(Operation); 112 | } 113 | TempOperationsLock.Unlock(); 114 | } 115 | 116 | virtual void AddTempMesh(UDynamicMesh* Mesh) 117 | { 118 | TempMeshesLock.Lock(); 119 | check(TempMeshes.Contains(Mesh) == false); 120 | TempMeshes.Add(Mesh); 121 | TempMeshesLock.Unlock(); 122 | } 123 | 124 | virtual void ReleaseTempMesh(UDynamicMesh* Mesh) 125 | { 126 | TempMeshesLock.Lock(); 127 | check(TempMeshes.Contains(Mesh)); 128 | TempMeshes.Remove(Mesh); 129 | TempMeshesLock.Unlock(); 130 | } 131 | 132 | virtual void AddTempOperation(UMeshProcessingBPToolOperation* Operation) 133 | { 134 | TempOperationsLock.Lock(); 135 | check(TempOperations.Contains(Operation) == false); 136 | TempOperations.Add(Operation); 137 | TempOperationsLock.Unlock(); 138 | } 139 | 140 | virtual void ReleaseTempOperation(UMeshProcessingBPToolOperation* Operation) 141 | { 142 | TempOperationsLock.Lock(); 143 | check(TempOperations.Contains(Operation)); 144 | TempOperations.Remove(Operation); 145 | TempOperationsLock.Unlock(); 146 | } 147 | 148 | virtual void WaitForAllPendingToFinish() 149 | { 150 | bool bDone = false; 151 | while (!bDone) 152 | { 153 | bDone = true; 154 | TempMeshesLock.Lock(); 155 | bDone = bDone && (TempMeshes.Num() == 0); 156 | TempMeshesLock.Unlock(); 157 | TempOperationsLock.Lock(); 158 | bDone = bDone && (TempOperations.Num() == 0); 159 | TempOperationsLock.Unlock(); 160 | 161 | if (!bDone) 162 | { 163 | FPlatformProcess::Sleep(0.25f); 164 | } 165 | } 166 | } 167 | }; 168 | 169 | 170 | 171 | // By default, a UMeshProcessingBPToolOperation BP-subclass can only be executed on the game thread (safest) 172 | bool UMeshProcessingBPToolOperation::GetEnableBackgroundExecution_Implementation() 173 | { 174 | return false; 175 | } 176 | 177 | 178 | 179 | UMeshProcessingBPTool::UMeshProcessingBPTool() 180 | { 181 | SetToolDisplayName(LOCTEXT("ToolName", "Noise")); 182 | } 183 | 184 | void UMeshProcessingBPTool::InitializeProperties() 185 | { 186 | Properties = NewObject(this); 187 | AddToolPropertySource(Properties); 188 | Properties->RestoreProperties(this); 189 | 190 | Executor = MakeShared(); 191 | } 192 | 193 | void UMeshProcessingBPTool::OnPropertyModified(UObject* PropertySet, FProperty* Property) 194 | { 195 | InvalidateResult(); 196 | } 197 | 198 | void UMeshProcessingBPTool::OnTick(float DeltaTime) 199 | { 200 | UBaseMeshProcessingTool::OnTick(DeltaTime); 201 | 202 | Executor->ExecuteOneOperationOnGameThread(); 203 | } 204 | 205 | void UMeshProcessingBPTool::OnShutdown(EToolShutdownType ShutdownType) 206 | { 207 | Executor->ClearOnToolShutdown(); 208 | Executor->WaitForAllPendingToFinish(); 209 | 210 | Properties->SaveProperties(this); 211 | } 212 | 213 | 214 | FText UMeshProcessingBPTool::GetToolMessageString() const 215 | { 216 | return LOCTEXT("StartMeshNoiseMessage", "Add noise to the mesh vertex positions."); 217 | } 218 | 219 | FText UMeshProcessingBPTool::GetAcceptTransactionName() const 220 | { 221 | return LOCTEXT("MeshProcessingBPToolTransactionName", "Mesh Noise"); 222 | } 223 | 224 | 225 | bool UMeshProcessingBPTool::HasMeshTopologyChanged() const 226 | { 227 | return true; 228 | } 229 | 230 | 231 | 232 | namespace Local 233 | { 234 | 235 | class FBPMeshProcessingOp : public FDynamicMeshOperator, public FBackgroundMeshProcessingExecutor::IExecuteTarget 236 | { 237 | public: 238 | struct FOptions 239 | { 240 | // TStrongObjectPtr is ugly here but Operation and TempMesh need to be kept alive for lifetime of the Op 241 | UMeshProcessingBPToolOperation* Operation; 242 | FMeshProcessingBPToolParameters Settings; 243 | 244 | UDynamicMesh* TempMesh; 245 | 246 | TSharedPtr Executor; 247 | }; 248 | 249 | // If executing BP on game thread from background compute thread, the op will busy-wait for 250 | // this boolean to become true 251 | std::atomic bBlueprintExecuted; 252 | 253 | FBPMeshProcessingOp(const FDynamicMesh3* Mesh, FOptions Options) 254 | { 255 | UseOptions = Options; 256 | ResultMesh->Copy(*Mesh); // copy input mesh into output mesh. Do not hold onto input Mesh reference as it is temporary!! 257 | 258 | bBlueprintExecuted = false; 259 | } 260 | 261 | virtual ~FBPMeshProcessingOp() override {} 262 | 263 | // set ability on protected transform. 264 | void SetTransform(const FTransformSRT3d& XForm) 265 | { 266 | ResultTransform = XForm; 267 | } 268 | 269 | // this may be called on Game Thread or in background compute thread, depending on Operation requirements 270 | virtual void ExecuteBlueprint(FProgressCancel* Progress) 271 | { 272 | // if the Operation cannot be executed in a background thread, make sure we are in the game thread... 273 | bool bSkipOperation = false; 274 | if (UseOptions.Operation->GetEnableBackgroundExecution() == false) 275 | { 276 | if ( ! ensure(IsInGameThread()) ) 277 | { 278 | bSkipOperation = true; 279 | } 280 | } 281 | else 282 | { 283 | ensure(IsInGameThread() == false); // just a sanity check to make sure we are actually in a background thread... 284 | } 285 | 286 | if (bSkipOperation == false && Progress->Cancelled() == false) 287 | { 288 | UseOptions.Operation->OnRecomputeMesh(UseOptions.TempMesh, UseOptions.Settings); 289 | } 290 | 291 | bBlueprintExecuted = true; // indicate that we have completed work, to end busy wait in CalculateResult() 292 | } 293 | 294 | // Called on background thread to compute the mesh op result. 295 | // The input mesh is stored and returned via .ResultMesh member. 296 | // .ResultInfo member is used to indicate success/failure 297 | virtual void CalculateResult(FProgressCancel* Progress) override 298 | { 299 | ResultInfo = FGeometryResult(); 300 | 301 | // abort if we don't have valid inputs 302 | if (UseOptions.Operation == nullptr) 303 | { 304 | ResultInfo.SetSuccess(false , Progress); 305 | UseOptions.Executor->ReleaseTempMesh(UseOptions.TempMesh); 306 | return; 307 | } 308 | 309 | // can this ever happen? 310 | check(UseOptions.TempMesh != nullptr); 311 | 312 | UseOptions.TempMesh->SetMesh(MoveTemp(*ResultMesh)); 313 | 314 | if (UseOptions.Operation->GetEnableBackgroundExecution()) 315 | { 316 | ExecuteBlueprint(Progress); 317 | } 318 | else 319 | { 320 | // if we cannot execute on background, push to game thread and then wait until it has been executed 321 | UseOptions.Executor->QueueForMainThread( FBackgroundMeshProcessingExecutor::FPendingOperation{ this, Progress } ); 322 | while (bBlueprintExecuted == false) 323 | { 324 | FPlatformProcess::Sleep(0.01f); 325 | } 326 | } 327 | 328 | if (Progress->Cancelled() == false) 329 | { 330 | TUniquePtr EditedMesh = UseOptions.TempMesh->ExtractMesh(); 331 | *ResultMesh = MoveTemp(*EditedMesh); 332 | } 333 | 334 | UseOptions.Executor->ReleaseTempOperation(UseOptions.Operation); 335 | UseOptions.Executor->ReleaseTempMesh(UseOptions.TempMesh); 336 | 337 | ResultInfo.SetSuccess(true, Progress); 338 | } 339 | 340 | 341 | protected: 342 | FOptions UseOptions; 343 | }; 344 | 345 | 346 | } 347 | 348 | 349 | 350 | TUniquePtr UMeshProcessingBPTool::MakeNewOperator() 351 | { 352 | // spawn a new instance of the Operation BP type, if it is set 353 | UMeshProcessingBPToolOperation* OperationInstance = nullptr; 354 | if (Properties->Operation != nullptr) 355 | { 356 | UClass* ClassType = Properties->Operation; 357 | OperationInstance = NewObject((UObject*)GetTransientPackage(), ClassType); 358 | this->Executor->AddTempOperation(OperationInstance); 359 | } 360 | 361 | Local::FBPMeshProcessingOp::FOptions Options; 362 | Options.Operation = OperationInstance; 363 | Options.TempMesh = NewObject(this); // TODO: this could come from a pool, to avoid UObject garbage spew 364 | this->Executor->AddTempMesh(Options.TempMesh); 365 | Options.Settings = Properties->Parameters; 366 | Options.Executor = this->Executor; 367 | 368 | TUniquePtr MeshOp = MakeUnique(&GetInitialMesh(), Options); 369 | MeshOp->SetTransform( (FTransform3d)GetPreviewTransform() ); 370 | 371 | return MeshOp; 372 | } 373 | 374 | 375 | 376 | 377 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Public/SampleModelingModeExtensionCommands.h: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #pragma once 5 | 6 | #include "CoreMinimal.h" 7 | #include "Framework/Commands/Commands.h" 8 | 9 | class SAMPLEMODELINGMODEEXTENSION_API FSampleModelingModeExtensionCommands : public TCommands 10 | { 11 | public: 12 | FSampleModelingModeExtensionCommands(); 13 | 14 | TSharedPtr BeginMeshNoiseTool; 15 | TSharedPtr BeginMeshPlaneCutTool; 16 | TSharedPtr BeginActorClickedBPTool; 17 | TSharedPtr BeginMeshProcessingBPTool; 18 | 19 | /** 20 | * Initialize commands 21 | */ 22 | virtual void RegisterCommands() override; 23 | }; 24 | -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Public/SampleModelingModeExtensionModule.h: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #pragma once 5 | 6 | #include "CoreMinimal.h" 7 | #include "Modules/ModuleManager.h" 8 | #include "ModelingModeToolExtensions.h" 9 | 10 | class FSampleModelingModeExtensionModule : public IModuleInterface, public IModelingModeToolExtension 11 | { 12 | public: 13 | 14 | /** IModuleInterface implementation */ 15 | virtual void StartupModule() override; 16 | virtual void ShutdownModule() override; 17 | 18 | /** IModelingModeToolExtension implementation */ 19 | virtual FText GetExtensionName() override; 20 | virtual FText GetToolSectionName() override; 21 | virtual void GetExtensionTools(const FExtensionToolQueryInfo& QueryInfo, TArray& ToolsOut) override; 22 | }; 23 | -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Public/SampleModelingModeExtensionStyle.h: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #pragma once 5 | 6 | #include "CoreMinimal.h" 7 | #include "Styling/ISlateStyle.h" 8 | #include "Styling/SlateStyle.h" 9 | 10 | class SAMPLEMODELINGMODEEXTENSION_API FSampleModelingModeExtensionStyle 11 | { 12 | public: 13 | static void Initialize(); 14 | static void Shutdown(); 15 | 16 | static TSharedPtr Get(); 17 | static FName GetStyleSetName(); 18 | 19 | private: 20 | static FString InContent(const FString& RelativePath, const ANSICHAR* Extension); 21 | static TSharedPtr StyleSet; 22 | }; -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Public/Tools/ActorClickedBPTool.h: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #pragma once 5 | 6 | #include "CoreMinimal.h" 7 | #include "BaseTools/SingleClickTool.h" 8 | #include "GameFramework/Actor.h" 9 | #include "ActorClickedBPTool.generated.h" 10 | 11 | /** 12 | * UActorClickedBPToolOperation is intended to be used as a basis for Blueprints 13 | * that can process an Actor, which can then be configured as the BP operation in 14 | * a UActorClickedBPTool instance. 15 | */ 16 | UCLASS(BlueprintType, Blueprintable) 17 | class SAMPLEMODELINGMODEEXTENSION_API UActorClickedBPToolOperation : public UObject 18 | { 19 | GENERATED_BODY() 20 | public: 21 | UFUNCTION(BlueprintImplementableEvent, CallInEditor, Category = "Events") 22 | void OnApplyActionToActor(AActor* TargetActor); 23 | }; 24 | 25 | 26 | 27 | /** 28 | * Tool Settings for a UActorClickedBPTool 29 | */ 30 | UCLASS() 31 | class SAMPLEMODELINGMODEEXTENSION_API UActorClickedBPToolProperties : public UInteractiveToolPropertySet 32 | { 33 | GENERATED_BODY() 34 | public: 35 | /** Blueprint to execute on Actor click */ 36 | UPROPERTY(EditAnywhere, Category = Options) 37 | TSubclassOf Operation; 38 | }; 39 | 40 | 41 | /** 42 | * UActorClickedBPTool is a Tool that executes an arbitrary Blueprint when an Actor in the active 43 | * level is clicked with the cursor. The Blueprint must be a subclass of UActorClickedBPToolOperation, 44 | * and is configured via the UActorClickedBPToolProperties. 45 | */ 46 | UCLASS() 47 | class SAMPLEMODELINGMODEEXTENSION_API UActorClickedBPTool : public USingleClickTool 48 | { 49 | GENERATED_BODY() 50 | 51 | public: 52 | virtual void Setup() override; 53 | virtual void Shutdown(EToolShutdownType ShutdownType) override; 54 | 55 | virtual FInputRayHit IsHitByClick(const FInputDeviceRay& ClickPos) override; 56 | 57 | virtual void OnClicked(const FInputDeviceRay& ClickPos) override; 58 | 59 | public: 60 | UPROPERTY() 61 | TObjectPtr PropertySet; 62 | }; 63 | 64 | 65 | 66 | UCLASS() 67 | class SAMPLEMODELINGMODEEXTENSION_API UActorClickedBPToolBuilder : public UInteractiveToolBuilder 68 | { 69 | GENERATED_BODY() 70 | public: 71 | virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override { return true; } 72 | virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override; 73 | }; -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Public/Tools/MeshNoiseTool.h: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #pragma once 5 | 6 | #include "CoreMinimal.h" 7 | #include "BaseTools/BaseMeshProcessingTool.h" 8 | #include "MeshNoiseTool.generated.h" 9 | 10 | 11 | UENUM() 12 | enum class EMeshNoiseToolNoiseType : uint8 13 | { 14 | Random, 15 | Perlin 16 | }; 17 | 18 | 19 | UCLASS() 20 | class SAMPLEMODELINGMODEEXTENSION_API UMeshNoiseProperties : public UInteractiveToolPropertySet 21 | { 22 | GENERATED_BODY() 23 | public: 24 | 25 | /** Number of edge subdivisions */ 26 | UPROPERTY(EditAnywhere, Category = Subdivisions, meta = (UIMin = "0", UIMax = "5", ClampMin = "0", ClampMax = "10")) 27 | int Subdivisions = 0; 28 | 29 | UPROPERTY(EditAnywhere, Category = Noise) 30 | EMeshNoiseToolNoiseType NoiseType = EMeshNoiseToolNoiseType::Perlin; 31 | 32 | /** Scale of the noise */ 33 | UPROPERTY(EditAnywhere, Category = Noise, meta = (UIMin = "-10.0", UIMax = "10.0", ClampMin = "-1000.0", ClampMax = "100.0")) 34 | float Scale = 5.0f; 35 | 36 | /** Frequency of the noise */ 37 | UPROPERTY(EditAnywhere, Category = Noise, meta = (UIMin = "0", UIMax = "5.0", EditCondition = "NoiseType == EMeshNoiseToolNoiseType::Perlin")) 38 | float Frequency = 1.0f; 39 | 40 | /** Random Seed */ 41 | UPROPERTY(EditAnywhere, Category = Noise, meta = (UIMin = "0", ClampMin = "0", EditCondition = "NoiseType == EMeshNoiseToolNoiseType::Random")) 42 | int Seed = 10; 43 | 44 | }; 45 | 46 | 47 | 48 | /** 49 | * UMeshNoiseTool applies PN Tessellation and Perlin or Random noise to an input Mesh 50 | */ 51 | UCLASS() 52 | class SAMPLEMODELINGMODEEXTENSION_API UMeshNoiseTool : public UBaseMeshProcessingTool 53 | { 54 | GENERATED_BODY() 55 | 56 | public: 57 | UMeshNoiseTool(); 58 | 59 | protected: 60 | // UBaseMeshProcessingTool API implementation 61 | 62 | virtual void InitializeProperties() override; 63 | virtual void OnShutdown(EToolShutdownType ShutdownType) override; 64 | 65 | virtual TUniquePtr MakeNewOperator() override; 66 | 67 | virtual bool RequiresInitialVtxNormals() const { return true; } 68 | virtual bool HasMeshTopologyChanged() const override; 69 | 70 | virtual FText GetToolMessageString() const override; 71 | virtual FText GetAcceptTransactionName() const override; 72 | 73 | // disable scaling to unit dimensions, this is a feature of UBaseMeshProcessingTool that is enabled by default 74 | virtual bool RequiresScaleNormalization() const { return false; } 75 | 76 | protected: 77 | // settings for this Tool that will be exposed in Modeling Mode details panel 78 | UPROPERTY() 79 | TObjectPtr NoiseProperties = nullptr; 80 | }; 81 | 82 | 83 | 84 | 85 | UCLASS() 86 | class SAMPLEMODELINGMODEEXTENSION_API UMeshNoiseToolBuilder : public UBaseMeshProcessingToolBuilder 87 | { 88 | GENERATED_BODY() 89 | public: 90 | virtual UBaseMeshProcessingTool* MakeNewToolInstance(UObject* Outer) const 91 | { 92 | return NewObject(Outer); 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Public/Tools/MeshPlaneCutTool.h: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #pragma once 5 | 6 | #include "CoreMinimal.h" 7 | #include "BaseTools/BaseMeshProcessingTool.h" 8 | #include "MeshPlaneCutTool.generated.h" 9 | 10 | class UCombinedTransformGizmo; 11 | class UTransformProxy; 12 | 13 | 14 | UCLASS() 15 | class SAMPLEMODELINGMODEEXTENSION_API UMeshPlaneCutProperties : public UInteractiveToolPropertySet 16 | { 17 | GENERATED_BODY() 18 | public: 19 | UPROPERTY(EditAnywhere, Category = Plane, meta = (TransientToolProperty)) 20 | bool bFillHole = true; 21 | 22 | UPROPERTY(EditAnywhere, Category = Plane, meta = (TransientToolProperty)) 23 | FVector Position; 24 | 25 | UPROPERTY(EditAnywhere, Category = Plane, meta = (TransientToolProperty)) 26 | FRotator Rotation; 27 | }; 28 | 29 | 30 | 31 | /** 32 | * UMeshPlaneCutTool apples a Plane Cut to a mesh, deleting one side of the cut and 33 | * filling any holes created by the cut. A 3D Gizmo used to provide a user interface for 34 | * positioning the plane. 35 | */ 36 | UCLASS() 37 | class SAMPLEMODELINGMODEEXTENSION_API UMeshPlaneCutTool : public UBaseMeshProcessingTool 38 | { 39 | GENERATED_BODY() 40 | 41 | public: 42 | UMeshPlaneCutTool(); 43 | 44 | protected: 45 | // UBaseMeshProcessingTool API implementation 46 | 47 | virtual void InitializeProperties() override; 48 | virtual void OnShutdown(EToolShutdownType ShutdownType) override; 49 | 50 | virtual TUniquePtr MakeNewOperator() override; 51 | 52 | // Do not need vertex normals for this UBaseMeshProcessingTool 53 | virtual bool RequiresInitialVtxNormals() const { return false; } 54 | // If we change the mesh this must return true. Depends on what the Tool does... 55 | virtual bool HasMeshTopologyChanged() const override; 56 | 57 | virtual FText GetToolMessageString() const override; 58 | virtual FText GetAcceptTransactionName() const override; 59 | 60 | // disable scaling to unit dimensions, this is a feature of UBaseMeshProcessingTool that is enabled by default 61 | virtual bool RequiresScaleNormalization() const { return false; } 62 | 63 | protected: 64 | // settings for this Tool that will be exposed in Modeling Mode details panel 65 | UPROPERTY() 66 | TObjectPtr Properties = nullptr; 67 | 68 | protected: 69 | // Transform of the 3D plane is tracked independently of the exposed Properties, this 70 | // is necessary to allow the Gizmo and PropertySet to both update the plane 71 | FTransform3d PlaneTransform; 72 | 73 | 74 | protected: 75 | // 3D Transform Gizmo support, used to interactively position the 3D plane 76 | 77 | UPROPERTY() 78 | TObjectPtr PlaneTransformGizmo; 79 | 80 | UPROPERTY() 81 | TObjectPtr PlaneTransformProxy; 82 | 83 | void InitializeTransformGizmo(); 84 | void OnGizmoTransformChanged(UTransformProxy* Proxy, FTransform Transform); 85 | 86 | }; 87 | 88 | 89 | 90 | 91 | UCLASS() 92 | class SAMPLEMODELINGMODEEXTENSION_API UMeshPlaneCutToolBuilder : public UBaseMeshProcessingToolBuilder 93 | { 94 | GENERATED_BODY() 95 | public: 96 | virtual UBaseMeshProcessingTool* MakeNewToolInstance(UObject* Outer) const 97 | { 98 | return NewObject(Outer); 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Public/Tools/MeshProcessingBPTool.h: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | #pragma once 5 | 6 | #include "CoreMinimal.h" 7 | #include "BaseTools/BaseMeshProcessingTool.h" 8 | #include "MeshProcessingBPTool.generated.h" 9 | 10 | class FBackgroundMeshProcessingExecutor; 11 | 12 | /** 13 | * Generic set of parameters for a UMeshProcessingBPToolOperation. 14 | * Add additional parameters here to expose in the Tool Settings. 15 | */ 16 | USTRUCT(BlueprintType) 17 | struct SAMPLEMODELINGMODEEXTENSION_API FMeshProcessingBPToolParameters 18 | { 19 | GENERATED_BODY() 20 | public: 21 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Parameters) 22 | float FloatParam1 = 1.0; 23 | 24 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Parameters) 25 | float FloatParam2 = 1.0; 26 | 27 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Parameters) 28 | int IntParam1 = 0; 29 | 30 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Parameters) 31 | int IntParam2 = 0; 32 | }; 33 | 34 | 35 | 36 | /** 37 | * UMeshProcessingBPToolOperation is intended to be used as a basis for Blueprints 38 | * that can process a mesh, which can then be configured as the BP operation in 39 | * a UMeshProcessingBPTool instance. 40 | */ 41 | UCLASS(Blueprintable, BlueprintType) 42 | class SAMPLEMODELINGMODEEXTENSION_API UMeshProcessingBPToolOperation : public UObject 43 | { 44 | GENERATED_BODY() 45 | public: 46 | /** 47 | * Implement this function to apply a mesh processing operation to the TargetMesh 48 | */ 49 | UFUNCTION(BlueprintImplementableEvent, Category = "Events") 50 | void OnRecomputeMesh(UDynamicMesh* TargetMesh, FMeshProcessingBPToolParameters Parameters); 51 | 52 | /** 53 | * Override GetEnableBackgroundExecution in BP to indicate that it is acceptable 54 | * to execute OnRecomputeMesh from a background thread. This is only the case if 55 | * no calls to external UObjects or Subsystems are used, ie only basic Geometry Script 56 | * operations are used with the TargetMesh. 57 | * 58 | * (It's probably not a good idea to use this, it may stop working in the future!) 59 | */ 60 | UFUNCTION(BlueprintNativeEvent, Category = "Events") 61 | bool GetEnableBackgroundExecution(); 62 | bool GetEnableBackgroundExecution_Implementation(); 63 | }; 64 | 65 | 66 | 67 | /** 68 | * Tool Settings for a UMeshProcessingBPTool 69 | */ 70 | UCLASS() 71 | class SAMPLEMODELINGMODEEXTENSION_API UMeshProcessingBPToolProperties : public UInteractiveToolPropertySet 72 | { 73 | GENERATED_BODY() 74 | public: 75 | /** Blueprint to execute */ 76 | UPROPERTY(EditAnywhere, Category = Operation) 77 | TSubclassOf Operation; 78 | 79 | UPROPERTY(EditAnywhere, Category = Settings) 80 | FMeshProcessingBPToolParameters Parameters; 81 | }; 82 | 83 | 84 | 85 | /** 86 | * UMeshProcessingBPTool is a Mesh Processing Tool that executes an arbitrary Blueprint to do 87 | * the mesh processing operation. The Blueprint must be a subclass of UMeshProcessingBPToolOperation, 88 | * and is configured via the UMeshProcessingBPToolProperties 89 | */ 90 | UCLASS() 91 | class SAMPLEMODELINGMODEEXTENSION_API UMeshProcessingBPTool : public UBaseMeshProcessingTool 92 | { 93 | GENERATED_BODY() 94 | 95 | public: 96 | UMeshProcessingBPTool(); 97 | 98 | protected: 99 | // UBaseMeshProcessingTool API implementation 100 | 101 | virtual void InitializeProperties() override; 102 | virtual void OnShutdown(EToolShutdownType ShutdownType) override; 103 | virtual void OnTick(float DeltaTime) override; 104 | 105 | virtual TUniquePtr MakeNewOperator() override; 106 | 107 | virtual bool RequiresInitialVtxNormals() const { return false; } 108 | virtual bool HasMeshTopologyChanged() const override; 109 | 110 | virtual FText GetToolMessageString() const override; 111 | virtual FText GetAcceptTransactionName() const override; 112 | 113 | // disable scaling to unit dimensions, this is a feature of UBaseMeshProcessingTool that is enabled by default 114 | virtual bool RequiresScaleNormalization() const { return false; } 115 | 116 | protected: 117 | // settings for this Tool that will be exposed in Modeling Mode details panel 118 | UPROPERTY() 119 | TObjectPtr Properties = nullptr; 120 | 121 | virtual void OnPropertyModified(UObject* PropertySet, FProperty* Property) override; 122 | 123 | // A helper class (defined in cpp) that is used to force execution of the Blueprint operation on the game thread 124 | TSharedPtr Executor; 125 | }; 126 | 127 | 128 | 129 | 130 | UCLASS() 131 | class SAMPLEMODELINGMODEEXTENSION_API UMeshProcessingBPToolBuilder : public UBaseMeshProcessingToolBuilder 132 | { 133 | GENERATED_BODY() 134 | public: 135 | virtual UBaseMeshProcessingTool* MakeNewToolInstance(UObject* Outer) const 136 | { 137 | return NewObject(Outer); 138 | } 139 | }; 140 | -------------------------------------------------------------------------------- /Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/SampleModelingModeExtension.Build.cs: -------------------------------------------------------------------------------- 1 | // Distributed under the Boost Software License, Version 1.0. 2 | // https://www.boost.org/LICENSE_1_0.txt 3 | 4 | using UnrealBuildTool; 5 | 6 | public class SampleModelingModeExtension : ModuleRules 7 | { 8 | public SampleModelingModeExtension(ReadOnlyTargetRules Target) : base(Target) 9 | { 10 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 11 | 12 | PublicIncludePaths.AddRange( 13 | new string[] { 14 | // ... add public include paths required here ... 15 | } 16 | ); 17 | 18 | 19 | PrivateIncludePaths.AddRange( 20 | new string[] { 21 | // ... add other private include paths required here ... 22 | } 23 | ); 24 | 25 | 26 | PublicDependencyModuleNames.AddRange( 27 | new string[] 28 | { 29 | "Core", 30 | "InteractiveToolsFramework", 31 | "GeometryCore", 32 | "GeometryFramework", 33 | "DynamicMesh", 34 | "GeometryAlgorithms", 35 | "ModelingComponents", 36 | "ModelingToolsEditorMode" 37 | // ... add other public dependencies that you statically link with here ... 38 | } 39 | ); 40 | 41 | 42 | PrivateDependencyModuleNames.AddRange( 43 | new string[] 44 | { 45 | "CoreUObject", 46 | "Engine", 47 | 48 | "Slate", 49 | "SlateCore", 50 | 51 | "ApplicationCore", 52 | "UnrealEd", 53 | "EditorFramework", 54 | "ContentBrowser", 55 | "LevelEditor", 56 | "StatusBar", 57 | "EditorStyle", 58 | "Projects" 59 | // ... add private dependencies that you statically link with here ... 60 | } 61 | ); 62 | 63 | 64 | DynamicallyLoadedModuleNames.AddRange( 65 | new string[] 66 | { 67 | // ... add any modules that your module loads dynamically here ... 68 | } 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UE5ModelingModeExtensionDemo 2 | Sample UE5 project/plugin that adds several Tools to Modeling Mode via Extension system 3 | -------------------------------------------------------------------------------- /Source/UE5ToolPluginDemo.Target.cs: -------------------------------------------------------------------------------- 1 | using UnrealBuildTool; 2 | using System.Collections.Generic; 3 | 4 | public class UE5ToolPluginDemoTarget : TargetRules 5 | { 6 | public UE5ToolPluginDemoTarget( TargetInfo Target) : base(Target) 7 | { 8 | Type = TargetType.Game; 9 | DefaultBuildSettings = BuildSettingsVersion.V2; 10 | ExtraModuleNames.AddRange( new string[] { "UE5ToolPluginDemo" } ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Source/UE5ToolPluginDemo/UE5ToolPluginDemo.Build.cs: -------------------------------------------------------------------------------- 1 | using UnrealBuildTool; 2 | 3 | public class UE5ToolPluginDemo : ModuleRules 4 | { 5 | public UE5ToolPluginDemo(ReadOnlyTargetRules Target) : base(Target) 6 | { 7 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 8 | 9 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); 10 | 11 | PrivateDependencyModuleNames.AddRange(new string[] { }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Source/UE5ToolPluginDemo/UE5ToolPluginDemo.cpp: -------------------------------------------------------------------------------- 1 | #include "UE5ToolPluginDemo.h" 2 | #include "Modules/ModuleManager.h" 3 | 4 | IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, UE5ToolPluginDemo, "UE5ToolPluginDemo" ); 5 | -------------------------------------------------------------------------------- /Source/UE5ToolPluginDemo/UE5ToolPluginDemo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | 5 | -------------------------------------------------------------------------------- /Source/UE5ToolPluginDemo/UE5ToolPluginDemoGameModeBase.cpp: -------------------------------------------------------------------------------- 1 | #include "UE5ToolPluginDemoGameModeBase.h" 2 | 3 | -------------------------------------------------------------------------------- /Source/UE5ToolPluginDemo/UE5ToolPluginDemoGameModeBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "GameFramework/GameModeBase.h" 5 | #include "UE5ToolPluginDemoGameModeBase.generated.h" 6 | 7 | /** 8 | * 9 | */ 10 | UCLASS() 11 | class UE5TOOLPLUGINDEMO_API AUE5ToolPluginDemoGameModeBase : public AGameModeBase 12 | { 13 | GENERATED_BODY() 14 | 15 | }; 16 | -------------------------------------------------------------------------------- /Source/UE5ToolPluginDemoEditor.Target.cs: -------------------------------------------------------------------------------- 1 | using UnrealBuildTool; 2 | using System.Collections.Generic; 3 | 4 | public class UE5ToolPluginDemoEditorTarget : TargetRules 5 | { 6 | public UE5ToolPluginDemoEditorTarget( TargetInfo Target) : base(Target) 7 | { 8 | Type = TargetType.Editor; 9 | DefaultBuildSettings = BuildSettingsVersion.V2; 10 | ExtraModuleNames.AddRange( new string[] { "UE5ToolPluginDemo" } ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /UE5ToolPluginDemo.uproject: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "EngineAssociation": "5.0", 4 | "Category": "", 5 | "Description": "", 6 | "Modules": [ 7 | { 8 | "Name": "UE5ToolPluginDemo", 9 | "Type": "Runtime", 10 | "LoadingPhase": "Default" 11 | } 12 | ], 13 | "Plugins": [ 14 | { 15 | "Name": "ModelingToolsEditorMode", 16 | "Enabled": true, 17 | "TargetAllowList": [ 18 | "Editor" 19 | ] 20 | }, 21 | { 22 | "Name": "Bridge", 23 | "Enabled": true, 24 | "SupportedTargetPlatforms": [ 25 | "Win64", 26 | "Mac", 27 | "Linux" 28 | ] 29 | }, 30 | { 31 | "Name": "GeometryScripting", 32 | "Enabled": true 33 | } 34 | ] 35 | } --------------------------------------------------------------------------------