├── Setup 3D Package Scripts.bat ├── Source └── ProductivityPlugin │ ├── Private │ ├── ProductivityTypes.cpp │ ├── ProductivityClasses.cpp │ ├── ProductivityPluginEditorSettings.cpp │ ├── InstancedMeshWrapper.cpp │ ├── ProductivityPluginModulePCH.h │ ├── ProductivityPluginStyle.cpp │ ├── ProductivityPluginCommands.cpp │ └── ProductivityPluginModule.cpp │ ├── Classes │ ├── InstancedMeshWrapper.h │ ├── ProductivityPluginStyle.h │ ├── ProductivityPluginEditorSettings.h │ ├── ProductivitySettings.h │ ├── ProductivityPluginCommands.h │ ├── ProductivityTypes.h │ └── ProductivityPluginModule.h │ └── ProductivityPlugin.Build.cs ├── Maya ├── BatchPlacer.pyc ├── BatchPlacerShelfButton.py └── BatchPlacer.py ├── Resources ├── Icon128.png ├── ButtonIcon.psd ├── DefaultIcon128.png ├── InstancedToStatic.png ├── StaticToInstanced.png ├── basicpluginSource.PNG ├── blankpluginSource.PNG ├── DarkGrayBackground.png ├── TabwindowTestPlugin.PNG ├── testAutoPluginToolbar.PNG ├── windowMenuTestAutoPlugin.PNG └── pluginEditorTestAutoPlugin.PNG ├── .gitignore ├── ProductivityPlugin.uplugin ├── LICENSE ├── Max ├── BatchPlacer.py └── LoadBatchPlacer.ms └── README.md /Setup 3D Package Scripts.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setx UE4_PRODUCTIVITY "%CD%" -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Private/ProductivityTypes.cpp: -------------------------------------------------------------------------------- 1 | #include "ProductivityPluginModulePCH.h" -------------------------------------------------------------------------------- /Maya/BatchPlacer.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Maya/BatchPlacer.pyc -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /Resources/ButtonIcon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Resources/ButtonIcon.psd -------------------------------------------------------------------------------- /Resources/DefaultIcon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Resources/DefaultIcon128.png -------------------------------------------------------------------------------- /Resources/InstancedToStatic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Resources/InstancedToStatic.png -------------------------------------------------------------------------------- /Resources/StaticToInstanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Resources/StaticToInstanced.png -------------------------------------------------------------------------------- /Resources/basicpluginSource.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Resources/basicpluginSource.PNG -------------------------------------------------------------------------------- /Resources/blankpluginSource.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Resources/blankpluginSource.PNG -------------------------------------------------------------------------------- /Resources/DarkGrayBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Resources/DarkGrayBackground.png -------------------------------------------------------------------------------- /Resources/TabwindowTestPlugin.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Resources/TabwindowTestPlugin.PNG -------------------------------------------------------------------------------- /Resources/testAutoPluginToolbar.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Resources/testAutoPluginToolbar.PNG -------------------------------------------------------------------------------- /Resources/windowMenuTestAutoPlugin.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Resources/windowMenuTestAutoPlugin.PNG -------------------------------------------------------------------------------- /Resources/pluginEditorTestAutoPlugin.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allar/ProductivityPlugin/HEAD/Resources/pluginEditorTestAutoPlugin.PNG -------------------------------------------------------------------------------- /Maya/BatchPlacerShelfButton.py: -------------------------------------------------------------------------------- 1 | import sys 2 | try: 3 | BatchPlacer.batchPlaceSelected() 4 | except: 5 | Dir = os.environ['UE4_PRODUCTIVITY'].replace("\\","/") + "/Maya" 6 | if Dir not in sys.path: 7 | sys.path.append(Dir) 8 | try: reload(BatchPlacer) 9 | except: import BatchPlacer 10 | BatchPlacer.main() -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Private/ProductivityClasses.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "ProductivityPluginModulePCH.h" 4 | 5 | 6 | UProductivitySettings::UProductivitySettings(const FObjectInitializer& ObjectInitializer) 7 | : Super(ObjectInitializer) 8 | , EnableProductivityServer(true) 9 | { } 10 | -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Private/ProductivityPluginEditorSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "ProductivityPluginModulePCH.h" 2 | #include "ProductivityPluginEditorSettings.h" 3 | 4 | UProductivityPluginEditorSettings::UProductivityPluginEditorSettings(const FObjectInitializer& ObjectInitializer) 5 | : Super(ObjectInitializer) 6 | { 7 | GroupStaticToInstancedResults = false; 8 | } -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Private/InstancedMeshWrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "ProductivityPluginModulePCH.h" 2 | #include "InstancedMeshWrapper.h" 3 | 4 | AInstancedMeshWrapper::AInstancedMeshWrapper(const class FObjectInitializer& PCIP) : Super(PCIP) 5 | { 6 | InstancedMeshes = PCIP.CreateDefaultSubobject(this, TEXT("InstancedMeshes_0")); 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Classes/InstancedMeshWrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "GameFramework/Actor.h" 4 | #include "InstancedMeshWrapper.generated.h" 5 | 6 | /** 7 | * 8 | */ 9 | UCLASS() 10 | class PRODUCTIVITYPLUGIN_API AInstancedMeshWrapper : public AActor 11 | { 12 | GENERATED_UCLASS_BODY() 13 | 14 | public: 15 | 16 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Meshes") 17 | UInstancedStaticMeshComponent* InstancedMeshes; 18 | }; 19 | -------------------------------------------------------------------------------- /ProductivityPlugin.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion" : 3, 3 | 4 | "FriendlyName" : "Productivity Plugin", 5 | "Version" : 1, 6 | "VersionName" : "0.1", 7 | "CreatedBy" : "Michael Allar", 8 | "CreatedByURL" : "http://michaelallar.com", 9 | "EngineVersion" : "4.6.1", 10 | "Description" : "Some tools that don't come with UE4", 11 | "Category" : "Tools", 12 | "EnabledByDefault" : true, 13 | 14 | "Modules" : 15 | [ 16 | { 17 | "Name" : "ProductivityPlugin", 18 | "Type" : "Runtime" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Classes/ProductivityPluginStyle.h: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | #pragma once 3 | 4 | #include "SlateBasics.h" 5 | 6 | /** */ 7 | class FProductivityPluginStyle 8 | { 9 | public: 10 | 11 | static void Initialize(); 12 | 13 | static void Shutdown(); 14 | 15 | /** reloads textures used by slate renderer */ 16 | static void ReloadTextures(); 17 | 18 | /** @return The Slate style set for the Shooter game */ 19 | static const ISlateStyle& Get(); 20 | 21 | static FName GetStyleSetName(); 22 | 23 | private: 24 | 25 | static TSharedRef< class FSlateStyleSet > Create(); 26 | 27 | private: 28 | 29 | static TSharedPtr< class FSlateStyleSet > StyleInstance; 30 | }; -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Classes/ProductivityPluginEditorSettings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ProductivityPluginEditorSettings.generated.h" 4 | 5 | /** 6 | * Implements the Editor's play settings. 7 | */ 8 | UCLASS(config = EditorUserSettings) 9 | class PRODUCTIVITYPLUGIN_API UProductivityPluginEditorSettings : public UObject 10 | { 11 | GENERATED_UCLASS_BODY() 12 | 13 | public: 14 | 15 | UPROPERTY(config, EditAnywhere, Category = ProductivityOptions) 16 | bool GroupStaticToInstancedResults; 17 | 18 | void SetGroupStaticToInstancedResults(const bool InGroupStaticToInstancedResults) { GroupStaticToInstancedResults = InGroupStaticToInstancedResults; SaveConfig(); } 19 | bool GetGroupStaticToInstancedResults() const { return GroupStaticToInstancedResults; } 20 | 21 | }; -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Classes/ProductivitySettings.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "ProductivityTypes.h" 6 | #include "ProductivitySettings.generated.h" 7 | 8 | 9 | /** 10 | * Implements the settings for the Slate Remote plug-in. 11 | */ 12 | UCLASS(config=Engine) 13 | class UProductivitySettings 14 | : public UObject 15 | { 16 | GENERATED_UCLASS_BODY() 17 | 18 | public: 19 | 20 | /** Whether the Productivity Server is enabled. */ 21 | UPROPERTY(config, EditAnywhere, Category = Productivity) 22 | bool EnableProductivityServer; 23 | 24 | /** Settings for batch placing. */ 25 | UPROPERTY(config, EditAnywhere, Category = Productivity) 26 | TArray BatchPlaceSettings; 27 | }; 28 | -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Classes/ProductivityPluginCommands.h: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | #pragma once 3 | 4 | #include "SlateBasics.h" 5 | #include "ProductivityPluginStyle.h" 6 | 7 | class FProductivityPluginCommands : public TCommands 8 | { 9 | public: 10 | 11 | FProductivityPluginCommands(); 12 | 13 | static void BindGlobalStaticToInstancedActions(); 14 | static TSharedRef< SWidget > GenerateStaticToInstancedMenuContent(TSharedRef InCommandList); 15 | 16 | #if WITH_EDITOR 17 | virtual void RegisterCommands() override; 18 | #endif 19 | 20 | TSharedPtr< FUICommandInfo > StaticToInstanced; 21 | TSharedPtr< FUICommandInfo > StaticToInstancedIsResultGrouped; 22 | 23 | PRODUCTIVITYPLUGIN_API static TSharedPtr GlobalStaticToInstancedActions; 24 | }; 25 | 26 | class PRODUCTIVITYPLUGIN_API FProductivityPluginCommandCallbacks 27 | { 28 | public: 29 | static void OnToggleStaticToInstancedResultGrouped(); 30 | static bool OnToggleStaticToInstancedResultGroupedEnabled(); 31 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2016 Google, Inc. http://angularjs.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Max/BatchPlacer.py: -------------------------------------------------------------------------------- 1 | import math 2 | import socket 3 | import time 4 | import struct 5 | import unicodedata 6 | 7 | global s 8 | s = None 9 | 10 | def packText(text): 11 | return struct.pack('I'+str(len(text)+1)+"s", len(text)+1, text) 12 | 13 | def SendUnrealBatchPlaceData(objName, locx, locy, locz, rotx, roty, rotz, scalex, scaley, scalez): 14 | global s 15 | if (s is None): 16 | batchPlaceOpenSocket() 17 | 18 | if (s is not None): 19 | output = "\x01" + packText(objName) + packText(locx) + packText(locy) + packText(locz) + packText(rotx) + packText(roty) + packText(rotz) + packText(scalex) + packText(scaley) + packText(scalez) 20 | padlen = 1024-len(output) 21 | output += "\x00"*padlen 22 | s.sendall(output) 23 | print ('Sent ' + objName) 24 | 25 | def batchPlaceOpenSocket(): 26 | global s 27 | if (s is not None): 28 | batchPlaceCloseSocket() 29 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 30 | try: 31 | s.connect(('127.0.0.1',51234)) 32 | except Exception, e: 33 | print "Could not establish connection to UE4." 34 | s.close() 35 | s = None 36 | 37 | def batchPlaceCloseSocket(): 38 | global s 39 | if (s != None): 40 | s.shutdown(2) 41 | s.close() 42 | s = None -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Private/ProductivityPluginModulePCH.h: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | #pragma once 3 | 4 | #include "Core.h" 5 | #include "Engine.h" 6 | #include "Networking.h" 7 | #include "SlateBasics.h" 8 | #include "SlateCore.h" 9 | #include "Sockets.h" 10 | #include "SocketSubsystem.h" 11 | 12 | #include "ProductivityTypes.h" 13 | #include "ProductivitySettings.h" 14 | #include "ProductivityPluginModule.h" 15 | #include "InstancedMeshWrapper.h" 16 | 17 | // You should place include statements to your module's private header files here. You only need to 18 | // add includes for headers that are used in most of your module's source files though. 19 | 20 | DECLARE_LOG_CATEGORY_EXTERN(LogProductivityPlugin, VeryVerbose, All); 21 | 22 | /* Private constants 23 | *****************************************************************************/ 24 | 25 | /** 26 | * Defines the default IP endpoint for the Slate Remote server running in the Editor. 27 | */ 28 | #define PRODUCTIVITY_SERVER_DEFAULT_EDITOR_ENDPOINT FIPv4Endpoint(FIPv4Address(127, 0, 0, 1), 51234) 29 | 30 | /** 31 | * Defines the protocol version of the UDP message transport. 32 | */ 33 | #define PRODUCTIVITY_SERVER_PROTOCOL_VERSION 1 34 | -------------------------------------------------------------------------------- /Max/LoadBatchPlacer.ms: -------------------------------------------------------------------------------- 1 | fn getProductivityEnvironmentPaths = 2 | ( 3 | dosCommand "echo %UE4_PRODUCTIVITY% >env.txt" 4 | in_text = openfile "env.txt" 5 | str = readLine in_text 6 | close in_text 7 | deleteFile "env.txt" 8 | trimRight str 9 | ) 10 | 11 | ue4_path = getProductivityEnvironmentPaths() 12 | batchPlacerPython = ue4_path + "\\Max\\" 13 | 14 | r = batchPlacerPython.count 15 | for i = 1 to r do 16 | ( 17 | if batchPlacerPython[i] == "\\" then 18 | ( 19 | batchPlacerPython = replace batchPlacerPython i 1 "/" 20 | ) 21 | ) 22 | 23 | python.execute("sys.path.append('" + batchPlacerPython + "')") 24 | python.execute("import BatchPlacer") 25 | 26 | fn batchPlaceSelected = 27 | ( 28 | for s in selection do 29 | ( 30 | locx = s.pos.x as string 31 | locy = s.pos.y as string 32 | locz = s.pos.z as string 33 | rotx = s.rotation.x_rotation as string 34 | roty = s.rotation.y_rotation as string 35 | rotz = s.rotation.z_rotation as string 36 | scalex = s.scale.x as string 37 | scaley = s.scale.y as string 38 | scalez = s.scale.z as string 39 | 40 | args = "'" + s.name + "','"+ locx + "','" + locy + "','" + locz + "','" + rotx + "','" + roty + "','" + rotz + "','" + scalex + "','" + scaley + "','" + scalez + "'" 41 | cmd = "BatchPlacer.SendUnrealBatchPlaceData(" + args + ")" 42 | 43 | python.execute cmd 44 | ) 45 | ) 46 | 47 | macroScript Batch_Place_Selected category:"Productivity" tooltip:"Batch place selected objects in UE4" 48 | icon:#("standard", 1) -- use first icon in standard 49 | ( 50 | batchPlaceSelected() 51 | ) -------------------------------------------------------------------------------- /Source/ProductivityPlugin/ProductivityPlugin.Build.cs: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | 3 | using UnrealBuildTool; 4 | 5 | public class ProductivityPlugin : ModuleRules 6 | { 7 | public ProductivityPlugin(TargetInfo Target) 8 | { 9 | 10 | PublicIncludePaths.AddRange( 11 | new string[] { 12 | "ProductivityPlugin/Classes" 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | "ProductivityPlugin/Private", 21 | // ... add other private include paths required here ... 22 | } 23 | ); 24 | 25 | 26 | PublicDependencyModuleNames.AddRange( 27 | new string[] 28 | { 29 | "Core", 30 | "CoreUObject", 31 | "Engine", 32 | "Slate", 33 | "SlateCore", 34 | "Networking", 35 | // ... add other public dependencies that you statically link with here ... 36 | } 37 | ); 38 | 39 | PrivateDependencyModuleNames.AddRange( 40 | new string[] 41 | { 42 | "Sockets", 43 | } 44 | ); 45 | 46 | 47 | if (UEBuildConfiguration.bBuildEditor) 48 | { 49 | PrivateDependencyModuleNames.AddRange( 50 | new string[] 51 | { 52 | "UnrealEd", 53 | "LevelEditor", 54 | 55 | // ... add private dependencies that you statically link with here ... 56 | } 57 | ); 58 | } 59 | 60 | 61 | 62 | 63 | DynamicallyLoadedModuleNames.AddRange( 64 | new string[] 65 | { 66 | // ... add any modules that your module loads dynamically here ... 67 | } 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Classes/ProductivityTypes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ProductivityTypes.generated.h" 4 | 5 | USTRUCT(BlueprintType) 6 | struct FMeshInfo 7 | { 8 | GENERATED_USTRUCT_BODY() 9 | 10 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Rendering) 11 | UStaticMesh* StaticMesh; 12 | 13 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Rendering) 14 | TArray Materials; 15 | 16 | bool operator==(const FMeshInfo& rightInfo) const 17 | { 18 | return StaticMesh == rightInfo.StaticMesh && Materials == rightInfo.Materials; 19 | } 20 | }; 21 | 22 | USTRUCT(BlueprintType) 23 | struct FBatchPlaceMeshInfo 24 | { 25 | GENERATED_USTRUCT_BODY() 26 | 27 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = BatchPlacing) 28 | FString ImportNameSubstring; 29 | 30 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = BatchPlacing) 31 | FMeshInfo MeshInfo; 32 | }; 33 | 34 | enum class EProductivityMessageType 35 | { 36 | ADD_STATICMESH = 1, 37 | }; 38 | 39 | struct FAddStaticMeshPayload 40 | { 41 | FString OriginalSceneName; 42 | FString LocationX; 43 | FString LocationY; 44 | FString LocationZ; 45 | FString RotationX; 46 | FString RotationY; 47 | FString RotationZ; 48 | FString ScaleX; 49 | FString ScaleY; 50 | FString ScaleZ; 51 | 52 | 53 | FORCEINLINE friend FArchive& operator<<(FArchive& Ar, FAddStaticMeshPayload& Payload) 54 | { 55 | return Ar << Payload.OriginalSceneName << Payload.LocationX << Payload.LocationY << Payload.LocationZ << Payload.RotationX << Payload.RotationY << Payload.RotationZ << Payload.ScaleX << Payload.ScaleY << Payload.ScaleZ; 56 | } 57 | }; 58 | 59 | struct FProductivityNetworkMessage 60 | { 61 | TEnumAsByte Type; 62 | FAddStaticMeshPayload Payload; 63 | 64 | FORCEINLINE friend FArchive& operator<<(FArchive& Ar, FProductivityNetworkMessage& NetworkMessage) 65 | { 66 | return Ar << NetworkMessage.Type << NetworkMessage.Payload; 67 | } 68 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProductivityPlugin 2 | Some tools and scripts that extend the functionality of UE4 3 | 4 | This repo is so unstable and nothing here is guaranteed to work, or to be documented. 5 | 6 | # What's Included? 7 | ## UE4 Plugin 8 | 1. Static Mesh Actor to Instanced Mesh Collection and vice versa 9 | 2. Ability to recieve batch placements of static meshes from any networked app (i.e. Maya) 10 | 11 | # Plugin Installation 12 | To build this plugin with your copy of UE4, place the repo so that all files are in Engine/Plugins/ProductivityPlugin. You should be able to regenerate your project files and build successfully. 13 | 14 | If you aren't going to be building this plugin from source code, download a binary version from the releases page and extract the ProductivityPlugin folder into Engine/Plugins/ 15 | 16 | After either option above, *it is very important* that you run the "Setup 3D Package Scripts.bat" file in Engine/Plugins/ProductivityPlugin in either case. 17 | 18 | # 3D Package Scripts 19 | To use Productivity Plugin to its max potential, you will need to install these scripts: 20 | 21 | ## Maya 22 | 1. Make sure you ran the "Setup 3D Package Scripts.bat" file as mentioned above. 23 | 2. Shove [this code](Maya/BatchPlacerShelfButton.py) into a Maya shelf button. This button, when clicked, will batch place selected objects into UE4. 24 | 25 | ## Max (Must be able to run Python, so 2014+) 26 | 1. Make sure you ran the "Setup 3D Package Scripts.bat" file as mentioned above. 27 | 2. Copy [LoadBatchPlacer.ms](Max/LoadBatchPlacer.ms) to your 3DS Max "Additional Startup Scripts" directory. This can be found by going to Customize->Configure System Paths 28 | 3. Use Customize->Customize Interface to assign the "Batch place selected objects in UE4" command under the "Productivity" category to anywhere on your interface or keyboard shortcuts. 29 | 30 | # Configuration 31 | To configure the Productivity Plugin, in UE4 go to Edit->Project Settings, then scroll down the left and click on Productivity Plugin. Here you can set settings as to enable the background Productivity Plugin server as well as set up Batch Placer settings. -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Classes/ProductivityPluginModule.h: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | 3 | #pragma once 4 | 5 | #include "ModuleManager.h" 6 | #include "Engine.h" 7 | #include "ProductivityTypes.h" 8 | 9 | class FProductivityTickObject : FTickableGameObject 10 | { 11 | public: 12 | FProductivityTickObject(class FProductivityPluginModule *_Owner); 13 | 14 | virtual void Tick(float DeltaTime); 15 | 16 | virtual bool IsTickable() const 17 | { 18 | return true; 19 | } 20 | virtual bool IsTickableWhenPaused() const 21 | { 22 | return true; 23 | } 24 | virtual bool IsTickableInEditor() const 25 | { 26 | return false; 27 | } 28 | virtual TStatId GetStatId() const override; 29 | 30 | private: 31 | class FProductivityPluginModule *Owner; 32 | }; 33 | 34 | class FProductivityPluginModule : public IModuleInterface 35 | { 36 | public: 37 | FProductivityPluginModule(); 38 | 39 | /** IModuleInterface implementation */ 40 | virtual void StartupModule() override; 41 | virtual void ShutdownModule() override; 42 | 43 | virtual bool SupportsDynamicReloading() override 44 | { 45 | return true; 46 | } 47 | 48 | void Tick(float DeltaTime); 49 | #if WITH_EDITOR 50 | /** Tools **/ 51 | 52 | /** This function will be bound to Command.*/ 53 | void StaticToInstancedClicked(); 54 | 55 | /** Server **/ 56 | bool HandleListenerConnectionAccepted(class FSocket* ClientSocket, const FIPv4Endpoint& ClientEndpoint); 57 | #endif 58 | 59 | protected: 60 | 61 | #if WITH_EDITOR 62 | void AddToolbarExtension(class FToolBarBuilder &); 63 | void AddMenuExtension(class FMenuBuilder &); 64 | void ProcessMessage(const FProductivityNetworkMessage& Message); 65 | void ProcessAddStaticMesh(const FProductivityNetworkMessage& Message); 66 | #endif 67 | 68 | // Callback for when the settings were saved. 69 | bool HandleSettingsSaved(); 70 | 71 | /** 72 | * Checks whether the Productivity Server is supported. 73 | * @return true if supported, false otherwise. 74 | */ 75 | bool SupportsProductivityServer() const; 76 | 77 | 78 | private: 79 | #if WITH_EDITOR 80 | TSharedPtr PluginCommands; 81 | 82 | FProductivityTickObject* TickObject; 83 | class FTcpListener *Listener; 84 | TQueue PendingClients; 85 | TArray Clients; 86 | #endif 87 | }; -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Private/ProductivityPluginStyle.cpp: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | #include "ProductivityPluginModulePCH.h" 3 | 4 | #include "ProductivityPluginStyle.h" 5 | #include "SlateGameResources.h" 6 | 7 | TSharedPtr< FSlateStyleSet > FProductivityPluginStyle::StyleInstance = NULL; 8 | 9 | void FProductivityPluginStyle::Initialize() 10 | { 11 | if (!StyleInstance.IsValid()) 12 | { 13 | StyleInstance = Create(); 14 | FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); 15 | } 16 | } 17 | 18 | void FProductivityPluginStyle::Shutdown() 19 | { 20 | FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); 21 | ensure(StyleInstance.IsUnique()); 22 | StyleInstance.Reset(); 23 | } 24 | 25 | FName FProductivityPluginStyle::GetStyleSetName() 26 | { 27 | static FName StyleSetName(TEXT("ProductivityPluginStyle")); 28 | return StyleSetName; 29 | } 30 | 31 | #define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) 32 | #define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) 33 | #define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) 34 | #define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) 35 | #define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) 36 | 37 | const FVector2D Icon16x16(16.0f, 16.0f); 38 | const FVector2D Icon20x20(20.0f, 20.0f); 39 | const FVector2D Icon40x40(40.0f, 40.0f); 40 | 41 | TSharedRef< FSlateStyleSet > FProductivityPluginStyle::Create() 42 | { 43 | TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("ProductivityPluginStyle")); 44 | Style->SetContentRoot(FPaths::EnginePluginsDir() / TEXT("ProductivityPlugin/Resources")); 45 | 46 | //Style->Set("ButtonIcon", new IMAGE_BRUSH(TEXT("ButtonIcon"), Icon40x40)); 47 | Style->Set("ProductivityPlugin.StaticToInstanced", new IMAGE_BRUSH(TEXT("StaticToInstanced"), Icon40x40)); 48 | Style->Set("ProductivityPlugin.StaticToInstanced.Small", new IMAGE_BRUSH(TEXT("StaticToInstanced"), Icon20x20)); 49 | 50 | return Style; 51 | } 52 | 53 | #undef IMAGE_BRUSH 54 | #undef BOX_BRUSH 55 | #undef BORDER_BRUSH 56 | #undef TTF_FONT 57 | #undef OTF_FONT 58 | 59 | void FProductivityPluginStyle::ReloadTextures() 60 | { 61 | FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); 62 | } 63 | 64 | const ISlateStyle& FProductivityPluginStyle::Get() 65 | { 66 | return *StyleInstance; 67 | } 68 | -------------------------------------------------------------------------------- /Maya/BatchPlacer.py: -------------------------------------------------------------------------------- 1 | import maya.OpenMaya as om 2 | import maya.cmds as cmds 3 | import math 4 | import socket 5 | import time 6 | import struct 7 | import unicodedata 8 | 9 | class BatchPlacer(): 10 | def packText(self, text): 11 | return struct.pack('I'+str(len(text)+1)+"s", len(text)+1, text) 12 | 13 | def SendUnrealBatchPlaceData(self, sock, objName, locx, locy, locz, rotx, roty, rotz, scalex, scaley, scalez): 14 | output = "\x01" + self.packText(objName) + self.packText(locx) + self.packText(locy) + self.packText(locz) + self.packText(rotx) + self.packText(roty) + self.packText(rotz) + self.packText(scalex) + self.packText(scaley) + self.packText(scalez) 15 | padlen = 1024-len(output) 16 | output += "\x00"*padlen 17 | sock.sendall(output) 18 | 19 | def exportSelected(self, sock): 20 | selection = om.MSelectionList() 21 | om.MGlobal.getActiveSelectionList(selection) 22 | selection_iter = om.MItSelectionList(selection) 23 | while not selection_iter.isDone(): 24 | obj = om.MObject() 25 | dagPath = om.MDagPath() 26 | selection_iter.getDependNode(obj) 27 | selection_iter.getDagPath(dagPath) 28 | node = om.MFnDependencyNode(obj) 29 | 30 | mt = om.MTransformationMatrix(dagPath.inclusiveMatrix()) 31 | loc = mt.translation(om.MSpace.kWorld) 32 | rot = mt.rotation().asEulerRotation() 33 | scaleUtil = om.MScriptUtil() 34 | scaleUtil.createFromList([0,0,0],3) 35 | scaleVec = scaleUtil.asDoublePtr() 36 | mt.getScale(scaleVec, om.MSpace.kWorld) 37 | scale = [om.MScriptUtil.getDoubleArrayItem(scaleVec, i) for i in range(0,3)] 38 | if (cmds.upAxis(q=True,axis=True) == "y"): 39 | self.SendUnrealBatchPlaceData(sock, unicodedata.normalize('NFKD', node.name()).encode('ascii','ignore'), str(loc.x), str(loc.z), str(loc.y), str(math.degrees(rot.x)), str(math.degrees(rot.z)), str(math.degrees(rot.y)*-1), str(scale[0]), str(scale[2]), str(scale[1])) 40 | else: 41 | self.SendUnrealBatchPlaceData(sock, unicodedata.normalize('NFKD', node.name()).encode('ascii','ignore'), str(loc.x), str(loc.y), str(loc.z), str(math.degrees(rot.x)), str(math.degrees(rot.y)), str(math.degrees(rot.z)), str(scale[0]), str(scale[1]), str(scale[2])) 42 | print "Sent mesh " + node.name() 43 | 44 | selection_iter.next() 45 | print "done sending" 46 | 47 | def main(): 48 | global placer 49 | global s 50 | placer = BatchPlacer() 51 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 52 | try: 53 | s.connect(('127.0.0.1',51234)) 54 | except Exception, e: 55 | cmds.error("Could not establish connection to UE4.") 56 | placer = None 57 | s.close() 58 | s = None 59 | 60 | 61 | placer.exportSelected(s) 62 | 63 | def batchPlaceSelected(): 64 | global placer 65 | global s 66 | if (placer != None and s != None): 67 | placer.exportSelected(s) 68 | else: 69 | main() 70 | 71 | if __name__ == "__main__": 72 | main() 73 | -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Private/ProductivityPluginCommands.cpp: -------------------------------------------------------------------------------- 1 | #include "ProductivityPluginModulePCH.h" 2 | #include "ProductivityPluginCommands.h" 3 | #include "ProductivityPluginEditorSettings.h" 4 | 5 | TSharedPtr FProductivityPluginCommands::GlobalStaticToInstancedActions; 6 | 7 | FProductivityPluginCommands::FProductivityPluginCommands() : TCommands(TEXT("ProductivityPlugin"), NSLOCTEXT("Contexts", "ProductivityPlugin", "ProductivityPlugin Plugin"), NAME_None, FProductivityPluginStyle::GetStyleSetName()) 8 | { 9 | 10 | } 11 | 12 | #if WITH_EDITOR 13 | 14 | #define LOCTEXT_NAMESPACE "" 15 | 16 | void FProductivityPluginCommands::RegisterCommands() 17 | { 18 | UI_COMMAND(StaticToInstanced, "Statics<>Instanced", "Batch converts all selected static mesh actors to instanced meshes and vice versa.", EUserInterfaceActionType::Button, FInputGesture()); 19 | UI_COMMAND(StaticToInstancedIsResultGrouped, "Group Static Meshes", "If checked, when static meshes are created from an instanced mesh wrapper, they will be grouped.", EUserInterfaceActionType::ToggleButton, FInputGesture()); 20 | } 21 | 22 | #undef LOCTEXT_NAMESPACE 23 | 24 | TSharedRef< SWidget > FProductivityPluginCommands::GenerateStaticToInstancedMenuContent(TSharedRef InCommandList) 25 | { 26 | const bool bShouldCloseWindowAfterMenuSelection = true; 27 | FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, InCommandList); 28 | 29 | MenuBuilder.BeginSection("StaticToInstancedOptions"); 30 | { 31 | MenuBuilder.AddMenuEntry(FProductivityPluginCommands::Get().StaticToInstancedIsResultGrouped); 32 | } 33 | MenuBuilder.EndSection(); 34 | 35 | return MenuBuilder.MakeWidget(); 36 | } 37 | 38 | void FProductivityPluginCommands::BindGlobalStaticToInstancedActions() 39 | { 40 | check(!GlobalStaticToInstancedActions.IsValid()); 41 | 42 | GlobalStaticToInstancedActions = MakeShareable(new FUICommandList); 43 | 44 | const FProductivityPluginCommands& Commands = FProductivityPluginCommands::Get(); 45 | FUICommandList& ActionList = *GlobalStaticToInstancedActions; 46 | 47 | ActionList.MapAction( 48 | FProductivityPluginCommands::Get().StaticToInstancedIsResultGrouped, 49 | FExecuteAction::CreateStatic(&FProductivityPluginCommandCallbacks::OnToggleStaticToInstancedResultGrouped), 50 | FCanExecuteAction(), 51 | FIsActionChecked::CreateStatic(&FProductivityPluginCommandCallbacks::OnToggleStaticToInstancedResultGroupedEnabled) 52 | ); 53 | } 54 | 55 | #endif 56 | 57 | void FProductivityPluginCommandCallbacks::OnToggleStaticToInstancedResultGrouped() 58 | { 59 | UProductivityPluginEditorSettings* PlayInSettings = GetMutableDefault(); 60 | PlayInSettings->SetGroupStaticToInstancedResults(!PlayInSettings->GetGroupStaticToInstancedResults()); 61 | } 62 | 63 | bool FProductivityPluginCommandCallbacks::OnToggleStaticToInstancedResultGroupedEnabled() 64 | { 65 | UProductivityPluginEditorSettings* PlayInSettings = GetMutableDefault(); 66 | return PlayInSettings->GetGroupStaticToInstancedResults(); 67 | } 68 | -------------------------------------------------------------------------------- /Source/ProductivityPlugin/Private/ProductivityPluginModule.cpp: -------------------------------------------------------------------------------- 1 | #include "ProductivityPluginModulePCH.h" 2 | 3 | #if WITH_EDITOR 4 | #include "ISettingsModule.h" 5 | #include "ISettingsSection.h" 6 | 7 | #include "SlateBasics.h" 8 | #include "SlateExtras.h" 9 | 10 | #include "ProductivityPluginStyle.h" 11 | #include "ProductivityPluginCommands.h" 12 | 13 | #include "ILayers.h" 14 | #include "LevelEditor.h" 15 | #include "ScopedTransaction.h" 16 | #endif 17 | 18 | #include "ProductivityTypes.h" 19 | 20 | static const FName ProductivityPluginTabName("ProductivityPlugin"); 21 | 22 | #define LOCTEXT_NAMESPACE "ProductivityPlugin" 23 | 24 | /** Tick Object **/ 25 | FProductivityTickObject::FProductivityTickObject(FProductivityPluginModule *_Owner) 26 | : Owner(_Owner) 27 | { 28 | } 29 | 30 | void FProductivityTickObject::Tick(float DeltaTime) 31 | { 32 | check(Owner != NULL); 33 | Owner->Tick(DeltaTime); 34 | } 35 | 36 | TStatId FProductivityTickObject::GetStatId() const 37 | { 38 | RETURN_QUICK_DECLARE_CYCLE_STAT(FProductivityPluginModule, STATGROUP_Tickables); 39 | } 40 | 41 | /** Module **/ 42 | FProductivityPluginModule::FProductivityPluginModule() 43 | #if WITH_EDITOR 44 | : TickObject(nullptr), 45 | Listener(nullptr) 46 | #endif 47 | { 48 | 49 | } 50 | 51 | void FProductivityPluginModule::StartupModule() 52 | { 53 | // This code will execute after your module is loaded into memory (but after global variables are initialized, of course.) 54 | 55 | FProductivityPluginStyle::Initialize(); 56 | //FProductivityPluginStyle::ReloadTextures(); 57 | 58 | #if WITH_EDITOR 59 | 60 | FProductivityPluginCommands::Register(); 61 | FProductivityPluginCommands::BindGlobalStaticToInstancedActions(); 62 | 63 | 64 | 65 | // register settings 66 | ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings"); 67 | 68 | if (SettingsModule != nullptr) 69 | { 70 | ISettingsSectionPtr SettingsSection = SettingsModule->RegisterSettings("Project", "Plugins", "ProductivityPlugin", 71 | LOCTEXT("ProductivitySettingsName", "Productivity Plugin"), 72 | LOCTEXT("ProductivitySettingsDescription", "Configure the Productivity Plugin."), 73 | GetMutableDefault() 74 | ); 75 | 76 | if (SettingsSection.IsValid()) 77 | { 78 | SettingsSection->OnModified().BindRaw(this, &FProductivityPluginModule::HandleSettingsSaved); 79 | } 80 | } 81 | 82 | PluginCommands = MakeShareable(new FUICommandList); 83 | 84 | PluginCommands->MapAction( 85 | FProductivityPluginCommands::Get().StaticToInstanced, 86 | FExecuteAction::CreateRaw(this, &FProductivityPluginModule::StaticToInstancedClicked), 87 | FCanExecuteAction()); 88 | 89 | FLevelEditorModule* LevelEditorModule = FModuleManager::GetModulePtr("LevelEditor"); 90 | if (LevelEditorModule != nullptr) 91 | { 92 | { 93 | TSharedPtr MenuExtender = MakeShareable(new FExtender()); 94 | MenuExtender->AddMenuExtension("WindowLayout", EExtensionHook::After, PluginCommands, FMenuExtensionDelegate::CreateRaw(this, &FProductivityPluginModule::AddMenuExtension)); 95 | 96 | LevelEditorModule->GetMenuExtensibilityManager()->AddExtender(MenuExtender); 97 | } 98 | 99 | { 100 | TSharedPtr ToolbarExtender = MakeShareable(new FExtender); 101 | ToolbarExtender->AddToolBarExtension("Game", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FProductivityPluginModule::AddToolbarExtension)); 102 | 103 | LevelEditorModule->GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); 104 | } 105 | 106 | if (SupportsProductivityServer()) 107 | { 108 | Listener = new FTcpListener(PRODUCTIVITY_SERVER_DEFAULT_EDITOR_ENDPOINT); 109 | Listener->OnConnectionAccepted().BindRaw(this, &FProductivityPluginModule::HandleListenerConnectionAccepted); 110 | 111 | TickObject = new FProductivityTickObject(this); 112 | } 113 | } 114 | 115 | #endif 116 | 117 | } 118 | 119 | void FProductivityPluginModule::ShutdownModule() 120 | { 121 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 122 | // we call this function before unloading the module. 123 | 124 | FProductivityPluginStyle::Shutdown(); 125 | 126 | #if WITH_EDITOR 127 | 128 | FProductivityPluginCommands::Unregister(); 129 | 130 | // unregister settings 131 | ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings"); 132 | 133 | if (SettingsModule != nullptr) 134 | { 135 | SettingsModule->UnregisterSettings("Project", "Plugins", "ProductivityPlugin"); 136 | } 137 | 138 | if (Listener) 139 | { 140 | Listener->Stop(); 141 | delete Listener; 142 | Listener = NULL; 143 | } 144 | 145 | if (!PendingClients.IsEmpty()) 146 | { 147 | FSocket *Client = NULL; 148 | while (PendingClients.Dequeue(Client)) 149 | { 150 | Client->Close(); 151 | } 152 | } 153 | for (TArray::TIterator ClientIt(Clients); ClientIt; ++ClientIt) 154 | { 155 | (*ClientIt)->Close(); 156 | } 157 | 158 | delete TickObject; 159 | TickObject = NULL; 160 | #endif 161 | } 162 | #if WITH_EDITOR 163 | void FProductivityPluginModule::StaticToInstancedClicked() 164 | { 165 | 166 | const FScopedTransaction Transaction(LOCTEXT("StaticToInstanced", "Convert Statics to Instances and back")); 167 | 168 | { 169 | 170 | /* Set up selected info */ 171 | TArray SelectedSMAs; 172 | USelection* SMASelection = GEditor->GetSelectedSet(AStaticMeshActor::StaticClass()); 173 | SMASelection->GetSelectedObjects(SelectedSMAs); 174 | 175 | TArray SelectedIMWs; 176 | USelection* IMWSelection = GEditor->GetSelectedSet(AInstancedMeshWrapper::StaticClass()); 177 | IMWSelection->GetSelectedObjects(SelectedIMWs); 178 | 179 | SMASelection->Modify(); 180 | IMWSelection->Modify(); 181 | 182 | GEditor->GetSelectedActors()->DeselectAll(); 183 | GEditor->GetSelectedObjects()->DeselectAll(); 184 | GEditor->SelectNone(true, true, false); 185 | GEditor->NoteSelectionChange(); 186 | 187 | /* Static Mesh to Instanced */ 188 | TArray MeshInfos; 189 | TArray< TArray > Transforms; 190 | 191 | for (AStaticMeshActor* MeshActor : SelectedSMAs) 192 | { 193 | FMeshInfo info; 194 | info.StaticMesh = MeshActor->GetStaticMeshComponent()->StaticMesh; 195 | MeshActor->GetStaticMeshComponent()->GetUsedMaterials(info.Materials); 196 | 197 | int32 idx = 0; 198 | 199 | if (MeshInfos.Find(info, idx)) 200 | { 201 | Transforms[idx].Add(MeshActor->GetTransform()); 202 | } 203 | else 204 | { 205 | TArray newTransformArray; 206 | newTransformArray.Add(MeshActor->GetTransform()); 207 | MeshInfos.Add(info); 208 | Transforms.Add(newTransformArray); 209 | } 210 | } 211 | 212 | for (int i = 0; i < SelectedSMAs.Num(); ++i) 213 | { 214 | SelectedSMAs[i]->GetLevel()->Modify(); 215 | GEditor->Layers->DisassociateActorFromLayers(SelectedSMAs[i]); 216 | SelectedSMAs[i]->GetWorld()->EditorDestroyActor(SelectedSMAs[i], false); 217 | } 218 | 219 | SelectedSMAs.Empty(); 220 | 221 | for (int i = 0; i < MeshInfos.Num(); ++i) 222 | { 223 | AInstancedMeshWrapper* Wrapper = Cast(GEditor->AddActor(GEditor->LevelViewportClients[0]->GetWorld()->GetLevel(0), AInstancedMeshWrapper::StaticClass(), FTransform::Identity)); 224 | if (Wrapper) 225 | { 226 | Wrapper->Modify(); 227 | Wrapper->InstancedMeshes->SetStaticMesh(MeshInfos[i].StaticMesh); 228 | for (int j = 0; j < MeshInfos[i].Materials.Num(); ++j) 229 | { 230 | Wrapper->InstancedMeshes->SetMaterial(j, MeshInfos[i].Materials[j]); 231 | } 232 | 233 | for (FTransform aTransform : Transforms[i]) 234 | { 235 | Wrapper->InstancedMeshes->AddInstanceWorldSpace(aTransform); 236 | } 237 | } 238 | } 239 | 240 | /* Instanced To Static Mesh */ 241 | 242 | for (AInstancedMeshWrapper* IMW : SelectedIMWs) 243 | { 244 | int32 InstanceCount = IMW->InstancedMeshes->GetInstanceCount(); 245 | UStaticMesh* IMWMesh = IMW->InstancedMeshes->StaticMesh; 246 | UE_LOG(LogProductivityPlugin, Verbose, TEXT("IMW Mesh: %s"), *IMWMesh->GetFullName()); 247 | 248 | bool bGroupResultingMeshes = FProductivityPluginCommandCallbacks::OnToggleStaticToInstancedResultGroupedEnabled(); 249 | 250 | TArray ActorsToGroup; 251 | 252 | for (int i = 0; i < InstanceCount; ++i) 253 | { 254 | FTransform InstanceTransform; 255 | IMW->InstancedMeshes->GetInstanceTransform(i, InstanceTransform, true); 256 | 257 | AStaticMeshActor* SMA = Cast(GEditor->AddActor(GEditor->LevelViewportClients[0]->GetWorld()->GetLevel(0), AStaticMeshActor::StaticClass(), InstanceTransform)); 258 | SMA->Modify(); 259 | //@TODO: Figure out why editor is skipping names 260 | SMA->SetActorLabel(*IMWMesh->GetName()); 261 | SMA->SetMobility(EComponentMobility::Movable); 262 | SMA->GetStaticMeshComponent()->SetStaticMesh(IMWMesh); 263 | SMA->SetMobility(EComponentMobility::Static); 264 | 265 | TArray Materials; 266 | IMW->InstancedMeshes->GetUsedMaterials(Materials); 267 | 268 | for (int j = 0; j < Materials.Num(); ++j) 269 | { 270 | SMA->GetStaticMeshComponent()->SetMaterial(j, Materials[j]); 271 | } 272 | 273 | ActorsToGroup.Add(SMA); 274 | } 275 | 276 | if (bGroupResultingMeshes) 277 | { 278 | if (ActorsToGroup.Num() > 1) 279 | { 280 | 281 | // Store off the current level and make the level that contain the actors to group as the current level 282 | UWorld* World = ActorsToGroup[0]->GetWorld(); 283 | check(World); 284 | { 285 | FActorSpawnParameters SpawnInfo; 286 | SpawnInfo.OverrideLevel = GEditor->LevelViewportClients[0]->GetWorld()->GetLevel(0); 287 | AGroupActor* SpawnedGroupActor = World->SpawnActor(SpawnInfo); 288 | 289 | for (int32 ActorIndex = 0; ActorIndex < ActorsToGroup.Num(); ++ActorIndex) 290 | { 291 | SpawnedGroupActor->Add(*ActorsToGroup[ActorIndex]); 292 | } 293 | 294 | SpawnedGroupActor->CenterGroupLocation(); 295 | SpawnedGroupActor->bLocked = true; 296 | } 297 | } 298 | } 299 | 300 | IMW->Modify(); 301 | IMW->GetLevel()->Modify(); 302 | GEditor->Layers->DisassociateActorFromLayers(IMW); 303 | IMW->GetWorld()->EditorDestroyActor(IMW, false); 304 | } 305 | 306 | SelectedIMWs.Empty(); 307 | 308 | // Remove all references to destroyed actors once at the end, instead of once for each Actor destroyed.. 309 | CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); 310 | } 311 | } 312 | #endif 313 | 314 | #if WITH_EDITOR 315 | 316 | void FProductivityPluginModule::AddMenuExtension(FMenuBuilder& builder) 317 | { 318 | { 319 | 320 | //builder.AddMenuEntry( 321 | // FProductivityPluginCommands::Get().StaticToInstanced, 322 | // NAME_None, 323 | // FProductivityPluginCommands::Get().StaticToInstanced->GetLabel(), 324 | // FProductivityPluginCommands::Get().StaticToInstanced->GetDescription(), 325 | // FProductivityPluginCommands::Get().StaticToInstanced->GetIcon(), 326 | // NAME_None); 327 | } 328 | } 329 | 330 | 331 | 332 | void FProductivityPluginModule::AddToolbarExtension(FToolBarBuilder &builder) 333 | { 334 | builder.BeginSection("Productivity"); 335 | 336 | builder.AddToolBarButton( 337 | FProductivityPluginCommands::Get().StaticToInstanced, 338 | NAME_None, 339 | FProductivityPluginCommands::Get().StaticToInstanced->GetLabel(), 340 | FProductivityPluginCommands::Get().StaticToInstanced->GetDescription(), 341 | FProductivityPluginCommands::Get().StaticToInstanced->GetIcon(), 342 | NAME_None); 343 | 344 | FUIAction StaticToInstancedOptionsMenuAction; 345 | 346 | builder.AddComboButton( 347 | StaticToInstancedOptionsMenuAction, 348 | FOnGetContent::CreateStatic(&FProductivityPluginCommands::GenerateStaticToInstancedMenuContent, FProductivityPluginCommands::GlobalStaticToInstancedActions.ToSharedRef()), 349 | LOCTEXT("StaticToInstancedOptions_Label", "Static<>Instanced Options"), 350 | LOCTEXT("StaticToInstancedOptions_Tooltip", "Options for converting static meshes to instanced meshes and vice versa."), 351 | FProductivityPluginCommands::Get().StaticToInstanced->GetIcon(), 352 | true 353 | ); 354 | 355 | builder.EndSection(); 356 | } 357 | 358 | #endif 359 | 360 | bool FProductivityPluginModule::SupportsProductivityServer() const 361 | { 362 | // disallow in Shipping and Test configurations 363 | if ((FApp::GetBuildConfiguration() == EBuildConfigurations::Shipping) || (FApp::GetBuildConfiguration() == EBuildConfigurations::Test)) 364 | { 365 | return false; 366 | } 367 | 368 | // disallow for commandlets 369 | if (IsRunningCommandlet()) 370 | { 371 | return false; 372 | } 373 | 374 | if (GEngine->IsEditor()) 375 | { 376 | return true; 377 | } 378 | 379 | return false; 380 | } 381 | 382 | bool FProductivityPluginModule::HandleSettingsSaved() 383 | { 384 | return true; 385 | } 386 | 387 | #if WITH_EDITOR 388 | bool FProductivityPluginModule::HandleListenerConnectionAccepted(class FSocket* ClientSocket, const FIPv4Endpoint& ClientEndpoint) 389 | { 390 | PendingClients.Enqueue(ClientSocket); 391 | return true; 392 | } 393 | #endif 394 | 395 | void FProductivityPluginModule::Tick(float DeltaTime) 396 | { 397 | #if WITH_EDITOR 398 | if (!PendingClients.IsEmpty()) 399 | { 400 | FSocket *Client = NULL; 401 | while (PendingClients.Dequeue(Client)) 402 | { 403 | Clients.Add(Client); 404 | } 405 | } 406 | 407 | // remove closed connections 408 | for (int32 ClientIndex = Clients.Num() - 1; ClientIndex >= 0; --ClientIndex) 409 | { 410 | if (Clients[ClientIndex]->GetConnectionState() != SCS_Connected) 411 | { 412 | Clients.RemoveAtSwap(ClientIndex); 413 | } 414 | } 415 | 416 | //poll for data 417 | for (TArray::TIterator ClientIt(Clients); ClientIt; ++ClientIt) 418 | { 419 | FSocket *Client = *ClientIt; 420 | uint32 DataSize = 0; 421 | while (Client->HasPendingData(DataSize)) 422 | { 423 | FArrayReaderPtr Datagram = MakeShareable(new FArrayReader(true)); 424 | Datagram->SetNumUninitialized(FMath::Min(DataSize, 1024u)); 425 | int32 BytesRead = 0; 426 | if (Client->Recv(Datagram->GetData(), Datagram->Num(), BytesRead)) 427 | { 428 | FProductivityNetworkMessage Message; 429 | *Datagram << Message; 430 | 431 | ProcessMessage(Message); 432 | uint8 ack = 1; 433 | int32 sent; 434 | Client->Send(&ack, 1, sent); 435 | } 436 | } 437 | } 438 | #endif 439 | } 440 | 441 | #if WITH_EDITOR 442 | 443 | void FProductivityPluginModule::ProcessMessage(const FProductivityNetworkMessage& Message) 444 | { 445 | ProcessAddStaticMesh(Message); 446 | } 447 | 448 | void FProductivityPluginModule::ProcessAddStaticMesh(const FProductivityNetworkMessage& Message) 449 | { 450 | UE_LOG(LogProductivityPlugin, Verbose, TEXT("Recieved Producitivty Message: Static mesh %s"), *Message.Payload.OriginalSceneName); 451 | 452 | const UProductivitySettings* ProductivitySettings = GetDefault(); 453 | for (FBatchPlaceMeshInfo BatchMeshInfo : ProductivitySettings->BatchPlaceSettings) 454 | { 455 | if (Message.Payload.OriginalSceneName.Contains(BatchMeshInfo.ImportNameSubstring) && BatchMeshInfo.MeshInfo.StaticMesh != nullptr) 456 | { 457 | const FScopedTransaction Transaction(LOCTEXT("BatchPlaceAddMesh", "Added mesh from Batch Placer")); 458 | 459 | FVector Location = FVector(FCString::Atof(*Message.Payload.LocationX), FCString::Atof(*Message.Payload.LocationY), FCString::Atof(*Message.Payload.LocationZ)); 460 | FVector Scale = FVector(FCString::Atof(*Message.Payload.ScaleX), FCString::Atof(*Message.Payload.ScaleY), FCString::Atof(*Message.Payload.ScaleZ)); 461 | FRotator Rotation = FRotator::MakeFromEuler(FVector(FCString::Atof(*Message.Payload.RotationX), FCString::Atof(*Message.Payload.RotationY), FCString::Atof(*Message.Payload.RotationZ))); 462 | FTransform Transform = FTransform(Rotation, Location, Scale); 463 | 464 | AStaticMeshActor* SMA = Cast(GEditor->AddActor(GEditor->LevelViewportClients[0]->GetWorld()->GetLevel(0), AStaticMeshActor::StaticClass(), Transform)); 465 | SMA->Modify(); 466 | //@TODO: Figure out why editor is skipping names 467 | SMA->SetActorLabel(BatchMeshInfo.ImportNameSubstring); 468 | SMA->SetMobility(EComponentMobility::Movable); 469 | SMA->GetStaticMeshComponent()->SetStaticMesh(BatchMeshInfo.MeshInfo.StaticMesh); 470 | SMA->SetMobility(EComponentMobility::Static); 471 | 472 | for (int i = 0; i < BatchMeshInfo.MeshInfo.Materials.Num(); ++i) 473 | { 474 | SMA->GetStaticMeshComponent()->SetMaterial(i, BatchMeshInfo.MeshInfo.Materials[i]); 475 | } 476 | break; 477 | } 478 | } 479 | } 480 | #endif 481 | 482 | DEFINE_LOG_CATEGORY(LogProductivityPlugin) 483 | 484 | #undef LOCTEXT_NAMESPACE 485 | 486 | IMPLEMENT_MODULE(FProductivityPluginModule, ProductivityPlugin) 487 | --------------------------------------------------------------------------------